non-standard padding, e.g. using `0x0` instead of PKCS padding. You
must call this before `cipher.final`.
+### cipher.getAuthTag()
+
+For authenticated encryption modes (currently supported: GCM), this
+method returns a `Buffer` that represents the _authentication tag_ that
+has been computed from the given data. Should be called after
+encryption has been completed using the `final` method!
+
## crypto.createDecipher(algorithm, password)
the ciphers block size. You must call this before streaming data to
`decipher.update`.
+### decipher.setAuthTag(buffer)
+
+For authenticated encryption modes (currently supported: GCM), this
+method must be used to pass in the received _authentication tag_.
+If no tag is provided or if the ciphertext has been tampered with,
+`final` will throw, thus indicating that the ciphertext should
+be discarded due to failed authentication.
+
+
## crypto.createSign(algorithm)
Creates and returns a signing object, with the given algorithm. On
Cipheriv.prototype.final = Cipher.prototype.final;
Cipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding;
+Cipheriv.prototype.getAuthTag = function() {
+ return this._binding.getAuthTag();
+};
+
+
+Cipheriv.prototype.setAuthTag = function(tagbuf) {
+ this._binding.setAuthTag(tagbuf);
+};
+
exports.createDecipher = exports.Decipher = Decipher;
Decipheriv.prototype.final = Cipher.prototype.final;
Decipheriv.prototype.finaltol = Cipher.prototype.final;
Decipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding;
+Decipheriv.prototype.getAuthTag = Cipheriv.prototype.getAuthTag;
+Decipheriv.prototype.setAuthTag = Cipheriv.prototype.setAuthTag;
NODE_SET_PROTOTYPE_METHOD(t, "update", Update);
NODE_SET_PROTOTYPE_METHOD(t, "final", Final);
NODE_SET_PROTOTYPE_METHOD(t, "setAutoPadding", SetAutoPadding);
+ NODE_SET_PROTOTYPE_METHOD(t, "getAuthTag", GetAuthTag);
+ NODE_SET_PROTOTYPE_METHOD(t, "setAuthTag", SetAuthTag);
target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "CipherBase"),
t->GetFunction());
}
+bool CipherBase::IsAuthenticatedMode() const {
+ // check if this cipher operates in an AEAD mode that we support.
+ if (!cipher_)
+ return false;
+ int mode = EVP_CIPHER_mode(cipher_);
+ return mode == EVP_CIPH_GCM_MODE;
+}
+
+
+bool CipherBase::GetAuthTag(char** out, unsigned int* out_len) const {
+ // only callable after Final and if encrypting.
+ if (initialised_ || kind_ != kCipher || !auth_tag_)
+ return false;
+ *out_len = auth_tag_len_;
+ *out = new char[auth_tag_len_];
+ memcpy(*out, auth_tag_, auth_tag_len_);
+ return true;
+}
+
+
+void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args.GetIsolate());
+ HandleScope handle_scope(args.GetIsolate());
+ CipherBase* cipher = Unwrap<CipherBase>(args.This());
+
+ char* out = NULL;
+ unsigned int out_len = 0;
+
+ if (cipher->GetAuthTag(&out, &out_len)) {
+ Local<Object> buf = Buffer::Use(env, out, out_len);
+ args.GetReturnValue().Set(buf);
+ } else {
+ ThrowError("Attempting to get auth tag in unsupported state");
+ }
+}
+
+
+bool CipherBase::SetAuthTag(const char* data, unsigned int len) {
+ if (!initialised_ || !IsAuthenticatedMode() || kind_ != kDecipher)
+ return false;
+ delete[] auth_tag_;
+ auth_tag_len_ = len;
+ auth_tag_ = new char[len];
+ memcpy(auth_tag_, data, len);
+ return true;
+}
+
+
+void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
+ HandleScope handle_scope(args.GetIsolate());
+
+ Local<Object> buf = args[0].As<Object>();
+ if (!buf->IsObject() || !Buffer::HasInstance(buf))
+ return ThrowTypeError("Argument must be a Buffer");
+
+ CipherBase* cipher = Unwrap<CipherBase>(args.This());
+
+ if (!cipher->SetAuthTag(Buffer::Data(buf), Buffer::Length(buf)))
+ ThrowError("Attempting to set auth tag in unsupported state");
+}
+
+
bool CipherBase::Update(const char* data,
int len,
unsigned char** out,
int* out_len) {
if (!initialised_)
return 0;
+
+ // on first update:
+ if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_ != NULL) {
+ EVP_CIPHER_CTX_ctrl(&ctx_,
+ EVP_CTRL_GCM_SET_TAG,
+ auth_tag_len_,
+ reinterpret_cast<unsigned char*>(auth_tag_));
+ delete[] auth_tag_;
+ auth_tag_ = NULL;
+ }
+
*out_len = len + EVP_CIPHER_CTX_block_size(&ctx_);
*out = new unsigned char[*out_len];
return EVP_CipherUpdate(&ctx_,
*out = new unsigned char[EVP_CIPHER_CTX_block_size(&ctx_)];
bool r = EVP_CipherFinal_ex(&ctx_, *out, out_len);
+
+ if (r && kind_ == kCipher) {
+ delete[] auth_tag_;
+ auth_tag_ = NULL;
+ if (IsAuthenticatedMode()) {
+ auth_tag_len_ = EVP_GCM_TLS_TAG_LEN; // use default tag length
+ auth_tag_ = new char[auth_tag_len_];
+ memset(auth_tag_, 0, auth_tag_len_);
+ EVP_CIPHER_CTX_ctrl(&ctx_,
+ EVP_CTRL_GCM_GET_TAG,
+ auth_tag_len_,
+ reinterpret_cast<unsigned char*>(auth_tag_));
+ }
+ }
+
EVP_CIPHER_CTX_cleanup(&ctx_);
initialised_ = false;
~CipherBase() {
if (!initialised_)
return;
+ delete[] auth_tag_;
EVP_CIPHER_CTX_cleanup(&ctx_);
}
bool Final(unsigned char** out, int *out_len);
bool SetAutoPadding(bool auto_padding);
+ bool IsAuthenticatedMode() const;
+ bool GetAuthTag(char** out, unsigned int* out_len) const;
+ bool SetAuthTag(const char* data, unsigned int len);
+
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InitIv(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Final(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAutoPadding(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void GetAuthTag(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void SetAuthTag(const v8::FunctionCallbackInfo<v8::Value>& args);
+
CipherBase(Environment* env,
v8::Local<v8::Object> wrap,
CipherKind kind)
: BaseObject(env, wrap),
cipher_(NULL),
initialised_(false),
- kind_(kind) {
+ kind_(kind),
+ auth_tag_(NULL),
+ auth_tag_len_(0) {
MakeWeak<CipherBase>(this);
}
const EVP_CIPHER* cipher_; /* coverity[member_decl] */
bool initialised_;
CipherKind kind_;
+ char* auth_tag_;
+ unsigned int auth_tag_len_;
};
class Hmac : public BaseObject {
--- /dev/null
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+
+
+var common = require('../common');
+var assert = require('assert');
+
+try {
+ var crypto = require('crypto');
+} catch (e) {
+ console.log('Not compiled with OPENSSL support.');
+ process.exit();
+}
+
+crypto.DEFAULT_ENCODING = 'buffer';
+
+//
+// Test authenticated encryption modes.
+//
+// !NEVER USE STATIC IVs IN REAL LIFE!
+//
+
+var TEST_CASES = [
+ { algo: 'aes-128-gcm', key: 'ipxp9a6i1Mb4USb4', iv: 'X6sIq117H0vR',
+ plain: 'Hello World!', ct: '4BE13896F64DFA2C2D0F2C76',
+ tag: '272B422F62EB545EAA15B5FF84092447', tampered: false },
+ { algo: 'aes-128-gcm', key: 'ipxp9a6i1Mb4USb4', iv: 'X6sIq117H0vR',
+ plain: 'Hello World!', ct: '4BE13596F64DFA2C2D0FAC76',
+ tag: '272B422F62EB545EAA15B5FF84092447', tampered: true },
+ { algo: 'aes-256-gcm', key: '3zTvzr3p67VC61jmV54rIYu1545x4TlY',
+ iv: '60iP0h6vJoEa', plain: 'Hello node.js world!',
+ ct: '58E62CFE7B1D274111A82267EBB93866E72B6C2A',
+ tag: '9BB44F663BADABACAE9720881FB1EC7A', tampered: false },
+ { algo: 'aes-256-gcm', key: '3zTvzr3p67VC61jmV54rIYu1545x4TlY',
+ iv: '60iP0h6vJoEa', plain: 'Hello node.js world!',
+ ct: '58E62CFF7B1D274011A82267EBB93866E72B6C2B',
+ tag: '9BB44F663BADABACAE9720881FB1EC7A', tampered: true },
+];
+
+var ciphers = crypto.getCiphers();
+
+for (var i in TEST_CASES) {
+ var test = TEST_CASES[i];
+
+ if (ciphers.indexOf(test.algo) == -1) {
+ console.log('skipping unsupported ' + test.algo + ' test');
+ continue;
+ }
+
+ (function() {
+ var encrypt = crypto.createCipheriv(test.algo, test.key, test.iv);
+ var hex = encrypt.update(test.plain, 'ascii', 'hex');
+ hex += encrypt.final('hex');
+ var auth_tag = encrypt.getAuthTag();
+ // only test basic encryption run if output is marked as tampered.
+ if (!test.tampered) {
+ assert.equal(hex.toUpperCase(), test.ct);
+ assert.equal(auth_tag.toString('hex').toUpperCase(), test.tag);
+ }
+ })();
+
+ (function() {
+ var decrypt = crypto.createDecipheriv(test.algo, test.key, test.iv);
+ decrypt.setAuthTag(new Buffer(test.tag, 'hex'));
+ var msg = decrypt.update(test.ct, 'hex', 'ascii');
+ if (!test.tampered) {
+ msg += decrypt.final('ascii');
+ assert.equal(msg, test.plain);
+ } else {
+ // assert that final throws if input data could not be verified!
+ assert.throws(function() { decrypt.final('ascii'); });
+ }
+ })();
+
+ // after normal operation, test some incorrect ways of calling the API:
+ // it's most certainly enough to run these tests with one algorithm only.
+
+ if (i > 0) {
+ continue;
+ }
+
+ (function() {
+ // non-authenticating mode:
+ var encrypt = crypto.createCipheriv('aes-128-cbc',
+ 'ipxp9a6i1Mb4USb4', '6fKjEjR3Vl30EUYC');
+ encrypt.update('blah', 'ascii');
+ encrypt.final();
+ assert.throws(function() { encrypt.getAuthTag(); });
+ })();
+
+ (function() {
+ // trying to get tag before inputting all data:
+ var encrypt = crypto.createCipheriv(test.algo, test.key, test.iv);
+ encrypt.update('blah', 'ascii');
+ assert.throws(function() { encrypt.getAuthTag(); });
+ })();
+
+ (function() {
+ // trying to set tag on encryption object:
+ var encrypt = crypto.createCipheriv(test.algo, test.key, test.iv);
+ assert.throws(function() {
+ encrypt.setAuthTag(new Buffer(test.tag, 'hex')); });
+ })();
+
+ (function() {
+ // trying to read tag from decryption object:
+ var decrypt = crypto.createDecipheriv(test.algo, test.key, test.iv);
+ assert.throws(function() { decrypt.getAuthTag(); });
+ })();
+}