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

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

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/internal/crypto/cipher.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_OPT_VALUE
} = require('internal/errors').codes;
const { validateString } = require('internal/validators');
const { validateEncoding, validateString } = require('internal/validators');

const {
preparePrivateKey,
Expand Down Expand Up @@ -161,6 +161,8 @@ Cipher.prototype.update = function update(data, inputEncoding, outputEncoding) {
throw invalidArrayBufferView('data', data);
}

validateEncoding(data, inputEncoding);

const ret = this[kHandle].update(data, inputEncoding);

if (outputEncoding && outputEncoding !== 'buffer') {
Expand Down
9 changes: 7 additions & 2 deletions lib/internal/crypto/hash.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const {
ERR_CRYPTO_HASH_UPDATE_FAILED,
ERR_INVALID_ARG_TYPE
} = require('internal/errors').codes;
const { validateString, validateUint32 } = require('internal/validators');
const { validateEncoding, validateString, validateUint32 } =
require('internal/validators');
const { normalizeEncoding } = require('internal/util');
const { isArrayBufferView } = require('internal/util/types');
const LazyTransform = require('internal/streams/lazy_transform');
Expand Down Expand Up @@ -61,6 +62,8 @@ Hash.prototype._flush = function _flush(callback) {
};

Hash.prototype.update = function update(data, encoding) {
encoding = encoding || getDefaultEncoding();

const state = this[kState];
if (state[kFinalized])
throw new ERR_CRYPTO_HASH_FINALIZED();
Expand All @@ -74,7 +77,9 @@ Hash.prototype.update = function update(data, encoding) {
data);
}

if (!this[kHandle].update(data, encoding || getDefaultEncoding()))
validateEncoding(data, encoding);

if (!this[kHandle].update(data, encoding))
throw new ERR_CRYPTO_HASH_UPDATE_FAILED();
return this;
};
Expand Down
14 changes: 14 additions & 0 deletions lib/internal/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
ERR_UNKNOWN_SIGNAL
}
} = require('internal/errors');
const { normalizeEncoding } = require('internal/util');
const {
isArrayBufferView
} = require('internal/util/types');
Expand Down Expand Up @@ -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?
mcollina marked this conversation as resolved.
Show resolved Hide resolved
}

module.exports = {
isInt32,
isUint32,
parseMode,
validateBuffer,
validateEncoding,
validateInteger,
validateInt32,
validateUint32,
Expand Down
2 changes: 1 addition & 1 deletion src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -843,7 +843,7 @@ void IndexOfString(const FunctionCallbackInfo<Value>& args) {

if (IsBigEndian()) {
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 =
reinterpret_cast<const uint16_t*>(decoder.out());

Expand Down
15 changes: 9 additions & 6 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4317,8 +4317,9 @@ void CipherBase::Update(const FunctionCallbackInfo<Value>& args) {
// Only copy the data if we have to, because it's a string
if (args[0]->IsString()) {
StringBytes::InlineDecoder decoder;
if (!decoder.Decode(env, args[0].As<String>(), args[1], UTF8)
.FromMaybe(false))
enum encoding enc = ParseEncoding(env->isolate(), args[1], UTF8);

if (decoder.Decode(env, args[0].As<String>(), enc).IsNothing())
return;
r = cipher->Update(decoder.out(), decoder.size(), &out);
} else {
Expand Down Expand Up @@ -4501,8 +4502,9 @@ void Hmac::HmacUpdate(const FunctionCallbackInfo<Value>& args) {
bool r = false;
if (args[0]->IsString()) {
StringBytes::InlineDecoder decoder;
if (decoder.Decode(env, args[0].As<String>(), args[1], UTF8)
.FromMaybe(false)) {
enum encoding enc = ParseEncoding(env->isolate(), args[1], UTF8);

if (!decoder.Decode(env, args[0].As<String>(), enc).IsNothing()) {
r = hmac->HmacUpdate(decoder.out(), decoder.size());
}
} else {
Expand Down Expand Up @@ -4626,8 +4628,9 @@ void Hash::HashUpdate(const FunctionCallbackInfo<Value>& args) {
bool r = true;
if (args[0]->IsString()) {
StringBytes::InlineDecoder decoder;
if (!decoder.Decode(env, args[0].As<String>(), args[1], UTF8)
.FromMaybe(false)) {
enum encoding enc = ParseEncoding(env->isolate(), args[1], UTF8);

if (decoder.Decode(env, args[0].As<String>(), enc).IsNothing()) {
args.GetReturnValue().Set(false);
return;
}
Expand Down
9 changes: 0 additions & 9 deletions src/string_bytes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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?
joyeecheung marked this conversation as resolved.
Show resolved Hide resolved
return true;
}


// Quick and dirty size calculation
// 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
Expand Down
15 changes: 1 addition & 14 deletions src/string_bytes.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,7 @@ class StringBytes {
public:
inline v8::Maybe<bool> Decode(Environment* env,
v8::Local<v8::String> string,
v8::Local<v8::Value> encoding,
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>();
}

enum encoding enc) {
size_t storage;
if (!StringBytes::StorageSize(env->isolate(), string, enc).To(&storage))
return v8::Nothing<bool>();
Expand All @@ -60,12 +53,6 @@ class StringBytes {
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
// as much as triple UTF-8 strings <= 65536 chars in length
static v8::Maybe<size_t> StorageSize(v8::Isolate* isolate,
Expand Down
88 changes: 56 additions & 32 deletions test/parallel/test-crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,42 +167,66 @@ testImmutability(crypto.getCurves);

// 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.
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() {
crypto.createDecipher('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;
common.expectsError(
() => crypto.createCipher('aes192', 'test').update('0', 'hex'),
Object.assign(
common.hasFipsCrypto ?
{
code: undefined,
type: Error,
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.createDecipher('aes192', 'test').update('0', 'hex'),
Object.assign(
common.hasFipsCrypto ?
{
code: undefined,
type: Error,
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
}
});
);

assert.throws(function() {
crypto.createHash('sha1').update('0', 'hex');
}, (err) => {
// Throws TypeError, so there is no opensslErrorStack property.
if ((err instanceof Error) &&
/^TypeError: Bad input string$/.test(err) &&
err.opensslErrorStack === undefined) {
return true;
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() {
const priv = [
Expand Down