imported changes from mozilla NSS:

- Bug 966856 - mozilla::pkix: support SHA-2 hashes in CertIDs in OCSP responses r=jschanck,djackson (78d2f4a3)
This commit is contained in:
roytam1 2021-12-17 16:54:54 +08:00
commit a140666918
6 changed files with 194 additions and 58 deletions

View file

@ -214,9 +214,13 @@ public:
const TestSignatureAlgorithm& signatureAlgorithm, const TestSignatureAlgorithm& signatureAlgorithm,
/*optional*/ const ByteString* certs = nullptr, /*optional*/ const ByteString* certs = nullptr,
/*optional*/ OCSPResponseExtension* singleExtensions = nullptr, /*optional*/ OCSPResponseExtension* singleExtensions = nullptr,
/*optional*/ OCSPResponseExtension* responseExtensions = nullptr) /*optional*/ OCSPResponseExtension* responseExtensions = nullptr,
/*optional*/ DigestAlgorithm certIDHashAlgorithm = DigestAlgorithm::sha1,
/*optional*/ ByteString certIDHashAlgorithmEncoded = ByteString())
{ {
OCSPResponseContext context(certID, producedAt); OCSPResponseContext context(certID, producedAt);
context.certIDHashAlgorithm = certIDHashAlgorithm;
context.certIDHashAlgorithmEncoded = certIDHashAlgorithmEncoded;
if (signerName) { if (signerName) {
context.signerNameDER = CNToDERName(signerName); context.signerNameDER = CNToDERName(signerName);
EXPECT_FALSE(ENCODING_FAILED(context.signerNameDER)); EXPECT_FALSE(ENCODING_FAILED(context.signerNameDER));
@ -454,6 +458,72 @@ TEST_F(pkixocsp_VerifyEncodedResponse_successful, ct_extension)
trustDomain.signedCertificateTimestamps); trustDomain.signedCertificateTimestamps);
} }
struct CertIDHashAlgorithm
{
DigestAlgorithm hashAlgorithm;
ByteString encodedHashAlgorithm;
Result expectedResult;
};
// python DottedOIDToCode.py --alg id-sha1 1.3.14.3.2.26
static const uint8_t alg_id_sha1[] = {
0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a
};
// python DottedOIDToCode.py --alg id-sha256 2.16.840.1.101.3.4.2.1
static const uint8_t alg_id_sha256[] = {
0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01
};
static const uint8_t not_an_encoded_hash_oid[] = {
0x01, 0x02, 0x03, 0x04
};
static const CertIDHashAlgorithm CERTID_HASH_ALGORITHMS[] = {
{ DigestAlgorithm::sha1, ByteString(), Success },
{ DigestAlgorithm::sha256, ByteString(), Success },
{ DigestAlgorithm::sha384, ByteString(), Success },
{ DigestAlgorithm::sha512, ByteString(), Success },
{ DigestAlgorithm::sha256, BytesToByteString(alg_id_sha1),
Result::ERROR_OCSP_MALFORMED_RESPONSE },
{ DigestAlgorithm::sha1, BytesToByteString(alg_id_sha256),
Result::ERROR_OCSP_MALFORMED_RESPONSE },
{ DigestAlgorithm::sha1, BytesToByteString(not_an_encoded_hash_oid),
Result::ERROR_OCSP_MALFORMED_RESPONSE },
};
class pkixocsp_VerifyEncodedResponse_CertIDHashAlgorithm
: public pkixocsp_VerifyEncodedResponse_successful
, public ::testing::WithParamInterface<CertIDHashAlgorithm>
{
};
TEST_P(pkixocsp_VerifyEncodedResponse_CertIDHashAlgorithm, CertIDHashAlgorithm)
{
ByteString responseString(
CreateEncodedOCSPSuccessfulResponse(
OCSPResponseContext::good, *endEntityCertID, byKey,
*rootKeyPair, oneDayBeforeNow,
oneDayBeforeNow, &oneDayAfterNow,
sha256WithRSAEncryption(),
nullptr,
nullptr,
nullptr,
GetParam().hashAlgorithm,
GetParam().encodedHashAlgorithm));
Input response;
ASSERT_EQ(Success,
response.Init(responseString.data(), responseString.length()));
bool expired;
ASSERT_EQ(GetParam().expectedResult,
VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS,
response, expired));
ASSERT_FALSE(expired);
}
INSTANTIATE_TEST_SUITE_P(pkixocsp_VerifyEncodedResponse_CertIDHashAlgorithm,
pkixocsp_VerifyEncodedResponse_CertIDHashAlgorithm,
testing::ValuesIn(CERTID_HASH_ALGORITHMS));
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// indirect responses (signed by a delegated OCSP responder cert) // indirect responses (signed by a delegated OCSP responder cert)

View file

@ -348,6 +348,14 @@ class OCSPResponseContext final {
OCSPResponseContext(const CertID& certID, std::time_t time); OCSPResponseContext(const CertID& certID, std::time_t time);
const CertID& certID; const CertID& certID;
// What digest algorithm to use to produce issuerNameHash and issuerKeyHash.
// Defaults to sha1.
DigestAlgorithm certIDHashAlgorithm;
// If non-empty, the sequence of bytes to use for hashAlgorithm when encoding
// this response. If empty, the sequence of bytes corresponding to
// certIDHashAlgorithm will be used. Defaults to empty.
ByteString certIDHashAlgorithmEncoded;
// TODO(bug 980538): add a way to specify what certificates are included. // TODO(bug 980538): add a way to specify what certificates are included.
// The fields below are in the order that they appear in an OCSP response. // The fields below are in the order that they appear in an OCSP response.

View file

@ -258,6 +258,20 @@ Result CheckSubjectPublicKeyInfo(Input subjectPublicKeyInfo,
#else #else
#error Unsupported compiler for MOZILLA_PKIX_UNREACHABLE_DEFAULT. #error Unsupported compiler for MOZILLA_PKIX_UNREACHABLE_DEFAULT.
#endif #endif
inline size_t DigestAlgorithmToSizeInBytes(DigestAlgorithm digestAlgorithm) {
switch (digestAlgorithm) {
case DigestAlgorithm::sha1:
return 160 / 8;
case DigestAlgorithm::sha256:
return 256 / 8;
case DigestAlgorithm::sha384:
return 384 / 8;
case DigestAlgorithm::sha512:
return 512 / 8;
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
}
}
} }
} // namespace mozilla::pkix } // namespace mozilla::pkix

View file

@ -27,12 +27,6 @@
#include "mozpkix/pkixcheck.h" #include "mozpkix/pkixcheck.h"
#include "mozpkix/pkixutil.h" #include "mozpkix/pkixutil.h"
namespace {
const size_t SHA1_DIGEST_LENGTH = 160 / 8;
} // namespace
namespace mozilla { namespace pkix { namespace mozilla { namespace pkix {
// These values correspond to the tag values in the ASN.1 CertStatus // These values correspond to the tag values in the ASN.1 CertStatus
@ -180,10 +174,12 @@ static inline Result MatchCertID(Reader& input,
const Context& context, const Context& context,
/*out*/ bool& match); /*out*/ bool& match);
static Result MatchKeyHash(TrustDomain& trustDomain, static Result MatchKeyHash(TrustDomain& trustDomain,
DigestAlgorithm hashAlgorithm,
Input issuerKeyHash, Input issuerKeyHash,
Input issuerSubjectPublicKeyInfo, Input issuerSubjectPublicKeyInfo,
/*out*/ bool& match); /*out*/ bool& match);
static Result KeyHash(TrustDomain& trustDomain, static Result KeyHash(TrustDomain& trustDomain,
DigestAlgorithm hashAlgorithm,
Input subjectPublicKeyInfo, Input subjectPublicKeyInfo,
/*out*/ uint8_t* hashBuf, size_t hashBufSize); /*out*/ uint8_t* hashBuf, size_t hashBufSize);
@ -212,7 +208,7 @@ MatchResponderID(TrustDomain& trustDomain,
if (rv != Success) { if (rv != Success) {
return rv; return rv;
} }
return MatchKeyHash(trustDomain, keyHash, return MatchKeyHash(trustDomain, DigestAlgorithm::sha1, keyHash,
potentialSignerSubjectPublicKeyInfo, match); potentialSignerSubjectPublicKeyInfo, match);
} }
@ -740,36 +736,36 @@ MatchCertID(Reader& input, const Context& context, /*out*/ bool& match)
return Success; return Success;
} }
// TODO: support SHA-2 hashes. size_t hashAlgorithmLength = DigestAlgorithmToSizeInBytes(hashAlgorithm);
if (issuerNameHash.GetLength() != hashAlgorithmLength) {
if (hashAlgorithm != DigestAlgorithm::sha1) {
// Again, not interested in this response. Consume input, return success.
input.SkipToEnd();
return Success;
}
if (issuerNameHash.GetLength() != SHA1_DIGEST_LENGTH) {
return Result::ERROR_OCSP_MALFORMED_RESPONSE; return Result::ERROR_OCSP_MALFORMED_RESPONSE;
} }
// From http://tools.ietf.org/html/rfc6960#section-4.1.1: // From http://tools.ietf.org/html/rfc6960#section-4.1.1:
// "The hash shall be calculated over the DER encoding of the // "The hash shall be calculated over the DER encoding of the
// issuer's name field in the certificate being checked." // issuer's name field in the certificate being checked."
uint8_t hashBuf[SHA1_DIGEST_LENGTH]; uint8_t hashBuf[MAX_DIGEST_SIZE_IN_BYTES];
if (hashAlgorithmLength > sizeof(hashBuf)) {
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
rv = context.trustDomain.DigestBuf(context.certID.issuer, rv = context.trustDomain.DigestBuf(context.certID.issuer,
DigestAlgorithm::sha1, hashBuf, hashAlgorithm, hashBuf,
sizeof(hashBuf)); hashAlgorithmLength);
if (rv != Success) {
return rv;
}
Input computed;
rv = computed.Init(hashBuf, hashAlgorithmLength);
if (rv != Success) { if (rv != Success) {
return rv; return rv;
} }
Input computed(hashBuf);
if (!InputsAreEqual(computed, issuerNameHash)) { if (!InputsAreEqual(computed, issuerNameHash)) {
// Again, not interested in this response. Consume input, return success. // Again, not interested in this response. Consume input, return success.
input.SkipToEnd(); input.SkipToEnd();
return Success; return Success;
} }
return MatchKeyHash(context.trustDomain, issuerKeyHash, return MatchKeyHash(context.trustDomain, hashAlgorithm, issuerKeyHash,
context.certID.issuerSubjectPublicKeyInfo, match); context.certID.issuerSubjectPublicKeyInfo, match);
} }
@ -783,30 +779,53 @@ MatchCertID(Reader& input, const Context& context, /*out*/ bool& match)
// -- BIT STRING subjectPublicKey [excluding // -- BIT STRING subjectPublicKey [excluding
// -- the tag, length, and number of unused // -- the tag, length, and number of unused
// -- bits] in the responder's certificate) // -- bits] in the responder's certificate)
//
// From https://datatracker.ietf.org/doc/html/rfc6960#section-4.1.1:
// CertID ::= SEQUENCE {
// hashAlgorithm AlgorithmIdentifier,
// issuerNameHash OCTET STRING, -- Hash of issuer's DN
// issuerKeyHash OCTET STRING, -- Hash of issuer's public key
// serialNumber CertificateSerialNumber }
// ...
// o hashAlgorithm is the hash algorithm used to generate the
// issuerNameHash and issuerKeyHash values.
// ...
// o issuerKeyHash is the hash of the issuer's public key. The hash
// shall be calculated over the value (excluding tag and length) of
// the subject public key field in the issuer's certificate.
static Result static Result
MatchKeyHash(TrustDomain& trustDomain, Input keyHash, MatchKeyHash(TrustDomain& trustDomain, DigestAlgorithm hashAlgorithm,
const Input subjectPublicKeyInfo, /*out*/ bool& match) Input keyHash, const Input subjectPublicKeyInfo,
/*out*/ bool& match)
{ {
if (keyHash.GetLength() != SHA1_DIGEST_LENGTH) { size_t hashLength = DigestAlgorithmToSizeInBytes(hashAlgorithm);
if (keyHash.GetLength() != hashLength) {
return Result::ERROR_OCSP_MALFORMED_RESPONSE; return Result::ERROR_OCSP_MALFORMED_RESPONSE;
} }
uint8_t hashBuf[SHA1_DIGEST_LENGTH]; uint8_t hashBuf[MAX_DIGEST_SIZE_IN_BYTES];
Result rv = KeyHash(trustDomain, subjectPublicKeyInfo, hashBuf, if (hashLength > MAX_DIGEST_SIZE_IN_BYTES) {
sizeof hashBuf); return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
Result rv = KeyHash(trustDomain, hashAlgorithm, subjectPublicKeyInfo,
hashBuf, hashLength);
if (rv != Success) {
return rv;
}
Input computed;
rv = computed.Init(hashBuf, hashLength);
if (rv != Success) { if (rv != Success) {
return rv; return rv;
} }
Input computed(hashBuf);
match = InputsAreEqual(computed, keyHash); match = InputsAreEqual(computed, keyHash);
return Success; return Success;
} }
// TODO(bug 966856): support SHA-2 hashes
Result Result
KeyHash(TrustDomain& trustDomain, const Input subjectPublicKeyInfo, KeyHash(TrustDomain& trustDomain, DigestAlgorithm hashAlgorithm,
/*out*/ uint8_t* hashBuf, size_t hashBufSize) const Input subjectPublicKeyInfo, /*out*/ uint8_t* hashBuf,
size_t hashBufSize)
{ {
if (!hashBuf || hashBufSize != SHA1_DIGEST_LENGTH) { if (!hashBuf || hashBufSize != DigestAlgorithmToSizeInBytes(hashAlgorithm)) {
return Result::FATAL_ERROR_LIBRARY_FAILURE; return Result::FATAL_ERROR_LIBRARY_FAILURE;
} }
@ -839,8 +858,8 @@ KeyHash(TrustDomain& trustDomain, const Input subjectPublicKeyInfo,
return rv; return rv;
} }
return trustDomain.DigestBuf(subjectPublicKey, DigestAlgorithm::sha1, return trustDomain.DigestBuf(subjectPublicKey, hashAlgorithm, hashBuf,
hashBuf, hashBufSize); hashBufSize);
} }
Result Result
@ -920,8 +939,6 @@ CreateEncodedOCSPRequest(TrustDomain& trustDomain, const struct CertID& certID,
// and thus more likely to fit within the 255 byte limit for OCSP GET that // and thus more likely to fit within the 255 byte limit for OCSP GET that
// is specified in RFC 5019 Section 5. // is specified in RFC 5019 Section 5.
// Bug 966856: Add the id-pkix-ocsp-pref-sig-algs extension.
// Since we don't know whether the OCSP responder supports anything other // Since we don't know whether the OCSP responder supports anything other
// than SHA-1, we have no choice but to use SHA-1 for issuerNameHash and // than SHA-1, we have no choice but to use SHA-1 for issuerNameHash and
// issuerKeyHash. // issuerKeyHash.
@ -985,7 +1002,8 @@ CreateEncodedOCSPRequest(TrustDomain& trustDomain, const struct CertID& certID,
// reqCert.issuerKeyHash (OCTET STRING) // reqCert.issuerKeyHash (OCTET STRING)
*d++ = 0x04; *d++ = 0x04;
*d++ = hashLen; *d++ = hashLen;
rv = KeyHash(trustDomain, certID.issuerSubjectPublicKeyInfo, d, hashLen); rv = KeyHash(trustDomain, DigestAlgorithm::sha1,
certID.issuerSubjectPublicKeyInfo, d, hashLen);
if (rv != Success) { if (rv != Success) {
return rv; return rv;
} }

View file

@ -42,14 +42,7 @@ DigestSignedData(TrustDomain& trustDomain,
return Result::ERROR_BAD_DER; return Result::ERROR_BAD_DER;
} }
size_t digestLen; size_t digestLen = DigestAlgorithmToSizeInBytes(signedDigest.digestAlgorithm);
switch (signedDigest.digestAlgorithm) {
case DigestAlgorithm::sha512: digestLen = 512 / 8; break;
case DigestAlgorithm::sha384: digestLen = 384 / 8; break;
case DigestAlgorithm::sha256: digestLen = 256 / 8; break;
case DigestAlgorithm::sha1: digestLen = 160 / 8; break;
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
}
assert(digestLen <= sizeof(digestBuf)); assert(digestLen <= sizeof(digestBuf));
rv = trustDomain.DigestBuf(signedData.data, signedDigest.digestAlgorithm, rv = trustDomain.DigestBuf(signedData.data, signedDigest.digestAlgorithm,

View file

@ -155,6 +155,8 @@ OCSPResponseExtension::OCSPResponseExtension()
OCSPResponseContext::OCSPResponseContext(const CertID& aCertID, time_t time) OCSPResponseContext::OCSPResponseContext(const CertID& aCertID, time_t time)
: certID(aCertID) : certID(aCertID)
, certIDHashAlgorithm(DigestAlgorithm::sha1)
, certIDHashAlgorithmEncoded(ByteString())
, responseStatus(successful) , responseStatus(successful)
, skipResponseBytes(false) , skipResponseBytes(false)
, producedAt(time) , producedAt(time)
@ -183,25 +185,26 @@ static ByteString CertID(OCSPResponseContext& context);
static ByteString CertStatus(OCSPResponseContext& context); static ByteString CertStatus(OCSPResponseContext& context);
static ByteString static ByteString
SHA1(const ByteString& toHash) HASH(const ByteString& toHash, DigestAlgorithm digestAlgorithm)
{ {
uint8_t digestBuf[20]; uint8_t digestBuf[MAX_DIGEST_SIZE_IN_BYTES];
Input input; Input input;
if (input.Init(toHash.data(), toHash.length()) != Success) { if (input.Init(toHash.data(), toHash.length()) != Success) {
abort(); abort();
} }
Result rv = TestDigestBuf(input, DigestAlgorithm::sha1, digestBuf, size_t digestLen = DigestAlgorithmToSizeInBytes(digestAlgorithm);
sizeof(digestBuf)); assert(digestLen <= sizeof(digestBuf));
Result rv = TestDigestBuf(input, digestAlgorithm, digestBuf, digestLen);
if (rv != Success) { if (rv != Success) {
abort(); abort();
} }
return ByteString(digestBuf, sizeof(digestBuf)); return ByteString(digestBuf, digestLen);
} }
static ByteString static ByteString
HashedOctetString(const ByteString& bytes) HashedOctetString(const ByteString& bytes, DigestAlgorithm digestAlgorithm)
{ {
ByteString digest(SHA1(bytes)); ByteString digest(HASH(bytes, digestAlgorithm));
if (ENCODING_FAILED(digest)) { if (ENCODING_FAILED(digest)) {
return ByteString(); return ByteString();
} }
@ -992,7 +995,7 @@ ResponderID(OCSPResponseContext& context)
ByteString ByteString
KeyHash(const ByteString& subjectPublicKey) KeyHash(const ByteString& subjectPublicKey)
{ {
return HashedOctetString(subjectPublicKey); return HashedOctetString(subjectPublicKey, DigestAlgorithm::sha1);
} }
// SingleResponse ::= SEQUENCE { // SingleResponse ::= SEQUENCE {
@ -1049,7 +1052,7 @@ CertID(OCSPResponseContext& context)
{ {
ByteString issuerName(context.certID.issuer.UnsafeGetData(), ByteString issuerName(context.certID.issuer.UnsafeGetData(),
context.certID.issuer.GetLength()); context.certID.issuer.GetLength());
ByteString issuerNameHash(HashedOctetString(issuerName)); ByteString issuerNameHash(HashedOctetString(issuerName, context.certIDHashAlgorithm));
if (ENCODING_FAILED(issuerNameHash)) { if (ENCODING_FAILED(issuerNameHash)) {
return ByteString(); return ByteString();
} }
@ -1073,8 +1076,8 @@ CertID(OCSPResponseContext& context)
!= Success) { != Success) {
return ByteString(); return ByteString();
} }
issuerKeyHash = KeyHash(ByteString(subjectPublicKey.UnsafeGetData(), issuerKeyHash = HashedOctetString(ByteString(subjectPublicKey.UnsafeGetData(),
subjectPublicKey.GetLength())); subjectPublicKey.GetLength()), context.certIDHashAlgorithm);
if (ENCODING_FAILED(issuerKeyHash)) { if (ENCODING_FAILED(issuerKeyHash)) {
return ByteString(); return ByteString();
} }
@ -1088,9 +1091,39 @@ CertID(OCSPResponseContext& context)
static const uint8_t alg_id_sha1[] = { static const uint8_t alg_id_sha1[] = {
0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a
}; };
// python DottedOIDToCode.py --alg id-sha256 2.16.840.1.101.3.4.2.1
static const uint8_t alg_id_sha256[] = {
0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01
};
// python DottedOIDToCode.py --alg id-sha384 2.16.840.1.101.3.4.2.2
static const uint8_t alg_id_sha384[] = {
0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02
};
// python DottedOIDToCode.py --alg id-sha512 2.16.840.1.101.3.4.2.3
static const uint8_t alg_id_sha512[] = {
0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03
};
ByteString value; ByteString value;
value.append(alg_id_sha1, sizeof(alg_id_sha1)); if (!context.certIDHashAlgorithmEncoded.empty()) {
value.append(context.certIDHashAlgorithmEncoded);
} else {
switch (context.certIDHashAlgorithm) {
case DigestAlgorithm::sha1:
value.append(alg_id_sha1, sizeof(alg_id_sha1));
break;
case DigestAlgorithm::sha256:
value.append(alg_id_sha256, sizeof(alg_id_sha256));
break;
case DigestAlgorithm::sha384:
value.append(alg_id_sha384, sizeof(alg_id_sha384));
break;
case DigestAlgorithm::sha512:
value.append(alg_id_sha512, sizeof(alg_id_sha512));
break;
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
}
}
value.append(issuerNameHash); value.append(issuerNameHash);
value.append(issuerKeyHash); value.append(issuerKeyHash);
value.append(serialNumber); value.append(serialNumber);