|
|
|
#include "node.h"
|
|
|
|
#include "node_buffer.h"
|
|
|
|
#include "node_crypto.h"
|
|
|
|
#include "node_crypto_bio.h"
|
|
|
|
#include "node_crypto_groups.h"
|
stream_base: introduce StreamBase
StreamBase is an improved way to write C++ streams. The class itself is
for separting `StreamWrap` (with the methods like `.writeAsciiString`,
`.writeBuffer`, `.writev`, etc) from the `HandleWrap` class, making
possible to write abstract C++ streams that are not bound to any uv
socket.
The following methods are important part of the abstraction (which
mimics libuv's stream API):
* Events:
* `OnAlloc(size_t size, uv_buf_t*)`
* `OnRead(ssize_t nread, const uv_buf_t*, uv_handle_type pending)`
* `OnAfterWrite(WriteWrap*)`
* Wrappers:
* `DoShutdown(ShutdownWrap*)`
* `DoTryWrite(uv_buf_t** bufs, size_t* count)`
* `DoWrite(WriteWrap*, uv_buf_t*, size_t count, uv_stream_t* handle)`
* `Error()`
* `ClearError()`
The implementation should provide all of these methods, thus providing
the access to the underlying resource (be it uv handle, TLS socket, or
anything else).
A C++ stream may consume the input of another stream by replacing the
event callbacks and proxying the writes. This kind of API is actually
used now for the TLSWrap implementation, making it possible to wrap TLS
stream into another TLS stream. Thus legacy API calls are no longer
required in `_tls_wrap.js`.
PR-URL: https://github.com/iojs/io.js/pull/840
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
10 years ago
|
|
|
#include "tls_wrap.h" // TLSWrap
|
|
|
|
|
|
|
|
#include "async-wrap.h"
|
|
|
|
#include "async-wrap-inl.h"
|
|
|
|
#include "env.h"
|
|
|
|
#include "env-inl.h"
|
|
|
|
#include "string_bytes.h"
|
|
|
|
#include "util.h"
|
|
|
|
#include "util-inl.h"
|
|
|
|
#include "v8.h"
|
|
|
|
// CNNIC Hash WhiteList is taken from
|
|
|
|
// https://hg.mozilla.org/mozilla-central/raw-file/98820360ab66/security/
|
|
|
|
// certverifier/CNNICHashWhitelist.inc
|
|
|
|
#include "CNNICHashWhitelist.inc"
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#if defined(_MSC_VER)
|
|
|
|
#define strcasecmp _stricmp
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
|
|
|
|
#define OPENSSL_CONST const
|
|
|
|
#else
|
|
|
|
#define OPENSSL_CONST
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(val) \
|
|
|
|
do { \
|
|
|
|
if (!Buffer::HasInstance(val) && !val->IsString()) { \
|
|
|
|
return env->ThrowTypeError("Not a string or buffer"); \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define THROW_AND_RETURN_IF_NOT_BUFFER(val) \
|
|
|
|
do { \
|
|
|
|
if (!Buffer::HasInstance(val)) { \
|
|
|
|
return env->ThrowTypeError("Not a buffer"); \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
static const char PUBLIC_KEY_PFX[] = "-----BEGIN PUBLIC KEY-----";
|
|
|
|
static const int PUBLIC_KEY_PFX_LEN = sizeof(PUBLIC_KEY_PFX) - 1;
|
|
|
|
static const char PUBRSA_KEY_PFX[] = "-----BEGIN RSA PUBLIC KEY-----";
|
|
|
|
static const int PUBRSA_KEY_PFX_LEN = sizeof(PUBRSA_KEY_PFX) - 1;
|
|
|
|
static const char CERTIFICATE_PFX[] = "-----BEGIN CERTIFICATE-----";
|
|
|
|
static const int CERTIFICATE_PFX_LEN = sizeof(CERTIFICATE_PFX) - 1;
|
|
|
|
|
|
|
|
static const int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL
|
|
|
|
| ASN1_STRFLGS_UTF8_CONVERT
|
|
|
|
| XN_FLAG_SEP_MULTILINE
|
|
|
|
| XN_FLAG_FN_SN;
|
|
|
|
|
|
|
|
namespace node {
|
|
|
|
namespace crypto {
|
|
|
|
|
|
|
|
using v8::Array;
|
|
|
|
using v8::Boolean;
|
|
|
|
using v8::Context;
|
|
|
|
using v8::DEFAULT;
|
|
|
|
using v8::DontDelete;
|
|
|
|
using v8::EscapableHandleScope;
|
|
|
|
using v8::Exception;
|
|
|
|
using v8::External;
|
|
|
|
using v8::False;
|
|
|
|
using v8::FunctionCallbackInfo;
|
|
|
|
using v8::FunctionTemplate;
|
|
|
|
using v8::HandleScope;
|
|
|
|
using v8::Integer;
|
|
|
|
using v8::Isolate;
|
|
|
|
using v8::Local;
|
|
|
|
using v8::Null;
|
|
|
|
using v8::Object;
|
|
|
|
using v8::Persistent;
|
|
|
|
using v8::PropertyAttribute;
|
|
|
|
using v8::PropertyCallbackInfo;
|
|
|
|
using v8::ReadOnly;
|
|
|
|
using v8::String;
|
|
|
|
using v8::V8;
|
|
|
|
using v8::Value;
|
|
|
|
|
|
|
|
|
|
|
|
// Subject DER of CNNIC ROOT CA and CNNIC EV ROOT CA are taken from
|
|
|
|
// https://hg.mozilla.org/mozilla-central/file/98820360ab66/security/
|
|
|
|
// certverifier/NSSCertDBTrustDomain.cpp#l672
|
|
|
|
// C = CN, O = CNNIC, CN = CNNIC ROOT
|
|
|
|
static const uint8_t CNNIC_ROOT_CA_SUBJECT_DATA[] =
|
|
|
|
"\x30\x32\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x0E\x30"
|
|
|
|
"\x0C\x06\x03\x55\x04\x0A\x13\x05\x43\x4E\x4E\x49\x43\x31\x13\x30\x11\x06"
|
|
|
|
"\x03\x55\x04\x03\x13\x0A\x43\x4E\x4E\x49\x43\x20\x52\x4F\x4F\x54";
|
|
|
|
static const uint8_t* cnnic_p = CNNIC_ROOT_CA_SUBJECT_DATA;
|
|
|
|
static X509_NAME* cnnic_name =
|
|
|
|
d2i_X509_NAME(nullptr, &cnnic_p, sizeof(CNNIC_ROOT_CA_SUBJECT_DATA)-1);
|
|
|
|
|
|
|
|
// C = CN, O = China Internet Network Information Center, CN = China
|
|
|
|
// Internet Network Information Center EV Certificates Root
|
|
|
|
static const uint8_t CNNIC_EV_ROOT_CA_SUBJECT_DATA[] =
|
|
|
|
"\x30\x81\x8A\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x32"
|
|
|
|
"\x30\x30\x06\x03\x55\x04\x0A\x0C\x29\x43\x68\x69\x6E\x61\x20\x49\x6E\x74"
|
|
|
|
"\x65\x72\x6E\x65\x74\x20\x4E\x65\x74\x77\x6F\x72\x6B\x20\x49\x6E\x66\x6F"
|
|
|
|
"\x72\x6D\x61\x74\x69\x6F\x6E\x20\x43\x65\x6E\x74\x65\x72\x31\x47\x30\x45"
|
|
|
|
"\x06\x03\x55\x04\x03\x0C\x3E\x43\x68\x69\x6E\x61\x20\x49\x6E\x74\x65\x72"
|
|
|
|
"\x6E\x65\x74\x20\x4E\x65\x74\x77\x6F\x72\x6B\x20\x49\x6E\x66\x6F\x72\x6D"
|
|
|
|
"\x61\x74\x69\x6F\x6E\x20\x43\x65\x6E\x74\x65\x72\x20\x45\x56\x20\x43\x65"
|
|
|
|
"\x72\x74\x69\x66\x69\x63\x61\x74\x65\x73\x20\x52\x6F\x6F\x74";
|
|
|
|
static const uint8_t* cnnic_ev_p = CNNIC_EV_ROOT_CA_SUBJECT_DATA;
|
|
|
|
static X509_NAME *cnnic_ev_name =
|
|
|
|
d2i_X509_NAME(nullptr, &cnnic_ev_p,
|
|
|
|
sizeof(CNNIC_EV_ROOT_CA_SUBJECT_DATA)-1);
|
|
|
|
|
|
|
|
static uv_mutex_t* locks;
|
|
|
|
|
|
|
|
const char* const root_certs[] = {
|
|
|
|
#include "node_root_certs.h" // NOLINT(build/include_order)
|
|
|
|
};
|
|
|
|
|
|
|
|
X509_STORE* root_cert_store;
|
|
|
|
|
|
|
|
// Just to generate static methods
|
stream_base: introduce StreamBase
StreamBase is an improved way to write C++ streams. The class itself is
for separting `StreamWrap` (with the methods like `.writeAsciiString`,
`.writeBuffer`, `.writev`, etc) from the `HandleWrap` class, making
possible to write abstract C++ streams that are not bound to any uv
socket.
The following methods are important part of the abstraction (which
mimics libuv's stream API):
* Events:
* `OnAlloc(size_t size, uv_buf_t*)`
* `OnRead(ssize_t nread, const uv_buf_t*, uv_handle_type pending)`
* `OnAfterWrite(WriteWrap*)`
* Wrappers:
* `DoShutdown(ShutdownWrap*)`
* `DoTryWrite(uv_buf_t** bufs, size_t* count)`
* `DoWrite(WriteWrap*, uv_buf_t*, size_t count, uv_stream_t* handle)`
* `Error()`
* `ClearError()`
The implementation should provide all of these methods, thus providing
the access to the underlying resource (be it uv handle, TLS socket, or
anything else).
A C++ stream may consume the input of another stream by replacing the
event callbacks and proxying the writes. This kind of API is actually
used now for the TLSWrap implementation, making it possible to wrap TLS
stream into another TLS stream. Thus legacy API calls are no longer
required in `_tls_wrap.js`.
PR-URL: https://github.com/iojs/io.js/pull/840
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
10 years ago
|
|
|
template class SSLWrap<TLSWrap>;
|
|
|
|
template void SSLWrap<TLSWrap>::AddMethods(Environment* env,
|
|
|
|
Local<FunctionTemplate> t);
|
stream_base: introduce StreamBase
StreamBase is an improved way to write C++ streams. The class itself is
for separting `StreamWrap` (with the methods like `.writeAsciiString`,
`.writeBuffer`, `.writev`, etc) from the `HandleWrap` class, making
possible to write abstract C++ streams that are not bound to any uv
socket.
The following methods are important part of the abstraction (which
mimics libuv's stream API):
* Events:
* `OnAlloc(size_t size, uv_buf_t*)`
* `OnRead(ssize_t nread, const uv_buf_t*, uv_handle_type pending)`
* `OnAfterWrite(WriteWrap*)`
* Wrappers:
* `DoShutdown(ShutdownWrap*)`
* `DoTryWrite(uv_buf_t** bufs, size_t* count)`
* `DoWrite(WriteWrap*, uv_buf_t*, size_t count, uv_stream_t* handle)`
* `Error()`
* `ClearError()`
The implementation should provide all of these methods, thus providing
the access to the underlying resource (be it uv handle, TLS socket, or
anything else).
A C++ stream may consume the input of another stream by replacing the
event callbacks and proxying the writes. This kind of API is actually
used now for the TLSWrap implementation, making it possible to wrap TLS
stream into another TLS stream. Thus legacy API calls are no longer
required in `_tls_wrap.js`.
PR-URL: https://github.com/iojs/io.js/pull/840
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
10 years ago
|
|
|
template void SSLWrap<TLSWrap>::InitNPN(SecureContext* sc);
|
|
|
|
template void SSLWrap<TLSWrap>::SetSNIContext(SecureContext* sc);
|
|
|
|
template int SSLWrap<TLSWrap>::SetCACerts(SecureContext* sc);
|
stream_base: introduce StreamBase
StreamBase is an improved way to write C++ streams. The class itself is
for separting `StreamWrap` (with the methods like `.writeAsciiString`,
`.writeBuffer`, `.writev`, etc) from the `HandleWrap` class, making
possible to write abstract C++ streams that are not bound to any uv
socket.
The following methods are important part of the abstraction (which
mimics libuv's stream API):
* Events:
* `OnAlloc(size_t size, uv_buf_t*)`
* `OnRead(ssize_t nread, const uv_buf_t*, uv_handle_type pending)`
* `OnAfterWrite(WriteWrap*)`
* Wrappers:
* `DoShutdown(ShutdownWrap*)`
* `DoTryWrite(uv_buf_t** bufs, size_t* count)`
* `DoWrite(WriteWrap*, uv_buf_t*, size_t count, uv_stream_t* handle)`
* `Error()`
* `ClearError()`
The implementation should provide all of these methods, thus providing
the access to the underlying resource (be it uv handle, TLS socket, or
anything else).
A C++ stream may consume the input of another stream by replacing the
event callbacks and proxying the writes. This kind of API is actually
used now for the TLSWrap implementation, making it possible to wrap TLS
stream into another TLS stream. Thus legacy API calls are no longer
required in `_tls_wrap.js`.
PR-URL: https://github.com/iojs/io.js/pull/840
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
10 years ago
|
|
|
template SSL_SESSION* SSLWrap<TLSWrap>::GetSessionCallback(
|
|
|
|
SSL* s,
|
|
|
|
unsigned char* key,
|
|
|
|
int len,
|
|
|
|
int* copy);
|
stream_base: introduce StreamBase
StreamBase is an improved way to write C++ streams. The class itself is
for separting `StreamWrap` (with the methods like `.writeAsciiString`,
`.writeBuffer`, `.writev`, etc) from the `HandleWrap` class, making
possible to write abstract C++ streams that are not bound to any uv
socket.
The following methods are important part of the abstraction (which
mimics libuv's stream API):
* Events:
* `OnAlloc(size_t size, uv_buf_t*)`
* `OnRead(ssize_t nread, const uv_buf_t*, uv_handle_type pending)`
* `OnAfterWrite(WriteWrap*)`
* Wrappers:
* `DoShutdown(ShutdownWrap*)`
* `DoTryWrite(uv_buf_t** bufs, size_t* count)`
* `DoWrite(WriteWrap*, uv_buf_t*, size_t count, uv_stream_t* handle)`
* `Error()`
* `ClearError()`
The implementation should provide all of these methods, thus providing
the access to the underlying resource (be it uv handle, TLS socket, or
anything else).
A C++ stream may consume the input of another stream by replacing the
event callbacks and proxying the writes. This kind of API is actually
used now for the TLSWrap implementation, making it possible to wrap TLS
stream into another TLS stream. Thus legacy API calls are no longer
required in `_tls_wrap.js`.
PR-URL: https://github.com/iojs/io.js/pull/840
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
10 years ago
|
|
|
template int SSLWrap<TLSWrap>::NewSessionCallback(SSL* s,
|
|
|
|
SSL_SESSION* sess);
|
|
|
|
template void SSLWrap<TLSWrap>::OnClientHello(
|
|
|
|
void* arg,
|
|
|
|
const ClientHelloParser::ClientHello& hello);
|
|
|
|
|
|
|
|
#ifdef OPENSSL_NPN_NEGOTIATED
|
stream_base: introduce StreamBase
StreamBase is an improved way to write C++ streams. The class itself is
for separting `StreamWrap` (with the methods like `.writeAsciiString`,
`.writeBuffer`, `.writev`, etc) from the `HandleWrap` class, making
possible to write abstract C++ streams that are not bound to any uv
socket.
The following methods are important part of the abstraction (which
mimics libuv's stream API):
* Events:
* `OnAlloc(size_t size, uv_buf_t*)`
* `OnRead(ssize_t nread, const uv_buf_t*, uv_handle_type pending)`
* `OnAfterWrite(WriteWrap*)`
* Wrappers:
* `DoShutdown(ShutdownWrap*)`
* `DoTryWrite(uv_buf_t** bufs, size_t* count)`
* `DoWrite(WriteWrap*, uv_buf_t*, size_t count, uv_stream_t* handle)`
* `Error()`
* `ClearError()`
The implementation should provide all of these methods, thus providing
the access to the underlying resource (be it uv handle, TLS socket, or
anything else).
A C++ stream may consume the input of another stream by replacing the
event callbacks and proxying the writes. This kind of API is actually
used now for the TLSWrap implementation, making it possible to wrap TLS
stream into another TLS stream. Thus legacy API calls are no longer
required in `_tls_wrap.js`.
PR-URL: https://github.com/iojs/io.js/pull/840
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
10 years ago
|
|
|
template int SSLWrap<TLSWrap>::AdvertiseNextProtoCallback(
|
|
|
|
SSL* s,
|
|
|
|
const unsigned char** data,
|
|
|
|
unsigned int* len,
|
|
|
|
void* arg);
|
stream_base: introduce StreamBase
StreamBase is an improved way to write C++ streams. The class itself is
for separting `StreamWrap` (with the methods like `.writeAsciiString`,
`.writeBuffer`, `.writev`, etc) from the `HandleWrap` class, making
possible to write abstract C++ streams that are not bound to any uv
socket.
The following methods are important part of the abstraction (which
mimics libuv's stream API):
* Events:
* `OnAlloc(size_t size, uv_buf_t*)`
* `OnRead(ssize_t nread, const uv_buf_t*, uv_handle_type pending)`
* `OnAfterWrite(WriteWrap*)`
* Wrappers:
* `DoShutdown(ShutdownWrap*)`
* `DoTryWrite(uv_buf_t** bufs, size_t* count)`
* `DoWrite(WriteWrap*, uv_buf_t*, size_t count, uv_stream_t* handle)`
* `Error()`
* `ClearError()`
The implementation should provide all of these methods, thus providing
the access to the underlying resource (be it uv handle, TLS socket, or
anything else).
A C++ stream may consume the input of another stream by replacing the
event callbacks and proxying the writes. This kind of API is actually
used now for the TLSWrap implementation, making it possible to wrap TLS
stream into another TLS stream. Thus legacy API calls are no longer
required in `_tls_wrap.js`.
PR-URL: https://github.com/iojs/io.js/pull/840
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
10 years ago
|
|
|
template int SSLWrap<TLSWrap>::SelectNextProtoCallback(
|
|
|
|
SSL* s,
|
|
|
|
unsigned char** out,
|
|
|
|
unsigned char* outlen,
|
|
|
|
const unsigned char* in,
|
|
|
|
unsigned int inlen,
|
|
|
|
void* arg);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
|
stream_base: introduce StreamBase
StreamBase is an improved way to write C++ streams. The class itself is
for separting `StreamWrap` (with the methods like `.writeAsciiString`,
`.writeBuffer`, `.writev`, etc) from the `HandleWrap` class, making
possible to write abstract C++ streams that are not bound to any uv
socket.
The following methods are important part of the abstraction (which
mimics libuv's stream API):
* Events:
* `OnAlloc(size_t size, uv_buf_t*)`
* `OnRead(ssize_t nread, const uv_buf_t*, uv_handle_type pending)`
* `OnAfterWrite(WriteWrap*)`
* Wrappers:
* `DoShutdown(ShutdownWrap*)`
* `DoTryWrite(uv_buf_t** bufs, size_t* count)`
* `DoWrite(WriteWrap*, uv_buf_t*, size_t count, uv_stream_t* handle)`
* `Error()`
* `ClearError()`
The implementation should provide all of these methods, thus providing
the access to the underlying resource (be it uv handle, TLS socket, or
anything else).
A C++ stream may consume the input of another stream by replacing the
event callbacks and proxying the writes. This kind of API is actually
used now for the TLSWrap implementation, making it possible to wrap TLS
stream into another TLS stream. Thus legacy API calls are no longer
required in `_tls_wrap.js`.
PR-URL: https://github.com/iojs/io.js/pull/840
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
10 years ago
|
|
|
template int SSLWrap<TLSWrap>::TLSExtStatusCallback(SSL* s, void* arg);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
template void SSLWrap<TLSWrap>::DestroySSL();
|
|
|
|
template int SSLWrap<TLSWrap>::SSLCertCallback(SSL* s, void* arg);
|
|
|
|
template void SSLWrap<TLSWrap>::WaitForCertCb(CertCb cb, void* arg);
|
|
|
|
|
|
|
|
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
|
|
|
|
template int SSLWrap<TLSWrap>::SelectALPNCallback(
|
|
|
|
SSL* s,
|
|
|
|
const unsigned char** out,
|
|
|
|
unsigned char* outlen,
|
|
|
|
const unsigned char* in,
|
|
|
|
unsigned int inlen,
|
|
|
|
void* arg);
|
|
|
|
#endif // TLSEXT_TYPE_application_layer_protocol_negotiation
|
|
|
|
|
|
|
|
static void crypto_threadid_cb(CRYPTO_THREADID* tid) {
|
|
|
|
static_assert(sizeof(uv_thread_t) <= sizeof(void*), // NOLINT(runtime/sizeof)
|
|
|
|
"uv_thread_t does not fit in a pointer");
|
|
|
|
CRYPTO_THREADID_set_pointer(tid, reinterpret_cast<void*>(uv_thread_self()));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void crypto_lock_init(void) {
|
|
|
|
int i, n;
|
|
|
|
|
|
|
|
n = CRYPTO_num_locks();
|
|
|
|
locks = new uv_mutex_t[n];
|
|
|
|
|
|
|
|
for (i = 0; i < n; i++)
|
|
|
|
if (uv_mutex_init(locks + i))
|
|
|
|
ABORT();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void crypto_lock_cb(int mode, int n, const char* file, int line) {
|
|
|
|
CHECK(!(mode & CRYPTO_LOCK) ^ !(mode & CRYPTO_UNLOCK));
|
|
|
|
CHECK(!(mode & CRYPTO_READ) ^ !(mode & CRYPTO_WRITE));
|
|
|
|
|
|
|
|
if (mode & CRYPTO_LOCK)
|
|
|
|
uv_mutex_lock(locks + n);
|
|
|
|
else
|
|
|
|
uv_mutex_unlock(locks + n);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int CryptoPemCallback(char *buf, int size, int rwflag, void *u) {
|
|
|
|
if (u) {
|
|
|
|
size_t buflen = static_cast<size_t>(size);
|
|
|
|
size_t len = strlen(static_cast<const char*>(u));
|
|
|
|
len = len > buflen ? buflen : len;
|
|
|
|
memcpy(buf, u, len);
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ThrowCryptoError(Environment* env,
|
|
|
|
unsigned long err,
|
|
|
|
const char* default_message = nullptr) {
|
|
|
|
HandleScope scope(env->isolate());
|
|
|
|
if (err != 0 || default_message == nullptr) {
|
|
|
|
char errmsg[128] = { 0 };
|
|
|
|
ERR_error_string_n(err, errmsg, sizeof(errmsg));
|
|
|
|
env->ThrowError(errmsg);
|
|
|
|
} else {
|
|
|
|
env->ThrowError(default_message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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(SecureContext::New);
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "SecureContext"));
|
|
|
|
|
|
|
|
env->SetProtoMethod(t, "init", SecureContext::Init);
|
|
|
|
env->SetProtoMethod(t, "setKey", SecureContext::SetKey);
|
|
|
|
env->SetProtoMethod(t, "setCert", SecureContext::SetCert);
|
|
|
|
env->SetProtoMethod(t, "addCACert", SecureContext::AddCACert);
|
|
|
|
env->SetProtoMethod(t, "addCRL", SecureContext::AddCRL);
|
|
|
|
env->SetProtoMethod(t, "addRootCerts", SecureContext::AddRootCerts);
|
|
|
|
env->SetProtoMethod(t, "setCiphers", SecureContext::SetCiphers);
|
|
|
|
env->SetProtoMethod(t, "setECDHCurve", SecureContext::SetECDHCurve);
|
|
|
|
env->SetProtoMethod(t, "setDHParam", SecureContext::SetDHParam);
|
|
|
|
env->SetProtoMethod(t, "setOptions", SecureContext::SetOptions);
|
|
|
|
env->SetProtoMethod(t, "setSessionIdContext",
|
|
|
|
SecureContext::SetSessionIdContext);
|
|
|
|
env->SetProtoMethod(t, "setSessionTimeout",
|
|
|
|
SecureContext::SetSessionTimeout);
|
|
|
|
env->SetProtoMethod(t, "close", SecureContext::Close);
|
|
|
|
env->SetProtoMethod(t, "loadPKCS12", SecureContext::LoadPKCS12);
|
|
|
|
env->SetProtoMethod(t, "getTicketKeys", SecureContext::GetTicketKeys);
|
|
|
|
env->SetProtoMethod(t, "setTicketKeys", SecureContext::SetTicketKeys);
|
|
|
|
env->SetProtoMethod(t, "setFreeListLength", SecureContext::SetFreeListLength);
|
|
|
|
env->SetProtoMethod(t,
|
|
|
|
"enableTicketKeyCallback",
|
|
|
|
SecureContext::EnableTicketKeyCallback);
|
|
|
|
env->SetProtoMethod(t, "getCertificate", SecureContext::GetCertificate<true>);
|
|
|
|
env->SetProtoMethod(t, "getIssuer", SecureContext::GetCertificate<false>);
|
|
|
|
|
|
|
|
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyReturnIndex"),
|
|
|
|
Integer::NewFromUnsigned(env->isolate(), kTicketKeyReturnIndex));
|
|
|
|
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyHMACIndex"),
|
|
|
|
Integer::NewFromUnsigned(env->isolate(), kTicketKeyHMACIndex));
|
|
|
|
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyAESIndex"),
|
|
|
|
Integer::NewFromUnsigned(env->isolate(), kTicketKeyAESIndex));
|
|
|
|
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyNameIndex"),
|
|
|
|
Integer::NewFromUnsigned(env->isolate(), kTicketKeyNameIndex));
|
|
|
|
t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyIVIndex"),
|
|
|
|
Integer::NewFromUnsigned(env->isolate(), kTicketKeyIVIndex));
|
|
|
|
|
|
|
|
t->PrototypeTemplate()->SetAccessor(
|
|
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "_external"),
|
|
|
|
CtxGetter,
|
|
|
|
nullptr,
|
|
|
|
env->as_external(),
|
|
|
|
DEFAULT,
|
|
|
|
static_cast<PropertyAttribute>(ReadOnly | DontDelete));
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "SecureContext"),
|
|
|
|
t->GetFunction());
|
|
|
|
env->set_secure_context_constructor_template(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
new SecureContext(env, args.This());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.Holder());
|
|
|
|
Environment* env = sc->env();
|
|
|
|
|
|
|
|
OPENSSL_CONST SSL_METHOD *method = SSLv23_method();
|
|
|
|
|
|
|
|
if (args.Length() == 1 && args[0]->IsString()) {
|
|
|
|
const node::Utf8Value sslmethod(env->isolate(), args[0]);
|
|
|
|
|
|
|
|
// Note that SSLv2 and SSLv3 are disallowed but SSLv2_method and friends
|
|
|
|
// are still accepted. They are OpenSSL's way of saying that all known
|
|
|
|
// protocols are supported unless explicitly disabled (which we do below
|
|
|
|
// for SSLv2 and SSLv3.)
|
|
|
|
if (strcmp(*sslmethod, "SSLv2_method") == 0) {
|
|
|
|
return env->ThrowError("SSLv2 methods disabled");
|
|
|
|
} else if (strcmp(*sslmethod, "SSLv2_server_method") == 0) {
|
|
|
|
return env->ThrowError("SSLv2 methods disabled");
|
|
|
|
} else if (strcmp(*sslmethod, "SSLv2_client_method") == 0) {
|
|
|
|
return env->ThrowError("SSLv2 methods disabled");
|
|
|
|
} else if (strcmp(*sslmethod, "SSLv3_method") == 0) {
|
|
|
|
return env->ThrowError("SSLv3 methods disabled");
|
|
|
|
} else if (strcmp(*sslmethod, "SSLv3_server_method") == 0) {
|
|
|
|
return env->ThrowError("SSLv3 methods disabled");
|
|
|
|
} else if (strcmp(*sslmethod, "SSLv3_client_method") == 0) {
|
|
|
|
return env->ThrowError("SSLv3 methods disabled");
|
|
|
|
} else if (strcmp(*sslmethod, "SSLv23_method") == 0) {
|
|
|
|
method = SSLv23_method();
|
|
|
|
} else if (strcmp(*sslmethod, "SSLv23_server_method") == 0) {
|
|
|
|
method = SSLv23_server_method();
|
|
|
|
} else if (strcmp(*sslmethod, "SSLv23_client_method") == 0) {
|
|
|
|
method = SSLv23_client_method();
|
|
|
|
} else if (strcmp(*sslmethod, "TLSv1_method") == 0) {
|
|
|
|
method = TLSv1_method();
|
|
|
|
} else if (strcmp(*sslmethod, "TLSv1_server_method") == 0) {
|
|
|
|
method = TLSv1_server_method();
|
|
|
|
} else if (strcmp(*sslmethod, "TLSv1_client_method") == 0) {
|
|
|
|
method = TLSv1_client_method();
|
|
|
|
} else if (strcmp(*sslmethod, "TLSv1_1_method") == 0) {
|
|
|
|
method = TLSv1_1_method();
|
|
|
|
} else if (strcmp(*sslmethod, "TLSv1_1_server_method") == 0) {
|
|
|
|
method = TLSv1_1_server_method();
|
|
|
|
} else if (strcmp(*sslmethod, "TLSv1_1_client_method") == 0) {
|
|
|
|
method = TLSv1_1_client_method();
|
|
|
|
} else if (strcmp(*sslmethod, "TLSv1_2_method") == 0) {
|
|
|
|
method = TLSv1_2_method();
|
|
|
|
} else if (strcmp(*sslmethod, "TLSv1_2_server_method") == 0) {
|
|
|
|
method = TLSv1_2_server_method();
|
|
|
|
} else if (strcmp(*sslmethod, "TLSv1_2_client_method") == 0) {
|
|
|
|
method = TLSv1_2_client_method();
|
|
|
|
} else {
|
|
|
|
return env->ThrowError("Unknown method");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sc->ctx_ = SSL_CTX_new(method);
|
|
|
|
SSL_CTX_set_app_data(sc->ctx_, sc);
|
|
|
|
|
|
|
|
// Disable SSLv2 in the case when method == SSLv23_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_, SSL_OP_NO_SSLv2);
|
|
|
|
SSL_CTX_set_options(sc->ctx_, SSL_OP_NO_SSLv3);
|
|
|
|
|
|
|
|
// SSL session cache configuration
|
|
|
|
SSL_CTX_set_session_cache_mode(sc->ctx_,
|
|
|
|
SSL_SESS_CACHE_SERVER |
|
|
|
|
SSL_SESS_CACHE_NO_INTERNAL |
|
|
|
|
SSL_SESS_CACHE_NO_AUTO_CLEAR);
|
|
|
|
SSL_CTX_sess_set_get_cb(sc->ctx_, SSLWrap<Connection>::GetSessionCallback);
|
|
|
|
SSL_CTX_sess_set_new_cb(sc->ctx_, SSLWrap<Connection>::NewSessionCallback);
|
|
|
|
|
|
|
|
sc->ca_store_ = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Takes a string or buffer and loads it into a BIO.
|
|
|
|
// Caller responsible for BIO_free_all-ing the returned object.
|
|
|
|
static BIO* LoadBIO(Environment* env, Local<Value> v) {
|
|
|
|
BIO* bio = NodeBIO::New();
|
|
|
|
if (!bio)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
HandleScope scope(env->isolate());
|
|
|
|
|
|
|
|
int r = -1;
|
|
|
|
|
|
|
|
if (v->IsString()) {
|
|
|
|
const node::Utf8Value s(env->isolate(), v);
|
|
|
|
r = BIO_write(bio, *s, s.length());
|
|
|
|
} else if (Buffer::HasInstance(v)) {
|
|
|
|
char* buffer_data = Buffer::Data(v);
|
|
|
|
size_t buffer_length = Buffer::Length(v);
|
|
|
|
r = BIO_write(bio, buffer_data, buffer_length);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r <= 0) {
|
|
|
|
BIO_free_all(bio);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return bio;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetKey(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.Holder());
|
|
|
|
|
|
|
|
unsigned int len = args.Length();
|
|
|
|
if (len != 1 && len != 2) {
|
|
|
|
return env->ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
if (len == 2 && !args[1]->IsString()) {
|
|
|
|
return env->ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
BIO *bio = LoadBIO(env, args[0]);
|
|
|
|
if (!bio)
|
|
|
|
return;
|
|
|
|
|
|
|
|
node::Utf8Value passphrase(env->isolate(), args[1]);
|
|
|
|
|
|
|
|
EVP_PKEY* key = PEM_read_bio_PrivateKey(bio,
|
|
|
|
nullptr,
|
|
|
|
CryptoPemCallback,
|
|
|
|
len == 1 ? nullptr : *passphrase);
|
|
|
|
|
|
|
|
if (!key) {
|
|
|
|
BIO_free_all(bio);
|
|
|
|
unsigned long err = ERR_get_error();
|
|
|
|
if (!err) {
|
|
|
|
return env->ThrowError("PEM_read_bio_PrivateKey");
|
|
|
|
}
|
|
|
|
return ThrowCryptoError(env, err);
|
|
|
|
}
|
|
|
|
|
|
|
|
int rv = SSL_CTX_use_PrivateKey(sc->ctx_, key);
|
|
|
|
EVP_PKEY_free(key);
|
|
|
|
BIO_free_all(bio);
|
|
|
|
|
|
|
|
if (!rv) {
|
|
|
|
unsigned long err = ERR_get_error();
|
|
|
|
if (!err)
|
|
|
|
return env->ThrowError("SSL_CTX_use_PrivateKey");
|
|
|
|
return ThrowCryptoError(env, err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert, X509** issuer) {
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
X509_STORE* store = SSL_CTX_get_cert_store(ctx);
|
|
|
|
X509_STORE_CTX store_ctx;
|
|
|
|
|
|
|
|
ret = X509_STORE_CTX_init(&store_ctx, store, nullptr, nullptr);
|
|
|
|
if (!ret)
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
ret = X509_STORE_CTX_get1_issuer(issuer, &store_ctx, cert);
|
|
|
|
X509_STORE_CTX_cleanup(&store_ctx);
|
|
|
|
|
|
|
|
end:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int SSL_CTX_use_certificate_chain(SSL_CTX* ctx,
|
|
|
|
X509* x,
|
|
|
|
STACK_OF(X509)* extra_certs,
|
|
|
|
X509** cert,
|
|
|
|
X509** issuer) {
|
|
|
|
CHECK_EQ(*issuer, nullptr);
|
|
|
|
CHECK_EQ(*cert, nullptr);
|
|
|
|
|
|
|
|
int ret = SSL_CTX_use_certificate(ctx, x);
|
|
|
|
|
|
|
|
if (ret) {
|
|
|
|
// If we could set up our certificate, now proceed to
|
|
|
|
// the CA certificates.
|
|
|
|
int r;
|
|
|
|
|
|
|
|
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`
|
|
|
|
r = SSL_CTX_add1_chain_cert(ctx, ca);
|
|
|
|
|
|
|
|
if (!r) {
|
|
|
|
ret = 0;
|
|
|
|
*issuer = nullptr;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
// 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) != 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, 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;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
if (ret && x != nullptr) {
|
|
|
|
*cert = X509_dup(x);
|
|
|
|
if (*cert == nullptr)
|
|
|
|
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,
|
|
|
|
BIO* in,
|
|
|
|
X509** cert,
|
|
|
|
X509** issuer) {
|
|
|
|
X509* x = nullptr;
|
|
|
|
|
|
|
|
// Just to ensure that `ERR_peek_last_error` below will return only errors
|
|
|
|
// that we are interested in
|
|
|
|
ERR_clear_error();
|
|
|
|
|
|
|
|
x = PEM_read_bio_X509_AUX(in, nullptr, CryptoPemCallback, nullptr);
|
|
|
|
|
|
|
|
if (x == nullptr) {
|
|
|
|
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_PEM_LIB);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
X509* extra = nullptr;
|
|
|
|
int ret = 0;
|
|
|
|
unsigned long err = 0;
|
|
|
|
|
|
|
|
// Read extra certs
|
|
|
|
STACK_OF(X509)* extra_certs = sk_X509_new_null();
|
|
|
|
if (extra_certs == nullptr) {
|
|
|
|
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_MALLOC_FAILURE);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((extra = PEM_read_bio_X509(in, nullptr, CryptoPemCallback, nullptr))) {
|
|
|
|
if (sk_X509_push(extra_certs, extra))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Failure, free all certs
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
extra = nullptr;
|
|
|
|
|
|
|
|
// 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
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = SSL_CTX_use_certificate_chain(ctx, x, extra_certs, cert, issuer);
|
|
|
|
if (!ret)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
done:
|
|
|
|
if (extra_certs != nullptr)
|
|
|
|
sk_X509_pop_free(extra_certs, X509_free);
|
|
|
|
if (extra != nullptr)
|
|
|
|
X509_free(extra);
|
|
|
|
if (x != nullptr)
|
|
|
|
X509_free(x);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetCert(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.Holder());
|
|
|
|
|
|
|
|
if (args.Length() != 1) {
|
|
|
|
return env->ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
BIO* bio = LoadBIO(env, args[0]);
|
|
|
|
if (!bio)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Free previous certs
|
|
|
|
if (sc->issuer_ != nullptr) {
|
|
|
|
X509_free(sc->issuer_);
|
|
|
|
sc->issuer_ = nullptr;
|
|
|
|
}
|
|
|
|
if (sc->cert_ != nullptr) {
|
|
|
|
X509_free(sc->cert_);
|
|
|
|
sc->cert_ = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
int rv = SSL_CTX_use_certificate_chain(sc->ctx_,
|
|
|
|
bio,
|
|
|
|
&sc->cert_,
|
|
|
|
&sc->issuer_);
|
|
|
|
|
|
|
|
BIO_free_all(bio);
|
|
|
|
|
|
|
|
if (!rv) {
|
|
|
|
unsigned long err = ERR_get_error();
|
|
|
|
if (!err) {
|
|
|
|
return env->ThrowError("SSL_CTX_use_certificate_chain");
|
|
|
|
}
|
|
|
|
return ThrowCryptoError(env, err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::AddCACert(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
bool newCAStore = false;
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.Holder());
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence compiler warning.
|
|
|
|
|
|
|
|
if (args.Length() != 1) {
|
|
|
|
return env->ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!sc->ca_store_) {
|
|
|
|
sc->ca_store_ = X509_STORE_new();
|
|
|
|
newCAStore = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned cert_count = 0;
|
|
|
|
if (BIO* bio = LoadBIO(env, args[0])) {
|
|
|
|
while (X509* x509 = // NOLINT(whitespace/if-one-line)
|
|
|
|
PEM_read_bio_X509(bio, nullptr, CryptoPemCallback, nullptr)) {
|
|
|
|
X509_STORE_add_cert(sc->ca_store_, x509);
|
|
|
|
SSL_CTX_add_client_CA(sc->ctx_, x509);
|
|
|
|
X509_free(x509);
|
|
|
|
cert_count += 1;
|
|
|
|
}
|
|
|
|
BIO_free_all(bio);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cert_count > 0 && newCAStore) {
|
|
|
|
SSL_CTX_set_cert_store(sc->ctx_, sc->ca_store_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.Holder());
|
|
|
|
|
|
|
|
if (args.Length() != 1) {
|
|
|
|
return env->ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence compiler warning.
|
|
|
|
|
|
|
|
BIO *bio = LoadBIO(env, args[0]);
|
|
|
|
if (!bio)
|
|
|
|
return;
|
|
|
|
|
|
|
|
X509_CRL *x509 =
|
|
|
|
PEM_read_bio_X509_CRL(bio, nullptr, CryptoPemCallback, nullptr);
|
|
|
|
|
|
|
|
if (x509 == nullptr) {
|
|
|
|
BIO_free_all(bio);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
X509_STORE_add_crl(sc->ca_store_, x509);
|
|
|
|
X509_STORE_set_flags(sc->ca_store_, X509_V_FLAG_CRL_CHECK |
|
|
|
|
X509_V_FLAG_CRL_CHECK_ALL);
|
|
|
|
BIO_free_all(bio);
|
|
|
|
X509_CRL_free(x509);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::AddRootCerts(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.Holder());
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence compiler warning.
|
|
|
|
|
|
|
|
CHECK_EQ(sc->ca_store_, nullptr);
|
|
|
|
|
|
|
|
if (!root_cert_store) {
|
|
|
|
root_cert_store = X509_STORE_new();
|
|
|
|
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(root_certs); i++) {
|
|
|
|
BIO* bp = NodeBIO::New();
|
|
|
|
|
|
|
|
if (!BIO_write(bp, root_certs[i], strlen(root_certs[i]))) {
|
|
|
|
BIO_free_all(bp);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
X509 *x509 = PEM_read_bio_X509(bp, nullptr, CryptoPemCallback, nullptr);
|
|
|
|
|
|
|
|
if (x509 == nullptr) {
|
|
|
|
BIO_free_all(bp);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
X509_STORE_add_cert(root_cert_store, x509);
|
|
|
|
|
|
|
|
BIO_free_all(bp);
|
|
|
|
X509_free(x509);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sc->ca_store_ = root_cert_store;
|
|
|
|
SSL_CTX_set_cert_store(sc->ctx_, sc->ca_store_);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetCiphers(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.Holder());
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence compiler warning.
|
|
|
|
|
|
|
|
if (args.Length() != 1 || !args[0]->IsString()) {
|
|
|
|
return sc->env()->ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
const node::Utf8Value ciphers(args.GetIsolate(), args[0]);
|
|
|
|
SSL_CTX_set_cipher_list(sc->ctx_, *ciphers);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetECDHCurve(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.Holder());
|
|
|
|
Environment* env = sc->env();
|
|
|
|
|
|
|
|
if (args.Length() != 1 || !args[0]->IsString())
|
|
|
|
return env->ThrowTypeError("First argument should be a string");
|
|
|
|
|
|
|
|
node::Utf8Value curve(env->isolate(), args[0]);
|
|
|
|
|
|
|
|
int nid = OBJ_sn2nid(*curve);
|
|
|
|
|
|
|
|
if (nid == NID_undef)
|
|
|
|
return env->ThrowTypeError("First argument should be a valid curve name");
|
|
|
|
|
|
|
|
EC_KEY* ecdh = EC_KEY_new_by_curve_name(nid);
|
|
|
|
|
|
|
|
if (ecdh == nullptr)
|
|
|
|
return env->ThrowTypeError("First argument should be a valid curve name");
|
|
|
|
|
|
|
|
SSL_CTX_set_options(sc->ctx_, SSL_OP_SINGLE_ECDH_USE);
|
|
|
|
SSL_CTX_set_tmp_ecdh(sc->ctx_, ecdh);
|
|
|
|
|
|
|
|
EC_KEY_free(ecdh);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetDHParam(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.This());
|
|
|
|
Environment* env = sc->env();
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence compiler warning.
|
|
|
|
|
|
|
|
// Auto DH is not supported in openssl 1.0.1, so dhparam needs
|
|
|
|
// to be specifed explicitly
|
|
|
|
if (args.Length() != 1)
|
|
|
|
return env->ThrowTypeError("Bad parameter");
|
|
|
|
|
|
|
|
// Invalid dhparam is silently discarded and DHE is no longer used.
|
|
|
|
BIO* bio = LoadBIO(env, args[0]);
|
|
|
|
if (!bio)
|
|
|
|
return;
|
|
|
|
|
|
|
|
DH* dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr);
|
|
|
|
BIO_free_all(bio);
|
|
|
|
|
|
|
|
if (dh == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const int size = BN_num_bits(dh->p);
|
|
|
|
if (size < 1024) {
|
|
|
|
return env->ThrowError("DH parameter is less than 1024 bits");
|
|
|
|
} else if (size < 2048) {
|
|
|
|
args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(
|
|
|
|
env->isolate(), "WARNING: DH parameter is less than 2048 bits"));
|
|
|
|
}
|
|
|
|
|
|
|
|
SSL_CTX_set_options(sc->ctx_, SSL_OP_SINGLE_DH_USE);
|
|
|
|
int r = SSL_CTX_set_tmp_dh(sc->ctx_, dh);
|
|
|
|
DH_free(dh);
|
|
|
|
|
|
|
|
if (!r)
|
|
|
|
return env->ThrowTypeError("Error setting temp DH parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetOptions(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.Holder());
|
|
|
|
|
|
|
|
if (args.Length() != 1 || !args[0]->IntegerValue()) {
|
|
|
|
return sc->env()->ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
SSL_CTX_set_options(sc->ctx_, static_cast<long>(args[0]->IntegerValue()));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetSessionIdContext(
|
|
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.Holder());
|
|
|
|
|
|
|
|
if (args.Length() != 1 || !args[0]->IsString()) {
|
|
|
|
return sc->env()->ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
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_, sid_ctx, sid_ctx_len);
|
|
|
|
if (r == 1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
BIO* bio;
|
|
|
|
BUF_MEM* mem;
|
|
|
|
Local<String> message;
|
|
|
|
|
|
|
|
bio = BIO_new(BIO_s_mem());
|
|
|
|
if (bio == nullptr) {
|
|
|
|
message = FIXED_ONE_BYTE_STRING(args.GetIsolate(),
|
|
|
|
"SSL_CTX_set_session_id_context error");
|
|
|
|
} else {
|
|
|
|
ERR_print_errors(bio);
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
message = OneByteString(args.GetIsolate(), mem->data, mem->length);
|
|
|
|
BIO_free_all(bio);
|
|
|
|
}
|
|
|
|
|
|
|
|
args.GetIsolate()->ThrowException(Exception::TypeError(message));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetSessionTimeout(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.Holder());
|
|
|
|
|
|
|
|
if (args.Length() != 1 || !args[0]->IsInt32()) {
|
|
|
|
return sc->env()->ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t sessionTimeout = args[0]->Int32Value();
|
|
|
|
SSL_CTX_set_timeout(sc->ctx_, sessionTimeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::Close(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.Holder());
|
|
|
|
sc->FreeCTXMem();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Takes .pfx or .p12 and password in string or buffer format
|
|
|
|
void SecureContext::LoadPKCS12(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
BIO* in = nullptr;
|
|
|
|
PKCS12* p12 = nullptr;
|
|
|
|
EVP_PKEY* pkey = nullptr;
|
|
|
|
X509* cert = nullptr;
|
|
|
|
STACK_OF(X509)* extra_certs = nullptr;
|
|
|
|
char* pass = nullptr;
|
|
|
|
bool ret = false;
|
|
|
|
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args.Holder());
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence compiler warning.
|
|
|
|
|
|
|
|
if (args.Length() < 1) {
|
|
|
|
return env->ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
in = LoadBIO(env, args[0]);
|
|
|
|
if (in == nullptr) {
|
|
|
|
return env->ThrowError("Unable to load BIO");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args.Length() >= 2) {
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[1]);
|
|
|
|
size_t passlen = Buffer::Length(args[1]);
|
|
|
|
pass = new char[passlen + 1];
|
|
|
|
memcpy(pass, Buffer::Data(args[1]), passlen);
|
|
|
|
pass[passlen] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Free previous certs
|
|
|
|
if (sc->issuer_ != nullptr) {
|
|
|
|
X509_free(sc->issuer_);
|
|
|
|
sc->issuer_ = nullptr;
|
|
|
|
}
|
|
|
|
if (sc->cert_ != nullptr) {
|
|
|
|
X509_free(sc->cert_);
|
|
|
|
sc->cert_ = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d2i_PKCS12_bio(in, &p12) &&
|
|
|
|
PKCS12_parse(p12, pass, &pkey, &cert, &extra_certs) &&
|
|
|
|
SSL_CTX_use_certificate_chain(sc->ctx_,
|
|
|
|
cert,
|
|
|
|
extra_certs,
|
|
|
|
&sc->cert_,
|
|
|
|
&sc->issuer_) &&
|
|
|
|
SSL_CTX_use_PrivateKey(sc->ctx_, pkey)) {
|
|
|
|
ret = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pkey != nullptr)
|
|
|
|
EVP_PKEY_free(pkey);
|
|
|
|
if (cert != nullptr)
|
|
|
|
X509_free(cert);
|
|
|
|
if (extra_certs != nullptr)
|
|
|
|
sk_X509_free(extra_certs);
|
|
|
|
|
|
|
|
PKCS12_free(p12);
|
|
|
|
BIO_free_all(in);
|
|
|
|
delete[] pass;
|
|
|
|
|
|
|
|
if (!ret) {
|
|
|
|
unsigned long err = ERR_get_error();
|
|
|
|
const char* str = ERR_reason_error_string(err);
|
|
|
|
return env->ThrowError(str);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::GetTicketKeys(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys)
|
|
|
|
|
|
|
|
SecureContext* wrap = Unwrap<SecureContext>(args.Holder());
|
|
|
|
|
|
|
|
Local<Object> buff = Buffer::New(wrap->env(), 48).ToLocalChecked();
|
|
|
|
if (SSL_CTX_get_tlsext_ticket_keys(wrap->ctx_,
|
|
|
|
Buffer::Data(buff),
|
|
|
|
Buffer::Length(buff)) != 1) {
|
|
|
|
return wrap->env()->ThrowError("Failed to fetch tls ticket keys");
|
|
|
|
}
|
|
|
|
|
|
|
|
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 = Unwrap<SecureContext>(args.Holder());
|
|
|
|
|
|
|
|
if (args.Length() < 1 ||
|
|
|
|
!Buffer::HasInstance(args[0]) ||
|
|
|
|
Buffer::Length(args[0]) != 48) {
|
|
|
|
return wrap->env()->ThrowTypeError("Bad argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SSL_CTX_set_tlsext_ticket_keys(wrap->ctx_,
|
|
|
|
Buffer::Data(args[0]),
|
|
|
|
Buffer::Length(args[0])) != 1) {
|
|
|
|
return wrap->env()->ThrowError("Failed to fetch tls ticket keys");
|
|
|
|
}
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(true);
|
|
|
|
#endif // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetFreeListLength(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
SecureContext* wrap = Unwrap<SecureContext>(args.Holder());
|
|
|
|
|
|
|
|
wrap->ctx_->freelist_max_len = args[0]->Int32Value();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::EnableTicketKeyCallback(
|
|
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
|
|
SecureContext* wrap = Unwrap<SecureContext>(args.Holder());
|
|
|
|
|
|
|
|
SSL_CTX_set_tlsext_ticket_key_cb(wrap->ctx_, 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->ctx));
|
|
|
|
|
|
|
|
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,
|
|
|
|
sc->object(),
|
|
|
|
env->ticketkeycallback_string(),
|
|
|
|
ARRAY_SIZE(argv),
|
|
|
|
argv);
|
|
|
|
Local<Array> arr = ret.As<Array>();
|
|
|
|
|
|
|
|
int r = arr->Get(kTicketKeyReturnIndex)->Int32Value();
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
Local<Value> hmac = arr->Get(kTicketKeyHMACIndex);
|
|
|
|
Local<Value> aes = arr->Get(kTicketKeyAESIndex);
|
|
|
|
if (Buffer::Length(aes) != kTicketPartSize)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (enc) {
|
|
|
|
Local<Value> name_val = arr->Get(kTicketKeyNameIndex);
|
|
|
|
Local<Value> iv_val = arr->Get(kTicketKeyIVIndex);
|
|
|
|
|
|
|
|
if (Buffer::Length(name_val) != kTicketPartSize ||
|
|
|
|
Buffer::Length(iv_val) != kTicketPartSize) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(name, Buffer::Data(name_val), kTicketPartSize);
|
|
|
|
memcpy(iv, Buffer::Data(iv_val), kTicketPartSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
HMAC_Init_ex(hctx,
|
|
|
|
Buffer::Data(hmac),
|
|
|
|
Buffer::Length(hmac),
|
|
|
|
EVP_sha256(),
|
|
|
|
nullptr);
|
|
|
|
|
|
|
|
const unsigned char* aes_key =
|
|
|
|
reinterpret_cast<unsigned char*>(Buffer::Data(aes));
|
|
|
|
if (enc) {
|
|
|
|
EVP_EncryptInit_ex(ectx,
|
|
|
|
EVP_aes_128_cbc(),
|
|
|
|
nullptr,
|
|
|
|
aes_key,
|
|
|
|
iv);
|
|
|
|
} else {
|
|
|
|
EVP_DecryptInit_ex(ectx,
|
|
|
|
EVP_aes_128_cbc(),
|
|
|
|
nullptr,
|
|
|
|
aes_key,
|
|
|
|
iv);
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::CtxGetter(Local<String> property,
|
|
|
|
const PropertyCallbackInfo<Value>& info) {
|
|
|
|
HandleScope scope(info.GetIsolate());
|
|
|
|
|
|
|
|
SSL_CTX* ctx = Unwrap<SecureContext>(info.Holder())->ctx_;
|
|
|
|
Local<External> ext = External::New(info.GetIsolate(), ctx);
|
|
|
|
info.GetReturnValue().Set(ext);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <bool primary>
|
|
|
|
void SecureContext::GetCertificate(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
SecureContext* wrap = Unwrap<SecureContext>(args.Holder());
|
|
|
|
Environment* env = wrap->env();
|
|
|
|
X509* cert;
|
|
|
|
|
|
|
|
if (primary)
|
|
|
|
cert = wrap->cert_;
|
|
|
|
else
|
|
|
|
cert = wrap->issuer_;
|
|
|
|
if (cert == nullptr)
|
|
|
|
return args.GetReturnValue().Set(Null(env->isolate()));
|
|
|
|
|
|
|
|
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->SetProtoMethod(t, "getPeerCertificate", GetPeerCertificate);
|
|
|
|
env->SetProtoMethod(t, "getSession", GetSession);
|
|
|
|
env->SetProtoMethod(t, "setSession", SetSession);
|
|
|
|
env->SetProtoMethod(t, "loadSession", LoadSession);
|
|
|
|
env->SetProtoMethod(t, "isSessionReused", IsSessionReused);
|
|
|
|
env->SetProtoMethod(t, "isInitFinished", IsInitFinished);
|
|
|
|
env->SetProtoMethod(t, "verifyError", VerifyError);
|
|
|
|
env->SetProtoMethod(t, "getCurrentCipher", GetCurrentCipher);
|
|
|
|
env->SetProtoMethod(t, "endParser", EndParser);
|
|
|
|
env->SetProtoMethod(t, "certCbDone", CertCbDone);
|
|
|
|
env->SetProtoMethod(t, "renegotiate", Renegotiate);
|
stream_base: introduce StreamBase
StreamBase is an improved way to write C++ streams. The class itself is
for separting `StreamWrap` (with the methods like `.writeAsciiString`,
`.writeBuffer`, `.writev`, etc) from the `HandleWrap` class, making
possible to write abstract C++ streams that are not bound to any uv
socket.
The following methods are important part of the abstraction (which
mimics libuv's stream API):
* Events:
* `OnAlloc(size_t size, uv_buf_t*)`
* `OnRead(ssize_t nread, const uv_buf_t*, uv_handle_type pending)`
* `OnAfterWrite(WriteWrap*)`
* Wrappers:
* `DoShutdown(ShutdownWrap*)`
* `DoTryWrite(uv_buf_t** bufs, size_t* count)`
* `DoWrite(WriteWrap*, uv_buf_t*, size_t count, uv_stream_t* handle)`
* `Error()`
* `ClearError()`
The implementation should provide all of these methods, thus providing
the access to the underlying resource (be it uv handle, TLS socket, or
anything else).
A C++ stream may consume the input of another stream by replacing the
event callbacks and proxying the writes. This kind of API is actually
used now for the TLSWrap implementation, making it possible to wrap TLS
stream into another TLS stream. Thus legacy API calls are no longer
required in `_tls_wrap.js`.
PR-URL: https://github.com/iojs/io.js/pull/840
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
10 years ago
|
|
|
env->SetProtoMethod(t, "shutdownSSL", Shutdown);
|
|
|
|
env->SetProtoMethod(t, "getTLSTicket", GetTLSTicket);
|
|
|
|
env->SetProtoMethod(t, "newSessionDone", NewSessionDone);
|
|
|
|
env->SetProtoMethod(t, "setOCSPResponse", SetOCSPResponse);
|
|
|
|
env->SetProtoMethod(t, "requestOCSP", RequestOCSP);
|
|
|
|
env->SetProtoMethod(t, "getEphemeralKeyInfo", GetEphemeralKeyInfo);
|
|
|
|
env->SetProtoMethod(t, "getProtocol", GetProtocol);
|
|
|
|
|
|
|
|
#ifdef SSL_set_max_send_fragment
|
|
|
|
env->SetProtoMethod(t, "setMaxSendFragment", SetMaxSendFragment);
|
|
|
|
#endif // SSL_set_max_send_fragment
|
|
|
|
|
|
|
|
#ifdef OPENSSL_NPN_NEGOTIATED
|
|
|
|
env->SetProtoMethod(t, "getNegotiatedProtocol", GetNegotiatedProto);
|
|
|
|
#endif // OPENSSL_NPN_NEGOTIATED
|
|
|
|
|
|
|
|
#ifdef OPENSSL_NPN_NEGOTIATED
|
|
|
|
env->SetProtoMethod(t, "setNPNProtocols", SetNPNProtocols);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
env->SetProtoMethod(t, "getALPNNegotiatedProtocol", GetALPNNegotiatedProto);
|
|
|
|
env->SetProtoMethod(t, "setALPNProtocols", SetALPNProtocols);
|
|
|
|
|
|
|
|
t->PrototypeTemplate()->SetAccessor(
|
|
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "_external"),
|
|
|
|
SSLGetter,
|
|
|
|
nullptr,
|
|
|
|
env->as_external(),
|
|
|
|
DEFAULT,
|
|
|
|
static_cast<PropertyAttribute>(ReadOnly | DontDelete));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::InitNPN(SecureContext* sc) {
|
|
|
|
#ifdef OPENSSL_NPN_NEGOTIATED
|
|
|
|
// Server should advertise NPN protocols
|
|
|
|
SSL_CTX_set_next_protos_advertised_cb(sc->ctx_,
|
|
|
|
AdvertiseNextProtoCallback,
|
|
|
|
nullptr);
|
|
|
|
// Client should select protocol from list of advertised
|
|
|
|
// If server supports NPN
|
|
|
|
SSL_CTX_set_next_proto_select_cb(sc->ctx_, SelectNextProtoCallback, nullptr);
|
|
|
|
#endif // OPENSSL_NPN_NEGOTIATED
|
|
|
|
|
|
|
|
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
|
|
|
|
// OCSP stapling
|
|
|
|
SSL_CTX_set_tlsext_status_cb(sc->ctx_, TLSExtStatusCallback);
|
|
|
|
SSL_CTX_set_tlsext_status_arg(sc->ctx_, nullptr);
|
|
|
|
#endif // NODE__HAVE_TLSEXT_STATUS_CB
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
SSL_SESSION* SSLWrap<Base>::GetSessionCallback(SSL* s,
|
|
|
|
unsigned char* key,
|
|
|
|
int len,
|
|
|
|
int* copy) {
|
|
|
|
Base* w = static_cast<Base*>(SSL_get_app_data(s));
|
|
|
|
|
|
|
|
*copy = 0;
|
|
|
|
SSL_SESSION* sess = w->next_sess_;
|
|
|
|
w->next_sess_ = nullptr;
|
|
|
|
|
|
|
|
return sess;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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> buff = Buffer::New(env, size).ToLocalChecked();
|
|
|
|
unsigned char* serialized = reinterpret_cast<unsigned char*>(
|
|
|
|
Buffer::Data(buff));
|
|
|
|
memset(serialized, 0, size);
|
|
|
|
i2d_SSL_SESSION(sess, &serialized);
|
|
|
|
|
|
|
|
Local<Object> session = Buffer::Copy(
|
|
|
|
env,
|
|
|
|
reinterpret_cast<char*>(sess->session_id),
|
|
|
|
sess->session_id_length).ToLocalChecked();
|
|
|
|
Local<Value> argv[] = { session, buff };
|
|
|
|
w->new_session_wait_ = true;
|
|
|
|
w->MakeCallback(env->onnewsession_string(), ARRAY_SIZE(argv), argv);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
Context::Scope context_scope(env->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(env->session_id_string(), buff);
|
|
|
|
if (hello.servername() == nullptr) {
|
|
|
|
hello_obj->Set(env->servername_string(), String::Empty(env->isolate()));
|
|
|
|
} else {
|
|
|
|
Local<String> servername = OneByteString(env->isolate(),
|
|
|
|
hello.servername(),
|
|
|
|
hello.servername_size());
|
|
|
|
hello_obj->Set(env->servername_string(), servername);
|
|
|
|
}
|
|
|
|
hello_obj->Set(env->tls_ticket_string(),
|
|
|
|
Boolean::New(env->isolate(), hello.has_ticket()));
|
|
|
|
hello_obj->Set(env->ocsp_request_string(),
|
|
|
|
Boolean::New(env->isolate(), hello.ocsp_request()));
|
|
|
|
|
|
|
|
Local<Value> argv[] = { hello_obj };
|
|
|
|
w->MakeCallback(env->onclienthello_string(), ARRAY_SIZE(argv), argv);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static bool SafeX509ExtPrint(BIO* out, X509_EXTENSION* ext) {
|
|
|
|
const X509V3_EXT_METHOD* method = X509V3_EXT_get(ext);
|
|
|
|
|
|
|
|
if (method != X509V3_EXT_get_nid(NID_subject_alt_name))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const unsigned char* p = ext->value->data;
|
|
|
|
GENERAL_NAMES* names = reinterpret_cast<GENERAL_NAMES*>(ASN1_item_d2i(
|
|
|
|
NULL,
|
|
|
|
&p,
|
|
|
|
ext->value->length,
|
|
|
|
ASN1_ITEM_ptr(method->it)));
|
|
|
|
if (names == NULL)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for (int i = 0; i < sk_GENERAL_NAME_num(names); i++) {
|
|
|
|
GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i);
|
|
|
|
|
|
|
|
if (i != 0)
|
|
|
|
BIO_write(out, ", ", 2);
|
|
|
|
|
|
|
|
if (gen->type == GEN_DNS) {
|
|
|
|
ASN1_IA5STRING* name = gen->d.dNSName;
|
|
|
|
|
|
|
|
BIO_write(out, "DNS:", 4);
|
|
|
|
BIO_write(out, name->data, name->length);
|
|
|
|
} else {
|
|
|
|
STACK_OF(CONF_VALUE)* nval = i2v_GENERAL_NAME(
|
|
|
|
const_cast<X509V3_EXT_METHOD*>(method), gen, NULL);
|
|
|
|
if (nval == NULL)
|
|
|
|
return false;
|
|
|
|
X509V3_EXT_val_prn(out, nval, 0, 0);
|
|
|
|
sk_CONF_VALUE_pop_free(nval, X509V3_conf_free);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static Local<Object> X509ToObject(Environment* env, X509* cert) {
|
|
|
|
EscapableHandleScope scope(env->isolate());
|
|
|
|
|
|
|
|
Local<Object> info = Object::New(env->isolate());
|
|
|
|
|
|
|
|
BIO* bio = BIO_new(BIO_s_mem());
|
|
|
|
BUF_MEM* mem;
|
|
|
|
if (X509_NAME_print_ex(bio,
|
|
|
|
X509_get_subject_name(cert),
|
|
|
|
0,
|
|
|
|
X509_NAME_FLAGS) > 0) {
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(env->subject_string(),
|
|
|
|
String::NewFromUtf8(env->isolate(), mem->data,
|
|
|
|
String::kNormalString, mem->length));
|
|
|
|
}
|
|
|
|
(void) BIO_reset(bio);
|
|
|
|
|
|
|
|
X509_NAME* issuer_name = X509_get_issuer_name(cert);
|
|
|
|
if (X509_NAME_print_ex(bio, issuer_name, 0, X509_NAME_FLAGS) > 0) {
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(env->issuer_string(),
|
|
|
|
String::NewFromUtf8(env->isolate(), mem->data,
|
|
|
|
String::kNormalString, mem->length));
|
|
|
|
}
|
|
|
|
(void) BIO_reset(bio);
|
|
|
|
|
|
|
|
int nids[] = { NID_subject_alt_name, NID_info_access };
|
|
|
|
Local<String> keys[] = { env->subjectaltname_string(),
|
|
|
|
env->infoaccess_string() };
|
|
|
|
CHECK_EQ(ARRAY_SIZE(nids), ARRAY_SIZE(keys));
|
|
|
|
for (unsigned int i = 0; i < ARRAY_SIZE(nids); i++) {
|
|
|
|
int index = X509_get_ext_by_NID(cert, nids[i], -1);
|
|
|
|
if (index < 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
X509_EXTENSION* ext;
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
ext = X509_get_ext(cert, index);
|
|
|
|
CHECK_NE(ext, nullptr);
|
|
|
|
|
|
|
|
if (!SafeX509ExtPrint(bio, ext)) {
|
|
|
|
rv = X509V3_EXT_print(bio, ext, 0, 0);
|
|
|
|
CHECK_EQ(rv, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(keys[i],
|
|
|
|
String::NewFromUtf8(env->isolate(), mem->data,
|
|
|
|
String::kNormalString, mem->length));
|
|
|
|
|
|
|
|
(void) BIO_reset(bio);
|
|
|
|
}
|
|
|
|
|
|
|
|
EVP_PKEY* pkey = X509_get_pubkey(cert);
|
|
|
|
RSA* rsa = nullptr;
|
|
|
|
if (pkey != nullptr)
|
|
|
|
rsa = EVP_PKEY_get1_RSA(pkey);
|
|
|
|
|
|
|
|
if (rsa != nullptr) {
|
|
|
|
BN_print(bio, rsa->n);
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(env->modulus_string(),
|
|
|
|
String::NewFromUtf8(env->isolate(), mem->data,
|
|
|
|
String::kNormalString, mem->length));
|
|
|
|
(void) BIO_reset(bio);
|
|
|
|
|
|
|
|
unsigned long exponent_word = BN_get_word(rsa->e);
|
|
|
|
BIO_printf(bio, "0x%lx", exponent_word);
|
|
|
|
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(env->exponent_string(),
|
|
|
|
String::NewFromUtf8(env->isolate(), mem->data,
|
|
|
|
String::kNormalString, mem->length));
|
|
|
|
(void) BIO_reset(bio);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pkey != nullptr) {
|
|
|
|
EVP_PKEY_free(pkey);
|
|
|
|
pkey = nullptr;
|
|
|
|
}
|
|
|
|
if (rsa != nullptr) {
|
|
|
|
RSA_free(rsa);
|
|
|
|
rsa = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
ASN1_TIME_print(bio, X509_get_notBefore(cert));
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(env->valid_from_string(),
|
|
|
|
String::NewFromUtf8(env->isolate(), mem->data,
|
|
|
|
String::kNormalString, mem->length));
|
|
|
|
(void) BIO_reset(bio);
|
|
|
|
|
|
|
|
ASN1_TIME_print(bio, X509_get_notAfter(cert));
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(env->valid_to_string(),
|
|
|
|
String::NewFromUtf8(env->isolate(), mem->data,
|
|
|
|
String::kNormalString, mem->length));
|
|
|
|
BIO_free_all(bio);
|
|
|
|
|
|
|
|
unsigned int md_size, i;
|
|
|
|
unsigned char md[EVP_MAX_MD_SIZE];
|
|
|
|
if (X509_digest(cert, EVP_sha1(), md, &md_size)) {
|
|
|
|
const char hex[] = "0123456789ABCDEF";
|
|
|
|
char fingerprint[EVP_MAX_MD_SIZE * 3];
|
|
|
|
|
|
|
|
// TODO(indutny): Unify it with buffer's code
|
|
|
|
for (i = 0; i < md_size; i++) {
|
|
|
|
fingerprint[3*i] = hex[(md[i] & 0xf0) >> 4];
|
|
|
|
fingerprint[(3*i)+1] = hex[(md[i] & 0x0f)];
|
|
|
|
fingerprint[(3*i)+2] = ':';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (md_size > 0) {
|
|
|
|
fingerprint[(3*(md_size-1))+2] = '\0';
|
|
|
|
} else {
|
|
|
|
fingerprint[0] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
info->Set(env->fingerprint_string(),
|
|
|
|
OneByteString(env->isolate(), fingerprint));
|
|
|
|
}
|
|
|
|
|
|
|
|
STACK_OF(ASN1_OBJECT)* eku = static_cast<STACK_OF(ASN1_OBJECT)*>(
|
|
|
|
X509_get_ext_d2i(cert, NID_ext_key_usage, nullptr, nullptr));
|
|
|
|
if (eku != nullptr) {
|
|
|
|
Local<Array> ext_key_usage = Array::New(env->isolate());
|
|
|
|
char buf[256];
|
|
|
|
|
|
|
|
int j = 0;
|
|
|
|
for (int i = 0; i < sk_ASN1_OBJECT_num(eku); i++) {
|
|
|
|
if (OBJ_obj2txt(buf, sizeof(buf), sk_ASN1_OBJECT_value(eku, i), 1) >= 0)
|
|
|
|
ext_key_usage->Set(j++, OneByteString(env->isolate(), buf));
|
|
|
|
}
|
|
|
|
|
|
|
|
sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free);
|
|
|
|
info->Set(env->ext_key_usage_string(), ext_key_usage);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ASN1_INTEGER* serial_number = X509_get_serialNumber(cert)) {
|
|
|
|
if (BIGNUM* bn = ASN1_INTEGER_to_BN(serial_number, nullptr)) {
|
|
|
|
if (char* buf = BN_bn2hex(bn)) {
|
|
|
|
info->Set(env->serial_number_string(),
|
|
|
|
OneByteString(env->isolate(), buf));
|
|
|
|
OPENSSL_free(buf);
|
|
|
|
}
|
|
|
|
BN_free(bn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Raw DER certificate
|
|
|
|
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);
|
|
|
|
info->Set(env->raw_string(), buff);
|
|
|
|
|
|
|
|
return scope.Escape(info);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO(indutny): Split it into multiple smaller functions
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::GetPeerCertificate(
|
|
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
Environment* env = w->ssl_env();
|
|
|
|
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence unused variable warning.
|
|
|
|
|
|
|
|
Local<Object> result;
|
|
|
|
Local<Object> info;
|
|
|
|
|
|
|
|
// NOTE: This is because of the odd OpenSSL behavior. On client `cert_chain`
|
|
|
|
// contains the `peer_certificate`, but on server it doesn't
|
|
|
|
X509* cert = w->is_server() ? SSL_get_peer_certificate(w->ssl_) : nullptr;
|
|
|
|
STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(w->ssl_);
|
|
|
|
STACK_OF(X509)* peer_certs = nullptr;
|
|
|
|
if (cert == nullptr && ssl_certs == nullptr)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
if (cert == nullptr && sk_X509_num(ssl_certs) == 0)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
// Short result requested
|
|
|
|
if (args.Length() < 1 || !args[0]->IsTrue()) {
|
|
|
|
result = X509ToObject(env,
|
|
|
|
cert == nullptr ? sk_X509_value(ssl_certs, 0) : cert);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clone `ssl_certs`, because we are going to destruct it
|
|
|
|
peer_certs = sk_X509_new(nullptr);
|
|
|
|
if (cert != nullptr)
|
|
|
|
sk_X509_push(peer_certs, cert);
|
|
|
|
for (int i = 0; i < sk_X509_num(ssl_certs); i++) {
|
|
|
|
cert = X509_dup(sk_X509_value(ssl_certs, i));
|
|
|
|
if (cert == nullptr)
|
|
|
|
goto done;
|
|
|
|
if (!sk_X509_push(peer_certs, cert))
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
// First and main certificate
|
|
|
|
cert = sk_X509_value(peer_certs, 0);
|
|
|
|
result = X509ToObject(env, cert);
|
|
|
|
info = result;
|
|
|
|
|
|
|
|
// Put issuer inside the object
|
|
|
|
cert = sk_X509_delete(peer_certs, 0);
|
|
|
|
while (sk_X509_num(peer_certs) > 0) {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < sk_X509_num(peer_certs); i++) {
|
|
|
|
X509* ca = sk_X509_value(peer_certs, i);
|
|
|
|
if (X509_check_issued(ca, cert) != X509_V_OK)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
Local<Object> ca_info = X509ToObject(env, ca);
|
|
|
|
info->Set(env->issuercert_string(), ca_info);
|
|
|
|
info = ca_info;
|
|
|
|
|
|
|
|
// NOTE: Intentionally freeing cert that is not used anymore
|
|
|
|
X509_free(cert);
|
|
|
|
|
|
|
|
// Delete cert and continue aggregating issuers
|
|
|
|
cert = sk_X509_delete(peer_certs, i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Issuer not found, break out of the loop
|
|
|
|
if (i == sk_X509_num(peer_certs))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Last certificate should be self-signed
|
|
|
|
while (X509_check_issued(cert, cert) != X509_V_OK) {
|
|
|
|
X509* ca;
|
|
|
|
if (SSL_CTX_get_issuer(w->ssl_->ctx, cert, &ca) <= 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
Local<Object> ca_info = X509ToObject(env, ca);
|
|
|
|
info->Set(env->issuercert_string(), ca_info);
|
|
|
|
info = ca_info;
|
|
|
|
|
|
|
|
// NOTE: Intentionally freeing cert that is not used anymore
|
|
|
|
X509_free(cert);
|
|
|
|
|
|
|
|
// Delete cert and continue aggregating issuers
|
|
|
|
cert = ca;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Self-issued certificate
|
|
|
|
if (X509_check_issued(cert, cert) == X509_V_OK)
|
|
|
|
info->Set(env->issuercert_string(), info);
|
|
|
|
|
|
|
|
CHECK_NE(cert, nullptr);
|
|
|
|
|
|
|
|
done:
|
|
|
|
if (cert != nullptr)
|
|
|
|
X509_free(cert);
|
|
|
|
if (peer_certs != nullptr)
|
|
|
|
sk_X509_pop_free(peer_certs, X509_free);
|
|
|
|
if (result.IsEmpty())
|
|
|
|
result = Object::New(env->isolate());
|
|
|
|
args.GetReturnValue().Set(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::GetSession(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
|
|
|
|
SSL_SESSION* sess = SSL_get_session(w->ssl_);
|
|
|
|
if (sess == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int slen = i2d_SSL_SESSION(sess, nullptr);
|
|
|
|
CHECK_GT(slen, 0);
|
|
|
|
|
|
|
|
char* sbuf = new char[slen];
|
|
|
|
unsigned char* p = reinterpret_cast<unsigned char*>(sbuf);
|
|
|
|
i2d_SSL_SESSION(sess, &p);
|
|
|
|
args.GetReturnValue().Set(Encode(env->isolate(), sbuf, slen, BUFFER));
|
|
|
|
delete[] sbuf;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::SetSession(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
|
|
|
|
if (args.Length() < 1 ||
|
|
|
|
(!args[0]->IsString() && !Buffer::HasInstance(args[0]))) {
|
|
|
|
return env->ThrowTypeError("Bad argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
size_t slen = Buffer::Length(args[0]);
|
|
|
|
char* sbuf = new char[slen];
|
|
|
|
memcpy(sbuf, Buffer::Data(args[0]), slen);
|
|
|
|
|
|
|
|
const unsigned char* p = reinterpret_cast<const unsigned char*>(sbuf);
|
|
|
|
SSL_SESSION* sess = d2i_SSL_SESSION(nullptr, &p, slen);
|
|
|
|
|
|
|
|
delete[] sbuf;
|
|
|
|
|
|
|
|
if (sess == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int r = SSL_set_session(w->ssl_, sess);
|
|
|
|
SSL_SESSION_free(sess);
|
|
|
|
|
|
|
|
if (!r)
|
|
|
|
return env->ThrowError("SSL_set_session error");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::LoadSession(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
Environment* env = w->ssl_env();
|
|
|
|
|
|
|
|
if (args.Length() >= 1 && Buffer::HasInstance(args[0])) {
|
|
|
|
ssize_t slen = Buffer::Length(args[0]);
|
|
|
|
char* sbuf = Buffer::Data(args[0]);
|
|
|
|
|
|
|
|
const unsigned char* p = reinterpret_cast<unsigned char*>(sbuf);
|
|
|
|
SSL_SESSION* sess = d2i_SSL_SESSION(nullptr, &p, slen);
|
|
|
|
|
|
|
|
// Setup next session and move hello to the BIO buffer
|
|
|
|
if (w->next_sess_ != nullptr)
|
|
|
|
SSL_SESSION_free(w->next_sess_);
|
|
|
|
w->next_sess_ = sess;
|
|
|
|
|
|
|
|
Local<Object> info = Object::New(env->isolate());
|
|
|
|
#ifndef OPENSSL_NO_TLSEXT
|
|
|
|
if (sess->tlsext_hostname == nullptr) {
|
|
|
|
info->Set(env->servername_string(), False(args.GetIsolate()));
|
|
|
|
} else {
|
|
|
|
info->Set(env->servername_string(),
|
|
|
|
OneByteString(args.GetIsolate(), sess->tlsext_hostname));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
args.GetReturnValue().Set(info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::IsSessionReused(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
bool yes = SSL_session_reused(w->ssl_);
|
|
|
|
args.GetReturnValue().Set(yes);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::EndParser(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
w->hello_parser_.End();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::Renegotiate(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence unused variable warning.
|
|
|
|
|
|
|
|
bool yes = SSL_renegotiate(w->ssl_) == 1;
|
|
|
|
args.GetReturnValue().Set(yes);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::Shutdown(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
|
|
|
|
int rv = SSL_shutdown(w->ssl_);
|
|
|
|
args.GetReturnValue().Set(rv);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::GetTLSTicket(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
Environment* env = w->ssl_env();
|
|
|
|
|
|
|
|
SSL_SESSION* sess = SSL_get_session(w->ssl_);
|
|
|
|
if (sess == nullptr || sess->tlsext_tick == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Local<Object> buff = Buffer::Copy(
|
|
|
|
env,
|
|
|
|
reinterpret_cast<char*>(sess->tlsext_tick),
|
|
|
|
sess->tlsext_ticklen).ToLocalChecked();
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(buff);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::NewSessionDone(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
w->new_session_wait_ = false;
|
|
|
|
w->NewSessionDoneCb();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::SetOCSPResponse(
|
|
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
|
|
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
|
|
|
|
HandleScope scope(args.GetIsolate());
|
|
|
|
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
if (args.Length() < 1 || !Buffer::HasInstance(args[0]))
|
|
|
|
return w->env()->ThrowTypeError("Must give a Buffer as first argument");
|
|
|
|
|
|
|
|
w->ocsp_response_.Reset(args.GetIsolate(), args[0].As<Object>());
|
|
|
|
#endif // NODE__HAVE_TLSEXT_STATUS_CB
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::RequestOCSP(
|
|
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
|
|
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
|
|
|
|
HandleScope scope(args.GetIsolate());
|
|
|
|
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
|
|
|
|
SSL_set_tlsext_status_type(w->ssl_, TLSEXT_STATUSTYPE_ocsp);
|
|
|
|
#endif // NODE__HAVE_TLSEXT_STATUS_CB
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::GetEphemeralKeyInfo(
|
|
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
CHECK_NE(w->ssl_, nullptr);
|
|
|
|
|
|
|
|
// tmp key is available on only client
|
|
|
|
if (w->is_server())
|
|
|
|
return args.GetReturnValue().SetNull();
|
|
|
|
|
|
|
|
Local<Object> info = Object::New(env->isolate());
|
|
|
|
|
|
|
|
EVP_PKEY* key;
|
|
|
|
|
|
|
|
if (SSL_get_server_tmp_key(w->ssl_, &key)) {
|
|
|
|
switch (EVP_PKEY_id(key)) {
|
|
|
|
case EVP_PKEY_DH:
|
|
|
|
info->Set(env->type_string(),
|
|
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "DH"));
|
|
|
|
info->Set(env->size_string(),
|
|
|
|
Integer::New(env->isolate(), EVP_PKEY_bits(key)));
|
|
|
|
break;
|
|
|
|
case EVP_PKEY_EC:
|
|
|
|
{
|
|
|
|
EC_KEY* ec = EVP_PKEY_get1_EC_KEY(key);
|
|
|
|
int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
|
|
|
|
EC_KEY_free(ec);
|
|
|
|
info->Set(env->type_string(),
|
|
|
|
FIXED_ONE_BYTE_STRING(env->isolate(), "ECDH"));
|
|
|
|
info->Set(env->name_string(),
|
|
|
|
OneByteString(args.GetIsolate(), OBJ_nid2sn(nid)));
|
|
|
|
info->Set(env->size_string(),
|
|
|
|
Integer::New(env->isolate(), EVP_PKEY_bits(key)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EVP_PKEY_free(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
return args.GetReturnValue().Set(info);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SSL_set_max_send_fragment
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::SetMaxSendFragment(
|
|
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
|
|
HandleScope scope(args.GetIsolate());
|
|
|
|
CHECK(args.Length() >= 1 && args[0]->IsNumber());
|
|
|
|
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
|
|
|
|
int rv = SSL_set_max_send_fragment(w->ssl_, args[0]->Int32Value());
|
|
|
|
args.GetReturnValue().Set(rv);
|
|
|
|
}
|
|
|
|
#endif // SSL_set_max_send_fragment
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::IsInitFinished(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
bool yes = SSL_is_init_finished(w->ssl_);
|
|
|
|
args.GetReturnValue().Set(yes);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::VerifyError(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(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 = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT;
|
|
|
|
if (X509* peer_cert = SSL_get_peer_certificate(w->ssl_)) {
|
|
|
|
X509_free(peer_cert);
|
|
|
|
x509_verify_error = SSL_get_verify_result(w->ssl_);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x509_verify_error == X509_V_OK)
|
|
|
|
return args.GetReturnValue().SetNull();
|
|
|
|
|
|
|
|
// XXX(bnoordhuis) X509_verify_cert_error_string() is not actually thread-safe
|
|
|
|
// in the presence of invalid error codes. Probably academical but something
|
|
|
|
// to keep in mind if/when node ever grows multi-isolate capabilities.
|
|
|
|
const char* reason = X509_verify_cert_error_string(x509_verify_error);
|
|
|
|
const char* code = reason;
|
|
|
|
#define CASE_X509_ERR(CODE) case X509_V_ERR_##CODE: code = #CODE; break;
|
|
|
|
switch (x509_verify_error) {
|
|
|
|
CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT)
|
|
|
|
CASE_X509_ERR(UNABLE_TO_GET_CRL)
|
|
|
|
CASE_X509_ERR(UNABLE_TO_DECRYPT_CERT_SIGNATURE)
|
|
|
|
CASE_X509_ERR(UNABLE_TO_DECRYPT_CRL_SIGNATURE)
|
|
|
|
CASE_X509_ERR(UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY)
|
|
|
|
CASE_X509_ERR(CERT_SIGNATURE_FAILURE)
|
|
|
|
CASE_X509_ERR(CRL_SIGNATURE_FAILURE)
|
|
|
|
CASE_X509_ERR(CERT_NOT_YET_VALID)
|
|
|
|
CASE_X509_ERR(CERT_HAS_EXPIRED)
|
|
|
|
CASE_X509_ERR(CRL_NOT_YET_VALID)
|
|
|
|
CASE_X509_ERR(CRL_HAS_EXPIRED)
|
|
|
|
CASE_X509_ERR(ERROR_IN_CERT_NOT_BEFORE_FIELD)
|
|
|
|
CASE_X509_ERR(ERROR_IN_CERT_NOT_AFTER_FIELD)
|
|
|
|
CASE_X509_ERR(ERROR_IN_CRL_LAST_UPDATE_FIELD)
|
|
|
|
CASE_X509_ERR(ERROR_IN_CRL_NEXT_UPDATE_FIELD)
|
|
|
|
CASE_X509_ERR(OUT_OF_MEM)
|
|
|
|
CASE_X509_ERR(DEPTH_ZERO_SELF_SIGNED_CERT)
|
|
|
|
CASE_X509_ERR(SELF_SIGNED_CERT_IN_CHAIN)
|
|
|
|
CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT_LOCALLY)
|
|
|
|
CASE_X509_ERR(UNABLE_TO_VERIFY_LEAF_SIGNATURE)
|
|
|
|
CASE_X509_ERR(CERT_CHAIN_TOO_LONG)
|
|
|
|
CASE_X509_ERR(CERT_REVOKED)
|
|
|
|
CASE_X509_ERR(INVALID_CA)
|
|
|
|
CASE_X509_ERR(PATH_LENGTH_EXCEEDED)
|
|
|
|
CASE_X509_ERR(INVALID_PURPOSE)
|
|
|
|
CASE_X509_ERR(CERT_UNTRUSTED)
|
|
|
|
CASE_X509_ERR(CERT_REJECTED)
|
|
|
|
}
|
|
|
|
#undef CASE_X509_ERR
|
|
|
|
|
|
|
|
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);
|
|
|
|
exception_object->Set(FIXED_ONE_BYTE_STRING(isolate, "code"),
|
|
|
|
OneByteString(isolate, code));
|
|
|
|
args.GetReturnValue().Set(exception_object);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::GetCurrentCipher(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
Environment* env = w->ssl_env();
|
|
|
|
|
|
|
|
OPENSSL_CONST SSL_CIPHER* c = SSL_get_current_cipher(w->ssl_);
|
|
|
|
if (c == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Local<Object> info = Object::New(env->isolate());
|
|
|
|
const char* cipher_name = SSL_CIPHER_get_name(c);
|
|
|
|
info->Set(env->name_string(), OneByteString(args.GetIsolate(), cipher_name));
|
|
|
|
const char* cipher_version = SSL_CIPHER_get_version(c);
|
|
|
|
info->Set(env->version_string(),
|
|
|
|
OneByteString(args.GetIsolate(), cipher_version));
|
|
|
|
args.GetReturnValue().Set(info);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::GetProtocol(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
|
|
|
|
const char* tls_version = SSL_get_version(w->ssl_);
|
|
|
|
args.GetReturnValue().Set(OneByteString(args.GetIsolate(), tls_version));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef OPENSSL_NPN_NEGOTIATED
|
|
|
|
template <class Base>
|
|
|
|
int SSLWrap<Base>::AdvertiseNextProtoCallback(SSL* s,
|
|
|
|
const unsigned char** data,
|
|
|
|
unsigned int* len,
|
|
|
|
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());
|
|
|
|
|
|
|
|
auto npn_buffer =
|
|
|
|
w->object()->GetPrivate(
|
|
|
|
env->context(),
|
|
|
|
env->npn_buffer_private_symbol()).ToLocalChecked();
|
|
|
|
|
|
|
|
if (npn_buffer->IsUndefined()) {
|
|
|
|
// No initialization - no NPN protocols
|
|
|
|
*data = reinterpret_cast<const unsigned char*>("");
|
|
|
|
*len = 0;
|
|
|
|
} else {
|
|
|
|
CHECK(Buffer::HasInstance(npn_buffer));
|
|
|
|
*data = reinterpret_cast<const unsigned char*>(Buffer::Data(npn_buffer));
|
|
|
|
*len = Buffer::Length(npn_buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
int SSLWrap<Base>::SelectNextProtoCallback(SSL* s,
|
|
|
|
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());
|
|
|
|
|
|
|
|
auto npn_buffer =
|
|
|
|
w->object()->GetPrivate(
|
|
|
|
env->context(),
|
|
|
|
env->npn_buffer_private_symbol()).ToLocalChecked();
|
|
|
|
|
|
|
|
if (npn_buffer->IsUndefined()) {
|
|
|
|
// We should at least select one protocol
|
|
|
|
// If server is using NPN
|
|
|
|
*out = reinterpret_cast<unsigned char*>(const_cast<char*>("http/1.1"));
|
|
|
|
*outlen = 8;
|
|
|
|
|
|
|
|
// set status: unsupported
|
|
|
|
CHECK(
|
|
|
|
w->object()->SetPrivate(
|
|
|
|
env->context(),
|
|
|
|
env->selected_npn_buffer_private_symbol(),
|
|
|
|
False(env->isolate())).FromJust());
|
|
|
|
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
CHECK(Buffer::HasInstance(npn_buffer));
|
|
|
|
const unsigned char* npn_protos =
|
|
|
|
reinterpret_cast<const unsigned char*>(Buffer::Data(npn_buffer));
|
|
|
|
size_t len = Buffer::Length(npn_buffer);
|
|
|
|
|
|
|
|
int status = SSL_select_next_proto(out, outlen, in, inlen, npn_protos, len);
|
|
|
|
Local<Value> result;
|
|
|
|
switch (status) {
|
|
|
|
case OPENSSL_NPN_UNSUPPORTED:
|
|
|
|
result = Null(env->isolate());
|
|
|
|
break;
|
|
|
|
case OPENSSL_NPN_NEGOTIATED:
|
|
|
|
result = OneByteString(env->isolate(), *out, *outlen);
|
|
|
|
break;
|
|
|
|
case OPENSSL_NPN_NO_OVERLAP:
|
|
|
|
result = False(env->isolate());
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
CHECK(
|
|
|
|
w->object()->SetPrivate(
|
|
|
|
env->context(),
|
|
|
|
env->selected_npn_buffer_private_symbol(),
|
|
|
|
result).FromJust());
|
|
|
|
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::GetNegotiatedProto(
|
|
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
Environment* env = w->env();
|
|
|
|
|
|
|
|
if (w->is_client()) {
|
|
|
|
auto selected_npn_buffer =
|
|
|
|
w->object()->GetPrivate(
|
|
|
|
env->context(),
|
|
|
|
env->selected_npn_buffer_private_symbol()).ToLocalChecked();
|
|
|
|
args.GetReturnValue().Set(selected_npn_buffer);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const unsigned char* npn_proto;
|
|
|
|
unsigned int npn_proto_len;
|
|
|
|
|
|
|
|
SSL_get0_next_proto_negotiated(w->ssl_, &npn_proto, &npn_proto_len);
|
|
|
|
|
|
|
|
if (!npn_proto)
|
|
|
|
return args.GetReturnValue().Set(false);
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(
|
|
|
|
OneByteString(args.GetIsolate(), npn_proto, npn_proto_len));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::SetNPNProtocols(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
Environment* env = w->env();
|
|
|
|
|
|
|
|
if (args.Length() < 1 || !Buffer::HasInstance(args[0]))
|
|
|
|
return env->ThrowTypeError("Must give a Buffer as first argument");
|
|
|
|
|
|
|
|
CHECK(
|
|
|
|
w->object()->SetPrivate(
|
|
|
|
env->context(),
|
|
|
|
env->npn_buffer_private_symbol(),
|
|
|
|
args[0]).FromJust());
|
|
|
|
}
|
|
|
|
#endif // OPENSSL_NPN_NEGOTIATED
|
|
|
|
|
|
|
|
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
|
|
|
|
typedef struct tlsextalpnctx_st {
|
|
|
|
unsigned char* data;
|
|
|
|
unsigned short len;
|
|
|
|
} tlsextalpnctx;
|
|
|
|
|
|
|
|
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();
|
|
|
|
CHECK(Buffer::HasInstance(alpn_buffer));
|
|
|
|
const unsigned char* alpn_protos =
|
|
|
|
reinterpret_cast<const unsigned char*>(Buffer::Data(alpn_buffer));
|
|
|
|
unsigned alpn_protos_len = Buffer::Length(alpn_buffer);
|
|
|
|
int status = SSL_select_next_proto(const_cast<unsigned char**>(out), outlen,
|
|
|
|
alpn_protos, alpn_protos_len, in, inlen);
|
|
|
|
|
|
|
|
switch (status) {
|
|
|
|
case OPENSSL_NPN_NO_OVERLAP:
|
|
|
|
// According to 3.2. Protocol Selection of RFC7301,
|
|
|
|
// fatal no_application_protocol alert shall be sent
|
|
|
|
// but current openssl does not support it yet. See
|
|
|
|
// https://rt.openssl.org/Ticket/Display.html?id=3463&user=guest&pass=guest
|
|
|
|
// Instead, we send a warning alert for now.
|
|
|
|
return SSL_TLSEXT_ERR_ALERT_WARNING;
|
|
|
|
case OPENSSL_NPN_NEGOTIATED:
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
default:
|
|
|
|
return SSL_TLSEXT_ERR_ALERT_FATAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // TLSEXT_TYPE_application_layer_protocol_negotiation
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::GetALPNNegotiatedProto(
|
|
|
|
const FunctionCallbackInfo<v8::Value>& args) {
|
|
|
|
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
|
|
|
|
HandleScope scope(args.GetIsolate());
|
|
|
|
Base* w = Unwrap<Base>(args.Holder());
|
|
|
|
|
|
|
|
const unsigned char* alpn_proto;
|
|
|
|
unsigned int alpn_proto_len;
|
|
|
|
|
|
|
|
SSL_get0_alpn_selected(w->ssl_, &alpn_proto, &alpn_proto_len);
|
|
|
|
|
|
|
|
if (!alpn_proto)
|
|
|
|
return args.GetReturnValue().Set(false);
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(
|
|
|
|
OneByteString(args.GetIsolate(), alpn_proto, alpn_proto_len));
|
|
|
|
#endif // TLSEXT_TYPE_application_layer_protocol_negotiation
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::SetALPNProtocols(
|
|
|
|
const FunctionCallbackInfo<v8::Value>& args) {
|
|
|
|
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
|
|
|
|
HandleScope scope(args.GetIsolate());
|
|
|
|
Base* w = Unwrap<Base>(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()) {
|
|
|
|
const unsigned char* alpn_protos =
|
|
|
|
reinterpret_cast<const unsigned char*>(Buffer::Data(args[0]));
|
|
|
|
unsigned alpn_protos_len = Buffer::Length(args[0]);
|
|
|
|
int r = SSL_set_alpn_protos(w->ssl_, alpn_protos, alpn_protos_len);
|
|
|
|
CHECK_EQ(r, 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(w->ssl_->ctx, SelectALPNCallback, nullptr);
|
|
|
|
}
|
|
|
|
#endif // TLSEXT_TYPE_application_layer_protocol_negotiation
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
|
|
|
|
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
|
|
|
|
const unsigned char* resp;
|
|
|
|
int len = SSL_get_tlsext_status_ocsp_resp(s, &resp);
|
|
|
|
Local<Value> arg;
|
|
|
|
if (resp == nullptr) {
|
|
|
|
arg = Null(env->isolate());
|
|
|
|
} else {
|
|
|
|
arg = Buffer::Copy(
|
|
|
|
env,
|
|
|
|
reinterpret_cast<char*>(const_cast<unsigned char*>(resp)),
|
|
|
|
len).ToLocalChecked();
|
|
|
|
}
|
|
|
|
|
|
|
|
w->MakeCallback(env->onocspresponse_string(), 1, &arg);
|
|
|
|
|
|
|
|
// Somehow, client is expecting different return value here
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
// Outgoing response
|
|
|
|
if (w->ocsp_response_.IsEmpty())
|
|
|
|
return SSL_TLSEXT_ERR_NOACK;
|
|
|
|
|
|
|
|
Local<Object> obj = PersistentToLocal(env->isolate(), w->ocsp_response_);
|
|
|
|
char* resp = Buffer::Data(obj);
|
|
|
|
size_t len = Buffer::Length(obj);
|
|
|
|
|
|
|
|
// OpenSSL takes control of the pointer after accepting it
|
|
|
|
char* data = reinterpret_cast<char*>(malloc(len));
|
|
|
|
CHECK_NE(data, nullptr);
|
|
|
|
memcpy(data, resp, len);
|
|
|
|
|
|
|
|
if (!SSL_set_tlsext_status_ocsp_resp(s, data, len))
|
|
|
|
free(data);
|
|
|
|
w->ocsp_response_.Reset();
|
|
|
|
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // NODE__HAVE_TLSEXT_STATUS_CB
|
|
|
|
|
|
|
|
|
|
|
|
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_)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
Environment* env = w->env();
|
|
|
|
HandleScope handle_scope(env->isolate());
|
|
|
|
Context::Scope context_scope(env->context());
|
|
|
|
w->cert_cb_running_ = true;
|
|
|
|
|
|
|
|
Local<Object> info = Object::New(env->isolate());
|
|
|
|
|
|
|
|
SSL_SESSION* sess = SSL_get_session(s);
|
|
|
|
if (sess != nullptr) {
|
|
|
|
if (sess->tlsext_hostname == nullptr) {
|
|
|
|
info->Set(env->servername_string(), String::Empty(env->isolate()));
|
|
|
|
} else {
|
|
|
|
Local<String> servername = OneByteString(env->isolate(),
|
|
|
|
sess->tlsext_hostname,
|
|
|
|
strlen(sess->tlsext_hostname));
|
|
|
|
info->Set(env->servername_string(), servername);
|
|
|
|
}
|
|
|
|
info->Set(env->tls_ticket_string(),
|
|
|
|
Boolean::New(env->isolate(), sess->tlsext_ticklen != 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ocsp = false;
|
|
|
|
#ifdef NODE__HAVE_TLSEXT_STATUS_CB
|
|
|
|
ocsp = s->tlsext_status_type == TLSEXT_STATUSTYPE_ocsp;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
info->Set(env->ocsp_request_string(), Boolean::New(env->isolate(), ocsp));
|
|
|
|
|
|
|
|
Local<Value> argv[] = { info };
|
|
|
|
w->MakeCallback(env->oncertcb_string(), ARRAY_SIZE(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 = Unwrap<Base>(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->sni_context_string());
|
|
|
|
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>());
|
|
|
|
w->sni_context_.Reset();
|
|
|
|
w->sni_context_.Reset(env->isolate(), ctx);
|
|
|
|
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
// NOTE: reference count is not increased by this API methods
|
|
|
|
X509* x509 = SSL_CTX_get0_certificate(sc->ctx_);
|
|
|
|
EVP_PKEY* pkey = SSL_CTX_get0_privatekey(sc->ctx_);
|
|
|
|
STACK_OF(X509)* chain;
|
|
|
|
|
|
|
|
rv = SSL_CTX_get0_chain_certs(sc->ctx_, &chain);
|
|
|
|
if (rv)
|
|
|
|
rv = SSL_use_certificate(w->ssl_, x509);
|
|
|
|
if (rv)
|
|
|
|
rv = SSL_use_PrivateKey(w->ssl_, pkey);
|
|
|
|
if (rv && chain != nullptr)
|
|
|
|
rv = SSL_set1_chain(w->ssl_, chain);
|
|
|
|
if (rv)
|
|
|
|
rv = w->SetCACerts(sc);
|
|
|
|
if (!rv) {
|
|
|
|
unsigned long err = ERR_get_error();
|
|
|
|
if (!err)
|
|
|
|
return env->ThrowError("CertCbDone");
|
|
|
|
return ThrowCryptoError(env, err);
|
|
|
|
}
|
|
|
|
} 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>::SSLGetter(Local<String> property,
|
|
|
|
const PropertyCallbackInfo<Value>& info) {
|
|
|
|
HandleScope scope(info.GetIsolate());
|
|
|
|
|
|
|
|
SSL* ssl = Unwrap<Base>(info.Holder())->ssl_;
|
|
|
|
Local<External> ext = External::New(info.GetIsolate(), ssl);
|
|
|
|
info.GetReturnValue().Set(ext);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::DestroySSL() {
|
|
|
|
if (ssl_ == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
SSL_free(ssl_);
|
|
|
|
env_->isolate()->AdjustAmountOfExternalAllocatedMemory(-kExternalSize);
|
|
|
|
ssl_ = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
void SSLWrap<Base>::SetSNIContext(SecureContext* sc) {
|
|
|
|
InitNPN(sc);
|
|
|
|
CHECK_EQ(SSL_set_SSL_CTX(ssl_, sc->ctx_), sc->ctx_);
|
|
|
|
|
|
|
|
SetCACerts(sc);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Base>
|
|
|
|
int SSLWrap<Base>::SetCACerts(SecureContext* sc) {
|
|
|
|
int err = SSL_set1_verify_cert_store(ssl_, SSL_CTX_get_cert_store(sc->ctx_));
|
|
|
|
if (err != 1)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
STACK_OF(X509_NAME)* list = SSL_dup_CA_list(
|
|
|
|
SSL_CTX_get_client_CA_list(sc->ctx_));
|
|
|
|
|
|
|
|
// NOTE: `SSL_set_client_CA_list` takes the ownership of `list`
|
|
|
|
SSL_set_client_CA_list(ssl_, list);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::OnClientHelloParseEnd(void* arg) {
|
|
|
|
Connection* conn = static_cast<Connection*>(arg);
|
|
|
|
|
|
|
|
// Write all accumulated data
|
|
|
|
int r = BIO_write(conn->bio_read_,
|
|
|
|
reinterpret_cast<char*>(conn->hello_data_),
|
|
|
|
conn->hello_offset_);
|
|
|
|
conn->HandleBIOError(conn->bio_read_, "BIO_write", r);
|
|
|
|
conn->SetShutdownFlags();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SSL_PRINT_DEBUG
|
|
|
|
# define DEBUG_PRINT(...) fprintf (stderr, __VA_ARGS__)
|
|
|
|
#else
|
|
|
|
# define DEBUG_PRINT(...)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
int Connection::HandleBIOError(BIO *bio, const char* func, int rv) {
|
|
|
|
if (rv >= 0)
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
int retry = BIO_should_retry(bio);
|
|
|
|
(void) retry; // unused if !defined(SSL_PRINT_DEBUG)
|
|
|
|
|
|
|
|
if (BIO_should_write(bio)) {
|
|
|
|
DEBUG_PRINT("[%p] BIO: %s want write. should retry %d\n",
|
|
|
|
ssl_,
|
|
|
|
func,
|
|
|
|
retry);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else if (BIO_should_read(bio)) {
|
|
|
|
DEBUG_PRINT("[%p] BIO: %s want read. should retry %d\n", ssl_, func, retry);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
char ssl_error_buf[512];
|
|
|
|
ERR_error_string_n(rv, ssl_error_buf, sizeof(ssl_error_buf));
|
|
|
|
|
|
|
|
HandleScope scope(ssl_env()->isolate());
|
|
|
|
Local<Value> exception =
|
|
|
|
Exception::Error(OneByteString(ssl_env()->isolate(), ssl_error_buf));
|
|
|
|
object()->Set(ssl_env()->error_string(), exception);
|
|
|
|
|
|
|
|
DEBUG_PRINT("[%p] BIO: %s failed: (%d) %s\n",
|
|
|
|
ssl_,
|
|
|
|
func,
|
|
|
|
rv,
|
|
|
|
ssl_error_buf);
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int Connection::HandleSSLError(const char* func,
|
|
|
|
int rv,
|
|
|
|
ZeroStatus zs,
|
|
|
|
SyscallStatus ss) {
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence unused variable warning.
|
|
|
|
|
|
|
|
if (rv > 0)
|
|
|
|
return rv;
|
|
|
|
if (rv == 0 && zs == kZeroIsNotAnError)
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
int err = SSL_get_error(ssl_, rv);
|
|
|
|
|
|
|
|
if (err == SSL_ERROR_NONE) {
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else if (err == SSL_ERROR_WANT_WRITE) {
|
|
|
|
DEBUG_PRINT("[%p] SSL: %s want write\n", ssl_, func);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else if (err == SSL_ERROR_WANT_READ) {
|
|
|
|
DEBUG_PRINT("[%p] SSL: %s want read\n", ssl_, func);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else if (err == SSL_ERROR_WANT_X509_LOOKUP) {
|
|
|
|
DEBUG_PRINT("[%p] SSL: %s want x509 lookup\n", ssl_, func);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else if (err == SSL_ERROR_ZERO_RETURN) {
|
|
|
|
HandleScope scope(ssl_env()->isolate());
|
|
|
|
|
|
|
|
Local<Value> exception =
|
|
|
|
Exception::Error(ssl_env()->zero_return_string());
|
|
|
|
object()->Set(ssl_env()->error_string(), exception);
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
} else if (err == SSL_ERROR_SYSCALL && ss == kIgnoreSyscall) {
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
HandleScope scope(ssl_env()->isolate());
|
|
|
|
BUF_MEM* mem;
|
|
|
|
BIO *bio;
|
|
|
|
|
|
|
|
CHECK(err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL);
|
|
|
|
|
|
|
|
// XXX We need to drain the error queue for this thread or else OpenSSL
|
|
|
|
// has the possibility of blocking connections? This problem is not well
|
|
|
|
// understood. And we should be somehow propagating these errors up
|
|
|
|
// into JavaScript. There is no test which demonstrates this problem.
|
|
|
|
// https://github.com/joyent/node/issues/1719
|
|
|
|
bio = BIO_new(BIO_s_mem());
|
|
|
|
if (bio != nullptr) {
|
|
|
|
ERR_print_errors(bio);
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
Local<Value> exception = Exception::Error(
|
|
|
|
OneByteString(ssl_env()->isolate(),
|
|
|
|
mem->data,
|
|
|
|
mem->length));
|
|
|
|
object()->Set(ssl_env()->error_string(), exception);
|
|
|
|
BIO_free_all(bio);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::ClearError() {
|
|
|
|
#ifndef NDEBUG
|
|
|
|
HandleScope scope(ssl_env()->isolate());
|
|
|
|
|
|
|
|
// We should clear the error in JS-land
|
|
|
|
Local<String> error_key = ssl_env()->error_string();
|
|
|
|
Local<Value> error = object()->Get(error_key);
|
|
|
|
CHECK_EQ(error->BooleanValue(), false);
|
|
|
|
#endif // NDEBUG
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::SetShutdownFlags() {
|
|
|
|
HandleScope scope(ssl_env()->isolate());
|
|
|
|
|
|
|
|
int flags = SSL_get_shutdown(ssl_);
|
|
|
|
|
|
|
|
if (flags & SSL_SENT_SHUTDOWN) {
|
|
|
|
Local<String> sent_shutdown_key = ssl_env()->sent_shutdown_string();
|
|
|
|
object()->Set(sent_shutdown_key, True(ssl_env()->isolate()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & SSL_RECEIVED_SHUTDOWN) {
|
|
|
|
Local<String> received_shutdown_key = ssl_env()->received_shutdown_string();
|
|
|
|
object()->Set(received_shutdown_key, True(ssl_env()->isolate()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::NewSessionDoneCb() {
|
|
|
|
HandleScope scope(env()->isolate());
|
|
|
|
|
|
|
|
MakeCallback(env()->onnewsessiondone_string(), 0, nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::Initialize(Environment* env, Local<Object> target) {
|
|
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(Connection::New);
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Connection"));
|
|
|
|
|
|
|
|
env->SetProtoMethod(t, "encIn", Connection::EncIn);
|
|
|
|
env->SetProtoMethod(t, "clearOut", Connection::ClearOut);
|
|
|
|
env->SetProtoMethod(t, "clearIn", Connection::ClearIn);
|
|
|
|
env->SetProtoMethod(t, "encOut", Connection::EncOut);
|
|
|
|
env->SetProtoMethod(t, "clearPending", Connection::ClearPending);
|
|
|
|
env->SetProtoMethod(t, "encPending", Connection::EncPending);
|
|
|
|
env->SetProtoMethod(t, "start", Connection::Start);
|
|
|
|
env->SetProtoMethod(t, "close", Connection::Close);
|
|
|
|
|
|
|
|
SSLWrap<Connection>::AddMethods(env, t);
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
|
|
env->SetProtoMethod(t, "getServername", Connection::GetServername);
|
|
|
|
env->SetProtoMethod(t, "setSNICallback", Connection::SetSNICallback);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Connection"),
|
|
|
|
t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
inline int compar(const void* a, const void* b) {
|
|
|
|
return memcmp(a, b, CNNIC_WHITELIST_HASH_LEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
inline int IsSelfSigned(X509* cert) {
|
|
|
|
return X509_NAME_cmp(X509_get_subject_name(cert),
|
|
|
|
X509_get_issuer_name(cert)) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
inline X509* FindRoot(STACK_OF(X509)* sk) {
|
|
|
|
for (int i = 0; i < sk_X509_num(sk); i++) {
|
|
|
|
X509* cert = sk_X509_value(sk, i);
|
|
|
|
if (IsSelfSigned(cert))
|
|
|
|
return cert;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Whitelist check for certs issued by CNNIC. See
|
|
|
|
// https://blog.mozilla.org/security/2015/04/02
|
|
|
|
// /distrusting-new-cnnic-certificates/
|
|
|
|
inline CheckResult CheckWhitelistedServerCert(X509_STORE_CTX* ctx) {
|
|
|
|
unsigned char hash[CNNIC_WHITELIST_HASH_LEN];
|
|
|
|
unsigned int hashlen = CNNIC_WHITELIST_HASH_LEN;
|
|
|
|
|
|
|
|
STACK_OF(X509)* chain = X509_STORE_CTX_get1_chain(ctx);
|
|
|
|
CHECK_NE(chain, nullptr);
|
|
|
|
CHECK_GT(sk_X509_num(chain), 0);
|
|
|
|
|
|
|
|
// Take the last cert as root at the first time.
|
|
|
|
X509* root_cert = sk_X509_value(chain, sk_X509_num(chain)-1);
|
|
|
|
X509_NAME* root_name = X509_get_subject_name(root_cert);
|
|
|
|
|
|
|
|
if (!IsSelfSigned(root_cert)) {
|
|
|
|
root_cert = FindRoot(chain);
|
|
|
|
CHECK_NE(root_cert, nullptr);
|
|
|
|
root_name = X509_get_subject_name(root_cert);
|
|
|
|
}
|
|
|
|
|
|
|
|
// When the cert is issued from either CNNNIC ROOT CA or CNNNIC EV
|
|
|
|
// ROOT CA, check a hash of its leaf cert if it is in the whitelist.
|
|
|
|
if (X509_NAME_cmp(root_name, cnnic_name) == 0 ||
|
|
|
|
X509_NAME_cmp(root_name, cnnic_ev_name) == 0) {
|
|
|
|
X509* leaf_cert = sk_X509_value(chain, 0);
|
|
|
|
int ret = X509_digest(leaf_cert, EVP_sha256(), hash,
|
|
|
|
&hashlen);
|
|
|
|
CHECK(ret);
|
|
|
|
|
|
|
|
void* result = bsearch(hash, WhitelistedCNNICHashes,
|
|
|
|
ARRAY_SIZE(WhitelistedCNNICHashes),
|
|
|
|
CNNIC_WHITELIST_HASH_LEN, compar);
|
|
|
|
if (result == nullptr) {
|
|
|
|
sk_X509_pop_free(chain, X509_free);
|
|
|
|
return CHECK_CERT_REVOKED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sk_X509_pop_free(chain, X509_free);
|
|
|
|
return CHECK_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
inline int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) {
|
|
|
|
// Failure on verification of the cert is handled in
|
|
|
|
// Connection::VerifyError.
|
|
|
|
if (preverify_ok == 0 || X509_STORE_CTX_get_error(ctx) != X509_V_OK)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
// Server does not need to check the whitelist.
|
|
|
|
SSL* ssl = static_cast<SSL*>(
|
|
|
|
X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
|
|
|
|
|
|
|
|
if (SSL_is_server(ssl))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
// Client needs to check if the server cert is listed in the
|
|
|
|
// whitelist when it is issued by the specific rootCAs.
|
|
|
|
CheckResult ret = CheckWhitelistedServerCert(ctx);
|
|
|
|
if (ret == CHECK_CERT_REVOKED)
|
|
|
|
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
|
|
int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) {
|
|
|
|
Connection* conn = static_cast<Connection*>(SSL_get_app_data(s));
|
|
|
|
Environment* env = conn->env();
|
|
|
|
HandleScope scope(env->isolate());
|
|
|
|
|
|
|
|
const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
|
|
|
|
|
|
|
|
if (servername) {
|
|
|
|
conn->servername_.Reset(env->isolate(),
|
|
|
|
OneByteString(env->isolate(), servername));
|
|
|
|
|
|
|
|
// Call the SNI callback and use its return value as context
|
|
|
|
if (!conn->sniObject_.IsEmpty()) {
|
|
|
|
conn->sni_context_.Reset();
|
|
|
|
|
|
|
|
Local<Object> sni_obj = PersistentToLocal(env->isolate(),
|
|
|
|
conn->sniObject_);
|
|
|
|
|
|
|
|
Local<Value> arg = PersistentToLocal(env->isolate(), conn->servername_);
|
|
|
|
Local<Value> ret = node::MakeCallback(env->isolate(),
|
|
|
|
sni_obj,
|
|
|
|
env->onselect_string(),
|
|
|
|
1,
|
|
|
|
&arg);
|
|
|
|
|
|
|
|
// If ret is SecureContext
|
|
|
|
Local<FunctionTemplate> secure_context_constructor_template =
|
|
|
|
env->secure_context_constructor_template();
|
|
|
|
if (secure_context_constructor_template->HasInstance(ret)) {
|
|
|
|
conn->sni_context_.Reset(env->isolate(), ret);
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(ret.As<Object>());
|
|
|
|
conn->SetSNIContext(sc);
|
|
|
|
} else {
|
|
|
|
return SSL_TLSEXT_ERR_NOACK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void Connection::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
if (args.Length() < 1 || !args[0]->IsObject()) {
|
|
|
|
env->ThrowError("First argument must be a tls module SecureContext");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SecureContext* sc = Unwrap<SecureContext>(args[0]->ToObject(env->isolate()));
|
|
|
|
|
|
|
|
bool is_server = args[1]->BooleanValue();
|
|
|
|
|
|
|
|
SSLWrap<Connection>::Kind kind =
|
|
|
|
is_server ? SSLWrap<Connection>::kServer : SSLWrap<Connection>::kClient;
|
|
|
|
Connection* conn = new Connection(env, args.This(), sc, kind);
|
|
|
|
conn->bio_read_ = NodeBIO::New();
|
|
|
|
conn->bio_write_ = NodeBIO::New();
|
|
|
|
|
|
|
|
SSL_set_app_data(conn->ssl_, conn);
|
|
|
|
|
|
|
|
if (is_server)
|
|
|
|
SSL_set_info_callback(conn->ssl_, SSLInfoCallback);
|
|
|
|
|
|
|
|
InitNPN(sc);
|
|
|
|
|
|
|
|
SSL_set_cert_cb(conn->ssl_, SSLWrap<Connection>::SSLCertCallback, conn);
|
|
|
|
|
|
|
|
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
|
|
if (is_server) {
|
|
|
|
SSL_CTX_set_tlsext_servername_callback(sc->ctx_, SelectSNIContextCallback_);
|
|
|
|
} else if (args[2]->IsString()) {
|
|
|
|
const node::Utf8Value servername(env->isolate(), args[2]);
|
|
|
|
SSL_set_tlsext_host_name(conn->ssl_, *servername);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
SSL_set_bio(conn->ssl_, conn->bio_read_, conn->bio_write_);
|
|
|
|
|
|
|
|
#ifdef SSL_MODE_RELEASE_BUFFERS
|
|
|
|
long mode = SSL_get_mode(conn->ssl_);
|
|
|
|
SSL_set_mode(conn->ssl_, mode | SSL_MODE_RELEASE_BUFFERS);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
int verify_mode;
|
|
|
|
if (is_server) {
|
|
|
|
bool request_cert = args[2]->BooleanValue();
|
|
|
|
if (!request_cert) {
|
|
|
|
// Note reject_unauthorized ignored.
|
|
|
|
verify_mode = SSL_VERIFY_NONE;
|
|
|
|
} else {
|
|
|
|
bool reject_unauthorized = args[3]->BooleanValue();
|
|
|
|
verify_mode = SSL_VERIFY_PEER;
|
|
|
|
if (reject_unauthorized)
|
|
|
|
verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Note request_cert and reject_unauthorized are ignored for clients.
|
|
|
|
verify_mode = SSL_VERIFY_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Always allow a connection. We'll reject in javascript.
|
|
|
|
SSL_set_verify(conn->ssl_, verify_mode, VerifyCallback);
|
|
|
|
|
|
|
|
if (is_server) {
|
|
|
|
SSL_set_accept_state(conn->ssl_);
|
|
|
|
} else {
|
|
|
|
SSL_set_connect_state(conn->ssl_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::SSLInfoCallback(const SSL *ssl_, int where, int ret) {
|
|
|
|
if (!(where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE)))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Be compatible with older versions of OpenSSL. SSL_get_app_data() wants
|
|
|
|
// a non-const SSL* in OpenSSL <= 0.9.7e.
|
|
|
|
SSL* ssl = const_cast<SSL*>(ssl_);
|
|
|
|
Connection* conn = static_cast<Connection*>(SSL_get_app_data(ssl));
|
|
|
|
Environment* env = conn->env();
|
|
|
|
HandleScope handle_scope(env->isolate());
|
|
|
|
Context::Scope context_scope(env->context());
|
|
|
|
|
|
|
|
if (where & SSL_CB_HANDSHAKE_START) {
|
|
|
|
conn->MakeCallback(env->onhandshakestart_string(), 0, nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (where & SSL_CB_HANDSHAKE_DONE) {
|
|
|
|
conn->MakeCallback(env->onhandshakedone_string(), 0, nullptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::EncIn(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Connection* conn = Unwrap<Connection>(args.Holder());
|
|
|
|
Environment* env = conn->env();
|
|
|
|
|
|
|
|
if (args.Length() < 3) {
|
|
|
|
return env->ThrowTypeError("Takes 3 parameters");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Buffer::HasInstance(args[0])) {
|
|
|
|
return env->ThrowTypeError("Second argument should be a buffer");
|
|
|
|
}
|
|
|
|
|
|
|
|
char* buffer_data = Buffer::Data(args[0]);
|
|
|
|
size_t buffer_length = Buffer::Length(args[0]);
|
|
|
|
|
|
|
|
size_t off = args[1]->Int32Value();
|
|
|
|
size_t len = args[2]->Int32Value();
|
|
|
|
|
|
|
|
if (!Buffer::IsWithinBounds(off, len, buffer_length))
|
|
|
|
return env->ThrowError("off + len > buffer.length");
|
|
|
|
|
|
|
|
int bytes_written;
|
|
|
|
char* data = buffer_data + off;
|
|
|
|
|
|
|
|
if (conn->is_server() && !conn->hello_parser_.IsEnded()) {
|
|
|
|
// Just accumulate data, everything will be pushed to BIO later
|
|
|
|
if (conn->hello_parser_.IsPaused()) {
|
|
|
|
bytes_written = 0;
|
|
|
|
} else {
|
|
|
|
// Copy incoming data to the internal buffer
|
|
|
|
// (which has a size of the biggest possible TLS frame)
|
|
|
|
size_t available = sizeof(conn->hello_data_) - conn->hello_offset_;
|
|
|
|
size_t copied = len < available ? len : available;
|
|
|
|
memcpy(conn->hello_data_ + conn->hello_offset_, data, copied);
|
|
|
|
conn->hello_offset_ += copied;
|
|
|
|
|
|
|
|
conn->hello_parser_.Parse(conn->hello_data_, conn->hello_offset_);
|
|
|
|
bytes_written = copied;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
bytes_written = BIO_write(conn->bio_read_, data, len);
|
|
|
|
conn->HandleBIOError(conn->bio_read_, "BIO_write", bytes_written);
|
|
|
|
conn->SetShutdownFlags();
|
|
|
|
}
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(bytes_written);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::ClearOut(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Connection* conn = Unwrap<Connection>(args.Holder());
|
|
|
|
Environment* env = conn->env();
|
|
|
|
|
|
|
|
if (args.Length() < 3) {
|
|
|
|
return env->ThrowTypeError("Takes 3 parameters");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Buffer::HasInstance(args[0])) {
|
|
|
|
return env->ThrowTypeError("Second argument should be a buffer");
|
|
|
|
}
|
|
|
|
|
|
|
|
char* buffer_data = Buffer::Data(args[0]);
|
|
|
|
size_t buffer_length = Buffer::Length(args[0]);
|
|
|
|
|
|
|
|
size_t off = args[1]->Int32Value();
|
|
|
|
size_t len = args[2]->Int32Value();
|
|
|
|
|
|
|
|
if (!Buffer::IsWithinBounds(off, len, buffer_length))
|
|
|
|
return env->ThrowError("off + len > buffer.length");
|
|
|
|
|
|
|
|
if (!SSL_is_init_finished(conn->ssl_)) {
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
if (conn->is_server()) {
|
|
|
|
rv = SSL_accept(conn->ssl_);
|
|
|
|
conn->HandleSSLError("SSL_accept:ClearOut",
|
|
|
|
rv,
|
|
|
|
kZeroIsAnError,
|
|
|
|
kSyscallError);
|
|
|
|
} else {
|
|
|
|
rv = SSL_connect(conn->ssl_);
|
|
|
|
conn->HandleSSLError("SSL_connect:ClearOut",
|
|
|
|
rv,
|
|
|
|
kZeroIsAnError,
|
|
|
|
kSyscallError);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rv < 0) {
|
|
|
|
return args.GetReturnValue().Set(rv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int bytes_read = SSL_read(conn->ssl_, buffer_data + off, len);
|
|
|
|
conn->HandleSSLError("SSL_read:ClearOut",
|
|
|
|
bytes_read,
|
|
|
|
kZeroIsNotAnError,
|
|
|
|
kSyscallError);
|
|
|
|
conn->SetShutdownFlags();
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(bytes_read);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::ClearPending(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Connection* conn = Unwrap<Connection>(args.Holder());
|
|
|
|
int bytes_pending = BIO_pending(conn->bio_read_);
|
|
|
|
args.GetReturnValue().Set(bytes_pending);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::EncPending(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Connection* conn = Unwrap<Connection>(args.Holder());
|
|
|
|
int bytes_pending = BIO_pending(conn->bio_write_);
|
|
|
|
args.GetReturnValue().Set(bytes_pending);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::EncOut(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Connection* conn = Unwrap<Connection>(args.Holder());
|
|
|
|
Environment* env = conn->env();
|
|
|
|
|
|
|
|
if (args.Length() < 3) {
|
|
|
|
return env->ThrowTypeError("Takes 3 parameters");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Buffer::HasInstance(args[0])) {
|
|
|
|
return env->ThrowTypeError("Second argument should be a buffer");
|
|
|
|
}
|
|
|
|
|
|
|
|
char* buffer_data = Buffer::Data(args[0]);
|
|
|
|
size_t buffer_length = Buffer::Length(args[0]);
|
|
|
|
|
|
|
|
size_t off = args[1]->Int32Value();
|
|
|
|
size_t len = args[2]->Int32Value();
|
|
|
|
|
|
|
|
if (!Buffer::IsWithinBounds(off, len, buffer_length))
|
|
|
|
return env->ThrowError("off + len > buffer.length");
|
|
|
|
|
|
|
|
int bytes_read = BIO_read(conn->bio_write_, buffer_data + off, len);
|
|
|
|
|
|
|
|
conn->HandleBIOError(conn->bio_write_, "BIO_read:EncOut", bytes_read);
|
|
|
|
conn->SetShutdownFlags();
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(bytes_read);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::ClearIn(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Connection* conn = Unwrap<Connection>(args.Holder());
|
|
|
|
Environment* env = conn->env();
|
|
|
|
|
|
|
|
if (args.Length() < 3) {
|
|
|
|
return env->ThrowTypeError("Takes 3 parameters");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Buffer::HasInstance(args[0])) {
|
|
|
|
return env->ThrowTypeError("Second argument should be a buffer");
|
|
|
|
}
|
|
|
|
|
|
|
|
char* buffer_data = Buffer::Data(args[0]);
|
|
|
|
size_t buffer_length = Buffer::Length(args[0]);
|
|
|
|
|
|
|
|
size_t off = args[1]->Int32Value();
|
|
|
|
size_t len = args[2]->Int32Value();
|
|
|
|
|
|
|
|
if (!Buffer::IsWithinBounds(off, len, buffer_length))
|
|
|
|
return env->ThrowError("off + len > buffer.length");
|
|
|
|
|
|
|
|
if (!SSL_is_init_finished(conn->ssl_)) {
|
|
|
|
int rv;
|
|
|
|
if (conn->is_server()) {
|
|
|
|
rv = SSL_accept(conn->ssl_);
|
|
|
|
conn->HandleSSLError("SSL_accept:ClearIn",
|
|
|
|
rv,
|
|
|
|
kZeroIsAnError,
|
|
|
|
kSyscallError);
|
|
|
|
} else {
|
|
|
|
rv = SSL_connect(conn->ssl_);
|
|
|
|
conn->HandleSSLError("SSL_connect:ClearIn",
|
|
|
|
rv,
|
|
|
|
kZeroIsAnError,
|
|
|
|
kSyscallError);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rv < 0) {
|
|
|
|
return args.GetReturnValue().Set(rv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int bytes_written = SSL_write(conn->ssl_, buffer_data + off, len);
|
|
|
|
|
|
|
|
conn->HandleSSLError("SSL_write:ClearIn",
|
|
|
|
bytes_written,
|
|
|
|
len == 0 ? kZeroIsNotAnError : kZeroIsAnError,
|
|
|
|
kSyscallError);
|
|
|
|
conn->SetShutdownFlags();
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(bytes_written);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::Start(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Connection* conn = Unwrap<Connection>(args.Holder());
|
|
|
|
|
|
|
|
int rv = 0;
|
|
|
|
if (!SSL_is_init_finished(conn->ssl_)) {
|
|
|
|
if (conn->is_server()) {
|
|
|
|
rv = SSL_accept(conn->ssl_);
|
|
|
|
conn->HandleSSLError("SSL_accept:Start",
|
|
|
|
rv,
|
|
|
|
kZeroIsAnError,
|
|
|
|
kSyscallError);
|
|
|
|
} else {
|
|
|
|
rv = SSL_connect(conn->ssl_);
|
|
|
|
conn->HandleSSLError("SSL_connect:Start",
|
|
|
|
rv,
|
|
|
|
kZeroIsAnError,
|
|
|
|
kSyscallError);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
args.GetReturnValue().Set(rv);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::Close(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Connection* conn = Unwrap<Connection>(args.Holder());
|
|
|
|
|
|
|
|
if (conn->ssl_ != nullptr) {
|
|
|
|
SSL_free(conn->ssl_);
|
|
|
|
conn->ssl_ = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
|
|
void Connection::GetServername(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Connection* conn = Unwrap<Connection>(args.Holder());
|
|
|
|
|
|
|
|
if (conn->is_server() && !conn->servername_.IsEmpty()) {
|
|
|
|
args.GetReturnValue().Set(conn->servername_);
|
|
|
|
} else {
|
|
|
|
args.GetReturnValue().Set(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::SetSNICallback(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Connection* conn = Unwrap<Connection>(args.Holder());
|
|
|
|
Environment* env = conn->env();
|
|
|
|
|
|
|
|
if (args.Length() < 1 || !args[0]->IsFunction()) {
|
|
|
|
return env->ThrowError("Must give a Function as first argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Object> obj = Object::New(env->isolate());
|
|
|
|
obj->Set(FIXED_ONE_BYTE_STRING(args.GetIsolate(), "onselect"), args[0]);
|
|
|
|
conn->sniObject_.Reset(args.GetIsolate(), obj);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::Initialize(Environment* env, Local<Object> target) {
|
|
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
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->SetProtoMethod(t, "getAuthTag", GetAuthTag);
|
|
|
|
env->SetProtoMethod(t, "setAuthTag", SetAuthTag);
|
|
|
|
env->SetProtoMethod(t, "setAAD", SetAAD);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "CipherBase"),
|
|
|
|
t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
CHECK_EQ(args.IsConstructCall(), true);
|
|
|
|
CipherKind kind = args[0]->IsTrue() ? kCipher : kDecipher;
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
new CipherBase(env, args.This(), kind);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::Init(const char* cipher_type,
|
|
|
|
const char* key_buf,
|
|
|
|
int key_buf_len) {
|
|
|
|
HandleScope scope(env()->isolate());
|
|
|
|
|
|
|
|
#ifdef NODE_FIPS_MODE
|
|
|
|
return env()->ThrowError(
|
|
|
|
"crypto.createCipher() is not supported in FIPS mode.");
|
|
|
|
#endif // NODE_FIPS_MODE
|
|
|
|
|
|
|
|
CHECK_EQ(cipher_, nullptr);
|
|
|
|
cipher_ = EVP_get_cipherbyname(cipher_type);
|
|
|
|
if (cipher_ == nullptr) {
|
|
|
|
return env()->ThrowError("Unknown cipher");
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
EVP_CIPHER_CTX_init(&ctx_);
|
|
|
|
const bool encrypt = (kind_ == kCipher);
|
|
|
|
EVP_CipherInit_ex(&ctx_, cipher_, nullptr, nullptr, nullptr, encrypt);
|
|
|
|
if (!EVP_CIPHER_CTX_set_key_length(&ctx_, key_len)) {
|
|
|
|
EVP_CIPHER_CTX_cleanup(&ctx_);
|
|
|
|
return env()->ThrowError("Invalid key length");
|
|
|
|
}
|
|
|
|
|
|
|
|
EVP_CipherInit_ex(&ctx_,
|
|
|
|
nullptr,
|
|
|
|
nullptr,
|
|
|
|
reinterpret_cast<unsigned char*>(key),
|
|
|
|
reinterpret_cast<unsigned char*>(iv),
|
|
|
|
kind_ == kCipher);
|
|
|
|
initialised_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::Init(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
CipherBase* cipher = Unwrap<CipherBase>(args.Holder());
|
|
|
|
|
|
|
|
if (args.Length() < 2 ||
|
|
|
|
!(args[0]->IsString() && Buffer::HasInstance(args[1]))) {
|
|
|
|
return cipher->env()->ThrowError("Must give cipher-type, key");
|
|
|
|
}
|
|
|
|
|
|
|
|
const node::Utf8Value cipher_type(args.GetIsolate(), args[0]);
|
|
|
|
const char* key_buf = Buffer::Data(args[1]);
|
|
|
|
ssize_t key_buf_len = Buffer::Length(args[1]);
|
|
|
|
cipher->Init(*cipher_type, key_buf, key_buf_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::InitIv(const char* cipher_type,
|
|
|
|
const char* key,
|
|
|
|
int key_len,
|
|
|
|
const char* iv,
|
|
|
|
int iv_len) {
|
|
|
|
HandleScope scope(env()->isolate());
|
|
|
|
|
|
|
|
cipher_ = EVP_get_cipherbyname(cipher_type);
|
|
|
|
if (cipher_ == nullptr) {
|
|
|
|
return env()->ThrowError("Unknown cipher");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* OpenSSL versions up to 0.9.8l failed to return the correct
|
|
|
|
iv_length (0) for ECB ciphers */
|
|
|
|
if (EVP_CIPHER_iv_length(cipher_) != iv_len &&
|
|
|
|
!(EVP_CIPHER_mode(cipher_) == EVP_CIPH_ECB_MODE && iv_len == 0)) {
|
|
|
|
return env()->ThrowError("Invalid IV length");
|
|
|
|
}
|
|
|
|
EVP_CIPHER_CTX_init(&ctx_);
|
|
|
|
const bool encrypt = (kind_ == kCipher);
|
|
|
|
EVP_CipherInit_ex(&ctx_, cipher_, nullptr, nullptr, nullptr, encrypt);
|
|
|
|
if (!EVP_CIPHER_CTX_set_key_length(&ctx_, key_len)) {
|
|
|
|
EVP_CIPHER_CTX_cleanup(&ctx_);
|
|
|
|
return env()->ThrowError("Invalid key length");
|
|
|
|
}
|
|
|
|
|
|
|
|
EVP_CipherInit_ex(&ctx_,
|
|
|
|
nullptr,
|
|
|
|
nullptr,
|
|
|
|
reinterpret_cast<const unsigned char*>(key),
|
|
|
|
reinterpret_cast<const unsigned char*>(iv),
|
|
|
|
kind_ == kCipher);
|
|
|
|
initialised_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::InitIv(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
CipherBase* cipher = Unwrap<CipherBase>(args.Holder());
|
|
|
|
Environment* env = cipher->env();
|
|
|
|
|
|
|
|
if (args.Length() < 3 || !args[0]->IsString()) {
|
|
|
|
return env->ThrowError("Must give cipher-type, key, and iv as argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[1]);
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[2]);
|
|
|
|
|
|
|
|
const node::Utf8Value cipher_type(env->isolate(), args[0]);
|
|
|
|
ssize_t key_len = Buffer::Length(args[1]);
|
|
|
|
const char* key_buf = Buffer::Data(args[1]);
|
|
|
|
ssize_t iv_len = Buffer::Length(args[2]);
|
|
|
|
const char* iv_buf = Buffer::Data(args[2]);
|
|
|
|
cipher->InitIv(*cipher_type, key_buf, key_len, iv_buf, iv_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CipherBase::IsAuthenticatedMode() const {
|
|
|
|
// check if this cipher operates in an AEAD mode that we support.
|
|
|
|
if (!cipher_)
|
|
|
|
return false;
|
|
|
|
int mode = EVP_CIPHER_mode(cipher_);
|
|
|
|
return mode == EVP_CIPH_GCM_MODE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CipherBase::GetAuthTag(char** out, unsigned int* out_len) const {
|
|
|
|
// only callable after Final and if encrypting.
|
|
|
|
if (initialised_ || kind_ != kCipher || !auth_tag_)
|
|
|
|
return false;
|
|
|
|
*out_len = auth_tag_len_;
|
|
|
|
*out = static_cast<char*>(malloc(auth_tag_len_));
|
|
|
|
CHECK_NE(*out, nullptr);
|
|
|
|
memcpy(*out, auth_tag_, auth_tag_len_);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
CipherBase* cipher = Unwrap<CipherBase>(args.Holder());
|
|
|
|
|
|
|
|
char* out = nullptr;
|
|
|
|
unsigned int out_len = 0;
|
|
|
|
|
|
|
|
if (cipher->GetAuthTag(&out, &out_len)) {
|
|
|
|
Local<Object> buf = Buffer::New(env, out, out_len).ToLocalChecked();
|
|
|
|
args.GetReturnValue().Set(buf);
|
|
|
|
} else {
|
|
|
|
env->ThrowError("Attempting to get auth tag in unsupported state");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CipherBase::SetAuthTag(const char* data, unsigned int len) {
|
|
|
|
if (!initialised_ || !IsAuthenticatedMode() || kind_ != kDecipher)
|
|
|
|
return false;
|
|
|
|
delete[] auth_tag_;
|
|
|
|
auth_tag_len_ = len;
|
|
|
|
auth_tag_ = new char[len];
|
|
|
|
memcpy(auth_tag_, data, len);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
Local<Object> buf = args[0].As<Object>();
|
|
|
|
if (!buf->IsObject() || !Buffer::HasInstance(buf))
|
|
|
|
return env->ThrowTypeError("Argument must be a Buffer");
|
|
|
|
|
|
|
|
CipherBase* cipher = Unwrap<CipherBase>(args.Holder());
|
|
|
|
|
|
|
|
if (!cipher->SetAuthTag(Buffer::Data(buf), Buffer::Length(buf)))
|
|
|
|
env->ThrowError("Attempting to set auth tag in unsupported state");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CipherBase::SetAAD(const char* data, unsigned int len) {
|
|
|
|
if (!initialised_ || !IsAuthenticatedMode())
|
|
|
|
return false;
|
|
|
|
int outlen;
|
|
|
|
if (!EVP_CipherUpdate(&ctx_,
|
|
|
|
nullptr,
|
|
|
|
&outlen,
|
|
|
|
reinterpret_cast<const unsigned char*>(data),
|
|
|
|
len)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::SetAAD(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
|
|
|
|
CipherBase* cipher = Unwrap<CipherBase>(args.Holder());
|
|
|
|
|
|
|
|
if (!cipher->SetAAD(Buffer::Data(args[0]), Buffer::Length(args[0])))
|
|
|
|
env->ThrowError("Attempting to set AAD in unsupported state");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CipherBase::Update(const char* data,
|
|
|
|
int len,
|
|
|
|
unsigned char** out,
|
|
|
|
int* out_len) {
|
|
|
|
if (!initialised_)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// on first update:
|
|
|
|
if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_ != nullptr) {
|
|
|
|
EVP_CIPHER_CTX_ctrl(&ctx_,
|
|
|
|
EVP_CTRL_GCM_SET_TAG,
|
|
|
|
auth_tag_len_,
|
|
|
|
reinterpret_cast<unsigned char*>(auth_tag_));
|
|
|
|
delete[] auth_tag_;
|
|
|
|
auth_tag_ = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
*out_len = len + EVP_CIPHER_CTX_block_size(&ctx_);
|
|
|
|
*out = new unsigned char[*out_len];
|
|
|
|
return EVP_CipherUpdate(&ctx_,
|
|
|
|
*out,
|
|
|
|
out_len,
|
|
|
|
reinterpret_cast<const unsigned char*>(data),
|
|
|
|
len);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::Update(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
CipherBase* cipher = Unwrap<CipherBase>(args.Holder());
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0]);
|
|
|
|
|
|
|
|
unsigned char* out = nullptr;
|
|
|
|
bool r;
|
|
|
|
int out_len = 0;
|
|
|
|
|
|
|
|
// Only copy the data if we have to, because it's a string
|
|
|
|
if (args[0]->IsString()) {
|
|
|
|
StringBytes::InlineDecoder decoder;
|
|
|
|
if (!decoder.Decode(env, args[0].As<String>(), args[1], BINARY))
|
|
|
|
return;
|
|
|
|
r = cipher->Update(decoder.out(), decoder.size(), &out, &out_len);
|
|
|
|
} else {
|
|
|
|
char* buf = Buffer::Data(args[0]);
|
|
|
|
size_t buflen = Buffer::Length(args[0]);
|
|
|
|
r = cipher->Update(buf, buflen, &out, &out_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!r) {
|
|
|
|
delete[] out;
|
|
|
|
return ThrowCryptoError(env,
|
|
|
|
ERR_get_error(),
|
|
|
|
"Trying to add data in unsupported state");
|
|
|
|
}
|
|
|
|
|
|
|
|
CHECK(out != nullptr || out_len == 0);
|
|
|
|
Local<Object> buf =
|
|
|
|
Buffer::Copy(env, reinterpret_cast<char*>(out), out_len).ToLocalChecked();
|
|
|
|
if (out)
|
|
|
|
delete[] out;
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CipherBase::SetAutoPadding(bool auto_padding) {
|
|
|
|
if (!initialised_)
|
|
|
|
return false;
|
|
|
|
return EVP_CIPHER_CTX_set_padding(&ctx_, auto_padding);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::SetAutoPadding(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
CipherBase* cipher = Unwrap<CipherBase>(args.Holder());
|
|
|
|
cipher->SetAutoPadding(args.Length() < 1 || args[0]->BooleanValue());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CipherBase::Final(unsigned char** out, int *out_len) {
|
|
|
|
if (!initialised_)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
*out = new unsigned char[EVP_CIPHER_CTX_block_size(&ctx_)];
|
|
|
|
int r = EVP_CipherFinal_ex(&ctx_, *out, out_len);
|
|
|
|
|
|
|
|
if (r && kind_ == kCipher) {
|
|
|
|
delete[] auth_tag_;
|
|
|
|
auth_tag_ = nullptr;
|
|
|
|
if (IsAuthenticatedMode()) {
|
|
|
|
auth_tag_len_ = EVP_GCM_TLS_TAG_LEN; // use default tag length
|
|
|
|
auth_tag_ = new char[auth_tag_len_];
|
|
|
|
memset(auth_tag_, 0, auth_tag_len_);
|
|
|
|
EVP_CIPHER_CTX_ctrl(&ctx_,
|
|
|
|
EVP_CTRL_GCM_GET_TAG,
|
|
|
|
auth_tag_len_,
|
|
|
|
reinterpret_cast<unsigned char*>(auth_tag_));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
EVP_CIPHER_CTX_cleanup(&ctx_);
|
|
|
|
initialised_ = false;
|
|
|
|
|
|
|
|
return r == 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::Final(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
CipherBase* cipher = Unwrap<CipherBase>(args.Holder());
|
|
|
|
|
|
|
|
unsigned char* out_value = nullptr;
|
|
|
|
int out_len = -1;
|
|
|
|
Local<Value> outString;
|
|
|
|
|
|
|
|
bool r = cipher->Final(&out_value, &out_len);
|
|
|
|
|
|
|
|
if (out_len <= 0 || !r) {
|
|
|
|
delete[] out_value;
|
|
|
|
out_value = nullptr;
|
|
|
|
out_len = 0;
|
|
|
|
if (!r) {
|
|
|
|
const char* msg = cipher->IsAuthenticatedMode() ?
|
|
|
|
"Unsupported state or unable to authenticate data" :
|
|
|
|
"Unsupported state";
|
|
|
|
|
|
|
|
return ThrowCryptoError(env,
|
|
|
|
ERR_get_error(),
|
|
|
|
msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Object> buf = Buffer::Copy(
|
|
|
|
env,
|
|
|
|
reinterpret_cast<char*>(out_value),
|
|
|
|
out_len).ToLocalChecked();
|
|
|
|
args.GetReturnValue().Set(buf);
|
|
|
|
delete[] out_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hmac::Initialize(Environment* env, v8::Local<v8::Object> target) {
|
|
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
env->SetProtoMethod(t, "init", HmacInit);
|
|
|
|
env->SetProtoMethod(t, "update", HmacUpdate);
|
|
|
|
env->SetProtoMethod(t, "digest", HmacDigest);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Hmac"), t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
|
|
|
CHECK_EQ(md_, nullptr);
|
|
|
|
md_ = EVP_get_digestbyname(hash_type);
|
|
|
|
if (md_ == nullptr) {
|
|
|
|
return env()->ThrowError("Unknown message digest");
|
|
|
|
}
|
|
|
|
HMAC_CTX_init(&ctx_);
|
|
|
|
int result = 0;
|
|
|
|
if (key_len == 0) {
|
|
|
|
result = HMAC_Init(&ctx_, "", 0, md_);
|
|
|
|
} else {
|
|
|
|
result = HMAC_Init(&ctx_, key, key_len, md_);
|
|
|
|
}
|
|
|
|
if (!result) {
|
|
|
|
return ThrowCryptoError(env(), ERR_get_error());
|
|
|
|
}
|
|
|
|
initialised_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hmac::HmacInit(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Hmac* hmac = Unwrap<Hmac>(args.Holder());
|
|
|
|
Environment* env = hmac->env();
|
|
|
|
|
|
|
|
if (args.Length() < 2 || !args[0]->IsString()) {
|
|
|
|
return env->ThrowError("Must give hashtype string, key as arguments");
|
|
|
|
}
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[1]);
|
|
|
|
|
|
|
|
const node::Utf8Value hash_type(env->isolate(), args[0]);
|
|
|
|
const char* buffer_data = Buffer::Data(args[1]);
|
|
|
|
size_t buffer_length = Buffer::Length(args[1]);
|
|
|
|
hmac->HmacInit(*hash_type, buffer_data, buffer_length);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Hmac::HmacUpdate(const char* data, int len) {
|
|
|
|
if (!initialised_)
|
|
|
|
return false;
|
|
|
|
HMAC_Update(&ctx_, reinterpret_cast<const unsigned char*>(data), len);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hmac::HmacUpdate(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
Hmac* hmac = Unwrap<Hmac>(args.Holder());
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0]);
|
|
|
|
|
|
|
|
// Only copy the data if we have to, because it's a string
|
|
|
|
bool r;
|
|
|
|
if (args[0]->IsString()) {
|
|
|
|
StringBytes::InlineDecoder decoder;
|
|
|
|
if (!decoder.Decode(env, args[0].As<String>(), args[1], BINARY))
|
|
|
|
return;
|
|
|
|
r = hmac->HmacUpdate(decoder.out(), decoder.size());
|
|
|
|
} else {
|
|
|
|
char* buf = Buffer::Data(args[0]);
|
|
|
|
size_t buflen = Buffer::Length(args[0]);
|
|
|
|
r = hmac->HmacUpdate(buf, buflen);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!r) {
|
|
|
|
return env->ThrowTypeError("HmacUpdate fail");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Hmac::HmacDigest(unsigned char** md_value, unsigned int* md_len) {
|
|
|
|
if (!initialised_)
|
|
|
|
return false;
|
|
|
|
*md_value = new unsigned char[EVP_MAX_MD_SIZE];
|
|
|
|
HMAC_Final(&ctx_, *md_value, md_len);
|
|
|
|
HMAC_CTX_cleanup(&ctx_);
|
|
|
|
initialised_ = false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hmac::HmacDigest(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
Hmac* hmac = Unwrap<Hmac>(args.Holder());
|
|
|
|
|
|
|
|
enum encoding encoding = BUFFER;
|
|
|
|
if (args.Length() >= 1) {
|
|
|
|
encoding = ParseEncoding(env->isolate(),
|
|
|
|
args[0]->ToString(env->isolate()),
|
|
|
|
BUFFER);
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned char* md_value = nullptr;
|
|
|
|
unsigned int md_len = 0;
|
|
|
|
|
|
|
|
bool r = hmac->HmacDigest(&md_value, &md_len);
|
|
|
|
if (!r) {
|
|
|
|
md_value = nullptr;
|
|
|
|
md_len = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Value> rc = StringBytes::Encode(env->isolate(),
|
|
|
|
reinterpret_cast<const char*>(md_value),
|
|
|
|
md_len,
|
|
|
|
encoding);
|
|
|
|
delete[] md_value;
|
|
|
|
args.GetReturnValue().Set(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hash::Initialize(Environment* env, v8::Local<v8::Object> target) {
|
|
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
env->SetProtoMethod(t, "update", HashUpdate);
|
|
|
|
env->SetProtoMethod(t, "digest", HashDigest);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Hash"), t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hash::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
if (args.Length() == 0 || !args[0]->IsString()) {
|
|
|
|
return env->ThrowError("Must give hashtype string as argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
const node::Utf8Value hash_type(env->isolate(), args[0]);
|
|
|
|
|
|
|
|
Hash* hash = new Hash(env, args.This());
|
|
|
|
if (!hash->HashInit(*hash_type)) {
|
|
|
|
return ThrowCryptoError(env, ERR_get_error(),
|
|
|
|
"Digest method not supported");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Hash::HashInit(const char* hash_type) {
|
|
|
|
CHECK_EQ(md_, nullptr);
|
|
|
|
md_ = EVP_get_digestbyname(hash_type);
|
|
|
|
if (md_ == nullptr)
|
|
|
|
return false;
|
|
|
|
EVP_MD_CTX_init(&mdctx_);
|
|
|
|
if (EVP_DigestInit_ex(&mdctx_, md_, nullptr) <= 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
initialised_ = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Hash::HashUpdate(const char* data, int len) {
|
|
|
|
if (!initialised_)
|
|
|
|
return false;
|
|
|
|
EVP_DigestUpdate(&mdctx_, data, len);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hash::HashUpdate(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
Hash* hash = Unwrap<Hash>(args.Holder());
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0]);
|
|
|
|
|
|
|
|
// Only copy the data if we have to, because it's a string
|
|
|
|
bool r;
|
|
|
|
if (args[0]->IsString()) {
|
|
|
|
StringBytes::InlineDecoder decoder;
|
|
|
|
if (!decoder.Decode(env, args[0].As<String>(), args[1], BINARY))
|
|
|
|
return;
|
|
|
|
r = hash->HashUpdate(decoder.out(), decoder.size());
|
|
|
|
} else {
|
|
|
|
char* buf = Buffer::Data(args[0]);
|
|
|
|
size_t buflen = Buffer::Length(args[0]);
|
|
|
|
r = hash->HashUpdate(buf, buflen);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!r) {
|
|
|
|
return env->ThrowTypeError("HashUpdate fail");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
Hash* hash = Unwrap<Hash>(args.Holder());
|
|
|
|
|
|
|
|
if (!hash->initialised_) {
|
|
|
|
return env->ThrowError("Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
enum encoding encoding = BUFFER;
|
|
|
|
if (args.Length() >= 1) {
|
|
|
|
encoding = ParseEncoding(env->isolate(),
|
|
|
|
args[0]->ToString(env->isolate()),
|
|
|
|
BUFFER);
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned char md_value[EVP_MAX_MD_SIZE];
|
|
|
|
unsigned int md_len;
|
|
|
|
|
|
|
|
EVP_DigestFinal_ex(&hash->mdctx_, md_value, &md_len);
|
|
|
|
EVP_MD_CTX_cleanup(&hash->mdctx_);
|
|
|
|
hash->initialised_ = false;
|
|
|
|
|
|
|
|
Local<Value> rc = StringBytes::Encode(env->isolate(),
|
|
|
|
reinterpret_cast<const char*>(md_value),
|
|
|
|
md_len,
|
|
|
|
encoding);
|
|
|
|
args.GetReturnValue().Set(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SignBase::CheckThrow(SignBase::Error error) {
|
|
|
|
HandleScope scope(env()->isolate());
|
|
|
|
|
|
|
|
switch (error) {
|
|
|
|
case kSignUnknownDigest:
|
|
|
|
return env()->ThrowError("Unknown message digest");
|
|
|
|
|
|
|
|
case kSignNotInitialised:
|
|
|
|
return env()->ThrowError("Not initialised");
|
|
|
|
|
|
|
|
case kSignInit:
|
|
|
|
case kSignUpdate:
|
|
|
|
case kSignPrivateKey:
|
|
|
|
case kSignPublicKey:
|
|
|
|
{
|
|
|
|
unsigned long err = ERR_get_error();
|
|
|
|
if (err)
|
|
|
|
return ThrowCryptoError(env(), err);
|
|
|
|
switch (error) {
|
|
|
|
case kSignInit:
|
|
|
|
return env()->ThrowError("EVP_SignInit_ex failed");
|
|
|
|
case kSignUpdate:
|
|
|
|
return env()->ThrowError("EVP_SignUpdate failed");
|
|
|
|
case kSignPrivateKey:
|
|
|
|
return env()->ThrowError("PEM_read_bio_PrivateKey failed");
|
|
|
|
case kSignPublicKey:
|
|
|
|
return env()->ThrowError("PEM_read_bio_PUBKEY failed");
|
|
|
|
default:
|
|
|
|
ABORT();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
case kSignOk:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Sign::Initialize(Environment* env, v8::Local<v8::Object> target) {
|
|
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
env->SetProtoMethod(t, "init", SignInit);
|
|
|
|
env->SetProtoMethod(t, "update", SignUpdate);
|
|
|
|
env->SetProtoMethod(t, "sign", SignFinal);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Sign"), t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Sign::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
new Sign(env, args.This());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SignBase::Error Sign::SignInit(const char* sign_type) {
|
|
|
|
CHECK_EQ(md_, nullptr);
|
|
|
|
md_ = EVP_get_digestbyname(sign_type);
|
|
|
|
if (!md_)
|
|
|
|
return kSignUnknownDigest;
|
|
|
|
|
|
|
|
EVP_MD_CTX_init(&mdctx_);
|
|
|
|
if (!EVP_SignInit_ex(&mdctx_, md_, nullptr))
|
|
|
|
return kSignInit;
|
|
|
|
initialised_ = true;
|
|
|
|
|
|
|
|
return kSignOk;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Sign::SignInit(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Sign* sign = Unwrap<Sign>(args.Holder());
|
|
|
|
|
|
|
|
if (args.Length() == 0 || !args[0]->IsString()) {
|
|
|
|
return sign->env()->ThrowError("Must give signtype string as argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
const node::Utf8Value sign_type(args.GetIsolate(), args[0]);
|
|
|
|
sign->CheckThrow(sign->SignInit(*sign_type));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SignBase::Error Sign::SignUpdate(const char* data, int len) {
|
|
|
|
if (!initialised_)
|
|
|
|
return kSignNotInitialised;
|
|
|
|
if (!EVP_SignUpdate(&mdctx_, data, len))
|
|
|
|
return kSignUpdate;
|
|
|
|
return kSignOk;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Sign::SignUpdate(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
Sign* sign = Unwrap<Sign>(args.Holder());
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0]);
|
|
|
|
|
|
|
|
// Only copy the data if we have to, because it's a string
|
|
|
|
Error err;
|
|
|
|
if (args[0]->IsString()) {
|
|
|
|
StringBytes::InlineDecoder decoder;
|
|
|
|
if (!decoder.Decode(env, args[0].As<String>(), args[1], BINARY))
|
|
|
|
return;
|
|
|
|
err = sign->SignUpdate(decoder.out(), decoder.size());
|
|
|
|
} else {
|
|
|
|
char* buf = Buffer::Data(args[0]);
|
|
|
|
size_t buflen = Buffer::Length(args[0]);
|
|
|
|
err = sign->SignUpdate(buf, buflen);
|
|
|
|
}
|
|
|
|
|
|
|
|
sign->CheckThrow(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SignBase::Error Sign::SignFinal(const char* key_pem,
|
|
|
|
int key_pem_len,
|
|
|
|
const char* passphrase,
|
|
|
|
unsigned char** sig,
|
|
|
|
unsigned int *sig_len) {
|
|
|
|
if (!initialised_)
|
|
|
|
return kSignNotInitialised;
|
|
|
|
|
|
|
|
BIO* bp = nullptr;
|
|
|
|
EVP_PKEY* pkey = nullptr;
|
|
|
|
bool fatal = true;
|
|
|
|
|
|
|
|
bp = BIO_new_mem_buf(const_cast<char*>(key_pem), key_pem_len);
|
|
|
|
if (bp == nullptr)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
pkey = PEM_read_bio_PrivateKey(bp,
|
|
|
|
nullptr,
|
|
|
|
CryptoPemCallback,
|
|
|
|
const_cast<char*>(passphrase));
|
|
|
|
|
|
|
|
// Errors might be injected into OpenSSL's error stack
|
|
|
|
// without `pkey` being set to nullptr;
|
|
|
|
// cf. the test of `test_bad_rsa_privkey.pem` for an example.
|
|
|
|
if (pkey == nullptr || 0 != ERR_peek_error())
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
#ifdef NODE_FIPS_MODE
|
|
|
|
/* Validate DSA2 parameters from FIPS 186-4 */
|
|
|
|
if (EVP_PKEY_DSA == pkey->type) {
|
|
|
|
size_t L = BN_num_bits(pkey->pkey.dsa->p);
|
|
|
|
size_t N = BN_num_bits(pkey->pkey.dsa->q);
|
|
|
|
bool result = false;
|
|
|
|
|
|
|
|
if (L == 1024 && N == 160)
|
|
|
|
result = true;
|
|
|
|
else if (L == 2048 && N == 224)
|
|
|
|
result = true;
|
|
|
|
else if (L == 2048 && N == 256)
|
|
|
|
result = true;
|
|
|
|
else if (L == 3072 && N == 256)
|
|
|
|
result = true;
|
|
|
|
|
|
|
|
if (!result) {
|
|
|
|
fatal = true;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // NODE_FIPS_MODE
|
|
|
|
|
|
|
|
if (EVP_SignFinal(&mdctx_, *sig, sig_len, pkey))
|
|
|
|
fatal = false;
|
|
|
|
|
|
|
|
initialised_ = false;
|
|
|
|
|
|
|
|
exit:
|
|
|
|
if (pkey != nullptr)
|
|
|
|
EVP_PKEY_free(pkey);
|
|
|
|
if (bp != nullptr)
|
|
|
|
BIO_free_all(bp);
|
|
|
|
|
|
|
|
EVP_MD_CTX_cleanup(&mdctx_);
|
|
|
|
|
|
|
|
if (fatal)
|
|
|
|
return kSignPrivateKey;
|
|
|
|
|
|
|
|
return kSignOk;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
Sign* sign = Unwrap<Sign>(args.Holder());
|
|
|
|
|
|
|
|
unsigned char* md_value;
|
|
|
|
unsigned int md_len;
|
|
|
|
|
|
|
|
unsigned int len = args.Length();
|
|
|
|
enum encoding encoding = BUFFER;
|
|
|
|
if (len >= 2 && args[1]->IsString()) {
|
|
|
|
encoding = ParseEncoding(env->isolate(),
|
|
|
|
args[1]->ToString(env->isolate()),
|
|
|
|
BUFFER);
|
|
|
|
}
|
|
|
|
|
|
|
|
node::Utf8Value passphrase(env->isolate(), args[2]);
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
size_t buf_len = Buffer::Length(args[0]);
|
|
|
|
char* buf = Buffer::Data(args[0]);
|
|
|
|
|
|
|
|
md_len = 8192; // Maximum key size is 8192 bits
|
|
|
|
md_value = new unsigned char[md_len];
|
|
|
|
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence compiler warning.
|
|
|
|
|
|
|
|
Error err = sign->SignFinal(
|
|
|
|
buf,
|
|
|
|
buf_len,
|
|
|
|
len >= 3 && !args[2]->IsNull() ? *passphrase : nullptr,
|
|
|
|
&md_value,
|
|
|
|
&md_len);
|
|
|
|
if (err != kSignOk) {
|
|
|
|
delete[] md_value;
|
|
|
|
md_value = nullptr;
|
|
|
|
md_len = 0;
|
|
|
|
return sign->CheckThrow(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Value> rc = StringBytes::Encode(env->isolate(),
|
|
|
|
reinterpret_cast<const char*>(md_value),
|
|
|
|
md_len,
|
|
|
|
encoding);
|
|
|
|
delete[] md_value;
|
|
|
|
args.GetReturnValue().Set(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Verify::Initialize(Environment* env, v8::Local<v8::Object> target) {
|
|
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
env->SetProtoMethod(t, "init", VerifyInit);
|
|
|
|
env->SetProtoMethod(t, "update", VerifyUpdate);
|
|
|
|
env->SetProtoMethod(t, "verify", VerifyFinal);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Verify"),
|
|
|
|
t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Verify::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
new Verify(env, args.This());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SignBase::Error Verify::VerifyInit(const char* verify_type) {
|
|
|
|
CHECK_EQ(md_, nullptr);
|
|
|
|
md_ = EVP_get_digestbyname(verify_type);
|
|
|
|
if (md_ == nullptr)
|
|
|
|
return kSignUnknownDigest;
|
|
|
|
|
|
|
|
EVP_MD_CTX_init(&mdctx_);
|
|
|
|
if (!EVP_VerifyInit_ex(&mdctx_, md_, nullptr))
|
|
|
|
return kSignInit;
|
|
|
|
initialised_ = true;
|
|
|
|
|
|
|
|
return kSignOk;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Verify::VerifyInit(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Verify* verify = Unwrap<Verify>(args.Holder());
|
|
|
|
|
|
|
|
if (args.Length() == 0 || !args[0]->IsString()) {
|
|
|
|
return verify->env()->ThrowError("Must give verifytype string as argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
const node::Utf8Value verify_type(args.GetIsolate(), args[0]);
|
|
|
|
verify->CheckThrow(verify->VerifyInit(*verify_type));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SignBase::Error Verify::VerifyUpdate(const char* data, int len) {
|
|
|
|
if (!initialised_)
|
|
|
|
return kSignNotInitialised;
|
|
|
|
|
|
|
|
if (!EVP_VerifyUpdate(&mdctx_, data, len))
|
|
|
|
return kSignUpdate;
|
|
|
|
|
|
|
|
return kSignOk;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Verify::VerifyUpdate(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
Verify* verify = Unwrap<Verify>(args.Holder());
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0]);
|
|
|
|
|
|
|
|
// Only copy the data if we have to, because it's a string
|
|
|
|
Error err;
|
|
|
|
if (args[0]->IsString()) {
|
|
|
|
StringBytes::InlineDecoder decoder;
|
|
|
|
if (!decoder.Decode(env, args[0].As<String>(), args[1], BINARY))
|
|
|
|
return;
|
|
|
|
err = verify->VerifyUpdate(decoder.out(), decoder.size());
|
|
|
|
} else {
|
|
|
|
char* buf = Buffer::Data(args[0]);
|
|
|
|
size_t buflen = Buffer::Length(args[0]);
|
|
|
|
err = verify->VerifyUpdate(buf, buflen);
|
|
|
|
}
|
|
|
|
|
|
|
|
verify->CheckThrow(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SignBase::Error Verify::VerifyFinal(const char* key_pem,
|
|
|
|
int key_pem_len,
|
|
|
|
const char* sig,
|
|
|
|
int siglen,
|
|
|
|
bool* verify_result) {
|
|
|
|
if (!initialised_)
|
|
|
|
return kSignNotInitialised;
|
|
|
|
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence compiler warning.
|
|
|
|
|
|
|
|
EVP_PKEY* pkey = nullptr;
|
|
|
|
BIO* bp = nullptr;
|
|
|
|
X509* x509 = nullptr;
|
|
|
|
bool fatal = true;
|
|
|
|
int r = 0;
|
|
|
|
|
|
|
|
bp = BIO_new_mem_buf(const_cast<char*>(key_pem), key_pem_len);
|
|
|
|
if (bp == nullptr)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
// Check if this is a PKCS#8 or RSA public key before trying as X.509.
|
|
|
|
// Split this out into a separate function once we have more than one
|
|
|
|
// consumer of public keys.
|
|
|
|
if (strncmp(key_pem, PUBLIC_KEY_PFX, PUBLIC_KEY_PFX_LEN) == 0) {
|
|
|
|
pkey = PEM_read_bio_PUBKEY(bp, nullptr, CryptoPemCallback, nullptr);
|
|
|
|
if (pkey == nullptr)
|
|
|
|
goto exit;
|
|
|
|
} else if (strncmp(key_pem, PUBRSA_KEY_PFX, PUBRSA_KEY_PFX_LEN) == 0) {
|
|
|
|
RSA* rsa =
|
|
|
|
PEM_read_bio_RSAPublicKey(bp, nullptr, CryptoPemCallback, nullptr);
|
|
|
|
if (rsa) {
|
|
|
|
pkey = EVP_PKEY_new();
|
|
|
|
if (pkey)
|
|
|
|
EVP_PKEY_set1_RSA(pkey, rsa);
|
|
|
|
RSA_free(rsa);
|
|
|
|
}
|
|
|
|
if (pkey == nullptr)
|
|
|
|
goto exit;
|
|
|
|
} else {
|
|
|
|
// X.509 fallback
|
|
|
|
x509 = PEM_read_bio_X509(bp, nullptr, CryptoPemCallback, nullptr);
|
|
|
|
if (x509 == nullptr)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
pkey = X509_get_pubkey(x509);
|
|
|
|
if (pkey == nullptr)
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
fatal = false;
|
|
|
|
r = EVP_VerifyFinal(&mdctx_,
|
|
|
|
reinterpret_cast<const unsigned char*>(sig),
|
|
|
|
siglen,
|
|
|
|
pkey);
|
|
|
|
|
|
|
|
exit:
|
|
|
|
if (pkey != nullptr)
|
|
|
|
EVP_PKEY_free(pkey);
|
|
|
|
if (bp != nullptr)
|
|
|
|
BIO_free_all(bp);
|
|
|
|
if (x509 != nullptr)
|
|
|
|
X509_free(x509);
|
|
|
|
|
|
|
|
EVP_MD_CTX_cleanup(&mdctx_);
|
|
|
|
initialised_ = false;
|
|
|
|
|
|
|
|
if (fatal)
|
|
|
|
return kSignPublicKey;
|
|
|
|
|
|
|
|
*verify_result = r == 1;
|
|
|
|
return kSignOk;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
Verify* verify = Unwrap<Verify>(args.Holder());
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
char* kbuf = Buffer::Data(args[0]);
|
|
|
|
ssize_t klen = Buffer::Length(args[0]);
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[1]);
|
|
|
|
|
|
|
|
// BINARY works for both buffers and binary strings.
|
|
|
|
enum encoding encoding = BINARY;
|
|
|
|
if (args.Length() >= 3) {
|
|
|
|
encoding = ParseEncoding(env->isolate(),
|
|
|
|
args[2]->ToString(env->isolate()),
|
|
|
|
BINARY);
|
|
|
|
}
|
|
|
|
|
|
|
|
ssize_t hlen = StringBytes::Size(env->isolate(), args[1], encoding);
|
|
|
|
|
|
|
|
// only copy if we need to, because it's a string.
|
|
|
|
char* hbuf;
|
|
|
|
if (args[1]->IsString()) {
|
|
|
|
hbuf = new char[hlen];
|
|
|
|
ssize_t hwritten = StringBytes::Write(env->isolate(),
|
|
|
|
hbuf,
|
|
|
|
hlen,
|
|
|
|
args[1],
|
|
|
|
encoding);
|
|
|
|
CHECK_EQ(hwritten, hlen);
|
|
|
|
} else {
|
|
|
|
hbuf = Buffer::Data(args[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool verify_result;
|
|
|
|
Error err = verify->VerifyFinal(kbuf, klen, hbuf, hlen, &verify_result);
|
|
|
|
if (args[1]->IsString())
|
|
|
|
delete[] hbuf;
|
|
|
|
if (err != kSignOk)
|
|
|
|
return verify->CheckThrow(err);
|
|
|
|
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(const char* key_pem,
|
|
|
|
int key_pem_len,
|
|
|
|
const char* passphrase,
|
|
|
|
int padding,
|
|
|
|
const unsigned char* data,
|
|
|
|
int len,
|
|
|
|
unsigned char** out,
|
|
|
|
size_t* out_len) {
|
|
|
|
EVP_PKEY* pkey = nullptr;
|
|
|
|
EVP_PKEY_CTX* ctx = nullptr;
|
|
|
|
BIO* bp = nullptr;
|
|
|
|
X509* x509 = nullptr;
|
|
|
|
bool fatal = true;
|
|
|
|
|
|
|
|
bp = BIO_new_mem_buf(const_cast<char*>(key_pem), key_pem_len);
|
|
|
|
if (bp == nullptr)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
// Check if this is a PKCS#8 or RSA public key before trying as X.509 and
|
|
|
|
// private key.
|
|
|
|
if (operation == kPublic &&
|
|
|
|
strncmp(key_pem, PUBLIC_KEY_PFX, PUBLIC_KEY_PFX_LEN) == 0) {
|
|
|
|
pkey = PEM_read_bio_PUBKEY(bp, nullptr, nullptr, nullptr);
|
|
|
|
if (pkey == nullptr)
|
|
|
|
goto exit;
|
|
|
|
} else if (operation == kPublic &&
|
|
|
|
strncmp(key_pem, PUBRSA_KEY_PFX, PUBRSA_KEY_PFX_LEN) == 0) {
|
|
|
|
RSA* rsa = PEM_read_bio_RSAPublicKey(bp, nullptr, nullptr, nullptr);
|
|
|
|
if (rsa) {
|
|
|
|
pkey = EVP_PKEY_new();
|
|
|
|
if (pkey)
|
|
|
|
EVP_PKEY_set1_RSA(pkey, rsa);
|
|
|
|
RSA_free(rsa);
|
|
|
|
}
|
|
|
|
if (pkey == nullptr)
|
|
|
|
goto exit;
|
|
|
|
} else if (operation == kPublic &&
|
|
|
|
strncmp(key_pem, CERTIFICATE_PFX, CERTIFICATE_PFX_LEN) == 0) {
|
|
|
|
x509 = PEM_read_bio_X509(bp, nullptr, CryptoPemCallback, nullptr);
|
|
|
|
if (x509 == nullptr)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
pkey = X509_get_pubkey(x509);
|
|
|
|
if (pkey == nullptr)
|
|
|
|
goto exit;
|
|
|
|
} else {
|
|
|
|
pkey = PEM_read_bio_PrivateKey(bp,
|
|
|
|
nullptr,
|
|
|
|
CryptoPemCallback,
|
|
|
|
const_cast<char*>(passphrase));
|
|
|
|
if (pkey == nullptr)
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx = EVP_PKEY_CTX_new(pkey, nullptr);
|
|
|
|
if (!ctx)
|
|
|
|
goto exit;
|
|
|
|
if (EVP_PKEY_cipher_init(ctx) <= 0)
|
|
|
|
goto exit;
|
|
|
|
if (EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
if (EVP_PKEY_cipher(ctx, nullptr, out_len, data, len) <= 0)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
*out = new unsigned char[*out_len];
|
|
|
|
|
|
|
|
if (EVP_PKEY_cipher(ctx, *out, out_len, data, len) <= 0)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
fatal = false;
|
|
|
|
|
|
|
|
exit:
|
|
|
|
if (x509 != nullptr)
|
|
|
|
X509_free(x509);
|
|
|
|
if (pkey != nullptr)
|
|
|
|
EVP_PKEY_free(pkey);
|
|
|
|
if (bp != nullptr)
|
|
|
|
BIO_free_all(bp);
|
|
|
|
if (ctx != nullptr)
|
|
|
|
EVP_PKEY_CTX_free(ctx);
|
|
|
|
|
|
|
|
return !fatal;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
char* kbuf = Buffer::Data(args[0]);
|
|
|
|
ssize_t klen = Buffer::Length(args[0]);
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[1]);
|
|
|
|
char* buf = Buffer::Data(args[1]);
|
|
|
|
ssize_t len = Buffer::Length(args[1]);
|
|
|
|
|
|
|
|
int padding = args[2]->Uint32Value();
|
|
|
|
|
|
|
|
String::Utf8Value passphrase(args[3]);
|
|
|
|
|
|
|
|
unsigned char* out_value = nullptr;
|
|
|
|
size_t out_len = 0;
|
|
|
|
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence compiler warning.
|
|
|
|
|
|
|
|
bool r = Cipher<operation, EVP_PKEY_cipher_init, EVP_PKEY_cipher>(
|
|
|
|
kbuf,
|
|
|
|
klen,
|
|
|
|
args.Length() >= 3 && !args[2]->IsNull() ? *passphrase : nullptr,
|
|
|
|
padding,
|
|
|
|
reinterpret_cast<const unsigned char*>(buf),
|
|
|
|
len,
|
|
|
|
&out_value,
|
|
|
|
&out_len);
|
|
|
|
|
|
|
|
if (out_len == 0 || !r) {
|
|
|
|
delete[] out_value;
|
|
|
|
out_value = nullptr;
|
|
|
|
out_len = 0;
|
|
|
|
if (!r) {
|
|
|
|
return ThrowCryptoError(env,
|
|
|
|
ERR_get_error());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Object> vbuf = Buffer::Copy(
|
|
|
|
env,
|
|
|
|
reinterpret_cast<char*>(out_value),
|
|
|
|
out_len).ToLocalChecked();
|
|
|
|
args.GetReturnValue().Set(vbuf);
|
|
|
|
delete[] out_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::Initialize(Environment* env, Local<Object> target) {
|
|
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
|
|
|
|
|
|
const PropertyAttribute attributes =
|
|
|
|
static_cast<PropertyAttribute>(v8::ReadOnly | v8::DontDelete);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
env->SetProtoMethod(t, "generateKeys", GenerateKeys);
|
|
|
|
env->SetProtoMethod(t, "computeSecret", ComputeSecret);
|
|
|
|
env->SetProtoMethod(t, "getPrime", GetPrime);
|
|
|
|
env->SetProtoMethod(t, "getGenerator", GetGenerator);
|
|
|
|
env->SetProtoMethod(t, "getPublicKey", GetPublicKey);
|
|
|
|
env->SetProtoMethod(t, "getPrivateKey", GetPrivateKey);
|
|
|
|
env->SetProtoMethod(t, "setPublicKey", SetPublicKey);
|
|
|
|
env->SetProtoMethod(t, "setPrivateKey", SetPrivateKey);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetAccessor(env->verify_error_string(),
|
|
|
|
DiffieHellman::VerifyErrorGetter,
|
|
|
|
nullptr,
|
|
|
|
env->as_external(),
|
|
|
|
DEFAULT,
|
|
|
|
attributes);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellman"),
|
|
|
|
t->GetFunction());
|
|
|
|
|
|
|
|
Local<FunctionTemplate> t2 = env->NewFunctionTemplate(DiffieHellmanGroup);
|
|
|
|
t2->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
env->SetProtoMethod(t2, "generateKeys", GenerateKeys);
|
|
|
|
env->SetProtoMethod(t2, "computeSecret", ComputeSecret);
|
|
|
|
env->SetProtoMethod(t2, "getPrime", GetPrime);
|
|
|
|
env->SetProtoMethod(t2, "getGenerator", GetGenerator);
|
|
|
|
env->SetProtoMethod(t2, "getPublicKey", GetPublicKey);
|
|
|
|
env->SetProtoMethod(t2, "getPrivateKey", GetPrivateKey);
|
|
|
|
|
|
|
|
t2->InstanceTemplate()->SetAccessor(env->verify_error_string(),
|
|
|
|
DiffieHellman::VerifyErrorGetter,
|
|
|
|
nullptr,
|
|
|
|
env->as_external(),
|
|
|
|
DEFAULT,
|
|
|
|
attributes);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellmanGroup"),
|
|
|
|
t2->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DiffieHellman::Init(int primeLength, int g) {
|
|
|
|
dh = DH_new();
|
|
|
|
if (!DH_generate_parameters_ex(dh, primeLength, g, 0))
|
|
|
|
return false;
|
|
|
|
bool result = VerifyContext();
|
|
|
|
if (!result)
|
|
|
|
return false;
|
|
|
|
initialised_ = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DiffieHellman::Init(const char* p, int p_len, int g) {
|
|
|
|
dh = DH_new();
|
|
|
|
dh->p = BN_bin2bn(reinterpret_cast<const unsigned char*>(p), p_len, 0);
|
|
|
|
dh->g = BN_new();
|
|
|
|
if (!BN_set_word(dh->g, g))
|
|
|
|
return false;
|
|
|
|
bool result = VerifyContext();
|
|
|
|
if (!result)
|
|
|
|
return false;
|
|
|
|
initialised_ = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DiffieHellman::Init(const char* p, int p_len, const char* g, int g_len) {
|
|
|
|
dh = DH_new();
|
|
|
|
dh->p = BN_bin2bn(reinterpret_cast<const unsigned char*>(p), p_len, 0);
|
|
|
|
dh->g = BN_bin2bn(reinterpret_cast<const unsigned char*>(g), g_len, 0);
|
|
|
|
bool result = VerifyContext();
|
|
|
|
if (!result)
|
|
|
|
return false;
|
|
|
|
initialised_ = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::DiffieHellmanGroup(
|
|
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
DiffieHellman* diffieHellman = new DiffieHellman(env, args.This());
|
|
|
|
|
|
|
|
if (args.Length() != 1 || !args[0]->IsString()) {
|
|
|
|
return env->ThrowError("No group name given");
|
|
|
|
}
|
|
|
|
|
|
|
|
bool initialized = false;
|
|
|
|
|
|
|
|
const node::Utf8Value group_name(env->isolate(), args[0]);
|
|
|
|
for (unsigned int i = 0; i < ARRAY_SIZE(modp_groups); ++i) {
|
|
|
|
const modp_group* it = modp_groups + i;
|
|
|
|
|
|
|
|
if (strcasecmp(*group_name, it->name) != 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
initialized = diffieHellman->Init(it->prime,
|
|
|
|
it->prime_size,
|
|
|
|
it->gen,
|
|
|
|
it->gen_size);
|
|
|
|
if (!initialized)
|
|
|
|
env->ThrowError("Initialization failed");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
env->ThrowError("Unknown group");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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]->Int32Value(),
|
|
|
|
args[1]->Int32Value());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (args[1]->IsInt32()) {
|
|
|
|
initialized = diffieHellman->Init(Buffer::Data(args[0]),
|
|
|
|
Buffer::Length(args[0]),
|
|
|
|
args[1]->Int32Value());
|
|
|
|
} else {
|
|
|
|
initialized = diffieHellman->Init(Buffer::Data(args[0]),
|
|
|
|
Buffer::Length(args[0]),
|
|
|
|
Buffer::Data(args[1]),
|
|
|
|
Buffer::Length(args[1]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!initialized) {
|
|
|
|
return ThrowCryptoError(env, ERR_get_error(), "Initialization failed");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.Holder());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowCryptoError(env, ERR_get_error(), "Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!DH_generate_key(diffieHellman->dh)) {
|
|
|
|
return ThrowCryptoError(env, ERR_get_error(), "Key generation failed");
|
|
|
|
}
|
|
|
|
|
|
|
|
int dataSize = BN_num_bytes(diffieHellman->dh->pub_key);
|
|
|
|
char* data = new char[dataSize];
|
|
|
|
BN_bn2bin(diffieHellman->dh->pub_key,
|
|
|
|
reinterpret_cast<unsigned char*>(data));
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(Encode(env->isolate(), data, dataSize, BUFFER));
|
|
|
|
delete[] data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::GetPrime(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.Holder());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowCryptoError(env, ERR_get_error(), "Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
int dataSize = BN_num_bytes(diffieHellman->dh->p);
|
|
|
|
char* data = new char[dataSize];
|
|
|
|
BN_bn2bin(diffieHellman->dh->p, reinterpret_cast<unsigned char*>(data));
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(Encode(env->isolate(), data, dataSize, BUFFER));
|
|
|
|
delete[] data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::GetGenerator(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.Holder());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowCryptoError(env, ERR_get_error(), "Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
int dataSize = BN_num_bytes(diffieHellman->dh->g);
|
|
|
|
char* data = new char[dataSize];
|
|
|
|
BN_bn2bin(diffieHellman->dh->g, reinterpret_cast<unsigned char*>(data));
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(Encode(env->isolate(), data, dataSize, BUFFER));
|
|
|
|
delete[] data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::GetPublicKey(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.Holder());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowCryptoError(env, ERR_get_error(), "Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (diffieHellman->dh->pub_key == nullptr) {
|
|
|
|
return env->ThrowError("No public key - did you forget to generate one?");
|
|
|
|
}
|
|
|
|
|
|
|
|
int dataSize = BN_num_bytes(diffieHellman->dh->pub_key);
|
|
|
|
char* data = new char[dataSize];
|
|
|
|
BN_bn2bin(diffieHellman->dh->pub_key,
|
|
|
|
reinterpret_cast<unsigned char*>(data));
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(Encode(env->isolate(), data, dataSize, BUFFER));
|
|
|
|
delete[] data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.Holder());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowCryptoError(env, ERR_get_error(), "Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (diffieHellman->dh->priv_key == nullptr) {
|
|
|
|
return env->ThrowError("No private key - did you forget to generate one?");
|
|
|
|
}
|
|
|
|
|
|
|
|
int dataSize = BN_num_bytes(diffieHellman->dh->priv_key);
|
|
|
|
char* data = new char[dataSize];
|
|
|
|
BN_bn2bin(diffieHellman->dh->priv_key,
|
|
|
|
reinterpret_cast<unsigned char*>(data));
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(Encode(env->isolate(), data, dataSize, BUFFER));
|
|
|
|
delete[] data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.Holder());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowCryptoError(env, ERR_get_error(), "Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence compiler warning.
|
|
|
|
BIGNUM* key = nullptr;
|
|
|
|
|
|
|
|
if (args.Length() == 0) {
|
|
|
|
return env->ThrowError("First argument must be other party's public key");
|
|
|
|
} else {
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
key = BN_bin2bn(
|
|
|
|
reinterpret_cast<unsigned char*>(Buffer::Data(args[0])),
|
|
|
|
Buffer::Length(args[0]),
|
|
|
|
0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int dataSize = DH_size(diffieHellman->dh);
|
|
|
|
char* data = new char[dataSize];
|
|
|
|
|
|
|
|
int size = DH_compute_key(reinterpret_cast<unsigned char*>(data),
|
|
|
|
key,
|
|
|
|
diffieHellman->dh);
|
|
|
|
|
|
|
|
if (size == -1) {
|
|
|
|
int checkResult;
|
|
|
|
int checked;
|
|
|
|
|
|
|
|
checked = DH_check_pub_key(diffieHellman->dh, key, &checkResult);
|
|
|
|
BN_free(key);
|
|
|
|
delete[] data;
|
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BN_free(key);
|
|
|
|
CHECK_GE(size, 0);
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
if (size != dataSize) {
|
|
|
|
CHECK(dataSize > size);
|
|
|
|
memmove(data + dataSize - size, data, size);
|
|
|
|
memset(data, 0, dataSize - size);
|
|
|
|
}
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(Encode(env->isolate(), data, dataSize, BUFFER));
|
|
|
|
delete[] data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::SetPublicKey(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.Holder());
|
|
|
|
Environment* env = diffieHellman->env();
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowCryptoError(env, ERR_get_error(), "Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args.Length() == 0) {
|
|
|
|
return env->ThrowError("First argument must be public key");
|
|
|
|
} else {
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
diffieHellman->dh->pub_key = BN_bin2bn(
|
|
|
|
reinterpret_cast<unsigned char*>(Buffer::Data(args[0])),
|
|
|
|
Buffer::Length(args[0]), 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::SetPrivateKey(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.Holder());
|
|
|
|
Environment* env = diffieHellman->env();
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowCryptoError(env, ERR_get_error(), "Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args.Length() == 0) {
|
|
|
|
return env->ThrowError("First argument must be private key");
|
|
|
|
} else {
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
diffieHellman->dh->priv_key = BN_bin2bn(
|
|
|
|
reinterpret_cast<unsigned char*>(Buffer::Data(args[0])),
|
|
|
|
Buffer::Length(args[0]),
|
|
|
|
0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::VerifyErrorGetter(Local<String> property,
|
|
|
|
const PropertyCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(args.GetIsolate());
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.Holder());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_)
|
|
|
|
return ThrowCryptoError(diffieHellman->env(), ERR_get_error(),
|
|
|
|
"Not initialized");
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(diffieHellman->verifyError_);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DiffieHellman::VerifyContext() {
|
|
|
|
int codes;
|
|
|
|
if (!DH_check(dh, &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->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
env->SetProtoMethod(t, "generateKeys", GenerateKeys);
|
|
|
|
env->SetProtoMethod(t, "computeSecret", ComputeSecret);
|
|
|
|
env->SetProtoMethod(t, "getPublicKey", GetPublicKey);
|
|
|
|
env->SetProtoMethod(t, "getPrivateKey", GetPrivateKey);
|
|
|
|
env->SetProtoMethod(t, "setPublicKey", SetPublicKey);
|
|
|
|
env->SetProtoMethod(t, "setPrivateKey", SetPrivateKey);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "ECDH"),
|
|
|
|
t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 env->ThrowTypeError("First argument should be a valid curve name");
|
|
|
|
|
|
|
|
EC_KEY* key = EC_KEY_new_by_curve_name(nid);
|
|
|
|
if (key == nullptr)
|
|
|
|
return env->ThrowError("Failed to create EC_KEY using curve name");
|
|
|
|
|
|
|
|
new ECDH(env, args.This(), key);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ECDH::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
ECDH* ecdh = Unwrap<ECDH>(args.Holder());
|
|
|
|
|
|
|
|
if (!EC_KEY_generate_key(ecdh->key_))
|
|
|
|
return env->ThrowError("Failed to generate EC_KEY");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EC_POINT* ECDH::BufferToPoint(char* data, size_t len) {
|
|
|
|
EC_POINT* pub;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
pub = EC_POINT_new(group_);
|
|
|
|
if (pub == nullptr) {
|
|
|
|
env()->ThrowError("Failed to allocate EC_POINT for a public key");
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = EC_POINT_oct2point(
|
|
|
|
group_,
|
|
|
|
pub,
|
|
|
|
reinterpret_cast<unsigned char*>(data),
|
|
|
|
len,
|
|
|
|
nullptr);
|
|
|
|
if (!r) {
|
|
|
|
env()->ThrowError("Failed to translate Buffer to a EC_POINT");
|
|
|
|
goto fatal;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pub;
|
|
|
|
|
|
|
|
fatal:
|
|
|
|
EC_POINT_free(pub);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ECDH::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
|
|
|
|
ECDH* ecdh = Unwrap<ECDH>(args.Holder());
|
|
|
|
|
|
|
|
if (!ecdh->IsKeyPairValid())
|
|
|
|
return env->ThrowError("Invalid key pair");
|
|
|
|
|
|
|
|
EC_POINT* pub = ecdh->BufferToPoint(Buffer::Data(args[0]),
|
|
|
|
Buffer::Length(args[0]));
|
|
|
|
if (pub == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// NOTE: field_size is in bits
|
|
|
|
int field_size = EC_GROUP_get_degree(ecdh->group_);
|
|
|
|
size_t out_len = (field_size + 7) / 8;
|
|
|
|
char* out = static_cast<char*>(malloc(out_len));
|
|
|
|
CHECK_NE(out, nullptr);
|
|
|
|
|
|
|
|
int r = ECDH_compute_key(out, out_len, pub, ecdh->key_, nullptr);
|
|
|
|
EC_POINT_free(pub);
|
|
|
|
if (!r) {
|
|
|
|
free(out);
|
|
|
|
return env->ThrowError("Failed to compute ECDH key");
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Object> buf = Buffer::New(env, out, out_len).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 = Unwrap<ECDH>(args.Holder());
|
|
|
|
|
|
|
|
const EC_POINT* pub = EC_KEY_get0_public_key(ecdh->key_);
|
|
|
|
if (pub == nullptr)
|
|
|
|
return env->ThrowError("Failed to get ECDH public key");
|
|
|
|
|
|
|
|
int size;
|
|
|
|
point_conversion_form_t form =
|
|
|
|
static_cast<point_conversion_form_t>(args[0]->Uint32Value());
|
|
|
|
|
|
|
|
size = EC_POINT_point2oct(ecdh->group_, pub, form, nullptr, 0, nullptr);
|
|
|
|
if (size == 0)
|
|
|
|
return env->ThrowError("Failed to get public key length");
|
|
|
|
|
|
|
|
unsigned char* out = static_cast<unsigned char*>(malloc(size));
|
|
|
|
CHECK_NE(out, nullptr);
|
|
|
|
|
|
|
|
int r = EC_POINT_point2oct(ecdh->group_, pub, form, out, size, nullptr);
|
|
|
|
if (r != size) {
|
|
|
|
free(out);
|
|
|
|
return env->ThrowError("Failed to get public key");
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Object> buf =
|
|
|
|
Buffer::New(env, reinterpret_cast<char*>(out), size).ToLocalChecked();
|
|
|
|
args.GetReturnValue().Set(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ECDH::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
ECDH* ecdh = Unwrap<ECDH>(args.Holder());
|
|
|
|
|
|
|
|
const BIGNUM* b = EC_KEY_get0_private_key(ecdh->key_);
|
|
|
|
if (b == nullptr)
|
|
|
|
return env->ThrowError("Failed to get ECDH private key");
|
|
|
|
|
|
|
|
int size = BN_num_bytes(b);
|
|
|
|
unsigned char* out = static_cast<unsigned char*>(malloc(size));
|
|
|
|
CHECK_NE(out, nullptr);
|
|
|
|
|
|
|
|
if (size != BN_bn2bin(b, out)) {
|
|
|
|
free(out);
|
|
|
|
return env->ThrowError("Failed to convert ECDH private key to Buffer");
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Object> buf =
|
|
|
|
Buffer::New(env, reinterpret_cast<char*>(out), size).ToLocalChecked();
|
|
|
|
args.GetReturnValue().Set(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ECDH::SetPrivateKey(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
ECDH* ecdh = Unwrap<ECDH>(args.Holder());
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
|
|
|
|
BIGNUM* priv = BN_bin2bn(
|
|
|
|
reinterpret_cast<unsigned char*>(Buffer::Data(args[0].As<Object>())),
|
|
|
|
Buffer::Length(args[0].As<Object>()),
|
|
|
|
nullptr);
|
|
|
|
if (priv == nullptr)
|
|
|
|
return env->ThrowError("Failed to convert Buffer to BN");
|
|
|
|
|
|
|
|
if (!ecdh->IsKeyValidForCurve(priv)) {
|
|
|
|
BN_free(priv);
|
|
|
|
return env->ThrowError("Private key is not valid for specified curve.");
|
|
|
|
}
|
|
|
|
|
|
|
|
int result = EC_KEY_set_private_key(ecdh->key_, priv);
|
|
|
|
BN_free(priv);
|
|
|
|
|
|
|
|
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_, nullptr);
|
|
|
|
|
|
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
(void) &mark_pop_error_on_return; // Silence compiler warning.
|
|
|
|
|
|
|
|
const BIGNUM* priv_key = EC_KEY_get0_private_key(ecdh->key_);
|
|
|
|
CHECK_NE(priv_key, nullptr);
|
|
|
|
|
|
|
|
EC_POINT* pub = EC_POINT_new(ecdh->group_);
|
|
|
|
CHECK_NE(pub, nullptr);
|
|
|
|
|
|
|
|
if (!EC_POINT_mul(ecdh->group_, pub, priv_key, nullptr, nullptr, nullptr)) {
|
|
|
|
EC_POINT_free(pub);
|
|
|
|
return env->ThrowError("Failed to generate ECDH public key");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!EC_KEY_set_public_key(ecdh->key_, pub)) {
|
|
|
|
EC_POINT_free(pub);
|
|
|
|
return env->ThrowError("Failed to set generated public key");
|
|
|
|
}
|
|
|
|
|
|
|
|
EC_POINT_free(pub);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ECDH::SetPublicKey(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
ECDH* ecdh = Unwrap<ECDH>(args.Holder());
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
|
|
|
|
EC_POINT* pub = ecdh->BufferToPoint(Buffer::Data(args[0].As<Object>()),
|
|
|
|
Buffer::Length(args[0].As<Object>()));
|
|
|
|
if (pub == nullptr)
|
|
|
|
return env->ThrowError("Failed to convert Buffer to EC_POINT");
|
|
|
|
|
|
|
|
int r = EC_KEY_set_public_key(ecdh->key_, pub);
|
|
|
|
EC_POINT_free(pub);
|
|
|
|
if (!r)
|
|
|
|
return env->ThrowError("Failed to set EC_POINT as the public key");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ECDH::IsKeyValidForCurve(const BIGNUM* private_key) {
|
|
|
|
ASSERT_NE(group_, nullptr);
|
|
|
|
CHECK_NE(private_key, nullptr);
|
|
|
|
// 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, BN_value_one()) < 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
BIGNUM* order = BN_new();
|
|
|
|
CHECK_NE(order, nullptr);
|
|
|
|
bool result = EC_GROUP_get_order(group_, order, nullptr) &&
|
|
|
|
BN_cmp(private_key, order) < 0;
|
|
|
|
BN_free(order);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ECDH::IsKeyPairValid() {
|
|
|
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
|
|
(void) &mark_pop_error_on_return; // Silence compiler warning.
|
|
|
|
return 1 == EC_KEY_check_key(key_);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class PBKDF2Request : public AsyncWrap {
|
|
|
|
public:
|
|
|
|
PBKDF2Request(Environment* env,
|
|
|
|
Local<Object> object,
|
|
|
|
const EVP_MD* digest,
|
|
|
|
ssize_t passlen,
|
|
|
|
char* pass,
|
|
|
|
ssize_t saltlen,
|
|
|
|
char* salt,
|
|
|
|
ssize_t iter,
|
|
|
|
ssize_t keylen)
|
|
|
|
: AsyncWrap(env, object, AsyncWrap::PROVIDER_CRYPTO),
|
|
|
|
digest_(digest),
|
|
|
|
error_(0),
|
|
|
|
passlen_(passlen),
|
|
|
|
pass_(pass),
|
|
|
|
saltlen_(saltlen),
|
|
|
|
salt_(salt),
|
|
|
|
keylen_(keylen),
|
|
|
|
key_(static_cast<char*>(malloc(keylen))),
|
|
|
|
iter_(iter) {
|
|
|
|
if (key() == nullptr)
|
|
|
|
FatalError("node::PBKDF2Request()", "Out of Memory");
|
|
|
|
Wrap(object, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
~PBKDF2Request() override {
|
|
|
|
release();
|
|
|
|
persistent().Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
uv_work_t* work_req() {
|
|
|
|
return &work_req_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline const EVP_MD* digest() const {
|
|
|
|
return digest_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline ssize_t passlen() const {
|
|
|
|
return passlen_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline char* pass() const {
|
|
|
|
return pass_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline ssize_t saltlen() const {
|
|
|
|
return saltlen_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline char* salt() const {
|
|
|
|
return salt_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline ssize_t keylen() const {
|
|
|
|
return keylen_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline char* key() const {
|
|
|
|
return key_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline ssize_t iter() const {
|
|
|
|
return iter_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void release() {
|
|
|
|
free(pass_);
|
|
|
|
pass_ = nullptr;
|
|
|
|
passlen_ = 0;
|
|
|
|
|
|
|
|
free(salt_);
|
|
|
|
salt_ = nullptr;
|
|
|
|
saltlen_ = 0;
|
|
|
|
|
|
|
|
free(key_);
|
|
|
|
key_ = nullptr;
|
|
|
|
keylen_ = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline int error() const {
|
|
|
|
return error_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void set_error(int err) {
|
|
|
|
error_ = err;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t self_size() const override { return sizeof(*this); }
|
|
|
|
|
|
|
|
uv_work_t work_req_;
|
|
|
|
|
|
|
|
private:
|
|
|
|
const EVP_MD* digest_;
|
|
|
|
int error_;
|
|
|
|
ssize_t passlen_;
|
|
|
|
char* pass_;
|
|
|
|
ssize_t saltlen_;
|
|
|
|
char* salt_;
|
|
|
|
ssize_t keylen_;
|
|
|
|
char* key_;
|
|
|
|
ssize_t iter_;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void EIO_PBKDF2(PBKDF2Request* req) {
|
|
|
|
req->set_error(PKCS5_PBKDF2_HMAC(
|
|
|
|
req->pass(),
|
|
|
|
req->passlen(),
|
|
|
|
reinterpret_cast<unsigned char*>(req->salt()),
|
|
|
|
req->saltlen(),
|
|
|
|
req->iter(),
|
|
|
|
req->digest(),
|
|
|
|
req->keylen(),
|
|
|
|
reinterpret_cast<unsigned char*>(req->key())));
|
|
|
|
OPENSSL_cleanse(req->pass(), req->passlen());
|
|
|
|
OPENSSL_cleanse(req->salt(), req->saltlen());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void EIO_PBKDF2(uv_work_t* work_req) {
|
|
|
|
PBKDF2Request* req = ContainerOf(&PBKDF2Request::work_req_, work_req);
|
|
|
|
EIO_PBKDF2(req);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void EIO_PBKDF2After(PBKDF2Request* req, Local<Value> argv[2]) {
|
|
|
|
if (req->error()) {
|
|
|
|
argv[0] = Undefined(req->env()->isolate());
|
|
|
|
argv[1] = Encode(req->env()->isolate(), req->key(), req->keylen(), BUFFER);
|
|
|
|
OPENSSL_cleanse(req->key(), req->keylen());
|
|
|
|
} else {
|
|
|
|
argv[0] = Exception::Error(req->env()->pbkdf2_error_string());
|
|
|
|
argv[1] = Undefined(req->env()->isolate());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void EIO_PBKDF2After(uv_work_t* work_req, int status) {
|
|
|
|
CHECK_EQ(status, 0);
|
|
|
|
PBKDF2Request* req = ContainerOf(&PBKDF2Request::work_req_, work_req);
|
|
|
|
Environment* env = req->env();
|
|
|
|
HandleScope handle_scope(env->isolate());
|
|
|
|
Context::Scope context_scope(env->context());
|
|
|
|
Local<Value> argv[2];
|
|
|
|
EIO_PBKDF2After(req, argv);
|
|
|
|
req->MakeCallback(env->ondone_string(), ARRAY_SIZE(argv), argv);
|
|
|
|
delete req;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PBKDF2(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
const EVP_MD* digest = nullptr;
|
|
|
|
const char* type_error = nullptr;
|
|
|
|
char* pass = nullptr;
|
|
|
|
char* salt = nullptr;
|
|
|
|
ssize_t passlen = -1;
|
|
|
|
ssize_t saltlen = -1;
|
|
|
|
double keylen = -1;
|
|
|
|
ssize_t iter = -1;
|
|
|
|
PBKDF2Request* req = nullptr;
|
|
|
|
Local<Object> obj;
|
|
|
|
|
|
|
|
if (args.Length() != 5 && args.Length() != 6) {
|
|
|
|
type_error = "Bad parameter";
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
passlen = Buffer::Length(args[0]);
|
|
|
|
if (passlen < 0) {
|
|
|
|
type_error = "Bad password";
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[1]);
|
|
|
|
|
|
|
|
pass = static_cast<char*>(malloc(passlen));
|
|
|
|
if (pass == nullptr) {
|
|
|
|
FatalError("node::PBKDF2()", "Out of Memory");
|
|
|
|
}
|
|
|
|
memcpy(pass, Buffer::Data(args[0]), passlen);
|
|
|
|
|
|
|
|
saltlen = Buffer::Length(args[1]);
|
|
|
|
if (saltlen < 0) {
|
|
|
|
type_error = "Bad salt";
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
salt = static_cast<char*>(malloc(saltlen));
|
|
|
|
if (salt == nullptr) {
|
|
|
|
FatalError("node::PBKDF2()", "Out of Memory");
|
|
|
|
}
|
|
|
|
memcpy(salt, Buffer::Data(args[1]), saltlen);
|
|
|
|
|
|
|
|
if (!args[2]->IsNumber()) {
|
|
|
|
type_error = "Iterations not a number";
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
iter = args[2]->Int32Value();
|
|
|
|
if (iter < 0) {
|
|
|
|
type_error = "Bad iterations";
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!args[3]->IsNumber()) {
|
|
|
|
type_error = "Key length not a number";
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
keylen = args[3]->NumberValue();
|
|
|
|
if (keylen < 0 || isnan(keylen) || isinf(keylen)) {
|
|
|
|
type_error = "Bad key length";
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args[4]->IsString()) {
|
|
|
|
node::Utf8Value digest_name(env->isolate(), args[4]);
|
|
|
|
digest = EVP_get_digestbyname(*digest_name);
|
|
|
|
if (digest == nullptr) {
|
|
|
|
type_error = "Bad digest name";
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (digest == nullptr) {
|
|
|
|
digest = EVP_sha1();
|
|
|
|
}
|
|
|
|
|
|
|
|
obj = env->NewInternalFieldObject();
|
|
|
|
req = new PBKDF2Request(env,
|
|
|
|
obj,
|
|
|
|
digest,
|
|
|
|
passlen,
|
|
|
|
pass,
|
|
|
|
saltlen,
|
|
|
|
salt,
|
|
|
|
iter,
|
|
|
|
static_cast<ssize_t>(keylen));
|
|
|
|
|
|
|
|
if (args[5]->IsFunction()) {
|
|
|
|
obj->Set(env->ondone_string(), args[5]);
|
|
|
|
|
|
|
|
if (env->in_domain())
|
|
|
|
obj->Set(env->domain_string(), env->domain_array()->Get(0));
|
|
|
|
uv_queue_work(env->event_loop(),
|
|
|
|
req->work_req(),
|
|
|
|
EIO_PBKDF2,
|
|
|
|
EIO_PBKDF2After);
|
|
|
|
} else {
|
|
|
|
env->PrintSyncTrace();
|
|
|
|
Local<Value> argv[2];
|
|
|
|
EIO_PBKDF2(req);
|
|
|
|
EIO_PBKDF2After(req, argv);
|
|
|
|
|
|
|
|
delete req;
|
|
|
|
|
|
|
|
if (argv[0]->IsObject())
|
|
|
|
env->isolate()->ThrowException(argv[0]);
|
|
|
|
else
|
|
|
|
args.GetReturnValue().Set(argv[1]);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
err:
|
|
|
|
free(salt);
|
|
|
|
free(pass);
|
|
|
|
return env->ThrowTypeError(type_error);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Only instantiate within a valid HandleScope.
|
|
|
|
class RandomBytesRequest : public AsyncWrap {
|
|
|
|
public:
|
|
|
|
RandomBytesRequest(Environment* env, Local<Object> object, size_t size)
|
|
|
|
: AsyncWrap(env, object, AsyncWrap::PROVIDER_CRYPTO),
|
|
|
|
error_(0),
|
|
|
|
size_(size),
|
|
|
|
data_(static_cast<char*>(malloc(size))) {
|
|
|
|
if (data() == nullptr)
|
|
|
|
FatalError("node::RandomBytesRequest()", "Out of Memory");
|
|
|
|
Wrap(object, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
~RandomBytesRequest() override {
|
|
|
|
persistent().Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
uv_work_t* work_req() {
|
|
|
|
return &work_req_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline size_t size() const {
|
|
|
|
return size_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline char* data() const {
|
|
|
|
return data_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void release() {
|
|
|
|
free(data_);
|
|
|
|
size_ = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void return_memory(char** d, size_t* len) {
|
|
|
|
*d = data_;
|
|
|
|
data_ = nullptr;
|
|
|
|
*len = size_;
|
|
|
|
size_ = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline unsigned long error() const {
|
|
|
|
return error_;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void set_error(unsigned long err) {
|
|
|
|
error_ = err;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t self_size() const override { return sizeof(*this); }
|
|
|
|
|
|
|
|
uv_work_t work_req_;
|
|
|
|
|
|
|
|
private:
|
|
|
|
unsigned long error_;
|
|
|
|
size_t size_;
|
|
|
|
char* data_;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void RandomBytesWork(uv_work_t* work_req) {
|
|
|
|
RandomBytesRequest* req =
|
|
|
|
ContainerOf(&RandomBytesRequest::work_req_, work_req);
|
|
|
|
|
|
|
|
// Ensure that OpenSSL's PRNG is properly seeded.
|
|
|
|
CheckEntropy();
|
|
|
|
|
|
|
|
const int r = RAND_bytes(reinterpret_cast<unsigned char*>(req->data()),
|
|
|
|
req->size());
|
|
|
|
|
|
|
|
// RAND_bytes() returns 0 on error.
|
|
|
|
if (r == 0) {
|
|
|
|
req->set_error(ERR_get_error());
|
|
|
|
} else if (r == -1) {
|
|
|
|
req->set_error(static_cast<unsigned long>(-1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// don't call this function without a valid HandleScope
|
|
|
|
void RandomBytesCheck(RandomBytesRequest* req, Local<Value> argv[2]) {
|
|
|
|
if (req->error()) {
|
|
|
|
char errmsg[256] = "Operation not supported";
|
|
|
|
|
|
|
|
if (req->error() != static_cast<unsigned long>(-1))
|
|
|
|
ERR_error_string_n(req->error(), errmsg, sizeof errmsg);
|
|
|
|
|
|
|
|
argv[0] = Exception::Error(OneByteString(req->env()->isolate(), errmsg));
|
|
|
|
argv[1] = Null(req->env()->isolate());
|
|
|
|
req->release();
|
|
|
|
} else {
|
|
|
|
char* data = nullptr;
|
|
|
|
size_t size;
|
|
|
|
req->return_memory(&data, &size);
|
|
|
|
argv[0] = Null(req->env()->isolate());
|
|
|
|
argv[1] = Buffer::New(req->env(), data, size).ToLocalChecked();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void RandomBytesAfter(uv_work_t* work_req, int status) {
|
|
|
|
CHECK_EQ(status, 0);
|
|
|
|
RandomBytesRequest* req =
|
|
|
|
ContainerOf(&RandomBytesRequest::work_req_, work_req);
|
|
|
|
Environment* env = req->env();
|
|
|
|
HandleScope handle_scope(env->isolate());
|
|
|
|
Context::Scope context_scope(env->context());
|
|
|
|
Local<Value> argv[2];
|
|
|
|
RandomBytesCheck(req, argv);
|
|
|
|
req->MakeCallback(env->ondone_string(), ARRAY_SIZE(argv), argv);
|
|
|
|
delete req;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void RandomBytes(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
// maybe allow a buffer to write to? cuts down on object creation
|
|
|
|
// when generating random data in a loop
|
|
|
|
if (!args[0]->IsUint32()) {
|
|
|
|
return env->ThrowTypeError("size must be a number >= 0");
|
|
|
|
}
|
|
|
|
|
|
|
|
const int64_t size = args[0]->IntegerValue();
|
|
|
|
if (size < 0 || size > Buffer::kMaxLength)
|
|
|
|
return env->ThrowRangeError("size is not a valid Smi");
|
|
|
|
|
|
|
|
Local<Object> obj = env->NewInternalFieldObject();
|
|
|
|
RandomBytesRequest* req = new RandomBytesRequest(env, obj, size);
|
|
|
|
|
|
|
|
if (args[1]->IsFunction()) {
|
|
|
|
obj->Set(FIXED_ONE_BYTE_STRING(args.GetIsolate(), "ondone"), args[1]);
|
|
|
|
|
|
|
|
if (env->in_domain())
|
|
|
|
obj->Set(env->domain_string(), env->domain_array()->Get(0));
|
|
|
|
uv_queue_work(env->event_loop(),
|
|
|
|
req->work_req(),
|
|
|
|
RandomBytesWork,
|
|
|
|
RandomBytesAfter);
|
|
|
|
args.GetReturnValue().Set(obj);
|
|
|
|
} else {
|
|
|
|
env->PrintSyncTrace();
|
|
|
|
Local<Value> argv[2];
|
|
|
|
RandomBytesWork(req->work_req());
|
|
|
|
RandomBytesCheck(req, argv);
|
|
|
|
delete req;
|
|
|
|
|
|
|
|
if (!argv[0]->IsNull())
|
|
|
|
env->isolate()->ThrowException(argv[0]);
|
|
|
|
else
|
|
|
|
args.GetReturnValue().Set(argv[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
SSL_CTX* ctx = SSL_CTX_new(TLSv1_server_method());
|
|
|
|
if (ctx == nullptr) {
|
|
|
|
return env->ThrowError("SSL_CTX_new() failed.");
|
|
|
|
}
|
|
|
|
|
|
|
|
SSL* ssl = SSL_new(ctx);
|
|
|
|
if (ssl == nullptr) {
|
|
|
|
SSL_CTX_free(ctx);
|
|
|
|
return env->ThrowError("SSL_new() failed.");
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Array> arr = Array::New(env->isolate());
|
|
|
|
STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(ssl);
|
|
|
|
|
|
|
|
for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); ++i) {
|
|
|
|
const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i);
|
|
|
|
arr->Set(i, OneByteString(args.GetIsolate(), SSL_CIPHER_get_name(cipher)));
|
|
|
|
}
|
|
|
|
|
|
|
|
SSL_free(ssl);
|
|
|
|
SSL_CTX_free(ctx);
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(arr);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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->arr->Length(), OneByteString(ctx->env()->isolate(), from));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
Local<Array> arr = Array::New(env->isolate(), num_curves);
|
|
|
|
EC_builtin_curve* curves;
|
|
|
|
size_t alloc_size;
|
|
|
|
|
|
|
|
if (num_curves) {
|
|
|
|
alloc_size = sizeof(*curves) * num_curves;
|
|
|
|
curves = static_cast<EC_builtin_curve*>(malloc(alloc_size));
|
|
|
|
|
|
|
|
CHECK_NE(curves, nullptr);
|
|
|
|
|
|
|
|
if (EC_get_builtin_curves(curves, num_curves)) {
|
|
|
|
for (size_t i = 0; i < num_curves; i++) {
|
|
|
|
arr->Set(i, OneByteString(env->isolate(), OBJ_nid2sn(curves[i].nid)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
free(curves);
|
|
|
|
}
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(arr);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Certificate::Initialize(Environment* env, Local<Object> target) {
|
|
|
|
HandleScope scope(env->isolate());
|
|
|
|
|
|
|
|
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
env->SetProtoMethod(t, "verifySpkac", VerifySpkac);
|
|
|
|
env->SetProtoMethod(t, "exportPublicKey", ExportPublicKey);
|
|
|
|
env->SetProtoMethod(t, "exportChallenge", ExportChallenge);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Certificate"),
|
|
|
|
t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Certificate::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
new Certificate(env, args.This());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Certificate::VerifySpkac(const char* data, unsigned int len) {
|
|
|
|
bool i = 0;
|
|
|
|
EVP_PKEY* pkey = nullptr;
|
|
|
|
NETSCAPE_SPKI* spki = nullptr;
|
|
|
|
|
|
|
|
spki = NETSCAPE_SPKI_b64_decode(data, len);
|
|
|
|
if (spki == nullptr)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
pkey = X509_PUBKEY_get(spki->spkac->pubkey);
|
|
|
|
if (pkey == nullptr)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
i = NETSCAPE_SPKI_verify(spki, pkey) > 0;
|
|
|
|
|
|
|
|
exit:
|
|
|
|
if (pkey != nullptr)
|
|
|
|
EVP_PKEY_free(pkey);
|
|
|
|
|
|
|
|
if (spki != nullptr)
|
|
|
|
NETSCAPE_SPKI_free(spki);
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Certificate::VerifySpkac(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Certificate* certificate = Unwrap<Certificate>(args.Holder());
|
|
|
|
Environment* env = certificate->env();
|
|
|
|
bool i = false;
|
|
|
|
|
|
|
|
if (args.Length() < 1)
|
|
|
|
return env->ThrowTypeError("Missing argument");
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
|
|
|
|
size_t length = Buffer::Length(args[0]);
|
|
|
|
if (length == 0)
|
|
|
|
return args.GetReturnValue().Set(i);
|
|
|
|
|
|
|
|
char* data = Buffer::Data(args[0]);
|
|
|
|
CHECK_NE(data, nullptr);
|
|
|
|
|
|
|
|
i = certificate->VerifySpkac(data, length);
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char* Certificate::ExportPublicKey(const char* data, int len) {
|
|
|
|
char* buf = nullptr;
|
|
|
|
EVP_PKEY* pkey = nullptr;
|
|
|
|
NETSCAPE_SPKI* spki = nullptr;
|
|
|
|
|
|
|
|
BIO* bio = BIO_new(BIO_s_mem());
|
|
|
|
if (bio == nullptr)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
spki = NETSCAPE_SPKI_b64_decode(data, len);
|
|
|
|
if (spki == nullptr)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
pkey = NETSCAPE_SPKI_get_pubkey(spki);
|
|
|
|
if (pkey == nullptr)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
if (PEM_write_bio_PUBKEY(bio, pkey) <= 0)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
BIO_write(bio, "\0", 1);
|
|
|
|
BUF_MEM* ptr;
|
|
|
|
BIO_get_mem_ptr(bio, &ptr);
|
|
|
|
|
|
|
|
buf = new char[ptr->length];
|
|
|
|
memcpy(buf, ptr->data, ptr->length);
|
|
|
|
|
|
|
|
exit:
|
|
|
|
if (pkey != nullptr)
|
|
|
|
EVP_PKEY_free(pkey);
|
|
|
|
|
|
|
|
if (spki != nullptr)
|
|
|
|
NETSCAPE_SPKI_free(spki);
|
|
|
|
|
|
|
|
if (bio != nullptr)
|
|
|
|
BIO_free_all(bio);
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Certificate::ExportPublicKey(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
Certificate* certificate = Unwrap<Certificate>(args.Holder());
|
|
|
|
|
|
|
|
if (args.Length() < 1)
|
|
|
|
return env->ThrowTypeError("Missing argument");
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
|
|
|
|
size_t length = Buffer::Length(args[0]);
|
|
|
|
if (length == 0)
|
|
|
|
return args.GetReturnValue().SetEmptyString();
|
|
|
|
|
|
|
|
char* data = Buffer::Data(args[0]);
|
|
|
|
CHECK_NE(data, nullptr);
|
|
|
|
|
|
|
|
const char* pkey = certificate->ExportPublicKey(data, length);
|
|
|
|
if (pkey == nullptr)
|
|
|
|
return args.GetReturnValue().SetEmptyString();
|
|
|
|
|
|
|
|
Local<Value> out = Encode(env->isolate(), pkey, strlen(pkey), BUFFER);
|
|
|
|
|
|
|
|
delete[] pkey;
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(out);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char* Certificate::ExportChallenge(const char* data, int len) {
|
|
|
|
NETSCAPE_SPKI* sp = nullptr;
|
|
|
|
|
|
|
|
sp = NETSCAPE_SPKI_b64_decode(data, len);
|
|
|
|
if (sp == nullptr)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
unsigned char* buf = nullptr;
|
|
|
|
ASN1_STRING_to_UTF8(&buf, sp->spkac->challenge);
|
|
|
|
|
|
|
|
NETSCAPE_SPKI_free(sp);
|
|
|
|
|
|
|
|
return reinterpret_cast<const char*>(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Certificate::ExportChallenge(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
|
|
|
|
Certificate* crt = Unwrap<Certificate>(args.Holder());
|
|
|
|
|
|
|
|
if (args.Length() < 1)
|
|
|
|
return env->ThrowTypeError("Missing argument");
|
|
|
|
|
|
|
|
THROW_AND_RETURN_IF_NOT_BUFFER(args[0]);
|
|
|
|
|
|
|
|
size_t len = Buffer::Length(args[0]);
|
|
|
|
if (len == 0)
|
|
|
|
return args.GetReturnValue().SetEmptyString();
|
|
|
|
|
|
|
|
char* data = Buffer::Data(args[0]);
|
|
|
|
CHECK_NE(data, nullptr);
|
|
|
|
|
|
|
|
const char* cert = crt->ExportChallenge(data, len);
|
|
|
|
if (cert == nullptr)
|
|
|
|
return args.GetReturnValue().SetEmptyString();
|
|
|
|
|
|
|
|
Local<Value> outString = Encode(env->isolate(), cert, strlen(cert), BUFFER);
|
|
|
|
|
|
|
|
OPENSSL_free(const_cast<char*>(cert));
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(outString);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void InitCryptoOnce() {
|
|
|
|
SSL_library_init();
|
|
|
|
OpenSSL_add_all_algorithms();
|
|
|
|
SSL_load_error_strings();
|
|
|
|
|
|
|
|
crypto_lock_init();
|
|
|
|
CRYPTO_set_locking_callback(crypto_lock_cb);
|
|
|
|
CRYPTO_THREADID_set_callback(crypto_threadid_cb);
|
|
|
|
|
|
|
|
#ifdef NODE_FIPS_MODE
|
|
|
|
if (!FIPS_mode_set(1)) {
|
|
|
|
int err = ERR_get_error();
|
|
|
|
fprintf(stderr, "openssl fips failed: %s\n", ERR_error_string(err, NULL));
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
#endif // NODE_FIPS_MODE
|
|
|
|
|
|
|
|
|
|
|
|
// Turn off compression. Saves memory and protects against CRIME attacks.
|
|
|
|
#if !defined(OPENSSL_NO_COMP)
|
|
|
|
#if OPENSSL_VERSION_NUMBER < 0x00908000L
|
|
|
|
STACK_OF(SSL_COMP)* comp_methods = SSL_COMP_get_compression_method();
|
|
|
|
#else
|
|
|
|
STACK_OF(SSL_COMP)* comp_methods = SSL_COMP_get_compression_methods();
|
|
|
|
#endif
|
|
|
|
sk_SSL_COMP_zero(comp_methods);
|
|
|
|
CHECK_EQ(sk_SSL_COMP_num(comp_methods), 0);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef OPENSSL_NO_ENGINE
|
|
|
|
ERR_load_ENGINE_strings();
|
|
|
|
ENGINE_load_builtin_engines();
|
|
|
|
#endif // !OPENSSL_NO_ENGINE
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef OPENSSL_NO_ENGINE
|
|
|
|
void SetEngine(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
Environment* env = Environment::GetCurrent(args);
|
|
|
|
CHECK(args.Length() >= 2 && args[0]->IsString());
|
|
|
|
unsigned int flags = args[1]->Uint32Value();
|
|
|
|
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence compiler warning.
|
|
|
|
|
|
|
|
const node::Utf8Value engine_id(env->isolate(), args[0]);
|
|
|
|
ENGINE* engine = ENGINE_by_id(*engine_id);
|
|
|
|
|
|
|
|
// Engine not found, try loading dynamically
|
|
|
|
if (engine == nullptr) {
|
|
|
|
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) {
|
|
|
|
char tmp[1024];
|
|
|
|
snprintf(tmp, sizeof(tmp), "Engine \"%s\" was not found", *engine_id);
|
|
|
|
return env->ThrowError(tmp);
|
|
|
|
} else {
|
|
|
|
return ThrowCryptoError(env, err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int r = ENGINE_set_default(engine, flags);
|
|
|
|
ENGINE_free(engine);
|
|
|
|
if (r == 0)
|
|
|
|
return ThrowCryptoError(env, ERR_get_error());
|
|
|
|
}
|
|
|
|
#endif // !OPENSSL_NO_ENGINE
|
|
|
|
|
|
|
|
|
|
|
|
// FIXME(bnoordhuis) Handle global init correctly.
|
|
|
|
void InitCrypto(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);
|
|
|
|
Connection::Initialize(env, target);
|
|
|
|
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);
|
|
|
|
Certificate::Initialize(env, target);
|
|
|
|
|
|
|
|
#ifndef OPENSSL_NO_ENGINE
|
|
|
|
env->SetMethod(target, "setEngine", SetEngine);
|
|
|
|
#endif // !OPENSSL_NO_ENGINE
|
|
|
|
env->SetMethod(target, "PBKDF2", PBKDF2);
|
|
|
|
env->SetMethod(target, "randomBytes", RandomBytes);
|
|
|
|
env->SetMethod(target, "getSSLCiphers", GetSSLCiphers);
|
|
|
|
env->SetMethod(target, "getCiphers", GetCiphers);
|
|
|
|
env->SetMethod(target, "getHashes", GetHashes);
|
|
|
|
env->SetMethod(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>);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace crypto
|
|
|
|
} // namespace node
|
|
|
|
|
|
|
|
NODE_MODULE_CONTEXT_AWARE_BUILTIN(crypto, node::crypto::InitCrypto)
|