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: migrate CipherBase to internal/errors #16527

Closed
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
8 changes: 8 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,13 @@ ever, happen.

Used when an invalid [crypto digest algorithm][] is specified.

<a id="ERR_CRYPTO_INVALID_STATE"></a>
### ERR_CRYPTO_INVALID_STATE

Used generically when a crypto method is used on an object that is in an
invalid state. For instance, calling [`cipher.getAuthTag()`][] before calling
`cipher.final()`.

<a id="ERR_CRYPTO_SIGN_KEY_REQUIRED"></a>
### ERR_CRYPTO_SIGN_KEY_REQUIRED

Expand Down Expand Up @@ -1498,6 +1505,7 @@ closed.
Used when creation of a [`zlib`][] object fails due to incorrect configuration.

[`--force-fips`]: cli.html#cli_force_fips
[`cipher.getAuthTag()`]: crypto.html#crypto_cipher_getauthtag
[`crypto.timingSafeEqual()`]: crypto.html#crypto_crypto_timingsafeequal_a_b
[`dgram.createSocket()`]: dgram.html#dgram_dgram_createsocket_options_callback
[`ERR_INVALID_ARG_TYPE`]: #ERR_INVALID_ARG_TYPE
Expand Down
100 changes: 88 additions & 12 deletions lib/internal/crypto/cipher.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ const {
RSA_PKCS1_PADDING
} = process.binding('constants').crypto;

const errors = require('internal/errors');

const {
getDefaultEncoding,
toBuf
} = require('internal/crypto/util');

const { isArrayBufferView } = require('internal/util/types');

const {
CipherBase,
privateDecrypt: _privateDecrypt,
Expand Down Expand Up @@ -58,9 +62,19 @@ function getDecoder(decoder, encoding) {
function Cipher(cipher, password, options) {
if (!(this instanceof Cipher))
return new Cipher(cipher, password, options);

if (typeof cipher !== 'string')
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'cipher', 'string');

password = toBuf(password);
if (!isArrayBufferView(password)) {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'password',
['string', 'Buffer', 'TypedArray', 'DataView']);
}

this._handle = new CipherBase(true);

this._handle.init(cipher, toBuf(password));
this._handle.init(cipher, password);
this._decoder = null;

LazyTransform.call(this, options);
Expand Down Expand Up @@ -88,11 +102,16 @@ Cipher.prototype.update = function update(data, inputEncoding, outputEncoding) {
inputEncoding = inputEncoding || encoding;
outputEncoding = outputEncoding || encoding;

var ret = this._handle.update(data, inputEncoding);
if (typeof data !== 'string' && !isArrayBufferView(data)) {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'data',
['string', 'Buffer', 'TypedArray', 'DataView']);
}

const ret = this._handle.update(data, inputEncoding);

if (outputEncoding && outputEncoding !== 'buffer') {
this._decoder = getDecoder(this._decoder, outputEncoding);
ret = this._decoder.write(ret);
return this._decoder.write(ret);
}

return ret;
Expand All @@ -101,42 +120,75 @@ Cipher.prototype.update = function update(data, inputEncoding, outputEncoding) {

Cipher.prototype.final = function final(outputEncoding) {
outputEncoding = outputEncoding || getDefaultEncoding();
var ret = this._handle.final();
const ret = this._handle.final();

if (outputEncoding && outputEncoding !== 'buffer') {
this._decoder = getDecoder(this._decoder, outputEncoding);
ret = this._decoder.end(ret);
return this._decoder.end(ret);
}

return ret;
};


Cipher.prototype.setAutoPadding = function setAutoPadding(ap) {
this._handle.setAutoPadding(ap);
if (this._handle.setAutoPadding(ap) === false)
throw new errors.Error('ERR_CRYPTO_INVALID_STATE', 'setAutoPadding');
return this;
};

Cipher.prototype.getAuthTag = function getAuthTag() {
return this._handle.getAuthTag();
const ret = this._handle.getAuthTag();
if (ret === undefined)
throw new errors.Error('ERR_CRYPTO_INVALID_STATE', 'getAuthTag');
return ret;
};


Cipher.prototype.setAuthTag = function setAuthTag(tagbuf) {
this._handle.setAuthTag(tagbuf);
if (!isArrayBufferView(tagbuf)) {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'buffer',
['Buffer', 'TypedArray', 'DataView']);
}
// Do not do a normal falsy check because the method returns
// undefined if it succeeds. Returns false specifically if it
// errored
if (this._handle.setAuthTag(tagbuf) === false)
throw new errors.Error('ERR_CRYPTO_INVALID_STATE', 'setAuthTag');
return this;
};

Cipher.prototype.setAAD = function setAAD(aadbuf) {
this._handle.setAAD(aadbuf);
if (!isArrayBufferView(aadbuf)) {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'buffer',
['Buffer', 'TypedArray', 'DataView']);
}
if (this._handle.setAAD(aadbuf) === false)
throw new errors.Error('ERR_CRYPTO_INVALID_STATE', 'setAAD');
return this;
};

function Cipheriv(cipher, key, iv, options) {
if (!(this instanceof Cipheriv))
return new Cipheriv(cipher, key, iv, options);

if (typeof cipher !== 'string')
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'cipher', 'string');

key = toBuf(key);
if (!isArrayBufferView(key)) {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'key',
['string', 'Buffer', 'TypedArray', 'DataView']);
}

iv = toBuf(iv);
if (!isArrayBufferView(iv)) {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'iv',
['string', 'Buffer', 'TypedArray', 'DataView']);
}

this._handle = new CipherBase(true);
this._handle.initiv(cipher, toBuf(key), toBuf(iv));
this._handle.initiv(cipher, key, iv);
this._decoder = null;

LazyTransform.call(this, options);
Expand All @@ -158,8 +210,17 @@ function Decipher(cipher, password, options) {
if (!(this instanceof Decipher))
return new Decipher(cipher, password, options);

if (typeof cipher !== 'string')
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'cipher', 'string');

password = toBuf(password);
if (!isArrayBufferView(password)) {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'password',
['string', 'Buffer', 'TypedArray', 'DataView']);
}

this._handle = new CipherBase(false);
this._handle.init(cipher, toBuf(password));
this._handle.init(cipher, password);
this._decoder = null;

LazyTransform.call(this, options);
Expand All @@ -182,8 +243,23 @@ function Decipheriv(cipher, key, iv, options) {
if (!(this instanceof Decipheriv))
return new Decipheriv(cipher, key, iv, options);

if (typeof cipher !== 'string')
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'cipher', 'string');

key = toBuf(key);
if (!isArrayBufferView(key)) {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'key',
['string', 'Buffer', 'TypedArray', 'DataView']);
}

iv = toBuf(iv);
if (!isArrayBufferView(iv)) {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'iv',
['string', 'Buffer', 'TypedArray', 'DataView']);
}

this._handle = new CipherBase(false);
this._handle.initiv(cipher, toBuf(key), toBuf(iv));
this._handle.initiv(cipher, key, iv);
this._decoder = null;

LazyTransform.call(this, options);
Expand Down
1 change: 1 addition & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ E('ERR_CRYPTO_HASH_DIGEST_NO_UTF16', 'hash.digest() does not support UTF-16');
E('ERR_CRYPTO_HASH_FINALIZED', 'Digest already called');
E('ERR_CRYPTO_HASH_UPDATE_FAILED', 'Hash update failed');
E('ERR_CRYPTO_INVALID_DIGEST', 'Invalid digest: %s');
E('ERR_CRYPTO_INVALID_STATE', 'Invalid state for operation %s');
E('ERR_CRYPTO_SIGN_KEY_REQUIRED', 'No key provided to sign');
E('ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH',
'Input buffers must have the same length');
Expand Down
43 changes: 6 additions & 37 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,6 @@
#include <stdlib.h>
#include <string.h>

#define THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(val, prefix) \
do { \
if (!Buffer::HasInstance(val) && !val->IsString()) { \
return env->ThrowTypeError(prefix " must be a string or a buffer"); \
} \
} while (0)

#define THROW_AND_RETURN_IF_NOT_BUFFER(val, prefix) \
do { \
if (!Buffer::HasInstance(val)) { \
Expand Down Expand Up @@ -3410,14 +3403,8 @@ void CipherBase::Init(const char* cipher_type,
void CipherBase::Init(const FunctionCallbackInfo<Value>& args) {
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
Environment* env = cipher->env();

if (args.Length() < 2) {
return env->ThrowError("Cipher type and key arguments are mandatory");
}

THROW_AND_RETURN_IF_NOT_STRING(args[0], "Cipher type");
THROW_AND_RETURN_IF_NOT_BUFFER(args[1], "Key");
CHECK_GE(args.Length(), 2);

const node::Utf8Value cipher_type(args.GetIsolate(), args[0]);
const char* key_buf = Buffer::Data(args[1]);
Expand Down Expand Up @@ -3480,13 +3467,7 @@ void CipherBase::InitIv(const FunctionCallbackInfo<Value>& args) {
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
Environment* env = cipher->env();

if (args.Length() < 3) {
return env->ThrowError("Cipher type, key, and IV arguments are mandatory");
}

THROW_AND_RETURN_IF_NOT_STRING(args[0], "Cipher type");
THROW_AND_RETURN_IF_NOT_BUFFER(args[1], "Key");
THROW_AND_RETURN_IF_NOT_BUFFER(args[2], "IV");
CHECK_GE(args.Length(), 3);

const node::Utf8Value cipher_type(env->isolate(), args[0]);
ssize_t key_len = Buffer::Length(args[1]);
Expand Down Expand Up @@ -3515,7 +3496,7 @@ void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {
if (cipher->initialised_ ||
cipher->kind_ != kCipher ||
cipher->auth_tag_len_ == 0) {
return env->ThrowError("Attempting to get auth tag in unsupported state");
return args.GetReturnValue().SetUndefined();
}

Local<Object> buf =
Expand All @@ -3526,17 +3507,13 @@ void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {


void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Auth tag");

CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());

if (!cipher->initialised_ ||
!cipher->IsAuthenticatedMode() ||
cipher->kind_ != kDecipher) {
return env->ThrowError("Attempting to set auth tag in unsupported state");
return args.GetReturnValue().Set(false);
}

// FIXME(bnoordhuis) Throw when buffer length is not a valid tag size.
Expand Down Expand Up @@ -3566,15 +3543,11 @@ bool CipherBase::SetAAD(const char* data, unsigned int len) {


void CipherBase::SetAAD(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "AAD");

CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());

if (!cipher->SetAAD(Buffer::Data(args[0]), Buffer::Length(args[0])))
env->ThrowError("Attempting to set AAD in unsupported state");
args.GetReturnValue().Set(false); // Report invalid state failure
}


Expand Down Expand Up @@ -3610,8 +3583,6 @@ void CipherBase::Update(const FunctionCallbackInfo<Value>& args) {
CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());

THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0], "Cipher data");

unsigned char* out = nullptr;
bool r;
int out_len = 0;
Expand Down Expand Up @@ -3651,13 +3622,11 @@ bool CipherBase::SetAutoPadding(bool auto_padding) {


void CipherBase::SetAutoPadding(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

CipherBase* cipher;
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());

if (!cipher->SetAutoPadding(args.Length() < 1 || args[0]->BooleanValue()))
env->ThrowError("Attempting to set auto padding in unsupported state");
args.GetReturnValue().Set(false); // Report invalid state failure
}


Expand Down
46 changes: 32 additions & 14 deletions test/parallel/test-crypto-cipher-decipher.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,14 @@ testCipher2(Buffer.from('0123456789abcdef'));
cipher.setAAD(aadbuf);
cipher.setAutoPadding();

assert.throws(() => {
cipher.getAuthTag();
}, /^Error: Attempting to get auth tag in unsupported state$/);
common.expectsError(
() => cipher.getAuthTag(),
{
code: 'ERR_CRYPTO_INVALID_STATE',
type: Error,
message: 'Invalid state for operation getAuthTag'
}
);

const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);

Expand All @@ -175,15 +180,28 @@ testCipher2(Buffer.from('0123456789abcdef'));
decipher.update(encrypted);
decipher.final();

assert.throws(() => {
decipher.setAAD(aadbuf);
}, /^Error: Attempting to set AAD in unsupported state$/);

assert.throws(() => {
decipher.setAuthTag(cipher.getAuthTag());
}, /^Error: Attempting to set auth tag in unsupported state$/);

assert.throws(() => {
decipher.setAutoPadding();
}, /^Error: Attempting to set auto padding in unsupported state$/);
common.expectsError(
() => decipher.setAAD(aadbuf),
{
code: 'ERR_CRYPTO_INVALID_STATE',
type: Error,
message: 'Invalid state for operation setAAD'
});

common.expectsError(
() => decipher.setAuthTag(cipher.getAuthTag()),
{
code: 'ERR_CRYPTO_INVALID_STATE',
type: Error,
message: 'Invalid state for operation setAuthTag'
});

common.expectsError(
() => decipher.setAutoPadding(),
{
code: 'ERR_CRYPTO_INVALID_STATE',
type: Error,
message: 'Invalid state for operation setAutoPadding'
}
);
}