Skip to content
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 IEEE-P1363 DSA signatures #29292

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 18 additions & 0 deletions doc/api/crypto.md
Expand Up @@ -1405,6 +1405,7 @@ changes:
-->

* `privateKey` {Object | string | Buffer | KeyObject}
* `dsaEncoding` {string}
* `padding` {integer}
* `saltLength` {integer}
* `outputEncoding` {string} The [encoding][] of the return value.
Expand All @@ -1417,6 +1418,10 @@ If `privateKey` is not a [`KeyObject`][], this function behaves as if
`privateKey` 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`
Expand Down Expand Up @@ -1513,6 +1518,7 @@ changes:
-->

* `object` {Object | string | Buffer | KeyObject}
* `dsaEncoding` {string}
* `padding` {integer}
* `saltLength` {integer}
* `signature` {string | Buffer | TypedArray | DataView}
Expand All @@ -1526,6 +1532,10 @@ If `object` is not a [`KeyObject`][], this function behaves as if
`object` 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 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`
Expand Down Expand Up @@ -2891,6 +2901,10 @@ 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`
Expand Down Expand Up @@ -2944,6 +2958,10 @@ 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 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`
Expand Down
37 changes: 32 additions & 5 deletions lib/internal/crypto/sig.js
Expand Up @@ -11,6 +11,8 @@ const { validateString } = require('internal/validators');
const {
Sign: _Sign,
Verify: _Verify,
kSigEncDER,
kSigEncP1363,
signOneShot: _signOneShot,
verifyOneShot: _verifyOneShot
} = internalBinding('crypto');
Expand Down Expand Up @@ -59,6 +61,20 @@ function getSaltLength(options) {
return getIntOption('saltLength', options);
}

function getDSASignatureEncoding(options) {
if (typeof options === 'object') {
const { dsaEncoding = 'der' } = options;
if (dsaEncoding === 'der')
return kSigEncDER;
else if (dsaEncoding === 'ieee-p1363')
return kSigEncP1363;
else
throw new ERR_INVALID_OPT_VALUE('dsaEncoding', dsaEncoding);
}

return kSigEncDER;
}

function getIntOption(name, options) {
const value = options[name];
if (value !== undefined) {
Expand All @@ -81,8 +97,11 @@ Sign.prototype.sign = function sign(options, encoding) {
const rsaPadding = getPadding(options);
const pssSaltLength = getSaltLength(options);

// Options specific to (EC)DSA
const dsaSigEnc = getDSASignatureEncoding(options);

const ret = this[kHandle].sign(data, format, type, passphrase, rsaPadding,
pssSaltLength);
pssSaltLength, dsaSigEnc);

encoding = encoding || getDefaultEncoding();
if (encoding && encoding !== 'buffer')
Expand Down Expand Up @@ -117,8 +136,11 @@ function signOneShot(algorithm, data, key) {
const rsaPadding = getPadding(key);
const pssSaltLength = getSaltLength(key);

// Options specific to (EC)DSA
const dsaSigEnc = getDSASignatureEncoding(key);

return _signOneShot(keyData, keyFormat, keyType, keyPassphrase, data,
algorithm, rsaPadding, pssSaltLength);
algorithm, rsaPadding, pssSaltLength, dsaSigEnc);
}

function Verify(algorithm, options) {
Expand Down Expand Up @@ -149,13 +171,15 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) {

// Options specific to RSA
const rsaPadding = getPadding(options);

const pssSaltLength = getSaltLength(options);

// Options specific to (EC)DSA
const dsaSigEnc = getDSASignatureEncoding(options);

signature = getArrayBufferView(signature, 'signature', sigEncoding);

return this[kHandle].verify(data, format, type, passphrase, signature,
rsaPadding, pssSaltLength);
rsaPadding, pssSaltLength, dsaSigEnc);
};

function verifyOneShot(algorithm, data, key, signature) {
Expand All @@ -181,6 +205,9 @@ function verifyOneShot(algorithm, data, key, signature) {
const rsaPadding = getPadding(key);
const pssSaltLength = getSaltLength(key);

// Options specific to (EC)DSA
const dsaSigEnc = getDSASignatureEncoding(key);

if (!isArrayBufferView(signature)) {
throw new ERR_INVALID_ARG_TYPE(
'signature',
Expand All @@ -190,7 +217,7 @@ function verifyOneShot(algorithm, data, key, signature) {
}

return _verifyOneShot(keyData, keyFormat, keyType, keyPassphrase, signature,
data, algorithm, rsaPadding, pssSaltLength);
data, algorithm, rsaPadding, pssSaltLength, dsaSigEnc);
}

module.exports = {
Expand Down
136 changes: 132 additions & 4 deletions src/node_crypto.cc
Expand Up @@ -4910,6 +4910,9 @@ void CheckThrow(Environment* env, SignBase::Error error) {
case SignBase::Error::kSignNotInitialised:
return env->ThrowError("Not initialised");

case SignBase::Error::kSignMalformedSignature:
return env->ThrowError("Malformed signature");

case SignBase::Error::kSignInit:
case SignBase::Error::kSignUpdate:
case SignBase::Error::kSignPrivateKey:
Expand Down Expand Up @@ -5007,6 +5010,89 @@ static int GetDefaultSignPadding(const ManagedEVPPKey& key) {
RSA_PKCS1_PADDING;
}

static const unsigned int kNoDsaSignature = static_cast<unsigned int>(-1);

// Returns the maximum size of each of the integers (r, s) of the DSA signature.
static unsigned int GetBytesOfRS(const ManagedEVPPKey& pkey) {
tniessen marked this conversation as resolved.
Show resolved Hide resolved
int bits, base_id = EVP_PKEY_base_id(pkey.get());

if (base_id == EVP_PKEY_DSA) {
DSA* dsa_key = EVP_PKEY_get0_DSA(pkey.get());
// Both r and s are computed mod q, so their width is limited by that of q.
bits = BN_num_bits(DSA_get0_q(dsa_key));
tniessen marked this conversation as resolved.
Show resolved Hide resolved
} else if (base_id == EVP_PKEY_EC) {
EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey.get());
const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key);
bits = EC_GROUP_order_bits(ec_group);
} else {
return kNoDsaSignature;
}

return (bits + 7) / 8;
}

static AllocatedBuffer ConvertSignatureToP1363(Environment* env,
const ManagedEVPPKey& pkey,
AllocatedBuffer&& signature) {
unsigned int n = GetBytesOfRS(pkey);
if (n == kNoDsaSignature)
return std::move(signature);

const unsigned char* sig_data =
reinterpret_cast<unsigned char*>(signature.data());

ECDSA_SIG* asn1_sig = d2i_ECDSA_SIG(nullptr, &sig_data, signature.size());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice to use automatic memory management for this. (Grep for DeleteFnPtr in node_crypto.h for examples.)

Ditto line 4913.

if (asn1_sig == nullptr)
return AllocatedBuffer();

AllocatedBuffer buf = env->AllocateManaged(2 * n);
unsigned char* data = reinterpret_cast<unsigned char*>(buf.data());

const BIGNUM* r = ECDSA_SIG_get0_r(asn1_sig);
const BIGNUM* s = ECDSA_SIG_get0_s(asn1_sig);
CHECK_EQ(n, BN_bn2binpad(r, data, n));
CHECK_EQ(n, BN_bn2binpad(s, data + n, n));

ECDSA_SIG_free(asn1_sig);

return buf;
}

static ByteSource ConvertSignatureToDER(
const ManagedEVPPKey& pkey,
const ArrayBufferViewContents<char>& signature) {
unsigned int n = GetBytesOfRS(pkey);
if (n == kNoDsaSignature)
return ByteSource::Foreign(signature.data(), signature.length());

const unsigned char* sig_data =
reinterpret_cast<const unsigned char*>(signature.data());

if (signature.length() != 2 * n)
return ByteSource();

ECDSA_SIG* asn1_sig = ECDSA_SIG_new();
CHECK_NOT_NULL(asn1_sig);
BIGNUM* r = BN_new();
CHECK_NOT_NULL(r);
BIGNUM* s = BN_new();
CHECK_NOT_NULL(s);
CHECK_EQ(r, BN_bin2bn(sig_data, n, r));
CHECK_EQ(s, BN_bin2bn(sig_data + n, n, s));
CHECK_EQ(1, ECDSA_SIG_set0(asn1_sig, r, s));

unsigned char* data = nullptr;
int len = i2d_ECDSA_SIG(asn1_sig, &data);
ECDSA_SIG_free(asn1_sig);

if (len <= 0)
return ByteSource();

CHECK_NOT_NULL(data);

return ByteSource::Allocated(reinterpret_cast<char*>(data), len);
}

static AllocatedBuffer Node_SignFinal(Environment* env,
EVPMDPointer&& mdctx,
const ManagedEVPPKey& pkey,
Expand Down Expand Up @@ -5066,7 +5152,8 @@ static inline bool ValidateDSAParameters(EVP_PKEY* key) {
Sign::SignResult Sign::SignFinal(
const ManagedEVPPKey& pkey,
int padding,
const Maybe<int>& salt_len) {
const Maybe<int>& salt_len,
DSASigEnc dsa_sig_enc) {
if (!mdctx_)
return SignResult(kSignNotInitialised);

Expand All @@ -5078,6 +5165,10 @@ Sign::SignResult Sign::SignFinal(
AllocatedBuffer buffer =
Node_SignFinal(env(), std::move(mdctx), pkey, padding, salt_len);
Error error = buffer.data() == nullptr ? kSignPrivateKey : kSignOk;
if (error == kSignOk && dsa_sig_enc == kSigEncP1363) {
buffer = ConvertSignatureToP1363(env(), pkey, std::move(buffer));
CHECK_NOT_NULL(buffer.data());
}
return SignResult(error, std::move(buffer));
}

Expand Down Expand Up @@ -5105,10 +5196,15 @@ void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
salt_len = Just<int>(args[offset + 1].As<Int32>()->Value());
}

CHECK(args[offset + 2]->IsInt32());
DSASigEnc dsa_sig_enc =
static_cast<DSASigEnc>(args[offset + 2].As<Int32>()->Value());

SignResult ret = sign->SignFinal(
key,
padding,
salt_len);
salt_len,
dsa_sig_enc);

if (ret.error != kSignOk)
return sign->CheckThrow(ret.error);
Expand Down Expand Up @@ -5152,6 +5248,10 @@ void SignOneShot(const FunctionCallbackInfo<Value>& args) {
rsa_salt_len = Just<int>(args[offset + 3].As<Int32>()->Value());
}

CHECK(args[offset + 4]->IsInt32());
DSASigEnc dsa_sig_enc =
static_cast<DSASigEnc>(args[offset + 4].As<Int32>()->Value());

EVP_PKEY_CTX* pkctx = nullptr;
EVPMDPointer mdctx(EVP_MD_CTX_new());
if (!mdctx ||
Expand Down Expand Up @@ -5179,6 +5279,10 @@ void SignOneShot(const FunctionCallbackInfo<Value>& args) {

signature.Resize(sig_len);

if (dsa_sig_enc == kSigEncP1363) {
signature = ConvertSignatureToP1363(env, key, std::move(signature));
}

args.GetReturnValue().Set(signature.ToBuffer().ToLocalChecked());
}

Expand Down Expand Up @@ -5284,6 +5388,17 @@ void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
salt_len = Just<int>(args[offset + 2].As<Int32>()->Value());
}

CHECK(args[offset + 3]->IsInt32());
DSASigEnc dsa_sig_enc =
static_cast<DSASigEnc>(args[offset + 3].As<Int32>()->Value());

ByteSource signature = ByteSource::Foreign(hbuf.data(), hbuf.length());
if (dsa_sig_enc == kSigEncP1363) {
signature = ConvertSignatureToDER(pkey, hbuf);
if (signature.get() == nullptr)
return verify->CheckThrow(Error::kSignMalformedSignature);
}

bool verify_result;
Error err = verify->VerifyFinal(pkey, hbuf.data(), hbuf.length(), padding,
salt_len, &verify_result);
Expand Down Expand Up @@ -5327,6 +5442,10 @@ void VerifyOneShot(const FunctionCallbackInfo<Value>& args) {
rsa_salt_len = Just<int>(args[offset + 4].As<Int32>()->Value());
}

CHECK(args[offset + 5]->IsInt32());
DSASigEnc dsa_sig_enc =
static_cast<DSASigEnc>(args[offset + 5].As<Int32>()->Value());

EVP_PKEY_CTX* pkctx = nullptr;
EVPMDPointer mdctx(EVP_MD_CTX_new());
if (!mdctx ||
Expand All @@ -5337,11 +5456,18 @@ void VerifyOneShot(const FunctionCallbackInfo<Value>& args) {
if (!ApplyRSAOptions(key, pkctx, rsa_padding, rsa_salt_len))
return CheckThrow(env, SignBase::Error::kSignPublicKey);

ByteSource sig_bytes = ByteSource::Foreign(sig.data(), sig.length());
if (dsa_sig_enc == kSigEncP1363) {
sig_bytes = ConvertSignatureToDER(key, sig);
if (!sig_bytes)
return CheckThrow(env, SignBase::Error::kSignMalformedSignature);
}

bool verify_result;
const int r = EVP_DigestVerify(
mdctx.get(),
reinterpret_cast<const unsigned char*>(sig.data()),
sig.length(),
reinterpret_cast<const unsigned char*>(sig_bytes.get()),
sig_bytes.size(),
reinterpret_cast<const unsigned char*>(data.data()),
data.length());
switch (r) {
Expand Down Expand Up @@ -7129,6 +7255,8 @@ void Initialize(Local<Object> target,
NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
NODE_DEFINE_CONSTANT(target, kSigEncDER);
NODE_DEFINE_CONSTANT(target, kSigEncP1363);
env->SetMethod(target, "randomBytes", RandomBytes);
env->SetMethod(target, "signOneShot", SignOneShot);
env->SetMethod(target, "verifyOneShot", VerifyOneShot);
Expand Down