Skip to content

Commit

Permalink
implement SupportsSignatureRSAPKCS1
Browse files Browse the repository at this point in the history
  • Loading branch information
qmuntal committed Dec 13, 2024
1 parent 65f2a3a commit 97c0d2c
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 70 deletions.
107 changes: 55 additions & 52 deletions evp.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,78 +89,77 @@ func hashFuncToMD(fn func() hash.Hash) (C.GO_EVP_MD_PTR, error) {
return md, nil
}

// cryptoHashToMD converts a crypto.Hash to a GO_EVP_MD_PTR.
func cryptoHashToMD(ch crypto.Hash) (md C.GO_EVP_MD_PTR) {
// cryptoHashToMD converts a crypto.Hash to a EVP_MD.
func cryptoHashToMD(ch crypto.Hash) C.GO_EVP_MD_PTR {
if v, ok := cacheMD.Load(ch); ok {
return v.(C.GO_EVP_MD_PTR)
}
defer func() {
if md != nil {
switch vMajor {
case 1:
// On OpenSSL 1 EVP_MD objects can be not-nil even
// when they are not supported. We need to pass the md
// to a EVP_MD_CTX to really know if they can be used.
ctx := C.go_openssl_EVP_MD_CTX_new()
if C.go_openssl_EVP_DigestInit_ex(ctx, md, nil) != 1 {
md = nil
}
C.go_openssl_EVP_MD_CTX_free(ctx)
case 3:
// On OpenSSL 3, directly operating on a EVP_MD object
// not created by EVP_MD_fetch has negative performance
// implications, as digest operations will have
// to fetch it on every call. Better to just fetch it once here.
md = C.go_openssl_EVP_MD_fetch(nil, C.go_openssl_EVP_MD_get0_name(md), nil)
default:
panic(errUnsupportedVersion())
}
}
cacheMD.Store(ch, md)
}()
// SupportsHash returns false for MD5SHA1 because we don't
// provide a hash.Hash implementation for it. Yet, it can
// still be used when signing/verifying with an RSA key.
if ch == crypto.MD5SHA1 {
if vMajor == 1 && vMinor == 0 {
return C.go_openssl_EVP_md5_sha1_backport()
} else {
return C.go_openssl_EVP_md5_sha1()
}
}
var md C.GO_EVP_MD_PTR
switch ch {
case crypto.RIPEMD160:
md = C.go_openssl_EVP_ripemd160()
case crypto.MD4:
return C.go_openssl_EVP_md4()
md = C.go_openssl_EVP_md4()
case crypto.MD5:
return C.go_openssl_EVP_md5()
md = C.go_openssl_EVP_md5()
case crypto.MD5SHA1:
if vMajor == 1 && vMinor == 0 {
md = C.go_openssl_EVP_md5_sha1_backport()
} else {
md = C.go_openssl_EVP_md5_sha1()
}
case crypto.SHA1:
return C.go_openssl_EVP_sha1()
md = C.go_openssl_EVP_sha1()
case crypto.SHA224:
return C.go_openssl_EVP_sha224()
md = C.go_openssl_EVP_sha224()
case crypto.SHA256:
return C.go_openssl_EVP_sha256()
md = C.go_openssl_EVP_sha256()
case crypto.SHA384:
return C.go_openssl_EVP_sha384()
md = C.go_openssl_EVP_sha384()
case crypto.SHA512:
return C.go_openssl_EVP_sha512()
md = C.go_openssl_EVP_sha512()
case crypto.SHA512_224:
if versionAtOrAbove(1, 1, 1) {
md = C.go_openssl_EVP_sha512_224()
}
case crypto.SHA512_256:
if versionAtOrAbove(1, 1, 1) {
md = C.go_openssl_EVP_sha512_256()
}
case crypto.SHA3_224:
if versionAtOrAbove(1, 1, 1) {
return C.go_openssl_EVP_sha3_224()
md = C.go_openssl_EVP_sha3_224()
}
case crypto.SHA3_256:
if versionAtOrAbove(1, 1, 1) {
return C.go_openssl_EVP_sha3_256()
md = C.go_openssl_EVP_sha3_256()
}
case crypto.SHA3_384:
if versionAtOrAbove(1, 1, 1) {
return C.go_openssl_EVP_sha3_384()
md = C.go_openssl_EVP_sha3_384()
}
case crypto.SHA3_512:
if versionAtOrAbove(1, 1, 1) {
return C.go_openssl_EVP_sha3_512()
md = C.go_openssl_EVP_sha3_512()
}
}
return nil
if md == nil {
cacheMD.Store(ch, nil)
}
if vMajor == 3 {
// On OpenSSL 3, directly operating on a EVP_MD object
// not created by EVP_MD_fetch has negative performance
// implications, as digest operations will have
// to fetch it on every call. Better to just fetch it once here.
md1 := C.go_openssl_EVP_MD_fetch(nil, C.go_openssl_EVP_MD_get0_name(md), nil)
// Don't overwrite md in case it can't be fetched, as the md may still be used
// outside of EVP_MD_CTX, for example to sign and verify RSA signatures.
if md1 != nil {
md = md1
}
}
cacheMD.Store(ch, md)
return md
}

// generateEVPPKey generates a new EVP_PKEY with the given id and properties.
Expand Down Expand Up @@ -218,6 +217,13 @@ type initFunc func(C.GO_EVP_PKEY_CTX_PTR) error
type cryptFunc func(C.GO_EVP_PKEY_CTX_PTR, *C.uchar, *C.size_t, *C.uchar, C.size_t) error
type verifyFunc func(C.GO_EVP_PKEY_CTX_PTR, *C.uchar, C.size_t, *C.uchar, C.size_t) error

func setRSAPAdding(ctx C.GO_EVP_PKEY_CTX_PTR, padding C.int) error {
if C.go_openssl_EVP_PKEY_CTX_ctrl(ctx, C.GO_EVP_PKEY_RSA, -1, C.GO_EVP_PKEY_CTRL_RSA_PADDING, padding, nil) != 1 {
return newOpenSSLError("EVP_PKEY_CTX_ctrl failed")
}
return nil
}

func setupEVP(withKey withKeyFunc, padding C.int,
h, mgfHash hash.Hash, label []byte, saltLen C.int, ch crypto.Hash,
init initFunc) (_ C.GO_EVP_PKEY_CTX_PTR, err error) {
Expand Down Expand Up @@ -246,10 +252,7 @@ func setupEVP(withKey withKeyFunc, padding C.int,
// Each padding type has its own requirements in terms of when to apply the padding,
// so it can't be just set at this point.
setPadding := func() error {
if C.go_openssl_EVP_PKEY_CTX_ctrl(ctx, C.GO_EVP_PKEY_RSA, -1, C.GO_EVP_PKEY_CTRL_RSA_PADDING, padding, nil) != 1 {
return newOpenSSLError("EVP_PKEY_CTX_ctrl failed")
}
return nil
return setRSAPAdding(ctx, padding)
}
switch padding {
case C.GO_RSA_PKCS1_OAEP_PADDING:
Expand Down
21 changes: 20 additions & 1 deletion hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,28 @@ func SHA512(p []byte) (sum [64]byte) {
return
}

// cacheHashSupported is a cache of crypto.Hash support.
var cacheHashSupported sync.Map

// SupportsHash returns true if a hash.Hash implementation is supported for h.
func SupportsHash(h crypto.Hash) bool {
return cryptoHashToMD(h) != nil
if v, ok := cacheHashSupported.Load(h); ok {
return v.(bool)
}
md := cryptoHashToMD(h)
if md == nil {
return false
}
// EVP_MD objects can be not-nil even when they can't be used
// in a EVP_MD_CTX, e.g. MD5 in FIPS mode. We need to prove
// if they can be used by passing them to a EVP_MD_CTX.
var supported bool
if ctx := C.go_openssl_EVP_MD_CTX_new(); ctx != nil {
supported = C.go_openssl_EVP_DigestInit_ex(ctx, md, nil) == 1
C.go_openssl_EVP_MD_CTX_free(ctx)
}
cacheHashSupported.Store(h, supported)
return supported
}

func SHA3_224(p []byte) (sum [28]byte) {
Expand Down
46 changes: 46 additions & 0 deletions rsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,55 @@ import (
"errors"
"hash"
"runtime"
"sync"
"unsafe"
)

var testRSAKey = sync.OnceValue(func() C.GO_EVP_PKEY_PTR {
pkey, err := generateEVPPKey(C.GO_EVP_PKEY_RSA, 512, "")
if err != nil {
// Try with a larger key.
pkey, _ = generateEVPPKey(C.GO_EVP_PKEY_RSA, 1024, "")
}
return pkey
})

var cachePKCS1Supported sync.Map

// SupportsSignatureRSAPKCS1v15 returns true if PKCS1 signatures are supported
// for the given hash.
func SupportsSignatureRSAPKCS1v15(ch crypto.Hash) (supported bool) {
if val, ok := cachePKCS1Supported.Load(ch); ok {
return val.(bool)
}
defer func() {
cachePKCS1Supported.Store(ch, supported)
}()
md := cryptoHashToMD(ch)
if ch != 0 && md == nil {
return false
}
pkey := testRSAKey()
if pkey == nil {
return false
}
ctx := C.go_openssl_EVP_PKEY_CTX_new(pkey, nil)
if ctx == nil {
return false
}
defer C.go_openssl_EVP_PKEY_CTX_free(ctx)
if C.go_openssl_EVP_PKEY_sign_init(ctx) != 1 {
return false
}
if C.go_openssl_EVP_PKEY_CTX_ctrl(ctx, -1, -1, C.GO_EVP_PKEY_CTRL_MD, 0, unsafe.Pointer(md)) != 1 {
return false
}
if setRSAPAdding(ctx, C.GO_RSA_PKCS1_PADDING) != nil {
return false
}
return true
}

func GenerateKeyRSA(bits int) (N, E, D, P, Q, Dp, Dq, Qinv BigInt, err error) {
bad := func(e error) (N, E, D, P, Q, Dp, Dq, Qinv BigInt, err error) {
return nil, nil, nil, nil, nil, nil, nil, nil, e
Expand Down
71 changes: 54 additions & 17 deletions rsa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,60 @@ func TestRSAEncryptDecryptOAEP_WrongLabel(t *testing.T) {
}

func TestRSASignVerifyPKCS1v15(t *testing.T) {
priv, pub := newRSAKey(t, 2048)
// These are all the hashes supported by Go's crypto/rsa package
// as off Go 1.24.
hashes := []crypto.Hash{
0,
crypto.MD5SHA1,
crypto.MD5,
crypto.SHA1,
crypto.SHA224,
crypto.SHA256,
crypto.SHA512,
crypto.SHA512_224,
crypto.SHA512_256,
crypto.SHA3_224,
crypto.SHA3_256,
crypto.SHA3_512,
crypto.RIPEMD160,
}
for _, hash := range hashes {
var name string
if hash == 0 {
name = "unhashed"
} else {
name = hash.String()
}
t.Run(name, func(t *testing.T) {
if !openssl.SupportsSignatureRSAPKCS1v15(hash) {
t.Skip("skipping test because hash is not supported")
}
// Construct a fake hashed data.
size := 1
if hash != 0 {
size = hash.Size()
}
hashed := make([]byte, size)
hashed[0] = 0x30
signed, err := openssl.SignRSAPKCS1v15(priv, hash, hashed)
if err != nil {
if openssl.FIPS() && (hash == 0 || hash == crypto.MD5SHA1 || hash == crypto.MD5 || hash == crypto.RIPEMD160) {
// This test is not supported in FIPS mode, but at least we
// can check that we don't panic.
t.Skip("skipping test in FIPS mode")
}
t.Fatal(err)
}
err = openssl.VerifyRSAPKCS1v15(pub, hash, hashed, signed)
if err != nil {
t.Fatal(err)
}
})
}
}

func TestRSAHashSignVerifyPKCS1v15(t *testing.T) {
sha256 := openssl.NewSHA256()
priv, pub := newRSAKey(t, 2048)
msg := []byte("hi!")
Expand All @@ -220,23 +274,6 @@ func TestRSASignVerifyPKCS1v15(t *testing.T) {
}
}

func TestRSASignVerifyPKCS1v15_Unhashed(t *testing.T) {
if openssl.SymCryptProviderAvailable() {
t.Skip("SymCrypt provider does not support unhashed PKCS1v15")
}

msg := []byte("hi!")
priv, pub := newRSAKey(t, 2048)
signed, err := openssl.SignRSAPKCS1v15(priv, 0, msg)
if err != nil {
t.Fatal(err)
}
err = openssl.VerifyRSAPKCS1v15(pub, 0, msg, signed)
if err != nil {
t.Fatal(err)
}
}

func TestRSASignVerifyPKCS1v15_Invalid(t *testing.T) {
sha256 := openssl.NewSHA256()
msg := []byte("hi!")
Expand Down
3 changes: 3 additions & 0 deletions shims.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,16 @@ DEFINEFUNC_LEGACY_1_0(int, SHA1_Init, (GO_SHA_CTX_PTR c), (c)) \
DEFINEFUNC_LEGACY_1_0(int, SHA1_Update, (GO_SHA_CTX_PTR c, const void *data, size_t len), (c, data, len)) \
DEFINEFUNC_LEGACY_1_0(int, SHA1_Final, (unsigned char *md, GO_SHA_CTX_PTR c), (md, c)) \
DEFINEFUNC_1_1(const GO_EVP_MD_PTR, EVP_md5_sha1, (void), ()) \
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_ripemd160, (void), ()) \
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_md4, (void), ()) \
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_md5, (void), ()) \
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_sha1, (void), ()) \
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_sha224, (void), ()) \
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_sha256, (void), ()) \
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_sha384, (void), ()) \
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_sha512, (void), ()) \
DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha512_224, (void), ()) \
DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha512_256, (void), ()) \
DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_224, (void), ()) \
DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_256, (void), ()) \
DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_384, (void), ()) \
Expand Down

0 comments on commit 97c0d2c

Please sign in to comment.