Skip to content

Commit 59a6c60

Browse files
tniessentargos
authored andcommitted
crypto: add support for OCB mode for AEAD
Backport-PR-URL: #22473 PR-URL: #21447 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 1d38399 commit 59a6c60

File tree

3 files changed

+301
-56
lines changed

3 files changed

+301
-56
lines changed

doc/api/crypto.md

+27-14
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
@@ -433,8 +433,8 @@ changes:
433433
* `buffer` {Buffer | TypedArray | DataView}
434434
* Returns: {Decipher} for method chaining.
435435

436-
When using an authenticated encryption mode (only `GCM` and `CCM` are currently
437-
supported), the `decipher.setAuthTag()` method is used to pass in the
436+
When using an authenticated encryption mode (`GCM`, `CCM` and `OCB` are
437+
currently supported), the `decipher.setAuthTag()` method is used to pass in the
438438
received _authentication tag_. If no tag is provided, or if the cipher text
439439
has been tampered with, [`decipher.final()`][] will throw, indicating that the
440440
cipher text should be discarded due to failed authentication.
@@ -1324,6 +1324,9 @@ This property is deprecated. Please use `crypto.setFips()` and
13241324
added: v0.1.94
13251325
deprecated: v10.0.0
13261326
changes:
1327+
- version: REPLACEME
1328+
pr-url: https://github.com/nodejs/node/pull/21447
1329+
description: Ciphers in OCB mode are now supported.
13271330
- version: v10.2.0
13281331
pr-url: https://github.com/nodejs/node/pull/20235
13291332
description: The `authTagLength` option can now be used to produce shorter
@@ -1341,7 +1344,7 @@ Creates and returns a `Cipher` object that uses the given `algorithm` and
13411344
`password`.
13421345

13431346
The `options` argument controls stream behavior and is optional except when a
1344-
cipher in CCM mode is used (e.g. `'aes-128-ccm'`). In that case, the
1347+
cipher in CCM or OCB mode is used (e.g. `'aes-128-ccm'`). In that case, the
13451348
`authTagLength` option is required and specifies the length of the
13461349
authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength`
13471350
option is not required but can be used to set the length of the authentication
@@ -1376,6 +1379,9 @@ Adversaries][] for details.
13761379
<!-- YAML
13771380
added: v0.1.94
13781381
changes:
1382+
- version: REPLACEME
1383+
pr-url: https://github.com/nodejs/node/pull/21447
1384+
description: Ciphers in OCB mode are now supported.
13791385
- version: v10.2.0
13801386
pr-url: https://github.com/nodejs/node/pull/20235
13811387
description: The `authTagLength` option can now be used to produce shorter
@@ -1395,7 +1401,7 @@ Creates and returns a `Cipher` object, with the given `algorithm`, `key` and
13951401
initialization vector (`iv`).
13961402

13971403
The `options` argument controls stream behavior and is optional except when a
1398-
cipher in CCM mode is used (e.g. `'aes-128-ccm'`). In that case, the
1404+
cipher in CCM or OCB mode is used (e.g. `'aes-128-ccm'`). In that case, the
13991405
`authTagLength` option is required and specifies the length of the
14001406
authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength`
14011407
option is not required but can be used to set the length of the authentication
@@ -1441,6 +1447,10 @@ called.
14411447
<!-- YAML
14421448
added: v0.1.94
14431449
deprecated: v10.0.0
1450+
changes:
1451+
- version: REPLACEME
1452+
pr-url: https://github.com/nodejs/node/pull/21447
1453+
description: Ciphers in OCB mode are now supported.
14441454
-->
14451455

14461456
> Stability: 0 - Deprecated: Use [`crypto.createDecipheriv()`][] instead.
@@ -1454,7 +1464,7 @@ Creates and returns a `Decipher` object that uses the given `algorithm` and
14541464
`password` (key).
14551465

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

@@ -1474,6 +1484,9 @@ to create the `Decipher` object.
14741484
<!-- YAML
14751485
added: v0.1.94
14761486
changes:
1487+
- version: REPLACEME
1488+
pr-url: https://github.com/nodejs/node/pull/21447
1489+
description: Ciphers in OCB mode are now supported.
14771490
- version: v10.2.0
14781491
pr-url: https://github.com/nodejs/node/pull/20039
14791492
description: The `authTagLength` option can now be used to restrict accepted
@@ -1493,7 +1506,7 @@ Creates and returns a `Decipher` object that uses the given `algorithm`, `key`
14931506
and initialization vector (`iv`).
14941507

14951508
The `options` argument controls stream behavior and is optional except when a
1496-
cipher in CCM mode is used (e.g. `'aes-128-ccm'`). In that case, the
1509+
cipher in CCM or OCB mode is used (e.g. `'aes-128-ccm'`). In that case, the
14971510
`authTagLength` option is required and specifies the length of the
14981511
authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength`
14991512
option is not required but can be used to restrict accepted authentication tags
@@ -2367,7 +2380,7 @@ See the reference for other recommendations and details.
23672380

23682381
### CCM mode
23692382

2370-
CCM is one of the two supported [AEAD algorithms][]. Applications which use this
2383+
CCM is one of the supported [AEAD algorithms][]. Applications which use this
23712384
mode must adhere to certain restrictions when using the cipher API:
23722385

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

src/node_crypto.cc

+39-21
Original file line numberDiff line numberDiff line change
@@ -2661,6 +2661,11 @@ void CipherBase::Init(const FunctionCallbackInfo<Value>& args) {
26612661
cipher->Init(*cipher_type, key_buf, key_buf_len, auth_tag_len);
26622662
}
26632663

2664+
static bool IsSupportedAuthenticatedMode(int mode) {
2665+
return mode == EVP_CIPH_CCM_MODE ||
2666+
mode == EVP_CIPH_GCM_MODE ||
2667+
mode == EVP_CIPH_OCB_MODE;
2668+
}
26642669

26652670
void CipherBase::InitIv(const char* cipher_type,
26662671
const char* key,
@@ -2678,8 +2683,7 @@ void CipherBase::InitIv(const char* cipher_type,
26782683

26792684
const int expected_iv_len = EVP_CIPHER_iv_length(cipher);
26802685
const int mode = EVP_CIPHER_mode(cipher);
2681-
const bool is_gcm_mode = (EVP_CIPH_GCM_MODE == mode);
2682-
const bool is_ccm_mode = (EVP_CIPH_CCM_MODE == mode);
2686+
const bool is_authenticated_mode = IsSupportedAuthenticatedMode(mode);
26832687
const bool has_iv = iv_len >= 0;
26842688

26852689
// Throw if no IV was passed and the cipher requires an IV
@@ -2690,7 +2694,7 @@ void CipherBase::InitIv(const char* cipher_type,
26902694
}
26912695

26922696
// Throw if an IV was passed which does not match the cipher's fixed IV length
2693-
if (!is_gcm_mode && !is_ccm_mode && has_iv && iv_len != expected_iv_len) {
2697+
if (!is_authenticated_mode && has_iv && iv_len != expected_iv_len) {
26942698
return env()->ThrowError("Invalid IV length");
26952699
}
26962700

@@ -2706,7 +2710,7 @@ void CipherBase::InitIv(const char* cipher_type,
27062710
"Failed to initialize cipher");
27072711
}
27082712

2709-
if (IsAuthenticatedMode()) {
2713+
if (is_authenticated_mode) {
27102714
CHECK(has_iv);
27112715
if (!InitAuthenticated(cipher_type, iv_len, auth_tag_len))
27122716
return;
@@ -2781,7 +2785,7 @@ bool CipherBase::InitAuthenticated(const char* cipher_type, int iv_len,
27812785
}
27822786

27832787
const int mode = EVP_CIPHER_CTX_mode(ctx_.get());
2784-
if (mode == EVP_CIPH_CCM_MODE) {
2788+
if (mode == EVP_CIPH_CCM_MODE || mode == EVP_CIPH_OCB_MODE) {
27852789
if (auth_tag_len == kNoAuthTagLength) {
27862790
char msg[128];
27872791
snprintf(msg, sizeof(msg), "authTagLength required for %s", cipher_type);
@@ -2791,27 +2795,29 @@ bool CipherBase::InitAuthenticated(const char* cipher_type, int iv_len,
27912795

27922796
#ifdef NODE_FIPS_MODE
27932797
// TODO(tniessen) Support CCM decryption in FIPS mode
2794-
if (kind_ == kDecipher && FIPS_mode()) {
2798+
if (mode == EVP_CIPH_CCM_MODE && kind_ == kDecipher && FIPS_mode()) {
27952799
env()->ThrowError("CCM decryption not supported in FIPS mode");
27962800
return false;
27972801
}
27982802
#endif
27992803

2800-
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_CCM_SET_TAG, auth_tag_len,
2804+
// Tell OpenSSL about the desired length.
2805+
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_SET_TAG, auth_tag_len,
28012806
nullptr)) {
28022807
env()->ThrowError("Invalid authentication tag length");
28032808
return false;
28042809
}
28052810

2806-
// When decrypting in CCM mode, this field will be set in setAuthTag().
2807-
if (kind_ == kCipher)
2808-
auth_tag_len_ = auth_tag_len;
2811+
// Remember the given authentication tag length for later.
2812+
auth_tag_len_ = auth_tag_len;
28092813

2810-
// Restrict the message length to min(INT_MAX, 2^(8*(15-iv_len))-1) bytes.
2811-
CHECK(iv_len >= 7 && iv_len <= 13);
2812-
max_message_size_ = INT_MAX;
2813-
if (iv_len == 12) max_message_size_ = 16777215;
2814-
if (iv_len == 13) max_message_size_ = 65535;
2814+
if (mode == EVP_CIPH_CCM_MODE) {
2815+
// Restrict the message length to min(INT_MAX, 2^(8*(15-iv_len))-1) bytes.
2816+
CHECK(iv_len >= 7 && iv_len <= 13);
2817+
max_message_size_ = INT_MAX;
2818+
if (iv_len == 12) max_message_size_ = 16777215;
2819+
if (iv_len == 13) max_message_size_ = 65535;
2820+
}
28152821
} else {
28162822
CHECK_EQ(mode, EVP_CIPH_GCM_MODE);
28172823

@@ -2850,7 +2856,7 @@ bool CipherBase::IsAuthenticatedMode() const {
28502856
// Check if this cipher operates in an AEAD mode that we support.
28512857
CHECK(ctx_);
28522858
const int mode = EVP_CIPHER_CTX_mode(ctx_.get());
2853-
return mode == EVP_CIPH_GCM_MODE || mode == EVP_CIPH_CCM_MODE;
2859+
return IsSupportedAuthenticatedMode(mode);
28542860
}
28552861

28562862

@@ -2883,7 +2889,6 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
28832889
return args.GetReturnValue().Set(false);
28842890
}
28852891

2886-
// Restrict GCM tag lengths according to NIST 800-38d, page 9.
28872892
unsigned int tag_len = Buffer::Length(args[0]);
28882893
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_.get());
28892894
if (mode == EVP_CIPH_GCM_MODE) {
@@ -2901,6 +2906,17 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
29012906
"Valid GCM tag lengths are 4, 8, 12, 13, 14, 15, 16.", tag_len);
29022907
ProcessEmitDeprecationWarning(cipher->env(), msg, "DEP0090");
29032908
}
2909+
} else if (mode == EVP_CIPH_OCB_MODE) {
2910+
// At this point, the tag length is already known and must match the
2911+
// length of the given authentication tag.
2912+
CHECK(mode == EVP_CIPH_CCM_MODE || mode == EVP_CIPH_OCB_MODE);
2913+
CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength);
2914+
if (cipher->auth_tag_len_ != tag_len) {
2915+
char msg[50];
2916+
snprintf(msg, sizeof(msg),
2917+
"Invalid authentication tag length: %u", tag_len);
2918+
return cipher->env()->ThrowError(msg);
2919+
}
29042920
}
29052921

29062922
// Note: we don't use std::min() here to work around a header conflict.
@@ -2991,7 +3007,7 @@ CipherBase::UpdateResult CipherBase::Update(const char* data,
29913007
if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_len_ > 0 &&
29923008
auth_tag_len_ != kNoAuthTagLength && !auth_tag_set_) {
29933009
CHECK(EVP_CIPHER_CTX_ctrl(ctx_.get(),
2994-
EVP_CTRL_GCM_SET_TAG,
3010+
EVP_CTRL_AEAD_SET_TAG,
29953011
auth_tag_len_,
29963012
reinterpret_cast<unsigned char*>(auth_tag_)));
29973013
auth_tag_set_ = true;
@@ -3104,10 +3120,12 @@ bool CipherBase::Final(unsigned char** out, int* out_len) {
31043120

31053121
if (ok && kind_ == kCipher && IsAuthenticatedMode()) {
31063122
// In GCM mode, the authentication tag length can be specified in advance,
3107-
// but defaults to 16 bytes when encrypting. In CCM mode, it must always
3108-
// be given by the user.
3109-
if (mode == EVP_CIPH_GCM_MODE && auth_tag_len_ == kNoAuthTagLength)
3123+
// but defaults to 16 bytes when encrypting. In CCM and OCB mode, it must
3124+
// always be given by the user.
3125+
if (auth_tag_len_ == kNoAuthTagLength) {
3126+
CHECK(mode == EVP_CIPH_GCM_MODE);
31103127
auth_tag_len_ = sizeof(auth_tag_);
3128+
}
31113129
CHECK_EQ(1, EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_GET_TAG,
31123130
auth_tag_len_,
31133131
reinterpret_cast<unsigned char*>(auth_tag_)));

0 commit comments

Comments
 (0)