Add EVP_marshal_digest_algorithm_no_params
See https://boringssl-review.googlesource.com/c/boringssl/+/67088 for a
discussion of the long and sordid history here. It seems the default is
actually without NULL, but a bunch of use cases (primarily ones we care
about) require you to include the NULL due to historical mistakes.
We care about encoding digest AlgIds in:
- RSASSA-PKCS1-v1_5 DigestInfo: NULL should be included, though we
don't use that function here.
- RSASSA-PSS and RSAES-OAEP AlgIds: NULL should be included. We don't
use that function here, but we may in the future.
- PKCS#12: This is ambiguous, but I think the best behavior for now is
to continue sending NULL. See comment added to that file.
- OCSP: I think this actually should omit the NULL, but we, OpenSSL, and
Go all include it, so I've included it for now.
- PKCS#7 and (if we implement it) CMS: This should omit the NULL. We
currently don't use this function in our PKCS#7 implementation.
Although the default probably should be to omit it, we seem to want to
include it in almost all our current callers, I've added a no_params
variant for the new behavior for now. In the future we can reevaluate
this and, if desired:
- Add a with_null API
- Make the unsuffixed one into the no_params one
(backwards-incompatible)
- Retire the no_params one
Fixed: 42290589
Change-Id: Icc62f9af8cddfbdb5317b3a0b0fb2bf46ff74408
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/78449
Auto-Submit: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/digest/digest_extra.cc b/crypto/digest/digest_extra.cc
index f68ede9..4cbfa1f 100644
--- a/crypto/digest/digest_extra.cc
+++ b/crypto/digest/digest_extra.cc
@@ -156,21 +156,22 @@
return ret;
}
-int EVP_marshal_digest_algorithm(CBB *cbb, const EVP_MD *md) {
+static int marshal_digest_algorithm(CBB *cbb, const EVP_MD *md,
+ bool with_null) {
CBB algorithm, oid, null;
if (!CBB_add_asn1(cbb, &algorithm, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1(&algorithm, &oid, CBS_ASN1_OBJECT)) {
return 0;
}
- int found = 0;
+ bool found = false;
int nid = EVP_MD_type(md);
- for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kMDOIDs); i++) {
- if (nid == kMDOIDs[i].nid) {
- if (!CBB_add_bytes(&oid, kMDOIDs[i].oid, kMDOIDs[i].oid_len)) {
+ for (const auto &mdoid : kMDOIDs) {
+ if (nid == mdoid.nid) {
+ if (!CBB_add_bytes(&oid, mdoid.oid, mdoid.oid_len)) {
return 0;
}
- found = 1;
+ found = true;
break;
}
}
@@ -180,8 +181,7 @@
return 0;
}
- // TODO(crbug.com/boringssl/710): Is this correct? See RFC 4055, section 2.1.
- if (!CBB_add_asn1(&algorithm, &null, CBS_ASN1_NULL) || //
+ if ((with_null && !CBB_add_asn1(&algorithm, &null, CBS_ASN1_NULL)) || //
!CBB_flush(cbb)) {
return 0;
}
@@ -189,6 +189,14 @@
return 1;
}
+int EVP_marshal_digest_algorithm(CBB *cbb, const EVP_MD *md) {
+ return marshal_digest_algorithm(cbb, md, /*with_null=*/true);
+}
+
+int EVP_marshal_digest_algorithm_no_params(CBB *cbb, const EVP_MD *md) {
+ return marshal_digest_algorithm(cbb, md, /*with_null=*/false);
+}
+
const EVP_MD *EVP_get_digestbyname(const char *name) {
for (unsigned i = 0; i < OPENSSL_ARRAY_SIZE(nid_to_digest_mapping); i++) {
const char *short_name = nid_to_digest_mapping[i].short_name;
diff --git a/crypto/digest/digest_test.cc b/crypto/digest/digest_test.cc
index 8237fb8..8cdc48d 100644
--- a/crypto/digest/digest_test.cc
+++ b/crypto/digest/digest_test.cc
@@ -282,9 +282,9 @@
ASSERT_TRUE(CBB_init(cbb.get(), 0));
EXPECT_FALSE(EVP_marshal_digest_algorithm(cbb.get(), EVP_md5_sha1()));
- static const uint8_t kSHA256[] = {0x30, 0x0d, 0x06, 0x09, 0x60,
- 0x86, 0x48, 0x01, 0x65, 0x03,
- 0x04, 0x02, 0x01, 0x05, 0x00};
+ static const uint8_t kSHA256NullParam[] = {0x30, 0x0d, 0x06, 0x09, 0x60,
+ 0x86, 0x48, 0x01, 0x65, 0x03,
+ 0x04, 0x02, 0x01, 0x05, 0x00};
static const uint8_t kSHA256NoParam[] = {0x30, 0x0b, 0x06, 0x09, 0x60,
0x86, 0x48, 0x01, 0x65, 0x03,
0x04, 0x02, 0x01};
@@ -292,23 +292,24 @@
0x30, 0x0e, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x01, 0x02, 0x01, 0x2a};
- // Serialize SHA-256.
+ // Serialize SHA-256, with and without NULL.
cbb.Reset();
ASSERT_TRUE(CBB_init(cbb.get(), 0));
ASSERT_TRUE(EVP_marshal_digest_algorithm(cbb.get(), EVP_sha256()));
- uint8_t *der;
- size_t der_len;
- ASSERT_TRUE(CBB_finish(cbb.get(), &der, &der_len));
- bssl::UniquePtr<uint8_t> free_der(der);
- EXPECT_EQ(Bytes(kSHA256), Bytes(der, der_len));
+ EXPECT_EQ(Bytes(kSHA256NullParam),
+ Bytes(CBB_data(cbb.get()), CBB_len(cbb.get())));
+ cbb.Reset();
+ ASSERT_TRUE(CBB_init(cbb.get(), 0));
+ ASSERT_TRUE(EVP_marshal_digest_algorithm_no_params(cbb.get(), EVP_sha256()));
+ EXPECT_EQ(Bytes(kSHA256NoParam),
+ Bytes(CBB_data(cbb.get()), CBB_len(cbb.get())));
- // Parse SHA-256.
+ // Parse SHA-256. Either absent or NULL parameters are tolerated for
+ // compatibility.
CBS cbs;
- CBS_init(&cbs, kSHA256, sizeof(kSHA256));
+ CBS_init(&cbs, kSHA256NullParam, sizeof(kSHA256NullParam));
EXPECT_EQ(EVP_sha256(), EVP_parse_digest_algorithm(&cbs));
EXPECT_EQ(0u, CBS_len(&cbs));
-
- // Missing parameters are tolerated for compatibility.
CBS_init(&cbs, kSHA256NoParam, sizeof(kSHA256NoParam));
EXPECT_EQ(EVP_sha256(), EVP_parse_digest_algorithm(&cbs));
EXPECT_EQ(0u, CBS_len(&cbs));
diff --git a/crypto/pkcs7/pkcs7_x509.cc b/crypto/pkcs7/pkcs7_x509.cc
index aabc9a2..5a76752 100644
--- a/crypto/pkcs7/pkcs7_x509.cc
+++ b/crypto/pkcs7/pkcs7_x509.cc
@@ -368,13 +368,10 @@
// write_sha256_ai writes an AlgorithmIdentifier for SHA-256 to
// |digest_algos_set|.
static int write_sha256_ai(CBB *digest_algos_set, const void *arg) {
- CBB seq;
- return CBB_add_asn1(digest_algos_set, &seq, CBS_ASN1_SEQUENCE) &&
- OBJ_nid2cbb(&seq, NID_sha256) && //
- // https://datatracker.ietf.org/doc/html/rfc5754#section-2
- // "Implementations MUST generate SHA2 AlgorithmIdentifiers with absent
- // parameters."
- CBB_flush(digest_algos_set);
+ // https://datatracker.ietf.org/doc/html/rfc5754#section-2
+ // "Implementations MUST generate SHA2 AlgorithmIdentifiers with absent
+ // parameters."
+ return EVP_marshal_digest_algorithm_no_params(digest_algos_set, EVP_sha256());
}
// sign_sha256 writes at most |max_out_sig| bytes of the signature of |data| by
diff --git a/crypto/pkcs8/pkcs8_x509.cc b/crypto/pkcs8/pkcs8_x509.cc
index 60bcfd4..ede84f7 100644
--- a/crypto/pkcs8/pkcs8_x509.cc
+++ b/crypto/pkcs8/pkcs8_x509.cc
@@ -1282,6 +1282,16 @@
CBB mac_data, digest_info, mac_cbb, mac_salt_cbb;
if (!CBB_add_asn1(&pfx, &mac_data, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1(&mac_data, &digest_info, CBS_ASN1_SEQUENCE) ||
+ // OpenSSL and NSS always include a NULL parameter with the digest
+ // algorithm. Windows does not. RFC 7292 imports DigestInfo from PKCS
+ // #7. PKCS #7 does not actually use DigestInfo. It just describes
+ // RSASSA-PKCS1-v1_5 signing as encoding a DigestInfo and then
+ // "encrypting" it with the private key. In that context, NULL should be
+ // included. Confusingly, there is also a digestAlgorithm field in
+ // SignerInfo. There, RFC 5754 says to omit the NULL. But that field
+ // does not use DigestInfo per se.
+ //
+ // We match OpenSSL, NSS, and RSASSA-PKCS1-v1_5 in including the NULL.
!EVP_marshal_digest_algorithm(&digest_info, mac_md) ||
!CBB_add_asn1(&digest_info, &mac_cbb, CBS_ASN1_OCTETSTRING) ||
!CBB_add_bytes(&mac_cbb, mac, mac_len) ||
diff --git a/include/openssl/digest.h b/include/openssl/digest.h
index 9c12747..6abab76 100644
--- a/include/openssl/digest.h
+++ b/include/openssl/digest.h
@@ -215,9 +215,26 @@
// EVP_marshal_digest_algorithm marshals |md| as an AlgorithmIdentifier
// structure and appends the result to |cbb|. It returns one on success and zero
-// on error.
+// on error. It sets the parameters field to NULL. Use
+// |EVP_marshal_digest_algorithm_no_params| to omit the parameters instead.
+//
+// In general, the parameters should be omitted for digest algorithms, but the
+// following specifications require a NULL parameter instead.
+//
+// - Hash algorithms and MGF-1 hash algorithms used in RSASSA-PSS and RSAES-OAEP
+// (see RFC 4055, Section 2.1)
+// - The hash algorithm in the DigestInfo structure of RSASSA-PKCS1-v1_5 (see
+// RFC 8017, Appendix A.2.4)
+//
+// Some existing software also uses NULL parameters in other contexts. In
+// practice, digest algorithms are encoded wildly inconsistently.
OPENSSL_EXPORT int EVP_marshal_digest_algorithm(CBB *cbb, const EVP_MD *md);
+// EVP_marshal_digest_algorithm_no_params behaves like
+// |EVP_marshal_digest_algorithm| but omits the parameters field.
+OPENSSL_EXPORT int EVP_marshal_digest_algorithm_no_params(CBB *cbb,
+ const EVP_MD *md);
+
// Deprecated functions.
diff --git a/pki/ocsp.cc b/pki/ocsp.cc
index 7984050..e36fb88 100644
--- a/pki/ocsp.cc
+++ b/pki/ocsp.cc
@@ -1056,6 +1056,11 @@
// issuerNameHash OCTET STRING, -- Hash of issuer's DN
// issuerKeyHash OCTET STRING, -- Hash of issuer's public key
// serialNumber CertificateSerialNumber }
+ //
+ // It is unclear whether the parameters for hashAlgorithm should be omitted or
+ // NULL. Section 2.1 of RFC 4055 would suggest omitting it is the right
+ // default behavior. However, both OpenSSL and Go include it, so we match them
+ // for now.
// TODO(eroman): Don't use SHA1.
const EVP_MD *md = EVP_sha1();