Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions deps/ncrypto/ncrypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3825,6 +3825,23 @@ bool EVPKeyCtxPointer::setSignatureMd(const EVPMDCtxPointer& md) {
1;
}

bool EVPKeyCtxPointer::setSignatureMd(const Digest& md) {
if (!ctx_ || !md) return false;
return EVP_PKEY_CTX_set_signature_md(ctx_.get(), md.get()) == 1;
}

#if OPENSSL_VERSION_MAJOR >= 3
int EVPKeyCtxPointer::initForSignEx(const OSSL_PARAM params[]) {
if (!ctx_) return 0;
return EVP_PKEY_sign_init_ex(ctx_.get(), params);
}

int EVPKeyCtxPointer::initForVerifyEx(const OSSL_PARAM params[]) {
if (!ctx_) return 0;
return EVP_PKEY_verify_init_ex(ctx_.get(), params);
}
#endif

bool EVPKeyCtxPointer::initForEncrypt() {
if (!ctx_) return false;
return EVP_PKEY_encrypt_init(ctx_.get()) == 1;
Expand Down
5 changes: 5 additions & 0 deletions deps/ncrypto/ncrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@ class EVPKeyCtxPointer final {
bool setRsaOaepLabel(DataPointer&& data);

bool setSignatureMd(const EVPMDCtxPointer& md);
bool setSignatureMd(const Digest& md);

bool publicCheck() const;
bool privateCheck() const;
Expand All @@ -821,6 +822,10 @@ class EVPKeyCtxPointer final {
bool initForKeygen();
int initForVerify();
int initForSign();
#if OPENSSL_VERSION_MAJOR >= 3
int initForVerifyEx(const OSSL_PARAM params[]);
int initForSignEx(const OSSL_PARAM params[]);
#endif

static EVPKeyCtxPointer New(const EVPKeyPointer& key);
static EVPKeyCtxPointer NewFromID(int id);
Expand Down
133 changes: 133 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -6133,6 +6133,68 @@ additional properties can be passed:

If the `callback` function is provided this function uses libuv's threadpool.

### `crypto.signDigest(algorithm, digest, key[, callback])`

<!-- YAML
added: REPLACEME
-->

<!--lint disable maximum-line-length remark-lint-->

* `algorithm` {string | null | undefined}
* `digest` {ArrayBuffer|Buffer|TypedArray|DataView}
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
* `callback` {Function}
* `err` {Error}
* `signature` {Buffer}
* Returns: {Buffer} if the `callback` function is not provided.

<!--lint enable maximum-line-length remark-lint-->

Calculates and returns the signature for `digest` using the given private key
and algorithm. Unlike [`crypto.sign()`][], this function does not hash the data
internally — `digest` is expected to be a pre-computed hash digest.

The interpretation of `algorithm` and `digest` depends on the key type:

* RSA, ECDSA, DSA: `algorithm` identifies the hash function used to create
`digest`. The resulting signatures are compatible with [`crypto.verify()`][]
and signatures produced by [`crypto.sign()`][] can be verified with
[`crypto.verifyDigest()`][].

* Ed25519, Ed448: `algorithm` must be `null` or `undefined`. These keys
use the Ed25519ph and Ed448ph prehash variants from [RFC 8032][]
respectively. `digest` must be the output of the appropriate prehash
function (SHA-512 for Ed25519ph, SHAKE256 with 64-byte output for
Ed448ph). The resulting signatures can only be verified with
[`crypto.verifyDigest()`][], not with [`crypto.verify()`][].

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
additional properties can be passed:

* `dsaEncoding` {string} For DSA and ECDSA, this option specifies the
format of the generated signature. It can be one of the following:
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
* `padding` {integer} Optional padding value for RSA, one of the following:

* `crypto.constants.RSA_PKCS1_PADDING` (default)
* `crypto.constants.RSA_PKCS1_PSS_PADDING`

`RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
used to create the digest as specified in section 3.1 of [RFC 4055][].
* `saltLength` {integer} Salt length for when padding is
`RSA_PKCS1_PSS_PADDING`. The special value
`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.
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed25519ph and Ed448ph,
this option specifies the optional context to differentiate signatures
generated for different purposes with the same key.

If the `callback` function is provided this function uses libuv's threadpool.

### `crypto.subtle`

<!-- YAML
Expand Down Expand Up @@ -6273,6 +6335,75 @@ key may be passed for `key`.

If the `callback` function is provided this function uses libuv's threadpool.

### `crypto.verifyDigest(algorithm, digest, key, signature[, callback])`

<!-- YAML
added: REPLACEME
-->

<!--lint disable maximum-line-length remark-lint-->

* `algorithm` {string|null|undefined}
* `digest` {ArrayBuffer|Buffer|TypedArray|DataView}
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
* `signature` {ArrayBuffer|Buffer|TypedArray|DataView}
* `callback` {Function}
* `err` {Error}
* `result` {boolean}
* Returns: {boolean} `true` or `false` depending on the validity of the
signature for the digest and public key if the `callback` function is not
provided.

<!--lint enable maximum-line-length remark-lint-->

Verifies the given signature for `digest` using the given key and algorithm.
Unlike [`crypto.verify()`][], this function does not hash the data
internally — `digest` is expected to be a pre-computed hash digest.

The interpretation of `algorithm` and `digest` depends on the key type:

* RSA, ECDSA, DSA: `algorithm` identifies the hash function used to create
`digest`. Signatures produced by [`crypto.sign()`][] can be verified with
this function, and signatures produced by [`crypto.signDigest()`][] can be
verified with [`crypto.verify()`][].
* Ed25519, Ed448: `algorithm` must be `null` or `undefined`. These keys
use the Ed25519ph and Ed448ph prehash variants from [RFC 8032][]
respectively. `digest` must be the output of the appropriate prehash
function (SHA-512 for Ed25519ph, SHAKE256 with 64-byte output for
Ed448ph). Only signatures produced by [`crypto.signDigest()`][] can be
verified with this function, not those from [`crypto.sign()`][].

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
additional properties can be passed:

* `dsaEncoding` {string} For DSA and ECDSA, this option specifies the
format of the signature. It can be one of the following:
* `'der'` (default): DER-encoded ASN.1 signature structure encoding `(r, s)`.
* `'ieee-p1363'`: Signature format `r || s` as proposed in IEEE-P1363.
* `padding` {integer} Optional padding value for RSA, one of the following:

* `crypto.constants.RSA_PKCS1_PADDING` (default)
* `crypto.constants.RSA_PKCS1_PSS_PADDING`

`RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function
used to create the digest as specified in section 3.1 of [RFC 4055][].
* `saltLength` {integer} Salt length for when padding is
`RSA_PKCS1_PSS_PADDING`. The special value
`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.
* `context` {ArrayBuffer|Buffer|TypedArray|DataView} For Ed25519ph and Ed448ph,
this option specifies the optional context to differentiate signatures
generated for different purposes with the same key.

The `signature` argument is the previously calculated signature for the `digest`.

Because public keys can be derived from private keys, a private key or a public
key may be passed for `key`.

If the `callback` function is provided this function uses libuv's threadpool.

### `crypto.webcrypto`

<!-- YAML
Expand Down Expand Up @@ -6899,7 +7030,9 @@ See the [list of SSL OP Flags][] for details.
[`crypto.randomBytes()`]: #cryptorandombytessize-callback
[`crypto.randomFill()`]: #cryptorandomfillbuffer-offset-size-callback
[`crypto.sign()`]: #cryptosignalgorithm-data-key-callback
[`crypto.signDigest()`]: #cryptosigndigestalgorithm-digest-key-callback
[`crypto.verify()`]: #cryptoverifyalgorithm-data-key-signature-callback
[`crypto.verifyDigest()`]: #cryptoverifydigestalgorithm-digest-key-signature-callback
[`crypto.webcrypto.getRandomValues()`]: webcrypto.md#cryptogetrandomvaluestypedarray
[`crypto.webcrypto.subtle`]: webcrypto.md#class-subtlecrypto
[`decipher.final()`]: #decipherfinaloutputencoding
Expand Down
4 changes: 4 additions & 0 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ const {
const {
Sign,
signOneShot,
signDigestOneShot,
Verify,
verifyOneShot,
verifyDigestOneShot,
} = require('internal/crypto/sig');
const {
Hash,
Expand Down Expand Up @@ -223,11 +225,13 @@ module.exports = {
scrypt,
scryptSync,
sign: signOneShot,
signDigest: signDigestOneShot,
setEngine,
timingSafeEqual,
getFips,
setFips,
verify: verifyOneShot,
verifyDigest: verifyDigestOneShot,
hash,
encapsulate,
decapsulate,
Expand Down
56 changes: 42 additions & 14 deletions lib/internal/crypto/sig.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const { Writable } = require('stream');
const { Buffer } = require('buffer');

const {
isAnyArrayBuffer,
isArrayBufferView,
} = require('internal/util/types');

Expand Down Expand Up @@ -152,14 +153,29 @@ Sign.prototype.sign = function sign(options, encoding) {
return ret;
};

function signOneShot(algorithm, data, key, callback) {
function validateDigestOrData(data, prehashed) {
if (prehashed) {
if (!isArrayBufferView(data) && !isAnyArrayBuffer(data)) {
throw new ERR_INVALID_ARG_TYPE(
'digest',
['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
data,
);
}
return data;
}

return getArrayBufferOrView(data, 'data');
}

function signOneShotImpl(algorithm, data, key, callback, prehashed) {
if (algorithm != null)
validateString(algorithm, 'algorithm');

if (callback !== undefined)
validateFunction(callback, 'callback');

data = getArrayBufferOrView(data, 'data');
data = validateDigestOrData(data, prehashed);

if (!key)
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
Expand Down Expand Up @@ -194,7 +210,8 @@ function signOneShot(algorithm, data, key, callback) {
rsaPadding,
dsaSigEnc,
context,
undefined);
undefined,
prehashed);

if (!callback) {
const { 0: err, 1: signature } = job.run();
Expand Down Expand Up @@ -248,22 +265,14 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) {
rsaPadding, pssSaltLength, dsaSigEnc);
};

function verifyOneShot(algorithm, data, key, signature, callback) {
function verifyOneShotImpl(algorithm, data, key, signature, callback, prehashed) {
if (algorithm != null)
validateString(algorithm, 'algorithm');

if (callback !== undefined)
validateFunction(callback, 'callback');

data = getArrayBufferOrView(data, 'data');

if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE(
'data',
['Buffer', 'TypedArray', 'DataView'],
data,
);
}
data = validateDigestOrData(data, prehashed);

// Options specific to RSA
const rsaPadding = getPadding(key);
Expand Down Expand Up @@ -303,7 +312,8 @@ function verifyOneShot(algorithm, data, key, signature, callback) {
rsaPadding,
dsaSigEnc,
context,
signature);
signature,
prehashed);

if (!callback) {
const { 0: err, 1: result } = job.run();
Expand All @@ -320,9 +330,27 @@ function verifyOneShot(algorithm, data, key, signature, callback) {
job.run();
}

function signOneShot(algorithm, data, key, callback) {
return signOneShotImpl(algorithm, data, key, callback, false /* not prehashed */);
}

function verifyOneShot(algorithm, data, key, signature, callback) {
return verifyOneShotImpl(algorithm, data, key, signature, callback, false /* not prehashed */);
}

function signDigestOneShot(algorithm, digest, key, callback) {
return signOneShotImpl(algorithm, digest, key, callback, true /* prehashed */);
}

function verifyDigestOneShot(algorithm, digest, key, signature, callback) {
return verifyOneShotImpl(algorithm, digest, key, signature, callback, true /* prehashed */);
}

module.exports = {
Sign,
signOneShot,
signDigestOneShot,
Verify,
verifyOneShot,
verifyDigestOneShot,
};
Loading