Function that DER-encodes the public key of RSA Blind Signature - Probabilistic Signature Scheme algorithm.
It is the C++ alternative of the following Goa function:
http://google3/privacy/net/boq/common/tokens/token_types.go;l=85;rcl=515461856
And the test for this function mimics the test for the above mentioned Goa function as well.
PiperOrigin-RevId: 536525707
diff --git a/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/at_crypto_utils_test.cc b/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/at_crypto_utils_test.cc
index ed1a76b..f3ecdfd 100644
--- a/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/at_crypto_utils_test.cc
+++ b/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/at_crypto_utils_test.cc
@@ -264,6 +264,93 @@
}
}
+// copybara:strip_begin(internal comment)
+// The input public key and the expected DER encoding are taken from the
+// following Goa test:
+// http://google3/privacy/net/boq/common/tokens/token_types_test.go;l=21;rcl=528885322
+// copybara:strip_end
+TEST(CryptoUtilsTest, RsaPssDerEncodingTest) {
+ RSAPublicKey public_key_e_not_padded;
+ RSAPublicKey public_key_e_padded;
+
+ public_key_e_not_padded.set_n(absl::HexStringToBytes(
+ "b259758bb02bc75b68b17612c9bf68c5fa05958a334c61e167bc20bcc75757c126e892"
+ "10b9df3989072cf6260e6883c7cd4af4d31dde9915b69b301fbef962de8c71bd2db5ec62"
+ "5da259712f86a8dc3d241e9688c82391b7bf1ebc358311f55c26be910b76f61fea408ed6"
+ "92f1a9578a622c82c0fcf6f69ef3670e38bfc90f63da4f3bbbd088c8ae7a3c5a55e66f64"
+ "74d562d32cce7b7edd7cf0149ca0e96cb6525e81fbba815a8f12748e34e5135f572b2e17"
+ "b7ba430081597e6fb9033c005884d5935118c60d75b010f6fece7ecdcc1cb7d58d138969"
+ "3d43377f4f3de949cb1e4105e792b96d7f04b0cd262ac33cffc5a890d267425e61c19e93"
+ "63550f2285"));
+ // A hex string of 3 bytes in length is passed.
+ public_key_e_not_padded.set_e(absl::HexStringToBytes("010001"));
+
+ public_key_e_padded.set_n(public_key_e_not_padded.n());
+ // A hex string of 4 bytes in length is passed.
+ public_key_e_padded.set_e(absl::HexStringToBytes("00010001"));
+
+ // Convert both padded and not padded rsa public keys to rsa structs.
+ ASSERT_OK_AND_ASSIGN(
+ bssl::UniquePtr<RSA> rsa_e_not_padded,
+ AnonymousTokensRSAPublicKeyToRSA(public_key_e_not_padded));
+ ASSERT_OK_AND_ASSIGN(bssl::UniquePtr<RSA> rsa_e_padded,
+ AnonymousTokensRSAPublicKeyToRSA(public_key_e_padded));
+ // Encode both padded and not padded rsa structs to DER.
+ ASSERT_OK_AND_ASSIGN(std::string result_e_not_padded,
+ RsaSsaPssPublicKeyToDerEncoding(rsa_e_not_padded.get()));
+ ASSERT_OK_AND_ASSIGN(std::string result_e_padded,
+ RsaSsaPssPublicKeyToDerEncoding(rsa_e_padded.get()));
+
+ std::string expected_der_encoding = absl::HexStringToBytes(
+ "30820152303d06092a864886f70d01010a3030a00d300b0609608648016503040202a11a"
+ "301806092a864886f70d010108300b0609608648016503040202a2030201300382010f00"
+ "3082010a0282010100b259758bb02bc75b68b17612c9bf68c5fa05958a334c61e167bc20"
+ "bcc75757c126e89210b9df3989072cf6260e6883c7cd4af4d31dde9915b69b301fbef962"
+ "de8c71bd2db5ec625da259712f86a8dc3d241e9688c82391b7bf1ebc358311f55c26be91"
+ "0b76f61fea408ed692f1a9578a622c82c0fcf6f69ef3670e38bfc90f63da4f3bbbd088c8"
+ "ae7a3c5a55e66f6474d562d32cce7b7edd7cf0149ca0e96cb6525e81fbba815a8f12748e"
+ "34e5135f572b2e17b7ba430081597e6fb9033c005884d5935118c60d75b010f6fece7ecd"
+ "cc1cb7d58d1389693d43377f4f3de949cb1e4105e792b96d7f04b0cd262ac33cffc5a890"
+ "d267425e61c19e9363550f22850203010001");
+
+ EXPECT_EQ(result_e_not_padded, expected_der_encoding);
+ EXPECT_EQ(result_e_padded, expected_der_encoding);
+}
+
+// The public key used in this test is taken from the test vectors found here:
+// https://www.ietf.org/archive/id/draft-ietf-privacypass-protocol-10.html#name-issuance-protocol-2-blind-rs
+TEST(CryptoUtilsTest, IetfPrivacyPassBlindRsaPublicKeyToDerTest) {
+ RSAPublicKey public_key;
+ public_key.set_n(absl::HexStringToBytes(
+ "cb1aed6b6a95f5b1ce013a4cfcab25b94b2e64a23034e4250a7eab43c0df3a8c12993af1"
+ "2b111908d4b471bec31d4b6c9ad9cdda90612a2ee903523e6de5a224d6b02f09e5c374d0"
+ "cfe01d8f529c500a78a2f67908fa682b5a2b430c81eaf1af72d7b5e794fc98a313927687"
+ "9757ce453b526ef9bf6ceb99979b8423b90f4461a22af37aab0cf5733f7597abe44d31c7"
+ "32db68a181c6cbbe607d8c0e52e0655fd9996dc584eca0be87afbcd78a337d17b1dba9e8"
+ "28bbd81e291317144e7ff89f55619709b096cbb9ea474cead264c2073fe49740c01f00e1"
+ "09106066983d21e5f83f086e2e823c879cd43cef700d2a352a9babd612d03cad02db134b"
+ "7e225a5f"));
+ public_key.set_e(absl::HexStringToBytes("010001"));
+ ASSERT_OK_AND_ASSIGN(bssl::UniquePtr<RSA> rsa,
+ AnonymousTokensRSAPublicKeyToRSA(public_key));
+ ASSERT_OK_AND_ASSIGN(std::string result,
+ RsaSsaPssPublicKeyToDerEncoding(rsa.get()));
+
+ std::string expected_der_encoding = absl::HexStringToBytes(
+ "30820152303d06092a864886f70d01010a3030a00d300b0609608648016503040202a11a"
+ "301806092a864886f70d010108300b0609608648016503040202a2030201300382010f00"
+ "3082010a0282010100cb1aed6b6a95f5b1ce013a4cfcab25b94b2e64a23034e4250a7eab"
+ "43c0df3a8c12993af12b111908d4b471bec31d4b6c9ad9cdda90612a2ee903523e6de5a2"
+ "24d6b02f09e5c374d0cfe01d8f529c500a78a2f67908fa682b5a2b430c81eaf1af72d7b5"
+ "e794fc98a3139276879757ce453b526ef9bf6ceb99979b8423b90f4461a22af37aab0cf5"
+ "733f7597abe44d31c732db68a181c6cbbe607d8c0e52e0655fd9996dc584eca0be87afbc"
+ "d78a337d17b1dba9e828bbd81e291317144e7ff89f55619709b096cbb9ea474cead264c2"
+ "073fe49740c01f00e109106066983d21e5f83f086e2e823c879cd43cef700d2a352a9bab"
+ "d612d03cad02db134b7e225a5f0203010001");
+
+ EXPECT_EQ(result, expected_der_encoding);
+}
+
using CreateTestKeyPairFunction =
absl::StatusOr<std::pair<RSAPublicKey, RSAPrivateKey>>();
diff --git a/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/constants.h b/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/constants.h
index 4f73afd..d020d5b 100644
--- a/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/constants.h
+++ b/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/constants.h
@@ -62,6 +62,20 @@
constexpr int kHkdfPublicMetadataInfoSizeInBytes = 5;
+// Object identifier for Rivest, Shamir, Adleman (RSA) Signature Scheme with
+// Appendix - Probabilistic Signature Scheme (RSASSA-PSS) defined here:
+// https://oidref.com/1.2.840.113549.1.1.10
+constexpr char kRsaSsaPssOid[] = "1.2.840.113549.1.1.10";
+
+// Object identifier for SHA384 defined here:
+// https://oidref.com/2.16.840.1.101.3.4.2.2
+constexpr char kSha384Oid[] = "2.16.840.1.101.3.4.2.2";
+
+// Object identifier for RSA algorithm that uses the Mask Generator Function 1
+// (MGF1) defined here:
+// https://oidref.com/1.2.840.113549.1.1.8
+constexpr char kRsaSsaPssMgf1Oid[] = "1.2.840.113549.1.1.8";
+
} // namespace anonymous_tokens
} // namespace private_membership
diff --git a/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/crypto_utils.cc b/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/crypto_utils.cc
index 16dad4f..6a620f5 100644
--- a/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/crypto_utils.cc
+++ b/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/crypto_utils.cc
@@ -19,6 +19,7 @@
#include <cstdint>
#include <iterator>
+#include <memory>
#include <string>
#include <utility>
#include <vector>
@@ -30,8 +31,10 @@
#include "quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/constants.h"
#include "quiche/blind_sign_auth/anonymous_tokens/cpp/shared/status_utils.h"
#include "quiche/blind_sign_auth/anonymous_tokens/proto/anonymous_tokens.pb.h"
+#include "openssl/bytestring.h"
#include "openssl/err.h"
#include "openssl/hkdf.h"
+#include "openssl/mem.h"
#include "openssl/rand.h"
#include "openssl/rsa.h"
@@ -93,6 +96,27 @@
} // namespace internal
+namespace {
+
+// Marshals an RSA public key in the DER format.
+absl::StatusOr<std::string> MarshalRsaPublicKey(const RSA* rsa) {
+ uint8_t* rsa_public_key_bytes;
+ size_t rsa_public_key_bytes_len = 0;
+ if (!RSA_public_key_to_bytes(&rsa_public_key_bytes, &rsa_public_key_bytes_len,
+ rsa)) {
+ return absl::InvalidArgumentError(absl::StrCat(
+ "Failed to marshall rsa public key to a DER encoded RSAPublicKey "
+ "structure (RFC 8017): ",
+ GetSslErrors()));
+ }
+ std::string rsa_public_key_str(reinterpret_cast<char*>(rsa_public_key_bytes),
+ rsa_public_key_bytes_len);
+ OPENSSL_free(rsa_public_key_bytes);
+ return rsa_public_key_str;
+}
+
+} // namespace
+
absl::StatusOr<BnCtxPtr> GetAndStartBigNumCtx() {
// Create context to be used in intermediate computation.
BnCtxPtr bn_ctx = BnCtxPtr(BN_CTX_new());
@@ -223,10 +247,10 @@
"Cannot add word to compute RSA sqrt(2): ", GetSslErrors()));
}
if (BN_lshift(sqrt2.get(), sqrt2.get(), 32) != 1) {
- return absl::InternalError(absl::StrCat(
- "Cannot shift to compute RSA sqrt(2): ", GetSslErrors()));
+ return absl::InternalError(absl::StrCat(
+ "Cannot shift to compute RSA sqrt(2): ", GetSslErrors()));
}
- if (BN_add_word(sqrt2.get(), internal::kBoringSSLRSASqrtTwo[i+1]) != 1) {
+ if (BN_add_word(sqrt2.get(), internal::kBoringSSLRSASqrtTwo[i + 1]) != 1) {
return absl::InternalError(absl::StrCat(
"Cannot add word to compute RSA sqrt(2): ", GetSslErrors()));
}
@@ -523,5 +547,126 @@
return absl::OkStatus();
}
+absl::StatusOr<std::string> RsaSsaPssPublicKeyToDerEncoding(const RSA* rsa) {
+ if (rsa == NULL) {
+ return absl::InvalidArgumentError("Public Key rsa is null.");
+ }
+ // Create DER encoded RSA public key string.
+ ANON_TOKENS_ASSIGN_OR_RETURN(std::string rsa_public_key_str,
+ MarshalRsaPublicKey(rsa));
+ // Main CRYPTO ByteBuilder object cbb which will be passed to CBB_finish to
+ // finalize and output the DER encoding of the RsaSsaPssPublicKey.
+ bssl::ScopedCBB cbb;
+ // initial_capacity only serves as a hint.
+ if (!CBB_init(cbb.get(), /*initial_capacity=*/2 * RSA_size(rsa))) {
+ return absl::InternalError("CBB_init() failed.");
+ }
+
+ // Temporary CBB objects to write ASN1 sequences and object identifiers into.
+ CBB outer_seq, inner_seq, param_seq, sha384_seq, mgf1_seq, mgf1_sha384_seq;
+ CBB param0_tag, param1_tag, param2_tag;
+ CBB rsassa_pss_oid, sha384_oid, mgf1_oid, mgf1_sha384_oid;
+ CBB public_key_bit_str_cbb;
+ // RsaSsaPssPublicKey ASN.1 structure example:
+ //
+ // SEQUENCE { # outer_seq
+ // SEQUENCE { # inner_seq
+ // OBJECT_IDENTIFIER{1.2.840.113549.1.1.10} # rsassa_pss_oid
+ // SEQUENCE { # param_seq
+ // [0] { # param0_tag
+ // { # sha384_seq
+ // OBJECT_IDENTIFIER{2.16.840.1.101.3.4.2.2} # sha384_oid
+ // }
+ // }
+ // [1] { # param1_tag
+ // { # mgf1_seq
+ // OBJECT_IDENTIFIER{1.2.840.113549.1.1.8} # mgf1_oid
+ // { # mgf1_sha384_seq
+ // OBJECT_IDENTIFIER{2.16.840.1.101.3.4.2.2}# mgf1_sha384_oid
+ // }
+ // }
+ // }
+ // [2] { # param2_tag
+ // INTEGER { 48 } # salt length
+ // }
+ // }
+ // }
+ // BIT STRING { # public_key_bit_str_cbb
+ // 0 # unused bits
+ // der_encoded_rsa_public_key_structure
+ // }
+ // }
+ //
+ // Start with the outer sequence.
+ if (!CBB_add_asn1(cbb.get(), &outer_seq, CBS_ASN1_SEQUENCE) ||
+ // The outer sequence consists of two parts; the inner sequence and the
+ // encoded rsa public key.
+ //
+ // Add the inner sequence to the outer sequence.
+ !CBB_add_asn1(&outer_seq, &inner_seq, CBS_ASN1_SEQUENCE) ||
+ // Add object identifier for RSASSA-PSS algorithm to the inner sequence.
+ !CBB_add_asn1(&inner_seq, &rsassa_pss_oid, CBS_ASN1_OBJECT) ||
+ !CBB_add_asn1_oid_from_text(&rsassa_pss_oid, kRsaSsaPssOid,
+ strlen(kRsaSsaPssOid)) ||
+ // Add a parameter sequence to the inner sequence.
+ !CBB_add_asn1(&inner_seq, ¶m_seq, CBS_ASN1_SEQUENCE) ||
+ // SHA384 hash function algorithm identifier will be parameter 0 in the
+ // parameter sequence.
+ !CBB_add_asn1(¶m_seq, ¶m0_tag,
+ CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0) ||
+ !CBB_add_asn1(¶m0_tag, &sha384_seq, CBS_ASN1_SEQUENCE) ||
+ // Add SHA384 object identifier to finish the SHA384 algorithm identifier
+ // and parameter 0.
+ !CBB_add_asn1(&sha384_seq, &sha384_oid, CBS_ASN1_OBJECT) ||
+ !CBB_add_asn1_oid_from_text(&sha384_oid, kSha384Oid,
+ strlen(kSha384Oid)) ||
+ // mgf1-SHA384 algorithm identifier as parameter 1 to the parameter
+ // sequence.
+ !CBB_add_asn1(¶m_seq, ¶m1_tag,
+ CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 1) ||
+ !CBB_add_asn1(¶m1_tag, &mgf1_seq, CBS_ASN1_SEQUENCE) ||
+ // Add mgf1 object identifier to the mgf1-SHA384 algorithm identifier.
+ !CBB_add_asn1(&mgf1_seq, &mgf1_oid, CBS_ASN1_OBJECT) ||
+ !CBB_add_asn1_oid_from_text(&mgf1_oid, kRsaSsaPssMgf1Oid,
+ strlen(kRsaSsaPssMgf1Oid)) ||
+ // Add SHA384 algorithm identifier to the mgf1-SHA384 algorithm
+ // identifier.
+ !CBB_add_asn1(&mgf1_seq, &mgf1_sha384_seq, CBS_ASN1_SEQUENCE) ||
+ // Add SHA384 object identifier to finish SHA384 algorithm identifier,
+ // mgf1-SHA384 algorithm identifier and parameter 1.
+ !CBB_add_asn1(&mgf1_sha384_seq, &mgf1_sha384_oid, CBS_ASN1_OBJECT) ||
+ !CBB_add_asn1_oid_from_text(&mgf1_sha384_oid, kSha384Oid,
+ strlen(kSha384Oid)) ||
+ // Add salt length as parameter 2 to the parameter sequence to finish the
+ // parameter sequence and the inner sequence.
+ !CBB_add_asn1(¶m_seq, ¶m2_tag,
+ CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 2) ||
+ !CBB_add_asn1_int64(¶m2_tag, kSaltLengthInBytes48) ||
+ // Add public key to the outer sequence as an ASN1 bitstring.
+ !CBB_add_asn1(&outer_seq, &public_key_bit_str_cbb, CBS_ASN1_BITSTRING) ||
+ !CBB_add_u8(&public_key_bit_str_cbb, 0 /* no unused bits */) ||
+ !CBB_add_bytes(
+ &public_key_bit_str_cbb,
+ reinterpret_cast<const uint8_t*>(rsa_public_key_str.data()),
+ rsa_public_key_str.size())) {
+ return absl::InvalidArgumentError(
+ "Failed to generate encoded self-signed certificate");
+ }
+ // Finish creating the DER-encoding of RsaSsaPssPublicKey.
+ uint8_t* rsa_ssa_pss_public_key_der;
+ size_t rsa_ssa_pss_public_key_der_len;
+ if (!CBB_finish(cbb.get(), &rsa_ssa_pss_public_key_der,
+ &rsa_ssa_pss_public_key_der_len)) {
+ return absl::InternalError("CBB_finish() failed.");
+ }
+ std::string rsa_ssa_pss_public_key_der_str(
+ reinterpret_cast<const char*>(rsa_ssa_pss_public_key_der),
+ rsa_ssa_pss_public_key_der_len);
+ // Free memory.
+ OPENSSL_free(rsa_ssa_pss_public_key_der);
+ // Return the DER encoding as string.
+ return rsa_ssa_pss_public_key_der_str;
+}
+
} // namespace anonymous_tokens
} // namespace private_membership
diff --git a/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/crypto_utils.h b/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/crypto_utils.h
index 109476f..292c969 100644
--- a/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/crypto_utils.h
+++ b/quiche/blind_sign_auth/anonymous_tokens/cpp/crypto/crypto_utils.h
@@ -183,6 +183,22 @@
absl::string_view message,
std::optional<absl::string_view> public_metadata = std::nullopt);
+// This method outputs a DER encoding of RSASSA-PSS (RSA Signature Scheme with
+// Appendix - Probabilistic Signature Scheme) Public Key as described here
+// https://datatracker.ietf.org/doc/html/rfc3447.html using the object
+// identifier(s) here: https://oidref.com/1.2.840.113549.1.1.10 and using a
+// fixed salt length of 48 bytes, SHA384 as the signature's hash function as
+// well as the hash function that the signature's mask generating function is
+// based on. A publicly availble equivalent function is available in Goa here:
+// https://github.com/cloudflare/pat-go/blob/11579ba5b0b9b77d3e8e3d5247a98811227ac82e/x509util.go#L56
+//
+// copybara:strip_begin(internal comment)
+// This method serves as a C++ version for the following Goa method:
+// http://google3/privacy/net/boq/common/tokens/token_types.go;l=85;rcl=515461856
+// copybara:strip_end
+absl::StatusOr<std::string> QUICHE_EXPORT RsaSsaPssPublicKeyToDerEncoding(
+ const RSA* rsa);
+
} // namespace anonymous_tokens
} // namespace private_membership