Skip to content

Commit

Permalink
crypto: add support for IEEE-P1363 DSA signatures
Browse files Browse the repository at this point in the history
PR-URL: #29292
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
tniessen authored and MylesBorins committed Nov 21, 2019
1 parent 01fa18c commit 18ec8a8
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 24 deletions.
18 changes: 18 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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) {
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));
} 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());
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
Loading

0 comments on commit 18ec8a8

Please sign in to comment.