Skip to content

Commit 34c627e

Browse files
tniessentargos
authored andcommitted
crypto: add RSA-PSS params to asymmetricKeyDetails
Fixes: #39837 Refs: openssl/openssl#10568 PR-URL: #39851 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com>
1 parent 3a8f77a commit 34c627e

8 files changed

+200
-8
lines changed

doc/api/crypto.md

+14-2
Original file line numberDiff line numberDiff line change
@@ -1908,11 +1908,20 @@ const {
19081908
### `keyObject.asymmetricKeyDetails`
19091909
<!-- YAML
19101910
added: v15.7.0
1911+
changes:
1912+
- version: REPLACEME
1913+
pr-url: https://github.com/nodejs/node/pull/39851
1914+
description: Expose `RSASSA-PSS-params` sequence parameters
1915+
for RSA-PSS keys.
19111916
-->
19121917

19131918
* {Object}
19141919
* `modulusLength`: {number} Key size in bits (RSA, DSA).
19151920
* `publicExponent`: {bigint} Public exponent (RSA).
1921+
* `hashAlgorithm`: {string} Name of the message digest (RSA-PSS).
1922+
* `mgf1HashAlgorithm`: {string} Name of the message digest used by
1923+
MGF1 (RSA-PSS).
1924+
* `saltLength`: {number} Minimal salt length in bytes (RSA-PSS).
19161925
* `divisorLength`: {number} Size of `q` in bits (DSA).
19171926
* `namedCurve`: {string} Name of the curve (EC).
19181927

@@ -1921,8 +1930,11 @@ this object contains information about the key. None of the information obtained
19211930
through this property can be used to uniquely identify a key or to compromise
19221931
the security of the key.
19231932

1924-
RSA-PSS parameters, DH, or any future key type details might be exposed via this
1925-
API using additional attributes.
1933+
For RSA-PSS keys, if the key material contains a `RSASSA-PSS-params` sequence,
1934+
the `hashAlgorithm`, `mgf1HashAlgorithm`, and `saltLength` properties will be
1935+
set.
1936+
1937+
Other key details might be exposed via this API using additional attributes.
19261938

19271939
### `keyObject.asymmetricKeyType`
19281940
<!-- YAML

src/crypto/crypto_rsa.cc

+78-4
Original file line numberDiff line numberDiff line change
@@ -547,10 +547,84 @@ Maybe<bool> GetRsaKeyDetail(
547547
reinterpret_cast<unsigned char*>(public_exponent.data());
548548
CHECK_EQ(BN_bn2binpad(e, data, len), len);
549549

550-
return target->Set(
551-
env->context(),
552-
env->public_exponent_string(),
553-
public_exponent.ToArrayBuffer());
550+
if (target
551+
->Set(
552+
env->context(),
553+
env->public_exponent_string(),
554+
public_exponent.ToArrayBuffer())
555+
.IsNothing()) {
556+
return Nothing<bool>();
557+
}
558+
559+
if (type == EVP_PKEY_RSA_PSS) {
560+
// Due to the way ASN.1 encoding works, default values are omitted when
561+
// encoding the data structure. However, there are also RSA-PSS keys for
562+
// which no parameters are set. In that case, the ASN.1 RSASSA-PSS-params
563+
// sequence will be missing entirely and RSA_get0_pss_params will return
564+
// nullptr. If parameters are present but all parameters are set to their
565+
// default values, an empty sequence will be stored in the ASN.1 structure.
566+
// In that case, RSA_get0_pss_params does not return nullptr but all fields
567+
// of the returned RSA_PSS_PARAMS will be set to nullptr.
568+
569+
const RSA_PSS_PARAMS* params = RSA_get0_pss_params(rsa);
570+
if (params != nullptr) {
571+
int hash_nid = NID_sha1;
572+
int mgf_nid = NID_mgf1;
573+
int mgf1_hash_nid = NID_sha1;
574+
int64_t salt_length = 20;
575+
576+
if (params->hashAlgorithm != nullptr) {
577+
hash_nid = OBJ_obj2nid(params->hashAlgorithm->algorithm);
578+
}
579+
580+
if (target
581+
->Set(
582+
env->context(),
583+
env->hash_algorithm_string(),
584+
OneByteString(env->isolate(), OBJ_nid2ln(hash_nid)))
585+
.IsNothing()) {
586+
return Nothing<bool>();
587+
}
588+
589+
if (params->maskGenAlgorithm != nullptr) {
590+
mgf_nid = OBJ_obj2nid(params->maskGenAlgorithm->algorithm);
591+
if (mgf_nid == NID_mgf1) {
592+
mgf1_hash_nid = OBJ_obj2nid(params->maskHash->algorithm);
593+
}
594+
}
595+
596+
// If, for some reason, the MGF is not MGF1, then the MGF1 hash function
597+
// is intentionally not added to the object.
598+
if (mgf_nid == NID_mgf1) {
599+
if (target
600+
->Set(
601+
env->context(),
602+
env->mgf1_hash_algorithm_string(),
603+
OneByteString(env->isolate(), OBJ_nid2ln(mgf1_hash_nid)))
604+
.IsNothing()) {
605+
return Nothing<bool>();
606+
}
607+
}
608+
609+
if (params->saltLength != nullptr) {
610+
if (ASN1_INTEGER_get_int64(&salt_length, params->saltLength) != 1) {
611+
ThrowCryptoError(env, ERR_get_error(), "ASN1_INTEGER_get_in64 error");
612+
return Nothing<bool>();
613+
}
614+
}
615+
616+
if (target
617+
->Set(
618+
env->context(),
619+
env->salt_length_string(),
620+
Number::New(env->isolate(), static_cast<double>(salt_length)))
621+
.IsNothing()) {
622+
return Nothing<bool>();
623+
}
624+
}
625+
}
626+
627+
return Just<bool>(true);
554628
}
555629

556630
namespace RSAAlg {

src/env.h

+3
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ constexpr size_t kFsStatsBufferLength =
268268
V(gid_string, "gid") \
269269
V(h2_string, "h2") \
270270
V(handle_string, "handle") \
271+
V(hash_algorithm_string, "hashAlgorithm") \
271272
V(help_text_string, "helpText") \
272273
V(homedir_string, "homedir") \
273274
V(host_string, "host") \
@@ -316,6 +317,7 @@ constexpr size_t kFsStatsBufferLength =
316317
V(message_port_string, "messagePort") \
317318
V(message_string, "message") \
318319
V(messageerror_string, "messageerror") \
320+
V(mgf1_hash_algorithm_string, "mgf1HashAlgorithm") \
319321
V(minttl_string, "minttl") \
320322
V(module_string, "module") \
321323
V(modulus_string, "modulus") \
@@ -385,6 +387,7 @@ constexpr size_t kFsStatsBufferLength =
385387
V(replacement_string, "replacement") \
386388
V(require_string, "require") \
387389
V(retry_string, "retry") \
390+
V(salt_length_string, "saltLength") \
388391
V(scheme_string, "scheme") \
389392
V(scopeid_string, "scopeid") \
390393
V(serial_number_string, "serialNumber") \

test/fixtures/keys/Makefile

+8
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@ all: \
6464
rsa_pss_private_2048.pem \
6565
rsa_pss_private_2048_sha256_sha256_16.pem \
6666
rsa_pss_private_2048_sha512_sha256_20.pem \
67+
rsa_pss_private_2048_sha1_sha1_20.pem \
6768
rsa_pss_public_2048.pem \
6869
rsa_pss_public_2048_sha256_sha256_16.pem \
6970
rsa_pss_public_2048_sha512_sha256_20.pem \
71+
rsa_pss_public_2048_sha1_sha1_20.pem \
7072
ed25519_private.pem \
7173
ed25519_public.pem \
7274
x25519_private.pem \
@@ -708,6 +710,9 @@ rsa_pss_private_2048_sha256_sha256_16.pem:
708710
rsa_pss_private_2048_sha512_sha256_20.pem:
709711
openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 -pkeyopt rsa_pss_keygen_md:sha512 -pkeyopt rsa_pss_keygen_mgf1_md:sha256 -pkeyopt rsa_pss_keygen_saltlen:20 -out rsa_pss_private_2048_sha512_sha256_20.pem
710712

713+
rsa_pss_private_2048_sha1_sha1_20.pem:
714+
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
715+
711716
rsa_pss_public_2048.pem: rsa_pss_private_2048.pem
712717
openssl pkey -in rsa_pss_private_2048.pem -pubout -out rsa_pss_public_2048.pem
713718

@@ -717,6 +722,9 @@ rsa_pss_public_2048_sha256_sha256_16.pem: rsa_pss_private_2048_sha256_sha256_16.
717722
rsa_pss_public_2048_sha512_sha256_20.pem: rsa_pss_private_2048_sha512_sha256_20.pem
718723
openssl pkey -in rsa_pss_private_2048_sha512_sha256_20.pem -pubout -out rsa_pss_public_2048_sha512_sha256_20.pem
719724

725+
rsa_pss_public_2048_sha1_sha1_20.pem: rsa_pss_private_2048_sha1_sha1_20.pem
726+
openssl pkey -in rsa_pss_private_2048_sha1_sha1_20.pem -pubout -out rsa_pss_public_2048_sha1_sha1_20.pem
727+
720728
ed25519_private.pem:
721729
openssl genpkey -algorithm ED25519 -out ed25519_private.pem
722730

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvQIBADANBgkqhkiG9w0BAQowAASCBKcwggSjAgEAAoIBAQCpdutzsPFQ1100
3+
ouR5aAwYry8aAtG0c+zX9UqNXGCpRDWzPPpXHUZSB1BmTTL4EhK2tkAfblYNqzRu
4+
CAYlKHbFpFLs2zLEorfp0WsFNPaBHE9JHpLIM4oXxPCUypZ7JAn56ZYonYCZ8Il5
5+
8SzD9aoF41RTEmpcx3XkL2RQa022RiSccYZKx/yzskUUAdTvTvYyujH1MkvsfVP+
6+
Ns5bRL6IVqowFd3xv6ctvfQMxz0rltgTC+wOm3CFtn+G63y6P/Z0U2DRdacsNkN6
7+
PFGXAIB0kSvKzs8gVocEBiSwMkcT/KD3R68PY18b2auqaGcm8gA+gaVJ36KAW4dO
8+
AjbY+YitAgMBAAECggEAfPvfFXln0Ra1gE+vMDdjzITPuWBg57Uj9fbMIEwEYnKT
9+
JHmRrNRDe9Y3HuxK7hjuQmFSE5xdzUD6rzgtyBP63TOfkV7tJ4dXGxS/2JxCPeDy
10+
PNxWp18Ttwoh4as0pudikDYN8DCRm3eC/TO5r2EtH6CVHZuUZI8bTMsDMiihrQ8F
11+
B8+KucBG5DDy/OlDeieAZxZA4Y0/c+W0DNZ/LIPGwaqMzYCSZJXyV0t33HytUwM2
12+
QZ+RbWqcUcrCI3lFAO8IyEULCi+RnSByZeJ0xwUkdQTI5jT6+G8BrO70Oiab8g+Q
13+
Rx2s7PxWpIMVS7/JD1PsL4hLrVh3uqh8PZl3/FG9IQKBgQDZWkOR2LA+ixmD6XJb
14+
Q+7zW2guHnK6wDrQFKmBGLaDdAER64WL1Unt6Umu7FPxth2niYMEgRexBgnj5hQN
15+
LfPYTiIeXs5ErrU96fVQABsV0Hra1M2Rhve5nynjFFpbHjDXtizzLpE30MsC7YkN
16+
EqD4YYzjWHrbk/UlQ7tx3eAvtQKBgQDHmNM4TRuyH2yaYxDqnho6fgJv7Z4KgbM0
17+
1wcUxi5kPDQsFtaVOzFhNserzsWvotQjLkC2+CK5qlCdm59ZlpUqszF6+YyUs5Gq
18+
WmHdqryduT1VxSV/pd6wGEQo27fxFV7LsT1JhVMh9Iri8MK0b1BD6+kVUf5NcKDB
19+
Od2o8A1gGQKBgA5Y3Pj1mrymJesFL91CYLWDpR7WN7CIG9m8Y2v4G6QVtjRenZQb
20+
YiPoMErxoqDj6pUyiIl1lADFa0W13ED6dYwjrDDhBTCXb7NEjELZnvATsOhc/6zJ
21+
gfSowvUQVN6K4aJ7jgAHZOKQT7ZDw7YvMpzyo4AmSQXRgG8TR34+rRu5AoGACApP
22+
9+SjSPmbFl0HQWw9Aj4xOvEHfMTcwzQmRN/23nLOZzhETJ6lzpS2VmVt8TVN9lzW
23+
nohAXdpOhQrP0HwQZjfxtlJ3J0ZUh9g8OQG3t2LO5bWbXRkBb3aKyFqRflSuDOaG
24+
4X9NagC/14R7U2loglPuf71d0SDIWQBLvZJt94ECgYEAnY7aKHnWdLszcB8uyEkJ
25+
EJkUEaa+K/nTqOzqffZ01cTWJmUG7a2KuvQ+UQM2BHk2+wBmUo45Iz/dyePOJY0B
26+
Fu2agiV4+R4z2XVQnIvXgY5HaPxvLz0THksY/pD58gBmFaLMx4ADEwQ+s4Y2g12H
27+
ABsKNRHfSnKTwOm/dYvcVqs=
28+
-----END PRIVATE KEY-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MIIBIjANBgkqhkiG9w0BAQowAAOCAQ8AMIIBCgKCAQEAqXbrc7DxUNddNKLkeWgM
3+
GK8vGgLRtHPs1/VKjVxgqUQ1szz6Vx1GUgdQZk0y+BIStrZAH25WDas0bggGJSh2
4+
xaRS7NsyxKK36dFrBTT2gRxPSR6SyDOKF8TwlMqWeyQJ+emWKJ2AmfCJefEsw/Wq
5+
BeNUUxJqXMd15C9kUGtNtkYknHGGSsf8s7JFFAHU7072Mrox9TJL7H1T/jbOW0S+
6+
iFaqMBXd8b+nLb30DMc9K5bYEwvsDptwhbZ/hut8uj/2dFNg0XWnLDZDejxRlwCA
7+
dJErys7PIFaHBAYksDJHE/yg90evD2NfG9mrqmhnJvIAPoGlSd+igFuHTgI22PmI
8+
rQIDAQAB
9+
-----END PUBLIC KEY-----

test/parallel/test-crypto-key-objects.js

+52
Original file line numberDiff line numberDiff line change
@@ -581,11 +581,21 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
581581
const publicKey = createPublicKey(publicPem);
582582
const privateKey = createPrivateKey(privatePem);
583583

584+
// Because no RSASSA-PSS-params appears in the PEM, no defaults should be
585+
// added for the PSS parameters. This is different from an empty
586+
// RSASSA-PSS-params sequence (see test below).
587+
const expectedKeyDetails = {
588+
modulusLength: 2048,
589+
publicExponent: 65537n
590+
};
591+
584592
assert.strictEqual(publicKey.type, 'public');
585593
assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
594+
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
586595

587596
assert.strictEqual(privateKey.type, 'private');
588597
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
598+
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
589599

590600
assert.throws(
591601
() => publicKey.export({ format: 'jwk' }),
@@ -623,6 +633,38 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
623633
});
624634
}
625635

636+
{
637+
// This key pair enforces sha1 as the message digest and the MGF1
638+
// message digest and a salt length of 20 bytes.
639+
640+
const publicPem = fixtures.readKey('rsa_pss_public_2048_sha1_sha1_20.pem');
641+
const privatePem =
642+
fixtures.readKey('rsa_pss_private_2048_sha1_sha1_20.pem');
643+
644+
const publicKey = createPublicKey(publicPem);
645+
const privateKey = createPrivateKey(privatePem);
646+
647+
// Unlike the previous key pair, this key pair contains an RSASSA-PSS-params
648+
// sequence. However, because all values in the RSASSA-PSS-params are set to
649+
// their defaults (see RFC 3447), the ASN.1 structure contains an empty
650+
// sequence. Node.js should add the default values to the key details.
651+
const expectedKeyDetails = {
652+
modulusLength: 2048,
653+
publicExponent: 65537n,
654+
hashAlgorithm: 'sha1',
655+
mgf1HashAlgorithm: 'sha1',
656+
saltLength: 20
657+
};
658+
659+
assert.strictEqual(publicKey.type, 'public');
660+
assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
661+
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
662+
663+
assert.strictEqual(privateKey.type, 'private');
664+
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
665+
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
666+
}
667+
626668
{
627669
// This key pair enforces sha256 as the message digest and the MGF1
628670
// message digest and a salt length of at least 16 bytes.
@@ -681,11 +723,21 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
681723
const publicKey = createPublicKey(publicPem);
682724
const privateKey = createPrivateKey(privatePem);
683725

726+
const expectedKeyDetails = {
727+
modulusLength: 2048,
728+
publicExponent: 65537n,
729+
hashAlgorithm: 'sha512',
730+
mgf1HashAlgorithm: 'sha256',
731+
saltLength: 20
732+
};
733+
684734
assert.strictEqual(publicKey.type, 'public');
685735
assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
736+
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
686737

687738
assert.strictEqual(privateKey.type, 'private');
688739
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
740+
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
689741

690742
// Node.js usually uses the same hash function for the message and for MGF1.
691743
// However, when a different MGF1 message digest algorithm has been

test/parallel/test-crypto-keygen.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -309,14 +309,20 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
309309
assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
310310
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
311311
modulusLength: 512,
312-
publicExponent: 65537n
312+
publicExponent: 65537n,
313+
hashAlgorithm: 'sha256',
314+
mgf1HashAlgorithm: 'sha256',
315+
saltLength: 16
313316
});
314317

315318
assert.strictEqual(privateKey.type, 'private');
316319
assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
317320
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
318321
modulusLength: 512,
319-
publicExponent: 65537n
322+
publicExponent: 65537n,
323+
hashAlgorithm: 'sha256',
324+
mgf1HashAlgorithm: 'sha256',
325+
saltLength: 16
320326
});
321327

322328
// Unlike RSA, RSA-PSS does not allow encryption.

0 commit comments

Comments
 (0)