/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/crypto/crypto_chacha20_poly1305.cc
341 строка
10 KB
Filip Skokan
src: implement MemoryRetainer protocol for ByteSource
29 июл 2026, 20:48
Не верифицирован
29 июл 2026, 20:48
29d183d
Код
Авторство
О чём код?
#include "crypto/crypto_chacha20_poly1305.h" #include "async_wrap-inl.h" #include "base_object-inl.h" #include "crypto/crypto_cipher.h" #include "crypto/crypto_keys.h" #include "crypto/crypto_util.h" #include "env-inl.h" #include "memory_tracker-inl.h" #include "threadpoolwork-inl.h" #include "v8.h" #include <openssl/evp.h> #ifdef OPENSSL_IS_BORINGSSL #include <openssl/aead.h> #endif namespace node { using ncrypto::Cipher; using ncrypto::CipherCtxPointer; using ncrypto::DataPointer; using v8::FunctionCallbackInfo; using v8::JustVoid; using v8::Local; using v8::Maybe; using v8::Nothing; using v8::Object; using v8::Value; namespace crypto { namespace { constexpr size_t kChaCha20Poly1305KeySize = 32; constexpr size_t kChaCha20Poly1305IvSize = 12; constexpr size_t kChaCha20Poly1305TagSize = 16; bool ValidateIV(Environment* env, CryptoJobMode mode, Local<Value> value, ChaCha20Poly1305CipherConfig* params) { ArrayBufferOrViewContents<unsigned char> iv(value); if (!iv.CheckSizeInt32()) [[unlikely]] { THROW_ERR_OUT_OF_RANGE(env, "iv is too large"); return false; } if (iv.size() != kChaCha20Poly1305IvSize) { THROW_ERR_CRYPTO_INVALID_IV(env); return false; } if (IsCryptoJobAsync(mode)) { params->iv = iv.ToCopy(); } else { params->iv = iv.ToByteSource(); } return true; } bool ValidateAdditionalData(Environment* env, CryptoJobMode mode, Local<Value> value, ChaCha20Poly1305CipherConfig* params) { if (IsAnyBufferSource(value)) { ArrayBufferOrViewContents<unsigned char> additional_data(value); if (!additional_data.CheckSizeInt32()) [[unlikely]] { THROW_ERR_OUT_OF_RANGE(env, "additional data is too large"); return false; } if (IsCryptoJobAsync(mode)) { params->additional_data = additional_data.ToCopy(); } else { params->additional_data = additional_data.ToByteSource(); } } return true; } } // namespace ChaCha20Poly1305CipherConfig::ChaCha20Poly1305CipherConfig( ChaCha20Poly1305CipherConfig&& other) noexcept : cipher(other.cipher), iv(std::move(other.iv)), additional_data(std::move(other.additional_data)) {} ChaCha20Poly1305CipherConfig& ChaCha20Poly1305CipherConfig::operator=( ChaCha20Poly1305CipherConfig&& other) noexcept { if (&other == this) return *this; this->~ChaCha20Poly1305CipherConfig(); return *new (this) ChaCha20Poly1305CipherConfig(std::move(other)); } void ChaCha20Poly1305CipherConfig::MemoryInfo(MemoryTracker* tracker) const { tracker->TraitTrackInline(iv, "iv"); tracker->TraitTrackInline(additional_data, "additional_data"); } Maybe<void> ChaCha20Poly1305CipherTraits::AdditionalConfig( CryptoJobMode mode, const FunctionCallbackInfo<Value>& args, unsigned int offset, WebCryptoCipherMode cipher_mode, ChaCha20Poly1305CipherConfig* params) { Environment* env = Environment::GetCurrent(args); params->cipher = ncrypto::Cipher::CHACHA20_POLY1305; #ifndef OPENSSL_IS_BORINGSSL // On BoringSSL, ChaCha20-Poly1305 is not exposed via the EVP_CIPHER registry // so FromNid() returns a null Cipher. We use EVP_AEAD directly in DoCipher // instead. if (!params->cipher) { THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env); return Nothing<void>(); } #endif // IV parameter (required) if (!ValidateIV(env, mode, args[offset], params)) { return Nothing<void>(); } // Additional authenticated data parameter (optional) if (static_cast<unsigned int>(args.Length()) > offset + 1) { if (!ValidateAdditionalData(env, mode, args[offset + 1], params)) { return Nothing<void>(); } } return JustVoid(); } WebCryptoCipherStatus ChaCha20Poly1305CipherTraits::DoCipher( Environment* env, const KeyObjectData& key_data, WebCryptoCipherMode cipher_mode, const ChaCha20Poly1305CipherConfig& params, const ByteSource& in, ByteSource* out) { CHECK_EQ(key_data.GetKeyType(), kKeyTypeSecret); // Validate key size if (key_data.GetSymmetricKeySize() != kChaCha20Poly1305KeySize) { return WebCryptoCipherStatus::INVALID_KEY_TYPE; } #ifdef OPENSSL_IS_BORINGSSL // BoringSSL does not expose ChaCha20-Poly1305 via the EVP_CIPHER registry; // it is only available through the EVP_AEAD API. Matches Chromium's // WebCrypto ChaCha20-Poly1305 implementation. const auto key_bytes = reinterpret_cast<const unsigned char*>(key_data.GetSymmetricKey()); const auto ad_bytes = params.additional_data.data<unsigned char>(); const auto ad_len = params.additional_data.size(); const auto iv_bytes = params.iv.data<unsigned char>(); const auto iv_len = params.iv.size(); bssl::ScopedEVP_AEAD_CTX ctx; if (!EVP_AEAD_CTX_init(ctx.get(), EVP_aead_chacha20_poly1305(), key_bytes, key_data.GetSymmetricKeySize(), kChaCha20Poly1305TagSize, nullptr)) { return WebCryptoCipherStatus::FAILED; } if (cipher_mode == kWebCryptoCipherEncrypt) { size_t out_len = 0; const size_t max_out_len = in.size() + kChaCha20Poly1305TagSize; auto buf = DataPointer::Alloc(max_out_len); if (!EVP_AEAD_CTX_seal(ctx.get(), static_cast<unsigned char*>(buf.get()), &out_len, max_out_len, iv_bytes, iv_len, in.data<unsigned char>(), in.size(), ad_bytes, ad_len)) { return WebCryptoCipherStatus::FAILED; } buf = buf.resize(out_len); *out = ByteSource::Allocated(buf.release()); return WebCryptoCipherStatus::OK; } // Decrypt if (in.size() < kChaCha20Poly1305TagSize) { return WebCryptoCipherStatus::FAILED; } size_t out_len = 0; const size_t max_out_len = in.size(); // at most |in_len| bytes written auto buf = DataPointer::Alloc(max_out_len == 0 ? 1 : max_out_len); if (!EVP_AEAD_CTX_open(ctx.get(), static_cast<unsigned char*>(buf.get()), &out_len, max_out_len, iv_bytes, iv_len, in.data<unsigned char>(), in.size(), ad_bytes, ad_len)) { return WebCryptoCipherStatus::FAILED; } if (out_len == 0) { *out = ByteSource(); } else { buf = buf.resize(out_len); *out = ByteSource::Allocated(buf.release()); } return WebCryptoCipherStatus::OK; #else auto ctx = CipherCtxPointer::New(); if (!ctx) { return WebCryptoCipherStatus::FAILED; } const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt; if (!ctx.init(params.cipher, encrypt)) { return WebCryptoCipherStatus::FAILED; } if (!ctx.setKeyLength(key_data.GetSymmetricKeySize()) || !ctx.init( Cipher(), encrypt, reinterpret_cast<const unsigned char*>(key_data.GetSymmetricKey()), params.iv.data<unsigned char>())) { return WebCryptoCipherStatus::FAILED; } size_t tag_len = kChaCha20Poly1305TagSize; size_t data_len = in.size(); switch (cipher_mode) { case kWebCryptoCipherDecrypt: { if (data_len < tag_len) { return WebCryptoCipherStatus::FAILED; } data_len -= tag_len; if (!ctx.setAeadTag(ncrypto::Buffer<const char>{ .data = in.data<char>() + data_len, .len = tag_len, })) { return WebCryptoCipherStatus::FAILED; } break; } case kWebCryptoCipherEncrypt: { break; } default: UNREACHABLE(); } size_t total = 0; const int block_size = ctx.getBlockSize(); if (block_size < 0) { return WebCryptoCipherStatus::FAILED; } int buf_len; if (!TryGetIntCipherOutputLength( data_len, static_cast<size_t>(block_size) + (encrypt ? tag_len : 0), &buf_len)) { return WebCryptoCipherStatus::FAILED; } int out_len; // Process additional authenticated data if present ncrypto::Buffer<const unsigned char> buffer = { .data = params.additional_data.data<unsigned char>(), .len = params.additional_data.size(), }; if (params.additional_data.size() && !ctx.update(buffer, nullptr, &out_len)) { return WebCryptoCipherStatus::FAILED; } auto buf = DataPointer::Alloc(buf_len); auto ptr = static_cast<unsigned char*>(buf.get()); // Process the input data buffer = { .data = in.data<unsigned char>(), .len = data_len, }; if (data_len == 0) { if (!ctx.update({}, ptr, &out_len)) { return WebCryptoCipherStatus::FAILED; } } else if (!ctx.update(buffer, ptr, &out_len)) { return WebCryptoCipherStatus::FAILED; } total += out_len; CHECK_LE(out_len, buf_len); out_len = block_size; if (!ctx.update({}, ptr + total, &out_len, true)) { return WebCryptoCipherStatus::FAILED; } total += out_len; // If encrypting, grab the generated auth tag and append it to the ciphertext if (encrypt) { if (!ctx.getAeadTag(kChaCha20Poly1305TagSize, ptr + total)) { return WebCryptoCipherStatus::FAILED; } total += kChaCha20Poly1305TagSize; } if (total == 0) { *out = ByteSource(); return WebCryptoCipherStatus::OK; } // Size down to the actual used space buf = buf.resize(total); *out = ByteSource::Allocated(buf.release()); return WebCryptoCipherStatus::OK; #endif // OPENSSL_IS_BORINGSSL } void ChaCha20Poly1305::Initialize(Environment* env, Local<Object> target) { ChaCha20Poly1305CryptoJob::Initialize(env, target); } void ChaCha20Poly1305::RegisterExternalReferences( ExternalReferenceRegistry* registry) { ChaCha20Poly1305CryptoJob::RegisterExternalReferences(registry); } } // namespace crypto } // namespace node