#include "crypto/crypto_sig.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_ec.h"
#include "crypto/crypto_hash.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "openssl/ec.h"
#if NCRYPTO_USE_OPENSSL_PROVIDER
#include <openssl/core_names.h>
#include <openssl/evp.h>
#endif
#include "threadpoolwork-inl.h"
#include "v8.h"
namespace node {
using ncrypto::BignumPointer;
using ncrypto::ClearErrorOnReturn;
using ncrypto::DataPointer;
using ncrypto::Digest;
using ncrypto::ECDSASigPointer;
using ncrypto::EVPKeyCtxPointer;
using ncrypto::EVPKeyPointer;
using ncrypto::EVPMDCtxPointer;
using v8::ArrayBuffer;
using v8::BackingStore;
using v8::BackingStoreInitializationMode;
using v8::Boolean;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Int32;
using v8::Isolate;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
namespace {
int GetPaddingFromJS(const EVPKeyPointer& key, Local<Value> val) {
if (!val->IsUndefined()) [[likely]] {
CHECK(val->IsInt32());
return val.As<Int32>()->Value();
}
return key.getDefaultSignPadding();
}
std::optional<int> GetSaltLenFromJS(Local<Value> val) {
std::optional<int> salt_len;
if (!val->IsUndefined()) [[likely]] {
CHECK(val->IsInt32());
salt_len = val.As<Int32>()->Value();
}
return salt_len;
}
DSASigEnc GetDSASigEncFromJS(Local<Value> val) {
CHECK(val->IsInt32());
int i = val.As<Int32>()->Value();
if (i < 0 || i >= static_cast<int>(DSASigEnc::Invalid)) [[unlikely]] {
return DSASigEnc::Invalid;
}
return static_cast<DSASigEnc>(val.As<Int32>()->Value());
}
bool ApplyRSAOptions(const EVPKeyPointer& pkey,
EVP_PKEY_CTX* pkctx,
int padding,
std::optional<int> salt_len) {
if (pkey.isRsaVariant()) {
return EVPKeyCtxPointer::setRsaPadding(pkctx, padding, salt_len);
}
return true;
}
std::unique_ptr<BackingStore> Node_SignFinal(Environment* env,
EVPMDCtxPointer&& mdctx,
const EVPKeyPointer& pkey,
int padding,
std::optional<int> pss_salt_len) {
auto data = mdctx.digestFinal(mdctx.getExpectedSize());
if (!data) [[unlikely]]
return nullptr;
auto sig = ArrayBuffer::NewBackingStore(env->isolate(), pkey.size());
ncrypto::Buffer<unsigned char> sig_buf{
.data = static_cast<unsigned char*>(sig->Data()),
.len = pkey.size(),
};
EVPKeyCtxPointer pkctx = pkey.newCtx();
if (pkctx.initForSign() > 0 &&
ApplyRSAOptions(pkey, pkctx.get(), padding, pss_salt_len) &&
pkctx.setSignatureMd(mdctx) && pkctx.signInto(data, &sig_buf))
[[likely]] {
CHECK_LE(sig_buf.len, sig->ByteLength());
if (sig_buf.len < sig->ByteLength()) {
auto new_sig = ArrayBuffer::NewBackingStore(
env->isolate(),
sig_buf.len,
BackingStoreInitializationMode::kUninitialized);
if (sig_buf.len > 0) [[likely]] {
memcpy(new_sig->Data(), sig->Data(), sig_buf.len);
}
sig = std::move(new_sig);
}
return sig;
}
return nullptr;
}
// Returns the maximum size of each of the integers (r, s) of the DSA signature.
std::unique_ptr<BackingStore> ConvertSignatureToP1363(
Environment* env,
const EVPKeyPointer& pkey,
std::unique_ptr<BackingStore>&& signature) {
uint32_t n = pkey.getBytesOfRS().value_or(kNoDsaSignature);
if (n == kNoDsaSignature) return std::move(signature);
auto buf = ArrayBuffer::NewBackingStore(
env->isolate(), 2 * n, BackingStoreInitializationMode::kUninitialized);
ncrypto::Buffer<const unsigned char> sig_buffer{
.data = static_cast<const unsigned char*>(signature->Data()),
.len = signature->ByteLength(),
};
if (!ncrypto::extractP1363(
sig_buffer, static_cast<unsigned char*>(buf->Data()), n)) {
return std::move(signature);
}
return buf;
}
// Returns the maximum size of each of the integers (r, s) of the DSA signature.
ByteSource ConvertSignatureToP1363(Environment* env,
const EVPKeyPointer& pkey,
const ByteSource& signature) {
unsigned int n = pkey.getBytesOfRS().value_or(kNoDsaSignature);
if (n == kNoDsaSignature) [[unlikely]]
return {};
auto data = DataPointer::Alloc(n * 2);
if (!data) [[unlikely]]
return {};
unsigned char* out = static_cast<unsigned char*>(data.get());
// Extracting the signature may not actually use all of the allocated space.
// We need to ensure that the buffer is zeroed out before use.
data.zero();
if (!ncrypto::extractP1363(signature, out, n)) [[unlikely]] {
return {};
}
return ByteSource::Allocated(data.release());
}
ByteSource ConvertSignatureToDER(const EVPKeyPointer& pkey, ByteSource&& out) {
unsigned int n = pkey.getBytesOfRS().value_or(kNoDsaSignature);
if (n == kNoDsaSignature) return std::move(out);
const unsigned char* sig_data = out.data<unsigned char>();
if (out.size() != 2 * n) return {};
auto asn1_sig = ECDSASigPointer::New();
CHECK(asn1_sig);
BignumPointer r(sig_data, n);
CHECK(r);
BignumPointer s(sig_data + n, n);
CHECK(s);
CHECK(asn1_sig.setParams(std::move(r), std::move(s)));
auto buf = asn1_sig.encode();
if (buf.len <= 0) [[unlikely]]
return {};
CHECK_NOT_NULL(buf.data);
return ByteSource::Allocated(buf);
}
void CheckThrow(Environment* env, SignBase::Error error) {
HandleScope scope(env->isolate());
switch (error) {
case SignBase::Error::UnknownDigest:
return THROW_ERR_CRYPTO_INVALID_DIGEST(env);
case SignBase::Error::NotInitialised:
return THROW_ERR_CRYPTO_INVALID_STATE(env, "Not initialised");
case SignBase::Error::MalformedSignature:
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Malformed signature");
case SignBase::Error::ContextUnsupported:
return THROW_ERR_CRYPTO_OPERATION_FAILED(
env, "Context parameter is unsupported");
case SignBase::Error::Init:
case SignBase::Error::Update:
case SignBase::Error::PrivateKey:
case SignBase::Error::PublicKey: {
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
if (err) return ThrowCryptoError(env, err);
switch (error) {
case SignBase::Error::Init:
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"EVP_SignInit_ex failed");
case SignBase::Error::Update:
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"EVP_SignUpdate failed");
case SignBase::Error::PrivateKey:
return THROW_ERR_CRYPTO_OPERATION_FAILED(
env, "PEM_read_bio_PrivateKey failed");
case SignBase::Error::PublicKey:
return THROW_ERR_CRYPTO_OPERATION_FAILED(
env, "PEM_read_bio_PUBKEY failed");
default:
ABORT();
}
}
case SignBase::Error::Ok:
return;
}
}
bool UseP1363Encoding(const EVPKeyPointer& key, const DSASigEnc dsa_encoding) {
return dsa_encoding == DSASigEnc::P1363 && key.isSigVariant();
}
bool CanUsePrehashedFallback(const EVPKeyPointer& key,
const Digest& digest,
bool has_context) {
if (!digest || has_context) return false;
if (key.isRsaVariant()) return true;
// SM2 digest signing first hashes the algorithm-specific Z value, so the
// lower-level prehashed sign/verify operation is not equivalent.
return key.isSigVariant() && !key.mayBeSM2();
}
ByteSource SignPrehashed(Environment* env,
const EVPKeyPointer& key,
const Digest& digest,
const ByteSource& input,
int padding,
std::optional<int> salt_length,
DSASigEnc dsa_encoding) {
EVPMDCtxPointer context = EVPMDCtxPointer::New();
if (!context || !context.digestInit(digest) || !context.digestUpdate(input))
[[unlikely]] {
return {};
}
auto data = context.digestFinal(context.getExpectedSize());
if (!data) [[unlikely]] {
return {};
}
EVPKeyCtxPointer pkctx = key.newCtx();
if (!pkctx || pkctx.initForSign() <= 0 ||
!ApplyRSAOptions(key, pkctx.get(), padding, salt_length) ||
!pkctx.setSignatureMd(context)) [[unlikely]] {
return {};
}
auto signature = pkctx.sign(data);
if (!signature) [[unlikely]] {
return {};
}
DCHECK(!signature.isSecure());
auto out = ByteSource::Allocated(signature.release());
if (UseP1363Encoding(key, dsa_encoding)) {
return ConvertSignatureToP1363(env, key, std::move(out));
}
return out;
}
bool VerifyPrehashed(const EVPKeyPointer& key,
const Digest& digest,
const ByteSource& input,
const ByteSource& signature,
int padding,
std::optional<int> salt_length) {
EVPMDCtxPointer context = EVPMDCtxPointer::New();
if (!context || !context.digestInit(digest) || !context.digestUpdate(input))
[[unlikely]] {
return false;
}
auto data = context.digestFinal(context.getExpectedSize());
if (!data) [[unlikely]] {
return false;
}
EVPKeyCtxPointer pkctx = key.newCtx();
return pkctx && pkctx.initForVerify() > 0 &&
ApplyRSAOptions(key, pkctx.get(), padding, salt_length) &&
pkctx.setSignatureMd(context) && pkctx.verify(signature, data);
}
} // namespace
SignBase::Error SignBase::Init(const char* digest) {
CHECK_NULL(mdctx_);
auto md = GetDigest(env(), digest);
if (!md) [[unlikely]]
return Error::UnknownDigest;
mdctx_ = EVPMDCtxPointer::New();
if (!mdctx_.digestInit(md)) [[unlikely]] {
mdctx_.reset();
return Error::Init;
}
return Error::Ok;
}
SignBase::Error SignBase::Update(const char* data, size_t len) {
if (mdctx_ == nullptr) [[unlikely]]
return Error::NotInitialised;
ncrypto::Buffer<const void> buf{
.data = data,
.len = len,
};
return mdctx_.digestUpdate(buf) ? Error::Ok : Error::Update;
}
SignBase::SignBase(Environment* env, Local<Object> wrap)
: BaseObject(env, wrap) {
MakeWeak();
}
void SignBase::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("mdctx", mdctx_ ? kSizeOf_EVP_MD_CTX : 0);
}
Sign::Sign(Environment* env, Local<Object> wrap) : SignBase(env, wrap) {}
void Sign::Initialize(Environment* env, Local<Object> target) {
Isolate* isolate = env->isolate();
Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New);
t->InstanceTemplate()->SetInternalFieldCount(Sign::kInternalFieldCount);
SetProtoMethod(isolate, t, "init", SignInit);
SetProtoMethod(isolate, t, "update", SignUpdate);
SetProtoMethod(isolate, t, "sign", SignFinal);
SetConstructorFunction(env->context(), target, "Sign", t);
SignJob::Initialize(env, target);
constexpr int kSignJobModeSign =
static_cast<int>(SignConfiguration::Mode::Sign);
constexpr int kSignJobModeVerify =
static_cast<int>(SignConfiguration::Mode::Verify);
constexpr auto kSigEncDER = DSASigEnc::DER;
constexpr auto kSigEncP1363 = DSASigEnc::P1363;
NODE_DEFINE_CONSTANT(target, kSignJobModeSign);
NODE_DEFINE_CONSTANT(target, kSignJobModeVerify);
NODE_DEFINE_CONSTANT(target, kSigEncDER);
NODE_DEFINE_CONSTANT(target, kSigEncP1363);
NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PSS_PADDING);
}
void Sign::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(New);
registry->Register(SignInit);
registry->Register(SignUpdate);
registry->Register(SignFinal);
SignJob::RegisterExternalReferences(registry);
}
void Sign::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
new Sign(env, args.This());
}
void Sign::SignInit(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Sign* sign;
ASSIGN_OR_RETURN_UNWRAP(&sign, args.This());
const node::Utf8Value sign_type(env->isolate(), args[0]);
crypto::CheckThrow(env, sign->Init(*sign_type));
}
void Sign::SignUpdate(const FunctionCallbackInfo<Value>& args) {
Decode<Sign>(args, [](Sign* sign, const FunctionCallbackInfo<Value>& args,
const char* data, size_t size) {
Environment* env = Environment::GetCurrent(args);
if (size > INT_MAX) [[unlikely]]
return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
Error err = sign->Update(data, size);
crypto::CheckThrow(sign->env(), err);
});
}
Sign::SignResult Sign::SignFinal(const EVPKeyPointer& pkey,
int padding,
std::optional<int> salt_len,
DSASigEnc dsa_sig_enc) {
if (!mdctx_) [[unlikely]] {
return SignResult(Error::NotInitialised);
}
EVPMDCtxPointer mdctx = std::move(mdctx_);
if (!pkey.validateDsaParameters()) {
return SignResult(Error::PrivateKey);
}
auto buffer =
Node_SignFinal(env(), std::move(mdctx), pkey, padding, salt_len);
Error error = buffer ? Error::Ok : Error::PrivateKey;
if (error == Error::Ok && dsa_sig_enc == DSASigEnc::P1363) {
buffer = ConvertSignatureToP1363(env(), pkey, std::move(buffer));
CHECK_NOT_NULL(buffer->Data());
}
return SignResult(error, std::move(buffer));
}
void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Sign* sign;
ASSIGN_OR_RETURN_UNWRAP(&sign, args.This());
ClearErrorOnReturn clear_error_on_return;
unsigned int offset = 0;
auto data = KeyObjectData::GetPrivateKeyFromJs(args, &offset, true);
if (!data) [[unlikely]]
return;
const auto& key = data.GetAsymmetricKey();
if (!key) [[unlikely]]
return;
const auto* algorithm = key.getAlgorithm();
if (algorithm != nullptr && algorithm->isOneShot()) [[unlikely]] {
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
return;
}
int padding = GetPaddingFromJS(key, args[offset]);
std::optional<int> salt_len = GetSaltLenFromJS(args[offset + 1]);
DSASigEnc dsa_sig_enc = GetDSASigEncFromJS(args[offset + 2]);
if (dsa_sig_enc == DSASigEnc::Invalid) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "invalid signature encoding");
return;
}
SignResult ret = sign->SignFinal(key, padding, salt_len, dsa_sig_enc);
if (ret.error != Error::Ok) [[unlikely]] {
return crypto::CheckThrow(env, ret.error);
}
auto ab = ArrayBuffer::New(env->isolate(), std::move(ret.signature));
args.GetReturnValue().Set(
Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>()));
}
Verify::Verify(Environment* env, Local<Object> wrap) : SignBase(env, wrap) {}
void Verify::Initialize(Environment* env, Local<Object> target) {
Isolate* isolate = env->isolate();
Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New);
t->InstanceTemplate()->SetInternalFieldCount(Verify::kInternalFieldCount);
SetProtoMethod(isolate, t, "init", VerifyInit);
SetProtoMethod(isolate, t, "update", VerifyUpdate);
SetProtoMethod(isolate, t, "verify", VerifyFinal);
SetConstructorFunction(env->context(), target, "Verify", t);
}
void Verify::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(New);
registry->Register(VerifyInit);
registry->Register(VerifyUpdate);
registry->Register(VerifyFinal);
}
void Verify::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
new Verify(env, args.This());
}
void Verify::VerifyInit(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Verify* verify;
ASSIGN_OR_RETURN_UNWRAP(&verify, args.This());
const node::Utf8Value verify_type(env->isolate(), args[0]);
crypto::CheckThrow(env, verify->Init(*verify_type));
}
void Verify::VerifyUpdate(const FunctionCallbackInfo<Value>& args) {
Decode<Verify>(args, [](Verify* verify,
const FunctionCallbackInfo<Value>& args,
const char* data, size_t size) {
Environment* env = Environment::GetCurrent(args);
if (size > INT_MAX) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
}
Error err = verify->Update(data, size);
crypto::CheckThrow(verify->env(), err);
});
}
SignBase::Error Verify::VerifyFinal(const EVPKeyPointer& pkey,
const ByteSource& sig,
int padding,
std::optional<int> saltlen,
bool* verify_result) {
if (!mdctx_) [[unlikely]]
return Error::NotInitialised;
*verify_result = false;
EVPMDCtxPointer mdctx = std::move(mdctx_);
auto data = mdctx.digestFinal(mdctx.getExpectedSize());
if (!data) [[unlikely]]
return Error::PublicKey;
EVPKeyCtxPointer pkctx = pkey.newCtx();
if (pkctx) [[likely]] {
const int init_ret = pkctx.initForVerify();
if (init_ret == -2) [[unlikely]]
return Error::PublicKey;
if (init_ret > 0 && ApplyRSAOptions(pkey, pkctx.get(), padding, saltlen) &&
pkctx.setSignatureMd(mdctx)) {
*verify_result = pkctx.verify(sig, data);
}
}
return Error::Ok;
}
void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ClearErrorOnReturn clear_error_on_return;
Verify* verify;
ASSIGN_OR_RETURN_UNWRAP(&verify, args.This());
unsigned int offset = 0;
auto data = KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &offset);
if (!data) [[unlikely]]
return;
const auto& key = data.GetAsymmetricKey();
if (!key) [[unlikely]]
return;
const auto* algorithm = key.getAlgorithm();
if (algorithm != nullptr && algorithm->isOneShot()) [[unlikely]] {
THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env);
return;
}
ArrayBufferOrViewContents<char> hbuf(args[offset]);
if (!hbuf.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "buffer is too big");
}
int padding = GetPaddingFromJS(key, args[offset + 1]);
std::optional<int> salt_len = GetSaltLenFromJS(args[offset + 2]);
DSASigEnc dsa_sig_enc = GetDSASigEncFromJS(args[offset + 3]);
if (dsa_sig_enc == DSASigEnc::Invalid) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "invalid signature encoding");
return;
}
ByteSource signature = hbuf.ToByteSource();
if (dsa_sig_enc == DSASigEnc::P1363) {
signature = ConvertSignatureToDER(key, hbuf.ToByteSource());
if (signature.data() == nullptr) [[unlikely]] {
return crypto::CheckThrow(env, Error::MalformedSignature);
}
}
bool verify_result;
Error err =
verify->VerifyFinal(key, signature, padding, salt_len, &verify_result);
if (err != Error::Ok) [[unlikely]]
return crypto::CheckThrow(env, err);
args.GetReturnValue().Set(verify_result);
}
SignConfiguration::SignConfiguration(SignConfiguration&& other) noexcept
: mode(other.mode),
key(std::move(other.key)),
data(std::move(other.data)),
signature(std::move(other.signature)),
digest(other.digest),
flags(other.flags),
padding(other.padding),
salt_length(other.salt_length),
dsa_encoding(other.dsa_encoding),
context_string(std::move(other.context_string)) {}
SignConfiguration& SignConfiguration::operator=(
SignConfiguration&& other) noexcept {
if (&other == this) return *this;
this->~SignConfiguration();
return *new (this) SignConfiguration(std::move(other));
}
void SignConfiguration::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("key", key);
tracker->TraitTrackInline(data, "data");
tracker->TraitTrackInline(signature, "signature");
tracker->TraitTrackInline(context_string, "context_string");
}
Maybe<void> SignTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
SignConfiguration* params) {
ClearErrorOnReturn clear_error_on_return;
Environment* env = Environment::GetCurrent(args);
CHECK(args[offset]->IsUint32()); // Sign Mode
params->mode =
static_cast<SignConfiguration::Mode>(args[offset].As<Uint32>()->Value());
unsigned int keyParamOffset = offset + 1;
if (params->mode == SignConfiguration::Mode::Verify) {
auto data =
KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &keyParamOffset);
if (!data) return Nothing<void>();
params->key = std::move(data);
} else {
auto data = KeyObjectData::GetPrivateKeyFromJs(args, &keyParamOffset, true);
if (!data) return Nothing<void>();
params->key = std::move(data);
}
ArrayBufferOrViewContents<char> data(args[offset + 6]);
if (!data.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "data is too big");
return Nothing<void>();
}
params->data = IsCryptoJobAsync(mode) ? data.ToCopy() : data.ToByteSource();
if (args[offset + 7]->IsString()) {
Utf8Value digest(env->isolate(), args[offset + 7]);
params->digest = GetDigestForJob(env, mode, *digest);
if (!params->digest) [[unlikely]] {
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", digest);
return Nothing<void>();
}
}
if (args[offset + 8]->IsInt32()) { // Salt length
params->flags |= SignConfiguration::kHasSaltLength;
params->salt_length =
GetSaltLenFromJS(args[offset + 8]).value_or(params->salt_length);
}
if (args[offset + 9]->IsUint32()) { // Padding
params->flags |= SignConfiguration::kHasPadding;
params->padding =
GetPaddingFromJS(params->key.GetAsymmetricKey(), args[offset + 9]);
}
if (args[offset + 10]->IsUint32()) { // DSA Encoding
params->dsa_encoding = GetDSASigEncFromJS(args[offset + 10]);
if (params->dsa_encoding == DSASigEnc::Invalid) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "invalid signature encoding");
return Nothing<void>();
}
}
if (!args[offset + 11]->IsUndefined()) { // Context string
ArrayBufferOrViewContents<char> context_string(args[offset + 11]);
if (context_string.size() > 255) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "context string must be at most 255 bytes");
return Nothing<void>();
}
params->flags |= SignConfiguration::kHasContextString;
params->context_string = IsCryptoJobAsync(mode)
? context_string.ToCopy()
: context_string.ToByteSource();
}
if (params->mode == SignConfiguration::Mode::Verify) {
ArrayBufferOrViewContents<char> signature(args[offset + 12]);
if (!signature.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "signature is too big");
return Nothing<void>();
}
// If this is an EC key (assuming ECDSA) we need to convert the
// the signature from WebCrypto format into DER format...
const auto& akey = params->key.GetAsymmetricKey();
if (UseP1363Encoding(akey, params->dsa_encoding)) {
params->signature = ConvertSignatureToDER(akey, signature.ToByteSource());
} else {
params->signature = IsCryptoJobAsync(mode) ? signature.ToCopy()
: signature.ToByteSource();
}
}
return JustVoid();
}
bool SignTraits::DeriveBits(Environment* env,
const SignConfiguration& params,
ByteSource* out,
CryptoJobMode mode,
CryptoErrorStore* errors) {
const auto& key = params.key.GetAsymmetricKey();
bool has_context = (params.flags & SignConfiguration::kHasContextString &&
params.context_string.size() > 0);
if (has_context && !key.supportsContextString()) {
errors->Insert(NodeCryptoError::CONTEXT_UNSUPPORTED);
errors->SetNodeErrorCode("ERR_CRYPTO_OPERATION_FAILED");
return false;
}
int padding = params.padding;
if (!(params.flags & SignConfiguration::kHasPadding)) {
padding = key.getDefaultSignPadding();
}
std::optional<int> salt_length =
params.flags & SignConfiguration::kHasSaltLength
? std::optional<int>(params.salt_length)
: std::nullopt;
auto context = EVPMDCtxPointer::New();
if (!context) [[unlikely]]
return false;
auto ctx = ([&] {
if (has_context) {
ncrypto::Buffer<const unsigned char> context_buf{
.data = params.context_string.data<unsigned char>(),
.len = params.context_string.size(),
};
switch (params.mode) {
case SignConfiguration::Mode::Sign:
return context.signInitWithContext(key, params.digest, context_buf);
case SignConfiguration::Mode::Verify:
return context.verifyInitWithContext(key, params.digest, context_buf);
}
} else {
switch (params.mode) {
case SignConfiguration::Mode::Sign:
return context.signInit(key, params.digest);
case SignConfiguration::Mode::Verify:
return context.verifyInit(key, params.digest);
}
}
UNREACHABLE();
})();
if (!ctx.has_value()) [[unlikely]] {
return false;
}
if (!ApplyRSAOptions(key, *ctx, padding, salt_length)) {
return false;
}
switch (params.mode) {
case SignConfiguration::Mode::Sign: {
const auto* algorithm = key.getAlgorithm();
if (algorithm != nullptr && algorithm->isOneShot()) {
auto data = context.signOneShot(params.data);
if (!data) [[unlikely]] {
return false;
}
DCHECK(!data.isSecure());
*out = ByteSource::Allocated(data.release());
} else {
auto data = context.sign(params.data);
// Only evaluated on the failure path: CanUsePrehashedFallback() has to
// reconstruct EC key material to detect SM2, which is far too
// expensive to pay for on every successful sign.
if (!data && CanUsePrehashedFallback(key, params.digest, has_context)) {
*out = SignPrehashed(env,
key,
params.digest,
params.data,
padding,
salt_length,
params.dsa_encoding);
return static_cast<bool>(*out);
}
if (!data) [[unlikely]] {
return false;
}
DCHECK(!data.isSecure());
auto bs = ByteSource::Allocated(data.release());
if (UseP1363Encoding(key, params.dsa_encoding)) {
*out = ConvertSignatureToP1363(env, key, std::move(bs));
} else {
*out = std::move(bs);
}
}
break;
}
case SignConfiguration::Mode::Verify: {
auto buf = DataPointer::Alloc(1);
static_cast<char*>(buf.get())[0] = 0;
// EVP_DigestVerify() documents 0 as a verification mismatch. In its
// Update/Final path, it maps a failed EVP_DigestVerifyUpdate() to -1.
// Some providers fail that combined operation but support raw
// verification of a precomputed digest, so only retry negative results.
// Retrying 0 would perform a second verification for every mismatch.
int verify_result = context.verifyOneShot(params.data, params.signature);
if (verify_result == 1 &&
!key.hasSmallOrderEdDsaPoint(params.signature)) {
static_cast<char*>(buf.get())[0] = 1;
} else if (verify_result < 0 &&
CanUsePrehashedFallback(key, params.digest, has_context) &&
VerifyPrehashed(key,
params.digest,
params.data,
params.signature,
padding,
salt_length)) {
static_cast<char*>(buf.get())[0] = 1;
}
*out = ByteSource::Allocated(buf.release());
}
}
return true;
}
MaybeLocal<Value> SignTraits::EncodeOutput(Environment* env,
const SignConfiguration& params,
ByteSource* out) {
switch (params.mode) {
case SignConfiguration::Mode::Sign:
return out->ToArrayBuffer(env);
case SignConfiguration::Mode::Verify:
return Boolean::New(env->isolate(), out->data<char>()[0] == 1);
}
UNREACHABLE();
}
} // namespace crypto
} // namespace node