Skip to content

Commit b65a8f3

Browse files
panvaaduh95
authored andcommitted
crypto: preserve RSA-PSS legacy pubkey DER
Recreate the historical rsaEncryption AlgorithmIdentifier used by X509Certificate legacy pubkey output for restricted and unrestricted RSA-PSS keys without relying on deprecated RSA APIs. Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #64547 Backport-PR-URL: #65087 Refs: #64211 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 49f2ae2 commit b65a8f3

6 files changed

Lines changed: 182 additions & 1 deletion

File tree

deps/ncrypto/ncrypto.cc

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,11 @@ const EVP_MD* GetDigestCtxMd(const EVP_MD_CTX* ctx) {
9393
}
9494

9595
#if NCRYPTO_USE_OPENSSL3_PROVIDER
96+
using ASN1StringPointer = DeleteFnPtr<ASN1_STRING, ASN1_STRING_free>;
9697
using OSSLParamBldPointer = DeleteFnPtr<OSSL_PARAM_BLD, OSSL_PARAM_BLD_free>;
98+
using RsaPssParamsPointer = DeleteFnPtr<RSA_PSS_PARAMS, RSA_PSS_PARAMS_free>;
99+
using X509AlgorPointer = DeleteFnPtr<X509_ALGOR, X509_ALGOR_free>;
100+
using X509PubkeyPointer = DeleteFnPtr<X509_PUBKEY, X509_PUBKEY_free>;
97101
struct OSSLParamDeleter {
98102
void operator()(OSSL_PARAM* params) const {
99103
if (params == nullptr) return;
@@ -5702,13 +5706,74 @@ bool ReadRsaPssParams(const EVP_PKEY* pkey, Rsa::PssParams* params) {
57025706

57035707
return true;
57045708
}
5709+
5710+
bool SetRsaPssHashAlgorithm(X509_ALGOR** out, const Digest& digest) {
5711+
if (EVP_MD_is_a(digest.get(), "SHA1")) return true;
5712+
5713+
X509AlgorPointer algorithm(X509_ALGOR_new());
5714+
if (!algorithm) return false;
5715+
X509_ALGOR_set_md(algorithm.get(), digest.get());
5716+
*out = algorithm.release();
5717+
return true;
5718+
}
5719+
5720+
bool SetRsaPssMaskGenAlgorithm(X509_ALGOR** out, const Digest& digest) {
5721+
if (EVP_MD_is_a(digest.get(), "SHA1")) return true;
5722+
5723+
X509AlgorPointer hash_algorithm(X509_ALGOR_new());
5724+
if (!hash_algorithm) return false;
5725+
X509_ALGOR_set_md(hash_algorithm.get(), digest.get());
5726+
5727+
ASN1StringPointer hash_algorithm_der(ASN1_item_pack(
5728+
hash_algorithm.get(), ASN1_ITEM_rptr(X509_ALGOR), nullptr));
5729+
if (!hash_algorithm_der) return false;
5730+
5731+
X509AlgorPointer algorithm(X509_ALGOR_new());
5732+
if (!algorithm || X509_ALGOR_set0(algorithm.get(),
5733+
OBJ_nid2obj(NID_mgf1),
5734+
V_ASN1_SEQUENCE,
5735+
hash_algorithm_der.get()) != 1) {
5736+
return false;
5737+
}
5738+
hash_algorithm_der.release();
5739+
*out = algorithm.release();
5740+
return true;
5741+
}
5742+
5743+
ASN1StringPointer EncodeRsaPssParams(const Rsa::PssParams& params) {
5744+
const Digest digest = Digest::FromName(params.digest.data());
5745+
if (!digest) return {};
5746+
5747+
const Digest mgf1_digest = params.mgf1_digest
5748+
? Digest::FromName(params.mgf1_digest->data())
5749+
: digest;
5750+
if (!mgf1_digest) return {};
5751+
5752+
RsaPssParamsPointer pss(RSA_PSS_PARAMS_new());
5753+
if (!pss || !SetRsaPssHashAlgorithm(&pss->hashAlgorithm, digest) ||
5754+
!SetRsaPssMaskGenAlgorithm(&pss->maskGenAlgorithm, mgf1_digest)) {
5755+
return {};
5756+
}
5757+
5758+
if (params.salt_length != 20) {
5759+
pss->saltLength = ASN1_INTEGER_new();
5760+
if (pss->saltLength == nullptr ||
5761+
ASN1_INTEGER_set_int64(pss->saltLength, params.salt_length) != 1) {
5762+
return {};
5763+
}
5764+
}
5765+
5766+
return ASN1StringPointer(
5767+
ASN1_item_pack(pss.get(), ASN1_ITEM_rptr(RSA_PSS_PARAMS), nullptr));
5768+
}
57055769
} // namespace
57065770

57075771
Rsa::Rsa() : rsa_(false) {}
57085772

57095773
Rsa::Rsa(const EVP_PKEY* pkey) : Rsa() {
57105774
const int type = EVPKeyPointer::id(pkey);
57115775
if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return;
5776+
rsa_pss_ = type == EVP_PKEY_RSA_PSS;
57125777
if (!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_N, &n_) ||
57135778
!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_E, &e_)) {
57145779
return;
@@ -5804,7 +5869,35 @@ BIOPointer Rsa::derPublicKey() const {
58045869
if (!bio) return {};
58055870
#if NCRYPTO_USE_OPENSSL3_PROVIDER
58065871
auto pkey = EVPKeyPointer::NewRSA(*this);
5807-
if (!pkey || i2d_PUBKEY_bio(bio.get(), pkey.get()) != 1) return {};
5872+
if (!pkey) return {};
5873+
if (!rsa_pss_) {
5874+
if (i2d_PUBKEY_bio(bio.get(), pkey.get()) != 1) return {};
5875+
return bio;
5876+
}
5877+
5878+
X509_PUBKEY* raw_pubkey = nullptr;
5879+
const int result = X509_PUBKEY_set(&raw_pubkey, pkey.get());
5880+
X509PubkeyPointer pubkey(raw_pubkey);
5881+
if (result != 1) return {};
5882+
5883+
int parameter_type = V_ASN1_UNDEF;
5884+
ASN1StringPointer parameters;
5885+
if (pss_params_) {
5886+
parameters = EncodeRsaPssParams(*pss_params_);
5887+
if (!parameters) return {};
5888+
parameter_type = V_ASN1_SEQUENCE;
5889+
}
5890+
5891+
if (X509_PUBKEY_set0_param(pubkey.get(),
5892+
OBJ_nid2obj(NID_rsaEncryption),
5893+
parameter_type,
5894+
parameters.get(),
5895+
nullptr,
5896+
0) != 1) {
5897+
return {};
5898+
}
5899+
parameters.release();
5900+
if (i2d_X509_PUBKEY_bio(bio.get(), pubkey.get()) != 1) return {};
58085901
#else
58095902
if (rsa_ == nullptr || i2d_RSA_PUBKEY_bio(bio.get(), rsa_) != 1) return {};
58105903
#endif

deps/ncrypto/ncrypto.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,7 @@ class Rsa final {
639639
private:
640640
#if NCRYPTO_USE_OPENSSL3_PROVIDER
641641
bool rsa_ = false;
642+
bool rsa_pss_ = false;
642643
DeleteFnPtr<BIGNUM, BN_free> n_;
643644
DeleteFnPtr<BIGNUM, BN_free> e_;
644645
DeleteFnPtr<BIGNUM, BN_clear_free> d_;

test/fixtures/keys/Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ all: \
7171
rsa_private_4096.pem \
7272
rsa_public_2048.pem \
7373
rsa_public_4096.pem \
74+
rsa_pss_cert_2048.pem \
75+
rsa_pss_cert_2048_sha256_sha256_16.pem \
7476
rsa_pss_private_2048.pem \
7577
rsa_pss_private_2048_sha256_sha256_16.pem \
7678
rsa_pss_private_2048_sha512_sha256_20.pem \
@@ -918,6 +920,12 @@ rsa_pss_private_2048_sha512_sha256_20.pem:
918920
rsa_pss_private_2048_sha1_sha1_20.pem:
919921
openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 -pkeyopt rsa_pss_keygen_md:sha1 -pkeyopt rsa_pss_keygen_mgf1_md:sha1 -pkeyopt rsa_pss_keygen_saltlen:20 -out rsa_pss_private_2048_sha1_sha1_20.pem
920922

923+
rsa_pss_cert_2048.pem: rsa_pss_private_2048.pem
924+
openssl req -new -x509 -key rsa_pss_private_2048.pem -subj "/CN=Node.js" -days 36500 -set_serial 1 -out rsa_pss_cert_2048.pem
925+
926+
rsa_pss_cert_2048_sha256_sha256_16.pem: rsa_pss_private_2048_sha256_sha256_16.pem
927+
openssl req -new -x509 -key rsa_pss_private_2048_sha256_sha256_16.pem -subj "/CN=Node.js" -days 36500 -set_serial 1 -out rsa_pss_cert_2048_sha256_sha256_16.pem
928+
921929
rsa_pss_public_2048.pem: rsa_pss_private_2048.pem
922930
openssl pkey -in rsa_pss_private_2048.pem -pubout -out rsa_pss_public_2048.pem
923931

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDWjCCAg6gAwIBAgIBATBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUA
3+
oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwEjEQMA4GA1UEAwwH
4+
Tm9kZS5qczAgFw0yNjA3MTYyMjE4MzJaGA8yMTI2MDYyMjIyMTgzMlowEjEQMA4G
5+
A1UEAwwHTm9kZS5qczCCASAwCwYJKoZIhvcNAQEKA4IBDwAwggEKAoIBAQDLg4x1
6+
Lzg+WAjkJEtt1u531G7u+msWvZg01SZ1PsPdiACmK/wKPMpcuJUOdswRP6XCa1qD
7+
8RxmzFUIqi+y7fSlgBIGuIc6IxVi6L35lomhD3IjqU6MbVFu5QElc1KMcEnGoGqZ
8+
yBk5vWZ1Gv95fOkAxYneF+4uiNTb7NRInf+u96l6lwsUbar2cDT48myQns+eHcvT
9+
J2e/dTeF24yfk5V+b8nHUU/JmkPXzi0l9/434xjezeEhx+46cv0+XhE8Z5pMLhPW
10+
jjrQ1obpsbtzZaGDo05pWBoUhqI1utNM0KlWN5PJqWxNdiVBDeGIqOy3RDul8USQ
11+
kOjyqKotp3UyyXn3AgMBAAGjUzBRMB0GA1UdDgQWBBQDCGflRn9LTmklPpSFii4J
12+
fQiQjTAfBgNVHSMEGDAWgBQDCGflRn9LTmklPpSFii4JfQiQjTAPBgNVHRMBAf8E
13+
BTADAQH/MEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG
14+
9w0BAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAsXJk8iu+XNSmIheNPvzfSoNp
15+
7kkFqFjWRwo/a4vXb4Y7DRDsMeUM4dGNtN75Gj5bppnYtQ3ku/DWGLrVRPGs/sRx
16+
itOJxvJzPNCv4yYUwm8p8ua7l1Sdmrder84XTG/Dm3dOoUOxCmEQzzjghXE0mRih
17+
UC4aPyOGNHNlQg5YyEmfEnltlL12ls8cHE+5R56xeI34T/MukH0NVf7wNNfqrMqy
18+
PrrXG79+D3+Jeu3ddtwvoKQyQ/VuszJGjdplkDodttyTfu3GOgC/bbR9iz2GkcTV
19+
NEkjJHenMzpoAoNZukeuClOHF6NT3hBnKPmcZYlLPgIY1iOBYoGwctgb5pPxNg==
20+
-----END CERTIFICATE-----
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDkDCCAkSgAwIBAgIBATBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUA
3+
oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCARAwEjEQMA4GA1UEAwwH
4+
Tm9kZS5qczAgFw0yNjA3MTYyMjAyMzVaGA8yMTI2MDYyMjIyMDIzNVowEjEQMA4G
5+
A1UEAwwHTm9kZS5qczCCAVYwQQYJKoZIhvcNAQEKMDSgDzANBglghkgBZQMEAgEF
6+
AKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIDAgEQA4IBDwAwggEKAoIB
7+
AQDfqNM4C+QtD73iILqOkqfV8ha3O19jpX8UujIk1Z72bbbuwEzh0+sBw0dD0N8C
8+
gkXnePOEEd6q7HNmbyCNqRpDK6NDvaCMDWgEaD/PlHkRntvKh81IXSMC5imjRfOc
9+
ZIE/Gnw7h8tanab0n75+ODvLJrmEWUG2q79Im1mWMx7Spod+Np6XEY+7I7nAUUWi
10+
vr35Yx5DeyxY8rxFGpsLtGsi7JNQO4aHyeBpj8tz0Fhv23uPywE2nGmPHfnkXWbr
11+
TcHGbzYBgEbeSH9KUkRwczqDXNOPhtfaEHEFTm0MoeKCnJe1VOjSywev77dV1KZf
12+
pVh3Kh0ZRQIe9YOVJhj4lMx3AgMBAAGjUzBRMB0GA1UdDgQWBBRrGC6N4gEM/Il9
13+
PMkKs+dhCbldkjAfBgNVHSMEGDAWgBRrGC6N4gEM/Il9PMkKs+dhCbldkjAPBgNV
14+
HRMBAf8EBTADAQH/MEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAa
15+
BgkqhkiG9w0BAQgwDQYJYIZIAWUDBAIBBQCiAwIBEAOCAQEA1SP4HNSgQm2OpuRb
16+
VjEDyVANpCyuKjUFY0FAwart4YSKHwERuR5VccugGwlbnH0vKQ2uwTB/a5BlXssg
17+
0KB8qjDoOoxzhgZTA7yuHuQLpgCQhd00ORpxFYI1CyB+HywrpbuV4LHJoZjO7B6O
18+
Z0PPRs77023WkT/O4KRrFH8uaMgPMwUIjIGXzSp+TYObLnYTWmGyJ7rJVwOPH30z
19+
+BY++W0ZhkCuJveajuF3vGpA4o/lA0kcf6Hz2Prl28kHu0hOpZNTx2zMl+GFtlWE
20+
TIybjAvbA4TJW0rAURRuP4lhYsPJAnZDAnxHFvLeLKypfctv7eAc82U8rVjT+DIA
21+
NjayPQ==
22+
-----END CERTIFICATE-----

test/parallel/test-crypto-x509.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ if (!common.hasCrypto)
77

88
const {
99
X509Certificate,
10+
createHash,
1011
createPrivateKey,
1112
generateKeyPairSync,
1213
createSign,
@@ -27,6 +28,42 @@ const ca = readFileSync(fixtures.path('keys', 'ca1-cert.pem'));
2728

2829
const privateKey = createPrivateKey(key);
2930

31+
if (!process.features.openssl_is_boringssl) {
32+
const expectedPubkeys = hasOpenSSL3 ? [
33+
[
34+
'rsa_pss_cert_2048.pem',
35+
292,
36+
'dff998a209bfa2e6ded1208c6e57f5b6bdedfa44b631265e3e244f38e637f6e4',
37+
],
38+
[
39+
'rsa_pss_cert_2048_sha256_sha256_16.pem',
40+
342,
41+
'da0bcd53fbe3969c7cc2730f86abc34e0e1c340264bbdfa3faf01484c2eeece0',
42+
],
43+
] : [
44+
[
45+
'rsa_pss_cert_2048.pem',
46+
294,
47+
'4d4f2f076aced4f0df922b84b466b0a60ba4cb50a23d695ae12ddc5fff7aca14',
48+
],
49+
[
50+
'rsa_pss_cert_2048_sha256_sha256_16.pem',
51+
294,
52+
'd37942c3bd02bc25c724fcd31efd647824e536c13d62d9ad0b5db8c0900d3cba',
53+
],
54+
];
55+
56+
for (const [name, length, digest] of expectedPubkeys) {
57+
const pssCert = new X509Certificate(
58+
readFileSync(fixtures.path('keys', name)));
59+
const pubkey = pssCert.toLegacyObject().pubkey;
60+
assert.strictEqual(pubkey.length, length);
61+
assert.strictEqual(
62+
createHash('sha256').update(pubkey).digest('hex'),
63+
digest);
64+
}
65+
}
66+
3067
[1, {}, false, null].forEach((i) => {
3168
assert.throws(() => new X509Certificate(i), {
3269
code: 'ERR_INVALID_ARG_TYPE'

0 commit comments

Comments
 (0)