diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 457bd2f6c5b189..b4322369ad84ee 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -79,7 +79,7 @@ std::optional CryptoErrorList::pop_front() { // ============================================================================ DataPointer DataPointer::Alloc(size_t len) { - return DataPointer(OPENSSL_malloc(len), len); + return DataPointer(OPENSSL_zalloc(len), len); } DataPointer::DataPointer(void* data, size_t length) @@ -1427,6 +1427,33 @@ DataPointer pbkdf2(const EVP_MD* md, // ============================================================================ +EVPKeyPointer::PrivateKeyEncodingConfig::PrivateKeyEncodingConfig( + const PrivateKeyEncodingConfig& other) + : PrivateKeyEncodingConfig(other.output_key_object, other.format, other.type) { + cipher = other.cipher; + if (other.passphrase.has_value()) { + auto& otherPassphrase = other.passphrase.value(); + auto newPassphrase = DataPointer::Alloc(otherPassphrase.size()); + memcpy(newPassphrase.get(), otherPassphrase.get(), otherPassphrase.size()); + passphrase = std::move(newPassphrase); + } +} + +EVPKeyPointer::AsymmetricKeyEncodingConfig::AsymmetricKeyEncodingConfig( + bool output_key_object, + PKFormatType format, + PKEncodingType type) + : output_key_object(output_key_object), + format(format), + type(type) {} + +EVPKeyPointer::PrivateKeyEncodingConfig& EVPKeyPointer::PrivateKeyEncodingConfig::operator=( + const PrivateKeyEncodingConfig& other) { + if (this == &other) return *this; + this->~PrivateKeyEncodingConfig(); + return *new (this) PrivateKeyEncodingConfig(other); +} + EVPKeyPointer EVPKeyPointer::New() { return EVPKeyPointer(EVP_PKEY_new()); } @@ -1660,14 +1687,13 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKeyPEM( } EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey( - PKFormatType format, - PKEncodingType encoding, + const PublicKeyEncodingConfig& config, const Buffer& buffer) { - if (format == PKFormatType::PEM) { + if (config.format == PKFormatType::PEM) { return TryParsePublicKeyPEM(buffer); } - if (format != PKFormatType::DER) { + if (config.format != PKFormatType::DER) { return ParseKeyResult(PKParseError::FAILED); } @@ -1675,12 +1701,12 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey( EVP_PKEY* key = nullptr; - if (encoding == PKEncodingType::PKCS1 && + if (config.type == PKEncodingType::PKCS1 && (key = d2i_PublicKey(EVP_PKEY_RSA, nullptr, &start, buffer.len))) { return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key)); } - if (encoding == PKEncodingType::SPKI && + if (config.type == PKEncodingType::SPKI && (key = d2i_PUBKEY(nullptr, &start, buffer.len))) { return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key)); } @@ -1688,13 +1714,34 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey( return ParseKeyResult(PKParseError::FAILED); } +namespace { +Buffer GetPassphrase(const EVPKeyPointer::PrivateKeyEncodingConfig& config) { + Buffer pass { + // OpenSSL will not actually dereference this pointer, so it can be any + // non-null pointer. We cannot assert that directly, which is why we + // intentionally use a pointer that will likely cause a segmentation fault + // when dereferenced. + .data = reinterpret_cast(-1), + .len = 0, + }; + if (config.passphrase.has_value()) { + auto& passphrase = config.passphrase.value(); + // The pass.data can't be a nullptr, even if the len is zero or else + // openssl will prompt for a password and we really don't want that. + if (passphrase.get() != nullptr) { + pass.data = static_cast(passphrase.get()); + } + pass.len = passphrase.size(); + } + return pass; +} +} // namespace + EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey( - PKFormatType format, - PKEncodingType encoding, - std::optional> maybe_passphrase, + const PrivateKeyEncodingConfig& config, const Buffer& buffer) { - static auto keyOrError = [&](EVPKeyPointer pkey, bool had_passphrase = false) { + static constexpr auto keyOrError = [](EVPKeyPointer pkey, bool had_passphrase = false) { if (int err = ERR_peek_error()) { if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_BAD_PASSWORD_READ && @@ -1707,24 +1754,23 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey( return ParseKeyResult(std::move(pkey)); }; - Buffer* passphrase = nullptr; - if (maybe_passphrase.has_value()) { - passphrase = &maybe_passphrase.value(); - } auto bio = BIOPointer::New(buffer); if (!bio) return ParseKeyResult(PKParseError::FAILED); - if (format == PKFormatType::PEM) { - auto key = PEM_read_bio_PrivateKey(bio.get(), nullptr, PasswordCallback, passphrase); - return keyOrError(EVPKeyPointer(key), maybe_passphrase.has_value()); + auto passphrase = GetPassphrase(config); + + if (config.format == PKFormatType::PEM) { + auto key = PEM_read_bio_PrivateKey(bio.get(), nullptr, PasswordCallback, + config.passphrase.has_value() ? &passphrase : nullptr); + return keyOrError(EVPKeyPointer(key), config.passphrase.has_value()); } - if (format != PKFormatType::DER) { + if (config.format != PKFormatType::DER) { return ParseKeyResult(PKParseError::FAILED); } - switch (encoding) { + switch (config.type) { case PKEncodingType::PKCS1: { auto key = d2i_PrivateKey_bio(bio.get(), nullptr); return keyOrError(EVPKeyPointer(key)); @@ -1734,8 +1780,8 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey( auto key = d2i_PKCS8PrivateKey_bio(bio.get(), nullptr, PasswordCallback, - passphrase); - return keyOrError(EVPKeyPointer(key), maybe_passphrase.has_value()); + config.passphrase.has_value() ? &passphrase : nullptr); + return keyOrError(EVPKeyPointer(key), config.passphrase.has_value()); } PKCS8Pointer p8inf(d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr)); @@ -1754,4 +1800,166 @@ EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey( }; } +Result EVPKeyPointer::writePrivateKey( + const PrivateKeyEncodingConfig& config) const { + if (config.format == PKFormatType::JWK) { + return Result(false); + } + + auto bio = BIOPointer::NewMem(); + if (!bio) { + return Result(false); + } + + auto passphrase = GetPassphrase(config); + MarkPopErrorOnReturn mark_pop_error_on_return; + bool err; + + switch (config.type) { + case PKEncodingType::PKCS1: { + // PKCS1 is only permitted for RSA keys. + if (id() != EVP_PKEY_RSA) return Result(false); + +#if OPENSSL_VERSION_MAJOR >= 3 + const RSA* rsa = EVP_PKEY_get0_RSA(get()); +#else + RSA* rsa = EVP_PKEY_get0_RSA(get()); +#endif + switch (config.format) { + case PKFormatType::PEM: { + err = PEM_write_bio_RSAPrivateKey(bio.get(), rsa, config.cipher, + reinterpret_cast(passphrase.data), + passphrase.len, nullptr, nullptr) != 1; + break; + } + case PKFormatType::DER: { + // Encoding PKCS1 as DER. This variation does not permit encryption. + err = i2d_RSAPrivateKey_bio(bio.get(), rsa) != 1; + break; + } + default: { + // Should never get here. + return Result(false); + } + } + break; + } + case PKEncodingType::PKCS8: { + switch (config.format) { + case PKFormatType::PEM: { + // Encode PKCS#8 as PEM. + err = PEM_write_bio_PKCS8PrivateKey( + bio.get(), get(), + config.cipher, + passphrase.data, + passphrase.len, + nullptr, nullptr) != 1; + break; + } + case PKFormatType::DER: { + err = i2d_PKCS8PrivateKey_bio( + bio.get(), get(), + config.cipher, + passphrase.data, + passphrase.len, + nullptr, nullptr) != 1; + break; + } + default: { + // Should never get here. + return Result(false); + } + } + break; + } + case PKEncodingType::SEC1: { + // SEC1 is only permitted for EC keys + if (id() != EVP_PKEY_EC) return Result(false); + +#if OPENSSL_VERSION_MAJOR >= 3 + const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get()); +#else + EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get()); +#endif + switch (config.format) { + case PKFormatType::PEM: { + err = PEM_write_bio_ECPrivateKey(bio.get(), + ec, + config.cipher, + reinterpret_cast(passphrase.data), + passphrase.len, + nullptr, + nullptr) != 1; + break; + } + case PKFormatType::DER: { + // Encoding SEC1 as DER. This variation does not permit encryption. + err = i2d_ECPrivateKey_bio(bio.get(), ec) != 1; + break; + } + default: { + // Should never get here. + return Result(false); + } + } + break; + } + default: { + // Not a valid private key encoding + return Result(false); + } + } + + if (err) { + // Failed to encode the private key. + return Result(false, mark_pop_error_on_return.peekError()); + } + + return bio; +} + +Result EVPKeyPointer::writePublicKey( + const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const { + auto bio = BIOPointer::NewMem(); + if (!bio) return Result(false); + + MarkPopErrorOnReturn mark_pop_error_on_return; + + if (config.type == ncrypto::EVPKeyPointer::PKEncodingType::PKCS1) { + // PKCS#1 is only valid for RSA keys. +#if OPENSSL_VERSION_MAJOR >= 3 + const RSA* rsa = EVP_PKEY_get0_RSA(get()); +#else + RSA* rsa = EVP_PKEY_get0_RSA(get()); +#endif + if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { + // Encode PKCS#1 as PEM. + if (PEM_write_bio_RSAPublicKey(bio.get(), rsa) != 1) { + return Result(false, mark_pop_error_on_return.peekError()); + } + return bio; + } + + // Encode PKCS#1 as DER. + if (i2d_RSAPublicKey_bio(bio.get(), rsa) != 1) { + return Result(false, mark_pop_error_on_return.peekError()); + } + return bio; + } + + if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { + // Encode SPKI as PEM. + if (PEM_write_bio_PUBKEY(bio.get(), get()) != 1) { + return Result(false, mark_pop_error_on_return.peekError()); + } + return bio; + } + + // Encode SPKI as DER. + if (i2d_PUBKEY_bio(bio.get(), get()) != 1) { + return Result(false, mark_pop_error_on_return.peekError()); + } + return bio; +} + } // namespace ncrypto diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 20b69dc67b13fd..08eeb5be556136 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -386,13 +386,13 @@ class EVPKeyPointer final { // SubjectPublicKeyInfo according to X.509. SPKI, // ECPrivateKey according to SEC1. - SEC1 + SEC1, }; enum class PKFormatType { DER, PEM, - JWK + JWK, }; enum class PKParseError { @@ -402,18 +402,36 @@ class EVPKeyPointer final { }; using ParseKeyResult = Result; + struct AsymmetricKeyEncodingConfig { + bool output_key_object = false; + PKFormatType format = PKFormatType::DER; + PKEncodingType type = PKEncodingType::PKCS8; + AsymmetricKeyEncodingConfig() = default; + AsymmetricKeyEncodingConfig(bool output_key_object, PKFormatType format, PKEncodingType type); + AsymmetricKeyEncodingConfig(const AsymmetricKeyEncodingConfig&) = default; + AsymmetricKeyEncodingConfig& operator=(const AsymmetricKeyEncodingConfig&) = default; + }; + using PublicKeyEncodingConfig = AsymmetricKeyEncodingConfig; + + struct PrivateKeyEncodingConfig: public AsymmetricKeyEncodingConfig { + const EVP_CIPHER* cipher = nullptr; + std::optional passphrase = std::nullopt; + PrivateKeyEncodingConfig() = default; + PrivateKeyEncodingConfig(bool output_key_object, PKFormatType format, PKEncodingType type) + : AsymmetricKeyEncodingConfig(output_key_object, format, type) {} + PrivateKeyEncodingConfig(const PrivateKeyEncodingConfig&); + PrivateKeyEncodingConfig& operator=(const PrivateKeyEncodingConfig&); + }; + static ParseKeyResult TryParsePublicKey( - PKFormatType format, - PKEncodingType encoding, + const PublicKeyEncodingConfig& config, const Buffer& buffer); static ParseKeyResult TryParsePublicKeyPEM( const Buffer& buffer); static ParseKeyResult TryParsePrivateKey( - PKFormatType format, - PKEncodingType encoding, - std::optional> passphrase, + const PrivateKeyEncodingConfig& config, const Buffer& buffer); EVPKeyPointer() = default; @@ -441,9 +459,11 @@ class EVPKeyPointer final { size_t rawPrivateKeySize() const; DataPointer rawPublicKey() const; DataPointer rawPrivateKey() const; - BIOPointer derPublicKey() const; + Result writePrivateKey(const PrivateKeyEncodingConfig& config) const; + Result writePublicKey(const PublicKeyEncodingConfig& config) const; + EVPKeyCtxPointer newCtx() const; static bool IsRSAPrivateKey(const Buffer& buffer); diff --git a/src/crypto/crypto_keygen.cc b/src/crypto/crypto_keygen.cc index 8a17cde8b9cac5..c13acd63886673 100644 --- a/src/crypto/crypto_keygen.cc +++ b/src/crypto/crypto_keygen.cc @@ -48,8 +48,7 @@ Maybe NidKeyPairGenTraits::AdditionalConfig( EVPKeyCtxPointer NidKeyPairGenTraits::Setup(NidKeyPairGenConfig* params) { EVPKeyCtxPointer ctx = EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(params->params.id, nullptr)); - if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0) - return EVPKeyCtxPointer(); + if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0) return {}; return ctx; } diff --git a/src/crypto/crypto_keygen.h b/src/crypto/crypto_keygen.h index 2707cb8b41dc53..f1e92c69cb4065 100644 --- a/src/crypto/crypto_keygen.h +++ b/src/crypto/crypto_keygen.h @@ -147,19 +147,16 @@ struct KeyPairGenTraits final { // process input parameters. This allows each job to have a variable // number of input parameters specific to each job type. if (KeyPairAlgorithmTraits::AdditionalConfig(mode, args, offset, params) - .IsNothing()) { + .IsNothing() || + !KeyObjectData::GetPublicKeyEncodingFromJs( + args, offset, kKeyContextGenerate) + .To(¶ms->public_key_encoding) || + !KeyObjectData::GetPrivateKeyEncodingFromJs( + args, offset, kKeyContextGenerate) + .To(¶ms->private_key_encoding)) { return v8::Nothing(); } - params->public_key_encoding = KeyObjectData::GetPublicKeyEncodingFromJs( - args, offset, kKeyContextGenerate); - - auto private_key_encoding = KeyObjectData::GetPrivateKeyEncodingFromJs( - args, offset, kKeyContextGenerate); - - if (!private_key_encoding.IsEmpty()) - params->private_key_encoding = private_key_encoding.Release(); - return v8::JustVoid(); } @@ -230,8 +227,8 @@ struct SecretKeyGenTraits final { template struct KeyPairGenConfig final : public MemoryRetainer { - PublicKeyEncodingConfig public_key_encoding; - PrivateKeyEncodingConfig private_key_encoding; + ncrypto::EVPKeyPointer::PublicKeyEncodingConfig public_key_encoding; + ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig private_key_encoding; KeyObjectData key; AlgorithmParams params; @@ -245,7 +242,7 @@ struct KeyPairGenConfig final : public MemoryRetainer { explicit KeyPairGenConfig(KeyPairGenConfig&& other) noexcept : public_key_encoding(other.public_key_encoding), private_key_encoding( - std::forward( + std::forward( other.private_key_encoding)), key(std::move(other.key)), params(std::move(other.params)) {} @@ -258,9 +255,10 @@ struct KeyPairGenConfig final : public MemoryRetainer { void MemoryInfo(MemoryTracker* tracker) const override { tracker->TrackField("key", key); - if (!private_key_encoding.passphrase_.IsEmpty()) { + if (private_key_encoding.passphrase.has_value()) { + auto& passphrase = private_key_encoding.passphrase.value(); tracker->TrackFieldWithSize("private_key_encoding.passphrase", - private_key_encoding.passphrase_->size()); + passphrase.size()); } tracker->TrackField("params", params); } diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index 784180e90a8702..f83002e8bc315f 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -25,6 +25,7 @@ using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Int32; using v8::Isolate; +using v8::Just; using v8::JustVoid; using v8::Local; using v8::Maybe; @@ -40,199 +41,86 @@ using v8::Value; namespace crypto { namespace { -void GetKeyFormatAndTypeFromJs( - AsymmetricKeyEncodingConfig* config, - const FunctionCallbackInfo& args, - unsigned int* offset, - KeyEncodingContext context) { +Maybe +GetKeyFormatAndTypeFromJs(const FunctionCallbackInfo& args, + unsigned int* offset, + KeyEncodingContext context) { + ncrypto::EVPKeyPointer::AsymmetricKeyEncodingConfig config; // During key pair generation, it is possible not to specify a key encoding, // which will lead to a key object being returned. if (args[*offset]->IsUndefined()) { CHECK_EQ(context, kKeyContextGenerate); CHECK(args[*offset + 1]->IsUndefined()); - config->output_key_object_ = true; + config.output_key_object = true; } else { - config->output_key_object_ = false; + config.output_key_object = false; CHECK(args[*offset]->IsInt32()); - config->format_ = static_cast( + config.format = static_cast( args[*offset].As()->Value()); if (args[*offset + 1]->IsInt32()) { - config->type_ = - static_cast(args[*offset + 1].As()->Value()); + config.type = static_cast( + args[*offset + 1].As()->Value()); } else { - CHECK( - (context == kKeyContextInput && - config->format_ == kKeyFormatPEM) || - (context == kKeyContextGenerate && - config->format_ == kKeyFormatJWK)); + CHECK((context == kKeyContextInput && + config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) || + (context == kKeyContextGenerate && + config.format == ncrypto::EVPKeyPointer::PKFormatType::JWK)); CHECK(args[*offset + 1]->IsNullOrUndefined()); - config->type_ = std::nullopt; + config.type = ncrypto::EVPKeyPointer::PKEncodingType::PKCS1; } } *offset += 2; + return Just(config); } -MaybeLocal BIOToStringOrBuffer(Environment* env, - const BIOPointer& bio, - PKFormatType format) { +MaybeLocal BIOToStringOrBuffer( + Environment* env, + const BIOPointer& bio, + const ncrypto::EVPKeyPointer::AsymmetricKeyEncodingConfig& config) { BUF_MEM* bptr = bio; - if (format == kKeyFormatPEM) { + if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { // PEM is an ASCII format, so we will return it as a string. - return String::NewFromUtf8(env->isolate(), bptr->data, - NewStringType::kNormal, - bptr->length).FromMaybe(Local()); - } else { - CHECK_EQ(format, kKeyFormatDER); - // DER is binary, return it as a buffer. - return Buffer::Copy(env, bptr->data, bptr->length) + return String::NewFromUtf8( + env->isolate(), bptr->data, NewStringType::kNormal, bptr->length) .FromMaybe(Local()); } -} - -MaybeLocal WritePrivateKey(Environment* env, - OSSL3_CONST EVP_PKEY* pkey, - const PrivateKeyEncodingConfig& config) { - auto bio = BIOPointer::NewMem(); - CHECK(bio); - // If an empty string was passed as the passphrase, the ByteSource might - // contain a null pointer, which OpenSSL will ignore, causing it to invoke its - // default passphrase callback, which would block the thread until the user - // manually enters a passphrase. We could supply our own passphrase callback - // to handle this special case, but it is easier to avoid passing a null - // pointer to OpenSSL. - char* pass = nullptr; - size_t pass_len = 0; - if (!config.passphrase_.IsEmpty()) { - pass = const_cast(config.passphrase_->data()); - pass_len = config.passphrase_->size(); - if (pass == nullptr) { - // OpenSSL will not actually dereference this pointer, so it can be any - // non-null pointer. We cannot assert that directly, which is why we - // intentionally use a pointer that will likely cause a segmentation fault - // when dereferenced. - CHECK_EQ(pass_len, 0); - pass = reinterpret_cast(-1); - CHECK_NE(pass, nullptr); - } - } + CHECK_EQ(config.format, ncrypto::EVPKeyPointer::PKFormatType::DER); + // DER is binary, return it as a buffer. + return Buffer::Copy(env, bptr->data, bptr->length).FromMaybe(Local()); +} - MarkPopErrorOnReturn mark_pop_error_on_return; - bool err; - - PKEncodingType encoding_type = config.type_.value(); - if (encoding_type == kKeyEncodingPKCS1) { - // PKCS#1 is only permitted for RSA keys. - CHECK_EQ(EVPKeyPointer::id(pkey), EVP_PKEY_RSA); - - OSSL3_CONST RSA* rsa = EVP_PKEY_get0_RSA(pkey); - if (config.format_ == kKeyFormatPEM) { - // Encode PKCS#1 as PEM. - err = PEM_write_bio_RSAPrivateKey(bio.get(), - rsa, - config.cipher_, - reinterpret_cast(pass), - pass_len, - nullptr, - nullptr) != 1; - } else { - // Encode PKCS#1 as DER. This does not permit encryption. - CHECK_EQ(config.format_, kKeyFormatDER); - CHECK_NULL(config.cipher_); - err = i2d_RSAPrivateKey_bio(bio.get(), rsa) != 1; - } - } else if (encoding_type == kKeyEncodingPKCS8) { - if (config.format_ == kKeyFormatPEM) { - // Encode PKCS#8 as PEM. - err = PEM_write_bio_PKCS8PrivateKey( - bio.get(), pkey, - config.cipher_, - pass, - pass_len, - nullptr, nullptr) != 1; - } else { - // Encode PKCS#8 as DER. - CHECK_EQ(config.format_, kKeyFormatDER); - err = i2d_PKCS8PrivateKey_bio( - bio.get(), pkey, - config.cipher_, - pass, - pass_len, - nullptr, nullptr) != 1; - } - } else { - CHECK_EQ(encoding_type, kKeyEncodingSEC1); - - // SEC1 is only permitted for EC keys. - CHECK_EQ(EVPKeyPointer::id(pkey), EVP_PKEY_EC); - - OSSL3_CONST EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey); - if (config.format_ == kKeyFormatPEM) { - // Encode SEC1 as PEM. - err = PEM_write_bio_ECPrivateKey(bio.get(), - ec_key, - config.cipher_, - reinterpret_cast(pass), - pass_len, - nullptr, - nullptr) != 1; - } else { - // Encode SEC1 as DER. This does not permit encryption. - CHECK_EQ(config.format_, kKeyFormatDER); - CHECK_NULL(config.cipher_); - err = i2d_ECPrivateKey_bio(bio.get(), ec_key) != 1; - } +MaybeLocal WritePrivateKey( + Environment* env, + const EVPKeyPointer& pkey, + const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config) { + CHECK(pkey); + auto res = pkey.writePrivateKey(config); + if (res) { + return BIOToStringOrBuffer(env, std::move(res.value), config); } - if (err) { - ThrowCryptoError(env, ERR_get_error(), "Failed to encode private key"); - return MaybeLocal(); - } - return BIOToStringOrBuffer(env, bio, config.format_); + ThrowCryptoError( + env, res.openssl_error.value_or(0), "Failed to encode private key"); + return MaybeLocal(); } -bool WritePublicKeyInner(OSSL3_CONST EVP_PKEY* pkey, - const BIOPointer& bio, - const PublicKeyEncodingConfig& config) { - if (config.type_.value() == kKeyEncodingPKCS1) { - // PKCS#1 is only valid for RSA keys. - CHECK_EQ(EVPKeyPointer::id(pkey), EVP_PKEY_RSA); - OSSL3_CONST RSA* rsa = EVP_PKEY_get0_RSA(pkey); - if (config.format_ == kKeyFormatPEM) { - // Encode PKCS#1 as PEM. - return PEM_write_bio_RSAPublicKey(bio.get(), rsa) == 1; - } else { - // Encode PKCS#1 as DER. - CHECK_EQ(config.format_, kKeyFormatDER); - return i2d_RSAPublicKey_bio(bio.get(), rsa) == 1; - } - } else { - CHECK_EQ(config.type_.value(), kKeyEncodingSPKI); - if (config.format_ == kKeyFormatPEM) { - // Encode SPKI as PEM. - return PEM_write_bio_PUBKEY(bio.get(), pkey) == 1; - } else { - // Encode SPKI as DER. - CHECK_EQ(config.format_, kKeyFormatDER); - return i2d_PUBKEY_bio(bio.get(), pkey) == 1; - } +MaybeLocal WritePublicKey( + Environment* env, + const EVPKeyPointer& pkey, + const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) { + CHECK(pkey); + auto res = pkey.writePublicKey(config); + if (res) { + return BIOToStringOrBuffer(env, res.value, config); } -} -MaybeLocal WritePublicKey(Environment* env, - OSSL3_CONST EVP_PKEY* pkey, - const PublicKeyEncodingConfig& config) { - auto bio = BIOPointer::NewMem(); - CHECK(bio); - - if (!WritePublicKeyInner(pkey, bio, config)) { - ThrowCryptoError(env, ERR_get_error(), "Failed to encode public key"); - return MaybeLocal(); - } - return BIOToStringOrBuffer(env, bio, config.format_); + ThrowCryptoError( + env, res.openssl_error.value_or(0), "Failed to encode public key"); + return MaybeLocal(); } Maybe ExportJWKSecretKey(Environment* env, @@ -247,12 +135,11 @@ Maybe ExportJWKSecretKey(Environment* env, key.GetSymmetricKeySize(), BASE64URL, &error); - if (key_data.IsEmpty()) { + if (!key_data.ToLocal(&raw)) { CHECK(!error.IsEmpty()); env->isolate()->ThrowException(error); return Nothing(); } - if (!key_data.ToLocal(&raw)) return Nothing(); if (target->Set( env->context(), @@ -277,8 +164,8 @@ KeyObjectData ImportJWKSecretKey(Environment* env, Local jwk) { } static_assert(String::kMaxLength <= INT_MAX); - auto key_data = ByteSource::FromEncodedString(env, key.As()); - return KeyObjectData::CreateSecret(std::move(key_data)); + return KeyObjectData::CreateSecret( + ByteSource::FromEncodedString(env, key.As())); } Maybe ExportJWKAsymmetricKey(Environment* env, @@ -290,7 +177,8 @@ Maybe ExportJWKAsymmetricKey(Environment* env, if (handleRsaPss) return ExportJWKRsaKey(env, key, target); break; } - case EVP_PKEY_RSA: return ExportJWKRsaKey(env, key, target); + case EVP_PKEY_RSA: + return ExportJWKRsaKey(env, key, target); case EVP_PKEY_EC: return ExportJWKEcKey(env, key, target); case EVP_PKEY_ED25519: @@ -299,7 +187,8 @@ Maybe ExportJWKAsymmetricKey(Environment* env, // Fall through case EVP_PKEY_X25519: // Fall through - case EVP_PKEY_X448: return ExportJWKEdKey(env, key, target); + case EVP_PKEY_X448: + return ExportJWKEdKey(env, key, target); } THROW_ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(env); return Nothing(); @@ -354,38 +243,22 @@ Maybe GetAsymmetricKeyDetail(Environment* env, KeyObjectData TryParsePrivateKey( Environment* env, - const PrivateKeyEncodingConfig& config, + const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config, const ncrypto::Buffer& buffer) { - std::optional> maybePassphrase = std::nullopt; - if (config.passphrase_.get() != nullptr) { - maybePassphrase = ncrypto::Buffer{ - .data = const_cast(config.passphrase_->data()), - .len = config.passphrase_->size(), - }; + auto res = EVPKeyPointer::TryParsePrivateKey(config, buffer); + if (res) { + return KeyObjectData::CreateAsymmetric(KeyType::kKeyTypePrivate, + std::move(res.value)); } - auto res = EVPKeyPointer::TryParsePrivateKey( - static_cast(config.format_), - static_cast( - config.type_.value_or(kKeyEncodingPKCS8)), - std::move(maybePassphrase), - buffer); - - if (!res) { - if (res.error.value() == EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { - THROW_ERR_MISSING_PASSPHRASE(env, - "Passphrase required for encrypted key"); - return {}; - } + if (res.error.value() == EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { + THROW_ERR_MISSING_PASSPHRASE(env, "Passphrase required for encrypted key"); + } else { ThrowCryptoError( env, res.openssl_error.value_or(0), "Failed to read private key"); - return {}; } - - return KeyObjectData::CreateAsymmetric(KeyType::kKeyTypePrivate, - std::move(res.value)); + return {}; } -} // namespace // This maps true to JustVoid and false to Nothing(). static inline Maybe NothingIfFalse(bool b) { @@ -408,58 +281,61 @@ Maybe ExportJWKInner(Environment* env, UNREACHABLE(); } } +} // namespace Maybe KeyObjectData::ToEncodedPublicKey( Environment* env, - const PublicKeyEncodingConfig& config, + const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config, Local* out) { CHECK(key_type_ != KeyType::kKeyTypeSecret); - if (config.output_key_object_) { + if (config.output_key_object) { // Note that this has the downside of containing sensitive data of the // private key. return NothingIfFalse( KeyObjectHandle::Create(env, addRefWithType(KeyType::kKeyTypePublic)) .ToLocal(out)); - } else if (config.format_ == kKeyFormatJWK) { + } else if (config.format == ncrypto::EVPKeyPointer::PKFormatType::JWK) { *out = Object::New(env->isolate()); return ExportJWKInner( env, addRefWithType(KeyType::kKeyTypePublic), *out, false); } return NothingIfFalse( - WritePublicKey(env, GetAsymmetricKey().get(), config).ToLocal(out)); + WritePublicKey(env, GetAsymmetricKey(), config).ToLocal(out)); } Maybe KeyObjectData::ToEncodedPrivateKey( Environment* env, - const PrivateKeyEncodingConfig& config, + const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config, Local* out) { CHECK(key_type_ != KeyType::kKeyTypeSecret); - if (config.output_key_object_) { + if (config.output_key_object) { return NothingIfFalse( KeyObjectHandle::Create(env, addRefWithType(KeyType::kKeyTypePrivate)) .ToLocal(out)); - } else if (config.format_ == kKeyFormatJWK) { + } else if (config.format == ncrypto::EVPKeyPointer::PKFormatType::JWK) { *out = Object::New(env->isolate()); return ExportJWKInner( env, addRefWithType(KeyType::kKeyTypePrivate), *out, false); } return NothingIfFalse( - WritePrivateKey(env, GetAsymmetricKey().get(), config).ToLocal(out)); + WritePrivateKey(env, GetAsymmetricKey(), config).ToLocal(out)); } -NonCopyableMaybe +Maybe KeyObjectData::GetPrivateKeyEncodingFromJs( const FunctionCallbackInfo& args, unsigned int* offset, KeyEncodingContext context) { Environment* env = Environment::GetCurrent(args); - PrivateKeyEncodingConfig result; - GetKeyFormatAndTypeFromJs(&result, args, offset, context); + ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config; + if (!GetKeyFormatAndTypeFromJs(args, offset, context).To(&config)) { + return Nothing(); + } - if (result.output_key_object_) { + if (config.output_key_object) { if (context != kKeyContextInput) (*offset)++; } else { @@ -467,44 +343,43 @@ KeyObjectData::GetPrivateKeyEncodingFromJs( if (context != kKeyContextInput) { if (args[*offset]->IsString()) { Utf8Value cipher_name(env->isolate(), args[*offset]); - result.cipher_ = EVP_get_cipherbyname(*cipher_name); - if (result.cipher_ == nullptr) { + config.cipher = EVP_get_cipherbyname(*cipher_name); + if (config.cipher == nullptr) { THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env); - return NonCopyableMaybe(); + return Nothing(); } needs_passphrase = true; } else { CHECK(args[*offset]->IsNullOrUndefined()); - result.cipher_ = nullptr; + config.cipher = nullptr; } (*offset)++; } if (IsAnyBufferSource(args[*offset])) { - CHECK_IMPLIES(context != kKeyContextInput, result.cipher_ != nullptr); + CHECK_IMPLIES(context != kKeyContextInput, config.cipher != nullptr); ArrayBufferOrViewContents passphrase(args[*offset]); if (!passphrase.CheckSizeInt32()) [[unlikely]] { THROW_ERR_OUT_OF_RANGE(env, "passphrase is too big"); - return NonCopyableMaybe(); + return Nothing(); } - result.passphrase_ = NonCopyableMaybe( - passphrase.ToNullTerminatedCopy()); + config.passphrase = passphrase.ToDataPointer(); } else { CHECK(args[*offset]->IsNullOrUndefined() && !needs_passphrase); } } (*offset)++; - return NonCopyableMaybe(std::move(result)); + return Just( + std::move(config)); } -PublicKeyEncodingConfig KeyObjectData::GetPublicKeyEncodingFromJs( +Maybe +KeyObjectData::GetPublicKeyEncodingFromJs( const FunctionCallbackInfo& args, unsigned int* offset, KeyEncodingContext context) { - PublicKeyEncodingConfig result; - GetKeyFormatAndTypeFromJs(&result, args, offset, context); - return result; + return GetKeyFormatAndTypeFromJs(args, offset, context); } KeyObjectData KeyObjectData::GetPrivateKeyFromJs( @@ -513,14 +388,17 @@ KeyObjectData KeyObjectData::GetPrivateKeyFromJs( bool allow_key_object) { if (args[*offset]->IsString() || IsAnyBufferSource(args[*offset])) { Environment* env = Environment::GetCurrent(args); - ByteSource key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]); - NonCopyableMaybe config = - GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput); + auto key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]); + + ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config; + if (!GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput) + .To(&config)) { + return {}; + } - if (config.IsEmpty()) return {}; return TryParsePrivateKey( env, - config.Release(), + config, ncrypto::Buffer{ .data = reinterpret_cast(key.data()), .len = key.size(), @@ -544,70 +422,62 @@ KeyObjectData KeyObjectData::GetPublicOrPrivateKeyFromJs( THROW_ERR_OUT_OF_RANGE(env, "keyData is too big"); return {}; } - NonCopyableMaybe config_ = - KeyObjectData::GetPrivateKeyEncodingFromJs( - args, offset, kKeyContextInput); - if (config_.IsEmpty()) return {}; - PrivateKeyEncodingConfig config = config_.Release(); + + ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config; + if (!KeyObjectData::GetPrivateKeyEncodingFromJs( + args, offset, kKeyContextInput) + .To(&config)) { + return {}; + } ncrypto::Buffer buffer = { .data = reinterpret_cast(data.data()), .len = data.size(), }; - std::optional> maybePassphrase = std::nullopt; - if (config.passphrase_.get() != nullptr) { - maybePassphrase = ncrypto::Buffer{ - .data = const_cast(config.passphrase_->data()), - .len = config.passphrase_->size(), - }; - } - - if (config.format_ == kKeyFormatPEM) { + if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { // For PEM, we can easily determine whether it is a public or private key // by looking for the respective PEM tags. auto res = EVPKeyPointer::TryParsePublicKeyPEM(buffer); - if (!res) { - if (res.error.value() == EVPKeyPointer::PKParseError::NOT_RECOGNIZED) { - return TryParsePrivateKey(env, config, buffer); - } - ThrowCryptoError(env, - res.openssl_error.value_or(0), - "Failed to read asymmetric key"); - return {}; + if (res) { + return CreateAsymmetric(kKeyTypePublic, std::move(res.value)); } - return CreateAsymmetric(kKeyTypePublic, std::move(res.value)); + + if (res.error.value() == EVPKeyPointer::PKParseError::NOT_RECOGNIZED) { + return TryParsePrivateKey(env, config, buffer); + } + ThrowCryptoError( + env, res.openssl_error.value_or(0), "Failed to read asymmetric key"); + return {}; } // For DER, the type determines how to parse it. SPKI, PKCS#8 and SEC1 are // easy, but PKCS#1 can be a public key or a private key. - bool is_public = ([&] { - switch (config.type_.value()) { - case kKeyEncodingPKCS1: + static const auto is_public = [](const auto& config, + const auto& buffer) -> bool { + switch (config.type) { + case ncrypto::EVPKeyPointer::PKEncodingType::PKCS1: return !EVPKeyPointer::IsRSAPrivateKey(buffer); - case kKeyEncodingSPKI: + case ncrypto::EVPKeyPointer::PKEncodingType::SPKI: return true; - case kKeyEncodingPKCS8: + case ncrypto::EVPKeyPointer::PKEncodingType::PKCS8: return false; - case kKeyEncodingSEC1: + case ncrypto::EVPKeyPointer::PKEncodingType::SEC1: return false; default: UNREACHABLE("Invalid key encoding type"); } - })(); - - if (is_public) { - auto res = EVPKeyPointer::TryParsePublicKey( - static_cast(config.format_), - static_cast(config.type_.value()), - buffer); - if (!res) { - ThrowCryptoError(env, - res.openssl_error.value_or(0), - "Failed to read asymmetric key"); - return {}; + }; + + if (is_public(config, buffer)) { + auto res = EVPKeyPointer::TryParsePublicKey(config, buffer); + if (res) { + return CreateAsymmetric(KeyType::kKeyTypePublic, std::move(res.value)); } - return CreateAsymmetric(KeyType::kKeyTypePublic, std::move(res.value)); + + ThrowCryptoError( + env, res.openssl_error.value_or(0), "Failed to read asymmetric key"); + return {}; } return TryParsePrivateKey(env, config, buffer); @@ -1104,20 +974,25 @@ void KeyObjectHandle::Export(const FunctionCallbackInfo& args) { result = key->ExportSecretKey(); } else if (type == kKeyTypePublic) { unsigned int offset = 0; - PublicKeyEncodingConfig config = KeyObjectData::GetPublicKeyEncodingFromJs( - args, &offset, kKeyContextExport); + ncrypto::EVPKeyPointer::PublicKeyEncodingConfig config; + if (!KeyObjectData::GetPublicKeyEncodingFromJs( + args, &offset, kKeyContextExport) + .To(&config)) { + return; + } CHECK_EQ(offset, static_cast(args.Length())); result = key->ExportPublicKey(config); } else { CHECK_EQ(type, kKeyTypePrivate); unsigned int offset = 0; - NonCopyableMaybe config = - KeyObjectData::GetPrivateKeyEncodingFromJs( - args, &offset, kKeyContextExport); - if (config.IsEmpty()) + ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config; + if (!KeyObjectData::GetPrivateKeyEncodingFromJs( + args, &offset, kKeyContextExport) + .To(&config)) { return; + } CHECK_EQ(offset, static_cast(args.Length())); - result = key->ExportPrivateKey(config.Release()); + result = key->ExportPrivateKey(config); } if (!result.IsEmpty()) @@ -1131,13 +1006,13 @@ MaybeLocal KeyObjectHandle::ExportSecretKey() const { } MaybeLocal KeyObjectHandle::ExportPublicKey( - const PublicKeyEncodingConfig& config) const { - return WritePublicKey(env(), data_.GetAsymmetricKey().get(), config); + const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const { + return WritePublicKey(env(), data_.GetAsymmetricKey(), config); } MaybeLocal KeyObjectHandle::ExportPrivateKey( - const PrivateKeyEncodingConfig& config) const { - return WritePrivateKey(env(), data_.GetAsymmetricKey().get(), config); + const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config) const { + return WritePrivateKey(env(), data_.GetAsymmetricKey(), config); } void KeyObjectHandle::ExportJWK( @@ -1292,11 +1167,25 @@ void Initialize(Environment* env, Local target) { FIXED_ONE_BYTE_STRING(env->isolate(), "KeyObjectHandle"), KeyObjectHandle::Initialize(env)).Check(); + constexpr int kKeyEncodingPKCS1 = + static_cast(ncrypto::EVPKeyPointer::PKEncodingType::PKCS1); + constexpr int kKeyEncodingPKCS8 = + static_cast(ncrypto::EVPKeyPointer::PKEncodingType::PKCS8); + constexpr int kKeyEncodingSPKI = + static_cast(ncrypto::EVPKeyPointer::PKEncodingType::SPKI); + constexpr int kKeyEncodingSEC1 = + static_cast(ncrypto::EVPKeyPointer::PKEncodingType::SEC1); + constexpr int kKeyFormatDER = + static_cast(ncrypto::EVPKeyPointer::PKFormatType::DER); + constexpr int kKeyFormatPEM = + static_cast(ncrypto::EVPKeyPointer::PKFormatType::PEM); + constexpr int kKeyFormatJWK = + static_cast(ncrypto::EVPKeyPointer::PKFormatType::JWK); + NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatRaw); NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatPKCS8); NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatSPKI); NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatJWK); - NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519); NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448); NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519); diff --git a/src/crypto/crypto_keys.h b/src/crypto/crypto_keys.h index fd7ff00b21c8e2..dfc817b287de0f 100644 --- a/src/crypto/crypto_keys.h +++ b/src/crypto/crypto_keys.h @@ -18,24 +18,6 @@ namespace node { namespace crypto { -// TODO(@jasnell): These static casts are temporarily while this code -// is being shifted over into ncrypto -enum PKEncodingType { - // RSAPublicKey / RSAPrivateKey according to PKCS#1. - kKeyEncodingPKCS1 = static_cast(EVPKeyPointer::PKEncodingType::PKCS1), - // PrivateKeyInfo or EncryptedPrivateKeyInfo according to PKCS#8. - kKeyEncodingPKCS8 = static_cast(EVPKeyPointer::PKEncodingType::PKCS8), - // SubjectPublicKeyInfo according to X.509. - kKeyEncodingSPKI = static_cast(EVPKeyPointer::PKEncodingType::SPKI), - // ECPrivateKey according to SEC1. - kKeyEncodingSEC1 = static_cast(EVPKeyPointer::PKEncodingType::SEC1), -}; - -enum PKFormatType { - kKeyFormatDER = static_cast(EVPKeyPointer::PKFormatType::DER), - kKeyFormatPEM = static_cast(EVPKeyPointer::PKFormatType::PEM), - kKeyFormatJWK = static_cast(EVPKeyPointer::PKFormatType::JWK), -}; enum KeyType { kKeyTypeSecret, @@ -58,22 +40,6 @@ enum class ParseKeyResult { kParseKeyOk, }; -struct AsymmetricKeyEncodingConfig { - bool output_key_object_ = false; - PKFormatType format_ = kKeyFormatDER; - std::optional type_ = std::nullopt; -}; - -using PublicKeyEncodingConfig = AsymmetricKeyEncodingConfig; - -struct PrivateKeyEncodingConfig : public AsymmetricKeyEncodingConfig { - const EVP_CIPHER* cipher_; - // The ByteSource alone is not enough to distinguish between "no passphrase" - // and a zero-length passphrase (which can be a null pointer), therefore, we - // use a NonCopyableMaybe. - NonCopyableMaybe passphrase_; -}; - // Objects of this class can safely be shared among threads. class KeyObjectData final : public MemoryRetainer { public: @@ -99,10 +65,10 @@ class KeyObjectData final : public MemoryRetainer { Mutex& mutex() const; - static PublicKeyEncodingConfig GetPublicKeyEncodingFromJs( - const v8::FunctionCallbackInfo& args, - unsigned int* offset, - KeyEncodingContext context); + static v8::Maybe + GetPublicKeyEncodingFromJs(const v8::FunctionCallbackInfo& args, + unsigned int* offset, + KeyEncodingContext context); static KeyObjectData GetPrivateKeyFromJs( const v8::FunctionCallbackInfo& args, @@ -112,18 +78,20 @@ class KeyObjectData final : public MemoryRetainer { static KeyObjectData GetPublicOrPrivateKeyFromJs( const v8::FunctionCallbackInfo& args, unsigned int* offset); - static NonCopyableMaybe GetPrivateKeyEncodingFromJs( - const v8::FunctionCallbackInfo& args, - unsigned int* offset, - KeyEncodingContext context); + static v8::Maybe + GetPrivateKeyEncodingFromJs(const v8::FunctionCallbackInfo& args, + unsigned int* offset, + KeyEncodingContext context); - v8::Maybe ToEncodedPublicKey(Environment* env, - const PublicKeyEncodingConfig& config, - v8::Local* out); + v8::Maybe ToEncodedPublicKey( + Environment* env, + const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config, + v8::Local* out); - v8::Maybe ToEncodedPrivateKey(Environment* env, - const PrivateKeyEncodingConfig& config, - v8::Local* out); + v8::Maybe ToEncodedPrivateKey( + Environment* env, + const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config, + v8::Local* out); inline KeyObjectData addRef() const { return KeyObjectData(key_type_, mutex_, data_); @@ -204,9 +172,9 @@ class KeyObjectHandle : public BaseObject { v8::MaybeLocal ExportSecretKey() const; v8::MaybeLocal ExportPublicKey( - const PublicKeyEncodingConfig& config) const; + const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const; v8::MaybeLocal ExportPrivateKey( - const PrivateKeyEncodingConfig& config) const; + const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config) const; KeyObjectHandle(Environment* env, v8::Local wrap); diff --git a/src/crypto/crypto_util.h b/src/crypto/crypto_util.h index 922e77091d7217..5c717c6fdb0fc4 100644 --- a/src/crypto/crypto_util.h +++ b/src/crypto/crypto_util.h @@ -689,12 +689,22 @@ class ArrayBufferOrViewContents { return std::move(buf).release(size()); } + inline ncrypto::DataPointer ToDataPointer() const { + if (empty()) return {}; + if (auto dp = ncrypto::DataPointer::Alloc(size())) { + memcpy(dp.get(), data(), size()); + return dp; + } + return {}; + } + template void CopyTo(M* dest, size_t len) const { static_assert(sizeof(M) == 1, "sizeof(M) must equal 1"); len = std::min(len, size()); - if (len > 0 && data() != nullptr) + if (len > 0 && data() != nullptr) { memcpy(dest, data(), len); + } } private: