Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(v10.x backport) crypto: add support for OCB mode for AEAD #22473

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -2659,6 +2659,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 @@ -2676,8 +2681,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 @@ -2688,7 +2692,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 @@ -2704,7 +2708,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 @@ -2779,7 +2783,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 @@ -2789,27 +2793,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 @@ -2848,7 +2854,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 @@ -2881,7 +2887,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 @@ -2899,6 +2904,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 @@ -2989,7 +3005,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 @@ -3102,10 +3118,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