From bbc8da2366e23cd6e9852f0c05b9aa6a85b52c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Thu, 14 Jun 2018 15:18:14 +0200 Subject: [PATCH 1/3] crypto: add support for OCB mode for AEAD --- src/node_crypto.cc | 48 ++-- test/parallel/test-crypto-authenticated.js | 252 +++++++++++++++++++-- 2 files changed, 261 insertions(+), 39 deletions(-) diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 5bceae0ce00c8b..e1180ff90e3571 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -2683,6 +2683,9 @@ void CipherBase::Init(const FunctionCallbackInfo& args) { cipher->Init(*cipher_type, key_buf, key_buf_len, auth_tag_len); } +#define IS_SUPPORTED_AUTHENTICATED_MODE(mode) ((mode) == EVP_CIPH_CCM_MODE || \ + (mode) == EVP_CIPH_GCM_MODE || \ + (mode) == EVP_CIPH_OCB_MODE) void CipherBase::InitIv(const char* cipher_type, const char* key, @@ -2700,8 +2703,7 @@ void CipherBase::InitIv(const char* cipher_type, const int expected_iv_len = EVP_CIPHER_iv_length(cipher); const int mode = EVP_CIPHER_mode(cipher); - const bool is_gcm_mode = (EVP_CIPH_GCM_MODE == mode); - const bool is_ccm_mode = (EVP_CIPH_CCM_MODE == mode); + const bool is_authenticated_mode = IS_SUPPORTED_AUTHENTICATED_MODE(mode); const bool has_iv = iv_len >= 0; // Throw if no IV was passed and the cipher requires an IV @@ -2712,7 +2714,7 @@ void CipherBase::InitIv(const char* cipher_type, } // Throw if an IV was passed which does not match the cipher's fixed IV length - if (!is_gcm_mode && !is_ccm_mode && has_iv && iv_len != expected_iv_len) { + if (!is_authenticated_mode && has_iv && iv_len != expected_iv_len) { return env()->ThrowError("Invalid IV length"); } @@ -2728,7 +2730,7 @@ void CipherBase::InitIv(const char* cipher_type, "Failed to initialize cipher"); } - if (IsAuthenticatedMode()) { + if (is_authenticated_mode) { CHECK(has_iv); if (!InitAuthenticated(cipher_type, iv_len, auth_tag_len)) return; @@ -2803,7 +2805,7 @@ bool CipherBase::InitAuthenticated(const char* cipher_type, int iv_len, } const int mode = EVP_CIPHER_CTX_mode(ctx_.get()); - if (mode == EVP_CIPH_CCM_MODE) { + if (mode == EVP_CIPH_CCM_MODE || mode == EVP_CIPH_OCB_MODE) { if (auth_tag_len == kNoAuthTagLength) { char msg[128]; snprintf(msg, sizeof(msg), "authTagLength required for %s", cipher_type); @@ -2813,25 +2815,29 @@ bool CipherBase::InitAuthenticated(const char* cipher_type, int iv_len, #ifdef NODE_FIPS_MODE // TODO(tniessen) Support CCM decryption in FIPS mode - if (kind_ == kDecipher && FIPS_mode()) { + if (mode == EVP_CIPH_CCM_MODE && kind_ == kDecipher && FIPS_mode()) { env()->ThrowError("CCM decryption not supported in FIPS mode"); return false; } #endif - if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_CCM_SET_TAG, auth_tag_len, + // Tell OpenSSL about the desired length. + if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_SET_TAG, auth_tag_len, nullptr)) { env()->ThrowError("Invalid authentication tag length"); return false; } + // Remember the given authentication tag length for later. auth_tag_len_ = auth_tag_len; - // Restrict the message length to min(INT_MAX, 2^(8*(15-iv_len))-1) bytes. - CHECK(iv_len >= 7 && iv_len <= 13); - max_message_size_ = INT_MAX; - if (iv_len == 12) max_message_size_ = 16777215; - if (iv_len == 13) max_message_size_ = 65535; + if (mode == EVP_CIPH_CCM_MODE) { + // Restrict the message length to min(INT_MAX, 2^(8*(15-iv_len))-1) bytes. + CHECK(iv_len >= 7 && iv_len <= 13); + max_message_size_ = INT_MAX; + if (iv_len == 12) max_message_size_ = 16777215; + if (iv_len == 13) max_message_size_ = 65535; + } } else { CHECK_EQ(mode, EVP_CIPH_GCM_MODE); @@ -2870,7 +2876,7 @@ bool CipherBase::IsAuthenticatedMode() const { // Check if this cipher operates in an AEAD mode that we support. CHECK(ctx_); const int mode = EVP_CIPHER_CTX_mode(ctx_.get()); - return mode == EVP_CIPH_GCM_MODE || mode == EVP_CIPH_CCM_MODE; + return IS_SUPPORTED_AUTHENTICATED_MODE(mode); } @@ -2903,16 +2909,18 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo& args) { return args.GetReturnValue().Set(false); } - // Restrict GCM tag lengths according to NIST 800-38d, page 9. unsigned int tag_len = Buffer::Length(args[0]); const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_.get()); bool is_valid; if (mode == EVP_CIPH_GCM_MODE) { + // Restrict GCM tag lengths according to NIST 800-38d, page 9. is_valid = (cipher->auth_tag_len_ == kNoAuthTagLength || cipher->auth_tag_len_ == tag_len) && IsValidGCMTagLength(tag_len); } else { - CHECK_EQ(mode, EVP_CIPH_CCM_MODE); + // At this point, the tag length is already known and must match the + // length of the given authentication tag. + CHECK(mode == EVP_CIPH_CCM_MODE || mode == EVP_CIPH_OCB_MODE); CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength); is_valid = cipher->auth_tag_len_ == tag_len; } @@ -3008,7 +3016,7 @@ CipherBase::UpdateResult CipherBase::Update(const char* data, if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_len_ > 0 && auth_tag_len_ != kNoAuthTagLength && !auth_tag_set_) { CHECK(EVP_CIPHER_CTX_ctrl(ctx_.get(), - EVP_CTRL_GCM_SET_TAG, + EVP_CTRL_AEAD_SET_TAG, auth_tag_len_, reinterpret_cast(auth_tag_))); auth_tag_set_ = true; @@ -3121,10 +3129,12 @@ bool CipherBase::Final(unsigned char** out, int* out_len) { if (ok && kind_ == kCipher && IsAuthenticatedMode()) { // In GCM mode, the authentication tag length can be specified in advance, - // but defaults to 16 bytes when encrypting. In CCM mode, it must always - // be given by the user. - if (mode == EVP_CIPH_GCM_MODE && auth_tag_len_ == kNoAuthTagLength) + // but defaults to 16 bytes when encrypting. In CCM and OCB mode, it must + // always be given by the user. + if (auth_tag_len_ == kNoAuthTagLength) { + CHECK(mode == EVP_CIPH_GCM_MODE); auth_tag_len_ = sizeof(auth_tag_); + } CHECK_EQ(1, EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_GET_TAG, auth_tag_len_, reinterpret_cast(auth_tag_))); diff --git a/test/parallel/test-crypto-authenticated.js b/test/parallel/test-crypto-authenticated.js index a4fe105b7edbe7..f7e108dcdda6d4 100644 --- a/test/parallel/test-crypto-authenticated.js +++ b/test/parallel/test-crypto-authenticated.js @@ -499,6 +499,207 @@ const TEST_CASES = [ tag: '65a6002b2cdfe9f00027f839332ca6fc', tampered: false }, + + // OCB test cases from RFC7253 + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa99887766554433221100', + plain: '', + ct: '', + tag: '785407bfffc8ad9edcc5520ac9111ee6' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa99887766554433221101', + plain: '0001020304050607', + plainIsHex: true, + aad: '0001020304050607', + ct: '6820b3657b6f615a', + tag: '5725bda0d3b4eb3a257c9af1f8f03009' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa99887766554433221102', + plain: '', + aad: '0001020304050607', + ct: '', + tag: '81017f8203f081277152fade694a0a00' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa99887766554433221103', + plain: '0001020304050607', + plainIsHex: true, + ct: '45dd69f8f5aae724', + tag: '14054cd1f35d82760b2cd00d2f99bfa9' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa99887766554433221104', + plain: '000102030405060708090a0b0c0d0e0f', + plainIsHex: true, + aad: '000102030405060708090a0b0c0d0e0f', + ct: '571d535b60b277188be5147170a9a22c', + tag: '3ad7a4ff3835b8c5701c1ccec8fc3358' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa99887766554433221105', + plain: '', + aad: '000102030405060708090a0b0c0d0e0f', + ct: '', + tag: '8cf761b6902ef764462ad86498ca6b97' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa99887766554433221106', + plain: '000102030405060708090a0b0c0d0e0f', + plainIsHex: true, + ct: '5ce88ec2e0692706a915c00aeb8b2396', + tag: 'f40e1c743f52436bdf06d8fa1eca343d' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa99887766554433221107', + plain: '000102030405060708090a0b0c0d0e0f1011121314151617', + plainIsHex: true, + aad: '000102030405060708090a0b0c0d0e0f1011121314151617', + ct: '1ca2207308c87c010756104d8840ce1952f09673a448a122', + tag: 'c92c62241051f57356d7f3c90bb0e07f' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa99887766554433221108', + plain: '', + aad: '000102030405060708090a0b0c0d0e0f1011121314151617', + ct: '', + tag: '6dc225a071fc1b9f7c69f93b0f1e10de' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa99887766554433221109', + plain: '000102030405060708090a0b0c0d0e0f1011121314151617', + plainIsHex: true, + ct: '221bd0de7fa6fe993eccd769460a0af2d6cded0c395b1c3c', + tag: 'e725f32494b9f914d85c0b1eb38357ff' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa9988776655443322110a', + plain: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', + plainIsHex: true, + aad: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', + ct: 'bd6f6c496201c69296c11efd138a467abd3c707924b964deaffc40319af5a485', + tag: '40fbba186c5553c68ad9f592a79a4240' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa9988776655443322110b', + plain: '', + aad: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', + ct: '', + tag: 'fe80690bee8a485d11f32965bc9d2a32' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa9988776655443322110c', + plain: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', + plainIsHex: true, + ct: '2942bfc773bda23cabc6acfd9bfd5835bd300f0973792ef46040c53f1432bcdf', + tag: 'b5e1dde3bc18a5f840b52e653444d5df' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa9988776655443322110d', + plain: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f' + + '2021222324252627', + plainIsHex: true, + aad: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20' + + '21222324252627', + ct: 'd5ca91748410c1751ff8a2f618255b68a0a12e093ff454606e59f9c1d0ddc54b65e8' + + '628e568bad7a', + tag: 'ed07ba06a4a69483a7035490c5769e60' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa9988776655443322110e', + plain: '', + plainIsHex: true, + aad: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20' + + '21222324252627', + ct: '', + tag: 'c5cd9d1850c141e358649994ee701b68' + }, + + { + algo: 'aes-128-ocb', + key: '000102030405060708090a0b0c0d0e0f', + iv: 'bbaa9988776655443322110f', + plain: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f' + + '2021222324252627', + plainIsHex: true, + ct: '4412923493c57d5de0d700f753cce0d1d2d95060122e9f15a5ddbfc5787e50b5cc55' + + 'ee507bcb084e', + tag: '479ad363ac366b95a98ca5f3000b1479' + }, + + { + algo: 'aes-128-ocb', + key: '0f0e0d0c0b0a09080706050403020100', + iv: 'bbaa9988776655443322110d', + plain: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f' + + '2021222324252627', + plainIsHex: true, + aad: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20' + + '21222324252627', + ct: '1792a4e31e0755fb03e31b22116e6c2ddf9efd6e33d536f1a0124b0a55bae884ed93' + + '481529c76b6a', + tag: 'd0c515f4d1cdd4fdac4f02aa' + }, + + { + algo: 'aes-128-ocb', + key: '0f0e0d0c0b0a09080706050403020100', + iv: 'bbaa9988776655443322110d', + plain: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f' + + '2021222324252627', + plainIsHex: true, + aad: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20' + + '21222324252627', + ct: '1792a4e31e0755fb03e31b22116e6c2ddf9efd6e33d536f1a0124b0a55bae884ed93' + + '481529c76b6a', + tag: 'd0c515f4d1cdd4fdac4f02ab', + tampered: true + } ]; const errMessages = { @@ -554,9 +755,10 @@ for (const test of TEST_CASES) { } const isCCM = /^aes-(128|192|256)-ccm$/.test(test.algo); + const isOCB = /^aes-(128|192|256)-ocb$/.test(test.algo); let options; - if (isCCM) + if (isCCM || isOCB) options = { authTagLength: test.tag.length / 2 }; const inputEncoding = test.plainIsHex ? 'hex' : 'ascii'; @@ -881,30 +1083,40 @@ for (const test of TEST_CASES) { } } -// Test that create(De|C)ipher(iv)? throws if the mode is CCM and no +// Test that create(De|C)ipher(iv)? throws if the mode is CCM or OCB and no // authentication tag has been specified. { - assert.throws(() => { - crypto.createCipheriv('aes-256-ccm', - 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', - 'qkuZpJWCewa6S'); - }, /^Error: authTagLength required for aes-256-ccm$/); - - // CCM decryption and create(De|C)ipher are unsupported in FIPS mode. - if (!common.hasFipsCrypto) { + for (const mode of ['ccm', 'ocb']) { assert.throws(() => { - crypto.createDecipheriv('aes-256-ccm', - 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', - 'qkuZpJWCewa6S'); - }, /^Error: authTagLength required for aes-256-ccm$/); + crypto.createCipheriv(`aes-256-${mode}`, + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6S'); + }, { + message: `authTagLength required for aes-256-${mode}` + }); - assert.throws(() => { - crypto.createCipher('aes-256-ccm', 'very bad password'); - }, /^Error: authTagLength required for aes-256-ccm$/); + // CCM decryption and create(De|C)ipher are unsupported in FIPS mode. + if (!common.hasFipsCrypto) { + assert.throws(() => { + crypto.createDecipheriv(`aes-256-${mode}`, + 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8', + 'qkuZpJWCewa6S'); + }, { + message: `authTagLength required for aes-256-${mode}` + }); - assert.throws(() => { - crypto.createDecipher('aes-256-ccm', 'very bad password'); - }, /^Error: authTagLength required for aes-256-ccm$/); + assert.throws(() => { + crypto.createCipher(`aes-256-${mode}`, 'very bad password'); + }, { + message: `authTagLength required for aes-256-${mode}` + }); + + assert.throws(() => { + crypto.createDecipher(`aes-256-${mode}`, 'very bad password'); + }, { + message: `authTagLength required for aes-256-${mode}` + }); + } } } From 501017a41ad627bcdb70617df646bfa29717e03f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Thu, 21 Jun 2018 17:54:17 +0200 Subject: [PATCH 2/3] doc: adapt crypto docs to explain OCB mode --- doc/api/crypto.md | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 7d7e913db18f32..4968701b18112c 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -249,11 +249,11 @@ added: v1.0.0 - `plaintextLength` {number} * Returns: {Cipher} for method chaining. -When using an authenticated encryption mode (only `GCM` and `CCM` are currently -supported), the `cipher.setAAD()` method sets the value used for the +When using an authenticated encryption mode (`GCM`, `CCM` and `OCB` are +currently supported), the `cipher.setAAD()` method sets the value used for the _additional authenticated data_ (AAD) input parameter. -The `options` argument is optional for `GCM`. When using `CCM`, the +The `options` argument is optional for `GCM` and `OCB`. When using `CCM`, the `plaintextLength` option must be specified and its value must match the length of the plaintext in bytes. See [CCM mode][]. @@ -263,8 +263,8 @@ The `cipher.setAAD()` method must be called before [`cipher.update()`][]. -* Returns: {Buffer} When using an authenticated encryption mode (only `GCM` and - `CCM` are currently supported), the `cipher.getAuthTag()` method returns a +* Returns: {Buffer} When using an authenticated encryption mode (`GCM`, `CCM` + and `OCB` are currently supported), the `cipher.getAuthTag()` method returns a [`Buffer`][] containing the _authentication tag_ that has been computed from the given data. @@ -412,8 +412,8 @@ changes: - `plaintextLength` {number} * Returns: {Decipher} for method chaining. -When using an authenticated encryption mode (only `GCM` and `CCM` are currently -supported), the `decipher.setAAD()` method sets the value used for the +When using an authenticated encryption mode (`GCM`, `CCM` and `OCB` are +currently supported), the `decipher.setAAD()` method sets the value used for the _additional authenticated data_ (AAD) input parameter. The `options` argument is optional for `GCM`. When using `CCM`, the @@ -436,8 +436,8 @@ changes: * `buffer` {Buffer | TypedArray | DataView} * Returns: {Decipher} for method chaining. -When using an authenticated encryption mode (only `GCM` and `CCM` are currently -supported), the `decipher.setAuthTag()` method is used to pass in the +When using an authenticated encryption mode (`GCM`, `CCM` and `OCB` are +currently supported), the `decipher.setAuthTag()` method is used to pass in the received _authentication tag_. If no tag is provided, or if the cipher text has been tampered with, [`decipher.final()`][] will throw, indicating that the cipher text should be discarded due to failed authentication. If the tag length @@ -1321,6 +1321,9 @@ This property is deprecated. Please use `crypto.setFips()` and added: v0.1.94 deprecated: v10.0.0 changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/??? + description: Ciphers in OCB mode are now supported. - version: v10.2.0 pr-url: https://github.com/nodejs/node/pull/20235 description: The `authTagLength` option can now be used to produce shorter @@ -1338,7 +1341,7 @@ Creates and returns a `Cipher` object that uses the given `algorithm` and `password`. The `options` argument controls stream behavior and is optional except when a -cipher in CCM mode is used (e.g. `'aes-128-ccm'`). In that case, the +cipher in CCM or OCB mode is used (e.g. `'aes-128-ccm'`). In that case, the `authTagLength` option is required and specifies the length of the authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength` option is not required but can be used to set the length of the authentication @@ -1373,6 +1376,9 @@ Adversaries][] for details. > Stability: 0 - Deprecated: Use [`crypto.createDecipheriv()`][] instead. @@ -1432,7 +1442,7 @@ Creates and returns a `Decipher` object that uses the given `algorithm` and `password` (key). The `options` argument controls stream behavior and is optional except when a -cipher in CCM mode is used (e.g. `'aes-128-ccm'`). In that case, the +cipher in CCM or OCB mode is used (e.g. `'aes-128-ccm'`). In that case, the `authTagLength` option is required and specifies the length of the authentication tag in bytes, see [CCM mode][]. @@ -1452,6 +1462,9 @@ to create the `Decipher` object.