Skip to content

Commit

Permalink
crypto: add support for OCB mode for AEAD
Browse files Browse the repository at this point in the history
Backport-PR-URL: #22473
PR-URL: #21447
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
tniessen authored and targos committed Sep 3, 2018
1 parent 1d38399 commit 59a6c60
Show file tree
Hide file tree
Showing 3 changed files with 301 additions and 56 deletions.
41 changes: 27 additions & 14 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -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][].

Expand All @@ -263,8 +263,8 @@ The `cipher.setAAD()` method must be called before [`cipher.update()`][].
<!-- YAML
added: v1.0.0
-->
* 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.

Expand Down Expand Up @@ -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
Expand All @@ -433,8 +433,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.
Expand Down Expand Up @@ -1324,6 +1324,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/21447
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
Expand All @@ -1341,7 +1344,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
Expand Down Expand Up @@ -1376,6 +1379,9 @@ Adversaries][] for details.
<!-- YAML
added: v0.1.94
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/21447
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
Expand All @@ -1395,7 +1401,7 @@ Creates and returns a `Cipher` object, with the given `algorithm`, `key` and
initialization vector (`iv`).

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
Expand Down Expand Up @@ -1441,6 +1447,10 @@ called.
<!-- YAML
added: v0.1.94
deprecated: v10.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/21447
description: Ciphers in OCB mode are now supported.
-->

> Stability: 0 - Deprecated: Use [`crypto.createDecipheriv()`][] instead.
Expand All @@ -1454,7 +1464,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][].

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

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 restrict accepted authentication tags
Expand Down Expand Up @@ -2367,7 +2380,7 @@ See the reference for other recommendations and details.

### CCM mode

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

- The authentication tag length must be specified during cipher creation by
Expand Down
60 changes: 39 additions & 21 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2661,6 +2661,11 @@ void CipherBase::Init(const FunctionCallbackInfo<Value>& args) {
cipher->Init(*cipher_type, key_buf, key_buf_len, auth_tag_len);
}

static bool IsSupportedAuthenticatedMode(int mode) {
return 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,
Expand All @@ -2678,8 +2683,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 = IsSupportedAuthenticatedMode(mode);
const bool has_iv = iv_len >= 0;

// Throw if no IV was passed and the cipher requires an IV
Expand All @@ -2690,7 +2694,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");
}

Expand All @@ -2706,7 +2710,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;
Expand Down Expand Up @@ -2781,7 +2785,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);
Expand All @@ -2791,27 +2795,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;
}

// When decrypting in CCM mode, this field will be set in setAuthTag().
if (kind_ == kCipher)
auth_tag_len_ = auth_tag_len;
// 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);

Expand Down Expand Up @@ -2850,7 +2856,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 IsSupportedAuthenticatedMode(mode);
}


Expand Down Expand Up @@ -2883,7 +2889,6 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& 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());
if (mode == EVP_CIPH_GCM_MODE) {
Expand All @@ -2901,6 +2906,17 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
"Valid GCM tag lengths are 4, 8, 12, 13, 14, 15, 16.", tag_len);
ProcessEmitDeprecationWarning(cipher->env(), msg, "DEP0090");
}
} else if (mode == EVP_CIPH_OCB_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);
if (cipher->auth_tag_len_ != tag_len) {
char msg[50];
snprintf(msg, sizeof(msg),
"Invalid authentication tag length: %u", tag_len);
return cipher->env()->ThrowError(msg);
}
}

// Note: we don't use std::min() here to work around a header conflict.
Expand Down Expand Up @@ -2991,7 +3007,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<unsigned char*>(auth_tag_)));
auth_tag_set_ = true;
Expand Down Expand Up @@ -3104,10 +3120,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<unsigned char*>(auth_tag_)));
Expand Down
Loading

0 comments on commit 59a6c60

Please sign in to comment.