Skip to content

Commit b3f459e

Browse files
committed
crypto: add support for OCB mode for AEAD
PR-URL: #21447 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent b75bde3 commit b3f459e

File tree

3 files changed

+290
-53
lines changed

3 files changed

+290
-53
lines changed

doc/api/crypto.md

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,11 @@ added: v1.0.0
249249
- `plaintextLength` {number}
250250
* Returns: {Cipher} for method chaining.
251251

252-
When using an authenticated encryption mode (only `GCM` and `CCM` are currently
253-
supported), the `cipher.setAAD()` method sets the value used for the
252+
When using an authenticated encryption mode (`GCM`, `CCM` and `OCB` are
253+
currently supported), the `cipher.setAAD()` method sets the value used for the
254254
_additional authenticated data_ (AAD) input parameter.
255255

256-
The `options` argument is optional for `GCM`. When using `CCM`, the
256+
The `options` argument is optional for `GCM` and `OCB`. When using `CCM`, the
257257
`plaintextLength` option must be specified and its value must match the length
258258
of the plaintext in bytes. See [CCM mode][].
259259

@@ -263,8 +263,8 @@ The `cipher.setAAD()` method must be called before [`cipher.update()`][].
263263
<!-- YAML
264264
added: v1.0.0
265265
-->
266-
* Returns: {Buffer} When using an authenticated encryption mode (only `GCM` and
267-
`CCM` are currently supported), the `cipher.getAuthTag()` method returns a
266+
* Returns: {Buffer} When using an authenticated encryption mode (`GCM`, `CCM`
267+
and `OCB` are currently supported), the `cipher.getAuthTag()` method returns a
268268
[`Buffer`][] containing the _authentication tag_ that has been computed from
269269
the given data.
270270

@@ -412,8 +412,8 @@ changes:
412412
- `plaintextLength` {number}
413413
* Returns: {Decipher} for method chaining.
414414

415-
When using an authenticated encryption mode (only `GCM` and `CCM` are currently
416-
supported), the `decipher.setAAD()` method sets the value used for the
415+
When using an authenticated encryption mode (`GCM`, `CCM` and `OCB` are
416+
currently supported), the `decipher.setAAD()` method sets the value used for the
417417
_additional authenticated data_ (AAD) input parameter.
418418

419419
The `options` argument is optional for `GCM`. When using `CCM`, the
@@ -436,8 +436,8 @@ changes:
436436
* `buffer` {Buffer | TypedArray | DataView}
437437
* Returns: {Decipher} for method chaining.
438438

439-
When using an authenticated encryption mode (only `GCM` and `CCM` are currently
440-
supported), the `decipher.setAuthTag()` method is used to pass in the
439+
When using an authenticated encryption mode (`GCM`, `CCM` and `OCB` are
440+
currently supported), the `decipher.setAuthTag()` method is used to pass in the
441441
received _authentication tag_. If no tag is provided, or if the cipher text
442442
has been tampered with, [`decipher.final()`][] will throw, indicating that the
443443
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
13211321
added: v0.1.94
13221322
deprecated: v10.0.0
13231323
changes:
1324+
- version: REPLACEME
1325+
pr-url: https://github.com/nodejs/node/pull/21447
1326+
description: Ciphers in OCB mode are now supported.
13241327
- version: v10.2.0
13251328
pr-url: https://github.com/nodejs/node/pull/20235
13261329
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
13381341
`password`.
13391342

13401343
The `options` argument controls stream behavior and is optional except when a
1341-
cipher in CCM mode is used (e.g. `'aes-128-ccm'`). In that case, the
1344+
cipher in CCM or OCB mode is used (e.g. `'aes-128-ccm'`). In that case, the
13421345
`authTagLength` option is required and specifies the length of the
13431346
authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength`
13441347
option is not required but can be used to set the length of the authentication
@@ -1373,6 +1376,9 @@ Adversaries][] for details.
13731376
<!-- YAML
13741377
added: v0.1.94
13751378
changes:
1379+
- version: REPLACEME
1380+
pr-url: https://github.com/nodejs/node/pull/21447
1381+
description: Ciphers in OCB mode are now supported.
13761382
- version: v10.2.0
13771383
pr-url: https://github.com/nodejs/node/pull/20235
13781384
description: The `authTagLength` option can now be used to produce shorter
@@ -1392,7 +1398,7 @@ Creates and returns a `Cipher` object, with the given `algorithm`, `key` and
13921398
initialization vector (`iv`).
13931399

13941400
The `options` argument controls stream behavior and is optional except when a
1395-
cipher in CCM mode is used (e.g. `'aes-128-ccm'`). In that case, the
1401+
cipher in CCM or OCB mode is used (e.g. `'aes-128-ccm'`). In that case, the
13961402
`authTagLength` option is required and specifies the length of the
13971403
authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength`
13981404
option is not required but can be used to set the length of the authentication
@@ -1419,6 +1425,10 @@ of time what a given IV will be.
14191425
<!-- YAML
14201426
added: v0.1.94
14211427
deprecated: v10.0.0
1428+
changes:
1429+
- version: REPLACEME
1430+
pr-url: https://github.com/nodejs/node/pull/21447
1431+
description: Ciphers in OCB mode are now supported.
14221432
-->
14231433

14241434
> Stability: 0 - Deprecated: Use [`crypto.createDecipheriv()`][] instead.
@@ -1432,7 +1442,7 @@ Creates and returns a `Decipher` object that uses the given `algorithm` and
14321442
`password` (key).
14331443

14341444
The `options` argument controls stream behavior and is optional except when a
1435-
cipher in CCM mode is used (e.g. `'aes-128-ccm'`). In that case, the
1445+
cipher in CCM or OCB mode is used (e.g. `'aes-128-ccm'`). In that case, the
14361446
`authTagLength` option is required and specifies the length of the
14371447
authentication tag in bytes, see [CCM mode][].
14381448

@@ -1452,6 +1462,9 @@ to create the `Decipher` object.
14521462
<!-- YAML
14531463
added: v0.1.94
14541464
changes:
1465+
- version: REPLACEME
1466+
pr-url: https://github.com/nodejs/node/pull/21447
1467+
description: Ciphers in OCB mode are now supported.
14551468
- version: v10.2.0
14561469
pr-url: https://github.com/nodejs/node/pull/20039
14571470
description: The `authTagLength` option can now be used to restrict accepted
@@ -1471,7 +1484,7 @@ Creates and returns a `Decipher` object that uses the given `algorithm`, `key`
14711484
and initialization vector (`iv`).
14721485

14731486
The `options` argument controls stream behavior and is optional except when a
1474-
cipher in CCM mode is used (e.g. `'aes-128-ccm'`). In that case, the
1487+
cipher in CCM or OCB mode is used (e.g. `'aes-128-ccm'`). In that case, the
14751488
`authTagLength` option is required and specifies the length of the
14761489
authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength`
14771490
option is not required but can be used to restrict accepted authentication tags
@@ -2321,7 +2334,7 @@ See the reference for other recommendations and details.
23212334

23222335
### CCM mode
23232336

2324-
CCM is one of the two supported [AEAD algorithms][]. Applications which use this
2337+
CCM is one of the supported [AEAD algorithms][]. Applications which use this
23252338
mode must adhere to certain restrictions when using the cipher API:
23262339

23272340
- The authentication tag length must be specified during cipher creation by

src/node_crypto.cc

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2683,6 +2683,11 @@ void CipherBase::Init(const FunctionCallbackInfo<Value>& args) {
26832683
cipher->Init(*cipher_type, key_buf, key_buf_len, auth_tag_len);
26842684
}
26852685

2686+
static bool IsSupportedAuthenticatedMode(int mode) {
2687+
return mode == EVP_CIPH_CCM_MODE ||
2688+
mode == EVP_CIPH_GCM_MODE ||
2689+
mode == EVP_CIPH_OCB_MODE;
2690+
}
26862691

26872692
void CipherBase::InitIv(const char* cipher_type,
26882693
const char* key,
@@ -2700,8 +2705,7 @@ void CipherBase::InitIv(const char* cipher_type,
27002705

27012706
const int expected_iv_len = EVP_CIPHER_iv_length(cipher);
27022707
const int mode = EVP_CIPHER_mode(cipher);
2703-
const bool is_gcm_mode = (EVP_CIPH_GCM_MODE == mode);
2704-
const bool is_ccm_mode = (EVP_CIPH_CCM_MODE == mode);
2708+
const bool is_authenticated_mode = IsSupportedAuthenticatedMode(mode);
27052709
const bool has_iv = iv_len >= 0;
27062710

27072711
// Throw if no IV was passed and the cipher requires an IV
@@ -2712,7 +2716,7 @@ void CipherBase::InitIv(const char* cipher_type,
27122716
}
27132717

27142718
// Throw if an IV was passed which does not match the cipher's fixed IV length
2715-
if (!is_gcm_mode && !is_ccm_mode && has_iv && iv_len != expected_iv_len) {
2719+
if (!is_authenticated_mode && has_iv && iv_len != expected_iv_len) {
27162720
return env()->ThrowError("Invalid IV length");
27172721
}
27182722

@@ -2728,7 +2732,7 @@ void CipherBase::InitIv(const char* cipher_type,
27282732
"Failed to initialize cipher");
27292733
}
27302734

2731-
if (IsAuthenticatedMode()) {
2735+
if (is_authenticated_mode) {
27322736
CHECK(has_iv);
27332737
if (!InitAuthenticated(cipher_type, iv_len, auth_tag_len))
27342738
return;
@@ -2803,7 +2807,7 @@ bool CipherBase::InitAuthenticated(const char* cipher_type, int iv_len,
28032807
}
28042808

28052809
const int mode = EVP_CIPHER_CTX_mode(ctx_.get());
2806-
if (mode == EVP_CIPH_CCM_MODE) {
2810+
if (mode == EVP_CIPH_CCM_MODE || mode == EVP_CIPH_OCB_MODE) {
28072811
if (auth_tag_len == kNoAuthTagLength) {
28082812
char msg[128];
28092813
snprintf(msg, sizeof(msg), "authTagLength required for %s", cipher_type);
@@ -2813,25 +2817,29 @@ bool CipherBase::InitAuthenticated(const char* cipher_type, int iv_len,
28132817

28142818
#ifdef NODE_FIPS_MODE
28152819
// TODO(tniessen) Support CCM decryption in FIPS mode
2816-
if (kind_ == kDecipher && FIPS_mode()) {
2820+
if (mode == EVP_CIPH_CCM_MODE && kind_ == kDecipher && FIPS_mode()) {
28172821
env()->ThrowError("CCM decryption not supported in FIPS mode");
28182822
return false;
28192823
}
28202824
#endif
28212825

2822-
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_CCM_SET_TAG, auth_tag_len,
2826+
// Tell OpenSSL about the desired length.
2827+
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_SET_TAG, auth_tag_len,
28232828
nullptr)) {
28242829
env()->ThrowError("Invalid authentication tag length");
28252830
return false;
28262831
}
28272832

2833+
// Remember the given authentication tag length for later.
28282834
auth_tag_len_ = auth_tag_len;
28292835

2830-
// Restrict the message length to min(INT_MAX, 2^(8*(15-iv_len))-1) bytes.
2831-
CHECK(iv_len >= 7 && iv_len <= 13);
2832-
max_message_size_ = INT_MAX;
2833-
if (iv_len == 12) max_message_size_ = 16777215;
2834-
if (iv_len == 13) max_message_size_ = 65535;
2836+
if (mode == EVP_CIPH_CCM_MODE) {
2837+
// Restrict the message length to min(INT_MAX, 2^(8*(15-iv_len))-1) bytes.
2838+
CHECK(iv_len >= 7 && iv_len <= 13);
2839+
max_message_size_ = INT_MAX;
2840+
if (iv_len == 12) max_message_size_ = 16777215;
2841+
if (iv_len == 13) max_message_size_ = 65535;
2842+
}
28352843
} else {
28362844
CHECK_EQ(mode, EVP_CIPH_GCM_MODE);
28372845

@@ -2870,7 +2878,7 @@ bool CipherBase::IsAuthenticatedMode() const {
28702878
// Check if this cipher operates in an AEAD mode that we support.
28712879
CHECK(ctx_);
28722880
const int mode = EVP_CIPHER_CTX_mode(ctx_.get());
2873-
return mode == EVP_CIPH_GCM_MODE || mode == EVP_CIPH_CCM_MODE;
2881+
return IsSupportedAuthenticatedMode(mode);
28742882
}
28752883

28762884

@@ -2903,16 +2911,18 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
29032911
return args.GetReturnValue().Set(false);
29042912
}
29052913

2906-
// Restrict GCM tag lengths according to NIST 800-38d, page 9.
29072914
unsigned int tag_len = Buffer::Length(args[0]);
29082915
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_.get());
29092916
bool is_valid;
29102917
if (mode == EVP_CIPH_GCM_MODE) {
2918+
// Restrict GCM tag lengths according to NIST 800-38d, page 9.
29112919
is_valid = (cipher->auth_tag_len_ == kNoAuthTagLength ||
29122920
cipher->auth_tag_len_ == tag_len) &&
29132921
IsValidGCMTagLength(tag_len);
29142922
} else {
2915-
CHECK_EQ(mode, EVP_CIPH_CCM_MODE);
2923+
// At this point, the tag length is already known and must match the
2924+
// length of the given authentication tag.
2925+
CHECK(mode == EVP_CIPH_CCM_MODE || mode == EVP_CIPH_OCB_MODE);
29162926
CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength);
29172927
is_valid = cipher->auth_tag_len_ == tag_len;
29182928
}
@@ -3008,7 +3018,7 @@ CipherBase::UpdateResult CipherBase::Update(const char* data,
30083018
if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_len_ > 0 &&
30093019
auth_tag_len_ != kNoAuthTagLength && !auth_tag_set_) {
30103020
CHECK(EVP_CIPHER_CTX_ctrl(ctx_.get(),
3011-
EVP_CTRL_GCM_SET_TAG,
3021+
EVP_CTRL_AEAD_SET_TAG,
30123022
auth_tag_len_,
30133023
reinterpret_cast<unsigned char*>(auth_tag_)));
30143024
auth_tag_set_ = true;
@@ -3121,10 +3131,12 @@ bool CipherBase::Final(unsigned char** out, int* out_len) {
31213131

31223132
if (ok && kind_ == kCipher && IsAuthenticatedMode()) {
31233133
// In GCM mode, the authentication tag length can be specified in advance,
3124-
// but defaults to 16 bytes when encrypting. In CCM mode, it must always
3125-
// be given by the user.
3126-
if (mode == EVP_CIPH_GCM_MODE && auth_tag_len_ == kNoAuthTagLength)
3134+
// but defaults to 16 bytes when encrypting. In CCM and OCB mode, it must
3135+
// always be given by the user.
3136+
if (auth_tag_len_ == kNoAuthTagLength) {
3137+
CHECK(mode == EVP_CIPH_GCM_MODE);
31273138
auth_tag_len_ = sizeof(auth_tag_);
3139+
}
31283140
CHECK_EQ(1, EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_GET_TAG,
31293141
auth_tag_len_,
31303142
reinterpret_cast<unsigned char*>(auth_tag_)));

0 commit comments

Comments
 (0)