From f217feb5467f5d4cc585f9c0b42c341e77bc11f8 Mon Sep 17 00:00:00 2001 From: Tom Stellard Date: Sep 12 2019 17:30:40 +0000 Subject: Allow pre-hashed inputs to some crypto APIs This change is not FIPS compliant but will allow existing applications to continue to run correctly while they migrate to using the crypto APIs in a FIPS compliant manner. A warning message will be printed when pre-hashed inputs are used to help users update their applications. Applications can set the GOLANG_STRICT_FIPS=1 environment variable to restore previous behavior and cause the crypto module to reject pre-hashed inputs. --- diff --git a/src/crypto/ecdsa/ecdsa.go b/src/crypto/ecdsa/ecdsa.go index 90f1ff2..14325a2 100644 --- a/src/crypto/ecdsa/ecdsa.go +++ b/src/crypto/ecdsa/ecdsa.go @@ -181,7 +181,12 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err randutil.MaybeReadByte(rand) if boring.Enabled() { - panic("ecdsa.Sign disabled in FIPS mode, use HashSign with raw message instead") + boring.PanicIfStrictFIPS("ecdsa.Sign disabled in FIPS mode, use HashSign with raw message instead") + b, err := boringPrivateKey(priv) + if err != nil { + return nil, nil, err + } + return boring.SignECDSA(b, hash, 0) } boring.UnreachableExceptTests() @@ -281,7 +286,12 @@ func HashSign(rand io.Reader, priv *PrivateKey, msg []byte, h crypto.Hash) (r, s // return value records whether the signature is valid. func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool { if boring.Enabled() { - panic("ecdsa.Verify disabled in FIPS mode, use HashVerify with raw message instead") + boring.PanicIfStrictFIPS("ecdsa.Verify disabled in FIPS mode, use HashVerify with raw message instead") + b, err := boringPublicKey(pub) + if err != nil { + return false + } + return boring.VerifyECDSA(b, hash, r, s, 0) } boring.UnreachableExceptTests() diff --git a/src/crypto/internal/boring/boring.go b/src/crypto/internal/boring/boring.go index 2799d9e..24da211 100644 --- a/src/crypto/internal/boring/boring.go +++ b/src/crypto/internal/boring/boring.go @@ -29,6 +29,13 @@ const ( // Enabled controls whether FIPS crypto is enabled. var enabled = false +// When this variable is true, the go crypto API will panic when a caller +// tries to use the API in a non-compliant manner. When this is false, the +// go crytpo API will allow existing go crypto APIs to be used even +// if they aren't FIPS compliant. However, all the unerlying crypto operations +// will still be done by OpenSSL. +var strictFIPS = false + func init() { runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -110,6 +117,14 @@ func UnreachableExceptTests() { } } +func PanicIfStrictFIPS(v interface{}) { + if os.Getenv("GOLANG_STRICT_FIPS") == "1" || strictFIPS { + panic(v) + } + print("Warning: Operation not allowed in FIPS mode: ") + println(v) +} + type fail string func (e fail) Error() string { return "boringcrypto: " + string(e) + " failed" } diff --git a/src/crypto/internal/boring/ecdsa.go b/src/crypto/internal/boring/ecdsa.go index 7c581f3..3e53b15 100644 --- a/src/crypto/internal/boring/ecdsa.go +++ b/src/crypto/internal/boring/ecdsa.go @@ -147,6 +147,14 @@ func SignECDSA(priv *PrivateKeyECDSA, hash []byte, h crypto.Hash) (r, s *big.Int func SignMarshalECDSA(priv *PrivateKeyECDSA, hash []byte, h crypto.Hash) ([]byte, error) { size := C._goboringcrypto_ECDSA_size(priv.key) sig := make([]byte, size) + if h == 0 { + var sigLen C.uint + if C._goboringcrypto_ECDSA_sign_nohash(base(hash), C.size_t(len(hash)), (*C.uint8_t)(unsafe.Pointer(&sig[0])), &sigLen, priv.key) == 0 { + return nil, fail("ECDSA_sign") + } + runtime.KeepAlive(priv) + return sig[:sigLen], nil + } md := cryptoHashToMD(h) if md == nil { panic("boring: invalid hash") @@ -167,6 +175,12 @@ func VerifyECDSA(pub *PublicKeyECDSA, msg []byte, r, s *big.Int, h crypto.Hash) if err != nil { return false } + if h == 0 { + ok := C._goboringcrypto_ECDSA_verify_nohash(base(msg), C.size_t(len(msg)), (*C.uint8_t)(unsafe.Pointer(&sig[0])), C.size_t(len(sig)), pub.key) > 0 + runtime.KeepAlive(pub) + return ok + } + md := cryptoHashToMD(h) if md == nil { panic("boring: invalid hash") diff --git a/src/crypto/internal/boring/goboringcrypto.h b/src/crypto/internal/boring/goboringcrypto.h index 785f98f..1e96758 100644 --- a/src/crypto/internal/boring/goboringcrypto.h +++ b/src/crypto/internal/boring/goboringcrypto.h @@ -51,6 +51,18 @@ return _g_internal_##func argscall; \ } +#define DEFINEOPENSSLFUNC(ret, func, args, argscall) \ + typedef ret(*_goboringcrypto_openssl_PTR_##func) args; \ + static _goboringcrypto_openssl_PTR_##func _g_openssl_##func = 0; \ + static inline ret _goboringcrypto_openssl_##func args \ + { \ + if (unlikely(!_g_openssl_##func)) \ + { \ + _g_openssl_##func = dlsym(handle, #func); \ + } \ + return _g_openssl_##func argscall; \ + } + #define DEFINEMACRO(ret, func, args, argscall) \ static inline ret _goboringcrypto_##func args \ { \ @@ -366,7 +378,19 @@ DEFINEFUNC(GO_ECDSA_SIG *, ECDSA_SIG_new, (void), ()) DEFINEFUNC(void, ECDSA_SIG_free, (GO_ECDSA_SIG * arg0), (arg0)) DEFINEFUNC(GO_ECDSA_SIG *, ECDSA_do_sign, (const uint8_t *arg0, size_t arg1, const GO_EC_KEY *arg2), (arg0, arg1, arg2)) DEFINEFUNC(int, ECDSA_do_verify, (const uint8_t *arg0, size_t arg1, const GO_ECDSA_SIG *arg2, const GO_EC_KEY *arg3), (arg0, arg1, arg2, arg3)) +/* We need to expose the ECDSA_{sign,verify} functions from OpenSSL in order to support + * applications that have not yet migrated to FIPS-certified APIs. Since we + * already define our own ECDSA_{sign,verify} function, we define this one with the + * openssl prefix so we don't have to change the existing function. + */ +DEFINEOPENSSLFUNC(int, ECDSA_sign, + (int arg0, const uint8_t *arg1, size_t arg2, uint8_t *arg3, unsigned int *arg4, const GO_EC_KEY *arg5), + (arg0, arg1, arg2, arg3, arg4, (GO_EC_KEY *)arg5)) DEFINEFUNC(size_t, ECDSA_size, (const GO_EC_KEY *arg0), (arg0)) +DEFINEOPENSSLFUNC(int, ECDSA_verify, + (int arg0, const uint8_t *arg1, size_t arg2, const uint8_t *arg3, size_t arg4, const GO_EC_KEY *arg5), + (arg0, arg1, arg2, arg3, arg4, (GO_EC_KEY *)arg5)) + DEFINEFUNCINTERNAL(EVP_MD_CTX*, EVP_MD_CTX_new, (void), ()) DEFINEFUNCINTERNAL(EVP_MD_CTX*, EVP_MD_CTX_create, (void), ()) @@ -425,7 +449,9 @@ static inline void _goboringcrypto_EVP_MD_CTX_free(EVP_MD_CTX *ctx) { } int _goboringcrypto_ECDSA_sign(EVP_MD *md, const uint8_t *arg1, size_t arg2, uint8_t *arg3, size_t *arg4, GO_EC_KEY *arg5); +int _goboringcrypto_ECDSA_sign_nohash(const uint8_t *arg1, size_t arg2, uint8_t *arg3, unsigned int *arg4, GO_EC_KEY *arg5); int _goboringcrypto_ECDSA_verify(EVP_MD *md, const uint8_t *arg1, size_t arg2, const uint8_t *arg3, size_t arg4, GO_EC_KEY *arg5); +int _goboringcrypto_ECDSA_verify_nohash(const uint8_t *arg1, size_t arg2, const uint8_t *arg3, size_t arg4, GO_EC_KEY *arg5); #include @@ -610,8 +636,11 @@ enum }; int _goboringcrypto_RSA_sign_pss_mgf1(GO_RSA *, size_t *out_len, uint8_t *out, size_t max_out, const uint8_t *in, size_t in_len, GO_EVP_MD *md, const GO_EVP_MD *mgf1_md, int salt_len); +int _goboringcrypto_RSA_sign_raw(GO_RSA *, size_t *out_len, uint8_t *out, size_t max_out, const uint8_t *in, size_t in_len, int padding); int _goboringcrypto_RSA_verify_pss_mgf1(GO_RSA *, const uint8_t *msg, size_t msg_len, GO_EVP_MD *md, const GO_EVP_MD *mgf1_md, int salt_len, const uint8_t *sig, size_t sig_len); +int _goboringcrypto_RSA_verify_raw(GO_RSA *, size_t *out_len, uint8_t *out, size_t max_out, const uint8_t *in, size_t in_len, int padding); + DEFINEFUNC(unsigned int, RSA_size, (const GO_RSA *arg0), (arg0)) DEFINEFUNC(int, RSA_check_key, (const GO_RSA *arg0), (arg0)) diff --git a/src/crypto/internal/boring/notboring.go b/src/crypto/internal/boring/notboring.go index 6eb015b..2d72ae0 100644 --- a/src/crypto/internal/boring/notboring.go +++ b/src/crypto/internal/boring/notboring.go @@ -29,6 +29,9 @@ func Unreachable() { // when BoringCrypto is in use. It is a no-op without BoringCrypto. func UnreachableExceptTests() {} +// This is a noop withotu BoringCrytpo. +func PanicIfStrictFIPS(v interface{}) {} + type randReader int func (randReader) Read(b []byte) (int, error) { panic("boringcrypto: not available") } diff --git a/src/crypto/internal/boring/openssl_ecdsa_signature.c b/src/crypto/internal/boring/openssl_ecdsa_signature.c index b208516..fb7b989 100644 --- a/src/crypto/internal/boring/openssl_ecdsa_signature.c +++ b/src/crypto/internal/boring/openssl_ecdsa_signature.c @@ -16,6 +16,12 @@ _goboringcrypto_ECDSA_sign(EVP_MD* md, const uint8_t *msg, size_t msgLen, uint8_ } int +_goboringcrypto_ECDSA_sign_nohash(const uint8_t *msg, size_t msgLen, uint8_t *sig, unsigned int *slen, GO_EC_KEY *eckey) +{ + return _goboringcrypto_openssl_ECDSA_sign(0, msg, msgLen, sig, slen, eckey); +} + +int _goboringcrypto_ECDSA_verify(EVP_MD* md, const uint8_t *msg, size_t msgLen, const uint8_t *sig, size_t slen, GO_EC_KEY *eckey) { @@ -25,3 +31,9 @@ _goboringcrypto_ECDSA_verify(EVP_MD* md, const uint8_t *msg, size_t msgLen, cons return _goboringcrypto_EVP_verify(md, NULL, msg, msgLen, sig, slen, key); } + +int +_goboringcrypto_ECDSA_verify_nohash(const uint8_t *msg, size_t msgLen, const uint8_t *sig, size_t slen, GO_EC_KEY *eckey) +{ + return _goboringcrypto_openssl_ECDSA_verify(0, msg, msgLen, sig, slen, eckey); +} diff --git a/src/crypto/internal/boring/openssl_port_rsa.c b/src/crypto/internal/boring/openssl_port_rsa.c index 94a8dc4..a5a76fe 100644 --- a/src/crypto/internal/boring/openssl_port_rsa.c +++ b/src/crypto/internal/boring/openssl_port_rsa.c @@ -8,6 +8,22 @@ #include "goboringcrypto.h" // Only in BoringSSL. +int +_goboringcrypto_RSA_verify_raw(GO_RSA *rsa, size_t *out_len, uint8_t *out, + size_t max_out, + const uint8_t *in, size_t in_len, int padding) +{ + if (max_out < _goboringcrypto_RSA_size(rsa)) { + return 0; + } + int ret = _goboringcrypto_RSA_public_decrypt (in_len, in, out, rsa, padding); + if (ret <= 0) { + return 0; + } +} + + +// Only in BoringSSL. int _goboringcrypto_RSA_generate_key_fips(GO_RSA *rsa, int size, GO_BN_GENCB *cb) { // BoringSSL's RSA_generate_key_fips hard-codes e to 65537. @@ -71,6 +87,17 @@ err: return ret; } +int _goboringcrypto_RSA_sign_raw(GO_RSA *rsa, size_t *out_len, uint8_t *out, size_t max_out, + const uint8_t *in, size_t in_len, int padding) { + if (max_out < _goboringcrypto_RSA_size(rsa)) + return 0; + int ret = _goboringcrypto_RSA_private_encrypt (in_len, in, out, rsa, padding); + if (ret <= 0) + return 0; + *out_len = ret; + return 1; +} + int _goboringcrypto_RSA_verify_pss_mgf1(RSA *rsa, const uint8_t *msg, size_t msg_len, EVP_MD *md, const EVP_MD *mgf1_md, int salt_len, const uint8_t *sig, size_t sig_len) { diff --git a/src/crypto/internal/boring/rsa.go b/src/crypto/internal/boring/rsa.go index 8966465..cc83689 100644 --- a/src/crypto/internal/boring/rsa.go +++ b/src/crypto/internal/boring/rsa.go @@ -14,6 +14,7 @@ package boring import "C" import ( "crypto" + "crypto/subtle" "errors" "hash" "math/big" @@ -291,6 +292,16 @@ func VerifyRSAPSS(pub *PublicKeyRSA, h crypto.Hash, hashed, sig []byte, saltLen func SignRSAPKCS1v15(priv *PrivateKeyRSA, h crypto.Hash, msg []byte) ([]byte, error) { out := make([]byte, C._goboringcrypto_RSA_size(priv.key)) + if h == 0 { + // No hashing. + PanicIfStrictFIPS("You must provide raw message and a hash algorithm for PKCS1v15 signing") + var outLen C.size_t + if C._goboringcrypto_RSA_sign_raw(priv.key, &outLen, base(out), C.size_t(len(out)), base(msg), C.size_t(len(msg)), C.GO_RSA_PKCS1_PADDING) == 0 { + return nil, fail("RSA_sign_raw") + } + runtime.KeepAlive(priv) + return out[:outLen], nil + } md := cryptoHashToMD(h) if md == nil { return nil, errors.New("crypto/rsa: unsupported hash function: " + strconv.Itoa(int(h))) @@ -312,6 +323,20 @@ func VerifyRSAPKCS1v15(pub *PublicKeyRSA, h crypto.Hash, msg, sig []byte) error sig = zsig } + if h == 0 { + PanicIfStrictFIPS("You must provide raw message and a hash algorithm for PKCS1v15 verification") + var outLen C.size_t + out := make([]byte, size) + if C._goboringcrypto_RSA_verify_raw(pub.key, &outLen, base(out), C.size_t(len(out)), base(sig), C.size_t(len(sig)), C.GO_RSA_PKCS1_PADDING) == 0 { + return fail("RSA_verify") + } + if subtle.ConstantTimeCompare(msg, out[:outLen]) != 1 { + return fail("RSA_verify") + } + runtime.KeepAlive(pub) + return nil + } + md := cryptoHashToMD(h) if md == nil { return errors.New("crypto/rsa: unsupported hash function")