Skip to content

Commit 7b07994

Browse files
qmuntaldagood
andauthored
Support more hash types in RSA PKCS1v15 and PSS (#235)
* implement SupportsSignatureRSAPKCS1 * fix memory leak * fallback to SupportsHash * add TestSupportsSignatureRSAPKCS1v15 * simplify func * fix FIPS mode * small fixes * test RSA PSS * improve error handling * revert setPadding changes * fix cryptoHashToMD * Update rsa_test.go Co-authored-by: Davis Goodin <[email protected]> --------- Co-authored-by: Davis Goodin <[email protected]>
1 parent 65f2a3a commit 7b07994

File tree

4 files changed

+159
-67
lines changed

4 files changed

+159
-67
lines changed

Diff for: evp.go

+48-48
Original file line numberDiff line numberDiff line change
@@ -89,78 +89,78 @@ func hashFuncToMD(fn func() hash.Hash) (C.GO_EVP_MD_PTR, error) {
8989
return md, nil
9090
}
9191

92-
// cryptoHashToMD converts a crypto.Hash to a GO_EVP_MD_PTR.
93-
func cryptoHashToMD(ch crypto.Hash) (md C.GO_EVP_MD_PTR) {
92+
// cryptoHashToMD converts a crypto.Hash to a EVP_MD.
93+
func cryptoHashToMD(ch crypto.Hash) C.GO_EVP_MD_PTR {
9494
if v, ok := cacheMD.Load(ch); ok {
9595
return v.(C.GO_EVP_MD_PTR)
9696
}
97-
defer func() {
98-
if md != nil {
99-
switch vMajor {
100-
case 1:
101-
// On OpenSSL 1 EVP_MD objects can be not-nil even
102-
// when they are not supported. We need to pass the md
103-
// to a EVP_MD_CTX to really know if they can be used.
104-
ctx := C.go_openssl_EVP_MD_CTX_new()
105-
if C.go_openssl_EVP_DigestInit_ex(ctx, md, nil) != 1 {
106-
md = nil
107-
}
108-
C.go_openssl_EVP_MD_CTX_free(ctx)
109-
case 3:
110-
// On OpenSSL 3, directly operating on a EVP_MD object
111-
// not created by EVP_MD_fetch has negative performance
112-
// implications, as digest operations will have
113-
// to fetch it on every call. Better to just fetch it once here.
114-
md = C.go_openssl_EVP_MD_fetch(nil, C.go_openssl_EVP_MD_get0_name(md), nil)
115-
default:
116-
panic(errUnsupportedVersion())
117-
}
118-
}
119-
cacheMD.Store(ch, md)
120-
}()
121-
// SupportsHash returns false for MD5SHA1 because we don't
122-
// provide a hash.Hash implementation for it. Yet, it can
123-
// still be used when signing/verifying with an RSA key.
124-
if ch == crypto.MD5SHA1 {
125-
if vMajor == 1 && vMinor == 0 {
126-
return C.go_openssl_EVP_md5_sha1_backport()
127-
} else {
128-
return C.go_openssl_EVP_md5_sha1()
129-
}
130-
}
97+
var md C.GO_EVP_MD_PTR
13198
switch ch {
99+
case crypto.RIPEMD160:
100+
md = C.go_openssl_EVP_ripemd160()
132101
case crypto.MD4:
133-
return C.go_openssl_EVP_md4()
102+
md = C.go_openssl_EVP_md4()
134103
case crypto.MD5:
135-
return C.go_openssl_EVP_md5()
104+
md = C.go_openssl_EVP_md5()
105+
case crypto.MD5SHA1:
106+
if vMajor == 1 && vMinor == 0 {
107+
md = C.go_openssl_EVP_md5_sha1_backport()
108+
} else {
109+
md = C.go_openssl_EVP_md5_sha1()
110+
}
136111
case crypto.SHA1:
137-
return C.go_openssl_EVP_sha1()
112+
md = C.go_openssl_EVP_sha1()
138113
case crypto.SHA224:
139-
return C.go_openssl_EVP_sha224()
114+
md = C.go_openssl_EVP_sha224()
140115
case crypto.SHA256:
141-
return C.go_openssl_EVP_sha256()
116+
md = C.go_openssl_EVP_sha256()
142117
case crypto.SHA384:
143-
return C.go_openssl_EVP_sha384()
118+
md = C.go_openssl_EVP_sha384()
144119
case crypto.SHA512:
145-
return C.go_openssl_EVP_sha512()
120+
md = C.go_openssl_EVP_sha512()
121+
case crypto.SHA512_224:
122+
if versionAtOrAbove(1, 1, 1) {
123+
md = C.go_openssl_EVP_sha512_224()
124+
}
125+
case crypto.SHA512_256:
126+
if versionAtOrAbove(1, 1, 1) {
127+
md = C.go_openssl_EVP_sha512_256()
128+
}
146129
case crypto.SHA3_224:
147130
if versionAtOrAbove(1, 1, 1) {
148-
return C.go_openssl_EVP_sha3_224()
131+
md = C.go_openssl_EVP_sha3_224()
149132
}
150133
case crypto.SHA3_256:
151134
if versionAtOrAbove(1, 1, 1) {
152-
return C.go_openssl_EVP_sha3_256()
135+
md = C.go_openssl_EVP_sha3_256()
153136
}
154137
case crypto.SHA3_384:
155138
if versionAtOrAbove(1, 1, 1) {
156-
return C.go_openssl_EVP_sha3_384()
139+
md = C.go_openssl_EVP_sha3_384()
157140
}
158141
case crypto.SHA3_512:
159142
if versionAtOrAbove(1, 1, 1) {
160-
return C.go_openssl_EVP_sha3_512()
143+
md = C.go_openssl_EVP_sha3_512()
161144
}
162145
}
163-
return nil
146+
if md == nil {
147+
cacheMD.Store(ch, nil)
148+
return nil
149+
}
150+
if vMajor == 3 {
151+
// On OpenSSL 3, directly operating on a EVP_MD object
152+
// not created by EVP_MD_fetch has negative performance
153+
// implications, as digest operations will have
154+
// to fetch it on every call. Better to just fetch it once here.
155+
md1 := C.go_openssl_EVP_MD_fetch(nil, C.go_openssl_EVP_MD_get0_name(md), nil)
156+
// Don't overwrite md in case it can't be fetched, as the md may still be used
157+
// outside of EVP_MD_CTX, for example to sign and verify RSA signatures.
158+
if md1 != nil {
159+
md = md1
160+
}
161+
}
162+
cacheMD.Store(ch, md)
163+
return md
164164
}
165165

166166
// generateEVPPKey generates a new EVP_PKEY with the given id and properties.

Diff for: hash.go

+22-2
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,29 @@ func SHA512(p []byte) (sum [64]byte) {
7878
return
7979
}
8080

81-
// SupportsHash returns true if a hash.Hash implementation is supported for h.
81+
// cacheHashSupported is a cache of crypto.Hash support.
82+
var cacheHashSupported sync.Map
83+
84+
// SupportsHash reports whether the current OpenSSL version supports the given hash.
8285
func SupportsHash(h crypto.Hash) bool {
83-
return cryptoHashToMD(h) != nil
86+
if v, ok := cacheHashSupported.Load(h); ok {
87+
return v.(bool)
88+
}
89+
md := cryptoHashToMD(h)
90+
if md == nil {
91+
cacheHashSupported.Store(h, false)
92+
return false
93+
}
94+
// EVP_MD objects can be non-nil even when they can't be used
95+
// in a EVP_MD_CTX, e.g. MD5 in FIPS mode. We need to prove
96+
// if they can be used by passing them to a EVP_MD_CTX.
97+
var supported bool
98+
if ctx := C.go_openssl_EVP_MD_CTX_new(); ctx != nil {
99+
supported = C.go_openssl_EVP_DigestInit_ex(ctx, md, nil) == 1
100+
C.go_openssl_EVP_MD_CTX_free(ctx)
101+
}
102+
cacheHashSupported.Store(h, supported)
103+
return supported
84104
}
85105

86106
func SHA3_224(p []byte) (sum [28]byte) {

Diff for: rsa_test.go

+86-17
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"math/big"
99
"strconv"
10+
"strings"
1011
"testing"
1112

1213
"github.com/golang-fips/openssl/v2"
@@ -193,7 +194,61 @@ func TestRSAEncryptDecryptOAEP_WrongLabel(t *testing.T) {
193194
}
194195
}
195196

197+
// These are all the hashes supported by Go's crypto/rsa package
198+
// as of Go 1.24.
199+
var stdHashes = [...]crypto.Hash{
200+
crypto.MD5SHA1,
201+
crypto.MD5,
202+
crypto.SHA1,
203+
crypto.SHA224,
204+
crypto.SHA256,
205+
crypto.SHA512,
206+
crypto.SHA512_224,
207+
crypto.SHA512_256,
208+
crypto.SHA3_224,
209+
crypto.SHA3_256,
210+
crypto.SHA3_512,
211+
crypto.RIPEMD160,
212+
}
213+
196214
func TestRSASignVerifyPKCS1v15(t *testing.T) {
215+
priv, pub := newRSAKey(t, 2048)
216+
for _, hash := range append([]crypto.Hash{0}, stdHashes[:]...) {
217+
var name string
218+
if hash == 0 {
219+
name = "unhashed"
220+
} else {
221+
name = hash.String()
222+
}
223+
t.Run(name, func(t *testing.T) {
224+
if hash != 0 && !openssl.SupportsHash(hash) {
225+
t.Skip("skipping test because hash is not supported")
226+
}
227+
// Construct a fake hashed data.
228+
size := 1
229+
if hash != 0 {
230+
size = hash.Size()
231+
}
232+
hashed := make([]byte, size)
233+
hashed[0] = 0x30
234+
signed, err := openssl.SignRSAPKCS1v15(priv, hash, hashed)
235+
if err != nil {
236+
if strings.Contains(err.Error(), "invalid digest") || strings.Contains(err.Error(), "digest not allowed") {
237+
// Can happen if the hash is supported by EVP_MD_CTX but not by EVP_PKEY_CTX.
238+
// There is nothing we can do about it.
239+
t.Skip("skipping test because hash is not supported")
240+
}
241+
t.Fatal(err)
242+
}
243+
err = openssl.VerifyRSAPKCS1v15(pub, hash, hashed, signed)
244+
if err != nil {
245+
t.Fatal(err)
246+
}
247+
})
248+
}
249+
}
250+
251+
func TestRSAHashSignVerifyPKCS1v15(t *testing.T) {
197252
sha256 := openssl.NewSHA256()
198253
priv, pub := newRSAKey(t, 2048)
199254
msg := []byte("hi!")
@@ -220,23 +275,6 @@ func TestRSASignVerifyPKCS1v15(t *testing.T) {
220275
}
221276
}
222277

223-
func TestRSASignVerifyPKCS1v15_Unhashed(t *testing.T) {
224-
if openssl.SymCryptProviderAvailable() {
225-
t.Skip("SymCrypt provider does not support unhashed PKCS1v15")
226-
}
227-
228-
msg := []byte("hi!")
229-
priv, pub := newRSAKey(t, 2048)
230-
signed, err := openssl.SignRSAPKCS1v15(priv, 0, msg)
231-
if err != nil {
232-
t.Fatal(err)
233-
}
234-
err = openssl.VerifyRSAPKCS1v15(pub, 0, msg, signed)
235-
if err != nil {
236-
t.Fatal(err)
237-
}
238-
}
239-
240278
func TestRSASignVerifyPKCS1v15_Invalid(t *testing.T) {
241279
sha256 := openssl.NewSHA256()
242280
msg := []byte("hi!")
@@ -254,6 +292,37 @@ func TestRSASignVerifyPKCS1v15_Invalid(t *testing.T) {
254292
}
255293

256294
func TestRSASignVerifyRSAPSS(t *testing.T) {
295+
priv, pub := newRSAKey(t, 2048)
296+
for _, hash := range stdHashes {
297+
t.Run(hash.String(), func(t *testing.T) {
298+
if !openssl.SupportsHash(hash) {
299+
t.Skip("skipping test because hash is not supported")
300+
}
301+
// Construct a fake hashed data.
302+
size := 1
303+
if hash != 0 {
304+
size = hash.Size()
305+
}
306+
hashed := make([]byte, size)
307+
hashed[0] = 0x30
308+
signed, err := openssl.SignRSAPSS(priv, hash, hashed, rsa.PSSSaltLengthEqualsHash)
309+
if err != nil {
310+
if strings.Contains(err.Error(), "invalid digest") || strings.Contains(err.Error(), "digest not allowed") {
311+
// Can happen if the hash is supported by EVP_MD_CTX but not by EVP_PKEY_CTX.
312+
// There is nothing we can do about it.
313+
t.Skip("skipping test because hash is not supported")
314+
}
315+
t.Fatal(err)
316+
}
317+
err = openssl.VerifyRSAPSS(pub, hash, hashed, signed, rsa.PSSSaltLengthEqualsHash)
318+
if err != nil {
319+
t.Fatal(err)
320+
}
321+
})
322+
}
323+
}
324+
325+
func TestRSASignVerifyRSAPSS_SaltLength(t *testing.T) {
257326
// Test cases taken from
258327
// https://github.com/golang/go/blob/54182ff54a687272dd7632c3a963e036ce03cb7c/src/crypto/rsa/pss_test.go#L200.
259328
const keyBits = 2048

Diff for: shims.h

+3
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,16 @@ DEFINEFUNC_LEGACY_1_0(int, SHA1_Init, (GO_SHA_CTX_PTR c), (c)) \
237237
DEFINEFUNC_LEGACY_1_0(int, SHA1_Update, (GO_SHA_CTX_PTR c, const void *data, size_t len), (c, data, len)) \
238238
DEFINEFUNC_LEGACY_1_0(int, SHA1_Final, (unsigned char *md, GO_SHA_CTX_PTR c), (md, c)) \
239239
DEFINEFUNC_1_1(const GO_EVP_MD_PTR, EVP_md5_sha1, (void), ()) \
240+
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_ripemd160, (void), ()) \
240241
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_md4, (void), ()) \
241242
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_md5, (void), ()) \
242243
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_sha1, (void), ()) \
243244
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_sha224, (void), ()) \
244245
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_sha256, (void), ()) \
245246
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_sha384, (void), ()) \
246247
DEFINEFUNC(const GO_EVP_MD_PTR, EVP_sha512, (void), ()) \
248+
DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha512_224, (void), ()) \
249+
DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha512_256, (void), ()) \
247250
DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_224, (void), ()) \
248251
DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_256, (void), ()) \
249252
DEFINEFUNC_1_1_1(const GO_EVP_MD_PTR, EVP_sha3_384, (void), ()) \

0 commit comments

Comments
 (0)