src: add error codes to errors thrown in C++

PR-URL: https://github.com/nodejs/node/pull/27700
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
This commit is contained in:
Yaniv Friedensohn 2019-05-11 20:00:38 +03:00 committed by Rich Trott
parent c065773cc5
commit a0e2c6d284
8 changed files with 92 additions and 66 deletions

View File

@ -12,7 +12,7 @@ const {
ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_TYPE,
ERR_INVALID_OPT_VALUE ERR_INVALID_OPT_VALUE
} = require('internal/errors').codes; } = require('internal/errors').codes;
const { validateString } = require('internal/validators'); const { validateEncoding, validateString } = require('internal/validators');
const { const {
preparePrivateKey, preparePrivateKey,
@ -161,6 +161,8 @@ Cipher.prototype.update = function update(data, inputEncoding, outputEncoding) {
throw invalidArrayBufferView('data', data); throw invalidArrayBufferView('data', data);
} }
validateEncoding(data, inputEncoding);
const ret = this[kHandle].update(data, inputEncoding); const ret = this[kHandle].update(data, inputEncoding);
if (outputEncoding && outputEncoding !== 'buffer') { if (outputEncoding && outputEncoding !== 'buffer') {

View File

@ -25,7 +25,8 @@ const {
ERR_CRYPTO_HASH_UPDATE_FAILED, ERR_CRYPTO_HASH_UPDATE_FAILED,
ERR_INVALID_ARG_TYPE ERR_INVALID_ARG_TYPE
} = require('internal/errors').codes; } = require('internal/errors').codes;
const { validateString, validateUint32 } = require('internal/validators'); const { validateEncoding, validateString, validateUint32 } =
require('internal/validators');
const { normalizeEncoding } = require('internal/util'); const { normalizeEncoding } = require('internal/util');
const { isArrayBufferView } = require('internal/util/types'); const { isArrayBufferView } = require('internal/util/types');
const LazyTransform = require('internal/streams/lazy_transform'); const LazyTransform = require('internal/streams/lazy_transform');
@ -61,6 +62,8 @@ Hash.prototype._flush = function _flush(callback) {
}; };
Hash.prototype.update = function update(data, encoding) { Hash.prototype.update = function update(data, encoding) {
encoding = encoding || getDefaultEncoding();
const state = this[kState]; const state = this[kState];
if (state[kFinalized]) if (state[kFinalized])
throw new ERR_CRYPTO_HASH_FINALIZED(); throw new ERR_CRYPTO_HASH_FINALIZED();
@ -74,7 +77,9 @@ Hash.prototype.update = function update(data, encoding) {
data); data);
} }
if (!this[kHandle].update(data, encoding || getDefaultEncoding())) validateEncoding(data, encoding);
if (!this[kHandle].update(data, encoding))
throw new ERR_CRYPTO_HASH_UPDATE_FAILED(); throw new ERR_CRYPTO_HASH_UPDATE_FAILED();
return this; return this;
}; };

View File

@ -9,6 +9,7 @@ const {
ERR_UNKNOWN_SIGNAL ERR_UNKNOWN_SIGNAL
} }
} = require('internal/errors'); } = require('internal/errors');
const { normalizeEncoding } = require('internal/util');
const { const {
isArrayBufferView isArrayBufferView
} = require('internal/util/types'); } = require('internal/util/types');
@ -142,11 +143,24 @@ const validateBuffer = hideStackFrames((buffer, name = 'buffer') => {
} }
}); });
function validateEncoding(data, encoding) {
const normalizedEncoding = normalizeEncoding(encoding);
const length = data.length;
if (normalizedEncoding === 'hex' && length % 2 !== 0) {
throw new ERR_INVALID_ARG_VALUE('encoding', encoding,
`is invalid for data of length ${length}`);
}
// TODO(bnoordhuis) Add BASE64 check?
}
module.exports = { module.exports = {
isInt32, isInt32,
isUint32, isUint32,
parseMode, parseMode,
validateBuffer, validateBuffer,
validateEncoding,
validateInteger, validateInteger,
validateInt32, validateInt32,
validateUint32, validateUint32,

View File

@ -843,7 +843,7 @@ void IndexOfString(const FunctionCallbackInfo<Value>& args) {
if (IsBigEndian()) { if (IsBigEndian()) {
StringBytes::InlineDecoder decoder; StringBytes::InlineDecoder decoder;
if (decoder.Decode(env, needle, args[3], UCS2).IsNothing()) return; if (decoder.Decode(env, needle, enc).IsNothing()) return;
const uint16_t* decoded_string = const uint16_t* decoded_string =
reinterpret_cast<const uint16_t*>(decoder.out()); reinterpret_cast<const uint16_t*>(decoder.out());

View File

@ -4317,8 +4317,9 @@ void CipherBase::Update(const FunctionCallbackInfo<Value>& args) {
// Only copy the data if we have to, because it's a string // Only copy the data if we have to, because it's a string
if (args[0]->IsString()) { if (args[0]->IsString()) {
StringBytes::InlineDecoder decoder; StringBytes::InlineDecoder decoder;
if (!decoder.Decode(env, args[0].As<String>(), args[1], UTF8) enum encoding enc = ParseEncoding(env->isolate(), args[1], UTF8);
.FromMaybe(false))
if (decoder.Decode(env, args[0].As<String>(), enc).IsNothing())
return; return;
r = cipher->Update(decoder.out(), decoder.size(), &out); r = cipher->Update(decoder.out(), decoder.size(), &out);
} else { } else {
@ -4501,8 +4502,9 @@ void Hmac::HmacUpdate(const FunctionCallbackInfo<Value>& args) {
bool r = false; bool r = false;
if (args[0]->IsString()) { if (args[0]->IsString()) {
StringBytes::InlineDecoder decoder; StringBytes::InlineDecoder decoder;
if (decoder.Decode(env, args[0].As<String>(), args[1], UTF8) enum encoding enc = ParseEncoding(env->isolate(), args[1], UTF8);
.FromMaybe(false)) {
if (!decoder.Decode(env, args[0].As<String>(), enc).IsNothing()) {
r = hmac->HmacUpdate(decoder.out(), decoder.size()); r = hmac->HmacUpdate(decoder.out(), decoder.size());
} }
} else { } else {
@ -4626,8 +4628,9 @@ void Hash::HashUpdate(const FunctionCallbackInfo<Value>& args) {
bool r = true; bool r = true;
if (args[0]->IsString()) { if (args[0]->IsString()) {
StringBytes::InlineDecoder decoder; StringBytes::InlineDecoder decoder;
if (!decoder.Decode(env, args[0].As<String>(), args[1], UTF8) enum encoding enc = ParseEncoding(env->isolate(), args[1], UTF8);
.FromMaybe(false)) {
if (decoder.Decode(env, args[0].As<String>(), enc).IsNothing()) {
args.GetReturnValue().Set(false); args.GetReturnValue().Set(false);
return; return;
} }

View File

@ -392,15 +392,6 @@ size_t StringBytes::Write(Isolate* isolate,
} }
bool StringBytes::IsValidString(Local<String> string,
enum encoding enc) {
if (enc == HEX && string->Length() % 2 != 0)
return false;
// TODO(bnoordhuis) Add BASE64 check?
return true;
}
// Quick and dirty size calculation // Quick and dirty size calculation
// Will always be at least big enough, but may have some extra // Will always be at least big enough, but may have some extra
// UTF8 can be as much as 3x the size, Base64 can have 1-2 extra bytes // UTF8 can be as much as 3x the size, Base64 can have 1-2 extra bytes

View File

@ -37,14 +37,7 @@ class StringBytes {
public: public:
inline v8::Maybe<bool> Decode(Environment* env, inline v8::Maybe<bool> Decode(Environment* env,
v8::Local<v8::String> string, v8::Local<v8::String> string,
v8::Local<v8::Value> encoding, enum encoding enc) {
enum encoding _default) {
enum encoding enc = ParseEncoding(env->isolate(), encoding, _default);
if (!StringBytes::IsValidString(string, enc)) {
env->ThrowTypeError("Bad input string");
return v8::Nothing<bool>();
}
size_t storage; size_t storage;
if (!StringBytes::StorageSize(env->isolate(), string, enc).To(&storage)) if (!StringBytes::StorageSize(env->isolate(), string, enc).To(&storage))
return v8::Nothing<bool>(); return v8::Nothing<bool>();
@ -60,12 +53,6 @@ class StringBytes {
inline size_t size() const { return length(); } inline size_t size() const { return length(); }
}; };
// Does the string match the encoding? Quick but non-exhaustive.
// Example: a HEX string must have a length that's a multiple of two.
// FIXME(bnoordhuis) IsMaybeValidString()? Naming things is hard...
static bool IsValidString(v8::Local<v8::String> string,
enum encoding enc);
// Fast, but can be 2 bytes oversized for Base64, and // Fast, but can be 2 bytes oversized for Base64, and
// as much as triple UTF-8 strings <= 65536 chars in length // as much as triple UTF-8 strings <= 65536 chars in length
static v8::Maybe<size_t> StorageSize(v8::Isolate* isolate, static v8::Maybe<size_t> StorageSize(v8::Isolate* isolate,

View File

@ -167,42 +167,66 @@ testImmutability(crypto.getCurves);
// Regression tests for https://github.com/nodejs/node-v0.x-archive/pull/5725: // Regression tests for https://github.com/nodejs/node-v0.x-archive/pull/5725:
// hex input that's not a power of two should throw, not assert in C++ land. // hex input that's not a power of two should throw, not assert in C++ land.
assert.throws(function() {
crypto.createCipher('aes192', 'test').update('0', 'hex');
}, (err) => {
const errorMessage =
common.hasFipsCrypto ? /not supported in FIPS mode/ : /Bad input string/;
// Throws general Error, so there is no opensslErrorStack property.
if ((err instanceof Error) &&
errorMessage.test(err) &&
err.opensslErrorStack === undefined) {
return true;
}
});
assert.throws(function() { common.expectsError(
crypto.createDecipher('aes192', 'test').update('0', 'hex'); () => crypto.createCipher('aes192', 'test').update('0', 'hex'),
}, (err) => { Object.assign(
const errorMessage = common.hasFipsCrypto ?
common.hasFipsCrypto ? /not supported in FIPS mode/ : /Bad input string/; {
// Throws general Error, so there is no opensslErrorStack property. code: undefined,
if ((err instanceof Error) && type: Error,
errorMessage.test(err) && message: /not supported in FIPS mode/,
err.opensslErrorStack === undefined) { } :
return true; {
} code: 'ERR_INVALID_ARG_VALUE',
}); type: TypeError,
message: "The argument 'encoding' is invalid for data of length 1." +
" Received 'hex'",
},
{ opensslErrorStack: undefined }
)
);
assert.throws(function() { common.expectsError(
crypto.createHash('sha1').update('0', 'hex'); () => crypto.createDecipher('aes192', 'test').update('0', 'hex'),
}, (err) => { Object.assign(
// Throws TypeError, so there is no opensslErrorStack property. common.hasFipsCrypto ?
if ((err instanceof Error) && {
/^TypeError: Bad input string$/.test(err) && code: undefined,
err.opensslErrorStack === undefined) { type: Error,
return true; message: /not supported in FIPS mode/,
} :
{
code: 'ERR_INVALID_ARG_VALUE',
type: TypeError,
message: "The argument 'encoding' is invalid for data of length 1." +
" Received 'hex'",
},
{ opensslErrorStack: undefined }
)
);
common.expectsError(
() => crypto.createHash('sha1').update('0', 'hex'),
{
code: 'ERR_INVALID_ARG_VALUE',
type: TypeError,
message: "The argument 'encoding' is invalid for data of length 1." +
" Received 'hex'",
opensslErrorStack: undefined
} }
}); );
common.expectsError(
() => crypto.createHmac('sha256', 'a secret').update('0', 'hex'),
{
code: 'ERR_INVALID_ARG_VALUE',
type: TypeError,
message: "The argument 'encoding' is invalid for data of length 1." +
" Received 'hex'",
opensslErrorStack: undefined
}
);
assert.throws(function() { assert.throws(function() {
const priv = [ const priv = [