nss: CVE-2014-1568 the patch comes from: http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-1568 https://bugzilla.mozilla.org/show_bug.cgi?id=1064636 nss ng log: ===== changeset: 11252:ad411fb64046 user: Kai Engert date: Tue Sep 23 19:28:34 2014 +0200 summary: Fix bug 1064636, patch part 2, r=rrelyea ===== changeset: 11253:4e90910ad2f9 user: Kai Engert date: Tue Sep 23 19:28:45 2014 +0200 summary: Fix bug 1064636, patch part 3, r=rrelyea ===== changeset: 11254:fb7208e91ae8 user: Kai Engert date: Tue Sep 23 19:28:52 2014 +0200 summary: Fix bug 1064636, patch part 1, r=rrelyea ===== changeset: 11255:8dd6c6ac977d user: Kai Engert date: Tue Sep 23 19:39:40 2014 +0200 summary: Bug 1064636, follow up commit to fix Windows build bustage Upstream-Status: Backport Signed-off-by: Li Wang --- nss/lib/cryptohi/secvfy.c | 202 +++++++++++++++++++++++++++----------------- nss/lib/softoken/pkcs11c.c | 69 +++++++-------- nss/lib/util/manifest.mn | 2 + nss/lib/util/nssutil.def | 6 ++ nss/lib/util/pkcs1sig.c | 169 ++++++++++++++++++++++++++++++++++++ nss/lib/util/pkcs1sig.h | 30 +++++++ 6 files changed, 360 insertions(+), 118 deletions(-) create mode 100644 nss/lib/util/pkcs1sig.c create mode 100644 nss/lib/util/pkcs1sig.h diff --git a/nss/lib/cryptohi/secvfy.c b/nss/lib/cryptohi/secvfy.c index c1ac39b..0a20672 100644 --- a/nss/lib/cryptohi/secvfy.c +++ b/nss/lib/cryptohi/secvfy.c @@ -12,78 +12,111 @@ #include "secasn1.h" #include "secoid.h" #include "pk11func.h" +#include "pkcs1sig.h" #include "secdig.h" #include "secerr.h" #include "keyi.h" /* -** Decrypt signature block using public key -** Store the hash algorithm oid tag in *tagp -** Store the digest in the digest buffer -** Store the digest length in *digestlen +** Recover the DigestInfo from an RSA PKCS#1 signature. +** +** If givenDigestAlg != SEC_OID_UNKNOWN, copy givenDigestAlg to digestAlgOut. +** Otherwise, parse the DigestInfo structure and store the decoded digest +** algorithm into digestAlgOut. +** +** Store the encoded DigestInfo into digestInfo. +** Store the DigestInfo length into digestInfoLen. +** +** This function does *not* verify that the AlgorithmIdentifier in the +** DigestInfo identifies givenDigestAlg or that the DigestInfo is encoded +** correctly; verifyPKCS1DigestInfo does that. +** ** XXX this is assuming that the signature algorithm has WITH_RSA_ENCRYPTION */ static SECStatus -DecryptSigBlock(SECOidTag *tagp, unsigned char *digest, - unsigned int *digestlen, unsigned int maxdigestlen, - SECKEYPublicKey *key, const SECItem *sig, char *wincx) +recoverPKCS1DigestInfo(SECOidTag givenDigestAlg, + /*out*/ SECOidTag* digestAlgOut, + /*out*/ unsigned char** digestInfo, + /*out*/ unsigned int* digestInfoLen, + SECKEYPublicKey* key, + const SECItem* sig, void* wincx) { - SGNDigestInfo *di = NULL; - unsigned char *buf = NULL; - SECStatus rv; - SECOidTag tag; - SECItem it; - - if (key == NULL) goto loser; - + SGNDigestInfo* di = NULL; + SECItem it; + PRBool rv = SECSuccess; + + PORT_Assert(digestAlgOut); + PORT_Assert(digestInfo); + PORT_Assert(digestInfoLen); + PORT_Assert(key); + PORT_Assert(key->keyType == rsaKey); + PORT_Assert(sig); + + it.data = NULL; it.len = SECKEY_PublicKeyStrength(key); - if (!it.len) goto loser; - it.data = buf = (unsigned char *)PORT_Alloc(it.len); - if (!buf) goto loser; + if (it.len != 0) { + it.data = (unsigned char *)PORT_Alloc(it.len); + } + if (it.len == 0 || it.data == NULL ) { + rv = SECFailure; + } - /* decrypt the block */ - rv = PK11_VerifyRecover(key, (SECItem *)sig, &it, wincx); - if (rv != SECSuccess) goto loser; + if (rv == SECSuccess) { + /* decrypt the block */ + rv = PK11_VerifyRecover(key, sig, &it, wincx); + } - di = SGN_DecodeDigestInfo(&it); - if (di == NULL) goto sigloser; + if (rv == SECSuccess) { + if (givenDigestAlg != SEC_OID_UNKNOWN) { + /* We don't need to parse the DigestInfo if the caller gave us the + * digest algorithm to use. Later verifyPKCS1DigestInfo will verify + * that the DigestInfo identifies the given digest algorithm and + * that the DigestInfo is encoded absolutely correctly. + */ + *digestInfoLen = it.len; + *digestInfo = (unsigned char*)it.data; + *digestAlgOut = givenDigestAlg; + return SECSuccess; + } + } - /* - ** Finally we have the digest info; now we can extract the algorithm - ** ID and the signature block - */ - tag = SECOID_GetAlgorithmTag(&di->digestAlgorithm); - /* Check that tag is an appropriate algorithm */ - if (tag == SEC_OID_UNKNOWN) { - goto sigloser; - } - /* make sure the "parameters" are not too bogus. */ - if (di->digestAlgorithm.parameters.len > 2) { - goto sigloser; - } - if (di->digest.len > maxdigestlen) { - PORT_SetError(SEC_ERROR_OUTPUT_LEN); - goto loser; + if (rv == SECSuccess) { + /* The caller didn't specify a digest algorithm to use, so choose the + * digest algorithm by parsing the AlgorithmIdentifier within the + * DigestInfo. + */ + di = SGN_DecodeDigestInfo(&it); + if (!di) { + rv = SECFailure; + } } - PORT_Memcpy(digest, di->digest.data, di->digest.len); - *tagp = tag; - *digestlen = di->digest.len; - goto done; - sigloser: - PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + if (rv == SECSuccess) { + *digestAlgOut = SECOID_GetAlgorithmTag(&di->digestAlgorithm); + if (*digestAlgOut == SEC_OID_UNKNOWN) { + rv = SECFailure; + } + } - loser: - rv = SECFailure; + if (di) { + SGN_DestroyDigestInfo(di); + } + + if (rv == SECSuccess) { + *digestInfoLen = it.len; + *digestInfo = (unsigned char*)it.data; + } else { + if (it.data) { + PORT_Free(it.data); + } + *digestInfo = NULL; + *digestInfoLen = 0; + PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + } - done: - if (di != NULL) SGN_DestroyDigestInfo(di); - if (buf != NULL) PORT_Free(buf); - return rv; } - struct VFYContextStr { SECOidTag hashAlg; /* the hash algorithm */ SECKEYPublicKey *key; @@ -99,14 +132,14 @@ struct VFYContextStr { union { unsigned char buffer[1]; - /* the digest in the decrypted RSA signature */ - unsigned char rsadigest[HASH_LENGTH_MAX]; /* the full DSA signature... 40 bytes */ unsigned char dsasig[DSA_MAX_SIGNATURE_LEN]; /* the full ECDSA signature */ unsigned char ecdsasig[2 * MAX_ECKEY_LEN]; } u; - unsigned int rsadigestlen; + unsigned int pkcs1RSADigestInfoLen; + /* the encoded DigestInfo from a RSA PKCS#1 signature */ + unsigned char *pkcs1RSADigestInfo; void * wincx; void *hashcx; const SECHashObject *hashobj; @@ -117,6 +150,17 @@ struct VFYContextStr { * VFY_EndWithSignature call. */ }; +static SECStatus +verifyPKCS1DigestInfo(const VFYContext* cx, const SECItem* digest) +{ + SECItem pkcs1DigestInfo; + pkcs1DigestInfo.data = cx->pkcs1RSADigestInfo; + pkcs1DigestInfo.len = cx->pkcs1RSADigestInfoLen; + return _SGN_VerifyPKCS1DigestInfo( + cx->hashAlg, digest, &pkcs1DigestInfo, + PR_TRUE /*XXX: unsafeAllowMissingParameters*/); +} + /* * decode the ECDSA or DSA signature from it's DER wrapping. * The unwrapped/raw signature is placed in the buffer pointed @@ -376,16 +420,16 @@ vfy_CreateContext(const SECKEYPublicKey *key, const SECItem *sig, cx->encAlg = encAlg; cx->hashAlg = hashAlg; cx->key = SECKEY_CopyPublicKey(key); + cx->pkcs1RSADigestInfo = NULL; rv = SECSuccess; if (sig) { switch (type) { case rsaKey: - rv = DecryptSigBlock(&cx->hashAlg, cx->u.buffer, &cx->rsadigestlen, - HASH_LENGTH_MAX, cx->key, sig, (char*)wincx); - if (cx->hashAlg != hashAlg && hashAlg != SEC_OID_UNKNOWN) { - PORT_SetError(SEC_ERROR_BAD_SIGNATURE); - rv = SECFailure; - } + rv = recoverPKCS1DigestInfo(hashAlg, &cx->hashAlg, + &cx->pkcs1RSADigestInfo, + &cx->pkcs1RSADigestInfoLen, + cx->key, + sig, wincx); break; case dsaKey: case ecKey: @@ -469,6 +513,9 @@ VFY_DestroyContext(VFYContext *cx, PRBool freeit) if (cx->key) { SECKEY_DestroyPublicKey(cx->key); } + if (cx->pkcs1RSADigestInfo) { + PORT_Free(cx->pkcs1RSADigestInfo); + } if (freeit) { PORT_ZFree(cx, sizeof(VFYContext)); } @@ -548,21 +595,25 @@ VFY_EndWithSignature(VFYContext *cx, SECItem *sig) } break; case rsaKey: + { + SECItem digest; + digest.data = final; + digest.len = part; if (sig) { - SECOidTag hashid = SEC_OID_UNKNOWN; - rv = DecryptSigBlock(&hashid, cx->u.buffer, &cx->rsadigestlen, - HASH_LENGTH_MAX, cx->key, sig, (char*)cx->wincx); - if ((rv != SECSuccess) || (hashid != cx->hashAlg)) { - PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + SECOidTag hashid; + PORT_Assert(cx->hashAlg != SEC_OID_UNKNOWN); + rv = recoverPKCS1DigestInfo(cx->hashAlg, &hashid, + &cx->pkcs1RSADigestInfo, + &cx->pkcs1RSADigestInfoLen, + cx->key, + sig, cx->wincx); + PORT_Assert(cx->hashAlg == hashid); + if (rv != SECSuccess) { return SECFailure; } } - if ((part != cx->rsadigestlen) || - PORT_Memcmp(final, cx->u.buffer, part)) { - PORT_SetError(SEC_ERROR_BAD_SIGNATURE); - return SECFailure; - } - break; + return verifyPKCS1DigestInfo(cx, &digest); + } default: PORT_SetError(SEC_ERROR_BAD_SIGNATURE); return SECFailure; /* shouldn't happen */ @@ -595,12 +646,7 @@ vfy_VerifyDigest(const SECItem *digest, const SECKEYPublicKey *key, if (cx != NULL) { switch (key->keyType) { case rsaKey: - if ((digest->len != cx->rsadigestlen) || - PORT_Memcmp(digest->data, cx->u.buffer, digest->len)) { - PORT_SetError(SEC_ERROR_BAD_SIGNATURE); - } else { - rv = SECSuccess; - } + rv = verifyPKCS1DigestInfo(cx, digest); break; case dsaKey: case ecKey: diff --git a/nss/lib/softoken/pkcs11c.c b/nss/lib/softoken/pkcs11c.c index 89b5bd8..ba6dcfa 100644 --- a/nss/lib/softoken/pkcs11c.c +++ b/nss/lib/softoken/pkcs11c.c @@ -23,6 +23,7 @@ #include "blapi.h" #include "pkcs11.h" #include "pkcs11i.h" +#include "pkcs1sig.h" #include "lowkeyi.h" #include "secder.h" #include "secdig.h" @@ -2580,54 +2581,42 @@ sftk_hashCheckSign(SFTKHashVerifyInfo *info, unsigned char *sig, } SECStatus -RSA_HashCheckSign(SECOidTag hashOid, NSSLOWKEYPublicKey *key, +RSA_HashCheckSign(SECOidTag digestOid, NSSLOWKEYPublicKey *key, unsigned char *sig, unsigned int sigLen, - unsigned char *digest, unsigned int digestLen) + unsigned char *digestData, unsigned int digestLen) { + unsigned char *pkcs1DigestInfoData; + SECItem pkcs1DigestInfo; + SECItem digest; + unsigned int bufferSize; + SECStatus rv; - SECItem it; - SGNDigestInfo *di = NULL; - SECStatus rv = SECSuccess; - - it.data = NULL; - - if (key == NULL) goto loser; - - it.len = nsslowkey_PublicModulusLen(key); - if (!it.len) goto loser; + /* pkcs1DigestInfo.data must be less than key->u.rsa.modulus.len */ + bufferSize = key->u.rsa.modulus.len; + pkcs1DigestInfoData = PORT_ZAlloc(bufferSize); + if (!pkcs1DigestInfoData) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } - it.data = (unsigned char *) PORT_Alloc(it.len); - if (it.data == NULL) goto loser; + pkcs1DigestInfo.data = pkcs1DigestInfoData; + pkcs1DigestInfo.len = bufferSize; /* decrypt the block */ - rv = RSA_CheckSignRecover(key, it.data, &it.len, it.len, sig, sigLen); - if (rv != SECSuccess) goto loser; - - di = SGN_DecodeDigestInfo(&it); - if (di == NULL) goto loser; - if (di->digest.len != digestLen) goto loser; - - /* make sure the tag is OK */ - if (SECOID_GetAlgorithmTag(&di->digestAlgorithm) != hashOid) { - goto loser; - } - /* make sure the "parameters" are not too bogus. */ - if (di->digestAlgorithm.parameters.len > 2) { - goto loser; - } - /* Now check the signature */ - if (PORT_Memcmp(digest, di->digest.data, di->digest.len) == 0) { - goto done; + rv = RSA_CheckSignRecover(key, pkcs1DigestInfo.data, + &pkcs1DigestInfo.len, pkcs1DigestInfo.len, + sig, sigLen); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + } else { + digest.data = (PRUint8*) digestData; + digest.len = digestLen; + rv = _SGN_VerifyPKCS1DigestInfo( + digestOid, &digest, &pkcs1DigestInfo, + PR_TRUE /*XXX: unsafeAllowMissingParameters*/); } - loser: - PORT_SetError(SEC_ERROR_BAD_SIGNATURE); - rv = SECFailure; - - done: - if (it.data != NULL) PORT_Free(it.data); - if (di != NULL) SGN_DestroyDigestInfo(di); - + PORT_Free(pkcs1DigestInfoData); return rv; } diff --git a/nss/lib/util/manifest.mn b/nss/lib/util/manifest.mn index ed54a16..9ff3758 100644 --- a/nss/lib/util/manifest.mn +++ b/nss/lib/util/manifest.mn @@ -22,6 +22,7 @@ EXPORTS = \ pkcs11t.h \ pkcs11n.h \ pkcs11u.h \ + pkcs1sig.h \ portreg.h \ secasn1.h \ secasn1t.h \ @@ -58,6 +59,7 @@ CSRCS = \ nssrwlk.c \ nssilock.c \ oidstring.c \ + pkcs1sig.c \ portreg.c \ secalgid.c \ secasn1d.c \ diff --git a/nss/lib/util/nssutil.def b/nss/lib/util/nssutil.def index 86a0ad7..9d98df2 100644 --- a/nss/lib/util/nssutil.def +++ b/nss/lib/util/nssutil.def @@ -271,3 +271,9 @@ SECITEM_ZfreeArray; ;+ local: ;+ *; ;+}; +;+NSSUTIL_3.17.1 { # NSS Utilities 3.17.1 release +;+ global: +_SGN_VerifyPKCS1DigestInfo; +;+ local: +;+ *; +;+}; diff --git a/nss/lib/util/pkcs1sig.c b/nss/lib/util/pkcs1sig.c new file mode 100644 index 0000000..03b16f5 --- /dev/null +++ b/nss/lib/util/pkcs1sig.c @@ -0,0 +1,169 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "pkcs1sig.h" +#include "hasht.h" +#include "secerr.h" +#include "secasn1t.h" +#include "secoid.h" + +typedef struct pkcs1PrefixStr pkcs1Prefix; +struct pkcs1PrefixStr { + unsigned int len; + PRUint8 *data; +}; + +typedef struct pkcs1PrefixesStr pkcs1Prefixes; +struct pkcs1PrefixesStr { + unsigned int digestLen; + pkcs1Prefix prefixWithParams; + pkcs1Prefix prefixWithoutParams; +}; + +/* The value for SGN_PKCS1_DIGESTINFO_MAX_PREFIX_LEN_EXCLUDING_OID is based on + * the possible prefix encodings as explained below. + */ +#define MAX_PREFIX_LEN_EXCLUDING_OID 10 + +static SECStatus +encodePrefix(const SECOidData *hashOid, unsigned int digestLen, + pkcs1Prefix *prefix, PRBool withParams) +{ + /* with params coding is: + * Sequence (2 bytes) { + * Sequence (2 bytes) { + * Oid (2 bytes) { + * Oid value (derOid->oid.len) + * } + * NULL (2 bytes) + * } + * OCTECT (2 bytes); + * + * without params coding is: + * Sequence (2 bytes) { + * Sequence (2 bytes) { + * Oid (2 bytes) { + * Oid value (derOid->oid.len) + * } + * } + * OCTECT (2 bytes); + */ + + unsigned int innerSeqLen = 2 + hashOid->oid.len; + unsigned int outerSeqLen = 2 + innerSeqLen + 2 + digestLen; + unsigned int extra = 0; + + if (withParams) { + innerSeqLen += 2; + outerSeqLen += 2; + extra = 2; + } + + if (innerSeqLen >= 128 || + outerSeqLen >= 128 || + (outerSeqLen + 2 - digestLen) > + (MAX_PREFIX_LEN_EXCLUDING_OID + hashOid->oid.len)) { + /* this is actually a library failure, It shouldn't happen */ + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + prefix->len = 6 + hashOid->oid.len + extra + 2; + prefix->data = PORT_Alloc(prefix->len); + if (!prefix->data) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + + prefix->data[0] = SEC_ASN1_SEQUENCE|SEC_ASN1_CONSTRUCTED; + prefix->data[1] = outerSeqLen; + prefix->data[2] = SEC_ASN1_SEQUENCE|SEC_ASN1_CONSTRUCTED; + prefix->data[3] = innerSeqLen; + prefix->data[4] = SEC_ASN1_OBJECT_ID; + prefix->data[5] = hashOid->oid.len; + PORT_Memcpy(&prefix->data[6], hashOid->oid.data, hashOid->oid.len); + if (withParams) { + prefix->data[6 + hashOid->oid.len] = SEC_ASN1_NULL; + prefix->data[6 + hashOid->oid.len + 1] = 0; + } + prefix->data[6 + hashOid->oid.len + extra] = SEC_ASN1_OCTET_STRING; + prefix->data[6 + hashOid->oid.len + extra + 1] = digestLen; + + return SECSuccess; +} + +SECStatus +_SGN_VerifyPKCS1DigestInfo(SECOidTag digestAlg, + const SECItem* digest, + const SECItem* dataRecoveredFromSignature, + PRBool unsafeAllowMissingParameters) +{ + SECOidData *hashOid; + pkcs1Prefixes pp; + const pkcs1Prefix* expectedPrefix; + SECStatus rv, rv2, rv3; + + if (!digest || !digest->data || + !dataRecoveredFromSignature || !dataRecoveredFromSignature->data) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + hashOid = SECOID_FindOIDByTag(digestAlg); + if (hashOid == NULL) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + pp.digestLen = digest->len; + pp.prefixWithParams.data = NULL; + pp.prefixWithoutParams.data = NULL; + + rv2 = encodePrefix(hashOid, pp.digestLen, &pp.prefixWithParams, PR_TRUE); + rv3 = encodePrefix(hashOid, pp.digestLen, &pp.prefixWithoutParams, PR_FALSE); + + rv = SECSuccess; + if (rv2 != SECSuccess || rv3 != SECSuccess) { + rv = SECFailure; + } + + if (rv == SECSuccess) { + /* We don't attempt to avoid timing attacks on these comparisons because + * signature verification is a public key operation, not a private key + * operation. + */ + + if (dataRecoveredFromSignature->len == + pp.prefixWithParams.len + pp.digestLen) { + expectedPrefix = &pp.prefixWithParams; + } else if (unsafeAllowMissingParameters && + dataRecoveredFromSignature->len == + pp.prefixWithoutParams.len + pp.digestLen) { + expectedPrefix = &pp.prefixWithoutParams; + } else { + PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + rv = SECFailure; + } + } + + if (rv == SECSuccess) { + if (memcmp(dataRecoveredFromSignature->data, expectedPrefix->data, + expectedPrefix->len) || + memcmp(dataRecoveredFromSignature->data + expectedPrefix->len, + digest->data, digest->len)) { + PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + rv = SECFailure; + } + } + + if (pp.prefixWithParams.data) { + PORT_Free(pp.prefixWithParams.data); + } + if (pp.prefixWithoutParams.data) { + PORT_Free(pp.prefixWithoutParams.data); + } + + return rv; +} diff --git a/nss/lib/util/pkcs1sig.h b/nss/lib/util/pkcs1sig.h new file mode 100644 index 0000000..7c52b15 --- /dev/null +++ b/nss/lib/util/pkcs1sig.h @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef _PKCS1SIG_H_ +#define _PKCS1SIG_H_ + +#include "hasht.h" +#include "seccomon.h" +#include "secoidt.h" + +/* SGN_VerifyPKCS1DigestInfo verifies that the length of the digest is correct + * for the given algorithm, then verifies that the recovered data from the + * PKCS#1 signature is a properly-formatted DigestInfo that identifies the + * given digest algorithm, then verifies that the digest in the DigestInfo + * matches the given digest. + * + * dataRecoveredFromSignature must be the result of calling PK11_VerifyRecover + * or equivalent. + * + * If unsafeAllowMissingParameters is true (not recommended), then a DigestInfo + * without the mandatory ASN.1 NULL parameter will also be accepted. + */ +SECStatus _SGN_VerifyPKCS1DigestInfo(SECOidTag digestAlg, + const SECItem* digest, + const SECItem* dataRecoveredFromSignature, + PRBool unsafeAllowMissingParameters); + +#endif /* _PKCS1SIG_H_ */ -- 1.7.9.5