Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
crypto: add sign/verify support for RSASSA-PSS
Adds support for the PSS padding scheme. Until now, the sign/verify
functions used the old EVP_Sign*/EVP_Verify* OpenSSL API, making it
impossible to change the padding scheme. Fixed by first computing the
message digest and then signing/verifying with a custom EVP_PKEY_CTX,
allowing us to specify options such as the padding scheme and the PSS
salt length.

Fixes: #1127
  • Loading branch information
tniessen committed Mar 26, 2017
commit a361f5361c9cb9a729f66c93da13ee4792216dbe
23 changes: 21 additions & 2 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -961,7 +961,7 @@ pQhByd5eyj3lgZ7m7jbchtdgyOF8Io/1ng==
console.log(sign.sign(privateKey).toString('hex'));
```

### sign.sign(private_key[, output_format])
### sign.sign(private_key[, output_format][, options])
<!-- YAML
added: v0.1.92
-->
Expand All @@ -984,6 +984,15 @@ The `output_format` can specify one of `'latin1'`, `'hex'` or `'base64'`. If
`output_format` is provided a string is returned; otherwise a [`Buffer`][] is
returned.

The optional `options` argument is an object which specifies additional
cryptographic parameters. Currently, the following options are supported:

* `padding` : {String} - RSA padding, either `'pkcs1'` for RSASSA-PKCS1-v1_5
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you use lowercase string here and drop the space before the :?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure, but note that the rest of the document seems to use the uppercase variant and has a space before the colon.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@tniessen Thanks for pointing that out – we’re moving to consistenly using lowercase in #11697, so it would be good if this PR was in line with that. I’ll comment that I’d prefer to have the space before the colon dropped there, too.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Now that an options object is introduced, it seems the current positional parameter, output_format, should be possible to express as an option. encoding would be the more common name for it, I think.

(default) or `'pss'` for RSASSA-PSS
* `saltLength` : {number} - salt length for RSASSA-PSS. If this is set to `-1`,
the salt length will be set to the digest size. If this is set to `-2`
(default), the salt length will be set to the maximum permissible value.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It might be nice to have these two values exposed as named constants on crypto.constants… what do you think?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Great, will do so! What is the best way to define a constant which is not #defined? I have only seen people using NODE_DEFINE_CONSTANT with existing defs. Should I just #define RSA_PKCS1_PSS_MAX_SALT_LENGTH -2 and then use NODE_DEFINE_CONSTANT?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should I just #define RSA_PKCS1_PSS_MAX_SALT_LENGTH -2 and then use NODE_DEFINE_CONSTANT?

I don’t see any problem with that. :) It’s quite possible that somebody else here comes up with a better suggestion, but for the time being, I’d recommend just doing it the way you just described.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Latest OpenSSL (1.1.1-dev) changes its definitions as digest, max and auto(only in verification). https://github.com/openssl/openssl/blob/master/doc/man1/pkeyutl.pod#rsa-algorithm
I guess that auto can be implemented even in openssl-1.0.2. I think it is better to follow it.

Copy link
Copy Markdown
Contributor

@sam-github sam-github Mar 6, 2017

Choose a reason for hiding this comment

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

named constants would be better, or perhaps strings ('digest', 'max', and 'auto'), or perhaps both (named constants with the values being strings, not magic negative numbers)


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There are no descriptions for mgf1 and its hash. The default behavior is that the mgf1 hash is the same as the sign/verify hash. I think it needs to not to add an additional option parameter for mgf1 but its spec should be described in the doc.

The `Sign` object can not be again used after `sign.sign()` method has been
called. Multiple calls to `sign.sign()` will result in an error being thrown.

Expand Down Expand Up @@ -1070,7 +1079,7 @@ then `input_encoding` is ignored.

This can be called many times with new data as it is streamed.

### verifier.verify(object, signature[, signature_format])
### verifier.verify(object, signature[, signature_format][, options])
<!-- YAML
added: v0.1.92
-->
Expand All @@ -1087,6 +1096,16 @@ If a `signature_format` is specified, the `signature` is expected to be a
string; otherwise `signature` is expected to be a [`Buffer`][] or
`Uint8Array`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Adding Uint8Array description was removed here that was in e40ac30. I guess you removed it when you resolved conflicts.

@addaleax Is this still needed, right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@shigeki Yes, I can fix that while landing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@tniessen Actually, can you address this and also add short changes: blocks to the metadata, like verifier.update (just above this) has? You can use REPLACEME for the version.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@addaleax Okay, please take care of this with your approval. Thanks.


The optional `options` argument is an object which specifies additional
cryptographic parameters. Currently, the following options are supported:

* `padding` : {String} - RSA padding, either `'pkcs1'` for RSASSA-PKCS1-v1_5
(default) or `'pss'` for RSASSA-PSS
* `saltLength` : {number} - salt length for RSASSA-PSS. If this is set to `-1`,
the salt length will be set to the digest size. A value of `-2` (default)
causes the salt length to be automatically determined based on the PSS block
structure.

Returns `true` or `false` depending on the validity of the signature for
the data and public key.

Expand Down
26 changes: 19 additions & 7 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,13 +297,18 @@ Sign.prototype._write = function _write(chunk, encoding, callback) {

Sign.prototype.update = Hash.prototype.update;

Sign.prototype.sign = function sign(options, encoding) {
if (!options)
Sign.prototype.sign = function sign(privateKey, encoding, options) {
if (!privateKey)
throw new Error('No key provided to sign');

var key = options.key || options;
var passphrase = options.passphrase || null;
var ret = this._handle.sign(toBuf(key), null, passphrase);
if (typeof encoding == 'object' && !options) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think === is better than ==.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I guess it does not make a difference when using typeof, but I can surely change it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

yes, doesn't make a difference, but node style is currently to always use ===, unless using == is necessary (mostly for comparisons against unsigned and null in a single operation)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

if typeof encoding == object, && there are also options, that should be an error

options = encoding;
encoding = null;
}

var key = privateKey.key || privateKey;
var passphrase = privateKey.passphrase || null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what about when privateKey is passed in options? It would not make sense that .passPhrase is supported when the options object is first argument, but not supported when it is second or third argument

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Is this still relevant when changing the options object to be the first argument, such that the first argument is either the privateKey itself or an options object with properties key and optionally passphrase, padding and/or saltLength?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

no, it would not be, if there is only one options object, there can be no inconsistency between the options allowed in the various options objects

var ret = this._handle.sign(toBuf(key), null, passphrase, options);

encoding = encoding || exports.DEFAULT_ENCODING;
if (encoding && encoding !== 'buffer')
Expand All @@ -329,9 +334,16 @@ util.inherits(Verify, stream.Writable);
Verify.prototype._write = Sign.prototype._write;
Verify.prototype.update = Sign.prototype.update;

Verify.prototype.verify = function verify(object, signature, sigEncoding) {
Verify.prototype.verify = function verify(object, signature, sigEncoding,
options) {
if (typeof sigEncoding == 'object' && !options) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same here

options = sigEncoding;
sigEncoding = null;
}

sigEncoding = sigEncoding || exports.DEFAULT_ENCODING;
return this._handle.verify(toBuf(object), toBuf(signature, sigEncoding));
return this._handle.verify(toBuf(object), toBuf(signature, sigEncoding),
null, options);
};

function rsaPublic(method, defaultPadding) {
Expand Down
151 changes: 139 additions & 12 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ using v8::HandleScope;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::Null;
using v8::Object;
using v8::Persistent;
Expand Down Expand Up @@ -3972,6 +3973,41 @@ void SignBase::CheckThrow(SignBase::Error error) {
}
}

int SignBase::GetRSAOptions(Environment *env, v8::Local<v8::Object> options,
int *padding, int *saltlen) {
Local<Value> key = String::NewFromUtf8(env->isolate(), "padding");
MaybeLocal<Value> maybePadding = options->Get(key);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You might want to add this to the PER_ISOLATE_STRING_PROPERTIES table in env.h and then simply use options->Get(env->padding_string()) instead

if (!maybePadding.IsEmpty()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If maybePadding is empty you can just return 0

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Considering that I return 0 after throwing an exception (so 0 <=> error), I should probably return 1 here as it should be optional to specify the padding. Apart from that, I agree.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Considering that I return 0 after throwing an exception (so 0 <=> error), I should probably return 1 here as it should be optional to specify the padding.

In the case where maybePadding is empty you can safely assume that an exception is pending (e.g. if the getter for "padding" threw an error – which is obviously mostly hypothetical, but since it’s an object passed in from userland, it’s possible). So, returning 0 and returning back to JS as quickly as possible should be the right thing to do.

If the property is missing, you’ll get an Undefined value, not an empty MaybeLocal.

Copy link
Copy Markdown
Member Author

@tniessen tniessen Mar 6, 2017

Choose a reason for hiding this comment

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

If the property is missing, you’ll get an Undefined value, not an empty MaybeLocal.

Oh, that makes it clear. I did not know that, sorry!

Local<Value> paddingValue = maybePadding.ToLocalChecked();
if (paddingValue->IsString()) {
v8::String::Utf8Value paddingUtf8(paddingValue);
const char *paddingName = *paddingUtf8;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

style nit: const char* (I know we’re not being consistent in the code base 😄)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Actually… crypto.constants.RSA_PKCS1_PSS_PADDING and crypto.constants.RSA_PKCS1_PADDING already exist and are accessible from userland. Maybe we could use these constants instead of a string?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nice, must have missed those when I skimmed through the constants. I just noticed that the asymmetric encrypt/decrypt API allows to specify padding as part of the first argument (where key, passphrase and padding are properties of the object). Should I remove the additional options object and add the properties to the first argument for sign() / verify() as well in order to keep it consistent?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@sam-github @addaleax @bnoordhuis Thoughts on this? I have no problem keeping it as it is, with options as an additional parameter, but you might prefer to keep it consistent with publicEncrypt etc.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, it should be like publicEncrypt, etc, with .padding part of the first argument.

if (strcmp(paddingName, "pkcs1") == 0) {
*padding = RSA_PKCS1_PADDING;
}
else if (strcmp(paddingName, "pss") == 0) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Lint error.

src/node_crypto.cc:3968:  An else should appear on the same line as the preceding }  [whitespace/newline] [4]
src/node_crypto.cc:3968:  If an else has a brace on one side, it should have it on both  [readability/braces] [5]

*padding = RSA_PKCS1_PSS_PADDING;
}
else {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

style nit: } else { on one line

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ditto.

src/node_crypto.cc:3971:  An else should appear on the same line as the preceding }  [whitespace/newline] [4]
src/node_crypto.cc:3971:  If an else has a brace on one side, it should have it on both  [readability/braces] [5]

env->ThrowError("Padding must be 'pkcs1' or 'pss'");
return 0;
}
}
}

if (*padding == RSA_PKCS1_PSS_PADDING) {
key = String::NewFromUtf8(env->isolate(), "saltLength");
MaybeLocal<Value> maybeSaltlen = options->Get(key);
if (!maybeSaltlen.IsEmpty()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ditto, if (maybeSaltlen.IsEmpty()) return 0;

Local<Value> saltlenValue = maybeSaltlen.ToLocalChecked();
if (saltlenValue->IsNumber()) {
*saltlen = saltlenValue->Int32Value();
}
}
}

return 1;
}



Expand Down Expand Up @@ -4001,7 +4037,7 @@ SignBase::Error Sign::SignInit(const char* sign_type) {
return kSignUnknownDigest;

EVP_MD_CTX_init(&mdctx_);
if (!EVP_SignInit_ex(&mdctx_, md, nullptr))
if (!EVP_DigestInit_ex(&mdctx_, md, nullptr))
return kSignInit;
initialised_ = true;

Expand All @@ -4028,7 +4064,7 @@ void Sign::SignInit(const FunctionCallbackInfo<Value>& args) {
SignBase::Error Sign::SignUpdate(const char* data, int len) {
if (!initialised_)
return kSignNotInitialised;
if (!EVP_SignUpdate(&mdctx_, data, len))
if (!EVP_DigestUpdate(&mdctx_, data, len))
return kSignUpdate;
return kSignOk;
}
Expand Down Expand Up @@ -4058,12 +4094,59 @@ void Sign::SignUpdate(const FunctionCallbackInfo<Value>& args) {
sign->CheckThrow(err);
}

static int Node_SignFinal(EVP_MD_CTX *mdctx, unsigned char *md, unsigned int *s,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

EVP_MD_CTX* mdctx, etc., for consistency with rest of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

s is an unnecessarily cryptic name for sig_len

EVP_PKEY *pkey, int padding, int pss_saltlen) {
unsigned char m[EVP_MAX_MD_SIZE];
unsigned int m_len;
int i = 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this isn't an index, node_crypto.cc usually calls this usagerv, can you rename it?

EVP_PKEY_CTX *pkctx = nullptr;

*s = 0;
if (!EVP_DigestFinal_ex(mdctx, &(m[0]), &m_len))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I’d personally prefer just m instead of &(m[0]) here

goto err;

if (mdctx->digest->flags & EVP_MD_FLAG_PKEY_METHOD_SIGNATURE) {
size_t sltmp = (size_t)EVP_PKEY_size(pkey);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

style nit: static_cast<size_t> instead of (size_t) (assuming that works here)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This causes s cross initialization error during build. Move its definition before goto.

../src/node_crypto.cc: In function ‘int node::crypto::Node_SignFinal(EVP_MD_CTX*, unsigned char*, unsigned int*, EVP_PKEY*, int, int)’:
../src/node_crypto.cc:4109:4: error: jump to label ‘err’ [-fpermissive]
    err:
    ^
../src/node_crypto.cc:4086:10: note:   from here
     goto err;
          ^
../src/node_crypto.cc:4089:12: note:   crosses initialization of ‘size_t sltmp’
     size_t sltmp = (size_t)EVP_PKEY_size(pkey);
            ^

i = 0;
pkctx = EVP_PKEY_CTX_new(pkey, nullptr);
if (!pkctx)
goto err;
if (EVP_PKEY_sign_init(pkctx) <= 0)
goto err;
if (pkey->type == EVP_PKEY_RSA || pkey->type == EVP_PKEY_RSA2) {
if (EVP_PKEY_CTX_set_rsa_padding(pkctx, padding) <= 0)
goto err;
if (padding == RSA_PKCS1_PSS_PADDING)
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkctx, pss_saltlen) <= 0)
goto err;
}
if (EVP_PKEY_CTX_set_signature_md(pkctx, mdctx->digest) <= 0)
goto err;
if (EVP_PKEY_sign(pkctx, md, &sltmp, m, m_len) <= 0)
goto err;
*s = sltmp;
i = 1;
err:
EVP_PKEY_CTX_free(pkctx);
return i;
}

if (!mdctx->digest->sign) {
EVPerr(EVP_F_EVP_SIGNFINAL, EVP_R_NO_SIGN_FUNCTION_CONFIGURED);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

would this be a bug in our javascript layer, defensively being checked for in the binding layer, or is it something that happens occaisonally?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

As far as I know, this can occur if a hash algorithm is used that does not support signing. When signing with RSA etc., this code will not even be reached (EVP_MD_FLAG_PKEY_METHOD_SIGNATURE means "Digest uses EVP_PKEY_METHOD for signing instead of MD specific signing").

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would be good to add a test for this, if its possible to reach from calling the js API. If not, defensive code is still good.

return 0;
}

return (mdctx->digest->sign(mdctx->digest->type, m, m_len, md, s,
pkey->pkey.ptr));
}

SignBase::Error Sign::SignFinal(const char* key_pem,
int key_pem_len,
const char* passphrase,
unsigned char** sig,
unsigned int *sig_len) {
unsigned int *sig_len,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

pre-existing, but can you move the *? it was on the opposite side from the other arguments

int padding,
int saltlen) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

sig_len, therefor salt_len

if (!initialised_)
return kSignNotInitialised;

Expand Down Expand Up @@ -4109,7 +4192,7 @@ SignBase::Error Sign::SignFinal(const char* key_pem,
}
#endif // NODE_FIPS_MODE

if (EVP_SignFinal(&mdctx_, *sig, sig_len, pkey))
if (Node_SignFinal(&mdctx_, *sig, sig_len, pkey, padding, saltlen))
fatal = false;

initialised_ = false;
Expand Down Expand Up @@ -4152,6 +4235,12 @@ void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
size_t buf_len = Buffer::Length(args[0]);
char* buf = Buffer::Data(args[0]);

int padding = RSA_PKCS1_PADDING;
int saltlen = -2;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Mh, sorry if I wasn’t clear, but my idea was that you could add #include "node_constants.h" at the top of this file and then use the constants here where you currently use the magic numbers directly

if (len >= 4 && args[3]->IsObject()) {
sign->GetRSAOptions(env, Local<Object>::Cast(args[3]), &padding, &saltlen);
}

md_len = 8192; // Maximum key size is 8192 bits
md_value = new unsigned char[md_len];

Expand All @@ -4163,7 +4252,9 @@ void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
buf_len,
len >= 3 && !args[2]->IsNull() ? *passphrase : nullptr,
&md_value,
&md_len);
&md_len,
padding,
saltlen);
if (err != kSignOk) {
delete[] md_value;
md_value = nullptr;
Expand Down Expand Up @@ -4207,7 +4298,7 @@ SignBase::Error Verify::VerifyInit(const char* verify_type) {
return kSignUnknownDigest;

EVP_MD_CTX_init(&mdctx_);
if (!EVP_VerifyInit_ex(&mdctx_, md, nullptr))
if (!EVP_DigestInit_ex(&mdctx_, md, nullptr))
return kSignInit;
initialised_ = true;

Expand Down Expand Up @@ -4235,7 +4326,7 @@ SignBase::Error Verify::VerifyUpdate(const char* data, int len) {
if (!initialised_)
return kSignNotInitialised;

if (!EVP_VerifyUpdate(&mdctx_, data, len))
if (!EVP_DigestUpdate(&mdctx_, data, len))
return kSignUpdate;

return kSignOk;
Expand Down Expand Up @@ -4271,6 +4362,8 @@ SignBase::Error Verify::VerifyFinal(const char* key_pem,
int key_pem_len,
const char* sig,
int siglen,
int padding,
int saltlen,
bool* verify_result) {
if (!initialised_)
return kSignNotInitialised;
Expand All @@ -4282,7 +4375,10 @@ SignBase::Error Verify::VerifyFinal(const char* key_pem,
BIO* bp = nullptr;
X509* x509 = nullptr;
bool fatal = true;
unsigned char m[EVP_MAX_MD_SIZE];
unsigned int m_len;
int r = 0;
EVP_PKEY_CTX *pkctx = nullptr;

bp = BIO_new_mem_buf(const_cast<char*>(key_pem), key_pem_len);
if (bp == nullptr)
Expand Down Expand Up @@ -4317,11 +4413,35 @@ SignBase::Error Verify::VerifyFinal(const char* key_pem,
goto exit;
}

if (!EVP_DigestFinal_ex(&mdctx_, m, &m_len)) {
goto exit;
}

fatal = false;
r = EVP_VerifyFinal(&mdctx_,
reinterpret_cast<const unsigned char*>(sig),
siglen,
pkey);

pkctx = EVP_PKEY_CTX_new(pkey, nullptr);
if (!pkctx)
goto err;
if (EVP_PKEY_verify_init(pkctx) <= 0)
goto err;
if (pkey->type == EVP_PKEY_RSA || pkey->type == EVP_PKEY_RSA2) {
if (EVP_PKEY_CTX_set_rsa_padding(pkctx, padding) <= 0)
goto err;
if (padding == RSA_PKCS1_PSS_PADDING)
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkctx, saltlen) <= 0)
goto err;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

style nit: can you add {} if the body of an if spans more than a single line?

}
if (EVP_PKEY_CTX_set_signature_md(pkctx, mdctx_.digest) <= 0)
goto err;
r = EVP_PKEY_verify(pkctx,
reinterpret_cast<const unsigned char*>(sig),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

node_crypto.cc is a bit schismatic, but style is either to have all the args aligned four spaces from start of line, or to align
them with the column after the (

siglen,
m,
m_len);
fatal = false;

err:
EVP_PKEY_CTX_free(pkctx);

exit:
if (pkey != nullptr)
Expand Down Expand Up @@ -4377,8 +4497,15 @@ void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
hbuf = Buffer::Data(args[1]);
}

int padding = RSA_PKCS1_PADDING;
int saltlen = -2;
if (args.Length() >= 4 && args[3]->IsObject()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Fwiw you don’t need the args.Length() part of the check, if args[3] is out of bounds it will just be undefined. I don’t mind keeping it if you think that helps with readability, though.

verify->GetRSAOptions(env, args[3]->ToObject(), &padding, &saltlen);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Forgot to comment … can you check the return value here and return from the function if there was an error?

}

bool verify_result;
Error err = verify->VerifyFinal(kbuf, klen, hbuf, hlen, &verify_result);
Error err = verify->VerifyFinal(kbuf, klen, hbuf, hlen, padding, saltlen,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

verify_result, therefor salt_len

&verify_result);
if (args[1]->IsString())
delete[] hbuf;
if (err != kSignOk)
Expand Down
8 changes: 7 additions & 1 deletion src/node_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,8 @@ class SignBase : public BaseObject {

protected:
void CheckThrow(Error error);
int GetRSAOptions(Environment *env, v8::Local<v8::Object> options,
int *padding, int *saltlen);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

salt_len


EVP_MD_CTX mdctx_; /* coverity[member_decl] */
bool initialised_;
Expand All @@ -592,7 +594,9 @@ class Sign : public SignBase {
int key_pem_len,
const char* passphrase,
unsigned char** sig,
unsigned int *sig_len);
unsigned int *sig_len,
int padding,
int saltlen);

protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
Expand All @@ -615,6 +619,8 @@ class Verify : public SignBase {
int key_pem_len,
const char* sig,
int siglen,
int padding,
int saltlen,
bool* verify_result);

protected:
Expand Down
Loading