mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
This makes the function more robust against V8 inlining. Fixes: https://github.com/nodejs/node/issues/34073 PR-URL: https://github.com/nodejs/node/pull/34141 Reviewed-By: Richard Lau <riclau@uk.ibm.com> Reviewed-By: Ujjwal Sharma <ryzokuken@disroot.org> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Zeyu Yang <himself65@outlook.com>
7058 lines
224 KiB
C++
7058 lines
224 KiB
C++
// Copyright Joyent, Inc. and other Node contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
// following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
#include "node_crypto.h"
|
|
#include "node_buffer.h"
|
|
#include "node_crypto_bio.h"
|
|
#include "node_crypto_common.h"
|
|
#include "node_crypto_clienthello-inl.h"
|
|
#include "node_crypto_groups.h"
|
|
#include "node_errors.h"
|
|
#include "node_mutex.h"
|
|
#include "node_process.h"
|
|
#include "allocated_buffer-inl.h"
|
|
#include "tls_wrap.h" // TLSWrap
|
|
|
|
#include "async_wrap-inl.h"
|
|
#include "base_object-inl.h"
|
|
#include "env-inl.h"
|
|
#include "memory_tracker-inl.h"
|
|
#include "string_bytes.h"
|
|
#include "threadpoolwork-inl.h"
|
|
#include "util-inl.h"
|
|
#include "v8.h"
|
|
|
|
#include <openssl/ec.h>
|
|
#include <openssl/ecdh.h>
|
|
#ifndef OPENSSL_NO_ENGINE
|
|
# include <openssl/engine.h>
|
|
#endif // !OPENSSL_NO_ENGINE
|
|
#include <openssl/evp.h>
|
|
#include <openssl/pem.h>
|
|
#include <openssl/x509v3.h>
|
|
#include <openssl/hmac.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/pkcs12.h>
|
|
|
|
#include <cerrno>
|
|
#include <climits> // INT_MAX
|
|
#include <cstring>
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace node {
|
|
namespace crypto {
|
|
|
|
using node::THROW_ERR_TLS_INVALID_PROTOCOL_METHOD;
|
|
|
|
using v8::Array;
|
|
using v8::ArrayBufferView;
|
|
using v8::Boolean;
|
|
using v8::ConstructorBehavior;
|
|
using v8::Context;
|
|
using v8::DontDelete;
|
|
using v8::Exception;
|
|
using v8::External;
|
|
using v8::False;
|
|
using v8::Function;
|
|
using v8::FunctionCallback;
|
|
using v8::FunctionCallbackInfo;
|
|
using v8::FunctionTemplate;
|
|
using v8::HandleScope;
|
|
using v8::Int32;
|
|
using v8::Integer;
|
|
using v8::Isolate;
|
|
using v8::Just;
|
|
using v8::Local;
|
|
using v8::Maybe;
|
|
using v8::MaybeLocal;
|
|
using v8::NewStringType;
|
|
using v8::Nothing;
|
|
using v8::Null;
|
|
using v8::Object;
|
|
using v8::PropertyAttribute;
|
|
using v8::ReadOnly;
|
|
using v8::SideEffectType;
|
|
using v8::Signature;
|
|
using v8::String;
|
|
using v8::Uint32;
|
|
using v8::Undefined;
|
|
using v8::Value;
|
|
|
|
#ifdef OPENSSL_NO_OCB
|
|
# define IS_OCB_MODE(mode) false
|
|
#else
|
|
# define IS_OCB_MODE(mode) ((mode) == EVP_CIPH_OCB_MODE)
|
|
#endif
|
|
|
|
static const char* const root_certs[] = {
|
|
#include "node_root_certs.h" // NOLINT(build/include_order)
|
|
};
|
|
|
|
static const char system_cert_path[] = NODE_OPENSSL_SYSTEM_CERT_PATH;
|
|
|
|
static X509_STORE* root_cert_store;
|
|
|
|
static bool extra_root_certs_loaded = false;
|
|
|
|
// Just to generate static methods
|
|
template void SSLWrap<TLSWrap>::AddMethods(Environment* env,
|
|
Local<FunctionTemplate> t);
|
|
template void SSLWrap<TLSWrap>::ConfigureSecureContext(SecureContext* sc);
|
|
template int SSLWrap<TLSWrap>::SetCACerts(SecureContext* sc);
|
|
template void SSLWrap<TLSWrap>::MemoryInfo(MemoryTracker* tracker) const;
|
|
template SSL_SESSION* SSLWrap<TLSWrap>::GetSessionCallback(
|
|
SSL* s,
|
|
const unsigned char* key,
|
|
int len,
|
|
int* copy);
|
|
template int SSLWrap<TLSWrap>::NewSessionCallback(SSL* s,
|
|
SSL_SESSION* sess);
|
|
template void SSLWrap<TLSWrap>::KeylogCallback(const SSL* s,
|
|
const char* line);
|
|
template void SSLWrap<TLSWrap>::OnClientHello(
|
|
void* arg,
|
|
const ClientHelloParser::ClientHello& hello);
|
|
template int SSLWrap<TLSWrap>::TLSExtStatusCallback(SSL* s, void* arg);
|
|
template void SSLWrap<TLSWrap>::DestroySSL();
|
|
template int SSLWrap<TLSWrap>::SSLCertCallback(SSL* s, void* arg);
|
|
template void SSLWrap<TLSWrap>::WaitForCertCb(CertCb cb, void* arg);
|
|
template int SSLWrap<TLSWrap>::SelectALPNCallback(
|
|
SSL* s,
|
|
const unsigned char** out,
|
|
unsigned char* outlen,
|
|
const unsigned char* in,
|
|
unsigned int inlen,
|
|
void* arg);
|
|
|
|
template <typename T>
|
|
void Decode(const FunctionCallbackInfo<Value>& args,
|
|
void (*callback)(T*, const FunctionCallbackInfo<Value>&,
|
|
const char*, size_t)) {
|
|
T* ctx;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
|
|
|
|
if (args[0]->IsString()) {
|
|
StringBytes::InlineDecoder decoder;
|
|
Environment* env = Environment::GetCurrent(args);
|
|
enum encoding enc = ParseEncoding(env->isolate(), args[1], UTF8);
|
|
if (decoder.Decode(env, args[0].As<String>(), enc).IsNothing())
|
|
return;
|
|
callback(ctx, args, decoder.out(), decoder.size());
|
|
} else {
|
|
ArrayBufferViewContents<char> buf(args[0]);
|
|
callback(ctx, args, buf.data(), buf.length());
|
|
}
|
|
}
|
|
|
|
static int PasswordCallback(char* buf, int size, int rwflag, void* u) {
|
|
const char* passphrase = static_cast<char*>(u);
|
|
if (passphrase != nullptr) {
|
|
size_t buflen = static_cast<size_t>(size);
|
|
size_t len = strlen(passphrase);
|
|
if (buflen < len)
|
|
return -1;
|
|
memcpy(buf, passphrase, len);
|
|
return len;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Loads OpenSSL engine by engine id and returns it. The loaded engine
|
|
// gets a reference so remember the corresponding call to ENGINE_free.
|
|
// In case of error the appropriate js exception is scheduled
|
|
// and nullptr is returned.
|
|
#ifndef OPENSSL_NO_ENGINE
|
|
static ENGINE* LoadEngineById(const char* engine_id, char (*errmsg)[1024]) {
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
ENGINE* engine = ENGINE_by_id(engine_id);
|
|
|
|
if (engine == nullptr) {
|
|
// Engine not found, try loading dynamically.
|
|
engine = ENGINE_by_id("dynamic");
|
|
if (engine != nullptr) {
|
|
if (!ENGINE_ctrl_cmd_string(engine, "SO_PATH", engine_id, 0) ||
|
|
!ENGINE_ctrl_cmd_string(engine, "LOAD", nullptr, 0)) {
|
|
ENGINE_free(engine);
|
|
engine = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (engine == nullptr) {
|
|
int err = ERR_get_error();
|
|
if (err != 0) {
|
|
ERR_error_string_n(err, *errmsg, sizeof(*errmsg));
|
|
} else {
|
|
snprintf(*errmsg, sizeof(*errmsg),
|
|
"Engine \"%s\" was not found", engine_id);
|
|
}
|
|
}
|
|
|
|
return engine;
|
|
}
|
|
#endif // !OPENSSL_NO_ENGINE
|
|
|
|
// This callback is used to avoid the default passphrase callback in OpenSSL
|
|
// which will typically prompt for the passphrase. The prompting is designed
|
|
// for the OpenSSL CLI, but works poorly for Node.js because it involves
|
|
// synchronous interaction with the controlling terminal, something we never
|
|
// want, and use this function to avoid it.
|
|
static int NoPasswordCallback(char* buf, int size, int rwflag, void* u) {
|
|
return 0;
|
|
}
|
|
|
|
|
|
// namespace node::crypto::error
|
|
namespace error {
|
|
Maybe<bool> Decorate(Environment* env, Local<Object> obj,
|
|
unsigned long err) { // NOLINT(runtime/int)
|
|
if (err == 0) return Just(true); // No decoration necessary.
|
|
|
|
const char* ls = ERR_lib_error_string(err);
|
|
const char* fs = ERR_func_error_string(err);
|
|
const char* rs = ERR_reason_error_string(err);
|
|
|
|
Isolate* isolate = env->isolate();
|
|
Local<Context> context = isolate->GetCurrentContext();
|
|
|
|
if (ls != nullptr) {
|
|
if (obj->Set(context, env->library_string(),
|
|
OneByteString(isolate, ls)).IsNothing()) {
|
|
return Nothing<bool>();
|
|
}
|
|
}
|
|
if (fs != nullptr) {
|
|
if (obj->Set(context, env->function_string(),
|
|
OneByteString(isolate, fs)).IsNothing()) {
|
|
return Nothing<bool>();
|
|
}
|
|
}
|
|
if (rs != nullptr) {
|
|
if (obj->Set(context, env->reason_string(),
|
|
OneByteString(isolate, rs)).IsNothing()) {
|
|
return Nothing<bool>();
|
|
}
|
|
|
|
// SSL has no API to recover the error name from the number, so we
|
|
// transform reason strings like "this error" to "ERR_SSL_THIS_ERROR",
|
|
// which ends up being close to the original error macro name.
|
|
std::string reason(rs);
|
|
|
|
for (auto& c : reason) {
|
|
if (c == ' ')
|
|
c = '_';
|
|
else
|
|
c = ToUpper(c);
|
|
}
|
|
|
|
#define OSSL_ERROR_CODES_MAP(V) \
|
|
V(SYS) \
|
|
V(BN) \
|
|
V(RSA) \
|
|
V(DH) \
|
|
V(EVP) \
|
|
V(BUF) \
|
|
V(OBJ) \
|
|
V(PEM) \
|
|
V(DSA) \
|
|
V(X509) \
|
|
V(ASN1) \
|
|
V(CONF) \
|
|
V(CRYPTO) \
|
|
V(EC) \
|
|
V(SSL) \
|
|
V(BIO) \
|
|
V(PKCS7) \
|
|
V(X509V3) \
|
|
V(PKCS12) \
|
|
V(RAND) \
|
|
V(DSO) \
|
|
V(ENGINE) \
|
|
V(OCSP) \
|
|
V(UI) \
|
|
V(COMP) \
|
|
V(ECDSA) \
|
|
V(ECDH) \
|
|
V(OSSL_STORE) \
|
|
V(FIPS) \
|
|
V(CMS) \
|
|
V(TS) \
|
|
V(HMAC) \
|
|
V(CT) \
|
|
V(ASYNC) \
|
|
V(KDF) \
|
|
V(SM2) \
|
|
V(USER) \
|
|
|
|
#define V(name) case ERR_LIB_##name: lib = #name "_"; break;
|
|
const char* lib = "";
|
|
const char* prefix = "OSSL_";
|
|
switch (ERR_GET_LIB(err)) { OSSL_ERROR_CODES_MAP(V) }
|
|
#undef V
|
|
#undef OSSL_ERROR_CODES_MAP
|
|
// Don't generate codes like "ERR_OSSL_SSL_".
|
|
if (lib && strcmp(lib, "SSL_") == 0)
|
|
prefix = "";
|
|
|
|
// All OpenSSL reason strings fit in a single 80-column macro definition,
|
|
// all prefix lengths are <= 10, and ERR_OSSL_ is 9, so 128 is more than
|
|
// sufficient.
|
|
char code[128];
|
|
snprintf(code, sizeof(code), "ERR_%s%s%s", prefix, lib, reason.c_str());
|
|
|
|
if (obj->Set(env->isolate()->GetCurrentContext(),
|
|
env->code_string(),
|
|
OneByteString(env->isolate(), code)).IsNothing())
|
|
return Nothing<bool>();
|
|
}
|
|
|
|
return Just(true);
|
|
}
|
|
} // namespace error
|
|
|
|
|
|
struct CryptoErrorVector : public std::vector<std::string> {
|
|
inline void Capture() {
|
|
clear();
|
|
while (auto err = ERR_get_error()) {
|
|
char buf[256];
|
|
ERR_error_string_n(err, buf, sizeof(buf));
|
|
push_back(buf);
|
|
}
|
|
std::reverse(begin(), end());
|
|
}
|
|
|
|
inline MaybeLocal<Value> ToException(
|
|
Environment* env,
|
|
Local<String> exception_string = Local<String>()) const {
|
|
if (exception_string.IsEmpty()) {
|
|
CryptoErrorVector copy(*this);
|
|
if (copy.empty()) copy.push_back("no error"); // But possibly a bug...
|
|
// Use last element as the error message, everything else goes
|
|
// into the .opensslErrorStack property on the exception object.
|
|
auto exception_string =
|
|
String::NewFromUtf8(env->isolate(), copy.back().data(),
|
|
NewStringType::kNormal, copy.back().size())
|
|
.ToLocalChecked();
|
|
copy.pop_back();
|
|
return copy.ToException(env, exception_string);
|
|
}
|
|
|
|
Local<Value> exception_v = Exception::Error(exception_string);
|
|
CHECK(!exception_v.IsEmpty());
|
|
|
|
if (!empty()) {
|
|
CHECK(exception_v->IsObject());
|
|
Local<Object> exception = exception_v.As<Object>();
|
|
Maybe<bool> ok = exception->Set(env->context(),
|
|
env->openssl_error_stack(),
|
|
ToV8Value(env->context(), *this).ToLocalChecked());
|
|
if (ok.IsNothing())
|
|
return MaybeLocal<Value>();
|
|
}
|
|
|
|
return exception_v;
|
|
}
|
|
};
|
|
|
|
|
|
void ThrowCryptoError(Environment* env,
|
|
unsigned long err, // NOLINT(runtime/int)
|
|
// Default, only used if there is no SSL `err` which can
|
|
// be used to create a long-style message string.
|
|
const char* message) {
|
|
char message_buffer[128] = {0};
|
|
if (err != 0 || message == nullptr) {
|
|
ERR_error_string_n(err, message_buffer, sizeof(message_buffer));
|
|
message = message_buffer;
|
|
}
|
|
HandleScope scope(env->isolate());
|
|
Local<String> exception_string =
|
|
String::NewFromUtf8(env->isolate(), message, NewStringType::kNormal)
|
|
.ToLocalChecked();
|
|
CryptoErrorVector errors;
|
|
errors.Capture();
|
|
Local<Value> exception;
|
|
if (!errors.ToException(env, exception_string).ToLocal(&exception))
|
|
return;
|
|
Local<Object> obj;
|
|
if (!exception->ToObject(env->context()).ToLocal(&obj))
|
|
return;
|
|
if (error::Decorate(env, obj, err).IsNothing())
|
|
return;
|
|
env->isolate()->ThrowException(exception);
|
|
}
|
|
|
|
|
|
// Ensure that OpenSSL has enough entropy (at least 256 bits) for its PRNG.
|
|
// The entropy pool starts out empty and needs to fill up before the PRNG
|
|
// can be used securely. Once the pool is filled, it never dries up again;
|
|
// its contents is stirred and reused when necessary.
|
|
//
|
|
// OpenSSL normally fills the pool automatically but not when someone starts
|
|
// generating random numbers before the pool is full: in that case OpenSSL
|
|
// keeps lowering the entropy estimate to thwart attackers trying to guess
|
|
// the initial state of the PRNG.
|
|
//
|
|
// When that happens, we will have to wait until enough entropy is available.
|
|
// That should normally never take longer than a few milliseconds.
|
|
//
|
|
// OpenSSL draws from /dev/random and /dev/urandom. While /dev/random may
|
|
// block pending "true" randomness, /dev/urandom is a CSPRNG that doesn't
|
|
// block under normal circumstances.
|
|
//
|
|
// The only time when /dev/urandom may conceivably block is right after boot,
|
|
// when the whole system is still low on entropy. That's not something we can
|
|
// do anything about.
|
|
inline void CheckEntropy() {
|
|
for (;;) {
|
|
int status = RAND_status();
|
|
CHECK_GE(status, 0); // Cannot fail.
|
|
if (status != 0)
|
|
break;
|
|
|
|
// Give up, RAND_poll() not supported.
|
|
if (RAND_poll() == 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
bool EntropySource(unsigned char* buffer, size_t length) {
|
|
// Ensure that OpenSSL's PRNG is properly seeded.
|
|
CheckEntropy();
|
|
// RAND_bytes() can return 0 to indicate that the entropy data is not truly
|
|
// random. That's okay, it's still better than V8's stock source of entropy,
|
|
// which is /dev/urandom on UNIX platforms and the current time on Windows.
|
|
return RAND_bytes(buffer, length) != -1;
|
|
}
|
|
|
|
void SecureContext::Initialize(Environment* env, Local<Object> target) {
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
t->InstanceTemplate()->SetInternalFieldCount(
|
|
SecureContext::kInternalFieldCount);
|
|
t->Inherit(BaseObject::GetConstructorTemplate(env));
|
|
Local<String> secureContextString =
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "SecureContext");
|
|
t->SetClassName(secureContextString);
|
|
|
|
env->SetProtoMethod(t, "init", Init);
|
|
env->SetProtoMethod(t, "setKey", SetKey);
|
|
#ifndef OPENSSL_NO_ENGINE
|
|
env->SetProtoMethod(t, "setEngineKey", SetEngineKey);
|
|
#endif // !OPENSSL_NO_ENGINE
|
|
env->SetProtoMethod(t, "setCert", SetCert);
|
|
env->SetProtoMethod(t, "addCACert", AddCACert);
|
|
env->SetProtoMethod(t, "addCRL", AddCRL);
|
|
env->SetProtoMethod(t, "addRootCerts", AddRootCerts);
|
|
env->SetProtoMethod(t, "setCipherSuites", SetCipherSuites);
|
|
env->SetProtoMethod(t, "setCiphers", SetCiphers);
|
|
env->SetProtoMethod(t, "setSigalgs", SetSigalgs);
|
|
env->SetProtoMethod(t, "setECDHCurve", SetECDHCurve);
|
|
env->SetProtoMethod(t, "setDHParam", SetDHParam);
|
|
env->SetProtoMethod(t, "setMaxProto", SetMaxProto);
|
|
env->SetProtoMethod(t, "setMinProto", SetMinProto);
|
|
env->SetProtoMethod(t, "getMaxProto", GetMaxProto);
|
|
env->SetProtoMethod(t, "getMinProto", GetMinProto);
|
|
env->SetProtoMethod(t, "setOptions", SetOptions);
|
|
env->SetProtoMethod(t, "setSessionIdContext", SetSessionIdContext);
|
|
env->SetProtoMethod(t, "setSessionTimeout", SetSessionTimeout);
|
|
env->SetProtoMethod(t, "close", Close);
|
|
env->SetProtoMethod(t, "loadPKCS12", LoadPKCS12);
|
|
#ifndef OPENSSL_NO_ENGINE
|
|
env->SetProtoMethod(t, "setClientCertEngine", SetClientCertEngine);
|
|
#endif // !OPENSSL_NO_ENGINE
|
|
env->SetProtoMethodNoSideEffect(t, "getTicketKeys", GetTicketKeys);
|
|
env->SetProtoMethod(t, "setTicketKeys", SetTicketKeys);
|
|
env->SetProtoMethod(t, "setFreeListLength", SetFreeListLength);
|
|
env->SetProtoMethod(t, "enableTicketKeyCallback", EnableTicketKeyCallback);
|
|
env->SetProtoMethodNoSideEffect(t, "getCertificate", GetCertificate<true>);
|
|
env->SetProtoMethodNoSideEffect(t, "getIssuer", GetCertificate<false>);
|
|
|
|
#define SET_INTEGER_CONSTANTS(name, value) \
|
|
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), name), \
|
|
Integer::NewFromUnsigned(env->isolate(), value));
|
|
SET_INTEGER_CONSTANTS("kTicketKeyReturnIndex", kTicketKeyReturnIndex);
|
|
SET_INTEGER_CONSTANTS("kTicketKeyHMACIndex", kTicketKeyHMACIndex);
|
|
SET_INTEGER_CONSTANTS("kTicketKeyAESIndex", kTicketKeyAESIndex);
|
|
SET_INTEGER_CONSTANTS("kTicketKeyNameIndex", kTicketKeyNameIndex);
|
|
SET_INTEGER_CONSTANTS("kTicketKeyIVIndex", kTicketKeyIVIndex);
|
|
|
|
#undef SET_INTEGER_CONSTANTS
|
|
|
|
Local<FunctionTemplate> ctx_getter_templ =
|
|
FunctionTemplate::New(env->isolate(),
|
|
CtxGetter,
|
|
Local<Value>(),
|
|
Signature::New(env->isolate(), t));
|
|
|
|
|
|
t->PrototypeTemplate()->SetAccessorProperty(
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "_external"),
|
|
ctx_getter_templ,
|
|
Local<FunctionTemplate>(),
|
|
static_cast<PropertyAttribute>(ReadOnly | DontDelete));
|
|
|
|
target->Set(env->context(), secureContextString,
|
|
t->GetFunction(env->context()).ToLocalChecked()).Check();
|
|
env->set_secure_context_constructor_template(t);
|
|
}
|
|
|
|
SecureContext::SecureContext(Environment* env, Local<Object> wrap)
|
|
: BaseObject(env, wrap) {
|
|
MakeWeak();
|
|
env->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize);
|
|
}
|
|
|
|
inline void SecureContext::Reset() {
|
|
if (ctx_ != nullptr) {
|
|
env()->isolate()->AdjustAmountOfExternalAllocatedMemory(-kExternalSize);
|
|
}
|
|
ctx_.reset();
|
|
cert_.reset();
|
|
issuer_.reset();
|
|
}
|
|
|
|
SecureContext::~SecureContext() {
|
|
Reset();
|
|
}
|
|
|
|
void SecureContext::New(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
new SecureContext(env, args.This());
|
|
}
|
|
|
|
// A maxVersion of 0 means "any", but OpenSSL may support TLS versions that
|
|
// Node.js doesn't, so pin the max to what we do support.
|
|
const int MAX_SUPPORTED_VERSION = TLS1_3_VERSION;
|
|
|
|
void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
Environment* env = sc->env();
|
|
|
|
CHECK_EQ(args.Length(), 3);
|
|
CHECK(args[1]->IsInt32());
|
|
CHECK(args[2]->IsInt32());
|
|
|
|
int min_version = args[1].As<Int32>()->Value();
|
|
int max_version = args[2].As<Int32>()->Value();
|
|
const SSL_METHOD* method = TLS_method();
|
|
|
|
if (max_version == 0)
|
|
max_version = MAX_SUPPORTED_VERSION;
|
|
|
|
if (args[0]->IsString()) {
|
|
const node::Utf8Value sslmethod(env->isolate(), args[0]);
|
|
|
|
// Note that SSLv2 and SSLv3 are disallowed but SSLv23_method and friends
|
|
// are still accepted. They are OpenSSL's way of saying that all known
|
|
// protocols below TLS 1.3 are supported unless explicitly disabled (which
|
|
// we do below for SSLv2 and SSLv3.)
|
|
if (strcmp(*sslmethod, "SSLv2_method") == 0) {
|
|
THROW_ERR_TLS_INVALID_PROTOCOL_METHOD(env, "SSLv2 methods disabled");
|
|
return;
|
|
} else if (strcmp(*sslmethod, "SSLv2_server_method") == 0) {
|
|
THROW_ERR_TLS_INVALID_PROTOCOL_METHOD(env, "SSLv2 methods disabled");
|
|
return;
|
|
} else if (strcmp(*sslmethod, "SSLv2_client_method") == 0) {
|
|
THROW_ERR_TLS_INVALID_PROTOCOL_METHOD(env, "SSLv2 methods disabled");
|
|
return;
|
|
} else if (strcmp(*sslmethod, "SSLv3_method") == 0) {
|
|
THROW_ERR_TLS_INVALID_PROTOCOL_METHOD(env, "SSLv3 methods disabled");
|
|
return;
|
|
} else if (strcmp(*sslmethod, "SSLv3_server_method") == 0) {
|
|
THROW_ERR_TLS_INVALID_PROTOCOL_METHOD(env, "SSLv3 methods disabled");
|
|
return;
|
|
} else if (strcmp(*sslmethod, "SSLv3_client_method") == 0) {
|
|
THROW_ERR_TLS_INVALID_PROTOCOL_METHOD(env, "SSLv3 methods disabled");
|
|
return;
|
|
} else if (strcmp(*sslmethod, "SSLv23_method") == 0) {
|
|
max_version = TLS1_2_VERSION;
|
|
} else if (strcmp(*sslmethod, "SSLv23_server_method") == 0) {
|
|
max_version = TLS1_2_VERSION;
|
|
method = TLS_server_method();
|
|
} else if (strcmp(*sslmethod, "SSLv23_client_method") == 0) {
|
|
max_version = TLS1_2_VERSION;
|
|
method = TLS_client_method();
|
|
} else if (strcmp(*sslmethod, "TLS_method") == 0) {
|
|
min_version = 0;
|
|
max_version = MAX_SUPPORTED_VERSION;
|
|
} else if (strcmp(*sslmethod, "TLS_server_method") == 0) {
|
|
min_version = 0;
|
|
max_version = MAX_SUPPORTED_VERSION;
|
|
method = TLS_server_method();
|
|
} else if (strcmp(*sslmethod, "TLS_client_method") == 0) {
|
|
min_version = 0;
|
|
max_version = MAX_SUPPORTED_VERSION;
|
|
method = TLS_client_method();
|
|
} else if (strcmp(*sslmethod, "TLSv1_method") == 0) {
|
|
min_version = TLS1_VERSION;
|
|
max_version = TLS1_VERSION;
|
|
} else if (strcmp(*sslmethod, "TLSv1_server_method") == 0) {
|
|
min_version = TLS1_VERSION;
|
|
max_version = TLS1_VERSION;
|
|
method = TLS_server_method();
|
|
} else if (strcmp(*sslmethod, "TLSv1_client_method") == 0) {
|
|
min_version = TLS1_VERSION;
|
|
max_version = TLS1_VERSION;
|
|
method = TLS_client_method();
|
|
} else if (strcmp(*sslmethod, "TLSv1_1_method") == 0) {
|
|
min_version = TLS1_1_VERSION;
|
|
max_version = TLS1_1_VERSION;
|
|
} else if (strcmp(*sslmethod, "TLSv1_1_server_method") == 0) {
|
|
min_version = TLS1_1_VERSION;
|
|
max_version = TLS1_1_VERSION;
|
|
method = TLS_server_method();
|
|
} else if (strcmp(*sslmethod, "TLSv1_1_client_method") == 0) {
|
|
min_version = TLS1_1_VERSION;
|
|
max_version = TLS1_1_VERSION;
|
|
method = TLS_client_method();
|
|
} else if (strcmp(*sslmethod, "TLSv1_2_method") == 0) {
|
|
min_version = TLS1_2_VERSION;
|
|
max_version = TLS1_2_VERSION;
|
|
} else if (strcmp(*sslmethod, "TLSv1_2_server_method") == 0) {
|
|
min_version = TLS1_2_VERSION;
|
|
max_version = TLS1_2_VERSION;
|
|
method = TLS_server_method();
|
|
} else if (strcmp(*sslmethod, "TLSv1_2_client_method") == 0) {
|
|
min_version = TLS1_2_VERSION;
|
|
max_version = TLS1_2_VERSION;
|
|
method = TLS_client_method();
|
|
} else {
|
|
const std::string msg("Unknown method: ");
|
|
THROW_ERR_TLS_INVALID_PROTOCOL_METHOD(env, (msg + * sslmethod).c_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
sc->ctx_.reset(SSL_CTX_new(method));
|
|
SSL_CTX_set_app_data(sc->ctx_.get(), sc);
|
|
|
|
// Disable SSLv2 in the case when method == TLS_method() and the
|
|
// cipher list contains SSLv2 ciphers (not the default, should be rare.)
|
|
// The bundled OpenSSL doesn't have SSLv2 support but the system OpenSSL may.
|
|
// SSLv3 is disabled because it's susceptible to downgrade attacks (POODLE.)
|
|
SSL_CTX_set_options(sc->ctx_.get(), SSL_OP_NO_SSLv2);
|
|
SSL_CTX_set_options(sc->ctx_.get(), SSL_OP_NO_SSLv3);
|
|
|
|
// Enable automatic cert chaining. This is enabled by default in OpenSSL, but
|
|
// disabled by default in BoringSSL. Enable it explicitly to make the
|
|
// behavior match when Node is built with BoringSSL.
|
|
SSL_CTX_clear_mode(sc->ctx_.get(), SSL_MODE_NO_AUTO_CHAIN);
|
|
|
|
// SSL session cache configuration
|
|
SSL_CTX_set_session_cache_mode(sc->ctx_.get(),
|
|
SSL_SESS_CACHE_CLIENT |
|
|
SSL_SESS_CACHE_SERVER |
|
|
SSL_SESS_CACHE_NO_INTERNAL |
|
|
SSL_SESS_CACHE_NO_AUTO_CLEAR);
|
|
|
|
SSL_CTX_set_min_proto_version(sc->ctx_.get(), min_version);
|
|
SSL_CTX_set_max_proto_version(sc->ctx_.get(), max_version);
|
|
|
|
// OpenSSL 1.1.0 changed the ticket key size, but the OpenSSL 1.0.x size was
|
|
// exposed in the public API. To retain compatibility, install a callback
|
|
// which restores the old algorithm.
|
|
if (RAND_bytes(sc->ticket_key_name_, sizeof(sc->ticket_key_name_)) <= 0 ||
|
|
RAND_bytes(sc->ticket_key_hmac_, sizeof(sc->ticket_key_hmac_)) <= 0 ||
|
|
RAND_bytes(sc->ticket_key_aes_, sizeof(sc->ticket_key_aes_)) <= 0) {
|
|
return env->ThrowError("Error generating ticket keys");
|
|
}
|
|
SSL_CTX_set_tlsext_ticket_key_cb(sc->ctx_.get(), TicketCompatibilityCallback);
|
|
}
|
|
|
|
|
|
// Takes a string or buffer and loads it into a BIO.
|
|
// Caller responsible for BIO_free_all-ing the returned object.
|
|
static BIOPointer LoadBIO(Environment* env, Local<Value> v) {
|
|
HandleScope scope(env->isolate());
|
|
|
|
if (v->IsString()) {
|
|
const node::Utf8Value s(env->isolate(), v);
|
|
return NodeBIO::NewFixed(*s, s.length());
|
|
}
|
|
|
|
if (v->IsArrayBufferView()) {
|
|
ArrayBufferViewContents<char> buf(v.As<ArrayBufferView>());
|
|
return NodeBIO::NewFixed(buf.data(), buf.length());
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
void SecureContext::SetKey(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
|
|
unsigned int len = args.Length();
|
|
if (len < 1) {
|
|
return THROW_ERR_MISSING_ARGS(env, "Private key argument is mandatory");
|
|
}
|
|
|
|
if (len > 2) {
|
|
return env->ThrowError("Only private key and pass phrase are expected");
|
|
}
|
|
|
|
if (len == 2) {
|
|
if (args[1]->IsUndefined() || args[1]->IsNull())
|
|
len = 1;
|
|
else
|
|
THROW_AND_RETURN_IF_NOT_STRING(env, args[1], "Pass phrase");
|
|
}
|
|
|
|
BIOPointer bio(LoadBIO(env, args[0]));
|
|
if (!bio)
|
|
return;
|
|
|
|
node::Utf8Value passphrase(env->isolate(), args[1]);
|
|
|
|
EVPKeyPointer key(
|
|
PEM_read_bio_PrivateKey(bio.get(),
|
|
nullptr,
|
|
PasswordCallback,
|
|
*passphrase));
|
|
|
|
if (!key) {
|
|
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
|
return ThrowCryptoError(env, err, "PEM_read_bio_PrivateKey");
|
|
}
|
|
|
|
int rv = SSL_CTX_use_PrivateKey(sc->ctx_.get(), key.get());
|
|
|
|
if (!rv) {
|
|
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
|
return ThrowCryptoError(env, err, "SSL_CTX_use_PrivateKey");
|
|
}
|
|
}
|
|
|
|
void SecureContext::SetSigalgs(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
Environment* env = sc->env();
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
CHECK_EQ(args.Length(), 1);
|
|
CHECK(args[0]->IsString());
|
|
|
|
const node::Utf8Value sigalgs(env->isolate(), args[0]);
|
|
|
|
int rv = SSL_CTX_set1_sigalgs_list(sc->ctx_.get(), *sigalgs);
|
|
|
|
if (rv == 0) {
|
|
return ThrowCryptoError(env, ERR_get_error());
|
|
}
|
|
}
|
|
|
|
#ifndef OPENSSL_NO_ENGINE
|
|
// Helpers for the smart pointer.
|
|
void ENGINE_free_fn(ENGINE* engine) { ENGINE_free(engine); }
|
|
|
|
void ENGINE_finish_and_free_fn(ENGINE* engine) {
|
|
ENGINE_finish(engine);
|
|
ENGINE_free(engine);
|
|
}
|
|
|
|
void SecureContext::SetEngineKey(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
|
|
CHECK_EQ(args.Length(), 2);
|
|
|
|
char errmsg[1024];
|
|
const node::Utf8Value engine_id(env->isolate(), args[1]);
|
|
std::unique_ptr<ENGINE, std::function<void(ENGINE*)>> e =
|
|
{ LoadEngineById(*engine_id, &errmsg),
|
|
ENGINE_free_fn };
|
|
if (e.get() == nullptr) {
|
|
return env->ThrowError(errmsg);
|
|
}
|
|
|
|
if (!ENGINE_init(e.get())) {
|
|
return env->ThrowError("ENGINE_init");
|
|
}
|
|
|
|
e.get_deleter() = ENGINE_finish_and_free_fn;
|
|
|
|
const node::Utf8Value key_name(env->isolate(), args[0]);
|
|
EVPKeyPointer key(ENGINE_load_private_key(e.get(), *key_name,
|
|
nullptr, nullptr));
|
|
|
|
if (!key) {
|
|
return ThrowCryptoError(env, ERR_get_error(), "ENGINE_load_private_key");
|
|
}
|
|
|
|
int rv = SSL_CTX_use_PrivateKey(sc->ctx_.get(), key.get());
|
|
|
|
if (rv == 0) {
|
|
return ThrowCryptoError(env, ERR_get_error(), "SSL_CTX_use_PrivateKey");
|
|
}
|
|
|
|
sc->private_key_engine_ = std::move(e);
|
|
}
|
|
#endif // !OPENSSL_NO_ENGINE
|
|
|
|
int SSL_CTX_use_certificate_chain(SSL_CTX* ctx,
|
|
X509Pointer&& x,
|
|
STACK_OF(X509)* extra_certs,
|
|
X509Pointer* cert,
|
|
X509Pointer* issuer_) {
|
|
CHECK(!*issuer_);
|
|
CHECK(!*cert);
|
|
X509* issuer = nullptr;
|
|
|
|
int ret = SSL_CTX_use_certificate(ctx, x.get());
|
|
|
|
if (ret) {
|
|
// If we could set up our certificate, now proceed to
|
|
// the CA certificates.
|
|
SSL_CTX_clear_extra_chain_certs(ctx);
|
|
|
|
for (int i = 0; i < sk_X509_num(extra_certs); i++) {
|
|
X509* ca = sk_X509_value(extra_certs, i);
|
|
|
|
// NOTE: Increments reference count on `ca`
|
|
if (!SSL_CTX_add1_chain_cert(ctx, ca)) {
|
|
ret = 0;
|
|
issuer = nullptr;
|
|
break;
|
|
}
|
|
// Note that we must not free r if it was successfully
|
|
// added to the chain (while we must free the main
|
|
// certificate, since its reference count is increased
|
|
// by SSL_CTX_use_certificate).
|
|
|
|
// Find issuer
|
|
if (issuer != nullptr || X509_check_issued(ca, x.get()) != X509_V_OK)
|
|
continue;
|
|
|
|
issuer = ca;
|
|
}
|
|
}
|
|
|
|
// Try getting issuer from a cert store
|
|
if (ret) {
|
|
if (issuer == nullptr) {
|
|
ret = SSL_CTX_get_issuer(ctx, x.get(), &issuer);
|
|
ret = ret < 0 ? 0 : 1;
|
|
// NOTE: get_cert_store doesn't increment reference count,
|
|
// no need to free `store`
|
|
} else {
|
|
// Increment issuer reference count
|
|
issuer = X509_dup(issuer);
|
|
if (issuer == nullptr) {
|
|
ret = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
issuer_->reset(issuer);
|
|
|
|
if (ret && x != nullptr) {
|
|
cert->reset(X509_dup(x.get()));
|
|
if (!*cert)
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
// Read a file that contains our certificate in "PEM" format,
|
|
// possibly followed by a sequence of CA certificates that should be
|
|
// sent to the peer in the Certificate message.
|
|
//
|
|
// Taken from OpenSSL - edited for style.
|
|
int SSL_CTX_use_certificate_chain(SSL_CTX* ctx,
|
|
BIOPointer&& in,
|
|
X509Pointer* cert,
|
|
X509Pointer* issuer) {
|
|
// Just to ensure that `ERR_peek_last_error` below will return only errors
|
|
// that we are interested in
|
|
ERR_clear_error();
|
|
|
|
X509Pointer x(
|
|
PEM_read_bio_X509_AUX(in.get(), nullptr, NoPasswordCallback, nullptr));
|
|
|
|
if (!x)
|
|
return 0;
|
|
|
|
unsigned long err = 0; // NOLINT(runtime/int)
|
|
|
|
StackOfX509 extra_certs(sk_X509_new_null());
|
|
if (!extra_certs)
|
|
return 0;
|
|
|
|
while (X509Pointer extra {PEM_read_bio_X509(in.get(),
|
|
nullptr,
|
|
NoPasswordCallback,
|
|
nullptr)}) {
|
|
if (sk_X509_push(extra_certs.get(), extra.get())) {
|
|
extra.release();
|
|
continue;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// When the while loop ends, it's usually just EOF.
|
|
err = ERR_peek_last_error();
|
|
if (ERR_GET_LIB(err) == ERR_LIB_PEM &&
|
|
ERR_GET_REASON(err) == PEM_R_NO_START_LINE) {
|
|
ERR_clear_error();
|
|
} else {
|
|
// some real error
|
|
return 0;
|
|
}
|
|
|
|
return SSL_CTX_use_certificate_chain(ctx,
|
|
std::move(x),
|
|
extra_certs.get(),
|
|
cert,
|
|
issuer);
|
|
}
|
|
|
|
|
|
void SecureContext::SetCert(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
|
|
if (args.Length() != 1) {
|
|
return THROW_ERR_MISSING_ARGS(env, "Certificate argument is mandatory");
|
|
}
|
|
|
|
BIOPointer bio(LoadBIO(env, args[0]));
|
|
if (!bio)
|
|
return;
|
|
|
|
sc->cert_.reset();
|
|
sc->issuer_.reset();
|
|
|
|
int rv = SSL_CTX_use_certificate_chain(sc->ctx_.get(),
|
|
std::move(bio),
|
|
&sc->cert_,
|
|
&sc->issuer_);
|
|
|
|
if (!rv) {
|
|
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
|
return ThrowCryptoError(env, err, "SSL_CTX_use_certificate_chain");
|
|
}
|
|
}
|
|
|
|
|
|
static X509_STORE* NewRootCertStore() {
|
|
static std::vector<X509*> root_certs_vector;
|
|
static Mutex root_certs_vector_mutex;
|
|
Mutex::ScopedLock lock(root_certs_vector_mutex);
|
|
|
|
if (root_certs_vector.empty()) {
|
|
for (size_t i = 0; i < arraysize(root_certs); i++) {
|
|
X509* x509 =
|
|
PEM_read_bio_X509(NodeBIO::NewFixed(root_certs[i],
|
|
strlen(root_certs[i])).get(),
|
|
nullptr, // no re-use of X509 structure
|
|
NoPasswordCallback,
|
|
nullptr); // no callback data
|
|
|
|
// Parse errors from the built-in roots are fatal.
|
|
CHECK_NOT_NULL(x509);
|
|
|
|
root_certs_vector.push_back(x509);
|
|
}
|
|
}
|
|
|
|
X509_STORE* store = X509_STORE_new();
|
|
if (*system_cert_path != '\0') {
|
|
X509_STORE_load_locations(store, system_cert_path, nullptr);
|
|
}
|
|
|
|
Mutex::ScopedLock cli_lock(node::per_process::cli_options_mutex);
|
|
if (per_process::cli_options->ssl_openssl_cert_store) {
|
|
X509_STORE_set_default_paths(store);
|
|
} else {
|
|
for (X509* cert : root_certs_vector) {
|
|
X509_up_ref(cert);
|
|
X509_STORE_add_cert(store, cert);
|
|
}
|
|
}
|
|
|
|
return store;
|
|
}
|
|
|
|
|
|
void GetRootCertificates(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
Local<Value> result[arraysize(root_certs)];
|
|
|
|
for (size_t i = 0; i < arraysize(root_certs); i++) {
|
|
if (!String::NewFromOneByte(
|
|
env->isolate(),
|
|
reinterpret_cast<const uint8_t*>(root_certs[i]),
|
|
NewStringType::kNormal).ToLocal(&result[i])) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
args.GetReturnValue().Set(
|
|
Array::New(env->isolate(), result, arraysize(root_certs)));
|
|
}
|
|
|
|
|
|
void SecureContext::AddCACert(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
if (args.Length() != 1) {
|
|
return THROW_ERR_MISSING_ARGS(env, "CA certificate argument is mandatory");
|
|
}
|
|
|
|
BIOPointer bio(LoadBIO(env, args[0]));
|
|
if (!bio)
|
|
return;
|
|
|
|
X509_STORE* cert_store = SSL_CTX_get_cert_store(sc->ctx_.get());
|
|
while (X509* x509 = PEM_read_bio_X509_AUX(
|
|
bio.get(), nullptr, NoPasswordCallback, nullptr)) {
|
|
if (cert_store == root_cert_store) {
|
|
cert_store = NewRootCertStore();
|
|
SSL_CTX_set_cert_store(sc->ctx_.get(), cert_store);
|
|
}
|
|
X509_STORE_add_cert(cert_store, x509);
|
|
SSL_CTX_add_client_CA(sc->ctx_.get(), x509);
|
|
X509_free(x509);
|
|
}
|
|
}
|
|
|
|
|
|
void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
|
|
if (args.Length() != 1) {
|
|
return THROW_ERR_MISSING_ARGS(env, "CRL argument is mandatory");
|
|
}
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
BIOPointer bio(LoadBIO(env, args[0]));
|
|
if (!bio)
|
|
return;
|
|
|
|
DeleteFnPtr<X509_CRL, X509_CRL_free> crl(
|
|
PEM_read_bio_X509_CRL(bio.get(), nullptr, NoPasswordCallback, nullptr));
|
|
|
|
if (!crl)
|
|
return env->ThrowError("Failed to parse CRL");
|
|
|
|
X509_STORE* cert_store = SSL_CTX_get_cert_store(sc->ctx_.get());
|
|
if (cert_store == root_cert_store) {
|
|
cert_store = NewRootCertStore();
|
|
SSL_CTX_set_cert_store(sc->ctx_.get(), cert_store);
|
|
}
|
|
|
|
X509_STORE_add_crl(cert_store, crl.get());
|
|
X509_STORE_set_flags(cert_store,
|
|
X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
|
|
}
|
|
|
|
|
|
static unsigned long AddCertsFromFile( // NOLINT(runtime/int)
|
|
X509_STORE* store,
|
|
const char* file) {
|
|
ERR_clear_error();
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
BIOPointer bio(BIO_new_file(file, "r"));
|
|
if (!bio)
|
|
return ERR_get_error();
|
|
|
|
while (X509* x509 =
|
|
PEM_read_bio_X509(bio.get(), nullptr, NoPasswordCallback, nullptr)) {
|
|
X509_STORE_add_cert(store, x509);
|
|
X509_free(x509);
|
|
}
|
|
|
|
unsigned long err = ERR_peek_error(); // NOLINT(runtime/int)
|
|
// Ignore error if its EOF/no start line found.
|
|
if (ERR_GET_LIB(err) == ERR_LIB_PEM &&
|
|
ERR_GET_REASON(err) == PEM_R_NO_START_LINE) {
|
|
return 0;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
void UseExtraCaCerts(const std::string& file) {
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
if (root_cert_store == nullptr) {
|
|
root_cert_store = NewRootCertStore();
|
|
|
|
if (!file.empty()) {
|
|
unsigned long err = AddCertsFromFile( // NOLINT(runtime/int)
|
|
root_cert_store,
|
|
file.c_str());
|
|
if (err) {
|
|
fprintf(stderr,
|
|
"Warning: Ignoring extra certs from `%s`, load failed: %s\n",
|
|
file.c_str(),
|
|
ERR_error_string(err, nullptr));
|
|
} else {
|
|
extra_root_certs_loaded = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void IsExtraRootCertsFileLoaded(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
return args.GetReturnValue().Set(extra_root_certs_loaded);
|
|
}
|
|
|
|
|
|
void SecureContext::AddRootCerts(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
if (root_cert_store == nullptr) {
|
|
root_cert_store = NewRootCertStore();
|
|
}
|
|
|
|
// Increment reference count so global store is not deleted along with CTX.
|
|
X509_STORE_up_ref(root_cert_store);
|
|
SSL_CTX_set_cert_store(sc->ctx_.get(), root_cert_store);
|
|
}
|
|
|
|
|
|
void SecureContext::SetCipherSuites(const FunctionCallbackInfo<Value>& args) {
|
|
// BoringSSL doesn't allow API config of TLS1.3 cipher suites.
|
|
#ifndef OPENSSL_IS_BORINGSSL
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
Environment* env = sc->env();
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
CHECK_EQ(args.Length(), 1);
|
|
CHECK(args[0]->IsString());
|
|
|
|
const node::Utf8Value ciphers(args.GetIsolate(), args[0]);
|
|
if (!SSL_CTX_set_ciphersuites(sc->ctx_.get(), *ciphers)) {
|
|
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
|
return ThrowCryptoError(env, err, "Failed to set ciphers");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void SecureContext::SetCiphers(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
Environment* env = sc->env();
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
CHECK_EQ(args.Length(), 1);
|
|
CHECK(args[0]->IsString());
|
|
|
|
const node::Utf8Value ciphers(args.GetIsolate(), args[0]);
|
|
if (!SSL_CTX_set_cipher_list(sc->ctx_.get(), *ciphers)) {
|
|
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
|
|
|
if (strlen(*ciphers) == 0 && ERR_GET_REASON(err) == SSL_R_NO_CIPHER_MATCH) {
|
|
// TLS1.2 ciphers were deliberately cleared, so don't consider
|
|
// SSL_R_NO_CIPHER_MATCH to be an error (this is how _set_cipher_suites()
|
|
// works). If the user actually sets a value (like "no-such-cipher"), then
|
|
// that's actually an error.
|
|
return;
|
|
}
|
|
return ThrowCryptoError(env, err, "Failed to set ciphers");
|
|
}
|
|
}
|
|
|
|
|
|
void SecureContext::SetECDHCurve(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
Environment* env = sc->env();
|
|
|
|
if (args.Length() != 1)
|
|
return THROW_ERR_MISSING_ARGS(env, "ECDH curve name argument is mandatory");
|
|
|
|
THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "ECDH curve name");
|
|
|
|
node::Utf8Value curve(env->isolate(), args[0]);
|
|
|
|
if (strcmp(*curve, "auto") == 0)
|
|
return;
|
|
|
|
if (!SSL_CTX_set1_curves_list(sc->ctx_.get(), *curve))
|
|
return env->ThrowError("Failed to set ECDH curve");
|
|
}
|
|
|
|
|
|
void SecureContext::SetDHParam(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.This());
|
|
Environment* env = sc->env();
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
// Auto DH is not supported in openssl 1.0.1, so dhparam needs
|
|
// to be specified explicitly
|
|
if (args.Length() != 1)
|
|
return THROW_ERR_MISSING_ARGS(env, "DH argument is mandatory");
|
|
|
|
DHPointer dh;
|
|
{
|
|
BIOPointer bio(LoadBIO(env, args[0]));
|
|
if (!bio)
|
|
return;
|
|
|
|
dh.reset(PEM_read_bio_DHparams(bio.get(), nullptr, nullptr, nullptr));
|
|
}
|
|
|
|
// Invalid dhparam is silently discarded and DHE is no longer used.
|
|
if (!dh)
|
|
return;
|
|
|
|
const BIGNUM* p;
|
|
DH_get0_pqg(dh.get(), &p, nullptr, nullptr);
|
|
const int size = BN_num_bits(p);
|
|
if (size < 1024) {
|
|
return THROW_ERR_INVALID_ARG_VALUE(
|
|
env, "DH parameter is less than 1024 bits");
|
|
} else if (size < 2048) {
|
|
args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(
|
|
env->isolate(), "DH parameter is less than 2048 bits"));
|
|
}
|
|
|
|
SSL_CTX_set_options(sc->ctx_.get(), SSL_OP_SINGLE_DH_USE);
|
|
int r = SSL_CTX_set_tmp_dh(sc->ctx_.get(), dh.get());
|
|
|
|
if (!r)
|
|
return env->ThrowTypeError("Error setting temp DH parameter");
|
|
}
|
|
|
|
|
|
void SecureContext::SetMinProto(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
|
|
CHECK_EQ(args.Length(), 1);
|
|
CHECK(args[0]->IsInt32());
|
|
|
|
int version = args[0].As<Int32>()->Value();
|
|
|
|
CHECK(SSL_CTX_set_min_proto_version(sc->ctx_.get(), version));
|
|
}
|
|
|
|
|
|
void SecureContext::SetMaxProto(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
|
|
CHECK_EQ(args.Length(), 1);
|
|
CHECK(args[0]->IsInt32());
|
|
|
|
int version = args[0].As<Int32>()->Value();
|
|
|
|
CHECK(SSL_CTX_set_max_proto_version(sc->ctx_.get(), version));
|
|
}
|
|
|
|
|
|
void SecureContext::GetMinProto(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
|
|
CHECK_EQ(args.Length(), 0);
|
|
|
|
long version = // NOLINT(runtime/int)
|
|
SSL_CTX_get_min_proto_version(sc->ctx_.get());
|
|
args.GetReturnValue().Set(static_cast<uint32_t>(version));
|
|
}
|
|
|
|
|
|
void SecureContext::GetMaxProto(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
|
|
CHECK_EQ(args.Length(), 0);
|
|
|
|
long version = // NOLINT(runtime/int)
|
|
SSL_CTX_get_max_proto_version(sc->ctx_.get());
|
|
args.GetReturnValue().Set(static_cast<uint32_t>(version));
|
|
}
|
|
|
|
|
|
void SecureContext::SetOptions(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
int64_t val;
|
|
|
|
if (args.Length() != 1 ||
|
|
!args[0]->IntegerValue(args.GetIsolate()->GetCurrentContext()).To(&val)) {
|
|
return THROW_ERR_INVALID_ARG_TYPE(
|
|
sc->env(), "Options must be an integer value");
|
|
}
|
|
|
|
SSL_CTX_set_options(sc->ctx_.get(),
|
|
static_cast<long>(val)); // NOLINT(runtime/int)
|
|
}
|
|
|
|
|
|
void SecureContext::SetSessionIdContext(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
Environment* env = sc->env();
|
|
|
|
if (args.Length() != 1) {
|
|
return THROW_ERR_MISSING_ARGS(
|
|
env, "Session ID context argument is mandatory");
|
|
}
|
|
|
|
THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "Session ID context");
|
|
|
|
const node::Utf8Value sessionIdContext(args.GetIsolate(), args[0]);
|
|
const unsigned char* sid_ctx =
|
|
reinterpret_cast<const unsigned char*>(*sessionIdContext);
|
|
unsigned int sid_ctx_len = sessionIdContext.length();
|
|
|
|
int r = SSL_CTX_set_session_id_context(sc->ctx_.get(), sid_ctx, sid_ctx_len);
|
|
if (r == 1)
|
|
return;
|
|
|
|
BUF_MEM* mem;
|
|
Local<String> message;
|
|
|
|
BIOPointer bio(BIO_new(BIO_s_mem()));
|
|
if (!bio) {
|
|
message = FIXED_ONE_BYTE_STRING(args.GetIsolate(),
|
|
"SSL_CTX_set_session_id_context error");
|
|
} else {
|
|
ERR_print_errors(bio.get());
|
|
BIO_get_mem_ptr(bio.get(), &mem);
|
|
message = OneByteString(args.GetIsolate(), mem->data, mem->length);
|
|
}
|
|
|
|
args.GetIsolate()->ThrowException(Exception::TypeError(message));
|
|
}
|
|
|
|
|
|
void SecureContext::SetSessionTimeout(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
|
|
if (args.Length() != 1 || !args[0]->IsInt32()) {
|
|
return THROW_ERR_INVALID_ARG_TYPE(
|
|
sc->env(), "Session timeout must be a 32-bit integer");
|
|
}
|
|
|
|
int32_t sessionTimeout = args[0].As<Int32>()->Value();
|
|
SSL_CTX_set_timeout(sc->ctx_.get(), sessionTimeout);
|
|
}
|
|
|
|
|
|
void SecureContext::Close(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
sc->Reset();
|
|
}
|
|
|
|
|
|
// Takes .pfx or .p12 and password in string or buffer format
|
|
void SecureContext::LoadPKCS12(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
std::vector<char> pass;
|
|
bool ret = false;
|
|
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
if (args.Length() < 1) {
|
|
return THROW_ERR_MISSING_ARGS(env, "PFX certificate argument is mandatory");
|
|
}
|
|
|
|
BIOPointer in(LoadBIO(env, args[0]));
|
|
if (!in)
|
|
return env->ThrowError("Unable to load BIO");
|
|
|
|
if (args.Length() >= 2) {
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(env, args[1], "Pass phrase");
|
|
Local<ArrayBufferView> abv = args[1].As<ArrayBufferView>();
|
|
size_t passlen = abv->ByteLength();
|
|
pass.resize(passlen + 1);
|
|
abv->CopyContents(pass.data(), passlen);
|
|
pass[passlen] = '\0';
|
|
}
|
|
|
|
// Free previous certs
|
|
sc->issuer_.reset();
|
|
sc->cert_.reset();
|
|
|
|
X509_STORE* cert_store = SSL_CTX_get_cert_store(sc->ctx_.get());
|
|
|
|
DeleteFnPtr<PKCS12, PKCS12_free> p12;
|
|
EVPKeyPointer pkey;
|
|
X509Pointer cert;
|
|
StackOfX509 extra_certs;
|
|
|
|
PKCS12* p12_ptr = nullptr;
|
|
EVP_PKEY* pkey_ptr = nullptr;
|
|
X509* cert_ptr = nullptr;
|
|
STACK_OF(X509)* extra_certs_ptr = nullptr;
|
|
if (d2i_PKCS12_bio(in.get(), &p12_ptr) &&
|
|
(p12.reset(p12_ptr), true) && // Move ownership to the smart pointer.
|
|
PKCS12_parse(p12.get(), pass.data(),
|
|
&pkey_ptr,
|
|
&cert_ptr,
|
|
&extra_certs_ptr) &&
|
|
(pkey.reset(pkey_ptr), cert.reset(cert_ptr),
|
|
extra_certs.reset(extra_certs_ptr), true) && // Move ownership.
|
|
SSL_CTX_use_certificate_chain(sc->ctx_.get(),
|
|
std::move(cert),
|
|
extra_certs.get(),
|
|
&sc->cert_,
|
|
&sc->issuer_) &&
|
|
SSL_CTX_use_PrivateKey(sc->ctx_.get(), pkey.get())) {
|
|
// Add CA certs too
|
|
for (int i = 0; i < sk_X509_num(extra_certs.get()); i++) {
|
|
X509* ca = sk_X509_value(extra_certs.get(), i);
|
|
|
|
if (cert_store == root_cert_store) {
|
|
cert_store = NewRootCertStore();
|
|
SSL_CTX_set_cert_store(sc->ctx_.get(), cert_store);
|
|
}
|
|
X509_STORE_add_cert(cert_store, ca);
|
|
SSL_CTX_add_client_CA(sc->ctx_.get(), ca);
|
|
}
|
|
ret = true;
|
|
}
|
|
|
|
if (!ret) {
|
|
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
|
const char* str = ERR_reason_error_string(err);
|
|
return env->ThrowError(str);
|
|
}
|
|
}
|
|
|
|
|
|
#ifndef OPENSSL_NO_ENGINE
|
|
void SecureContext::SetClientCertEngine(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
CHECK_EQ(args.Length(), 1);
|
|
CHECK(args[0]->IsString());
|
|
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
|
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
// SSL_CTX_set_client_cert_engine does not itself support multiple
|
|
// calls by cleaning up before overwriting the client_cert_engine
|
|
// internal context variable.
|
|
// Instead of trying to fix up this problem we in turn also do not
|
|
// support multiple calls to SetClientCertEngine.
|
|
if (sc->client_cert_engine_provided_) {
|
|
return env->ThrowError(
|
|
"Multiple calls to SetClientCertEngine are not allowed");
|
|
}
|
|
|
|
const node::Utf8Value engine_id(env->isolate(), args[0]);
|
|
char errmsg[1024];
|
|
DeleteFnPtr<ENGINE, ENGINE_free_fn> engine(
|
|
LoadEngineById(*engine_id, &errmsg));
|
|
|
|
if (!engine)
|
|
return env->ThrowError(errmsg);
|
|
|
|
// Note that this takes another reference to `engine`.
|
|
int r = SSL_CTX_set_client_cert_engine(sc->ctx_.get(), engine.get());
|
|
if (r == 0)
|
|
return ThrowCryptoError(env, ERR_get_error());
|
|
sc->client_cert_engine_provided_ = true;
|
|
}
|
|
#endif // !OPENSSL_NO_ENGINE
|
|
|
|
|
|
void SecureContext::GetTicketKeys(const FunctionCallbackInfo<Value>& args) {
|
|
#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys)
|
|
|
|
SecureContext* wrap;
|
|
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
|
|
|
|
Local<Object> buff = Buffer::New(wrap->env(), 48).ToLocalChecked();
|
|
memcpy(Buffer::Data(buff), wrap->ticket_key_name_, 16);
|
|
memcpy(Buffer::Data(buff) + 16, wrap->ticket_key_hmac_, 16);
|
|
memcpy(Buffer::Data(buff) + 32, wrap->ticket_key_aes_, 16);
|
|
|
|
args.GetReturnValue().Set(buff);
|
|
#endif // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys)
|
|
}
|
|
|
|
|
|
void SecureContext::SetTicketKeys(const FunctionCallbackInfo<Value>& args) {
|
|
#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys)
|
|
SecureContext* wrap;
|
|
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
|
|
Environment* env = wrap->env();
|
|
|
|
// TODO(@sam-github) Move type and len check to js, and CHECK() in C++.
|
|
if (args.Length() < 1) {
|
|
return THROW_ERR_MISSING_ARGS(env, "Ticket keys argument is mandatory");
|
|
}
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Ticket keys");
|
|
ArrayBufferViewContents<char> buf(args[0].As<ArrayBufferView>());
|
|
|
|
if (buf.length() != 48) {
|
|
return THROW_ERR_INVALID_ARG_VALUE(
|
|
env, "Ticket keys length must be 48 bytes");
|
|
}
|
|
|
|
memcpy(wrap->ticket_key_name_, buf.data(), 16);
|
|
memcpy(wrap->ticket_key_hmac_, buf.data() + 16, 16);
|
|
memcpy(wrap->ticket_key_aes_, buf.data() + 32, 16);
|
|
|
|
args.GetReturnValue().Set(true);
|
|
#endif // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys)
|
|
}
|
|
|
|
|
|
void SecureContext::SetFreeListLength(const FunctionCallbackInfo<Value>& args) {
|
|
}
|
|
|
|
|
|
// Currently, EnableTicketKeyCallback and TicketKeyCallback are only present for
|
|
// the regression test in test/parallel/test-https-resume-after-renew.js.
|
|
void SecureContext::EnableTicketKeyCallback(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* wrap;
|
|
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
|
|
|
|
SSL_CTX_set_tlsext_ticket_key_cb(wrap->ctx_.get(), TicketKeyCallback);
|
|
}
|
|
|
|
|
|
int SecureContext::TicketKeyCallback(SSL* ssl,
|
|
unsigned char* name,
|
|
unsigned char* iv,
|
|
EVP_CIPHER_CTX* ectx,
|
|
HMAC_CTX* hctx,
|
|
int enc) {
|
|
static const int kTicketPartSize = 16;
|
|
|
|
SecureContext* sc = static_cast<SecureContext*>(
|
|
SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl)));
|
|
|
|
Environment* env = sc->env();
|
|
HandleScope handle_scope(env->isolate());
|
|
Context::Scope context_scope(env->context());
|
|
|
|
Local<Value> argv[] = {
|
|
Buffer::Copy(env,
|
|
reinterpret_cast<char*>(name),
|
|
kTicketPartSize).ToLocalChecked(),
|
|
Buffer::Copy(env,
|
|
reinterpret_cast<char*>(iv),
|
|
kTicketPartSize).ToLocalChecked(),
|
|
Boolean::New(env->isolate(), enc != 0)
|
|
};
|
|
|
|
Local<Value> ret = node::MakeCallback(env->isolate(),
|
|
sc->object(),
|
|
env->ticketkeycallback_string(),
|
|
arraysize(argv),
|
|
argv,
|
|
{0, 0}).ToLocalChecked();
|
|
Local<Array> arr = ret.As<Array>();
|
|
|
|
int r =
|
|
arr->Get(env->context(),
|
|
kTicketKeyReturnIndex).ToLocalChecked()
|
|
->Int32Value(env->context()).FromJust();
|
|
if (r < 0)
|
|
return r;
|
|
|
|
Local<Value> hmac = arr->Get(env->context(),
|
|
kTicketKeyHMACIndex).ToLocalChecked();
|
|
Local<Value> aes = arr->Get(env->context(),
|
|
kTicketKeyAESIndex).ToLocalChecked();
|
|
if (Buffer::Length(aes) != kTicketPartSize)
|
|
return -1;
|
|
|
|
if (enc) {
|
|
Local<Value> name_val = arr->Get(env->context(),
|
|
kTicketKeyNameIndex).ToLocalChecked();
|
|
Local<Value> iv_val = arr->Get(env->context(),
|
|
kTicketKeyIVIndex).ToLocalChecked();
|
|
|
|
if (Buffer::Length(name_val) != kTicketPartSize ||
|
|
Buffer::Length(iv_val) != kTicketPartSize) {
|
|
return -1;
|
|
}
|
|
|
|
name_val.As<ArrayBufferView>()->CopyContents(name, kTicketPartSize);
|
|
iv_val.As<ArrayBufferView>()->CopyContents(iv, kTicketPartSize);
|
|
}
|
|
|
|
ArrayBufferViewContents<unsigned char> hmac_buf(hmac);
|
|
HMAC_Init_ex(hctx,
|
|
hmac_buf.data(),
|
|
hmac_buf.length(),
|
|
EVP_sha256(),
|
|
nullptr);
|
|
|
|
ArrayBufferViewContents<unsigned char> aes_key(aes.As<ArrayBufferView>());
|
|
if (enc) {
|
|
EVP_EncryptInit_ex(ectx,
|
|
EVP_aes_128_cbc(),
|
|
nullptr,
|
|
aes_key.data(),
|
|
iv);
|
|
} else {
|
|
EVP_DecryptInit_ex(ectx,
|
|
EVP_aes_128_cbc(),
|
|
nullptr,
|
|
aes_key.data(),
|
|
iv);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
int SecureContext::TicketCompatibilityCallback(SSL* ssl,
|
|
unsigned char* name,
|
|
unsigned char* iv,
|
|
EVP_CIPHER_CTX* ectx,
|
|
HMAC_CTX* hctx,
|
|
int enc) {
|
|
SecureContext* sc = static_cast<SecureContext*>(
|
|
SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl)));
|
|
|
|
if (enc) {
|
|
memcpy(name, sc->ticket_key_name_, sizeof(sc->ticket_key_name_));
|
|
if (RAND_bytes(iv, 16) <= 0 ||
|
|
EVP_EncryptInit_ex(ectx, EVP_aes_128_cbc(), nullptr,
|
|
sc->ticket_key_aes_, iv) <= 0 ||
|
|
HMAC_Init_ex(hctx, sc->ticket_key_hmac_, sizeof(sc->ticket_key_hmac_),
|
|
EVP_sha256(), nullptr) <= 0) {
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if (memcmp(name, sc->ticket_key_name_, sizeof(sc->ticket_key_name_)) != 0) {
|
|
// The ticket key name does not match. Discard the ticket.
|
|
return 0;
|
|
}
|
|
|
|
if (EVP_DecryptInit_ex(ectx, EVP_aes_128_cbc(), nullptr, sc->ticket_key_aes_,
|
|
iv) <= 0 ||
|
|
HMAC_Init_ex(hctx, sc->ticket_key_hmac_, sizeof(sc->ticket_key_hmac_),
|
|
EVP_sha256(), nullptr) <= 0) {
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
void SecureContext::CtxGetter(const FunctionCallbackInfo<Value>& info) {
|
|
SecureContext* sc;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sc, info.This());
|
|
Local<External> ext = External::New(info.GetIsolate(), sc->ctx_.get());
|
|
info.GetReturnValue().Set(ext);
|
|
}
|
|
|
|
|
|
template <bool primary>
|
|
void SecureContext::GetCertificate(const FunctionCallbackInfo<Value>& args) {
|
|
SecureContext* wrap;
|
|
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
|
|
Environment* env = wrap->env();
|
|
X509* cert;
|
|
|
|
if (primary)
|
|
cert = wrap->cert_.get();
|
|
else
|
|
cert = wrap->issuer_.get();
|
|
if (cert == nullptr)
|
|
return args.GetReturnValue().SetNull();
|
|
|
|
int size = i2d_X509(cert, nullptr);
|
|
Local<Object> buff = Buffer::New(env, size).ToLocalChecked();
|
|
unsigned char* serialized = reinterpret_cast<unsigned char*>(
|
|
Buffer::Data(buff));
|
|
i2d_X509(cert, &serialized);
|
|
|
|
args.GetReturnValue().Set(buff);
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::AddMethods(Environment* env, Local<FunctionTemplate> t) {
|
|
HandleScope scope(env->isolate());
|
|
|
|
env->SetProtoMethodNoSideEffect(t, "getPeerCertificate", GetPeerCertificate);
|
|
env->SetProtoMethodNoSideEffect(t, "getCertificate", GetCertificate);
|
|
env->SetProtoMethodNoSideEffect(t, "getFinished", GetFinished);
|
|
env->SetProtoMethodNoSideEffect(t, "getPeerFinished", GetPeerFinished);
|
|
env->SetProtoMethodNoSideEffect(t, "getSession", GetSession);
|
|
env->SetProtoMethod(t, "setSession", SetSession);
|
|
env->SetProtoMethod(t, "loadSession", LoadSession);
|
|
env->SetProtoMethodNoSideEffect(t, "isSessionReused", IsSessionReused);
|
|
env->SetProtoMethodNoSideEffect(t, "verifyError", VerifyError);
|
|
env->SetProtoMethodNoSideEffect(t, "getCipher", GetCipher);
|
|
env->SetProtoMethodNoSideEffect(t, "getSharedSigalgs", GetSharedSigalgs);
|
|
env->SetProtoMethodNoSideEffect(
|
|
t, "exportKeyingMaterial", ExportKeyingMaterial);
|
|
env->SetProtoMethod(t, "endParser", EndParser);
|
|
env->SetProtoMethod(t, "certCbDone", CertCbDone);
|
|
env->SetProtoMethod(t, "renegotiate", Renegotiate);
|
|
env->SetProtoMethodNoSideEffect(t, "getTLSTicket", GetTLSTicket);
|
|
env->SetProtoMethod(t, "newSessionDone", NewSessionDone);
|
|
env->SetProtoMethod(t, "setOCSPResponse", SetOCSPResponse);
|
|
env->SetProtoMethod(t, "requestOCSP", RequestOCSP);
|
|
env->SetProtoMethodNoSideEffect(t, "getEphemeralKeyInfo",
|
|
GetEphemeralKeyInfo);
|
|
env->SetProtoMethodNoSideEffect(t, "getProtocol", GetProtocol);
|
|
|
|
#ifdef SSL_set_max_send_fragment
|
|
env->SetProtoMethod(t, "setMaxSendFragment", SetMaxSendFragment);
|
|
#endif // SSL_set_max_send_fragment
|
|
|
|
env->SetProtoMethodNoSideEffect(t, "getALPNNegotiatedProtocol",
|
|
GetALPNNegotiatedProto);
|
|
env->SetProtoMethod(t, "setALPNProtocols", SetALPNProtocols);
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::ConfigureSecureContext(SecureContext* sc) {
|
|
// OCSP stapling
|
|
SSL_CTX_set_tlsext_status_cb(sc->ctx_.get(), TLSExtStatusCallback);
|
|
SSL_CTX_set_tlsext_status_arg(sc->ctx_.get(), nullptr);
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
SSL_SESSION* SSLWrap<Base>::GetSessionCallback(SSL* s,
|
|
const unsigned char* key,
|
|
int len,
|
|
int* copy) {
|
|
Base* w = static_cast<Base*>(SSL_get_app_data(s));
|
|
|
|
*copy = 0;
|
|
return w->next_sess_.release();
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
int SSLWrap<Base>::NewSessionCallback(SSL* s, SSL_SESSION* sess) {
|
|
Base* w = static_cast<Base*>(SSL_get_app_data(s));
|
|
Environment* env = w->ssl_env();
|
|
HandleScope handle_scope(env->isolate());
|
|
Context::Scope context_scope(env->context());
|
|
|
|
if (!w->session_callbacks_)
|
|
return 0;
|
|
|
|
// Check if session is small enough to be stored
|
|
int size = i2d_SSL_SESSION(sess, nullptr);
|
|
if (size > SecureContext::kMaxSessionSize)
|
|
return 0;
|
|
|
|
// Serialize session
|
|
Local<Object> session = Buffer::New(env, size).ToLocalChecked();
|
|
unsigned char* session_data = reinterpret_cast<unsigned char*>(
|
|
Buffer::Data(session));
|
|
memset(session_data, 0, size);
|
|
i2d_SSL_SESSION(sess, &session_data);
|
|
|
|
unsigned int session_id_length;
|
|
const unsigned char* session_id_data = SSL_SESSION_get_id(sess,
|
|
&session_id_length);
|
|
Local<Object> session_id = Buffer::Copy(
|
|
env,
|
|
reinterpret_cast<const char*>(session_id_data),
|
|
session_id_length).ToLocalChecked();
|
|
Local<Value> argv[] = { session_id, session };
|
|
// On servers, we pause the handshake until callback of 'newSession', which
|
|
// calls NewSessionDoneCb(). On clients, there is no callback to wait for.
|
|
if (w->is_server())
|
|
w->awaiting_new_session_ = true;
|
|
w->MakeCallback(env->onnewsession_string(), arraysize(argv), argv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::KeylogCallback(const SSL* s, const char* line) {
|
|
Base* w = static_cast<Base*>(SSL_get_app_data(s));
|
|
Environment* env = w->ssl_env();
|
|
HandleScope handle_scope(env->isolate());
|
|
Context::Scope context_scope(env->context());
|
|
|
|
const size_t size = strlen(line);
|
|
Local<Value> line_bf = Buffer::Copy(env, line, 1 + size).ToLocalChecked();
|
|
char* data = Buffer::Data(line_bf);
|
|
data[size] = '\n';
|
|
w->MakeCallback(env->onkeylog_string(), 1, &line_bf);
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::OnClientHello(void* arg,
|
|
const ClientHelloParser::ClientHello& hello) {
|
|
Base* w = static_cast<Base*>(arg);
|
|
Environment* env = w->ssl_env();
|
|
HandleScope handle_scope(env->isolate());
|
|
Local<Context> context = env->context();
|
|
Context::Scope context_scope(context);
|
|
|
|
Local<Object> hello_obj = Object::New(env->isolate());
|
|
Local<Object> buff = Buffer::Copy(
|
|
env,
|
|
reinterpret_cast<const char*>(hello.session_id()),
|
|
hello.session_size()).ToLocalChecked();
|
|
hello_obj->Set(context, env->session_id_string(), buff).Check();
|
|
if (hello.servername() == nullptr) {
|
|
hello_obj->Set(context,
|
|
env->servername_string(),
|
|
String::Empty(env->isolate())).Check();
|
|
} else {
|
|
Local<String> servername = OneByteString(env->isolate(),
|
|
hello.servername(),
|
|
hello.servername_size());
|
|
hello_obj->Set(context, env->servername_string(), servername).Check();
|
|
}
|
|
hello_obj->Set(context,
|
|
env->tls_ticket_string(),
|
|
Boolean::New(env->isolate(), hello.has_ticket())).Check();
|
|
|
|
Local<Value> argv[] = { hello_obj };
|
|
w->MakeCallback(env->onclienthello_string(), arraysize(argv), argv);
|
|
}
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::GetPeerCertificate(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
Environment* env = w->ssl_env();
|
|
|
|
bool abbreviated = args.Length() < 1 || !args[0]->IsTrue();
|
|
|
|
Local<Value> ret;
|
|
if (GetPeerCert(env, w->ssl_, abbreviated, w->is_server()).ToLocal(&ret))
|
|
args.GetReturnValue().Set(ret);
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::GetCertificate(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
Environment* env = w->ssl_env();
|
|
|
|
Local<Value> ret;
|
|
if (GetCert(env, w->ssl_).ToLocal(&ret))
|
|
args.GetReturnValue().Set(ret);
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::GetFinished(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
|
|
// We cannot just pass nullptr to SSL_get_finished()
|
|
// because it would further be propagated to memcpy(),
|
|
// where the standard requirements as described in ISO/IEC 9899:2011
|
|
// sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated.
|
|
// Thus, we use a dummy byte.
|
|
char dummy[1];
|
|
size_t len = SSL_get_finished(w->ssl_.get(), dummy, sizeof dummy);
|
|
if (len == 0)
|
|
return;
|
|
|
|
AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, len);
|
|
CHECK_EQ(len, SSL_get_finished(w->ssl_.get(), buf.data(), len));
|
|
args.GetReturnValue().Set(buf.ToBuffer().ToLocalChecked());
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::GetPeerFinished(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
|
|
// We cannot just pass nullptr to SSL_get_peer_finished()
|
|
// because it would further be propagated to memcpy(),
|
|
// where the standard requirements as described in ISO/IEC 9899:2011
|
|
// sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated.
|
|
// Thus, we use a dummy byte.
|
|
char dummy[1];
|
|
size_t len = SSL_get_peer_finished(w->ssl_.get(), dummy, sizeof dummy);
|
|
if (len == 0)
|
|
return;
|
|
|
|
AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, len);
|
|
CHECK_EQ(len, SSL_get_peer_finished(w->ssl_.get(), buf.data(), len));
|
|
args.GetReturnValue().Set(buf.ToBuffer().ToLocalChecked());
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::GetSession(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
|
|
SSL_SESSION* sess = SSL_get_session(w->ssl_.get());
|
|
if (sess == nullptr)
|
|
return;
|
|
|
|
int slen = i2d_SSL_SESSION(sess, nullptr);
|
|
if (slen <= 0)
|
|
return; // Invalid or malformed session.
|
|
|
|
AllocatedBuffer sbuf = AllocatedBuffer::AllocateManaged(env, slen);
|
|
unsigned char* p = reinterpret_cast<unsigned char*>(sbuf.data());
|
|
CHECK_LT(0, i2d_SSL_SESSION(sess, &p));
|
|
args.GetReturnValue().Set(sbuf.ToBuffer().ToLocalChecked());
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::SetSession(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
|
|
if (args.Length() < 1)
|
|
return THROW_ERR_MISSING_ARGS(env, "Session argument is mandatory");
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Session");
|
|
|
|
SSLSessionPointer sess = GetTLSSession(args[0]);
|
|
if (sess == nullptr)
|
|
return;
|
|
|
|
if (!SetTLSSession(w->ssl_, sess))
|
|
return env->ThrowError("SSL_set_session error");
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::LoadSession(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
|
|
// TODO(@sam-github) check arg length and types in js, and CHECK in c++
|
|
if (args.Length() >= 1 && Buffer::HasInstance(args[0])) {
|
|
ArrayBufferViewContents<unsigned char> sbuf(args[0]);
|
|
|
|
const unsigned char* p = sbuf.data();
|
|
SSL_SESSION* sess = d2i_SSL_SESSION(nullptr, &p, sbuf.length());
|
|
|
|
// Setup next session and move hello to the BIO buffer
|
|
w->next_sess_.reset(sess);
|
|
}
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::IsSessionReused(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
bool yes = SSL_session_reused(w->ssl_.get());
|
|
args.GetReturnValue().Set(yes);
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::EndParser(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
w->hello_parser_.End();
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::Renegotiate(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
if (SSL_renegotiate(w->ssl_.get()) != 1) {
|
|
return ThrowCryptoError(w->ssl_env(), ERR_get_error());
|
|
}
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::GetTLSTicket(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
Environment* env = w->ssl_env();
|
|
|
|
SSL_SESSION* sess = SSL_get_session(w->ssl_.get());
|
|
if (sess == nullptr)
|
|
return;
|
|
|
|
const unsigned char* ticket;
|
|
size_t length;
|
|
SSL_SESSION_get0_ticket(sess, &ticket, &length);
|
|
|
|
if (ticket == nullptr)
|
|
return;
|
|
|
|
Local<Object> buff = Buffer::Copy(
|
|
env, reinterpret_cast<const char*>(ticket), length).ToLocalChecked();
|
|
|
|
args.GetReturnValue().Set(buff);
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::NewSessionDone(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
w->awaiting_new_session_ = false;
|
|
w->NewSessionDoneCb();
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::SetOCSPResponse(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
Environment* env = w->env();
|
|
|
|
if (args.Length() < 1)
|
|
return THROW_ERR_MISSING_ARGS(env, "OCSP response argument is mandatory");
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "OCSP response");
|
|
|
|
w->ocsp_response_.Reset(args.GetIsolate(), args[0].As<ArrayBufferView>());
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::RequestOCSP(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
|
|
SSL_set_tlsext_status_type(w->ssl_.get(), TLSEXT_STATUSTYPE_ocsp);
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::GetEphemeralKeyInfo(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
CHECK(w->ssl_);
|
|
|
|
// tmp key is available on only client
|
|
if (w->is_server())
|
|
return args.GetReturnValue().SetNull();
|
|
|
|
Local<Object> ret;
|
|
if (GetEphemeralKey(env, w->ssl_).ToLocal(&ret))
|
|
args.GetReturnValue().Set(ret);
|
|
|
|
// TODO(@sam-github) semver-major: else return ThrowCryptoError(env,
|
|
// ERR_get_error())
|
|
}
|
|
|
|
|
|
#ifdef SSL_set_max_send_fragment
|
|
template <class Base>
|
|
void SSLWrap<Base>::SetMaxSendFragment(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(args.Length() >= 1 && args[0]->IsNumber());
|
|
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
|
|
int rv = SSL_set_max_send_fragment(
|
|
w->ssl_.get(),
|
|
args[0]->Int32Value(w->ssl_env()->context()).FromJust());
|
|
args.GetReturnValue().Set(rv);
|
|
}
|
|
#endif // SSL_set_max_send_fragment
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::VerifyError(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
|
|
// XXX(bnoordhuis) The UNABLE_TO_GET_ISSUER_CERT error when there is no
|
|
// peer certificate is questionable but it's compatible with what was
|
|
// here before.
|
|
long x509_verify_error = // NOLINT(runtime/int)
|
|
VerifyPeerCertificate(w->ssl_, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT);
|
|
|
|
if (x509_verify_error == X509_V_OK)
|
|
return args.GetReturnValue().SetNull();
|
|
|
|
const char* reason = X509_verify_cert_error_string(x509_verify_error);
|
|
const char* code = reason;
|
|
code = X509ErrorCode(x509_verify_error);
|
|
|
|
Isolate* isolate = args.GetIsolate();
|
|
Local<String> reason_string = OneByteString(isolate, reason);
|
|
Local<Value> exception_value = Exception::Error(reason_string);
|
|
Local<Object> exception_object =
|
|
exception_value->ToObject(isolate->GetCurrentContext()).ToLocalChecked();
|
|
exception_object->Set(w->env()->context(), w->env()->code_string(),
|
|
OneByteString(isolate, code)).Check();
|
|
args.GetReturnValue().Set(exception_object);
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::GetCipher(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
Environment* env = w->ssl_env();
|
|
|
|
const SSL_CIPHER* c = SSL_get_current_cipher(w->ssl_.get());
|
|
if (c == nullptr)
|
|
return;
|
|
|
|
Local<Object> ret;
|
|
if (GetCipherInfo(env, w->ssl_).ToLocal(&ret))
|
|
args.GetReturnValue().Set(ret);
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::GetSharedSigalgs(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
Environment* env = w->ssl_env();
|
|
|
|
SSL* ssl = w->ssl_.get();
|
|
int nsig = SSL_get_shared_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr,
|
|
nullptr);
|
|
MaybeStackBuffer<Local<Value>, 16> ret_arr(nsig);
|
|
|
|
for (int i = 0; i < nsig; i++) {
|
|
int hash_nid;
|
|
int sign_nid;
|
|
std::string sig_with_md;
|
|
|
|
SSL_get_shared_sigalgs(ssl, i, &sign_nid, &hash_nid, nullptr, nullptr,
|
|
nullptr);
|
|
|
|
switch (sign_nid) {
|
|
case EVP_PKEY_RSA:
|
|
sig_with_md = "RSA+";
|
|
break;
|
|
|
|
case EVP_PKEY_RSA_PSS:
|
|
sig_with_md = "RSA-PSS+";
|
|
break;
|
|
|
|
case EVP_PKEY_DSA:
|
|
sig_with_md = "DSA+";
|
|
break;
|
|
|
|
case EVP_PKEY_EC:
|
|
sig_with_md = "ECDSA+";
|
|
break;
|
|
|
|
case NID_ED25519:
|
|
sig_with_md = "Ed25519+";
|
|
break;
|
|
|
|
case NID_ED448:
|
|
sig_with_md = "Ed448+";
|
|
break;
|
|
#ifndef OPENSSL_NO_GOST
|
|
case NID_id_GostR3410_2001:
|
|
sig_with_md = "gost2001+";
|
|
break;
|
|
|
|
case NID_id_GostR3410_2012_256:
|
|
sig_with_md = "gost2012_256+";
|
|
break;
|
|
|
|
case NID_id_GostR3410_2012_512:
|
|
sig_with_md = "gost2012_512+";
|
|
break;
|
|
#endif // !OPENSSL_NO_GOST
|
|
default:
|
|
const char* sn = OBJ_nid2sn(sign_nid);
|
|
|
|
if (sn != nullptr) {
|
|
sig_with_md = std::string(sn) + "+";
|
|
} else {
|
|
sig_with_md = "UNDEF+";
|
|
}
|
|
break;
|
|
}
|
|
|
|
const char* sn_hash = OBJ_nid2sn(hash_nid);
|
|
if (sn_hash != nullptr) {
|
|
sig_with_md += std::string(sn_hash);
|
|
} else {
|
|
sig_with_md += "UNDEF";
|
|
}
|
|
ret_arr[i] = OneByteString(env->isolate(), sig_with_md.c_str());
|
|
}
|
|
|
|
args.GetReturnValue().Set(
|
|
Array::New(env->isolate(), ret_arr.out(), ret_arr.length()));
|
|
}
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::ExportKeyingMaterial(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(args[0]->IsInt32());
|
|
CHECK(args[1]->IsString());
|
|
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
Environment* env = w->ssl_env();
|
|
|
|
uint32_t olen = args[0].As<Uint32>()->Value();
|
|
node::Utf8Value label(env->isolate(), args[1]);
|
|
|
|
AllocatedBuffer out = AllocatedBuffer::AllocateManaged(env, olen);
|
|
|
|
ByteSource context;
|
|
bool use_context = !args[2]->IsUndefined();
|
|
if (use_context)
|
|
context = ByteSource::FromBuffer(args[2]);
|
|
|
|
if (SSL_export_keying_material(w->ssl_.get(),
|
|
reinterpret_cast<unsigned char*>(out.data()),
|
|
olen,
|
|
*label,
|
|
label.length(),
|
|
reinterpret_cast<const unsigned char*>(
|
|
context.get()),
|
|
context.size(),
|
|
use_context) != 1) {
|
|
return ThrowCryptoError(env, ERR_get_error(), "SSL_export_keying_material");
|
|
}
|
|
|
|
args.GetReturnValue().Set(out.ToBuffer().ToLocalChecked());
|
|
}
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::GetProtocol(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
|
|
const char* tls_version = SSL_get_version(w->ssl_.get());
|
|
args.GetReturnValue().Set(OneByteString(args.GetIsolate(), tls_version));
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
int SSLWrap<Base>::SelectALPNCallback(SSL* s,
|
|
const unsigned char** out,
|
|
unsigned char* outlen,
|
|
const unsigned char* in,
|
|
unsigned int inlen,
|
|
void* arg) {
|
|
Base* w = static_cast<Base*>(SSL_get_app_data(s));
|
|
Environment* env = w->env();
|
|
HandleScope handle_scope(env->isolate());
|
|
Context::Scope context_scope(env->context());
|
|
|
|
Local<Value> alpn_buffer =
|
|
w->object()->GetPrivate(
|
|
env->context(),
|
|
env->alpn_buffer_private_symbol()).ToLocalChecked();
|
|
ArrayBufferViewContents<unsigned char> alpn_protos(alpn_buffer);
|
|
int status = SSL_select_next_proto(const_cast<unsigned char**>(out), outlen,
|
|
alpn_protos.data(), alpn_protos.length(),
|
|
in, inlen);
|
|
// According to 3.2. Protocol Selection of RFC7301, fatal
|
|
// no_application_protocol alert shall be sent but OpenSSL 1.0.2 does not
|
|
// support it yet. See
|
|
// https://rt.openssl.org/Ticket/Display.html?id=3463&user=guest&pass=guest
|
|
return status == OPENSSL_NPN_NEGOTIATED ? SSL_TLSEXT_ERR_OK
|
|
: SSL_TLSEXT_ERR_NOACK;
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::GetALPNNegotiatedProto(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
|
|
const unsigned char* alpn_proto;
|
|
unsigned int alpn_proto_len;
|
|
|
|
SSL_get0_alpn_selected(w->ssl_.get(), &alpn_proto, &alpn_proto_len);
|
|
|
|
Local<Value> result;
|
|
if (alpn_proto_len == 0) {
|
|
result = False(args.GetIsolate());
|
|
} else if (alpn_proto_len == sizeof("h2") - 1 &&
|
|
0 == memcmp(alpn_proto, "h2", sizeof("h2") - 1)) {
|
|
result = w->env()->h2_string();
|
|
} else if (alpn_proto_len == sizeof("http/1.1") - 1 &&
|
|
0 == memcmp(alpn_proto, "http/1.1", sizeof("http/1.1") - 1)) {
|
|
result = w->env()->http_1_1_string();
|
|
} else {
|
|
result = OneByteString(args.GetIsolate(), alpn_proto, alpn_proto_len);
|
|
}
|
|
|
|
args.GetReturnValue().Set(result);
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::SetALPNProtocols(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
Environment* env = w->env();
|
|
if (args.Length() < 1 || !Buffer::HasInstance(args[0]))
|
|
return env->ThrowTypeError("Must give a Buffer as first argument");
|
|
|
|
if (w->is_client()) {
|
|
CHECK(SetALPN(w->ssl_, args[0]));
|
|
} else {
|
|
CHECK(
|
|
w->object()->SetPrivate(
|
|
env->context(),
|
|
env->alpn_buffer_private_symbol(),
|
|
args[0]).FromJust());
|
|
// Server should select ALPN protocol from list of advertised by client
|
|
SSL_CTX_set_alpn_select_cb(SSL_get_SSL_CTX(w->ssl_.get()),
|
|
SelectALPNCallback,
|
|
nullptr);
|
|
}
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
int SSLWrap<Base>::TLSExtStatusCallback(SSL* s, void* arg) {
|
|
Base* w = static_cast<Base*>(SSL_get_app_data(s));
|
|
Environment* env = w->env();
|
|
HandleScope handle_scope(env->isolate());
|
|
|
|
if (w->is_client()) {
|
|
// Incoming response
|
|
Local<Value> arg;
|
|
MaybeLocal<Value> ret = GetSSLOCSPResponse(env, s, Null(env->isolate()));
|
|
if (ret.ToLocal(&arg))
|
|
w->MakeCallback(env->onocspresponse_string(), 1, &arg);
|
|
|
|
// No async acceptance is possible, so always return 1 to accept the
|
|
// response. The listener for 'OCSPResponse' event has no control over
|
|
// return value, but it can .destroy() the connection if the response is not
|
|
// acceptable.
|
|
return 1;
|
|
} else {
|
|
// Outgoing response
|
|
if (w->ocsp_response_.IsEmpty())
|
|
return SSL_TLSEXT_ERR_NOACK;
|
|
|
|
Local<ArrayBufferView> obj = PersistentToLocal::Default(env->isolate(),
|
|
w->ocsp_response_);
|
|
size_t len = obj->ByteLength();
|
|
|
|
// OpenSSL takes control of the pointer after accepting it
|
|
unsigned char* data = MallocOpenSSL<unsigned char>(len);
|
|
obj->CopyContents(data, len);
|
|
|
|
if (!SSL_set_tlsext_status_ocsp_resp(s, data, len))
|
|
OPENSSL_free(data);
|
|
w->ocsp_response_.Reset();
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
}
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::WaitForCertCb(CertCb cb, void* arg) {
|
|
cert_cb_ = cb;
|
|
cert_cb_arg_ = arg;
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
int SSLWrap<Base>::SSLCertCallback(SSL* s, void* arg) {
|
|
Base* w = static_cast<Base*>(SSL_get_app_data(s));
|
|
|
|
if (!w->is_server())
|
|
return 1;
|
|
|
|
if (!w->is_waiting_cert_cb())
|
|
return 1;
|
|
|
|
if (w->cert_cb_running_)
|
|
// Not an error. Suspend handshake with SSL_ERROR_WANT_X509_LOOKUP, and
|
|
// handshake will continue after certcb is done.
|
|
return -1;
|
|
|
|
Environment* env = w->env();
|
|
Local<Context> context = env->context();
|
|
HandleScope handle_scope(env->isolate());
|
|
Context::Scope context_scope(context);
|
|
w->cert_cb_running_ = true;
|
|
|
|
Local<Object> info = Object::New(env->isolate());
|
|
|
|
const char* servername = GetServerName(s);
|
|
if (servername == nullptr) {
|
|
info->Set(context,
|
|
env->servername_string(),
|
|
String::Empty(env->isolate())).Check();
|
|
} else {
|
|
Local<String> str = OneByteString(env->isolate(), servername,
|
|
strlen(servername));
|
|
info->Set(context, env->servername_string(), str).Check();
|
|
}
|
|
|
|
const bool ocsp = (SSL_get_tlsext_status_type(s) == TLSEXT_STATUSTYPE_ocsp);
|
|
info->Set(context, env->ocsp_request_string(),
|
|
Boolean::New(env->isolate(), ocsp)).Check();
|
|
|
|
Local<Value> argv[] = { info };
|
|
w->MakeCallback(env->oncertcb_string(), arraysize(argv), argv);
|
|
|
|
if (!w->cert_cb_running_)
|
|
return 1;
|
|
|
|
// Performing async action, wait...
|
|
return -1;
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::CertCbDone(const FunctionCallbackInfo<Value>& args) {
|
|
Base* w;
|
|
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
|
|
Environment* env = w->env();
|
|
|
|
CHECK(w->is_waiting_cert_cb() && w->cert_cb_running_);
|
|
|
|
Local<Object> object = w->object();
|
|
Local<Value> ctx = object->Get(env->context(),
|
|
env->sni_context_string()).ToLocalChecked();
|
|
Local<FunctionTemplate> cons = env->secure_context_constructor_template();
|
|
|
|
// Not an object, probably undefined or null
|
|
if (!ctx->IsObject())
|
|
goto fire_cb;
|
|
|
|
if (cons->HasInstance(ctx)) {
|
|
SecureContext* sc = Unwrap<SecureContext>(ctx.As<Object>());
|
|
CHECK_NOT_NULL(sc);
|
|
// Store the SNI context for later use.
|
|
w->sni_context_ = BaseObjectPtr<SecureContext>(sc);
|
|
|
|
if (UseSNIContext(w->ssl_, w->sni_context_) && !w->SetCACerts(sc)) {
|
|
// Not clear why sometimes we throw error, and sometimes we call
|
|
// onerror(). Both cause .destroy(), but onerror does a bit more.
|
|
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
|
return ThrowCryptoError(env, err, "CertCbDone");
|
|
}
|
|
} else {
|
|
// Failure: incorrect SNI context object
|
|
Local<Value> err = Exception::TypeError(env->sni_context_err_string());
|
|
w->MakeCallback(env->onerror_string(), 1, &err);
|
|
return;
|
|
}
|
|
|
|
fire_cb:
|
|
CertCb cb;
|
|
void* arg;
|
|
|
|
cb = w->cert_cb_;
|
|
arg = w->cert_cb_arg_;
|
|
|
|
w->cert_cb_running_ = false;
|
|
w->cert_cb_ = nullptr;
|
|
w->cert_cb_arg_ = nullptr;
|
|
|
|
cb(arg);
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::DestroySSL() {
|
|
if (!ssl_)
|
|
return;
|
|
|
|
env_->isolate()->AdjustAmountOfExternalAllocatedMemory(-kExternalSize);
|
|
ssl_.reset();
|
|
}
|
|
|
|
|
|
template <class Base>
|
|
int SSLWrap<Base>::SetCACerts(SecureContext* sc) {
|
|
int err = SSL_set1_verify_cert_store(ssl_.get(),
|
|
SSL_CTX_get_cert_store(sc->ctx_.get()));
|
|
if (err != 1)
|
|
return err;
|
|
|
|
STACK_OF(X509_NAME)* list = SSL_dup_CA_list(
|
|
SSL_CTX_get_client_CA_list(sc->ctx_.get()));
|
|
|
|
// NOTE: `SSL_set_client_CA_list` takes the ownership of `list`
|
|
SSL_set_client_CA_list(ssl_.get(), list);
|
|
return 1;
|
|
}
|
|
|
|
template <class Base>
|
|
void SSLWrap<Base>::MemoryInfo(MemoryTracker* tracker) const {
|
|
tracker->TrackField("ocsp_response", ocsp_response_);
|
|
tracker->TrackField("sni_context", sni_context_);
|
|
}
|
|
|
|
int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) {
|
|
// From https://www.openssl.org/docs/man1.1.1/man3/SSL_verify_cb:
|
|
//
|
|
// If VerifyCallback returns 1, the verification process is continued. If
|
|
// VerifyCallback always returns 1, the TLS/SSL handshake will not be
|
|
// terminated with respect to verification failures and the connection will
|
|
// be established. The calling process can however retrieve the error code
|
|
// of the last verification error using SSL_get_verify_result(3) or by
|
|
// maintaining its own error storage managed by VerifyCallback.
|
|
//
|
|
// Since we cannot perform I/O quickly enough with X509_STORE_CTX_ APIs in
|
|
// this callback, we ignore all preverify_ok errors and let the handshake
|
|
// continue. It is imperative that the user use Connection::VerifyError after
|
|
// the 'secure' callback has been made.
|
|
return 1;
|
|
}
|
|
|
|
static bool IsSupportedAuthenticatedMode(const EVP_CIPHER* cipher) {
|
|
const int mode = EVP_CIPHER_mode(cipher);
|
|
// Check `chacha20-poly1305` separately, it is also an AEAD cipher,
|
|
// but its mode is 0 which doesn't indicate
|
|
return EVP_CIPHER_nid(cipher) == NID_chacha20_poly1305 ||
|
|
mode == EVP_CIPH_CCM_MODE ||
|
|
mode == EVP_CIPH_GCM_MODE ||
|
|
IS_OCB_MODE(mode);
|
|
}
|
|
|
|
static bool IsSupportedAuthenticatedMode(const EVP_CIPHER_CTX* ctx) {
|
|
const EVP_CIPHER* cipher = EVP_CIPHER_CTX_cipher(ctx);
|
|
return IsSupportedAuthenticatedMode(cipher);
|
|
}
|
|
|
|
enum class ParseKeyResult {
|
|
kParseKeyOk,
|
|
kParseKeyNotRecognized,
|
|
kParseKeyNeedPassphrase,
|
|
kParseKeyFailed
|
|
};
|
|
|
|
static ParseKeyResult TryParsePublicKey(
|
|
EVPKeyPointer* pkey,
|
|
const BIOPointer& bp,
|
|
const char* name,
|
|
// NOLINTNEXTLINE(runtime/int)
|
|
const std::function<EVP_PKEY*(const unsigned char** p, long l)>& parse) {
|
|
unsigned char* der_data;
|
|
long der_len; // NOLINT(runtime/int)
|
|
|
|
// This skips surrounding data and decodes PEM to DER.
|
|
{
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
if (PEM_bytes_read_bio(&der_data, &der_len, nullptr, name,
|
|
bp.get(), nullptr, nullptr) != 1)
|
|
return ParseKeyResult::kParseKeyNotRecognized;
|
|
}
|
|
|
|
// OpenSSL might modify the pointer, so we need to make a copy before parsing.
|
|
const unsigned char* p = der_data;
|
|
pkey->reset(parse(&p, der_len));
|
|
OPENSSL_clear_free(der_data, der_len);
|
|
|
|
return *pkey ? ParseKeyResult::kParseKeyOk :
|
|
ParseKeyResult::kParseKeyFailed;
|
|
}
|
|
|
|
static ParseKeyResult ParsePublicKeyPEM(EVPKeyPointer* pkey,
|
|
const char* key_pem,
|
|
int key_pem_len) {
|
|
BIOPointer bp(BIO_new_mem_buf(const_cast<char*>(key_pem), key_pem_len));
|
|
if (!bp)
|
|
return ParseKeyResult::kParseKeyFailed;
|
|
|
|
ParseKeyResult ret;
|
|
|
|
// Try parsing as a SubjectPublicKeyInfo first.
|
|
ret = TryParsePublicKey(pkey, bp, "PUBLIC KEY",
|
|
[](const unsigned char** p, long l) { // NOLINT(runtime/int)
|
|
return d2i_PUBKEY(nullptr, p, l);
|
|
});
|
|
if (ret != ParseKeyResult::kParseKeyNotRecognized)
|
|
return ret;
|
|
|
|
// Maybe it is PKCS#1.
|
|
CHECK(BIO_reset(bp.get()));
|
|
ret = TryParsePublicKey(pkey, bp, "RSA PUBLIC KEY",
|
|
[](const unsigned char** p, long l) { // NOLINT(runtime/int)
|
|
return d2i_PublicKey(EVP_PKEY_RSA, nullptr, p, l);
|
|
});
|
|
if (ret != ParseKeyResult::kParseKeyNotRecognized)
|
|
return ret;
|
|
|
|
// X.509 fallback.
|
|
CHECK(BIO_reset(bp.get()));
|
|
return TryParsePublicKey(pkey, bp, "CERTIFICATE",
|
|
[](const unsigned char** p, long l) { // NOLINT(runtime/int)
|
|
X509Pointer x509(d2i_X509(nullptr, p, l));
|
|
return x509 ? X509_get_pubkey(x509.get()) : nullptr;
|
|
});
|
|
}
|
|
|
|
static ParseKeyResult ParsePublicKey(EVPKeyPointer* pkey,
|
|
const PublicKeyEncodingConfig& config,
|
|
const char* key,
|
|
size_t key_len) {
|
|
if (config.format_ == kKeyFormatPEM) {
|
|
return ParsePublicKeyPEM(pkey, key, key_len);
|
|
} else {
|
|
CHECK_EQ(config.format_, kKeyFormatDER);
|
|
|
|
const unsigned char* p = reinterpret_cast<const unsigned char*>(key);
|
|
if (config.type_.ToChecked() == kKeyEncodingPKCS1) {
|
|
pkey->reset(d2i_PublicKey(EVP_PKEY_RSA, nullptr, &p, key_len));
|
|
} else {
|
|
CHECK_EQ(config.type_.ToChecked(), kKeyEncodingSPKI);
|
|
pkey->reset(d2i_PUBKEY(nullptr, &p, key_len));
|
|
}
|
|
|
|
return *pkey ? ParseKeyResult::kParseKeyOk :
|
|
ParseKeyResult::kParseKeyFailed;
|
|
}
|
|
}
|
|
|
|
static inline Local<Value> BIOToStringOrBuffer(Environment* env,
|
|
BIO* bio,
|
|
PKFormatType format) {
|
|
BUF_MEM* bptr;
|
|
BIO_get_mem_ptr(bio, &bptr);
|
|
if (format == kKeyFormatPEM) {
|
|
// PEM is an ASCII format, so we will return it as a string.
|
|
return String::NewFromUtf8(env->isolate(), bptr->data,
|
|
NewStringType::kNormal,
|
|
bptr->length).ToLocalChecked();
|
|
} else {
|
|
CHECK_EQ(format, kKeyFormatDER);
|
|
// DER is binary, return it as a buffer.
|
|
return Buffer::Copy(env, bptr->data, bptr->length).ToLocalChecked();
|
|
}
|
|
}
|
|
|
|
static bool WritePublicKeyInner(EVP_PKEY* pkey,
|
|
const BIOPointer& bio,
|
|
const PublicKeyEncodingConfig& config) {
|
|
if (config.type_.ToChecked() == kKeyEncodingPKCS1) {
|
|
// PKCS#1 is only valid for RSA keys.
|
|
CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_RSA);
|
|
RSAPointer rsa(EVP_PKEY_get1_RSA(pkey));
|
|
if (config.format_ == kKeyFormatPEM) {
|
|
// Encode PKCS#1 as PEM.
|
|
return PEM_write_bio_RSAPublicKey(bio.get(), rsa.get()) == 1;
|
|
} else {
|
|
// Encode PKCS#1 as DER.
|
|
CHECK_EQ(config.format_, kKeyFormatDER);
|
|
return i2d_RSAPublicKey_bio(bio.get(), rsa.get()) == 1;
|
|
}
|
|
} else {
|
|
CHECK_EQ(config.type_.ToChecked(), 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
static MaybeLocal<Value> WritePublicKey(Environment* env,
|
|
EVP_PKEY* pkey,
|
|
const PublicKeyEncodingConfig& config) {
|
|
BIOPointer bio(BIO_new(BIO_s_mem()));
|
|
CHECK(bio);
|
|
|
|
if (!WritePublicKeyInner(pkey, bio, config)) {
|
|
ThrowCryptoError(env, ERR_get_error(), "Failed to encode public key");
|
|
return MaybeLocal<Value>();
|
|
}
|
|
return BIOToStringOrBuffer(env, bio.get(), config.format_);
|
|
}
|
|
|
|
static bool IsASN1Sequence(const unsigned char* data, size_t size,
|
|
size_t* data_offset, size_t* data_size) {
|
|
if (size < 2 || data[0] != 0x30)
|
|
return false;
|
|
|
|
if (data[1] & 0x80) {
|
|
// Long form.
|
|
size_t n_bytes = data[1] & ~0x80;
|
|
if (n_bytes + 2 > size || n_bytes > sizeof(size_t))
|
|
return false;
|
|
size_t length = 0;
|
|
for (size_t i = 0; i < n_bytes; i++)
|
|
length = (length << 8) | data[i + 2];
|
|
*data_offset = 2 + n_bytes;
|
|
*data_size = std::min(size - 2 - n_bytes, length);
|
|
} else {
|
|
// Short form.
|
|
*data_offset = 2;
|
|
*data_size = std::min<size_t>(size - 2, data[1]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool IsRSAPrivateKey(const unsigned char* data, size_t size) {
|
|
// Both RSAPrivateKey and RSAPublicKey structures start with a SEQUENCE.
|
|
size_t offset, len;
|
|
if (!IsASN1Sequence(data, size, &offset, &len))
|
|
return false;
|
|
|
|
// An RSAPrivateKey sequence always starts with a single-byte integer whose
|
|
// value is either 0 or 1, whereas an RSAPublicKey starts with the modulus
|
|
// (which is the product of two primes and therefore at least 4), so we can
|
|
// decide the type of the structure based on the first three bytes of the
|
|
// sequence.
|
|
return len >= 3 &&
|
|
data[offset] == 2 &&
|
|
data[offset + 1] == 1 &&
|
|
!(data[offset + 2] & 0xfe);
|
|
}
|
|
|
|
static bool IsEncryptedPrivateKeyInfo(const unsigned char* data, size_t size) {
|
|
// Both PrivateKeyInfo and EncryptedPrivateKeyInfo start with a SEQUENCE.
|
|
size_t offset, len;
|
|
if (!IsASN1Sequence(data, size, &offset, &len))
|
|
return false;
|
|
|
|
// A PrivateKeyInfo sequence always starts with an integer whereas an
|
|
// EncryptedPrivateKeyInfo starts with an AlgorithmIdentifier.
|
|
return len >= 1 &&
|
|
data[offset] != 2;
|
|
}
|
|
|
|
static ParseKeyResult ParsePrivateKey(EVPKeyPointer* pkey,
|
|
const PrivateKeyEncodingConfig& config,
|
|
const char* key,
|
|
size_t key_len) {
|
|
// OpenSSL needs a non-const pointer, that's why the const_cast is required.
|
|
char* const passphrase = const_cast<char*>(config.passphrase_.get());
|
|
|
|
if (config.format_ == kKeyFormatPEM) {
|
|
BIOPointer bio(BIO_new_mem_buf(key, key_len));
|
|
if (!bio)
|
|
return ParseKeyResult::kParseKeyFailed;
|
|
|
|
pkey->reset(PEM_read_bio_PrivateKey(bio.get(),
|
|
nullptr,
|
|
PasswordCallback,
|
|
passphrase));
|
|
} else {
|
|
CHECK_EQ(config.format_, kKeyFormatDER);
|
|
|
|
if (config.type_.ToChecked() == kKeyEncodingPKCS1) {
|
|
const unsigned char* p = reinterpret_cast<const unsigned char*>(key);
|
|
pkey->reset(d2i_PrivateKey(EVP_PKEY_RSA, nullptr, &p, key_len));
|
|
} else if (config.type_.ToChecked() == kKeyEncodingPKCS8) {
|
|
BIOPointer bio(BIO_new_mem_buf(key, key_len));
|
|
if (!bio)
|
|
return ParseKeyResult::kParseKeyFailed;
|
|
|
|
if (IsEncryptedPrivateKeyInfo(
|
|
reinterpret_cast<const unsigned char*>(key), key_len)) {
|
|
pkey->reset(d2i_PKCS8PrivateKey_bio(bio.get(),
|
|
nullptr,
|
|
PasswordCallback,
|
|
passphrase));
|
|
} else {
|
|
PKCS8Pointer p8inf(d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr));
|
|
if (p8inf)
|
|
pkey->reset(EVP_PKCS82PKEY(p8inf.get()));
|
|
}
|
|
} else {
|
|
CHECK_EQ(config.type_.ToChecked(), kKeyEncodingSEC1);
|
|
const unsigned char* p = reinterpret_cast<const unsigned char*>(key);
|
|
pkey->reset(d2i_PrivateKey(EVP_PKEY_EC, nullptr, &p, key_len));
|
|
}
|
|
}
|
|
|
|
// OpenSSL can fail to parse the key but still return a non-null pointer.
|
|
unsigned long err = ERR_peek_error(); // NOLINT(runtime/int)
|
|
if (err != 0)
|
|
pkey->reset();
|
|
|
|
if (*pkey)
|
|
return ParseKeyResult::kParseKeyOk;
|
|
if (ERR_GET_LIB(err) == ERR_LIB_PEM &&
|
|
ERR_GET_REASON(err) == PEM_R_BAD_PASSWORD_READ) {
|
|
if (config.passphrase_.get() == nullptr)
|
|
return ParseKeyResult::kParseKeyNeedPassphrase;
|
|
}
|
|
return ParseKeyResult::kParseKeyFailed;
|
|
}
|
|
|
|
ByteSource::ByteSource(ByteSource&& other)
|
|
: data_(other.data_),
|
|
allocated_data_(other.allocated_data_),
|
|
size_(other.size_) {
|
|
other.allocated_data_ = nullptr;
|
|
}
|
|
|
|
ByteSource::~ByteSource() {
|
|
OPENSSL_clear_free(allocated_data_, size_);
|
|
}
|
|
|
|
ByteSource& ByteSource::operator=(ByteSource&& other) {
|
|
if (&other != this) {
|
|
OPENSSL_clear_free(allocated_data_, size_);
|
|
data_ = other.data_;
|
|
allocated_data_ = other.allocated_data_;
|
|
other.allocated_data_ = nullptr;
|
|
size_ = other.size_;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
const char* ByteSource::get() const {
|
|
return data_;
|
|
}
|
|
|
|
size_t ByteSource::size() const {
|
|
return size_;
|
|
}
|
|
|
|
ByteSource ByteSource::FromStringOrBuffer(Environment* env,
|
|
Local<Value> value) {
|
|
return Buffer::HasInstance(value) ? FromBuffer(value)
|
|
: FromString(env, value.As<String>());
|
|
}
|
|
|
|
ByteSource ByteSource::FromString(Environment* env, Local<String> str,
|
|
bool ntc) {
|
|
CHECK(str->IsString());
|
|
size_t size = str->Utf8Length(env->isolate());
|
|
size_t alloc_size = ntc ? size + 1 : size;
|
|
char* data = MallocOpenSSL<char>(alloc_size);
|
|
int opts = String::NO_OPTIONS;
|
|
if (!ntc) opts |= String::NO_NULL_TERMINATION;
|
|
str->WriteUtf8(env->isolate(), data, alloc_size, nullptr, opts);
|
|
return Allocated(data, size);
|
|
}
|
|
|
|
ByteSource ByteSource::FromBuffer(Local<Value> buffer, bool ntc) {
|
|
CHECK(buffer->IsArrayBufferView());
|
|
Local<ArrayBufferView> abv = buffer.As<ArrayBufferView>();
|
|
size_t size = abv->ByteLength();
|
|
if (ntc) {
|
|
char* data = MallocOpenSSL<char>(size + 1);
|
|
abv->CopyContents(data, size);
|
|
data[size] = 0;
|
|
return Allocated(data, size);
|
|
}
|
|
return Foreign(Buffer::Data(buffer), size);
|
|
}
|
|
|
|
ByteSource ByteSource::NullTerminatedCopy(Environment* env,
|
|
Local<Value> value) {
|
|
return Buffer::HasInstance(value) ? FromBuffer(value, true)
|
|
: FromString(env, value.As<String>(), true);
|
|
}
|
|
|
|
ByteSource ByteSource::FromSymmetricKeyObjectHandle(Local<Value> handle) {
|
|
CHECK(handle->IsObject());
|
|
KeyObjectHandle* key = Unwrap<KeyObjectHandle>(handle.As<Object>());
|
|
CHECK_NOT_NULL(key);
|
|
return Foreign(key->Data()->GetSymmetricKey(),
|
|
key->Data()->GetSymmetricKeySize());
|
|
}
|
|
|
|
ByteSource::ByteSource(const char* data, char* allocated_data, size_t size)
|
|
: data_(data),
|
|
allocated_data_(allocated_data),
|
|
size_(size) {}
|
|
|
|
ByteSource ByteSource::Allocated(char* data, size_t size) {
|
|
return ByteSource(data, data, size);
|
|
}
|
|
|
|
ByteSource ByteSource::Foreign(const char* data, size_t size) {
|
|
return ByteSource(data, nullptr, size);
|
|
}
|
|
|
|
enum KeyEncodingContext {
|
|
kKeyContextInput,
|
|
kKeyContextExport,
|
|
kKeyContextGenerate
|
|
};
|
|
|
|
static void GetKeyFormatAndTypeFromJs(
|
|
AsymmetricKeyEncodingConfig* config,
|
|
const FunctionCallbackInfo<Value>& args,
|
|
unsigned int* offset,
|
|
KeyEncodingContext context) {
|
|
// 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;
|
|
} else {
|
|
config->output_key_object_ = false;
|
|
|
|
CHECK(args[*offset]->IsInt32());
|
|
config->format_ = static_cast<PKFormatType>(
|
|
args[*offset].As<Int32>()->Value());
|
|
|
|
if (args[*offset + 1]->IsInt32()) {
|
|
config->type_ = Just<PKEncodingType>(static_cast<PKEncodingType>(
|
|
args[*offset + 1].As<Int32>()->Value()));
|
|
} else {
|
|
CHECK(context == kKeyContextInput && config->format_ == kKeyFormatPEM);
|
|
CHECK(args[*offset + 1]->IsNullOrUndefined());
|
|
config->type_ = Nothing<PKEncodingType>();
|
|
}
|
|
}
|
|
|
|
*offset += 2;
|
|
}
|
|
|
|
static PublicKeyEncodingConfig GetPublicKeyEncodingFromJs(
|
|
const FunctionCallbackInfo<Value>& args,
|
|
unsigned int* offset,
|
|
KeyEncodingContext context) {
|
|
PublicKeyEncodingConfig result;
|
|
GetKeyFormatAndTypeFromJs(&result, args, offset, context);
|
|
return result;
|
|
}
|
|
|
|
static inline ManagedEVPPKey GetParsedKey(Environment* env,
|
|
EVPKeyPointer&& pkey,
|
|
ParseKeyResult ret,
|
|
const char* default_msg) {
|
|
switch (ret) {
|
|
case ParseKeyResult::kParseKeyOk:
|
|
CHECK(pkey);
|
|
break;
|
|
case ParseKeyResult::kParseKeyNeedPassphrase:
|
|
THROW_ERR_MISSING_PASSPHRASE(env,
|
|
"Passphrase required for encrypted key");
|
|
break;
|
|
default:
|
|
ThrowCryptoError(env, ERR_get_error(), default_msg);
|
|
}
|
|
|
|
return ManagedEVPPKey(std::move(pkey));
|
|
}
|
|
|
|
static NonCopyableMaybe<PrivateKeyEncodingConfig> GetPrivateKeyEncodingFromJs(
|
|
const FunctionCallbackInfo<Value>& args,
|
|
unsigned int* offset,
|
|
KeyEncodingContext context) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
PrivateKeyEncodingConfig result;
|
|
GetKeyFormatAndTypeFromJs(&result, args, offset, context);
|
|
|
|
if (result.output_key_object_) {
|
|
if (context != kKeyContextInput)
|
|
(*offset)++;
|
|
} else {
|
|
bool needs_passphrase = false;
|
|
if (context != kKeyContextInput) {
|
|
if (args[*offset]->IsString()) {
|
|
String::Utf8Value cipher_name(env->isolate(),
|
|
args[*offset].As<String>());
|
|
result.cipher_ = EVP_get_cipherbyname(*cipher_name);
|
|
if (result.cipher_ == nullptr) {
|
|
THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env);
|
|
return NonCopyableMaybe<PrivateKeyEncodingConfig>();
|
|
}
|
|
needs_passphrase = true;
|
|
} else {
|
|
CHECK(args[*offset]->IsNullOrUndefined());
|
|
result.cipher_ = nullptr;
|
|
}
|
|
(*offset)++;
|
|
}
|
|
|
|
if (args[*offset]->IsString() || Buffer::HasInstance(args[*offset])) {
|
|
CHECK_IMPLIES(context != kKeyContextInput, result.cipher_ != nullptr);
|
|
|
|
result.passphrase_ = ByteSource::NullTerminatedCopy(env, args[*offset]);
|
|
} else {
|
|
CHECK(args[*offset]->IsNullOrUndefined() && !needs_passphrase);
|
|
}
|
|
}
|
|
|
|
(*offset)++;
|
|
return NonCopyableMaybe<PrivateKeyEncodingConfig>(std::move(result));
|
|
}
|
|
|
|
static ManagedEVPPKey GetPrivateKeyFromJs(
|
|
const FunctionCallbackInfo<Value>& args,
|
|
unsigned int* offset,
|
|
bool allow_key_object) {
|
|
if (args[*offset]->IsString() || Buffer::HasInstance(args[*offset])) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
ByteSource key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]);
|
|
NonCopyableMaybe<PrivateKeyEncodingConfig> config =
|
|
GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput);
|
|
if (config.IsEmpty())
|
|
return ManagedEVPPKey();
|
|
|
|
EVPKeyPointer pkey;
|
|
ParseKeyResult ret =
|
|
ParsePrivateKey(&pkey, config.Release(), key.get(), key.size());
|
|
return GetParsedKey(env, std::move(pkey), ret,
|
|
"Failed to read private key");
|
|
} else {
|
|
CHECK(args[*offset]->IsObject() && allow_key_object);
|
|
KeyObjectHandle* key;
|
|
ASSIGN_OR_RETURN_UNWRAP(&key, args[*offset].As<Object>(), ManagedEVPPKey());
|
|
CHECK_EQ(key->Data()->GetKeyType(), kKeyTypePrivate);
|
|
(*offset) += 4;
|
|
return key->Data()->GetAsymmetricKey();
|
|
}
|
|
}
|
|
|
|
static ManagedEVPPKey GetPublicOrPrivateKeyFromJs(
|
|
const FunctionCallbackInfo<Value>& args,
|
|
unsigned int* offset) {
|
|
if (args[*offset]->IsString() || Buffer::HasInstance(args[*offset])) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
ByteSource data = ByteSource::FromStringOrBuffer(env, args[(*offset)++]);
|
|
NonCopyableMaybe<PrivateKeyEncodingConfig> config_ =
|
|
GetPrivateKeyEncodingFromJs(args, offset, kKeyContextInput);
|
|
if (config_.IsEmpty())
|
|
return ManagedEVPPKey();
|
|
|
|
ParseKeyResult ret;
|
|
PrivateKeyEncodingConfig config = config_.Release();
|
|
EVPKeyPointer pkey;
|
|
if (config.format_ == kKeyFormatPEM) {
|
|
// For PEM, we can easily determine whether it is a public or private key
|
|
// by looking for the respective PEM tags.
|
|
ret = ParsePublicKeyPEM(&pkey, data.get(), data.size());
|
|
if (ret == ParseKeyResult::kParseKeyNotRecognized) {
|
|
ret = ParsePrivateKey(&pkey, config, data.get(), data.size());
|
|
}
|
|
} else {
|
|
// 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_.ToChecked()) {
|
|
case kKeyEncodingPKCS1:
|
|
is_public = !IsRSAPrivateKey(
|
|
reinterpret_cast<const unsigned char*>(data.get()), data.size());
|
|
break;
|
|
case kKeyEncodingSPKI:
|
|
is_public = true;
|
|
break;
|
|
case kKeyEncodingPKCS8:
|
|
case kKeyEncodingSEC1:
|
|
is_public = false;
|
|
break;
|
|
default:
|
|
UNREACHABLE("Invalid key encoding type");
|
|
}
|
|
|
|
if (is_public) {
|
|
ret = ParsePublicKey(&pkey, config, data.get(), data.size());
|
|
} else {
|
|
ret = ParsePrivateKey(&pkey, config, data.get(), data.size());
|
|
}
|
|
}
|
|
|
|
return GetParsedKey(env, std::move(pkey), ret,
|
|
"Failed to read asymmetric key");
|
|
} else {
|
|
CHECK(args[*offset]->IsObject());
|
|
KeyObjectHandle* key = Unwrap<KeyObjectHandle>(args[*offset].As<Object>());
|
|
CHECK_NOT_NULL(key);
|
|
CHECK_NE(key->Data()->GetKeyType(), kKeyTypeSecret);
|
|
(*offset) += 4;
|
|
return key->Data()->GetAsymmetricKey();
|
|
}
|
|
}
|
|
|
|
static MaybeLocal<Value> WritePrivateKey(
|
|
Environment* env,
|
|
EVP_PKEY* pkey,
|
|
const PrivateKeyEncodingConfig& config) {
|
|
BIOPointer bio(BIO_new(BIO_s_mem()));
|
|
CHECK(bio);
|
|
|
|
bool err;
|
|
|
|
if (config.type_.ToChecked() == kKeyEncodingPKCS1) {
|
|
// PKCS#1 is only permitted for RSA keys.
|
|
CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_RSA);
|
|
|
|
RSAPointer rsa(EVP_PKEY_get1_RSA(pkey));
|
|
if (config.format_ == kKeyFormatPEM) {
|
|
// Encode PKCS#1 as PEM.
|
|
const char* pass = config.passphrase_.get();
|
|
err = PEM_write_bio_RSAPrivateKey(
|
|
bio.get(), rsa.get(),
|
|
config.cipher_,
|
|
reinterpret_cast<unsigned char*>(const_cast<char*>(pass)),
|
|
config.passphrase_.size(),
|
|
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.get()) != 1;
|
|
}
|
|
} else if (config.type_.ToChecked() == kKeyEncodingPKCS8) {
|
|
if (config.format_ == kKeyFormatPEM) {
|
|
// Encode PKCS#8 as PEM.
|
|
err = PEM_write_bio_PKCS8PrivateKey(
|
|
bio.get(), pkey,
|
|
config.cipher_,
|
|
const_cast<char*>(config.passphrase_.get()),
|
|
config.passphrase_.size(),
|
|
nullptr, nullptr) != 1;
|
|
} else {
|
|
// Encode PKCS#8 as DER.
|
|
CHECK_EQ(config.format_, kKeyFormatDER);
|
|
err = i2d_PKCS8PrivateKey_bio(
|
|
bio.get(), pkey,
|
|
config.cipher_,
|
|
const_cast<char*>(config.passphrase_.get()),
|
|
config.passphrase_.size(),
|
|
nullptr, nullptr) != 1;
|
|
}
|
|
} else {
|
|
CHECK_EQ(config.type_.ToChecked(), kKeyEncodingSEC1);
|
|
|
|
// SEC1 is only permitted for EC keys.
|
|
CHECK_EQ(EVP_PKEY_id(pkey), EVP_PKEY_EC);
|
|
|
|
ECKeyPointer ec_key(EVP_PKEY_get1_EC_KEY(pkey));
|
|
if (config.format_ == kKeyFormatPEM) {
|
|
// Encode SEC1 as PEM.
|
|
const char* pass = config.passphrase_.get();
|
|
err = PEM_write_bio_ECPrivateKey(
|
|
bio.get(), ec_key.get(),
|
|
config.cipher_,
|
|
reinterpret_cast<unsigned char*>(const_cast<char*>(pass)),
|
|
config.passphrase_.size(),
|
|
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.get()) != 1;
|
|
}
|
|
}
|
|
|
|
if (err) {
|
|
ThrowCryptoError(env, ERR_get_error(), "Failed to encode private key");
|
|
return MaybeLocal<Value>();
|
|
}
|
|
return BIOToStringOrBuffer(env, bio.get(), config.format_);
|
|
}
|
|
|
|
ManagedEVPPKey::ManagedEVPPKey(EVPKeyPointer&& pkey) : pkey_(std::move(pkey)) {}
|
|
|
|
ManagedEVPPKey::ManagedEVPPKey(const ManagedEVPPKey& that) {
|
|
*this = that;
|
|
}
|
|
|
|
ManagedEVPPKey& ManagedEVPPKey::operator=(const ManagedEVPPKey& that) {
|
|
pkey_.reset(that.get());
|
|
|
|
if (pkey_)
|
|
EVP_PKEY_up_ref(pkey_.get());
|
|
|
|
return *this;
|
|
}
|
|
|
|
ManagedEVPPKey::operator bool() const {
|
|
return !!pkey_;
|
|
}
|
|
|
|
EVP_PKEY* ManagedEVPPKey::get() const {
|
|
return pkey_.get();
|
|
}
|
|
|
|
std::shared_ptr<KeyObjectData> KeyObjectData::CreateSecret(
|
|
Local<ArrayBufferView> abv) {
|
|
size_t key_len = abv->ByteLength();
|
|
char* mem = MallocOpenSSL<char>(key_len);
|
|
abv->CopyContents(mem, key_len);
|
|
return std::shared_ptr<KeyObjectData>(new KeyObjectData(
|
|
std::unique_ptr<char, std::function<void(char*)>>(mem,
|
|
[key_len](char* p) {
|
|
OPENSSL_clear_free(p, key_len);
|
|
}),
|
|
key_len));
|
|
}
|
|
|
|
std::shared_ptr<KeyObjectData> KeyObjectData::CreateAsymmetric(
|
|
KeyType key_type,
|
|
const ManagedEVPPKey& pkey) {
|
|
CHECK(pkey);
|
|
return std::shared_ptr<KeyObjectData>(new KeyObjectData(key_type, pkey));
|
|
}
|
|
|
|
KeyType KeyObjectData::GetKeyType() const {
|
|
return key_type_;
|
|
}
|
|
|
|
ManagedEVPPKey KeyObjectData::GetAsymmetricKey() const {
|
|
CHECK_NE(key_type_, kKeyTypeSecret);
|
|
return asymmetric_key_;
|
|
}
|
|
|
|
const char* KeyObjectData::GetSymmetricKey() const {
|
|
CHECK_EQ(key_type_, kKeyTypeSecret);
|
|
return symmetric_key_.get();
|
|
}
|
|
|
|
size_t KeyObjectData::GetSymmetricKeySize() const {
|
|
CHECK_EQ(key_type_, kKeyTypeSecret);
|
|
return symmetric_key_len_;
|
|
}
|
|
|
|
Local<Function> KeyObjectHandle::Initialize(Environment* env,
|
|
Local<Object> target) {
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
t->InstanceTemplate()->SetInternalFieldCount(
|
|
KeyObjectHandle::kInternalFieldCount);
|
|
t->Inherit(BaseObject::GetConstructorTemplate(env));
|
|
|
|
env->SetProtoMethod(t, "init", Init);
|
|
env->SetProtoMethodNoSideEffect(t, "getSymmetricKeySize",
|
|
GetSymmetricKeySize);
|
|
env->SetProtoMethodNoSideEffect(t, "getAsymmetricKeyType",
|
|
GetAsymmetricKeyType);
|
|
env->SetProtoMethod(t, "export", Export);
|
|
|
|
auto function = t->GetFunction(env->context()).ToLocalChecked();
|
|
target->Set(env->context(),
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "KeyObjectHandle"),
|
|
function).Check();
|
|
|
|
return function;
|
|
}
|
|
|
|
MaybeLocal<Object> KeyObjectHandle::Create(
|
|
Environment* env,
|
|
std::shared_ptr<KeyObjectData> data) {
|
|
Local<Object> obj;
|
|
if (!env->crypto_key_object_handle_constructor()
|
|
->NewInstance(env->context(), 0, nullptr)
|
|
.ToLocal(&obj)) {
|
|
return MaybeLocal<Object>();
|
|
}
|
|
|
|
KeyObjectHandle* key = Unwrap<KeyObjectHandle>(obj);
|
|
CHECK_NOT_NULL(key);
|
|
key->data_ = data;
|
|
return obj;
|
|
}
|
|
|
|
const std::shared_ptr<KeyObjectData>& KeyObjectHandle::Data() {
|
|
return data_;
|
|
}
|
|
|
|
void KeyObjectHandle::New(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(args.IsConstructCall());
|
|
Environment* env = Environment::GetCurrent(args);
|
|
new KeyObjectHandle(env, args.This());
|
|
}
|
|
|
|
KeyObjectHandle::KeyObjectHandle(Environment* env,
|
|
Local<Object> wrap)
|
|
: BaseObject(env, wrap) {
|
|
MakeWeak();
|
|
}
|
|
|
|
void KeyObjectHandle::Init(const FunctionCallbackInfo<Value>& args) {
|
|
KeyObjectHandle* key;
|
|
ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder());
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
CHECK(args[0]->IsInt32());
|
|
KeyType type = static_cast<KeyType>(args[0].As<Uint32>()->Value());
|
|
|
|
unsigned int offset;
|
|
ManagedEVPPKey pkey;
|
|
|
|
switch (type) {
|
|
case kKeyTypeSecret:
|
|
CHECK_EQ(args.Length(), 2);
|
|
CHECK(args[1]->IsArrayBufferView());
|
|
key->data_ = KeyObjectData::CreateSecret(args[1].As<ArrayBufferView>());
|
|
break;
|
|
case kKeyTypePublic:
|
|
CHECK_EQ(args.Length(), 4);
|
|
|
|
offset = 1;
|
|
pkey = GetPublicOrPrivateKeyFromJs(args, &offset);
|
|
if (!pkey)
|
|
return;
|
|
key->data_ = KeyObjectData::CreateAsymmetric(type, pkey);
|
|
break;
|
|
case kKeyTypePrivate:
|
|
CHECK_EQ(args.Length(), 5);
|
|
|
|
offset = 1;
|
|
pkey = GetPrivateKeyFromJs(args, &offset, false);
|
|
if (!pkey)
|
|
return;
|
|
key->data_ = KeyObjectData::CreateAsymmetric(type, pkey);
|
|
break;
|
|
default:
|
|
CHECK(false);
|
|
}
|
|
}
|
|
|
|
Local<Value> KeyObjectHandle::GetAsymmetricKeyType() const {
|
|
const ManagedEVPPKey& key = data_->GetAsymmetricKey();
|
|
switch (EVP_PKEY_id(key.get())) {
|
|
case EVP_PKEY_RSA:
|
|
return env()->crypto_rsa_string();
|
|
case EVP_PKEY_RSA_PSS:
|
|
return env()->crypto_rsa_pss_string();
|
|
case EVP_PKEY_DSA:
|
|
return env()->crypto_dsa_string();
|
|
case EVP_PKEY_DH:
|
|
return env()->crypto_dh_string();
|
|
case EVP_PKEY_EC:
|
|
return env()->crypto_ec_string();
|
|
case EVP_PKEY_ED25519:
|
|
return env()->crypto_ed25519_string();
|
|
case EVP_PKEY_ED448:
|
|
return env()->crypto_ed448_string();
|
|
case EVP_PKEY_X25519:
|
|
return env()->crypto_x25519_string();
|
|
case EVP_PKEY_X448:
|
|
return env()->crypto_x448_string();
|
|
default:
|
|
return Undefined(env()->isolate());
|
|
}
|
|
}
|
|
|
|
void KeyObjectHandle::GetAsymmetricKeyType(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
KeyObjectHandle* key;
|
|
ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder());
|
|
|
|
args.GetReturnValue().Set(key->GetAsymmetricKeyType());
|
|
}
|
|
|
|
void KeyObjectHandle::GetSymmetricKeySize(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
KeyObjectHandle* key;
|
|
ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder());
|
|
args.GetReturnValue().Set(
|
|
static_cast<uint32_t>(key->Data()->GetSymmetricKeySize()));
|
|
}
|
|
|
|
void KeyObjectHandle::Export(const FunctionCallbackInfo<Value>& args) {
|
|
KeyObjectHandle* key;
|
|
ASSIGN_OR_RETURN_UNWRAP(&key, args.Holder());
|
|
|
|
KeyType type = key->Data()->GetKeyType();
|
|
|
|
MaybeLocal<Value> result;
|
|
if (type == kKeyTypeSecret) {
|
|
result = key->ExportSecretKey();
|
|
} else if (type == kKeyTypePublic) {
|
|
unsigned int offset = 0;
|
|
PublicKeyEncodingConfig config =
|
|
GetPublicKeyEncodingFromJs(args, &offset, kKeyContextExport);
|
|
CHECK_EQ(offset, static_cast<unsigned int>(args.Length()));
|
|
result = key->ExportPublicKey(config);
|
|
} else {
|
|
CHECK_EQ(type, kKeyTypePrivate);
|
|
unsigned int offset = 0;
|
|
NonCopyableMaybe<PrivateKeyEncodingConfig> config =
|
|
GetPrivateKeyEncodingFromJs(args, &offset, kKeyContextExport);
|
|
if (config.IsEmpty())
|
|
return;
|
|
CHECK_EQ(offset, static_cast<unsigned int>(args.Length()));
|
|
result = key->ExportPrivateKey(config.Release());
|
|
}
|
|
|
|
if (!result.IsEmpty())
|
|
args.GetReturnValue().Set(result.ToLocalChecked());
|
|
}
|
|
|
|
Local<Value> KeyObjectHandle::ExportSecretKey() const {
|
|
const char* buf = data_->GetSymmetricKey();
|
|
unsigned int len = data_->GetSymmetricKeySize();
|
|
return Buffer::Copy(env(), buf, len).ToLocalChecked();
|
|
}
|
|
|
|
MaybeLocal<Value> KeyObjectHandle::ExportPublicKey(
|
|
const PublicKeyEncodingConfig& config) const {
|
|
return WritePublicKey(env(), data_->GetAsymmetricKey().get(), config);
|
|
}
|
|
|
|
MaybeLocal<Value> KeyObjectHandle::ExportPrivateKey(
|
|
const PrivateKeyEncodingConfig& config) const {
|
|
return WritePrivateKey(env(), data_->GetAsymmetricKey().get(), config);
|
|
}
|
|
|
|
void NativeKeyObject::New(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
CHECK_EQ(args.Length(), 1);
|
|
CHECK(args[0]->IsObject());
|
|
KeyObjectHandle* handle = Unwrap<KeyObjectHandle>(args[0].As<Object>());
|
|
new NativeKeyObject(env, args.This(), handle->Data());
|
|
}
|
|
|
|
BaseObjectPtr<BaseObject> NativeKeyObject::KeyObjectTransferData::Deserialize(
|
|
Environment* env,
|
|
Local<Context> context,
|
|
std::unique_ptr<worker::TransferData> self) {
|
|
if (context != env->context()) {
|
|
THROW_ERR_MESSAGE_TARGET_CONTEXT_UNAVAILABLE(env);
|
|
return {};
|
|
}
|
|
|
|
Local<Value> handle = KeyObjectHandle::Create(env, data_).ToLocalChecked();
|
|
Local<Function> key_ctor;
|
|
switch (data_->GetKeyType()) {
|
|
case kKeyTypeSecret:
|
|
key_ctor = env->crypto_key_object_secret_constructor();
|
|
break;
|
|
case kKeyTypePublic:
|
|
key_ctor = env->crypto_key_object_public_constructor();
|
|
break;
|
|
case kKeyTypePrivate:
|
|
key_ctor = env->crypto_key_object_private_constructor();
|
|
break;
|
|
default:
|
|
CHECK(false);
|
|
}
|
|
|
|
Local<Value> key =
|
|
key_ctor->NewInstance(context, 1, &handle).ToLocalChecked();
|
|
return BaseObjectPtr<BaseObject>(Unwrap<KeyObjectHandle>(key.As<Object>()));
|
|
}
|
|
|
|
BaseObject::TransferMode NativeKeyObject::GetTransferMode() const {
|
|
return BaseObject::TransferMode::kCloneable;
|
|
}
|
|
|
|
std::unique_ptr<worker::TransferData> NativeKeyObject::CloneForMessaging()
|
|
const {
|
|
return std::make_unique<KeyObjectTransferData>(handle_data_);
|
|
}
|
|
|
|
static void CreateNativeKeyObjectClass(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
CHECK_EQ(args.Length(), 1);
|
|
Local<Value> callback = args[0];
|
|
CHECK(callback->IsFunction());
|
|
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(NativeKeyObject::New);
|
|
t->InstanceTemplate()->SetInternalFieldCount(
|
|
KeyObjectHandle::kInternalFieldCount);
|
|
t->Inherit(BaseObject::GetConstructorTemplate(env));
|
|
|
|
Local<Value> ctor = t->GetFunction(env->context()).ToLocalChecked();
|
|
|
|
Local<Value> recv = Undefined(env->isolate());
|
|
Local<Value> ret_v;
|
|
if (!callback.As<Function>()->Call(
|
|
env->context(), recv, 1, &ctor).ToLocal(&ret_v)) {
|
|
return;
|
|
}
|
|
Local<Array> ret = ret_v.As<Array>();
|
|
if (!ret->Get(env->context(), 1).ToLocal(&ctor)) return;
|
|
env->set_crypto_key_object_secret_constructor(ctor.As<Function>());
|
|
if (!ret->Get(env->context(), 2).ToLocal(&ctor)) return;
|
|
env->set_crypto_key_object_public_constructor(ctor.As<Function>());
|
|
if (!ret->Get(env->context(), 3).ToLocal(&ctor)) return;
|
|
env->set_crypto_key_object_private_constructor(ctor.As<Function>());
|
|
args.GetReturnValue().Set(ret);
|
|
}
|
|
|
|
CipherBase::CipherBase(Environment* env,
|
|
Local<Object> wrap,
|
|
CipherKind kind)
|
|
: BaseObject(env, wrap),
|
|
ctx_(nullptr),
|
|
kind_(kind),
|
|
auth_tag_state_(kAuthTagUnknown),
|
|
auth_tag_len_(kNoAuthTagLength),
|
|
pending_auth_failed_(false) {
|
|
MakeWeak();
|
|
}
|
|
|
|
void CipherBase::Initialize(Environment* env, Local<Object> target) {
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(
|
|
CipherBase::kInternalFieldCount);
|
|
t->Inherit(BaseObject::GetConstructorTemplate(env));
|
|
|
|
env->SetProtoMethod(t, "init", Init);
|
|
env->SetProtoMethod(t, "initiv", InitIv);
|
|
env->SetProtoMethod(t, "update", Update);
|
|
env->SetProtoMethod(t, "final", Final);
|
|
env->SetProtoMethod(t, "setAutoPadding", SetAutoPadding);
|
|
env->SetProtoMethodNoSideEffect(t, "getAuthTag", GetAuthTag);
|
|
env->SetProtoMethod(t, "setAuthTag", SetAuthTag);
|
|
env->SetProtoMethod(t, "setAAD", SetAAD);
|
|
|
|
target->Set(env->context(),
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "CipherBase"),
|
|
t->GetFunction(env->context()).ToLocalChecked()).Check();
|
|
}
|
|
|
|
|
|
void CipherBase::New(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(args.IsConstructCall());
|
|
CipherKind kind = args[0]->IsTrue() ? kCipher : kDecipher;
|
|
Environment* env = Environment::GetCurrent(args);
|
|
new CipherBase(env, args.This(), kind);
|
|
}
|
|
|
|
void CipherBase::CommonInit(const char* cipher_type,
|
|
const EVP_CIPHER* cipher,
|
|
const unsigned char* key,
|
|
int key_len,
|
|
const unsigned char* iv,
|
|
int iv_len,
|
|
unsigned int auth_tag_len) {
|
|
CHECK(!ctx_);
|
|
ctx_.reset(EVP_CIPHER_CTX_new());
|
|
|
|
const int mode = EVP_CIPHER_mode(cipher);
|
|
if (mode == EVP_CIPH_WRAP_MODE)
|
|
EVP_CIPHER_CTX_set_flags(ctx_.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
|
|
|
|
const bool encrypt = (kind_ == kCipher);
|
|
if (1 != EVP_CipherInit_ex(ctx_.get(), cipher, nullptr,
|
|
nullptr, nullptr, encrypt)) {
|
|
return ThrowCryptoError(env(), ERR_get_error(),
|
|
"Failed to initialize cipher");
|
|
}
|
|
|
|
if (IsSupportedAuthenticatedMode(cipher)) {
|
|
CHECK_GE(iv_len, 0);
|
|
if (!InitAuthenticated(cipher_type, iv_len, auth_tag_len))
|
|
return;
|
|
}
|
|
|
|
if (!EVP_CIPHER_CTX_set_key_length(ctx_.get(), key_len)) {
|
|
ctx_.reset();
|
|
return env()->ThrowError("Invalid key length");
|
|
}
|
|
|
|
if (1 != EVP_CipherInit_ex(ctx_.get(), nullptr, nullptr, key, iv, encrypt)) {
|
|
return ThrowCryptoError(env(), ERR_get_error(),
|
|
"Failed to initialize cipher");
|
|
}
|
|
}
|
|
|
|
void CipherBase::Init(const char* cipher_type,
|
|
const char* key_buf,
|
|
int key_buf_len,
|
|
unsigned int auth_tag_len) {
|
|
HandleScope scope(env()->isolate());
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
#ifdef NODE_FIPS_MODE
|
|
if (FIPS_mode()) {
|
|
return env()->ThrowError(
|
|
"crypto.createCipher() is not supported in FIPS mode.");
|
|
}
|
|
#endif // NODE_FIPS_MODE
|
|
|
|
const EVP_CIPHER* const cipher = EVP_get_cipherbyname(cipher_type);
|
|
if (cipher == nullptr)
|
|
return THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env());
|
|
|
|
unsigned char key[EVP_MAX_KEY_LENGTH];
|
|
unsigned char iv[EVP_MAX_IV_LENGTH];
|
|
|
|
int key_len = EVP_BytesToKey(cipher,
|
|
EVP_md5(),
|
|
nullptr,
|
|
reinterpret_cast<const unsigned char*>(key_buf),
|
|
key_buf_len,
|
|
1,
|
|
key,
|
|
iv);
|
|
CHECK_NE(key_len, 0);
|
|
|
|
const int mode = EVP_CIPHER_mode(cipher);
|
|
if (kind_ == kCipher && (mode == EVP_CIPH_CTR_MODE ||
|
|
mode == EVP_CIPH_GCM_MODE ||
|
|
mode == EVP_CIPH_CCM_MODE)) {
|
|
// Ignore the return value (i.e. possible exception) because we are
|
|
// not calling back into JS anyway.
|
|
ProcessEmitWarning(env(),
|
|
"Use Cipheriv for counter mode of %s",
|
|
cipher_type);
|
|
}
|
|
|
|
CommonInit(cipher_type, cipher, key, key_len, iv,
|
|
EVP_CIPHER_iv_length(cipher), auth_tag_len);
|
|
}
|
|
|
|
|
|
void CipherBase::Init(const FunctionCallbackInfo<Value>& args) {
|
|
CipherBase* cipher;
|
|
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
|
|
|
|
CHECK_GE(args.Length(), 3);
|
|
|
|
const node::Utf8Value cipher_type(args.GetIsolate(), args[0]);
|
|
ArrayBufferViewContents<char> key_buf(args[1]);
|
|
|
|
// Don't assign to cipher->auth_tag_len_ directly; the value might not
|
|
// represent a valid length at this point.
|
|
unsigned int auth_tag_len;
|
|
if (args[2]->IsUint32()) {
|
|
auth_tag_len = args[2].As<Uint32>()->Value();
|
|
} else {
|
|
CHECK(args[2]->IsInt32() && args[2].As<Int32>()->Value() == -1);
|
|
auth_tag_len = kNoAuthTagLength;
|
|
}
|
|
|
|
cipher->Init(*cipher_type, key_buf.data(), key_buf.length(), auth_tag_len);
|
|
}
|
|
|
|
void CipherBase::InitIv(const char* cipher_type,
|
|
const unsigned char* key,
|
|
int key_len,
|
|
const unsigned char* iv,
|
|
int iv_len,
|
|
unsigned int auth_tag_len) {
|
|
HandleScope scope(env()->isolate());
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
const EVP_CIPHER* const cipher = EVP_get_cipherbyname(cipher_type);
|
|
if (cipher == nullptr) {
|
|
return THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env());
|
|
}
|
|
|
|
const int expected_iv_len = EVP_CIPHER_iv_length(cipher);
|
|
const bool is_authenticated_mode = IsSupportedAuthenticatedMode(cipher);
|
|
const bool has_iv = iv_len >= 0;
|
|
|
|
// Throw if no IV was passed and the cipher requires an IV
|
|
if (!has_iv && expected_iv_len != 0) {
|
|
char msg[128];
|
|
snprintf(msg, sizeof(msg), "Missing IV for cipher %s", cipher_type);
|
|
return env()->ThrowError(msg);
|
|
}
|
|
|
|
// Throw if an IV was passed which does not match the cipher's fixed IV length
|
|
if (!is_authenticated_mode && has_iv && iv_len != expected_iv_len) {
|
|
return env()->ThrowError("Invalid IV length");
|
|
}
|
|
|
|
if (EVP_CIPHER_nid(cipher) == NID_chacha20_poly1305) {
|
|
CHECK(has_iv);
|
|
// Check for invalid IV lengths, since OpenSSL does not under some
|
|
// conditions:
|
|
// https://www.openssl.org/news/secadv/20190306.txt.
|
|
if (iv_len > 12) {
|
|
return env()->ThrowError("Invalid IV length");
|
|
}
|
|
}
|
|
|
|
CommonInit(cipher_type, cipher, key, key_len, iv, iv_len, auth_tag_len);
|
|
}
|
|
|
|
|
|
static ByteSource GetSecretKeyBytes(Environment* env, Local<Value> value) {
|
|
// A key can be passed as a string, buffer or KeyObject with type 'secret'.
|
|
// If it is a string, we need to convert it to a buffer. We are not doing that
|
|
// in JS to avoid creating an unprotected copy on the heap.
|
|
return value->IsString() || Buffer::HasInstance(value) ?
|
|
ByteSource::FromStringOrBuffer(env, value) :
|
|
ByteSource::FromSymmetricKeyObjectHandle(value);
|
|
}
|
|
|
|
void CipherBase::InitIv(const FunctionCallbackInfo<Value>& args) {
|
|
CipherBase* cipher;
|
|
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
|
|
Environment* env = cipher->env();
|
|
|
|
CHECK_GE(args.Length(), 4);
|
|
|
|
const node::Utf8Value cipher_type(env->isolate(), args[0]);
|
|
const ByteSource key = GetSecretKeyBytes(env, args[1]);
|
|
|
|
ArrayBufferViewContents<unsigned char> iv_buf;
|
|
ssize_t iv_len = -1;
|
|
if (!args[2]->IsNull()) {
|
|
CHECK(args[2]->IsArrayBufferView());
|
|
iv_buf.Read(args[2].As<ArrayBufferView>());
|
|
iv_len = iv_buf.length();
|
|
}
|
|
|
|
// Don't assign to cipher->auth_tag_len_ directly; the value might not
|
|
// represent a valid length at this point.
|
|
unsigned int auth_tag_len;
|
|
if (args[3]->IsUint32()) {
|
|
auth_tag_len = args[3].As<Uint32>()->Value();
|
|
} else {
|
|
CHECK(args[3]->IsInt32() && args[3].As<Int32>()->Value() == -1);
|
|
auth_tag_len = kNoAuthTagLength;
|
|
}
|
|
|
|
cipher->InitIv(*cipher_type,
|
|
reinterpret_cast<const unsigned char*>(key.get()),
|
|
key.size(),
|
|
iv_buf.data(),
|
|
static_cast<int>(iv_len),
|
|
auth_tag_len);
|
|
}
|
|
|
|
|
|
static bool IsValidGCMTagLength(unsigned int tag_len) {
|
|
return tag_len == 4 || tag_len == 8 || (tag_len >= 12 && tag_len <= 16);
|
|
}
|
|
|
|
bool CipherBase::InitAuthenticated(const char* cipher_type, int iv_len,
|
|
unsigned int auth_tag_len) {
|
|
CHECK(IsAuthenticatedMode());
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(),
|
|
EVP_CTRL_AEAD_SET_IVLEN,
|
|
iv_len,
|
|
nullptr)) {
|
|
env()->ThrowError("Invalid IV length");
|
|
return false;
|
|
}
|
|
|
|
const int mode = EVP_CIPHER_CTX_mode(ctx_.get());
|
|
if (mode == EVP_CIPH_GCM_MODE) {
|
|
if (auth_tag_len != kNoAuthTagLength) {
|
|
if (!IsValidGCMTagLength(auth_tag_len)) {
|
|
char msg[50];
|
|
snprintf(msg, sizeof(msg),
|
|
"Invalid authentication tag length: %u", auth_tag_len);
|
|
env()->ThrowError(msg);
|
|
return false;
|
|
}
|
|
|
|
// Remember the given authentication tag length for later.
|
|
auth_tag_len_ = auth_tag_len;
|
|
}
|
|
} else {
|
|
if (auth_tag_len == kNoAuthTagLength) {
|
|
char msg[128];
|
|
snprintf(msg, sizeof(msg), "authTagLength required for %s", cipher_type);
|
|
env()->ThrowError(msg);
|
|
return false;
|
|
}
|
|
|
|
#ifdef NODE_FIPS_MODE
|
|
// TODO(tniessen) Support CCM decryption in FIPS mode
|
|
if (mode == EVP_CIPH_CCM_MODE && kind_ == kDecipher && FIPS_mode()) {
|
|
env()->ThrowError("CCM decryption not supported in FIPS mode");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Tell OpenSSL about the desired length.
|
|
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_SET_TAG, auth_tag_len,
|
|
nullptr)) {
|
|
env()->ThrowError("Invalid authentication tag length");
|
|
return false;
|
|
}
|
|
|
|
// Remember the given authentication tag length for later.
|
|
auth_tag_len_ = auth_tag_len;
|
|
|
|
if (mode == EVP_CIPH_CCM_MODE) {
|
|
// Restrict the message length to min(INT_MAX, 2^(8*(15-iv_len))-1) bytes.
|
|
CHECK(iv_len >= 7 && iv_len <= 13);
|
|
max_message_size_ = INT_MAX;
|
|
if (iv_len == 12) max_message_size_ = 16777215;
|
|
if (iv_len == 13) max_message_size_ = 65535;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CipherBase::CheckCCMMessageLength(int message_len) {
|
|
CHECK(ctx_);
|
|
CHECK(EVP_CIPHER_CTX_mode(ctx_.get()) == EVP_CIPH_CCM_MODE);
|
|
|
|
if (message_len > max_message_size_) {
|
|
env()->ThrowError("Message exceeds maximum size");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CipherBase::IsAuthenticatedMode() const {
|
|
// Check if this cipher operates in an AEAD mode that we support.
|
|
CHECK(ctx_);
|
|
return IsSupportedAuthenticatedMode(ctx_.get());
|
|
}
|
|
|
|
|
|
void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
CipherBase* cipher;
|
|
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
|
|
|
|
// Only callable after Final and if encrypting.
|
|
if (cipher->ctx_ ||
|
|
cipher->kind_ != kCipher ||
|
|
cipher->auth_tag_len_ == kNoAuthTagLength) {
|
|
return args.GetReturnValue().SetUndefined();
|
|
}
|
|
|
|
Local<Object> buf =
|
|
Buffer::Copy(env, cipher->auth_tag_, cipher->auth_tag_len_)
|
|
.ToLocalChecked();
|
|
args.GetReturnValue().Set(buf);
|
|
}
|
|
|
|
|
|
void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
|
|
CipherBase* cipher;
|
|
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
|
|
|
|
if (!cipher->ctx_ ||
|
|
!cipher->IsAuthenticatedMode() ||
|
|
cipher->kind_ != kDecipher ||
|
|
cipher->auth_tag_state_ != kAuthTagUnknown) {
|
|
return args.GetReturnValue().Set(false);
|
|
}
|
|
|
|
unsigned int tag_len = Buffer::Length(args[0]);
|
|
const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_.get());
|
|
bool is_valid;
|
|
if (mode == EVP_CIPH_GCM_MODE) {
|
|
// Restrict GCM tag lengths according to NIST 800-38d, page 9.
|
|
is_valid = (cipher->auth_tag_len_ == kNoAuthTagLength ||
|
|
cipher->auth_tag_len_ == tag_len) &&
|
|
IsValidGCMTagLength(tag_len);
|
|
} else {
|
|
// At this point, the tag length is already known and must match the
|
|
// length of the given authentication tag.
|
|
CHECK(IsSupportedAuthenticatedMode(cipher->ctx_.get()));
|
|
CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength);
|
|
is_valid = cipher->auth_tag_len_ == tag_len;
|
|
}
|
|
|
|
if (!is_valid) {
|
|
char msg[50];
|
|
snprintf(msg, sizeof(msg),
|
|
"Invalid authentication tag length: %u", tag_len);
|
|
return cipher->env()->ThrowError(msg);
|
|
}
|
|
|
|
cipher->auth_tag_len_ = tag_len;
|
|
cipher->auth_tag_state_ = kAuthTagKnown;
|
|
CHECK_LE(cipher->auth_tag_len_, sizeof(cipher->auth_tag_));
|
|
|
|
memset(cipher->auth_tag_, 0, sizeof(cipher->auth_tag_));
|
|
args[0].As<ArrayBufferView>()->CopyContents(
|
|
cipher->auth_tag_, cipher->auth_tag_len_);
|
|
|
|
args.GetReturnValue().Set(true);
|
|
}
|
|
|
|
|
|
bool CipherBase::MaybePassAuthTagToOpenSSL() {
|
|
if (auth_tag_state_ == kAuthTagKnown) {
|
|
if (!EVP_CIPHER_CTX_ctrl(ctx_.get(),
|
|
EVP_CTRL_AEAD_SET_TAG,
|
|
auth_tag_len_,
|
|
reinterpret_cast<unsigned char*>(auth_tag_))) {
|
|
return false;
|
|
}
|
|
auth_tag_state_ = kAuthTagPassedToOpenSSL;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CipherBase::SetAAD(const char* data, unsigned int len, int plaintext_len) {
|
|
if (!ctx_ || !IsAuthenticatedMode())
|
|
return false;
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
int outlen;
|
|
const int mode = EVP_CIPHER_CTX_mode(ctx_.get());
|
|
|
|
// When in CCM mode, we need to set the authentication tag and the plaintext
|
|
// length in advance.
|
|
if (mode == EVP_CIPH_CCM_MODE) {
|
|
if (plaintext_len < 0) {
|
|
env()->ThrowError("plaintextLength required for CCM mode with AAD");
|
|
return false;
|
|
}
|
|
|
|
if (!CheckCCMMessageLength(plaintext_len))
|
|
return false;
|
|
|
|
if (kind_ == kDecipher) {
|
|
if (!MaybePassAuthTagToOpenSSL())
|
|
return false;
|
|
}
|
|
|
|
// Specify the plaintext length.
|
|
if (!EVP_CipherUpdate(ctx_.get(), nullptr, &outlen, nullptr, plaintext_len))
|
|
return false;
|
|
}
|
|
|
|
return 1 == EVP_CipherUpdate(ctx_.get(),
|
|
nullptr,
|
|
&outlen,
|
|
reinterpret_cast<const unsigned char*>(data),
|
|
len);
|
|
}
|
|
|
|
|
|
void CipherBase::SetAAD(const FunctionCallbackInfo<Value>& args) {
|
|
CipherBase* cipher;
|
|
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
|
|
|
|
CHECK_EQ(args.Length(), 2);
|
|
CHECK(args[1]->IsInt32());
|
|
int plaintext_len = args[1].As<Int32>()->Value();
|
|
ArrayBufferViewContents<char> buf(args[0]);
|
|
|
|
bool b = cipher->SetAAD(buf.data(), buf.length(), plaintext_len);
|
|
args.GetReturnValue().Set(b); // Possibly report invalid state failure
|
|
}
|
|
|
|
CipherBase::UpdateResult CipherBase::Update(const char* data,
|
|
int len,
|
|
AllocatedBuffer* out) {
|
|
if (!ctx_)
|
|
return kErrorState;
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
const int mode = EVP_CIPHER_CTX_mode(ctx_.get());
|
|
|
|
if (mode == EVP_CIPH_CCM_MODE) {
|
|
if (!CheckCCMMessageLength(len))
|
|
return kErrorMessageSize;
|
|
}
|
|
|
|
// Pass the authentication tag to OpenSSL if possible. This will only happen
|
|
// once, usually on the first update.
|
|
if (kind_ == kDecipher && IsAuthenticatedMode()) {
|
|
CHECK(MaybePassAuthTagToOpenSSL());
|
|
}
|
|
|
|
int buf_len = len + EVP_CIPHER_CTX_block_size(ctx_.get());
|
|
// For key wrapping algorithms, get output size by calling
|
|
// EVP_CipherUpdate() with null output.
|
|
if (kind_ == kCipher && mode == EVP_CIPH_WRAP_MODE &&
|
|
EVP_CipherUpdate(ctx_.get(),
|
|
nullptr,
|
|
&buf_len,
|
|
reinterpret_cast<const unsigned char*>(data),
|
|
len) != 1) {
|
|
return kErrorState;
|
|
}
|
|
|
|
*out = AllocatedBuffer::AllocateManaged(env(), buf_len);
|
|
int r = EVP_CipherUpdate(ctx_.get(),
|
|
reinterpret_cast<unsigned char*>(out->data()),
|
|
&buf_len,
|
|
reinterpret_cast<const unsigned char*>(data),
|
|
len);
|
|
|
|
CHECK_LE(static_cast<size_t>(buf_len), out->size());
|
|
out->Resize(buf_len);
|
|
|
|
// When in CCM mode, EVP_CipherUpdate will fail if the authentication tag is
|
|
// invalid. In that case, remember the error and throw in final().
|
|
if (!r && kind_ == kDecipher && mode == EVP_CIPH_CCM_MODE) {
|
|
pending_auth_failed_ = true;
|
|
return kSuccess;
|
|
}
|
|
return r == 1 ? kSuccess : kErrorState;
|
|
}
|
|
|
|
|
|
void CipherBase::Update(const FunctionCallbackInfo<Value>& args) {
|
|
Decode<CipherBase>(args, [](CipherBase* cipher,
|
|
const FunctionCallbackInfo<Value>& args,
|
|
const char* data, size_t size) {
|
|
AllocatedBuffer out;
|
|
UpdateResult r = cipher->Update(data, size, &out);
|
|
|
|
if (r != kSuccess) {
|
|
if (r == kErrorState) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
ThrowCryptoError(env, ERR_get_error(),
|
|
"Trying to add data in unsupported state");
|
|
}
|
|
return;
|
|
}
|
|
|
|
CHECK(out.data() != nullptr || out.size() == 0);
|
|
args.GetReturnValue().Set(out.ToBuffer().ToLocalChecked());
|
|
});
|
|
}
|
|
|
|
|
|
bool CipherBase::SetAutoPadding(bool auto_padding) {
|
|
if (!ctx_)
|
|
return false;
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
return EVP_CIPHER_CTX_set_padding(ctx_.get(), auto_padding);
|
|
}
|
|
|
|
|
|
void CipherBase::SetAutoPadding(const FunctionCallbackInfo<Value>& args) {
|
|
CipherBase* cipher;
|
|
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
|
|
|
|
bool b = cipher->SetAutoPadding(args.Length() < 1 || args[0]->IsTrue());
|
|
args.GetReturnValue().Set(b); // Possibly report invalid state failure
|
|
}
|
|
|
|
bool CipherBase::Final(AllocatedBuffer* out) {
|
|
if (!ctx_)
|
|
return false;
|
|
|
|
const int mode = EVP_CIPHER_CTX_mode(ctx_.get());
|
|
|
|
*out = AllocatedBuffer::AllocateManaged(
|
|
env(),
|
|
static_cast<size_t>(EVP_CIPHER_CTX_block_size(ctx_.get())));
|
|
|
|
if (kind_ == kDecipher && IsSupportedAuthenticatedMode(ctx_.get())) {
|
|
MaybePassAuthTagToOpenSSL();
|
|
}
|
|
|
|
// In CCM mode, final() only checks whether authentication failed in update().
|
|
// EVP_CipherFinal_ex must not be called and will fail.
|
|
bool ok;
|
|
if (kind_ == kDecipher && mode == EVP_CIPH_CCM_MODE) {
|
|
ok = !pending_auth_failed_;
|
|
*out = AllocatedBuffer::AllocateManaged(env(), 0); // Empty buffer.
|
|
} else {
|
|
int out_len = out->size();
|
|
ok = EVP_CipherFinal_ex(ctx_.get(),
|
|
reinterpret_cast<unsigned char*>(out->data()),
|
|
&out_len) == 1;
|
|
|
|
if (out_len >= 0)
|
|
out->Resize(out_len);
|
|
else
|
|
*out = AllocatedBuffer(); // *out will not be used.
|
|
|
|
if (ok && kind_ == kCipher && IsAuthenticatedMode()) {
|
|
// In GCM mode, the authentication tag length can be specified in advance,
|
|
// but defaults to 16 bytes when encrypting. In CCM and OCB mode, it must
|
|
// always be given by the user.
|
|
if (auth_tag_len_ == kNoAuthTagLength) {
|
|
CHECK(mode == EVP_CIPH_GCM_MODE);
|
|
auth_tag_len_ = sizeof(auth_tag_);
|
|
}
|
|
CHECK_EQ(1, EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_GET_TAG,
|
|
auth_tag_len_,
|
|
reinterpret_cast<unsigned char*>(auth_tag_)));
|
|
}
|
|
}
|
|
|
|
ctx_.reset();
|
|
|
|
return ok;
|
|
}
|
|
|
|
|
|
void CipherBase::Final(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
CipherBase* cipher;
|
|
ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder());
|
|
if (cipher->ctx_ == nullptr) return env->ThrowError("Unsupported state");
|
|
|
|
AllocatedBuffer out;
|
|
|
|
// Check IsAuthenticatedMode() first, Final() destroys the EVP_CIPHER_CTX.
|
|
const bool is_auth_mode = cipher->IsAuthenticatedMode();
|
|
bool r = cipher->Final(&out);
|
|
|
|
if (!r) {
|
|
const char* msg = is_auth_mode
|
|
? "Unsupported state or unable to authenticate data"
|
|
: "Unsupported state";
|
|
|
|
return ThrowCryptoError(env, ERR_get_error(), msg);
|
|
}
|
|
|
|
args.GetReturnValue().Set(out.ToBuffer().ToLocalChecked());
|
|
}
|
|
|
|
Hmac::Hmac(Environment* env, Local<Object> wrap)
|
|
: BaseObject(env, wrap),
|
|
ctx_(nullptr) {
|
|
MakeWeak();
|
|
}
|
|
|
|
void Hmac::Initialize(Environment* env, Local<Object> target) {
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(
|
|
Hmac::kInternalFieldCount);
|
|
t->Inherit(BaseObject::GetConstructorTemplate(env));
|
|
|
|
env->SetProtoMethod(t, "init", HmacInit);
|
|
env->SetProtoMethod(t, "update", HmacUpdate);
|
|
env->SetProtoMethod(t, "digest", HmacDigest);
|
|
|
|
target->Set(env->context(),
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "Hmac"),
|
|
t->GetFunction(env->context()).ToLocalChecked()).Check();
|
|
}
|
|
|
|
|
|
void Hmac::New(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
new Hmac(env, args.This());
|
|
}
|
|
|
|
|
|
void Hmac::HmacInit(const char* hash_type, const char* key, int key_len) {
|
|
HandleScope scope(env()->isolate());
|
|
|
|
const EVP_MD* md = EVP_get_digestbyname(hash_type);
|
|
if (md == nullptr) {
|
|
return env()->ThrowError("Unknown message digest");
|
|
}
|
|
if (key_len == 0) {
|
|
key = "";
|
|
}
|
|
ctx_.reset(HMAC_CTX_new());
|
|
if (!ctx_ || !HMAC_Init_ex(ctx_.get(), key, key_len, md, nullptr)) {
|
|
ctx_.reset();
|
|
return ThrowCryptoError(env(), ERR_get_error());
|
|
}
|
|
}
|
|
|
|
|
|
void Hmac::HmacInit(const FunctionCallbackInfo<Value>& args) {
|
|
Hmac* hmac;
|
|
ASSIGN_OR_RETURN_UNWRAP(&hmac, args.Holder());
|
|
Environment* env = hmac->env();
|
|
|
|
const node::Utf8Value hash_type(env->isolate(), args[0]);
|
|
ByteSource key = GetSecretKeyBytes(env, args[1]);
|
|
hmac->HmacInit(*hash_type, key.get(), key.size());
|
|
}
|
|
|
|
|
|
bool Hmac::HmacUpdate(const char* data, int len) {
|
|
if (!ctx_)
|
|
return false;
|
|
int r = HMAC_Update(ctx_.get(),
|
|
reinterpret_cast<const unsigned char*>(data),
|
|
len);
|
|
return r == 1;
|
|
}
|
|
|
|
|
|
void Hmac::HmacUpdate(const FunctionCallbackInfo<Value>& args) {
|
|
Decode<Hmac>(args, [](Hmac* hmac, const FunctionCallbackInfo<Value>& args,
|
|
const char* data, size_t size) {
|
|
bool r = hmac->HmacUpdate(data, size);
|
|
args.GetReturnValue().Set(r);
|
|
});
|
|
}
|
|
|
|
|
|
void Hmac::HmacDigest(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
Hmac* hmac;
|
|
ASSIGN_OR_RETURN_UNWRAP(&hmac, args.Holder());
|
|
|
|
enum encoding encoding = BUFFER;
|
|
if (args.Length() >= 1) {
|
|
encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
|
|
}
|
|
|
|
unsigned char md_value[EVP_MAX_MD_SIZE];
|
|
unsigned int md_len = 0;
|
|
|
|
if (hmac->ctx_) {
|
|
HMAC_Final(hmac->ctx_.get(), md_value, &md_len);
|
|
hmac->ctx_.reset();
|
|
}
|
|
|
|
Local<Value> error;
|
|
MaybeLocal<Value> rc =
|
|
StringBytes::Encode(env->isolate(),
|
|
reinterpret_cast<const char*>(md_value),
|
|
md_len,
|
|
encoding,
|
|
&error);
|
|
if (rc.IsEmpty()) {
|
|
CHECK(!error.IsEmpty());
|
|
env->isolate()->ThrowException(error);
|
|
return;
|
|
}
|
|
args.GetReturnValue().Set(rc.ToLocalChecked());
|
|
}
|
|
|
|
Hash::Hash(Environment* env, Local<Object> wrap)
|
|
: BaseObject(env, wrap),
|
|
mdctx_(nullptr),
|
|
has_md_(false),
|
|
md_value_(nullptr) {
|
|
MakeWeak();
|
|
}
|
|
|
|
void Hash::Initialize(Environment* env, Local<Object> target) {
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(
|
|
Hash::kInternalFieldCount);
|
|
t->Inherit(BaseObject::GetConstructorTemplate(env));
|
|
|
|
env->SetProtoMethod(t, "update", HashUpdate);
|
|
env->SetProtoMethod(t, "digest", HashDigest);
|
|
|
|
target->Set(env->context(),
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "Hash"),
|
|
t->GetFunction(env->context()).ToLocalChecked()).Check();
|
|
}
|
|
|
|
Hash::~Hash() {
|
|
if (md_value_ != nullptr)
|
|
OPENSSL_clear_free(md_value_, md_len_);
|
|
}
|
|
|
|
void Hash::New(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
const Hash* orig = nullptr;
|
|
const EVP_MD* md = nullptr;
|
|
|
|
if (args[0]->IsObject()) {
|
|
ASSIGN_OR_RETURN_UNWRAP(&orig, args[0].As<Object>());
|
|
md = EVP_MD_CTX_md(orig->mdctx_.get());
|
|
} else {
|
|
const node::Utf8Value hash_type(env->isolate(), args[0]);
|
|
md = EVP_get_digestbyname(*hash_type);
|
|
}
|
|
|
|
Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
|
|
if (!args[1]->IsUndefined()) {
|
|
CHECK(args[1]->IsUint32());
|
|
xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
|
|
}
|
|
|
|
Hash* hash = new Hash(env, args.This());
|
|
if (md == nullptr || !hash->HashInit(md, xof_md_len)) {
|
|
return ThrowCryptoError(env, ERR_get_error(),
|
|
"Digest method not supported");
|
|
}
|
|
|
|
if (orig != nullptr &&
|
|
0 >= EVP_MD_CTX_copy(hash->mdctx_.get(), orig->mdctx_.get())) {
|
|
return ThrowCryptoError(env, ERR_get_error(), "Digest copy error");
|
|
}
|
|
}
|
|
|
|
|
|
bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
|
|
mdctx_.reset(EVP_MD_CTX_new());
|
|
if (!mdctx_ || EVP_DigestInit_ex(mdctx_.get(), md, nullptr) <= 0) {
|
|
mdctx_.reset();
|
|
return false;
|
|
}
|
|
|
|
md_len_ = EVP_MD_size(md);
|
|
if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) {
|
|
// This is a little hack to cause createHash to fail when an incorrect
|
|
// hashSize option was passed for a non-XOF hash function.
|
|
if ((EVP_MD_flags(md) & EVP_MD_FLAG_XOF) == 0) {
|
|
EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
|
|
return false;
|
|
}
|
|
md_len_ = xof_md_len.FromJust();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Hash::HashUpdate(const char* data, int len) {
|
|
if (!mdctx_)
|
|
return false;
|
|
EVP_DigestUpdate(mdctx_.get(), data, len);
|
|
return true;
|
|
}
|
|
|
|
|
|
void Hash::HashUpdate(const FunctionCallbackInfo<Value>& args) {
|
|
Decode<Hash>(args, [](Hash* hash, const FunctionCallbackInfo<Value>& args,
|
|
const char* data, size_t size) {
|
|
bool r = hash->HashUpdate(data, size);
|
|
args.GetReturnValue().Set(r);
|
|
});
|
|
}
|
|
|
|
|
|
void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
Hash* hash;
|
|
ASSIGN_OR_RETURN_UNWRAP(&hash, args.Holder());
|
|
|
|
enum encoding encoding = BUFFER;
|
|
if (args.Length() >= 1) {
|
|
encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
|
|
}
|
|
|
|
// TODO(tniessen): SHA3_squeeze does not work for zero-length outputs on all
|
|
// platforms and will cause a segmentation fault if called. This workaround
|
|
// causes hash.digest() to correctly return an empty buffer / string.
|
|
// See https://github.com/openssl/openssl/issues/9431.
|
|
if (!hash->has_md_ && hash->md_len_ == 0) {
|
|
hash->has_md_ = true;
|
|
}
|
|
|
|
if (!hash->has_md_) {
|
|
// Some hash algorithms such as SHA3 do not support calling
|
|
// EVP_DigestFinal_ex more than once, however, Hash._flush
|
|
// and Hash.digest can both be used to retrieve the digest,
|
|
// so we need to cache it.
|
|
// See https://github.com/nodejs/node/issues/28245.
|
|
|
|
hash->md_value_ = MallocOpenSSL<unsigned char>(hash->md_len_);
|
|
|
|
size_t default_len = EVP_MD_CTX_size(hash->mdctx_.get());
|
|
int ret;
|
|
if (hash->md_len_ == default_len) {
|
|
ret = EVP_DigestFinal_ex(hash->mdctx_.get(), hash->md_value_,
|
|
&hash->md_len_);
|
|
} else {
|
|
ret = EVP_DigestFinalXOF(hash->mdctx_.get(), hash->md_value_,
|
|
hash->md_len_);
|
|
}
|
|
|
|
if (ret != 1) {
|
|
OPENSSL_free(hash->md_value_);
|
|
hash->md_value_ = nullptr;
|
|
return ThrowCryptoError(env, ERR_get_error());
|
|
}
|
|
|
|
hash->has_md_ = true;
|
|
}
|
|
|
|
Local<Value> error;
|
|
MaybeLocal<Value> rc =
|
|
StringBytes::Encode(env->isolate(),
|
|
reinterpret_cast<const char*>(hash->md_value_),
|
|
hash->md_len_,
|
|
encoding,
|
|
&error);
|
|
if (rc.IsEmpty()) {
|
|
CHECK(!error.IsEmpty());
|
|
env->isolate()->ThrowException(error);
|
|
return;
|
|
}
|
|
args.GetReturnValue().Set(rc.ToLocalChecked());
|
|
}
|
|
|
|
|
|
SignBase::Error SignBase::Init(const char* sign_type) {
|
|
CHECK_NULL(mdctx_);
|
|
// Historically, "dss1" and "DSS1" were DSA aliases for SHA-1
|
|
// exposed through the public API.
|
|
if (strcmp(sign_type, "dss1") == 0 ||
|
|
strcmp(sign_type, "DSS1") == 0) {
|
|
sign_type = "SHA1";
|
|
}
|
|
const EVP_MD* md = EVP_get_digestbyname(sign_type);
|
|
if (md == nullptr)
|
|
return kSignUnknownDigest;
|
|
|
|
mdctx_.reset(EVP_MD_CTX_new());
|
|
if (!mdctx_ || !EVP_DigestInit_ex(mdctx_.get(), md, nullptr)) {
|
|
mdctx_.reset();
|
|
return kSignInit;
|
|
}
|
|
|
|
return kSignOk;
|
|
}
|
|
|
|
|
|
SignBase::Error SignBase::Update(const char* data, int len) {
|
|
if (mdctx_ == nullptr)
|
|
return kSignNotInitialised;
|
|
if (!EVP_DigestUpdate(mdctx_.get(), data, len))
|
|
return kSignUpdate;
|
|
return kSignOk;
|
|
}
|
|
|
|
|
|
void CheckThrow(Environment* env, SignBase::Error error) {
|
|
HandleScope scope(env->isolate());
|
|
|
|
switch (error) {
|
|
case SignBase::Error::kSignUnknownDigest:
|
|
return env->ThrowError("Unknown message digest");
|
|
|
|
case SignBase::Error::kSignNotInitialised:
|
|
return env->ThrowError("Not initialised");
|
|
|
|
case SignBase::Error::kSignMalformedSignature:
|
|
return env->ThrowError("Malformed signature");
|
|
|
|
case SignBase::Error::kSignInit:
|
|
case SignBase::Error::kSignUpdate:
|
|
case SignBase::Error::kSignPrivateKey:
|
|
case SignBase::Error::kSignPublicKey:
|
|
{
|
|
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
|
if (err)
|
|
return ThrowCryptoError(env, err);
|
|
switch (error) {
|
|
case SignBase::Error::kSignInit:
|
|
return env->ThrowError("EVP_SignInit_ex failed");
|
|
case SignBase::Error::kSignUpdate:
|
|
return env->ThrowError("EVP_SignUpdate failed");
|
|
case SignBase::Error::kSignPrivateKey:
|
|
return env->ThrowError("PEM_read_bio_PrivateKey failed");
|
|
case SignBase::Error::kSignPublicKey:
|
|
return env->ThrowError("PEM_read_bio_PUBKEY failed");
|
|
default:
|
|
ABORT();
|
|
}
|
|
}
|
|
|
|
case SignBase::Error::kSignOk:
|
|
return;
|
|
}
|
|
}
|
|
|
|
SignBase::SignBase(Environment* env, Local<Object> wrap)
|
|
: BaseObject(env, wrap) {
|
|
}
|
|
|
|
void SignBase::CheckThrow(SignBase::Error error) {
|
|
node::crypto::CheckThrow(env(), error);
|
|
}
|
|
|
|
static bool ApplyRSAOptions(const ManagedEVPPKey& pkey,
|
|
EVP_PKEY_CTX* pkctx,
|
|
int padding,
|
|
const Maybe<int>& salt_len) {
|
|
if (EVP_PKEY_id(pkey.get()) == EVP_PKEY_RSA ||
|
|
EVP_PKEY_id(pkey.get()) == EVP_PKEY_RSA2 ||
|
|
EVP_PKEY_id(pkey.get()) == EVP_PKEY_RSA_PSS) {
|
|
if (EVP_PKEY_CTX_set_rsa_padding(pkctx, padding) <= 0)
|
|
return false;
|
|
if (padding == RSA_PKCS1_PSS_PADDING && salt_len.IsJust()) {
|
|
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkctx, salt_len.FromJust()) <= 0)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
Sign::Sign(Environment* env, Local<Object> wrap) : SignBase(env, wrap) {
|
|
MakeWeak();
|
|
}
|
|
|
|
void Sign::Initialize(Environment* env, Local<Object> target) {
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(
|
|
SignBase::kInternalFieldCount);
|
|
t->Inherit(BaseObject::GetConstructorTemplate(env));
|
|
|
|
env->SetProtoMethod(t, "init", SignInit);
|
|
env->SetProtoMethod(t, "update", SignUpdate);
|
|
env->SetProtoMethod(t, "sign", SignFinal);
|
|
|
|
target->Set(env->context(),
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "Sign"),
|
|
t->GetFunction(env->context()).ToLocalChecked()).Check();
|
|
}
|
|
|
|
|
|
void Sign::New(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
new Sign(env, args.This());
|
|
}
|
|
|
|
|
|
void Sign::SignInit(const FunctionCallbackInfo<Value>& args) {
|
|
Sign* sign;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sign, args.Holder());
|
|
|
|
const node::Utf8Value sign_type(args.GetIsolate(), args[0]);
|
|
sign->CheckThrow(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) {
|
|
Error err = sign->Update(data, size);
|
|
sign->CheckThrow(err);
|
|
});
|
|
}
|
|
|
|
static int GetDefaultSignPadding(const ManagedEVPPKey& key) {
|
|
return EVP_PKEY_id(key.get()) == EVP_PKEY_RSA_PSS ? RSA_PKCS1_PSS_PADDING :
|
|
RSA_PKCS1_PADDING;
|
|
}
|
|
|
|
static const unsigned int kNoDsaSignature = static_cast<unsigned int>(-1);
|
|
|
|
// Returns the maximum size of each of the integers (r, s) of the DSA signature.
|
|
static unsigned int GetBytesOfRS(const ManagedEVPPKey& pkey) {
|
|
int bits, base_id = EVP_PKEY_base_id(pkey.get());
|
|
|
|
if (base_id == EVP_PKEY_DSA) {
|
|
DSA* dsa_key = EVP_PKEY_get0_DSA(pkey.get());
|
|
// Both r and s are computed mod q, so their width is limited by that of q.
|
|
bits = BN_num_bits(DSA_get0_q(dsa_key));
|
|
} else if (base_id == EVP_PKEY_EC) {
|
|
EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey.get());
|
|
const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key);
|
|
bits = EC_GROUP_order_bits(ec_group);
|
|
} else {
|
|
return kNoDsaSignature;
|
|
}
|
|
|
|
return (bits + 7) / 8;
|
|
}
|
|
|
|
static AllocatedBuffer ConvertSignatureToP1363(Environment* env,
|
|
const ManagedEVPPKey& pkey,
|
|
AllocatedBuffer&& signature) {
|
|
unsigned int n = GetBytesOfRS(pkey);
|
|
if (n == kNoDsaSignature)
|
|
return std::move(signature);
|
|
|
|
const unsigned char* sig_data =
|
|
reinterpret_cast<unsigned char*>(signature.data());
|
|
|
|
ECDSASigPointer asn1_sig(d2i_ECDSA_SIG(nullptr, &sig_data, signature.size()));
|
|
if (!asn1_sig)
|
|
return AllocatedBuffer();
|
|
|
|
AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, 2 * n);
|
|
unsigned char* data = reinterpret_cast<unsigned char*>(buf.data());
|
|
|
|
const BIGNUM* r = ECDSA_SIG_get0_r(asn1_sig.get());
|
|
const BIGNUM* s = ECDSA_SIG_get0_s(asn1_sig.get());
|
|
CHECK_EQ(n, static_cast<unsigned int>(BN_bn2binpad(r, data, n)));
|
|
CHECK_EQ(n, static_cast<unsigned int>(BN_bn2binpad(s, data + n, n)));
|
|
|
|
return buf;
|
|
}
|
|
|
|
static ByteSource ConvertSignatureToDER(
|
|
const ManagedEVPPKey& pkey,
|
|
const ArrayBufferViewContents<char>& signature) {
|
|
unsigned int n = GetBytesOfRS(pkey);
|
|
if (n == kNoDsaSignature)
|
|
return ByteSource::Foreign(signature.data(), signature.length());
|
|
|
|
const unsigned char* sig_data =
|
|
reinterpret_cast<const unsigned char*>(signature.data());
|
|
|
|
if (signature.length() != 2 * n)
|
|
return ByteSource();
|
|
|
|
ECDSASigPointer asn1_sig(ECDSA_SIG_new());
|
|
CHECK(asn1_sig);
|
|
BIGNUM* r = BN_new();
|
|
CHECK_NOT_NULL(r);
|
|
BIGNUM* s = BN_new();
|
|
CHECK_NOT_NULL(s);
|
|
CHECK_EQ(r, BN_bin2bn(sig_data, n, r));
|
|
CHECK_EQ(s, BN_bin2bn(sig_data + n, n, s));
|
|
CHECK_EQ(1, ECDSA_SIG_set0(asn1_sig.get(), r, s));
|
|
|
|
unsigned char* data = nullptr;
|
|
int len = i2d_ECDSA_SIG(asn1_sig.get(), &data);
|
|
|
|
if (len <= 0)
|
|
return ByteSource();
|
|
|
|
CHECK_NOT_NULL(data);
|
|
|
|
return ByteSource::Allocated(reinterpret_cast<char*>(data), len);
|
|
}
|
|
|
|
static AllocatedBuffer Node_SignFinal(Environment* env,
|
|
EVPMDPointer&& mdctx,
|
|
const ManagedEVPPKey& pkey,
|
|
int padding,
|
|
Maybe<int> pss_salt_len) {
|
|
unsigned char m[EVP_MAX_MD_SIZE];
|
|
unsigned int m_len;
|
|
|
|
if (!EVP_DigestFinal_ex(mdctx.get(), m, &m_len))
|
|
return AllocatedBuffer();
|
|
|
|
int signed_sig_len = EVP_PKEY_size(pkey.get());
|
|
CHECK_GE(signed_sig_len, 0);
|
|
size_t sig_len = static_cast<size_t>(signed_sig_len);
|
|
AllocatedBuffer sig = AllocatedBuffer::AllocateManaged(env, sig_len);
|
|
|
|
EVPKeyCtxPointer pkctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
|
|
if (pkctx &&
|
|
EVP_PKEY_sign_init(pkctx.get()) > 0 &&
|
|
ApplyRSAOptions(pkey, pkctx.get(), padding, pss_salt_len) &&
|
|
EVP_PKEY_CTX_set_signature_md(pkctx.get(),
|
|
EVP_MD_CTX_md(mdctx.get())) > 0 &&
|
|
EVP_PKEY_sign(pkctx.get(),
|
|
reinterpret_cast<unsigned char*>(sig.data()),
|
|
&sig_len,
|
|
m,
|
|
m_len) > 0) {
|
|
sig.Resize(sig_len);
|
|
return sig;
|
|
}
|
|
|
|
return AllocatedBuffer();
|
|
}
|
|
|
|
static inline bool ValidateDSAParameters(EVP_PKEY* key) {
|
|
#ifdef NODE_FIPS_MODE
|
|
/* Validate DSA2 parameters from FIPS 186-4 */
|
|
if (FIPS_mode() && EVP_PKEY_DSA == EVP_PKEY_base_id(key)) {
|
|
DSA* dsa = EVP_PKEY_get0_DSA(key);
|
|
const BIGNUM* p;
|
|
DSA_get0_pqg(dsa, &p, nullptr, nullptr);
|
|
size_t L = BN_num_bits(p);
|
|
const BIGNUM* q;
|
|
DSA_get0_pqg(dsa, nullptr, &q, nullptr);
|
|
size_t N = BN_num_bits(q);
|
|
|
|
return (L == 1024 && N == 160) ||
|
|
(L == 2048 && N == 224) ||
|
|
(L == 2048 && N == 256) ||
|
|
(L == 3072 && N == 256);
|
|
}
|
|
#endif // NODE_FIPS_MODE
|
|
|
|
return true;
|
|
}
|
|
|
|
Sign::SignResult Sign::SignFinal(
|
|
const ManagedEVPPKey& pkey,
|
|
int padding,
|
|
const Maybe<int>& salt_len,
|
|
DSASigEnc dsa_sig_enc) {
|
|
if (!mdctx_)
|
|
return SignResult(kSignNotInitialised);
|
|
|
|
EVPMDPointer mdctx = std::move(mdctx_);
|
|
|
|
if (!ValidateDSAParameters(pkey.get()))
|
|
return SignResult(kSignPrivateKey);
|
|
|
|
AllocatedBuffer buffer =
|
|
Node_SignFinal(env(), std::move(mdctx), pkey, padding, salt_len);
|
|
Error error = buffer.data() == nullptr ? kSignPrivateKey : kSignOk;
|
|
if (error == kSignOk && dsa_sig_enc == kSigEncP1363) {
|
|
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) {
|
|
Sign* sign;
|
|
ASSIGN_OR_RETURN_UNWRAP(&sign, args.Holder());
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
unsigned int offset = 0;
|
|
ManagedEVPPKey key = GetPrivateKeyFromJs(args, &offset, true);
|
|
if (!key)
|
|
return;
|
|
|
|
int padding = GetDefaultSignPadding(key);
|
|
if (!args[offset]->IsUndefined()) {
|
|
CHECK(args[offset]->IsInt32());
|
|
padding = args[offset].As<Int32>()->Value();
|
|
}
|
|
|
|
Maybe<int> salt_len = Nothing<int>();
|
|
if (!args[offset + 1]->IsUndefined()) {
|
|
CHECK(args[offset + 1]->IsInt32());
|
|
salt_len = Just<int>(args[offset + 1].As<Int32>()->Value());
|
|
}
|
|
|
|
CHECK(args[offset + 2]->IsInt32());
|
|
DSASigEnc dsa_sig_enc =
|
|
static_cast<DSASigEnc>(args[offset + 2].As<Int32>()->Value());
|
|
|
|
SignResult ret = sign->SignFinal(
|
|
key,
|
|
padding,
|
|
salt_len,
|
|
dsa_sig_enc);
|
|
|
|
if (ret.error != kSignOk)
|
|
return sign->CheckThrow(ret.error);
|
|
|
|
args.GetReturnValue().Set(ret.signature.ToBuffer().ToLocalChecked());
|
|
}
|
|
|
|
void SignOneShot(const FunctionCallbackInfo<Value>& args) {
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
unsigned int offset = 0;
|
|
ManagedEVPPKey key = GetPrivateKeyFromJs(args, &offset, true);
|
|
if (!key)
|
|
return;
|
|
|
|
if (!ValidateDSAParameters(key.get()))
|
|
return CheckThrow(env, SignBase::Error::kSignPrivateKey);
|
|
|
|
ArrayBufferViewContents<char> data(args[offset]);
|
|
|
|
const EVP_MD* md;
|
|
if (args[offset + 1]->IsNullOrUndefined()) {
|
|
md = nullptr;
|
|
} else {
|
|
const node::Utf8Value sign_type(args.GetIsolate(), args[offset + 1]);
|
|
md = EVP_get_digestbyname(*sign_type);
|
|
if (md == nullptr)
|
|
return CheckThrow(env, SignBase::Error::kSignUnknownDigest);
|
|
}
|
|
|
|
int rsa_padding = GetDefaultSignPadding(key);
|
|
if (!args[offset + 2]->IsUndefined()) {
|
|
CHECK(args[offset + 2]->IsInt32());
|
|
rsa_padding = args[offset + 2].As<Int32>()->Value();
|
|
}
|
|
|
|
Maybe<int> rsa_salt_len = Nothing<int>();
|
|
if (!args[offset + 3]->IsUndefined()) {
|
|
CHECK(args[offset + 3]->IsInt32());
|
|
rsa_salt_len = Just<int>(args[offset + 3].As<Int32>()->Value());
|
|
}
|
|
|
|
CHECK(args[offset + 4]->IsInt32());
|
|
DSASigEnc dsa_sig_enc =
|
|
static_cast<DSASigEnc>(args[offset + 4].As<Int32>()->Value());
|
|
|
|
EVP_PKEY_CTX* pkctx = nullptr;
|
|
EVPMDPointer mdctx(EVP_MD_CTX_new());
|
|
if (!mdctx ||
|
|
!EVP_DigestSignInit(mdctx.get(), &pkctx, md, nullptr, key.get())) {
|
|
return CheckThrow(env, SignBase::Error::kSignInit);
|
|
}
|
|
|
|
if (!ApplyRSAOptions(key, pkctx, rsa_padding, rsa_salt_len))
|
|
return CheckThrow(env, SignBase::Error::kSignPrivateKey);
|
|
|
|
const unsigned char* input =
|
|
reinterpret_cast<const unsigned char*>(data.data());
|
|
size_t sig_len;
|
|
if (!EVP_DigestSign(mdctx.get(), nullptr, &sig_len, input, data.length()))
|
|
return CheckThrow(env, SignBase::Error::kSignPrivateKey);
|
|
|
|
AllocatedBuffer signature = AllocatedBuffer::AllocateManaged(env, sig_len);
|
|
if (!EVP_DigestSign(mdctx.get(),
|
|
reinterpret_cast<unsigned char*>(signature.data()),
|
|
&sig_len,
|
|
input,
|
|
data.length())) {
|
|
return CheckThrow(env, SignBase::Error::kSignPrivateKey);
|
|
}
|
|
|
|
signature.Resize(sig_len);
|
|
|
|
if (dsa_sig_enc == kSigEncP1363) {
|
|
signature = ConvertSignatureToP1363(env, key, std::move(signature));
|
|
}
|
|
|
|
args.GetReturnValue().Set(signature.ToBuffer().ToLocalChecked());
|
|
}
|
|
|
|
Verify::Verify(Environment* env, Local<Object> wrap)
|
|
: SignBase(env, wrap) {
|
|
MakeWeak();
|
|
}
|
|
|
|
void Verify::Initialize(Environment* env, Local<Object> target) {
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(
|
|
SignBase::kInternalFieldCount);
|
|
t->Inherit(BaseObject::GetConstructorTemplate(env));
|
|
|
|
env->SetProtoMethod(t, "init", VerifyInit);
|
|
env->SetProtoMethod(t, "update", VerifyUpdate);
|
|
env->SetProtoMethod(t, "verify", VerifyFinal);
|
|
|
|
target->Set(env->context(),
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "Verify"),
|
|
t->GetFunction(env->context()).ToLocalChecked()).Check();
|
|
}
|
|
|
|
|
|
void Verify::New(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
new Verify(env, args.This());
|
|
}
|
|
|
|
|
|
void Verify::VerifyInit(const FunctionCallbackInfo<Value>& args) {
|
|
Verify* verify;
|
|
ASSIGN_OR_RETURN_UNWRAP(&verify, args.Holder());
|
|
|
|
const node::Utf8Value verify_type(args.GetIsolate(), args[0]);
|
|
verify->CheckThrow(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) {
|
|
Error err = verify->Update(data, size);
|
|
verify->CheckThrow(err);
|
|
});
|
|
}
|
|
|
|
|
|
SignBase::Error Verify::VerifyFinal(const ManagedEVPPKey& pkey,
|
|
const ByteSource& sig,
|
|
int padding,
|
|
const Maybe<int>& saltlen,
|
|
bool* verify_result) {
|
|
if (!mdctx_)
|
|
return kSignNotInitialised;
|
|
|
|
unsigned char m[EVP_MAX_MD_SIZE];
|
|
unsigned int m_len;
|
|
*verify_result = false;
|
|
EVPMDPointer mdctx = std::move(mdctx_);
|
|
|
|
if (!EVP_DigestFinal_ex(mdctx.get(), m, &m_len))
|
|
return kSignPublicKey;
|
|
|
|
EVPKeyCtxPointer pkctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
|
|
if (pkctx &&
|
|
EVP_PKEY_verify_init(pkctx.get()) > 0 &&
|
|
ApplyRSAOptions(pkey, pkctx.get(), padding, saltlen) &&
|
|
EVP_PKEY_CTX_set_signature_md(pkctx.get(),
|
|
EVP_MD_CTX_md(mdctx.get())) > 0) {
|
|
const unsigned char* s = reinterpret_cast<const unsigned char*>(sig.get());
|
|
const int r = EVP_PKEY_verify(pkctx.get(), s, sig.size(), m, m_len);
|
|
*verify_result = r == 1;
|
|
}
|
|
|
|
return kSignOk;
|
|
}
|
|
|
|
|
|
void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
Verify* verify;
|
|
ASSIGN_OR_RETURN_UNWRAP(&verify, args.Holder());
|
|
|
|
unsigned int offset = 0;
|
|
ManagedEVPPKey pkey = GetPublicOrPrivateKeyFromJs(args, &offset);
|
|
if (!pkey)
|
|
return;
|
|
|
|
ArrayBufferViewContents<char> hbuf(args[offset]);
|
|
|
|
int padding = GetDefaultSignPadding(pkey);
|
|
if (!args[offset + 1]->IsUndefined()) {
|
|
CHECK(args[offset + 1]->IsInt32());
|
|
padding = args[offset + 1].As<Int32>()->Value();
|
|
}
|
|
|
|
Maybe<int> salt_len = Nothing<int>();
|
|
if (!args[offset + 2]->IsUndefined()) {
|
|
CHECK(args[offset + 2]->IsInt32());
|
|
salt_len = Just<int>(args[offset + 2].As<Int32>()->Value());
|
|
}
|
|
|
|
CHECK(args[offset + 3]->IsInt32());
|
|
DSASigEnc dsa_sig_enc =
|
|
static_cast<DSASigEnc>(args[offset + 3].As<Int32>()->Value());
|
|
|
|
ByteSource signature = ByteSource::Foreign(hbuf.data(), hbuf.length());
|
|
if (dsa_sig_enc == kSigEncP1363) {
|
|
signature = ConvertSignatureToDER(pkey, hbuf);
|
|
if (signature.get() == nullptr)
|
|
return verify->CheckThrow(Error::kSignMalformedSignature);
|
|
}
|
|
|
|
bool verify_result;
|
|
Error err = verify->VerifyFinal(pkey, signature, padding,
|
|
salt_len, &verify_result);
|
|
if (err != kSignOk)
|
|
return verify->CheckThrow(err);
|
|
args.GetReturnValue().Set(verify_result);
|
|
}
|
|
|
|
void VerifyOneShot(const FunctionCallbackInfo<Value>& args) {
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
unsigned int offset = 0;
|
|
ManagedEVPPKey key = GetPublicOrPrivateKeyFromJs(args, &offset);
|
|
if (!key)
|
|
return;
|
|
|
|
ArrayBufferViewContents<char> sig(args[offset]);
|
|
|
|
ArrayBufferViewContents<char> data(args[offset + 1]);
|
|
|
|
const EVP_MD* md;
|
|
if (args[offset + 2]->IsNullOrUndefined()) {
|
|
md = nullptr;
|
|
} else {
|
|
const node::Utf8Value sign_type(args.GetIsolate(), args[offset + 2]);
|
|
md = EVP_get_digestbyname(*sign_type);
|
|
if (md == nullptr)
|
|
return CheckThrow(env, SignBase::Error::kSignUnknownDigest);
|
|
}
|
|
|
|
int rsa_padding = GetDefaultSignPadding(key);
|
|
if (!args[offset + 3]->IsUndefined()) {
|
|
CHECK(args[offset + 3]->IsInt32());
|
|
rsa_padding = args[offset + 3].As<Int32>()->Value();
|
|
}
|
|
|
|
Maybe<int> rsa_salt_len = Nothing<int>();
|
|
if (!args[offset + 4]->IsUndefined()) {
|
|
CHECK(args[offset + 4]->IsInt32());
|
|
rsa_salt_len = Just<int>(args[offset + 4].As<Int32>()->Value());
|
|
}
|
|
|
|
CHECK(args[offset + 5]->IsInt32());
|
|
DSASigEnc dsa_sig_enc =
|
|
static_cast<DSASigEnc>(args[offset + 5].As<Int32>()->Value());
|
|
|
|
EVP_PKEY_CTX* pkctx = nullptr;
|
|
EVPMDPointer mdctx(EVP_MD_CTX_new());
|
|
if (!mdctx ||
|
|
!EVP_DigestVerifyInit(mdctx.get(), &pkctx, md, nullptr, key.get())) {
|
|
return CheckThrow(env, SignBase::Error::kSignInit);
|
|
}
|
|
|
|
if (!ApplyRSAOptions(key, pkctx, rsa_padding, rsa_salt_len))
|
|
return CheckThrow(env, SignBase::Error::kSignPublicKey);
|
|
|
|
ByteSource sig_bytes = ByteSource::Foreign(sig.data(), sig.length());
|
|
if (dsa_sig_enc == kSigEncP1363) {
|
|
sig_bytes = ConvertSignatureToDER(key, sig);
|
|
if (!sig_bytes)
|
|
return CheckThrow(env, SignBase::Error::kSignMalformedSignature);
|
|
}
|
|
|
|
bool verify_result;
|
|
const int r = EVP_DigestVerify(
|
|
mdctx.get(),
|
|
reinterpret_cast<const unsigned char*>(sig_bytes.get()),
|
|
sig_bytes.size(),
|
|
reinterpret_cast<const unsigned char*>(data.data()),
|
|
data.length());
|
|
switch (r) {
|
|
case 1:
|
|
verify_result = true;
|
|
break;
|
|
case 0:
|
|
verify_result = false;
|
|
break;
|
|
default:
|
|
return CheckThrow(env, SignBase::Error::kSignPublicKey);
|
|
}
|
|
|
|
args.GetReturnValue().Set(verify_result);
|
|
}
|
|
|
|
template <PublicKeyCipher::Operation operation,
|
|
PublicKeyCipher::EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
|
|
PublicKeyCipher::EVP_PKEY_cipher_t EVP_PKEY_cipher>
|
|
bool PublicKeyCipher::Cipher(Environment* env,
|
|
const ManagedEVPPKey& pkey,
|
|
int padding,
|
|
const EVP_MD* digest,
|
|
const void* oaep_label,
|
|
size_t oaep_label_len,
|
|
const unsigned char* data,
|
|
int len,
|
|
AllocatedBuffer* out) {
|
|
EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
|
|
if (!ctx)
|
|
return false;
|
|
if (EVP_PKEY_cipher_init(ctx.get()) <= 0)
|
|
return false;
|
|
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), padding) <= 0)
|
|
return false;
|
|
|
|
if (digest != nullptr) {
|
|
if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), digest) <= 0)
|
|
return false;
|
|
}
|
|
|
|
if (oaep_label_len != 0) {
|
|
// OpenSSL takes ownership of the label, so we need to create a copy.
|
|
void* label = OPENSSL_memdup(oaep_label, oaep_label_len);
|
|
CHECK_NOT_NULL(label);
|
|
if (0 >= EVP_PKEY_CTX_set0_rsa_oaep_label(ctx.get(),
|
|
reinterpret_cast<unsigned char*>(label),
|
|
oaep_label_len)) {
|
|
OPENSSL_free(label);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
size_t out_len = 0;
|
|
if (EVP_PKEY_cipher(ctx.get(), nullptr, &out_len, data, len) <= 0)
|
|
return false;
|
|
|
|
*out = AllocatedBuffer::AllocateManaged(env, out_len);
|
|
|
|
if (EVP_PKEY_cipher(ctx.get(),
|
|
reinterpret_cast<unsigned char*>(out->data()),
|
|
&out_len,
|
|
data,
|
|
len) <= 0) {
|
|
return false;
|
|
}
|
|
|
|
out->Resize(out_len);
|
|
return true;
|
|
}
|
|
|
|
|
|
template <PublicKeyCipher::Operation operation,
|
|
PublicKeyCipher::EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
|
|
PublicKeyCipher::EVP_PKEY_cipher_t EVP_PKEY_cipher>
|
|
void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
unsigned int offset = 0;
|
|
ManagedEVPPKey pkey = GetPublicOrPrivateKeyFromJs(args, &offset);
|
|
if (!pkey)
|
|
return;
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(env, args[offset], "Data");
|
|
ArrayBufferViewContents<unsigned char> buf(args[offset]);
|
|
|
|
uint32_t padding;
|
|
if (!args[offset + 1]->Uint32Value(env->context()).To(&padding)) return;
|
|
|
|
const node::Utf8Value oaep_str(env->isolate(), args[offset + 2]);
|
|
const char* oaep_hash = args[offset + 2]->IsString() ? *oaep_str : nullptr;
|
|
const EVP_MD* digest = nullptr;
|
|
if (oaep_hash != nullptr) {
|
|
digest = EVP_get_digestbyname(oaep_hash);
|
|
if (digest == nullptr)
|
|
return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env);
|
|
}
|
|
|
|
ArrayBufferViewContents<unsigned char> oaep_label;
|
|
if (!args[offset + 3]->IsUndefined()) {
|
|
CHECK(args[offset + 3]->IsArrayBufferView());
|
|
oaep_label.Read(args[offset + 3].As<ArrayBufferView>());
|
|
}
|
|
|
|
AllocatedBuffer out;
|
|
|
|
bool r = Cipher<operation, EVP_PKEY_cipher_init, EVP_PKEY_cipher>(
|
|
env,
|
|
pkey,
|
|
padding,
|
|
digest,
|
|
oaep_label.data(),
|
|
oaep_label.length(),
|
|
buf.data(),
|
|
buf.length(),
|
|
&out);
|
|
|
|
if (!r)
|
|
return ThrowCryptoError(env, ERR_get_error());
|
|
|
|
args.GetReturnValue().Set(out.ToBuffer().ToLocalChecked());
|
|
}
|
|
|
|
DiffieHellman::DiffieHellman(Environment* env, Local<Object> wrap)
|
|
: BaseObject(env, wrap), verifyError_(0) {
|
|
MakeWeak();
|
|
}
|
|
|
|
void DiffieHellman::Initialize(Environment* env, Local<Object> target) {
|
|
auto make = [&] (Local<String> name, FunctionCallback callback) {
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(callback);
|
|
|
|
const PropertyAttribute attributes =
|
|
static_cast<PropertyAttribute>(ReadOnly | DontDelete);
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(
|
|
DiffieHellman::kInternalFieldCount);
|
|
t->Inherit(BaseObject::GetConstructorTemplate(env));
|
|
|
|
env->SetProtoMethod(t, "generateKeys", GenerateKeys);
|
|
env->SetProtoMethod(t, "computeSecret", ComputeSecret);
|
|
env->SetProtoMethodNoSideEffect(t, "getPrime", GetPrime);
|
|
env->SetProtoMethodNoSideEffect(t, "getGenerator", GetGenerator);
|
|
env->SetProtoMethodNoSideEffect(t, "getPublicKey", GetPublicKey);
|
|
env->SetProtoMethodNoSideEffect(t, "getPrivateKey", GetPrivateKey);
|
|
env->SetProtoMethod(t, "setPublicKey", SetPublicKey);
|
|
env->SetProtoMethod(t, "setPrivateKey", SetPrivateKey);
|
|
|
|
Local<FunctionTemplate> verify_error_getter_templ =
|
|
FunctionTemplate::New(env->isolate(),
|
|
DiffieHellman::VerifyErrorGetter,
|
|
Local<Value>(),
|
|
Signature::New(env->isolate(), t),
|
|
/* length */ 0,
|
|
ConstructorBehavior::kThrow,
|
|
SideEffectType::kHasNoSideEffect);
|
|
|
|
t->InstanceTemplate()->SetAccessorProperty(
|
|
env->verify_error_string(),
|
|
verify_error_getter_templ,
|
|
Local<FunctionTemplate>(),
|
|
attributes);
|
|
|
|
target->Set(env->context(),
|
|
name,
|
|
t->GetFunction(env->context()).ToLocalChecked()).Check();
|
|
};
|
|
|
|
make(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellman"), New);
|
|
make(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellmanGroup"),
|
|
DiffieHellmanGroup);
|
|
}
|
|
|
|
|
|
bool DiffieHellman::Init(int primeLength, int g) {
|
|
dh_.reset(DH_new());
|
|
if (!DH_generate_parameters_ex(dh_.get(), primeLength, g, nullptr))
|
|
return false;
|
|
return VerifyContext();
|
|
}
|
|
|
|
|
|
bool DiffieHellman::Init(const char* p, int p_len, int g) {
|
|
dh_.reset(DH_new());
|
|
if (p_len <= 0) {
|
|
BNerr(BN_F_BN_GENERATE_PRIME_EX, BN_R_BITS_TOO_SMALL);
|
|
return false;
|
|
}
|
|
if (g <= 1) {
|
|
DHerr(DH_F_DH_BUILTIN_GENPARAMS, DH_R_BAD_GENERATOR);
|
|
return false;
|
|
}
|
|
BIGNUM* bn_p =
|
|
BN_bin2bn(reinterpret_cast<const unsigned char*>(p), p_len, nullptr);
|
|
BIGNUM* bn_g = BN_new();
|
|
if (!BN_set_word(bn_g, g) ||
|
|
!DH_set0_pqg(dh_.get(), bn_p, nullptr, bn_g)) {
|
|
BN_free(bn_p);
|
|
BN_free(bn_g);
|
|
return false;
|
|
}
|
|
return VerifyContext();
|
|
}
|
|
|
|
|
|
bool DiffieHellman::Init(const char* p, int p_len, const char* g, int g_len) {
|
|
dh_.reset(DH_new());
|
|
if (p_len <= 0) {
|
|
BNerr(BN_F_BN_GENERATE_PRIME_EX, BN_R_BITS_TOO_SMALL);
|
|
return false;
|
|
}
|
|
if (g_len <= 0) {
|
|
DHerr(DH_F_DH_BUILTIN_GENPARAMS, DH_R_BAD_GENERATOR);
|
|
return false;
|
|
}
|
|
BIGNUM* bn_g =
|
|
BN_bin2bn(reinterpret_cast<const unsigned char*>(g), g_len, nullptr);
|
|
if (BN_is_zero(bn_g) || BN_is_one(bn_g)) {
|
|
BN_free(bn_g);
|
|
DHerr(DH_F_DH_BUILTIN_GENPARAMS, DH_R_BAD_GENERATOR);
|
|
return false;
|
|
}
|
|
BIGNUM* bn_p =
|
|
BN_bin2bn(reinterpret_cast<const unsigned char*>(p), p_len, nullptr);
|
|
if (!DH_set0_pqg(dh_.get(), bn_p, nullptr, bn_g)) {
|
|
BN_free(bn_p);
|
|
BN_free(bn_g);
|
|
return false;
|
|
}
|
|
return VerifyContext();
|
|
}
|
|
|
|
inline const modp_group* FindDiffieHellmanGroup(const char* name) {
|
|
for (const modp_group& group : modp_groups) {
|
|
if (StringEqualNoCase(name, group.name))
|
|
return &group;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void DiffieHellman::DiffieHellmanGroup(
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
DiffieHellman* diffieHellman = new DiffieHellman(env, args.This());
|
|
|
|
CHECK_EQ(args.Length(), 1);
|
|
THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "Group name");
|
|
|
|
bool initialized = false;
|
|
|
|
const node::Utf8Value group_name(env->isolate(), args[0]);
|
|
const modp_group* group = FindDiffieHellmanGroup(*group_name);
|
|
if (group == nullptr)
|
|
return THROW_ERR_CRYPTO_UNKNOWN_DH_GROUP(env);
|
|
|
|
initialized = diffieHellman->Init(group->prime,
|
|
group->prime_size,
|
|
group->gen);
|
|
if (!initialized)
|
|
env->ThrowError("Initialization failed");
|
|
}
|
|
|
|
|
|
void DiffieHellman::New(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
DiffieHellman* diffieHellman =
|
|
new DiffieHellman(env, args.This());
|
|
bool initialized = false;
|
|
|
|
if (args.Length() == 2) {
|
|
if (args[0]->IsInt32()) {
|
|
if (args[1]->IsInt32()) {
|
|
initialized = diffieHellman->Init(args[0].As<Int32>()->Value(),
|
|
args[1].As<Int32>()->Value());
|
|
}
|
|
} else {
|
|
ArrayBufferViewContents<char> arg0(args[0]);
|
|
if (args[1]->IsInt32()) {
|
|
initialized = diffieHellman->Init(arg0.data(),
|
|
arg0.length(),
|
|
args[1].As<Int32>()->Value());
|
|
} else {
|
|
ArrayBufferViewContents<char> arg1(args[1]);
|
|
initialized = diffieHellman->Init(arg0.data(), arg0.length(),
|
|
arg1.data(), arg1.length());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!initialized) {
|
|
return ThrowCryptoError(env, ERR_get_error(), "Initialization failed");
|
|
}
|
|
}
|
|
|
|
|
|
void DiffieHellman::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
DiffieHellman* diffieHellman;
|
|
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder());
|
|
|
|
if (!DH_generate_key(diffieHellman->dh_.get())) {
|
|
return ThrowCryptoError(env, ERR_get_error(), "Key generation failed");
|
|
}
|
|
|
|
const BIGNUM* pub_key;
|
|
DH_get0_key(diffieHellman->dh_.get(), &pub_key, nullptr);
|
|
const int size = BN_num_bytes(pub_key);
|
|
CHECK_GE(size, 0);
|
|
AllocatedBuffer data = AllocatedBuffer::AllocateManaged(env, size);
|
|
CHECK_EQ(size,
|
|
BN_bn2binpad(
|
|
pub_key, reinterpret_cast<unsigned char*>(data.data()), size));
|
|
args.GetReturnValue().Set(data.ToBuffer().ToLocalChecked());
|
|
}
|
|
|
|
|
|
void DiffieHellman::GetField(const FunctionCallbackInfo<Value>& args,
|
|
const BIGNUM* (*get_field)(const DH*),
|
|
const char* err_if_null) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
DiffieHellman* dh;
|
|
ASSIGN_OR_RETURN_UNWRAP(&dh, args.Holder());
|
|
|
|
const BIGNUM* num = get_field(dh->dh_.get());
|
|
if (num == nullptr) return env->ThrowError(err_if_null);
|
|
|
|
const int size = BN_num_bytes(num);
|
|
CHECK_GE(size, 0);
|
|
AllocatedBuffer data = AllocatedBuffer::AllocateManaged(env, size);
|
|
CHECK_EQ(
|
|
size,
|
|
BN_bn2binpad(num, reinterpret_cast<unsigned char*>(data.data()), size));
|
|
args.GetReturnValue().Set(data.ToBuffer().ToLocalChecked());
|
|
}
|
|
|
|
void DiffieHellman::GetPrime(const FunctionCallbackInfo<Value>& args) {
|
|
GetField(args, [](const DH* dh) -> const BIGNUM* {
|
|
const BIGNUM* p;
|
|
DH_get0_pqg(dh, &p, nullptr, nullptr);
|
|
return p;
|
|
}, "p is null");
|
|
}
|
|
|
|
|
|
void DiffieHellman::GetGenerator(const FunctionCallbackInfo<Value>& args) {
|
|
GetField(args, [](const DH* dh) -> const BIGNUM* {
|
|
const BIGNUM* g;
|
|
DH_get0_pqg(dh, nullptr, nullptr, &g);
|
|
return g;
|
|
}, "g is null");
|
|
}
|
|
|
|
|
|
void DiffieHellman::GetPublicKey(const FunctionCallbackInfo<Value>& args) {
|
|
GetField(args, [](const DH* dh) -> const BIGNUM* {
|
|
const BIGNUM* pub_key;
|
|
DH_get0_key(dh, &pub_key, nullptr);
|
|
return pub_key;
|
|
}, "No public key - did you forget to generate one?");
|
|
}
|
|
|
|
|
|
void DiffieHellman::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
|
|
GetField(args, [](const DH* dh) -> const BIGNUM* {
|
|
const BIGNUM* priv_key;
|
|
DH_get0_key(dh, nullptr, &priv_key);
|
|
return priv_key;
|
|
}, "No private key - did you forget to generate one?");
|
|
}
|
|
|
|
static void ZeroPadDiffieHellmanSecret(size_t remainder_size,
|
|
AllocatedBuffer* ret) {
|
|
// DH_size returns number of bytes in a prime number.
|
|
// DH_compute_key returns number of bytes in a remainder of exponent, which
|
|
// may have less bytes than a prime number. Therefore add 0-padding to the
|
|
// allocated buffer.
|
|
const size_t prime_size = ret->size();
|
|
if (remainder_size != prime_size) {
|
|
CHECK_LT(remainder_size, prime_size);
|
|
const size_t padding = prime_size - remainder_size;
|
|
memmove(ret->data() + padding, ret->data(), remainder_size);
|
|
memset(ret->data(), 0, padding);
|
|
}
|
|
}
|
|
|
|
void DiffieHellman::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
DiffieHellman* diffieHellman;
|
|
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder());
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
CHECK_EQ(args.Length(), 1);
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Other party's public key");
|
|
ArrayBufferViewContents<unsigned char> key_buf(args[0].As<ArrayBufferView>());
|
|
BignumPointer key(BN_bin2bn(key_buf.data(), key_buf.length(), nullptr));
|
|
|
|
AllocatedBuffer ret =
|
|
AllocatedBuffer::AllocateManaged(env, DH_size(diffieHellman->dh_.get()));
|
|
|
|
int size = DH_compute_key(reinterpret_cast<unsigned char*>(ret.data()),
|
|
key.get(),
|
|
diffieHellman->dh_.get());
|
|
|
|
if (size == -1) {
|
|
int checkResult;
|
|
int checked;
|
|
|
|
checked = DH_check_pub_key(diffieHellman->dh_.get(),
|
|
key.get(),
|
|
&checkResult);
|
|
|
|
if (!checked) {
|
|
return ThrowCryptoError(env, ERR_get_error(), "Invalid Key");
|
|
} else if (checkResult) {
|
|
if (checkResult & DH_CHECK_PUBKEY_TOO_SMALL) {
|
|
return env->ThrowError("Supplied key is too small");
|
|
} else if (checkResult & DH_CHECK_PUBKEY_TOO_LARGE) {
|
|
return env->ThrowError("Supplied key is too large");
|
|
} else {
|
|
return env->ThrowError("Invalid key");
|
|
}
|
|
} else {
|
|
return env->ThrowError("Invalid key");
|
|
}
|
|
|
|
UNREACHABLE();
|
|
}
|
|
|
|
CHECK_GE(size, 0);
|
|
ZeroPadDiffieHellmanSecret(static_cast<size_t>(size), &ret);
|
|
|
|
args.GetReturnValue().Set(ret.ToBuffer().ToLocalChecked());
|
|
}
|
|
|
|
void DiffieHellman::SetKey(const FunctionCallbackInfo<Value>& args,
|
|
int (*set_field)(DH*, BIGNUM*), const char* what) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
DiffieHellman* dh;
|
|
ASSIGN_OR_RETURN_UNWRAP(&dh, args.Holder());
|
|
|
|
char errmsg[64];
|
|
|
|
CHECK_EQ(args.Length(), 1);
|
|
if (!Buffer::HasInstance(args[0])) {
|
|
snprintf(errmsg, sizeof(errmsg), "%s must be a buffer", what);
|
|
return THROW_ERR_INVALID_ARG_TYPE(env, errmsg);
|
|
}
|
|
|
|
ArrayBufferViewContents<unsigned char> buf(args[0].As<ArrayBufferView>());
|
|
BIGNUM* num =
|
|
BN_bin2bn(buf.data(), buf.length(), nullptr);
|
|
CHECK_NOT_NULL(num);
|
|
CHECK_EQ(1, set_field(dh->dh_.get(), num));
|
|
}
|
|
|
|
|
|
void DiffieHellman::SetPublicKey(const FunctionCallbackInfo<Value>& args) {
|
|
SetKey(args,
|
|
[](DH* dh, BIGNUM* num) { return DH_set0_key(dh, num, nullptr); },
|
|
"Public key");
|
|
}
|
|
|
|
void DiffieHellman::SetPrivateKey(const FunctionCallbackInfo<Value>& args) {
|
|
SetKey(args,
|
|
[](DH* dh, BIGNUM* num) { return DH_set0_key(dh, nullptr, num); },
|
|
"Private key");
|
|
}
|
|
|
|
|
|
void DiffieHellman::VerifyErrorGetter(const FunctionCallbackInfo<Value>& args) {
|
|
HandleScope scope(args.GetIsolate());
|
|
|
|
DiffieHellman* diffieHellman;
|
|
ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder());
|
|
|
|
args.GetReturnValue().Set(diffieHellman->verifyError_);
|
|
}
|
|
|
|
|
|
bool DiffieHellman::VerifyContext() {
|
|
int codes;
|
|
if (!DH_check(dh_.get(), &codes))
|
|
return false;
|
|
verifyError_ = codes;
|
|
return true;
|
|
}
|
|
|
|
|
|
void ECDH::Initialize(Environment* env, Local<Object> target) {
|
|
HandleScope scope(env->isolate());
|
|
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
t->Inherit(BaseObject::GetConstructorTemplate(env));
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(ECDH::kInternalFieldCount);
|
|
|
|
env->SetProtoMethod(t, "generateKeys", GenerateKeys);
|
|
env->SetProtoMethod(t, "computeSecret", ComputeSecret);
|
|
env->SetProtoMethodNoSideEffect(t, "getPublicKey", GetPublicKey);
|
|
env->SetProtoMethodNoSideEffect(t, "getPrivateKey", GetPrivateKey);
|
|
env->SetProtoMethod(t, "setPublicKey", SetPublicKey);
|
|
env->SetProtoMethod(t, "setPrivateKey", SetPrivateKey);
|
|
|
|
target->Set(env->context(),
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "ECDH"),
|
|
t->GetFunction(env->context()).ToLocalChecked()).Check();
|
|
}
|
|
|
|
ECDH::ECDH(Environment* env, Local<Object> wrap, ECKeyPointer&& key)
|
|
: BaseObject(env, wrap),
|
|
key_(std::move(key)),
|
|
group_(EC_KEY_get0_group(key_.get())) {
|
|
MakeWeak();
|
|
CHECK_NOT_NULL(group_);
|
|
}
|
|
|
|
ECDH::~ECDH() {}
|
|
|
|
void ECDH::New(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
// TODO(indutny): Support raw curves?
|
|
CHECK(args[0]->IsString());
|
|
node::Utf8Value curve(env->isolate(), args[0]);
|
|
|
|
int nid = OBJ_sn2nid(*curve);
|
|
if (nid == NID_undef)
|
|
return THROW_ERR_INVALID_ARG_VALUE(env,
|
|
"First argument should be a valid curve name");
|
|
|
|
ECKeyPointer key(EC_KEY_new_by_curve_name(nid));
|
|
if (!key)
|
|
return env->ThrowError("Failed to create EC_KEY using curve name");
|
|
|
|
new ECDH(env, args.This(), std::move(key));
|
|
}
|
|
|
|
|
|
void ECDH::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
ECDH* ecdh;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder());
|
|
|
|
if (!EC_KEY_generate_key(ecdh->key_.get()))
|
|
return env->ThrowError("Failed to generate EC_KEY");
|
|
}
|
|
|
|
|
|
ECPointPointer ECDH::BufferToPoint(Environment* env,
|
|
const EC_GROUP* group,
|
|
Local<Value> buf) {
|
|
int r;
|
|
|
|
ECPointPointer pub(EC_POINT_new(group));
|
|
if (!pub) {
|
|
env->ThrowError("Failed to allocate EC_POINT for a public key");
|
|
return pub;
|
|
}
|
|
|
|
ArrayBufferViewContents<unsigned char> input(buf);
|
|
r = EC_POINT_oct2point(
|
|
group,
|
|
pub.get(),
|
|
input.data(),
|
|
input.length(),
|
|
nullptr);
|
|
if (!r)
|
|
return ECPointPointer();
|
|
|
|
return pub;
|
|
}
|
|
|
|
|
|
void ECDH::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Data");
|
|
|
|
ECDH* ecdh;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder());
|
|
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
if (!ecdh->IsKeyPairValid())
|
|
return env->ThrowError("Invalid key pair");
|
|
|
|
ECPointPointer pub(
|
|
ECDH::BufferToPoint(env,
|
|
ecdh->group_,
|
|
args[0]));
|
|
if (!pub) {
|
|
args.GetReturnValue().Set(
|
|
FIXED_ONE_BYTE_STRING(env->isolate(),
|
|
"ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY"));
|
|
return;
|
|
}
|
|
|
|
// NOTE: field_size is in bits
|
|
int field_size = EC_GROUP_get_degree(ecdh->group_);
|
|
size_t out_len = (field_size + 7) / 8;
|
|
AllocatedBuffer out = AllocatedBuffer::AllocateManaged(env, out_len);
|
|
|
|
int r = ECDH_compute_key(
|
|
out.data(), out_len, pub.get(), ecdh->key_.get(), nullptr);
|
|
if (!r)
|
|
return env->ThrowError("Failed to compute ECDH key");
|
|
|
|
Local<Object> buf = out.ToBuffer().ToLocalChecked();
|
|
args.GetReturnValue().Set(buf);
|
|
}
|
|
|
|
|
|
void ECDH::GetPublicKey(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
// Conversion form
|
|
CHECK_EQ(args.Length(), 1);
|
|
|
|
ECDH* ecdh;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder());
|
|
|
|
const EC_GROUP* group = EC_KEY_get0_group(ecdh->key_.get());
|
|
const EC_POINT* pub = EC_KEY_get0_public_key(ecdh->key_.get());
|
|
if (pub == nullptr)
|
|
return env->ThrowError("Failed to get ECDH public key");
|
|
|
|
CHECK(args[0]->IsUint32());
|
|
uint32_t val = args[0].As<Uint32>()->Value();
|
|
point_conversion_form_t form = static_cast<point_conversion_form_t>(val);
|
|
|
|
const char* error;
|
|
Local<Object> buf;
|
|
if (!ECPointToBuffer(env, group, pub, form, &error).ToLocal(&buf))
|
|
return env->ThrowError(error);
|
|
args.GetReturnValue().Set(buf);
|
|
}
|
|
|
|
|
|
void ECDH::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
ECDH* ecdh;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder());
|
|
|
|
const BIGNUM* b = EC_KEY_get0_private_key(ecdh->key_.get());
|
|
if (b == nullptr)
|
|
return env->ThrowError("Failed to get ECDH private key");
|
|
|
|
const int size = BN_num_bytes(b);
|
|
AllocatedBuffer out = AllocatedBuffer::AllocateManaged(env, size);
|
|
CHECK_EQ(size, BN_bn2binpad(b,
|
|
reinterpret_cast<unsigned char*>(out.data()),
|
|
size));
|
|
|
|
Local<Object> buf = out.ToBuffer().ToLocalChecked();
|
|
args.GetReturnValue().Set(buf);
|
|
}
|
|
|
|
|
|
void ECDH::SetPrivateKey(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
ECDH* ecdh;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder());
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Private key");
|
|
ArrayBufferViewContents<unsigned char> priv_buffer(args[0]);
|
|
|
|
BignumPointer priv(BN_bin2bn(
|
|
priv_buffer.data(), priv_buffer.length(), nullptr));
|
|
if (!priv)
|
|
return env->ThrowError("Failed to convert Buffer to BN");
|
|
|
|
if (!ecdh->IsKeyValidForCurve(priv)) {
|
|
return env->ThrowError("Private key is not valid for specified curve.");
|
|
}
|
|
|
|
int result = EC_KEY_set_private_key(ecdh->key_.get(), priv.get());
|
|
priv.reset();
|
|
|
|
if (!result) {
|
|
return env->ThrowError("Failed to convert BN to a private key");
|
|
}
|
|
|
|
// To avoid inconsistency, clear the current public key in-case computing
|
|
// the new one fails for some reason.
|
|
EC_KEY_set_public_key(ecdh->key_.get(), nullptr);
|
|
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
USE(&mark_pop_error_on_return);
|
|
|
|
const BIGNUM* priv_key = EC_KEY_get0_private_key(ecdh->key_.get());
|
|
CHECK_NOT_NULL(priv_key);
|
|
|
|
ECPointPointer pub(EC_POINT_new(ecdh->group_));
|
|
CHECK(pub);
|
|
|
|
if (!EC_POINT_mul(ecdh->group_, pub.get(), priv_key,
|
|
nullptr, nullptr, nullptr)) {
|
|
return env->ThrowError("Failed to generate ECDH public key");
|
|
}
|
|
|
|
if (!EC_KEY_set_public_key(ecdh->key_.get(), pub.get()))
|
|
return env->ThrowError("Failed to set generated public key");
|
|
}
|
|
|
|
|
|
void ECDH::SetPublicKey(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
ECDH* ecdh;
|
|
ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder());
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Public key");
|
|
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
ECPointPointer pub(
|
|
ECDH::BufferToPoint(env,
|
|
ecdh->group_,
|
|
args[0]));
|
|
if (!pub)
|
|
return env->ThrowError("Failed to convert Buffer to EC_POINT");
|
|
|
|
int r = EC_KEY_set_public_key(ecdh->key_.get(), pub.get());
|
|
if (!r)
|
|
return env->ThrowError("Failed to set EC_POINT as the public key");
|
|
}
|
|
|
|
|
|
bool ECDH::IsKeyValidForCurve(const BignumPointer& private_key) {
|
|
CHECK(group_);
|
|
CHECK(private_key);
|
|
// Private keys must be in the range [1, n-1].
|
|
// Ref: Section 3.2.1 - http://www.secg.org/sec1-v2.pdf
|
|
if (BN_cmp(private_key.get(), BN_value_one()) < 0) {
|
|
return false;
|
|
}
|
|
BignumPointer order(BN_new());
|
|
CHECK(order);
|
|
return EC_GROUP_get_order(group_, order.get(), nullptr) &&
|
|
BN_cmp(private_key.get(), order.get()) < 0;
|
|
}
|
|
|
|
|
|
bool ECDH::IsKeyPairValid() {
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
USE(&mark_pop_error_on_return);
|
|
return 1 == EC_KEY_check_key(key_.get());
|
|
}
|
|
|
|
|
|
// TODO(addaleax): If there is an `AsyncWrap`, it currently has no access to
|
|
// this object. This makes proper reporting of memory usage impossible.
|
|
struct CryptoJob : public ThreadPoolWork {
|
|
std::unique_ptr<AsyncWrap> async_wrap;
|
|
inline explicit CryptoJob(Environment* env) : ThreadPoolWork(env) {}
|
|
inline void AfterThreadPoolWork(int status) final;
|
|
virtual void AfterThreadPoolWork() = 0;
|
|
static inline void Run(std::unique_ptr<CryptoJob> job, Local<Value> wrap);
|
|
};
|
|
|
|
|
|
void CryptoJob::AfterThreadPoolWork(int status) {
|
|
CHECK(status == 0 || status == UV_ECANCELED);
|
|
std::unique_ptr<CryptoJob> job(this);
|
|
if (status == UV_ECANCELED) return;
|
|
HandleScope handle_scope(env()->isolate());
|
|
Context::Scope context_scope(env()->context());
|
|
CHECK_EQ(false, async_wrap->persistent().IsWeak());
|
|
AfterThreadPoolWork();
|
|
}
|
|
|
|
|
|
void CryptoJob::Run(std::unique_ptr<CryptoJob> job, Local<Value> wrap) {
|
|
CHECK(wrap->IsObject());
|
|
CHECK_NULL(job->async_wrap);
|
|
job->async_wrap.reset(Unwrap<AsyncWrap>(wrap.As<Object>()));
|
|
CHECK_EQ(false, job->async_wrap->persistent().IsWeak());
|
|
job->ScheduleWork();
|
|
job.release(); // Run free, little job!
|
|
}
|
|
|
|
|
|
inline void CopyBuffer(Local<Value> buf, std::vector<char>* vec) {
|
|
CHECK(buf->IsArrayBufferView());
|
|
vec->clear();
|
|
vec->resize(buf.As<ArrayBufferView>()->ByteLength());
|
|
buf.As<ArrayBufferView>()->CopyContents(vec->data(), vec->size());
|
|
}
|
|
|
|
|
|
struct RandomBytesJob : public CryptoJob {
|
|
unsigned char* data;
|
|
size_t size;
|
|
CryptoErrorVector errors;
|
|
Maybe<int> rc;
|
|
|
|
inline explicit RandomBytesJob(Environment* env)
|
|
: CryptoJob(env), rc(Nothing<int>()) {}
|
|
|
|
inline void DoThreadPoolWork() override {
|
|
CheckEntropy(); // Ensure that OpenSSL's PRNG is properly seeded.
|
|
rc = Just(RAND_bytes(data, size));
|
|
if (0 == rc.FromJust()) errors.Capture();
|
|
}
|
|
|
|
inline void AfterThreadPoolWork() override {
|
|
Local<Value> arg = ToResult();
|
|
async_wrap->MakeCallback(env()->ondone_string(), 1, &arg);
|
|
}
|
|
|
|
inline Local<Value> ToResult() const {
|
|
if (errors.empty()) return Undefined(env()->isolate());
|
|
return errors.ToException(env()).ToLocalChecked();
|
|
}
|
|
};
|
|
|
|
|
|
void RandomBytes(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(args[0]->IsArrayBufferView()); // buffer; wrap object retains ref.
|
|
CHECK(args[1]->IsUint32()); // offset
|
|
CHECK(args[2]->IsUint32()); // size
|
|
CHECK(args[3]->IsObject() || args[3]->IsUndefined()); // wrap object
|
|
const uint32_t offset = args[1].As<Uint32>()->Value();
|
|
const uint32_t size = args[2].As<Uint32>()->Value();
|
|
CHECK_GE(offset + size, offset); // Overflow check.
|
|
CHECK_LE(offset + size, Buffer::Length(args[0])); // Bounds check.
|
|
Environment* env = Environment::GetCurrent(args);
|
|
std::unique_ptr<RandomBytesJob> job(new RandomBytesJob(env));
|
|
job->data = reinterpret_cast<unsigned char*>(Buffer::Data(args[0])) + offset;
|
|
job->size = size;
|
|
if (args[3]->IsObject()) return RandomBytesJob::Run(std::move(job), args[3]);
|
|
env->PrintSyncTrace();
|
|
job->DoThreadPoolWork();
|
|
args.GetReturnValue().Set(job->ToResult());
|
|
}
|
|
|
|
|
|
struct PBKDF2Job : public CryptoJob {
|
|
unsigned char* keybuf_data;
|
|
size_t keybuf_size;
|
|
std::vector<char> pass;
|
|
std::vector<char> salt;
|
|
uint32_t iteration_count;
|
|
const EVP_MD* digest;
|
|
Maybe<bool> success;
|
|
|
|
inline explicit PBKDF2Job(Environment* env)
|
|
: CryptoJob(env), success(Nothing<bool>()) {}
|
|
|
|
inline ~PBKDF2Job() override {
|
|
Cleanse();
|
|
}
|
|
|
|
inline void DoThreadPoolWork() override {
|
|
auto salt_data = reinterpret_cast<const unsigned char*>(salt.data());
|
|
const bool ok =
|
|
PKCS5_PBKDF2_HMAC(pass.data(), pass.size(), salt_data, salt.size(),
|
|
iteration_count, digest, keybuf_size, keybuf_data);
|
|
success = Just(ok);
|
|
Cleanse();
|
|
}
|
|
|
|
inline void AfterThreadPoolWork() override {
|
|
Local<Value> arg = ToResult();
|
|
async_wrap->MakeCallback(env()->ondone_string(), 1, &arg);
|
|
}
|
|
|
|
inline Local<Value> ToResult() const {
|
|
return Boolean::New(env()->isolate(), success.FromJust());
|
|
}
|
|
|
|
inline void Cleanse() {
|
|
OPENSSL_cleanse(pass.data(), pass.size());
|
|
OPENSSL_cleanse(salt.data(), salt.size());
|
|
pass.clear();
|
|
salt.clear();
|
|
}
|
|
};
|
|
|
|
|
|
inline void PBKDF2(const FunctionCallbackInfo<Value>& args) {
|
|
auto rv = args.GetReturnValue();
|
|
Environment* env = Environment::GetCurrent(args);
|
|
CHECK(args[0]->IsArrayBufferView()); // keybuf; wrap object retains ref.
|
|
CHECK(args[1]->IsArrayBufferView()); // pass
|
|
CHECK(args[2]->IsArrayBufferView()); // salt
|
|
CHECK(args[3]->IsUint32()); // iteration_count
|
|
CHECK(args[4]->IsString()); // digest_name
|
|
CHECK(args[5]->IsObject() || args[5]->IsUndefined()); // wrap object
|
|
std::unique_ptr<PBKDF2Job> job(new PBKDF2Job(env));
|
|
job->keybuf_data = reinterpret_cast<unsigned char*>(Buffer::Data(args[0]));
|
|
job->keybuf_size = Buffer::Length(args[0]);
|
|
CopyBuffer(args[1], &job->pass);
|
|
CopyBuffer(args[2], &job->salt);
|
|
job->iteration_count = args[3].As<Uint32>()->Value();
|
|
Utf8Value digest_name(args.GetIsolate(), args[4]);
|
|
job->digest = EVP_get_digestbyname(*digest_name);
|
|
if (job->digest == nullptr) return rv.Set(-1);
|
|
if (args[5]->IsObject()) return PBKDF2Job::Run(std::move(job), args[5]);
|
|
env->PrintSyncTrace();
|
|
job->DoThreadPoolWork();
|
|
rv.Set(job->ToResult());
|
|
}
|
|
|
|
|
|
#ifndef OPENSSL_NO_SCRYPT
|
|
struct ScryptJob : public CryptoJob {
|
|
unsigned char* keybuf_data;
|
|
size_t keybuf_size;
|
|
std::vector<char> pass;
|
|
std::vector<char> salt;
|
|
uint32_t N;
|
|
uint32_t r;
|
|
uint32_t p;
|
|
uint64_t maxmem;
|
|
CryptoErrorVector errors;
|
|
|
|
inline explicit ScryptJob(Environment* env) : CryptoJob(env) {}
|
|
|
|
inline ~ScryptJob() override {
|
|
Cleanse();
|
|
}
|
|
|
|
inline bool Validate() {
|
|
if (1 == EVP_PBE_scrypt(nullptr, 0, nullptr, 0, N, r, p, maxmem,
|
|
nullptr, 0)) {
|
|
return true;
|
|
} else {
|
|
// Note: EVP_PBE_scrypt() does not always put errors on the error stack.
|
|
errors.Capture();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
inline void DoThreadPoolWork() override {
|
|
auto salt_data = reinterpret_cast<const unsigned char*>(salt.data());
|
|
if (1 != EVP_PBE_scrypt(pass.data(), pass.size(), salt_data, salt.size(),
|
|
N, r, p, maxmem, keybuf_data, keybuf_size)) {
|
|
errors.Capture();
|
|
}
|
|
}
|
|
|
|
inline void AfterThreadPoolWork() override {
|
|
Local<Value> arg = ToResult();
|
|
async_wrap->MakeCallback(env()->ondone_string(), 1, &arg);
|
|
}
|
|
|
|
inline Local<Value> ToResult() const {
|
|
if (errors.empty()) return Undefined(env()->isolate());
|
|
return errors.ToException(env()).ToLocalChecked();
|
|
}
|
|
|
|
inline void Cleanse() {
|
|
OPENSSL_cleanse(pass.data(), pass.size());
|
|
OPENSSL_cleanse(salt.data(), salt.size());
|
|
pass.clear();
|
|
salt.clear();
|
|
}
|
|
};
|
|
|
|
|
|
void Scrypt(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
CHECK(args[0]->IsArrayBufferView()); // keybuf; wrap object retains ref.
|
|
CHECK(args[1]->IsArrayBufferView()); // pass
|
|
CHECK(args[2]->IsArrayBufferView()); // salt
|
|
CHECK(args[3]->IsUint32()); // N
|
|
CHECK(args[4]->IsUint32()); // r
|
|
CHECK(args[5]->IsUint32()); // p
|
|
CHECK(args[6]->IsNumber()); // maxmem
|
|
CHECK(args[7]->IsObject() || args[7]->IsUndefined()); // wrap object
|
|
std::unique_ptr<ScryptJob> job(new ScryptJob(env));
|
|
job->keybuf_data = reinterpret_cast<unsigned char*>(Buffer::Data(args[0]));
|
|
job->keybuf_size = Buffer::Length(args[0]);
|
|
CopyBuffer(args[1], &job->pass);
|
|
CopyBuffer(args[2], &job->salt);
|
|
job->N = args[3].As<Uint32>()->Value();
|
|
job->r = args[4].As<Uint32>()->Value();
|
|
job->p = args[5].As<Uint32>()->Value();
|
|
Local<Context> ctx = env->isolate()->GetCurrentContext();
|
|
job->maxmem = static_cast<uint64_t>(args[6]->IntegerValue(ctx).ToChecked());
|
|
if (!job->Validate()) {
|
|
// EVP_PBE_scrypt() does not always put errors on the error stack
|
|
// and therefore ToResult() may or may not return an exception
|
|
// object. Return a sentinel value to inform JS land it should
|
|
// throw an ERR_CRYPTO_SCRYPT_INVALID_PARAMETER on our behalf.
|
|
auto result = job->ToResult();
|
|
if (result->IsUndefined()) result = Null(args.GetIsolate());
|
|
return args.GetReturnValue().Set(result);
|
|
}
|
|
if (args[7]->IsObject()) return ScryptJob::Run(std::move(job), args[7]);
|
|
env->PrintSyncTrace();
|
|
job->DoThreadPoolWork();
|
|
args.GetReturnValue().Set(job->ToResult());
|
|
}
|
|
#endif // OPENSSL_NO_SCRYPT
|
|
|
|
|
|
class KeyPairGenerationConfig {
|
|
public:
|
|
virtual EVPKeyCtxPointer Setup() = 0;
|
|
virtual bool Configure(const EVPKeyCtxPointer& ctx) {
|
|
return true;
|
|
}
|
|
virtual ~KeyPairGenerationConfig() = default;
|
|
};
|
|
|
|
class RSAKeyPairGenerationConfig : public KeyPairGenerationConfig {
|
|
public:
|
|
RSAKeyPairGenerationConfig(unsigned int modulus_bits, unsigned int exponent)
|
|
: modulus_bits_(modulus_bits), exponent_(exponent) {}
|
|
|
|
EVPKeyCtxPointer Setup() override {
|
|
return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr));
|
|
}
|
|
|
|
bool Configure(const EVPKeyCtxPointer& ctx) override {
|
|
if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), modulus_bits_) <= 0)
|
|
return false;
|
|
|
|
// 0x10001 is the default RSA exponent.
|
|
if (exponent_ != 0x10001) {
|
|
BignumPointer bn(BN_new());
|
|
CHECK_NOT_NULL(bn.get());
|
|
CHECK(BN_set_word(bn.get(), exponent_));
|
|
// EVP_CTX acceps ownership of bn on success.
|
|
if (EVP_PKEY_CTX_set_rsa_keygen_pubexp(ctx.get(), bn.get()) <= 0)
|
|
return false;
|
|
bn.release();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
const unsigned int modulus_bits_;
|
|
const unsigned int exponent_;
|
|
};
|
|
|
|
class RSAPSSKeyPairGenerationConfig : public RSAKeyPairGenerationConfig {
|
|
public:
|
|
RSAPSSKeyPairGenerationConfig(unsigned int modulus_bits,
|
|
unsigned int exponent,
|
|
const EVP_MD* md,
|
|
const EVP_MD* mgf1_md,
|
|
int saltlen)
|
|
: RSAKeyPairGenerationConfig(modulus_bits, exponent),
|
|
md_(md), mgf1_md_(mgf1_md), saltlen_(saltlen) {}
|
|
|
|
EVPKeyCtxPointer Setup() override {
|
|
return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA_PSS, nullptr));
|
|
}
|
|
|
|
bool Configure(const EVPKeyCtxPointer& ctx) override {
|
|
if (!RSAKeyPairGenerationConfig::Configure(ctx))
|
|
return false;
|
|
|
|
if (md_ != nullptr) {
|
|
if (EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx.get(), md_) <= 0)
|
|
return false;
|
|
}
|
|
|
|
if (mgf1_md_ != nullptr) {
|
|
if (EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(ctx.get(), mgf1_md_) <= 0)
|
|
return false;
|
|
}
|
|
|
|
if (saltlen_ >= 0) {
|
|
if (EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(ctx.get(), saltlen_) <= 0)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
const EVP_MD* md_;
|
|
const EVP_MD* mgf1_md_;
|
|
const int saltlen_;
|
|
};
|
|
|
|
class DSAKeyPairGenerationConfig : public KeyPairGenerationConfig {
|
|
public:
|
|
DSAKeyPairGenerationConfig(unsigned int modulus_bits, int divisor_bits)
|
|
: modulus_bits_(modulus_bits), divisor_bits_(divisor_bits) {}
|
|
|
|
EVPKeyCtxPointer Setup() override {
|
|
EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_DSA, nullptr));
|
|
if (!param_ctx)
|
|
return nullptr;
|
|
|
|
if (EVP_PKEY_paramgen_init(param_ctx.get()) <= 0)
|
|
return nullptr;
|
|
|
|
if (EVP_PKEY_CTX_set_dsa_paramgen_bits(param_ctx.get(), modulus_bits_) <= 0)
|
|
return nullptr;
|
|
|
|
if (divisor_bits_ != -1) {
|
|
if (EVP_PKEY_CTX_ctrl(param_ctx.get(), EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN,
|
|
EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS, divisor_bits_,
|
|
nullptr) <= 0) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
EVP_PKEY* raw_params = nullptr;
|
|
if (EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0)
|
|
return nullptr;
|
|
EVPKeyPointer params(raw_params);
|
|
param_ctx.reset();
|
|
|
|
EVPKeyCtxPointer key_ctx(EVP_PKEY_CTX_new(params.get(), nullptr));
|
|
return key_ctx;
|
|
}
|
|
|
|
private:
|
|
const unsigned int modulus_bits_;
|
|
const int divisor_bits_;
|
|
};
|
|
|
|
class ECKeyPairGenerationConfig : public KeyPairGenerationConfig {
|
|
public:
|
|
ECKeyPairGenerationConfig(int curve_nid, int param_encoding)
|
|
: curve_nid_(curve_nid), param_encoding_(param_encoding) {}
|
|
|
|
EVPKeyCtxPointer Setup() override {
|
|
EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr));
|
|
if (!param_ctx)
|
|
return nullptr;
|
|
|
|
if (EVP_PKEY_paramgen_init(param_ctx.get()) <= 0)
|
|
return nullptr;
|
|
|
|
if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(param_ctx.get(),
|
|
curve_nid_) <= 0)
|
|
return nullptr;
|
|
|
|
if (EVP_PKEY_CTX_set_ec_param_enc(param_ctx.get(), param_encoding_) <= 0)
|
|
return nullptr;
|
|
|
|
EVP_PKEY* raw_params = nullptr;
|
|
if (EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0)
|
|
return nullptr;
|
|
EVPKeyPointer params(raw_params);
|
|
param_ctx.reset();
|
|
|
|
EVPKeyCtxPointer key_ctx(EVP_PKEY_CTX_new(params.get(), nullptr));
|
|
return key_ctx;
|
|
}
|
|
|
|
private:
|
|
const int curve_nid_;
|
|
const int param_encoding_;
|
|
};
|
|
|
|
class NidKeyPairGenerationConfig : public KeyPairGenerationConfig {
|
|
public:
|
|
explicit NidKeyPairGenerationConfig(int id) : id_(id) {}
|
|
|
|
EVPKeyCtxPointer Setup() override {
|
|
return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(id_, nullptr));
|
|
}
|
|
|
|
private:
|
|
const int id_;
|
|
};
|
|
|
|
// TODO(tniessen): Use std::variant instead.
|
|
// Diffie-Hellman can either generate keys using a fixed prime, or by first
|
|
// generating a random prime of a given size (in bits). Only one of both options
|
|
// may be specified.
|
|
struct PrimeInfo {
|
|
BignumPointer fixed_value_;
|
|
unsigned int prime_size_;
|
|
};
|
|
|
|
class DHKeyPairGenerationConfig : public KeyPairGenerationConfig {
|
|
public:
|
|
explicit DHKeyPairGenerationConfig(PrimeInfo&& prime_info,
|
|
unsigned int generator)
|
|
: prime_info_(std::move(prime_info)),
|
|
generator_(generator) {}
|
|
|
|
EVPKeyCtxPointer Setup() override {
|
|
EVPKeyPointer params;
|
|
if (prime_info_.fixed_value_) {
|
|
DHPointer dh(DH_new());
|
|
if (!dh)
|
|
return nullptr;
|
|
|
|
BIGNUM* prime = prime_info_.fixed_value_.get();
|
|
BignumPointer bn_g(BN_new());
|
|
if (!BN_set_word(bn_g.get(), generator_) ||
|
|
!DH_set0_pqg(dh.get(), prime, nullptr, bn_g.get()))
|
|
return nullptr;
|
|
|
|
prime_info_.fixed_value_.release();
|
|
bn_g.release();
|
|
|
|
params = EVPKeyPointer(EVP_PKEY_new());
|
|
CHECK(params);
|
|
EVP_PKEY_assign_DH(params.get(), dh.release());
|
|
} else {
|
|
EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_DH, nullptr));
|
|
if (!param_ctx)
|
|
return nullptr;
|
|
|
|
if (EVP_PKEY_paramgen_init(param_ctx.get()) <= 0)
|
|
return nullptr;
|
|
|
|
if (EVP_PKEY_CTX_set_dh_paramgen_prime_len(param_ctx.get(),
|
|
prime_info_.prime_size_) <= 0)
|
|
return nullptr;
|
|
|
|
if (EVP_PKEY_CTX_set_dh_paramgen_generator(param_ctx.get(),
|
|
generator_) <= 0)
|
|
return nullptr;
|
|
|
|
EVP_PKEY* raw_params = nullptr;
|
|
if (EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0)
|
|
return nullptr;
|
|
params = EVPKeyPointer(raw_params);
|
|
}
|
|
|
|
return EVPKeyCtxPointer(EVP_PKEY_CTX_new(params.get(), nullptr));
|
|
}
|
|
|
|
private:
|
|
PrimeInfo prime_info_;
|
|
unsigned int generator_;
|
|
};
|
|
|
|
class GenerateKeyPairJob : public CryptoJob {
|
|
public:
|
|
GenerateKeyPairJob(Environment* env,
|
|
std::unique_ptr<KeyPairGenerationConfig> config,
|
|
PublicKeyEncodingConfig public_key_encoding,
|
|
PrivateKeyEncodingConfig&& private_key_encoding)
|
|
: CryptoJob(env),
|
|
config_(std::move(config)),
|
|
public_key_encoding_(public_key_encoding),
|
|
private_key_encoding_(std::forward<PrivateKeyEncodingConfig>(
|
|
private_key_encoding)),
|
|
pkey_(nullptr) {}
|
|
|
|
inline void DoThreadPoolWork() override {
|
|
if (!GenerateKey())
|
|
errors_.Capture();
|
|
}
|
|
|
|
inline bool GenerateKey() {
|
|
// Make sure that the CSPRNG is properly seeded so the results are secure.
|
|
CheckEntropy();
|
|
|
|
// Create the key generation context.
|
|
EVPKeyCtxPointer ctx = config_->Setup();
|
|
if (!ctx)
|
|
return false;
|
|
|
|
// Initialize key generation.
|
|
if (EVP_PKEY_keygen_init(ctx.get()) <= 0)
|
|
return false;
|
|
|
|
// Configure key generation.
|
|
if (!config_->Configure(ctx))
|
|
return false;
|
|
|
|
// Generate the key.
|
|
EVP_PKEY* pkey = nullptr;
|
|
if (EVP_PKEY_keygen(ctx.get(), &pkey) != 1)
|
|
return false;
|
|
pkey_ = ManagedEVPPKey(EVPKeyPointer(pkey));
|
|
return true;
|
|
}
|
|
|
|
inline void AfterThreadPoolWork() override {
|
|
Local<Value> args[3];
|
|
ToResult(&args[0], &args[1], &args[2]);
|
|
async_wrap->MakeCallback(env()->ondone_string(), 3, args);
|
|
}
|
|
|
|
inline void ToResult(Local<Value>* err,
|
|
Local<Value>* pubkey,
|
|
Local<Value>* privkey) {
|
|
if (pkey_ && EncodeKeys(pubkey, privkey)) {
|
|
CHECK(errors_.empty());
|
|
*err = Undefined(env()->isolate());
|
|
} else {
|
|
if (errors_.empty())
|
|
errors_.Capture();
|
|
CHECK(!errors_.empty());
|
|
*err = errors_.ToException(env()).ToLocalChecked();
|
|
*pubkey = Undefined(env()->isolate());
|
|
*privkey = Undefined(env()->isolate());
|
|
}
|
|
}
|
|
|
|
inline bool EncodeKeys(Local<Value>* pubkey, Local<Value>* privkey) {
|
|
// Encode the public key.
|
|
if (public_key_encoding_.output_key_object_) {
|
|
// Note that this has the downside of containing sensitive data of the
|
|
// private key.
|
|
std::shared_ptr<KeyObjectData> data =
|
|
KeyObjectData::CreateAsymmetric(kKeyTypePublic, pkey_);
|
|
if (!KeyObjectHandle::Create(env(), data).ToLocal(pubkey))
|
|
return false;
|
|
} else {
|
|
if (!WritePublicKey(env(), pkey_.get(), public_key_encoding_)
|
|
.ToLocal(pubkey))
|
|
return false;
|
|
}
|
|
|
|
// Now do the same for the private key.
|
|
if (private_key_encoding_.output_key_object_) {
|
|
std::shared_ptr<KeyObjectData> data =
|
|
KeyObjectData::CreateAsymmetric(kKeyTypePrivate, pkey_);
|
|
if (!KeyObjectHandle::Create(env(), data).ToLocal(privkey))
|
|
return false;
|
|
} else {
|
|
if (!WritePrivateKey(env(), pkey_.get(), private_key_encoding_)
|
|
.ToLocal(privkey))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
CryptoErrorVector errors_;
|
|
std::unique_ptr<KeyPairGenerationConfig> config_;
|
|
PublicKeyEncodingConfig public_key_encoding_;
|
|
PrivateKeyEncodingConfig private_key_encoding_;
|
|
ManagedEVPPKey pkey_;
|
|
};
|
|
|
|
void GenerateKeyPair(const FunctionCallbackInfo<Value>& args,
|
|
unsigned int offset,
|
|
std::unique_ptr<KeyPairGenerationConfig> config) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
PublicKeyEncodingConfig public_key_encoding =
|
|
GetPublicKeyEncodingFromJs(args, &offset, kKeyContextGenerate);
|
|
NonCopyableMaybe<PrivateKeyEncodingConfig> private_key_encoding =
|
|
GetPrivateKeyEncodingFromJs(args, &offset, kKeyContextGenerate);
|
|
|
|
if (private_key_encoding.IsEmpty())
|
|
return;
|
|
|
|
std::unique_ptr<GenerateKeyPairJob> job(
|
|
new GenerateKeyPairJob(env, std::move(config), public_key_encoding,
|
|
private_key_encoding.Release()));
|
|
if (args[offset]->IsObject())
|
|
return GenerateKeyPairJob::Run(std::move(job), args[offset]);
|
|
env->PrintSyncTrace();
|
|
job->DoThreadPoolWork();
|
|
Local<Value> err, pubkey, privkey;
|
|
job->ToResult(&err, &pubkey, &privkey);
|
|
|
|
Local<Value> ret[] = { err, pubkey, privkey };
|
|
args.GetReturnValue().Set(Array::New(env->isolate(), ret, arraysize(ret)));
|
|
}
|
|
|
|
void GenerateKeyPairRSA(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(args[0]->IsUint32());
|
|
const uint32_t modulus_bits = args[0].As<Uint32>()->Value();
|
|
CHECK(args[1]->IsUint32());
|
|
const uint32_t exponent = args[1].As<Uint32>()->Value();
|
|
std::unique_ptr<KeyPairGenerationConfig> config(
|
|
new RSAKeyPairGenerationConfig(modulus_bits, exponent));
|
|
GenerateKeyPair(args, 2, std::move(config));
|
|
}
|
|
|
|
void GenerateKeyPairRSAPSS(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
CHECK(args[0]->IsUint32());
|
|
const uint32_t modulus_bits = args[0].As<Uint32>()->Value();
|
|
CHECK(args[1]->IsUint32());
|
|
const uint32_t exponent = args[1].As<Uint32>()->Value();
|
|
|
|
const EVP_MD* md = nullptr;
|
|
if (!args[2]->IsUndefined()) {
|
|
CHECK(args[2]->IsString());
|
|
String::Utf8Value md_name(env->isolate(), args[2].As<String>());
|
|
md = EVP_get_digestbyname(*md_name);
|
|
if (md == nullptr)
|
|
return env->ThrowTypeError("Digest method not supported");
|
|
}
|
|
|
|
const EVP_MD* mgf1_md = nullptr;
|
|
if (!args[3]->IsUndefined()) {
|
|
CHECK(args[3]->IsString());
|
|
String::Utf8Value mgf1_md_name(env->isolate(), args[3].As<String>());
|
|
mgf1_md = EVP_get_digestbyname(*mgf1_md_name);
|
|
if (mgf1_md == nullptr)
|
|
return env->ThrowTypeError("Digest method not supported");
|
|
}
|
|
|
|
int saltlen = -1;
|
|
if (!args[4]->IsUndefined()) {
|
|
CHECK(args[4]->IsInt32());
|
|
saltlen = args[4].As<Int32>()->Value();
|
|
}
|
|
|
|
std::unique_ptr<KeyPairGenerationConfig> config(
|
|
new RSAPSSKeyPairGenerationConfig(modulus_bits, exponent,
|
|
md, mgf1_md, saltlen));
|
|
GenerateKeyPair(args, 5, std::move(config));
|
|
}
|
|
|
|
void GenerateKeyPairDSA(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(args[0]->IsUint32());
|
|
const uint32_t modulus_bits = args[0].As<Uint32>()->Value();
|
|
CHECK(args[1]->IsInt32());
|
|
const int32_t divisor_bits = args[1].As<Int32>()->Value();
|
|
std::unique_ptr<KeyPairGenerationConfig> config(
|
|
new DSAKeyPairGenerationConfig(modulus_bits, divisor_bits));
|
|
GenerateKeyPair(args, 2, std::move(config));
|
|
}
|
|
|
|
void GenerateKeyPairEC(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(args[0]->IsString());
|
|
String::Utf8Value curve_name(args.GetIsolate(), args[0].As<String>());
|
|
int curve_nid = EC_curve_nist2nid(*curve_name);
|
|
if (curve_nid == NID_undef)
|
|
curve_nid = OBJ_sn2nid(*curve_name);
|
|
// TODO(tniessen): Should we also support OBJ_ln2nid? (Other APIs don't.)
|
|
if (curve_nid == NID_undef) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
return env->ThrowTypeError("Invalid ECDH curve name");
|
|
}
|
|
CHECK(args[1]->IsUint32());
|
|
const uint32_t param_encoding = args[1].As<Int32>()->Value();
|
|
CHECK(param_encoding == OPENSSL_EC_NAMED_CURVE ||
|
|
param_encoding == OPENSSL_EC_EXPLICIT_CURVE);
|
|
std::unique_ptr<KeyPairGenerationConfig> config(
|
|
new ECKeyPairGenerationConfig(curve_nid, param_encoding));
|
|
GenerateKeyPair(args, 2, std::move(config));
|
|
}
|
|
|
|
void GenerateKeyPairNid(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(args[0]->IsInt32());
|
|
const int id = args[0].As<Int32>()->Value();
|
|
std::unique_ptr<KeyPairGenerationConfig> config(
|
|
new NidKeyPairGenerationConfig(id));
|
|
GenerateKeyPair(args, 1, std::move(config));
|
|
}
|
|
|
|
void GenerateKeyPairDH(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
PrimeInfo prime_info = {};
|
|
unsigned int generator;
|
|
if (args[0]->IsString()) {
|
|
String::Utf8Value group_name(args.GetIsolate(), args[0].As<String>());
|
|
const modp_group* group = FindDiffieHellmanGroup(*group_name);
|
|
if (group == nullptr)
|
|
return THROW_ERR_CRYPTO_UNKNOWN_DH_GROUP(env);
|
|
|
|
prime_info.fixed_value_ = BignumPointer(
|
|
BN_bin2bn(reinterpret_cast<const unsigned char*>(group->prime),
|
|
group->prime_size, nullptr));
|
|
generator = group->gen;
|
|
} else {
|
|
if (args[0]->IsInt32()) {
|
|
prime_info.prime_size_ = args[0].As<Int32>()->Value();
|
|
} else {
|
|
ArrayBufferViewContents<unsigned char> input(args[0]);
|
|
prime_info.fixed_value_ = BignumPointer(
|
|
BN_bin2bn(input.data(), input.length(), nullptr));
|
|
}
|
|
|
|
CHECK(args[1]->IsInt32());
|
|
generator = args[1].As<Int32>()->Value();
|
|
}
|
|
|
|
std::unique_ptr<KeyPairGenerationConfig> config(
|
|
new DHKeyPairGenerationConfig(std::move(prime_info), generator));
|
|
GenerateKeyPair(args, 2, std::move(config));
|
|
}
|
|
|
|
|
|
void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
SSLCtxPointer ctx(SSL_CTX_new(TLS_method()));
|
|
CHECK(ctx);
|
|
|
|
SSLPointer ssl(SSL_new(ctx.get()));
|
|
CHECK(ssl);
|
|
|
|
STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(ssl.get());
|
|
// TLSv1.3 ciphers aren't listed by EVP. There are only 5, we could just
|
|
// document them, but since there are only 5, easier to just add them manually
|
|
// and not have to explain their absence in the API docs. They are lower-cased
|
|
// because the docs say they will be.
|
|
static const char* TLS13_CIPHERS[] = {
|
|
"tls_aes_256_gcm_sha384",
|
|
"tls_chacha20_poly1305_sha256",
|
|
"tls_aes_128_gcm_sha256",
|
|
"tls_aes_128_ccm_8_sha256",
|
|
"tls_aes_128_ccm_sha256"
|
|
};
|
|
|
|
const int n = sk_SSL_CIPHER_num(ciphers);
|
|
std::vector<Local<Value>> arr(n + arraysize(TLS13_CIPHERS));
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i);
|
|
arr[i] = OneByteString(env->isolate(), SSL_CIPHER_get_name(cipher));
|
|
}
|
|
|
|
for (unsigned i = 0; i < arraysize(TLS13_CIPHERS); ++i) {
|
|
const char* name = TLS13_CIPHERS[i];
|
|
arr[n + i] = OneByteString(env->isolate(), name);
|
|
}
|
|
|
|
args.GetReturnValue().Set(Array::New(env->isolate(), arr.data(), arr.size()));
|
|
}
|
|
|
|
|
|
class CipherPushContext {
|
|
public:
|
|
explicit CipherPushContext(Environment* env)
|
|
: arr(Array::New(env->isolate())),
|
|
env_(env) {
|
|
}
|
|
|
|
inline Environment* env() const { return env_; }
|
|
|
|
Local<Array> arr;
|
|
|
|
private:
|
|
Environment* env_;
|
|
};
|
|
|
|
|
|
template <class TypeName>
|
|
static void array_push_back(const TypeName* md,
|
|
const char* from,
|
|
const char* to,
|
|
void* arg) {
|
|
CipherPushContext* ctx = static_cast<CipherPushContext*>(arg);
|
|
ctx->arr->Set(ctx->env()->context(),
|
|
ctx->arr->Length(),
|
|
OneByteString(ctx->env()->isolate(), from)).Check();
|
|
}
|
|
|
|
|
|
void GetCiphers(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
CipherPushContext ctx(env);
|
|
EVP_CIPHER_do_all_sorted(array_push_back<EVP_CIPHER>, &ctx);
|
|
args.GetReturnValue().Set(ctx.arr);
|
|
}
|
|
|
|
|
|
void GetHashes(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
CipherPushContext ctx(env);
|
|
EVP_MD_do_all_sorted(array_push_back<EVP_MD>, &ctx);
|
|
args.GetReturnValue().Set(ctx.arr);
|
|
}
|
|
|
|
|
|
void GetCurves(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
const size_t num_curves = EC_get_builtin_curves(nullptr, 0);
|
|
|
|
if (num_curves) {
|
|
std::vector<EC_builtin_curve> curves(num_curves);
|
|
|
|
if (EC_get_builtin_curves(curves.data(), num_curves)) {
|
|
std::vector<Local<Value>> arr(num_curves);
|
|
|
|
for (size_t i = 0; i < num_curves; i++)
|
|
arr[i] = OneByteString(env->isolate(), OBJ_nid2sn(curves[i].nid));
|
|
|
|
args.GetReturnValue().Set(
|
|
Array::New(env->isolate(), arr.data(), arr.size()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
args.GetReturnValue().Set(Array::New(env->isolate()));
|
|
}
|
|
|
|
|
|
bool VerifySpkac(const char* data, unsigned int len) {
|
|
NetscapeSPKIPointer spki(NETSCAPE_SPKI_b64_decode(data, len));
|
|
if (!spki)
|
|
return false;
|
|
|
|
EVPKeyPointer pkey(X509_PUBKEY_get(spki->spkac->pubkey));
|
|
if (!pkey)
|
|
return false;
|
|
|
|
return NETSCAPE_SPKI_verify(spki.get(), pkey.get()) > 0;
|
|
}
|
|
|
|
|
|
void VerifySpkac(const FunctionCallbackInfo<Value>& args) {
|
|
bool verify_result = false;
|
|
|
|
ArrayBufferViewContents<char> input(args[0]);
|
|
if (input.length() == 0)
|
|
return args.GetReturnValue().SetEmptyString();
|
|
|
|
CHECK_NOT_NULL(input.data());
|
|
|
|
verify_result = VerifySpkac(input.data(), input.length());
|
|
|
|
args.GetReturnValue().Set(verify_result);
|
|
}
|
|
|
|
AllocatedBuffer ExportPublicKey(Environment* env,
|
|
const char* data,
|
|
int len,
|
|
size_t* size) {
|
|
BIOPointer bio(BIO_new(BIO_s_mem()));
|
|
if (!bio) return AllocatedBuffer();
|
|
|
|
NetscapeSPKIPointer spki(NETSCAPE_SPKI_b64_decode(data, len));
|
|
if (!spki) return AllocatedBuffer();
|
|
|
|
EVPKeyPointer pkey(NETSCAPE_SPKI_get_pubkey(spki.get()));
|
|
if (!pkey) return AllocatedBuffer();
|
|
|
|
if (PEM_write_bio_PUBKEY(bio.get(), pkey.get()) <= 0)
|
|
return AllocatedBuffer();
|
|
|
|
BUF_MEM* ptr;
|
|
BIO_get_mem_ptr(bio.get(), &ptr);
|
|
|
|
*size = ptr->length;
|
|
AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, *size);
|
|
memcpy(buf.data(), ptr->data, *size);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
void ExportPublicKey(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
ArrayBufferViewContents<char> input(args[0]);
|
|
if (input.length() == 0)
|
|
return args.GetReturnValue().SetEmptyString();
|
|
|
|
CHECK_NOT_NULL(input.data());
|
|
|
|
size_t pkey_size;
|
|
AllocatedBuffer pkey =
|
|
ExportPublicKey(env, input.data(), input.length(), &pkey_size);
|
|
if (pkey.data() == nullptr)
|
|
return args.GetReturnValue().SetEmptyString();
|
|
|
|
args.GetReturnValue().Set(pkey.ToBuffer().ToLocalChecked());
|
|
}
|
|
|
|
|
|
OpenSSLBuffer ExportChallenge(const char* data, int len) {
|
|
NetscapeSPKIPointer sp(NETSCAPE_SPKI_b64_decode(data, len));
|
|
if (!sp)
|
|
return nullptr;
|
|
|
|
unsigned char* buf = nullptr;
|
|
ASN1_STRING_to_UTF8(&buf, sp->spkac->challenge);
|
|
|
|
return OpenSSLBuffer(reinterpret_cast<char*>(buf));
|
|
}
|
|
|
|
|
|
void ExportChallenge(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
ArrayBufferViewContents<char> input(args[0]);
|
|
if (input.length() == 0)
|
|
return args.GetReturnValue().SetEmptyString();
|
|
|
|
OpenSSLBuffer cert = ExportChallenge(input.data(), input.length());
|
|
if (!cert)
|
|
return args.GetReturnValue().SetEmptyString();
|
|
|
|
Local<Value> outString =
|
|
Encode(env->isolate(), cert.get(), strlen(cert.get()), BUFFER);
|
|
|
|
args.GetReturnValue().Set(outString);
|
|
}
|
|
|
|
|
|
// Convert the input public key to compressed, uncompressed, or hybrid formats.
|
|
void ConvertKey(const FunctionCallbackInfo<Value>& args) {
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
CHECK_EQ(args.Length(), 3);
|
|
CHECK(args[0]->IsArrayBufferView());
|
|
|
|
size_t len = args[0].As<ArrayBufferView>()->ByteLength();
|
|
if (len == 0)
|
|
return args.GetReturnValue().SetEmptyString();
|
|
|
|
node::Utf8Value curve(env->isolate(), args[1]);
|
|
|
|
int nid = OBJ_sn2nid(*curve);
|
|
if (nid == NID_undef)
|
|
return env->ThrowTypeError("Invalid ECDH curve name");
|
|
|
|
ECGroupPointer group(
|
|
EC_GROUP_new_by_curve_name(nid));
|
|
if (group == nullptr)
|
|
return env->ThrowError("Failed to get EC_GROUP");
|
|
|
|
ECPointPointer pub(
|
|
ECDH::BufferToPoint(env,
|
|
group.get(),
|
|
args[0]));
|
|
|
|
if (pub == nullptr)
|
|
return env->ThrowError("Failed to convert Buffer to EC_POINT");
|
|
|
|
CHECK(args[2]->IsUint32());
|
|
uint32_t val = args[2].As<Uint32>()->Value();
|
|
point_conversion_form_t form = static_cast<point_conversion_form_t>(val);
|
|
|
|
const char* error;
|
|
Local<Object> buf;
|
|
if (!ECPointToBuffer(env, group.get(), pub.get(), form, &error).ToLocal(&buf))
|
|
return env->ThrowError(error);
|
|
args.GetReturnValue().Set(buf);
|
|
}
|
|
|
|
AllocatedBuffer StatelessDiffieHellman(Environment* env, ManagedEVPPKey our_key,
|
|
ManagedEVPPKey their_key) {
|
|
size_t out_size;
|
|
|
|
EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(our_key.get(), nullptr));
|
|
if (!ctx ||
|
|
EVP_PKEY_derive_init(ctx.get()) <= 0 ||
|
|
EVP_PKEY_derive_set_peer(ctx.get(), their_key.get()) <= 0 ||
|
|
EVP_PKEY_derive(ctx.get(), nullptr, &out_size) <= 0)
|
|
return AllocatedBuffer();
|
|
|
|
AllocatedBuffer result = AllocatedBuffer::AllocateManaged(env, out_size);
|
|
CHECK_NOT_NULL(result.data());
|
|
|
|
unsigned char* data = reinterpret_cast<unsigned char*>(result.data());
|
|
if (EVP_PKEY_derive(ctx.get(), data, &out_size) <= 0)
|
|
return AllocatedBuffer();
|
|
|
|
ZeroPadDiffieHellmanSecret(out_size, &result);
|
|
return result;
|
|
}
|
|
|
|
void StatelessDiffieHellman(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
CHECK(args[0]->IsObject() && args[1]->IsObject());
|
|
KeyObjectHandle* our_key_object;
|
|
ASSIGN_OR_RETURN_UNWRAP(&our_key_object, args[0].As<Object>());
|
|
CHECK_EQ(our_key_object->Data()->GetKeyType(), kKeyTypePrivate);
|
|
KeyObjectHandle* their_key_object;
|
|
ASSIGN_OR_RETURN_UNWRAP(&their_key_object, args[1].As<Object>());
|
|
CHECK_NE(their_key_object->Data()->GetKeyType(), kKeyTypeSecret);
|
|
|
|
ManagedEVPPKey our_key = our_key_object->Data()->GetAsymmetricKey();
|
|
ManagedEVPPKey their_key = their_key_object->Data()->GetAsymmetricKey();
|
|
|
|
AllocatedBuffer out = StatelessDiffieHellman(env, our_key, their_key);
|
|
if (out.size() == 0)
|
|
return ThrowCryptoError(env, ERR_get_error(), "diffieHellman failed");
|
|
|
|
args.GetReturnValue().Set(out.ToBuffer().ToLocalChecked());
|
|
}
|
|
|
|
|
|
void TimingSafeEqual(const FunctionCallbackInfo<Value>& args) {
|
|
// Moving the type checking into JS leads to test failures, most likely due
|
|
// to V8 inlining certain parts of the wrapper. Therefore, keep them in C++.
|
|
// Refs: https://github.com/nodejs/node/issues/34073.
|
|
Environment* env = Environment::GetCurrent(args);
|
|
if (!args[0]->IsArrayBufferView()) {
|
|
THROW_ERR_INVALID_ARG_TYPE(
|
|
env, "The \"buf1\" argument must be an instance of "
|
|
"Buffer, TypedArray, or DataView.");
|
|
return;
|
|
}
|
|
if (!args[1]->IsArrayBufferView()) {
|
|
THROW_ERR_INVALID_ARG_TYPE(
|
|
env, "The \"buf2\" argument must be an instance of "
|
|
"Buffer, TypedArray, or DataView.");
|
|
return;
|
|
}
|
|
|
|
ArrayBufferViewContents<char> buf1(args[0].As<ArrayBufferView>());
|
|
ArrayBufferViewContents<char> buf2(args[1].As<ArrayBufferView>());
|
|
|
|
if (buf1.length() != buf2.length()) {
|
|
THROW_ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH(env);
|
|
return;
|
|
}
|
|
|
|
return args.GetReturnValue().Set(
|
|
CRYPTO_memcmp(buf1.data(), buf2.data(), buf1.length()) == 0);
|
|
}
|
|
|
|
void InitCryptoOnce() {
|
|
#ifndef OPENSSL_IS_BORINGSSL
|
|
OPENSSL_INIT_SETTINGS* settings = OPENSSL_INIT_new();
|
|
|
|
// --openssl-config=...
|
|
if (!per_process::cli_options->openssl_config.empty()) {
|
|
const char* conf = per_process::cli_options->openssl_config.c_str();
|
|
OPENSSL_INIT_set_config_filename(settings, conf);
|
|
}
|
|
|
|
OPENSSL_init_ssl(0, settings);
|
|
OPENSSL_INIT_free(settings);
|
|
settings = nullptr;
|
|
#endif
|
|
|
|
#ifdef NODE_FIPS_MODE
|
|
/* Override FIPS settings in cnf file, if needed. */
|
|
unsigned long err = 0; // NOLINT(runtime/int)
|
|
if (per_process::cli_options->enable_fips_crypto ||
|
|
per_process::cli_options->force_fips_crypto) {
|
|
if (0 == FIPS_mode() && !FIPS_mode_set(1)) {
|
|
err = ERR_get_error();
|
|
}
|
|
}
|
|
if (0 != err) {
|
|
fprintf(stderr,
|
|
"openssl fips failed: %s\n",
|
|
ERR_error_string(err, nullptr));
|
|
UNREACHABLE();
|
|
}
|
|
#endif // NODE_FIPS_MODE
|
|
|
|
|
|
// Turn off compression. Saves memory and protects against CRIME attacks.
|
|
// No-op with OPENSSL_NO_COMP builds of OpenSSL.
|
|
sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
|
|
|
|
#ifndef OPENSSL_NO_ENGINE
|
|
ERR_load_ENGINE_strings();
|
|
ENGINE_load_builtin_engines();
|
|
#endif // !OPENSSL_NO_ENGINE
|
|
|
|
NodeBIO::GetMethod();
|
|
}
|
|
|
|
|
|
#ifndef OPENSSL_NO_ENGINE
|
|
void SetEngine(const FunctionCallbackInfo<Value>& args) {
|
|
Environment* env = Environment::GetCurrent(args);
|
|
CHECK(args.Length() >= 2 && args[0]->IsString());
|
|
uint32_t flags;
|
|
if (!args[1]->Uint32Value(env->context()).To(&flags)) return;
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
// Load engine.
|
|
const node::Utf8Value engine_id(env->isolate(), args[0]);
|
|
char errmsg[1024];
|
|
ENGINE* engine = LoadEngineById(*engine_id, &errmsg);
|
|
|
|
if (engine == nullptr) {
|
|
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
|
if (err == 0)
|
|
return args.GetReturnValue().Set(false);
|
|
return ThrowCryptoError(env, err);
|
|
}
|
|
|
|
int r = ENGINE_set_default(engine, flags);
|
|
ENGINE_free(engine);
|
|
if (r == 0)
|
|
return ThrowCryptoError(env, ERR_get_error());
|
|
|
|
args.GetReturnValue().Set(true);
|
|
}
|
|
#endif // !OPENSSL_NO_ENGINE
|
|
|
|
#ifdef NODE_FIPS_MODE
|
|
void GetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
|
|
args.GetReturnValue().Set(FIPS_mode() ? 1 : 0);
|
|
}
|
|
|
|
void SetFipsCrypto(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK(!per_process::cli_options->force_fips_crypto);
|
|
Environment* env = Environment::GetCurrent(args);
|
|
const bool enabled = FIPS_mode();
|
|
bool enable = args[0]->BooleanValue(env->isolate());
|
|
|
|
if (enable == enabled)
|
|
return; // No action needed.
|
|
if (!FIPS_mode_set(enable)) {
|
|
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
|
return ThrowCryptoError(env, err);
|
|
}
|
|
}
|
|
#endif /* NODE_FIPS_MODE */
|
|
|
|
|
|
void Initialize(Local<Object> target,
|
|
Local<Value> unused,
|
|
Local<Context> context,
|
|
void* priv) {
|
|
static uv_once_t init_once = UV_ONCE_INIT;
|
|
uv_once(&init_once, InitCryptoOnce);
|
|
|
|
Environment* env = Environment::GetCurrent(context);
|
|
SecureContext::Initialize(env, target);
|
|
env->set_crypto_key_object_handle_constructor(
|
|
KeyObjectHandle::Initialize(env, target));
|
|
env->SetMethod(target, "createNativeKeyObjectClass",
|
|
CreateNativeKeyObjectClass);
|
|
CipherBase::Initialize(env, target);
|
|
DiffieHellman::Initialize(env, target);
|
|
ECDH::Initialize(env, target);
|
|
Hmac::Initialize(env, target);
|
|
Hash::Initialize(env, target);
|
|
Sign::Initialize(env, target);
|
|
Verify::Initialize(env, target);
|
|
|
|
env->SetMethodNoSideEffect(target, "certVerifySpkac", VerifySpkac);
|
|
env->SetMethodNoSideEffect(target, "certExportPublicKey", ExportPublicKey);
|
|
env->SetMethodNoSideEffect(target, "certExportChallenge", ExportChallenge);
|
|
env->SetMethodNoSideEffect(target, "getRootCertificates",
|
|
GetRootCertificates);
|
|
// Exposed for testing purposes only.
|
|
env->SetMethodNoSideEffect(target, "isExtraRootCertsFileLoaded",
|
|
IsExtraRootCertsFileLoaded);
|
|
|
|
env->SetMethodNoSideEffect(target, "ECDHConvertKey", ConvertKey);
|
|
#ifndef OPENSSL_NO_ENGINE
|
|
env->SetMethod(target, "setEngine", SetEngine);
|
|
#endif // !OPENSSL_NO_ENGINE
|
|
|
|
#ifdef NODE_FIPS_MODE
|
|
env->SetMethodNoSideEffect(target, "getFipsCrypto", GetFipsCrypto);
|
|
env->SetMethod(target, "setFipsCrypto", SetFipsCrypto);
|
|
#endif
|
|
|
|
env->SetMethod(target, "pbkdf2", PBKDF2);
|
|
env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
|
|
env->SetMethod(target, "generateKeyPairRSAPSS", GenerateKeyPairRSAPSS);
|
|
env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
|
|
env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
|
|
env->SetMethod(target, "generateKeyPairNid", GenerateKeyPairNid);
|
|
env->SetMethod(target, "generateKeyPairDH", GenerateKeyPairDH);
|
|
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519);
|
|
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448);
|
|
NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519);
|
|
NODE_DEFINE_CONSTANT(target, EVP_PKEY_X448);
|
|
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
|
|
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
|
|
NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
|
|
NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
|
|
NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
|
|
NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
|
|
NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
|
|
NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
|
|
NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
|
|
NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
|
|
NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
|
|
NODE_DEFINE_CONSTANT(target, kSigEncDER);
|
|
NODE_DEFINE_CONSTANT(target, kSigEncP1363);
|
|
env->SetMethodNoSideEffect(target, "statelessDH", StatelessDiffieHellman);
|
|
env->SetMethod(target, "randomBytes", RandomBytes);
|
|
env->SetMethod(target, "signOneShot", SignOneShot);
|
|
env->SetMethod(target, "verifyOneShot", VerifyOneShot);
|
|
env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
|
|
env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
|
|
env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
|
|
env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
|
|
env->SetMethodNoSideEffect(target, "getCurves", GetCurves);
|
|
env->SetMethod(target, "publicEncrypt",
|
|
PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
|
|
EVP_PKEY_encrypt_init,
|
|
EVP_PKEY_encrypt>);
|
|
env->SetMethod(target, "privateDecrypt",
|
|
PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
|
|
EVP_PKEY_decrypt_init,
|
|
EVP_PKEY_decrypt>);
|
|
env->SetMethod(target, "privateEncrypt",
|
|
PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
|
|
EVP_PKEY_sign_init,
|
|
EVP_PKEY_sign>);
|
|
env->SetMethod(target, "publicDecrypt",
|
|
PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
|
|
EVP_PKEY_verify_recover_init,
|
|
EVP_PKEY_verify_recover>);
|
|
#ifndef OPENSSL_NO_SCRYPT
|
|
env->SetMethod(target, "scrypt", Scrypt);
|
|
#endif // OPENSSL_NO_SCRYPT
|
|
}
|
|
|
|
} // namespace crypto
|
|
} // namespace node
|
|
|
|
NODE_MODULE_CONTEXT_AWARE_INTERNAL(crypto, node::crypto::Initialize)
|