-
Notifications
You must be signed in to change notification settings - Fork 29.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
crypto: add support for SM2 #37066
base: main
Are you sure you want to change the base?
crypto: add support for SM2 #37066
Changes from all commits
70988ea
4282617
a52008c
a76c7e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1307,6 +1307,9 @@ API using additional attributes. | |
<!-- YAML | ||
added: v11.6.0 | ||
changes: | ||
- version: REPLACEME | ||
pr-url: ??? | ||
description: Added support for `'sm2'`. | ||
- version: | ||
- v13.9.0 | ||
- v12.17.0 | ||
|
@@ -1336,6 +1339,7 @@ types are: | |
* `'rsa-pss'` (OID 1.2.840.113549.1.1.10) | ||
* `'dsa'` (OID 1.2.840.10040.4.1) | ||
* `'ec'` (OID 1.2.840.10045.2.1) | ||
* `'sm2'` (OID 1.2.840.10045.2.1) | ||
* `'x25519'` (OID 1.3.101.110) | ||
* `'x448'` (OID 1.3.101.111) | ||
* `'ed25519'` (OID 1.3.101.112) | ||
|
@@ -2356,6 +2360,9 @@ input.on('readable', () => { | |
<!-- YAML | ||
added: v11.6.0 | ||
changes: | ||
- version: REPLACEME | ||
pr-url: ??? | ||
description: Add `asymmetricKeyType` option. | ||
- version: v15.0.0 | ||
pr-url: https://github.com/nodejs/node/pull/35093 | ||
description: The key can also be an ArrayBuffer. The encoding option was | ||
|
@@ -2371,6 +2378,7 @@ changes: | |
required only if the `format` is `'der'` and ignored if it is `'pem'`. | ||
* `passphrase`: {string | Buffer} The passphrase to use for decryption. | ||
* `encoding`: {string} The string encoding to use when `key` is a string. | ||
* `asymmetricKeyType` {string} The requested asymmetric key type. | ||
* Returns: {KeyObject} | ||
<!--lint enable maximum-line-length remark-lint--> | ||
|
||
|
@@ -2381,10 +2389,18 @@ must be an object with the properties described above. | |
If the private key is encrypted, a `passphrase` must be specified. The length | ||
of the passphrase is limited to 1024 bytes. | ||
|
||
If the `asymmetricKeyType` is specified, Node.js will attempt to assign the | ||
given type to the key. This can be used, for example, to distinguish between | ||
EC keys on the SM2 curve (`'ec'`) and SM2 keys (`'sm2'`). If the given type | ||
cannot be assigned to the key, the function fails. | ||
|
||
### `crypto.createPublicKey(key)` | ||
<!-- YAML | ||
added: v11.6.0 | ||
changes: | ||
- version: REPLACEME | ||
pr-url: ??? | ||
description: Add `asymmetricKeyType` option. | ||
- version: v15.0.0 | ||
pr-url: https://github.com/nodejs/node/pull/35093 | ||
description: The key can also be an ArrayBuffer. The encoding option was | ||
|
@@ -2405,6 +2421,7 @@ changes: | |
* `type`: {string} Must be `'pkcs1'` or `'spki'`. This option is required | ||
only if the `format` is `'der'`. | ||
* `encoding` {string} The string encoding to use when `key` is a string. | ||
* `asymmetricKeyType` {string} The requested asymmetric key type. | ||
* Returns: {KeyObject} | ||
<!--lint enable maximum-line-length remark-lint--> | ||
|
||
|
@@ -2415,6 +2432,11 @@ otherwise, `key` must be an object with the properties described above. | |
|
||
If the format is `'pem'`, the `'key'` may also be an X.509 certificate. | ||
|
||
If the `asymmetricKeyType` is specified, Node.js will attempt to assign the | ||
given type to the key. This can be used, for example, to distinguish between | ||
EC keys on the SM2 curve (`'ec'`) and SM2 keys (`'sm2'`). If the given type | ||
cannot be assigned to the key, the function fails. | ||
|
||
Because public keys can be derived from private keys, a private key may be | ||
passed instead of a public key. In that case, this function behaves as if | ||
[`crypto.createPrivateKey()`][] had been called, except that the type of the | ||
|
@@ -2555,6 +2577,9 @@ console.log(key.export().toString('hex')); // e89..........41e | |
<!-- YAML | ||
added: v10.12.0 | ||
changes: | ||
- version: REPLACEME | ||
pr-url: ??? | ||
description: Add support for SM2. | ||
- version: | ||
- v13.9.0 | ||
- v12.17.0 | ||
|
@@ -2573,7 +2598,7 @@ changes: | |
--> | ||
|
||
* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, `'ed448'`, | ||
`'x25519'`, `'x448'`, or `'dh'`. | ||
`'x25519'`, `'x448'`, `'dh'`, or `'sm2'`. | ||
* `options`: {Object} | ||
* `modulusLength`: {number} Key size in bits (RSA, DSA). | ||
* `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`. | ||
|
@@ -2592,7 +2617,7 @@ changes: | |
* `privateKey`: {string | Buffer | KeyObject} | ||
|
||
Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519, | ||
Ed448, X25519, X448, and DH are currently supported. | ||
Ed448, X25519, X448, DH, and SM2 are currently supported. | ||
|
||
If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function | ||
behaves as if [`keyObject.export()`][] had been called on its result. Otherwise, | ||
|
@@ -2630,6 +2655,9 @@ a `Promise` for an `Object` with `publicKey` and `privateKey` properties. | |
<!-- YAML | ||
added: v10.12.0 | ||
changes: | ||
- version: REPLACEME | ||
pr-url: ??? | ||
description: Add support for SM2. | ||
- version: | ||
- v13.9.0 | ||
- v12.17.0 | ||
|
@@ -2645,7 +2673,7 @@ changes: | |
--> | ||
|
||
* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, `'ed448'`, | ||
`'x25519'`, `'x448'`, or `'dh'`. | ||
`'x25519'`, `'x448'`, `'dh'`, or `'sm2'`. | ||
* `options`: {Object} | ||
* `modulusLength`: {number} Key size in bits (RSA, DSA). | ||
* `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`. | ||
|
@@ -2663,7 +2691,7 @@ changes: | |
* `privateKey`: {string | Buffer | KeyObject} | ||
|
||
Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519, | ||
Ed448, X25519, X448, and DH are currently supported. | ||
Ed448, X25519, X448, DH, and SM2 are currently supported. | ||
|
||
If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function | ||
behaves as if [`keyObject.export()`][] had been called on its result. Otherwise, | ||
|
@@ -3627,6 +3655,9 @@ Throws an error if FIPS mode is not available. | |
<!-- YAML | ||
added: v12.0.0 | ||
changes: | ||
- version: REPLACEME | ||
pr-url: ??? | ||
description: Add support for SM2. | ||
- version: | ||
- v13.2.0 | ||
- v12.16.0 | ||
|
@@ -3643,7 +3674,7 @@ changes: | |
|
||
Calculates and returns the signature for `data` using the given private key and | ||
algorithm. If `algorithm` is `null` or `undefined`, then the algorithm is | ||
dependent upon the key type (especially Ed25519 and Ed448). | ||
dependent upon the key type (especially Ed25519, Ed448, and SM2). | ||
|
||
If `key` is not a [`KeyObject`][], this function behaves as if `key` had been | ||
passed to [`crypto.createPrivateKey()`][]. If it is an object, the following | ||
|
@@ -3664,6 +3695,9 @@ additional properties can be passed: | |
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest | ||
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the | ||
maximum permissible value. | ||
* `sm2Identifier` {ArrayBuffer|Buffer|TypedArray|DataView} For SM2, this option | ||
specifies the SM2 identifier. The same identifier must be specified during | ||
verification. | ||
|
||
### `crypto.timingSafeEqual(a, b)` | ||
<!-- YAML | ||
|
@@ -3699,6 +3733,9 @@ not introduce timing vulnerabilities. | |
<!-- YAML | ||
added: v12.0.0 | ||
changes: | ||
- version: REPLACEME | ||
pr-url: ??? | ||
description: Add support for SM2. | ||
- version: v15.0.0 | ||
pr-url: https://github.com/nodejs/node/pull/35093 | ||
description: The data, key, and signature arguments can also be ArrayBuffer. | ||
|
@@ -3719,7 +3756,7 @@ changes: | |
|
||
Verifies the given signature for `data` using the given key and algorithm. If | ||
`algorithm` is `null` or `undefined`, then the algorithm is dependent upon the | ||
key type (especially Ed25519 and Ed448). | ||
key type (especially Ed25519, Ed448, and SM2). | ||
|
||
If `key` is not a [`KeyObject`][], this function behaves as if `key` had been | ||
passed to [`crypto.createPublicKey()`][]. If it is an object, the following | ||
|
@@ -3740,6 +3777,8 @@ additional properties can be passed: | |
`crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest | ||
size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the | ||
maximum permissible value. | ||
* `sm2Identifier` {ArrayBuffer|Buffer|TypedArray|DataView} For SM2, this option | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer to make this a more generic name, e.g. just |
||
specifies the SM2 identifier. | ||
|
||
The `signature` argument is the previously calculated signature for the `data`. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,16 @@ const { | |
kKeyEncodingPKCS8, | ||
kKeyEncodingSPKI, | ||
kKeyEncodingSEC1, | ||
EVP_PKEY_DH, | ||
EVP_PKEY_DSA, | ||
EVP_PKEY_EC, | ||
EVP_PKEY_ED25519, | ||
EVP_PKEY_ED448, | ||
EVP_PKEY_RSA, | ||
EVP_PKEY_RSA_PSS, | ||
EVP_PKEY_SM2, | ||
EVP_PKEY_X25519, | ||
EVP_PKEY_X448 | ||
} = internalBinding('crypto'); | ||
|
||
const { | ||
|
@@ -359,13 +369,41 @@ function prepareAsymmetricKey(key, ctx) { | |
// Expect PEM by default, mostly for backward compatibility. | ||
return { format: kKeyFormatPEM, data: getArrayBufferOrView(key, 'key') }; | ||
} else if (typeof key === 'object') { | ||
const { key: data, encoding } = key; | ||
const { key: data, encoding, asymmetricKeyType: typeStr } = key; | ||
|
||
let asymmetricKeyType; | ||
if (typeStr == null) | ||
asymmetricKeyType = undefined; | ||
else if (typeStr === 'dh') | ||
asymmetricKeyType = EVP_PKEY_DH; | ||
else if (typeStr === 'dsa') | ||
asymmetricKeyType = EVP_PKEY_DSA; | ||
else if (typeStr === 'ec') | ||
asymmetricKeyType = EVP_PKEY_EC; | ||
else if (typeStr === 'ed25519') | ||
asymmetricKeyType = EVP_PKEY_ED25519; | ||
else if (typeStr === 'ed448') | ||
asymmetricKeyType = EVP_PKEY_ED448; | ||
else if (typeStr === 'rsa') | ||
asymmetricKeyType = EVP_PKEY_RSA; | ||
else if (typeStr === 'rsa-pss') | ||
asymmetricKeyType = EVP_PKEY_RSA_PSS; | ||
else if (typeStr === 'sm2') | ||
asymmetricKeyType = EVP_PKEY_SM2; | ||
else if (typeStr === 'x25519') | ||
asymmetricKeyType = EVP_PKEY_X25519; | ||
else if (typeStr === 'x448') | ||
asymmetricKeyType = EVP_PKEY_X448; | ||
else | ||
throw new ERR_INVALID_ARG_VALUE('options.asymmetricKeyType', typeStr); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A switch statement would be nicer here from a code readability point of view. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest key-map object instead: const keyTypeMap = {
'dh': EVP_PKEY_DH,
'dsa': EVP_PKEY_DSA,
'ec': EVP_PKEY_EC,
'ed25519': EVP_PKEY_ED25519,
'ed448': EVP_PKEY_ED448,
'rsa': EVP_PKEY_RSA,
'rsa-pss': EVP_PKEY_RSA_PSS,
'sm2': EVP_PKEY_SM2,
'x25519': EVP_PKEY_X25519,
'x448': EVP_PKEY_X448,
null: undefined // Map null to undefined
};
const asymmetricKeyType = keyTypeMap[typeStr];
if (asymmetricKeyType === undefined) { // if asymmetricKeyType null or not in keyTypeMap
throw new ERR_INVALID_ARG_VALUE('options.asymmetricKeyType', typeStr);
} |
||
|
||
// The 'key' property can be a KeyObject as well to allow specifying | ||
// additional options such as padding along with the key. | ||
const common = { asymmetricKeyType }; | ||
if (isKeyObject(data)) | ||
return { data: getKeyObjectHandle(data, ctx) }; | ||
return { data: getKeyObjectHandle(data, ctx), ...common }; | ||
else if (isCryptoKey(data)) | ||
return { data: getKeyObjectHandle(data[kKeyObject], ctx) }; | ||
return { data: getKeyObjectHandle(data[kKeyObject], ctx), ...common }; | ||
// Either PEM or DER using PKCS#1 or SPKI. | ||
if (!isStringOrBuffer(data)) { | ||
throw new ERR_INVALID_ARG_TYPE( | ||
|
@@ -378,6 +416,7 @@ function prepareAsymmetricKey(key, ctx) { | |
(ctx === kConsumePrivate || ctx === kCreatePrivate) ? false : undefined; | ||
return { | ||
data: getArrayBufferOrView(data, 'key', encoding), | ||
...common, | ||
...parseKeyEncoding(key, undefined, isPublic) | ||
}; | ||
} | ||
|
@@ -428,17 +467,19 @@ function createSecretKey(key, encoding) { | |
} | ||
|
||
function createPublicKey(key) { | ||
const { format, type, data } = prepareAsymmetricKey(key, kCreatePublic); | ||
const { format, type, data, passphrase, asymmetricKeyType } = | ||
prepareAsymmetricKey(key, kCreatePublic); | ||
const handle = new KeyObjectHandle(); | ||
handle.init(kKeyTypePublic, data, format, type); | ||
handle.init(kKeyTypePublic, data, format, type, passphrase, asymmetricKeyType); | ||
return new PublicKeyObject(handle); | ||
} | ||
|
||
function createPrivateKey(key) { | ||
const { format, type, data, passphrase } = | ||
const { format, type, data, passphrase, asymmetricKeyType } = | ||
prepareAsymmetricKey(key, kCreatePrivate); | ||
const handle = new KeyObjectHandle(); | ||
handle.init(kKeyTypePrivate, data, format, type, passphrase); | ||
handle.init(kKeyTypePrivate, data, format, type, passphrase, | ||
asymmetricKeyType); | ||
return new PrivateKeyObject(handle); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to document the expected values here (or link to where they are documented)
(I know there's a paragraph added below but listing the values here would be helpful)