Skip to content

Commit 32c32b8

Browse files
committed
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>
1 parent b70e12f commit 32c32b8

6 files changed

Lines changed: 169 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;
@@ -5684,13 +5688,74 @@ bool ReadRsaPssParams(const EVP_PKEY* pkey, Rsa::PssParams* params) {
56845688

56855689
return true;
56865690
}
5691+
5692+
bool SetRsaPssHashAlgorithm(X509_ALGOR** out, const Digest& digest) {
5693+
if (EVP_MD_is_a(digest.get(), "SHA1")) return true;
5694+
5695+
X509AlgorPointer algorithm(X509_ALGOR_new());
5696+
if (!algorithm) return false;
5697+
X509_ALGOR_set_md(algorithm.get(), digest.get());
5698+
*out = algorithm.release();
5699+
return true;
5700+
}
5701+
5702+
bool SetRsaPssMaskGenAlgorithm(X509_ALGOR** out, const Digest& digest) {
5703+
if (EVP_MD_is_a(digest.get(), "SHA1")) return true;
5704+
5705+
X509AlgorPointer hash_algorithm(X509_ALGOR_new());
5706+
if (!hash_algorithm) return false;
5707+
X509_ALGOR_set_md(hash_algorithm.get(), digest.get());
5708+
5709+
ASN1StringPointer hash_algorithm_der(ASN1_item_pack(
5710+
hash_algorithm.get(), ASN1_ITEM_rptr(X509_ALGOR), nullptr));
5711+
if (!hash_algorithm_der) return false;
5712+
5713+
X509AlgorPointer algorithm(X509_ALGOR_new());
5714+
if (!algorithm || X509_ALGOR_set0(algorithm.get(),
5715+
OBJ_nid2obj(NID_mgf1),
5716+
V_ASN1_SEQUENCE,
5717+
hash_algorithm_der.get()) != 1) {
5718+
return false;
5719+
}
5720+
hash_algorithm_der.release();
5721+
*out = algorithm.release();
5722+
return true;
5723+
}
5724+
5725+
ASN1StringPointer EncodeRsaPssParams(const Rsa::PssParams& params) {
5726+
const Digest digest = Digest::FromName(params.digest.data());
5727+
if (!digest) return {};
5728+
5729+
const Digest mgf1_digest = params.mgf1_digest
5730+
? Digest::FromName(params.mgf1_digest->data())
5731+
: digest;
5732+
if (!mgf1_digest) return {};
5733+
5734+
RsaPssParamsPointer pss(RSA_PSS_PARAMS_new());
5735+
if (!pss || !SetRsaPssHashAlgorithm(&pss->hashAlgorithm, digest) ||
5736+
!SetRsaPssMaskGenAlgorithm(&pss->maskGenAlgorithm, mgf1_digest)) {
5737+
return {};
5738+
}
5739+
5740+
if (params.salt_length != 20) {
5741+
pss->saltLength = ASN1_INTEGER_new();
5742+
if (pss->saltLength == nullptr ||
5743+
ASN1_INTEGER_set_int64(pss->saltLength, params.salt_length) != 1) {
5744+
return {};
5745+
}
5746+
}
5747+
5748+
return ASN1StringPointer(
5749+
ASN1_item_pack(pss.get(), ASN1_ITEM_rptr(RSA_PSS_PARAMS), nullptr));
5750+
}
56875751
} // namespace
56885752

56895753
Rsa::Rsa() : rsa_(false) {}
56905754

56915755
Rsa::Rsa(const EVP_PKEY* pkey) : Rsa() {
56925756
const int type = EVPKeyPointer::id(pkey);
56935757
if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return;
5758+
rsa_pss_ = type == EVP_PKEY_RSA_PSS;
56945759
if (!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_N, &n_) ||
56955760
!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_E, &e_)) {
56965761
return;
@@ -5786,7 +5851,35 @@ BIOPointer Rsa::derPublicKey() const {
57865851
if (!bio) return {};
57875852
#if NCRYPTO_USE_OPENSSL3_PROVIDER
57885853
auto pkey = EVPKeyPointer::NewRSA(*this);
5789-
if (!pkey || i2d_PUBKEY_bio(bio.get(), pkey.get()) != 1) return {};
5854+
if (!pkey) return {};
5855+
if (!rsa_pss_) {
5856+
if (i2d_PUBKEY_bio(bio.get(), pkey.get()) != 1) return {};
5857+
return bio;
5858+
}
5859+
5860+
X509_PUBKEY* raw_pubkey = nullptr;
5861+
const int result = X509_PUBKEY_set(&raw_pubkey, pkey.get());
5862+
X509PubkeyPointer pubkey(raw_pubkey);
5863+
if (result != 1) return {};
5864+
5865+
int parameter_type = V_ASN1_UNDEF;
5866+
ASN1StringPointer parameters;
5867+
if (pss_params_) {
5868+
parameters = EncodeRsaPssParams(*pss_params_);
5869+
if (!parameters) return {};
5870+
parameter_type = V_ASN1_SEQUENCE;
5871+
}
5872+
5873+
if (X509_PUBKEY_set0_param(pubkey.get(),
5874+
OBJ_nid2obj(NID_rsaEncryption),
5875+
parameter_type,
5876+
parameters.get(),
5877+
nullptr,
5878+
0) != 1) {
5879+
return {};
5880+
}
5881+
parameters.release();
5882+
if (i2d_X509_PUBKEY_bio(bio.get(), pubkey.get()) != 1) return {};
57905883
#else
57915884
if (rsa_ == nullptr || i2d_RSA_PUBKEY_bio(bio.get(), rsa_) != 1) return {};
57925885
#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: 24 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,29 @@ const ca = readFileSync(fixtures.path('keys', 'ca1-cert.pem'));
2728

2829
const privateKey = createPrivateKey(key);
2930

31+
if (!process.features.openssl_is_boringssl) {
32+
for (const [name, length, digest] of [
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+
const pssCert = new X509Certificate(
45+
readFileSync(fixtures.path('keys', name)));
46+
const pubkey = pssCert.toLegacyObject().pubkey;
47+
assert.strictEqual(pubkey.length, length);
48+
assert.strictEqual(
49+
createHash('sha256').update(pubkey).digest('hex'),
50+
digest);
51+
}
52+
}
53+
3054
[1, {}, false, null].forEach((i) => {
3155
assert.throws(() => new X509Certificate(i), {
3256
code: 'ERR_INVALID_ARG_TYPE'

0 commit comments

Comments
 (0)