Skip to content

Commit 2cebe48

Browse files
committed
crypto: discover ciphers from OpenSSL providers
Enumerate usable ciphers and aliases from activated OpenSSL 3 providers instead of maintaining lists of provider-only algorithms. Normalize names, skip numeric OID aliases, and filter NULL, TLS composite, multiblock, and encrypt-then-MAC implementations that cannot be used by the Cipher APIs. Resolve predefined ciphers lazily and retain OpenSSL 1.1 and BoringSSL fallbacks. This exposes CBC-CTS, SM4-GCM, SM4-CCM, SM4-XTS, and additional AES key wrap implementations. Add `ctsMode` (CS1/CS2/CS3) and `xtsStandard` (GB/IEEE) options to select the provider CTS variant and SM4-XTS standard. Enforce one-shot updates for CBC-CTS, AES key wrap, SIV/GCM-SIV, and CCM decryption. Reject finalization without required input or CCM tags, and defer authentication failures to final(). Document the corresponding streaming and XTS data-unit constraints. Add known-answer vectors, option validation, provider alias coverage, and state-transition tests for the newly exposed algorithms. Fixes: #43040 Fixes: #64866 Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent f4bcf75 commit 2cebe48

17 files changed

Lines changed: 1056 additions & 210 deletions

‎deps/ncrypto/ncrypto.cc‎

Lines changed: 217 additions & 77 deletions
Large diffs are not rendered by default.

‎deps/ncrypto/ncrypto.h‎

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ class Cipher final {
452452
Cipher(const Cipher& other);
453453
Cipher& operator=(const Cipher& other);
454454
inline Cipher& operator=(const EVP_CIPHER* cipher) {
455-
#if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV
455+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
456456
fetched_cipher_.reset();
457457
#endif
458458
cipher_ = cipher;
@@ -476,6 +476,7 @@ class Cipher final {
476476
bool isWrapMode() const;
477477
bool isCtrMode() const;
478478
bool isCcmMode() const;
479+
bool isCtsMode() const;
479480
bool isOcbMode() const;
480481
bool isSivMode() const;
481482
bool isGcmSivMode() const;
@@ -499,28 +500,24 @@ class Cipher final {
499500
// is able to do so.
500501
static void ForEach(CipherNameCallback callback);
501502

502-
// Utilities to get various ciphers by type. If the underlying
503-
// implementation does not support the requested cipher, then
504-
// the result will be an empty Cipher object whose bool operator
505-
// will return false.
506-
507-
static const Cipher EMPTY;
508-
static const Cipher AES_128_CBC;
509-
static const Cipher AES_192_CBC;
510-
static const Cipher AES_256_CBC;
511-
static const Cipher AES_128_CTR;
512-
static const Cipher AES_192_CTR;
513-
static const Cipher AES_256_CTR;
514-
static const Cipher AES_128_GCM;
515-
static const Cipher AES_192_GCM;
516-
static const Cipher AES_256_GCM;
517-
static const Cipher AES_128_KW;
518-
static const Cipher AES_192_KW;
519-
static const Cipher AES_256_KW;
520-
static const Cipher AES_128_OCB;
521-
static const Cipher AES_192_OCB;
522-
static const Cipher AES_256_OCB;
523-
static const Cipher CHACHA20_POLY1305;
503+
// Lazily resolves common ciphers. If the underlying implementation does not
504+
// support the requested cipher, the returned Cipher will be empty.
505+
static const Cipher& AES_128_CBC();
506+
static const Cipher& AES_192_CBC();
507+
static const Cipher& AES_256_CBC();
508+
static const Cipher& AES_128_CTR();
509+
static const Cipher& AES_192_CTR();
510+
static const Cipher& AES_256_CTR();
511+
static const Cipher& AES_128_GCM();
512+
static const Cipher& AES_192_GCM();
513+
static const Cipher& AES_256_GCM();
514+
static const Cipher& AES_128_KW();
515+
static const Cipher& AES_192_KW();
516+
static const Cipher& AES_256_KW();
517+
static const Cipher& AES_128_OCB();
518+
static const Cipher& AES_192_OCB();
519+
static const Cipher& AES_256_OCB();
520+
static const Cipher& CHACHA20_POLY1305();
524521

525522
struct CipherParams {
526523
int padding;
@@ -550,7 +547,7 @@ class Cipher final {
550547

551548
private:
552549
const EVP_CIPHER* cipher_ = nullptr;
553-
#if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV
550+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
554551
explicit Cipher(DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free> cipher);
555552
DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free> fetched_cipher_;
556553
#endif
@@ -940,7 +937,9 @@ class CipherCtxPointer final {
940937
bool setIvLength(size_t length);
941938
bool setAeadTag(const Buffer<const char>& tag);
942939
bool setAeadTagLength(size_t length);
940+
bool setCtsMode(const char* mode);
943941
bool setPadding(bool padding);
942+
bool setXtsStandard(const char* standard);
944943
bool init(const Cipher& cipher,
945944
bool encrypt,
946945
const unsigned char* key = nullptr,
@@ -953,6 +952,7 @@ class CipherCtxPointer final {
953952
bool isGcmMode() const;
954953
bool isOcbMode() const;
955954
bool isCcmMode() const;
955+
bool isCtsMode() const;
956956
bool isWrapMode() const;
957957
bool isSivMode() const;
958958
bool isGcmSivMode() const;

‎doc/api/crypto.md‎

Lines changed: 216 additions & 61 deletions
Large diffs are not rendered by default.

‎lib/internal/crypto/cipher.js‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const {
3333

3434
const {
3535
validateEncoding,
36+
validateOneOf,
3637
validateUint32,
3738
validateObject,
3839
validateString,
@@ -60,6 +61,9 @@ const { normalizeEncoding, kEmptyObject } = require('internal/util');
6061

6162
const { StringDecoder } = require('string_decoder');
6263

64+
const kCtsModes = ['CS1', 'CS2', 'CS3'];
65+
const kXtsStandards = ['GB', 'IEEE'];
66+
6367
function rsaFunctionFor(method, defaultPadding, keyType) {
6468
const keyName = keyType === 'private' ? 'privateKey' : undefined;
6569
return (key, buffer) => {
@@ -120,7 +124,21 @@ function getUIntOption(options, key) {
120124

121125
function createCipherBase(cipher, credential, options, isEncrypt, iv) {
122126
const authTagLength = getUIntOption(options, 'authTagLength');
123-
this[kHandle] = new CipherBase(isEncrypt, cipher, credential, iv, authTagLength);
127+
const ctsMode = getStringOption(options, 'ctsMode') ?? undefined;
128+
const xtsStandard = getStringOption(options, 'xtsStandard') ?? undefined;
129+
if (ctsMode !== undefined)
130+
validateOneOf(ctsMode, 'options.ctsMode', kCtsModes);
131+
if (xtsStandard !== undefined)
132+
validateOneOf(xtsStandard, 'options.xtsStandard', kXtsStandards);
133+
134+
this[kHandle] = new CipherBase(
135+
isEncrypt,
136+
cipher,
137+
credential,
138+
iv,
139+
authTagLength,
140+
ctsMode,
141+
xtsStandard);
124142
this._decoder = null;
125143

126144
FunctionPrototypeCall(LazyTransform, this, options);

‎src/crypto/crypto_aes.h‎

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ namespace node::crypto {
1313
constexpr unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
1414

1515
#define VARIANTS_COMMON(V) \
16-
V(CTR_128, AES_CTR_Cipher, ncrypto::Cipher::AES_128_CTR) \
17-
V(CTR_192, AES_CTR_Cipher, ncrypto::Cipher::AES_192_CTR) \
18-
V(CTR_256, AES_CTR_Cipher, ncrypto::Cipher::AES_256_CTR) \
19-
V(CBC_128, AES_Cipher, ncrypto::Cipher::AES_128_CBC) \
20-
V(CBC_192, AES_Cipher, ncrypto::Cipher::AES_192_CBC) \
21-
V(CBC_256, AES_Cipher, ncrypto::Cipher::AES_256_CBC) \
22-
V(GCM_128, AES_Cipher, ncrypto::Cipher::AES_128_GCM) \
23-
V(GCM_192, AES_Cipher, ncrypto::Cipher::AES_192_GCM) \
24-
V(GCM_256, AES_Cipher, ncrypto::Cipher::AES_256_GCM) \
16+
V(CTR_128, AES_CTR_Cipher, ncrypto::Cipher::AES_128_CTR()) \
17+
V(CTR_192, AES_CTR_Cipher, ncrypto::Cipher::AES_192_CTR()) \
18+
V(CTR_256, AES_CTR_Cipher, ncrypto::Cipher::AES_256_CTR()) \
19+
V(CBC_128, AES_Cipher, ncrypto::Cipher::AES_128_CBC()) \
20+
V(CBC_192, AES_Cipher, ncrypto::Cipher::AES_192_CBC()) \
21+
V(CBC_256, AES_Cipher, ncrypto::Cipher::AES_256_CBC()) \
22+
V(GCM_128, AES_Cipher, ncrypto::Cipher::AES_128_GCM()) \
23+
V(GCM_192, AES_Cipher, ncrypto::Cipher::AES_192_GCM()) \
24+
V(GCM_256, AES_Cipher, ncrypto::Cipher::AES_256_GCM()) \
2525
VARIANTS_KW(V)
2626

2727
#ifdef OPENSSL_IS_BORINGSSL
@@ -33,16 +33,16 @@ constexpr unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
3333
V(KW_256, AES_KW_Cipher, static_cast<const EVP_CIPHER*>(nullptr))
3434
#else
3535
#define VARIANTS_KW(V) \
36-
V(KW_128, AES_Cipher, ncrypto::Cipher::AES_128_KW) \
37-
V(KW_192, AES_Cipher, ncrypto::Cipher::AES_192_KW) \
38-
V(KW_256, AES_Cipher, ncrypto::Cipher::AES_256_KW)
36+
V(KW_128, AES_Cipher, ncrypto::Cipher::AES_128_KW()) \
37+
V(KW_192, AES_Cipher, ncrypto::Cipher::AES_192_KW()) \
38+
V(KW_256, AES_Cipher, ncrypto::Cipher::AES_256_KW())
3939
#endif
4040

4141
#if OPENSSL_WITH_AES_OCB
4242
#define VARIANTS_OCB(V) \
43-
V(OCB_128, AES_Cipher, ncrypto::Cipher::AES_128_OCB) \
44-
V(OCB_192, AES_Cipher, ncrypto::Cipher::AES_192_OCB) \
45-
V(OCB_256, AES_Cipher, ncrypto::Cipher::AES_256_OCB)
43+
V(OCB_128, AES_Cipher, ncrypto::Cipher::AES_128_OCB()) \
44+
V(OCB_192, AES_Cipher, ncrypto::Cipher::AES_192_OCB()) \
45+
V(OCB_256, AES_Cipher, ncrypto::Cipher::AES_256_OCB())
4646
#else
4747
#define VARIANTS_OCB(V)
4848
#endif

‎src/crypto/crypto_chacha20_poly1305.cc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ Maybe<void> ChaCha20Poly1305CipherTraits::AdditionalConfig(
105105
ChaCha20Poly1305CipherConfig* params) {
106106
Environment* env = Environment::GetCurrent(args);
107107

108-
params->cipher = ncrypto::Cipher::CHACHA20_POLY1305;
108+
params->cipher = ncrypto::Cipher::CHACHA20_POLY1305();
109109

110110
#ifndef OPENSSL_IS_BORINGSSL
111111
// On BoringSSL, ChaCha20-Poly1305 is not exposed via the EVP_CIPHER registry

‎src/crypto/crypto_cipher.cc‎

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ CipherBase::CipherBase(Environment* env, Local<Object> wrap, CipherKind kind)
227227
auth_tag_state_(kAuthTagUnknown),
228228
auth_tag_len_(kNoAuthTagLength),
229229
pending_auth_failed_(false),
230-
has_siv_update_(false),
230+
has_one_shot_update_(false),
231231
siv_aad_components_(0) {
232232
MakeWeak();
233233
}
@@ -311,7 +311,7 @@ void CipherBase::RegisterExternalReferences(
311311
void CipherBase::New(const FunctionCallbackInfo<Value>& args) {
312312
CHECK(args.IsConstructCall());
313313
Environment* env = Environment::GetCurrent(args);
314-
CHECK_EQ(args.Length(), 5);
314+
CHECK_EQ(args.Length(), 7);
315315

316316
CipherBase* cipher =
317317
new CipherBase(env, args.This(), args[0]->IsTrue() ? kCipher : kDecipher);
@@ -343,7 +343,19 @@ void CipherBase::New(const FunctionCallbackInfo<Value>& args) {
343343
auth_tag_len = kNoAuthTagLength;
344344
}
345345

346-
cipher->InitIv(*cipher_type, key_buf, iv_buf, auth_tag_len);
346+
CHECK(args[5]->IsString() || args[5]->IsUndefined());
347+
CHECK(args[6]->IsString() || args[6]->IsUndefined());
348+
const Utf8Value cts_mode(env->isolate(),
349+
args[5]->IsString() ? args[5] : Local<Value>());
350+
const Utf8Value xts_standard(env->isolate(),
351+
args[6]->IsString() ? args[6] : Local<Value>());
352+
353+
cipher->InitIv(*cipher_type,
354+
key_buf,
355+
iv_buf,
356+
auth_tag_len,
357+
args[5]->IsString() ? *cts_mode : nullptr,
358+
args[6]->IsString() ? *xts_standard : nullptr);
347359
}
348360

349361
void CipherBase::CommonInit(const char* cipher_type,
@@ -352,7 +364,9 @@ void CipherBase::CommonInit(const char* cipher_type,
352364
int key_len,
353365
const unsigned char* iv,
354366
int iv_len,
355-
unsigned int auth_tag_len) {
367+
unsigned int auth_tag_len,
368+
const char* cts_mode,
369+
const char* xts_standard) {
356370
MarkPopErrorOnReturn mark_pop_error_on_return;
357371
CHECK(!ctx_);
358372
ctx_ = CipherCtxPointer::New();
@@ -372,6 +386,18 @@ void CipherBase::CommonInit(const char* cipher_type,
372386
"Failed to initialize cipher");
373387
}
374388

389+
if (cts_mode != nullptr && !ctx_.setCtsMode(cts_mode)) {
390+
ctx_.reset();
391+
return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(
392+
env(), "%s does not support the ctsMode option", cipher_type);
393+
}
394+
395+
if (xts_standard != nullptr && !ctx_.setXtsStandard(xts_standard)) {
396+
ctx_.reset();
397+
return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(
398+
env(), "%s does not support the xtsStandard option", cipher_type);
399+
}
400+
375401
if (cipher.isSupportedAuthenticatedMode()) {
376402
CHECK_GE(iv_len, 0);
377403
if (!InitAuthenticated(cipher_type, iv_len, auth_tag_len)) {
@@ -394,7 +420,9 @@ void CipherBase::CommonInit(const char* cipher_type,
394420
void CipherBase::InitIv(const char* cipher_type,
395421
const ByteSource& key_buf,
396422
const ArrayBufferOrViewContents<unsigned char>& iv_buf,
397-
unsigned int auth_tag_len) {
423+
unsigned int auth_tag_len,
424+
const char* cts_mode,
425+
const char* xts_standard) {
398426
HandleScope scope(env()->isolate());
399427
MarkPopErrorOnReturn mark_pop_error_on_return;
400428

@@ -436,14 +464,15 @@ void CipherBase::InitIv(const char* cipher_type,
436464
return THROW_ERR_CRYPTO_INVALID_IV(env());
437465
}
438466

439-
CommonInit(
440-
cipher_type,
441-
cipher,
442-
key_buf.data<unsigned char>(),
443-
key_buf.size(),
444-
iv_buf.data(),
445-
iv_buf.size(),
446-
auth_tag_len);
467+
CommonInit(cipher_type,
468+
cipher,
469+
key_buf.data<unsigned char>(),
470+
key_buf.size(),
471+
iv_buf.data(),
472+
iv_buf.size(),
473+
auth_tag_len,
474+
cts_mode,
475+
xts_standard);
447476
}
448477

449478
bool CipherBase::InitAuthenticated(const char* cipher_type,
@@ -563,7 +592,7 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
563592
}
564593

565594
if ((cipher->ctx_.isSivMode() || cipher->ctx_.isGcmSivMode()) &&
566-
cipher->has_siv_update_) {
595+
cipher->has_one_shot_update_) {
567596
return args.GetReturnValue().Set(false);
568597
}
569598

@@ -598,7 +627,7 @@ bool CipherBase::SetAAD(
598627
if (!ctx_ || !IsAuthenticatedMode())
599628
return false;
600629
const bool is_siv = ctx_.isSivMode();
601-
if ((is_siv || ctx_.isGcmSivMode()) && has_siv_update_) return false;
630+
if ((is_siv || ctx_.isGcmSivMode()) && has_one_shot_update_) return false;
602631
if (is_siv && siv_aad_components_ >= kMaxSivAADComponents) return false;
603632
MarkPopErrorOnReturn mark_pop_error_on_return;
604633

@@ -659,12 +688,18 @@ CipherBase::UpdateResult CipherBase::Update(
659688
if (!ctx_ || len > INT_MAX) return kErrorState;
660689
MarkPopErrorOnReturn mark_pop_error_on_return;
661690

662-
if (ctx_.isCcmMode() && !CheckCCMMessageLength(len)) {
691+
const bool is_ccm_mode = ctx_.isCcmMode();
692+
const bool is_ccm_decipher = kind_ == kDecipher && is_ccm_mode;
693+
694+
if (is_ccm_mode && !CheckCCMMessageLength(len)) {
663695
return kErrorMessageSize;
664696
}
665697

666698
const bool is_siv = ctx_.isSivMode() || ctx_.isGcmSivMode();
667-
if (is_siv && has_siv_update_) {
699+
const bool is_cts = ctx_.isCtsMode();
700+
const bool is_wrap = ctx_.isWrapMode();
701+
const bool is_one_shot = is_ccm_decipher || is_siv || is_cts || is_wrap;
702+
if (is_one_shot && has_one_shot_update_) {
668703
return kErrorState;
669704
}
670705

@@ -694,8 +729,8 @@ CipherBase::UpdateResult CipherBase::Update(
694729

695730
bool r = ctx_.update(
696731
buffer, static_cast<unsigned char*>((*out)->Data()), &buf_len);
697-
if (is_siv) {
698-
has_siv_update_ = true;
732+
if (is_ccm_decipher || is_siv || ((is_cts || is_wrap) && r)) {
733+
has_one_shot_update_ = true;
699734
}
700735

701736
// When in CCM mode, EVP_CipherUpdate will fail if the authentication tag is
@@ -773,7 +808,9 @@ void CipherBase::SetAutoPadding(const FunctionCallbackInfo<Value>& args) {
773808

774809
bool CipherBase::Final(std::unique_ptr<BackingStore>* out) {
775810
if (!ctx_) return false;
776-
if ((ctx_.isSivMode() || ctx_.isGcmSivMode()) && !has_siv_update_) {
811+
const bool is_one_shot = ctx_.isSivMode() || ctx_.isGcmSivMode() ||
812+
ctx_.isCtsMode() || ctx_.isWrapMode();
813+
if (is_one_shot && !has_one_shot_update_) {
777814
ctx_.reset();
778815
return false;
779816
}
@@ -796,7 +833,8 @@ bool CipherBase::Final(std::unique_ptr<BackingStore>* out) {
796833
// EVP_CipherFinal_ex must not be called and will fail.
797834
bool ok;
798835
if (kind_ == kDecipher && ctx_.isCcmMode()) {
799-
ok = !pending_auth_failed_;
836+
ok = auth_tag_state_ == kAuthTagSetByUser && has_one_shot_update_ &&
837+
!pending_auth_failed_;
800838
*out = ArrayBuffer::NewBackingStore(env()->isolate(), 0);
801839
} else {
802840
int out_len = (*out)->ByteLength();

‎src/crypto/crypto_cipher.h‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,15 @@ class CipherBase : public BaseObject {
5151
int key_len,
5252
const unsigned char* iv,
5353
int iv_len,
54-
unsigned int auth_tag_len);
54+
unsigned int auth_tag_len,
55+
const char* cts_mode,
56+
const char* xts_standard);
5557
void InitIv(const char* cipher_type,
5658
const ByteSource& key_buf,
5759
const ArrayBufferOrViewContents<unsigned char>& iv_buf,
58-
unsigned int auth_tag_len);
60+
unsigned int auth_tag_len,
61+
const char* cts_mode,
62+
const char* xts_standard);
5963
bool InitAuthenticated(const char* cipher_type,
6064
int iv_len,
6165
unsigned int auth_tag_len);
@@ -87,7 +91,7 @@ class CipherBase : public BaseObject {
8791
unsigned int auth_tag_len_;
8892
char auth_tag_[ncrypto::Cipher::MAX_AUTH_TAG_LENGTH];
8993
bool pending_auth_failed_;
90-
bool has_siv_update_;
94+
bool has_one_shot_update_;
9195
unsigned int siv_aad_components_;
9296
int max_message_size_;
9397
};

0 commit comments

Comments
 (0)