|
|
|
// Copyright Joyent, Inc. and other Node contributors.
|
|
|
|
//
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
// copy of this software and associated documentation files (the
|
|
|
|
// "Software"), to deal in the Software without restriction, including
|
|
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
|
|
// following conditions:
|
|
|
|
//
|
|
|
|
// The above copyright notice and this permission notice shall be included
|
|
|
|
// in all copies or substantial portions of the Software.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
|
|
|
#include "node.h"
|
|
|
|
#include "node_buffer.h"
|
|
|
|
#include "node_crypto.h"
|
|
|
|
#include "node_crypto_bio.h"
|
|
|
|
#include "node_crypto_groups.h"
|
|
|
|
#include "node_root_certs.h"
|
|
|
|
|
|
|
|
#include "string_bytes.h"
|
|
|
|
#include "v8.h"
|
|
|
|
|
|
|
|
#include <errno.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 ASSERT_IS_STRING_OR_BUFFER(val) do { \
|
|
|
|
if (!Buffer::HasInstance(val) && !val->IsString()) { \
|
|
|
|
return ThrowTypeError("Not a string or buffer"); \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define ASSERT_IS_BUFFER(val) do { \
|
|
|
|
if (!Buffer::HasInstance(val)) { \
|
|
|
|
return 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 int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL
|
|
|
|
| ASN1_STRFLGS_ESC_MSB
|
|
|
|
| XN_FLAG_SEP_MULTILINE
|
|
|
|
| XN_FLAG_FN_SN;
|
|
|
|
|
|
|
|
namespace node {
|
|
|
|
namespace crypto {
|
|
|
|
|
|
|
|
using v8::Array;
|
|
|
|
using v8::Exception;
|
|
|
|
using v8::False;
|
|
|
|
using v8::FunctionCallbackInfo;
|
|
|
|
using v8::FunctionTemplate;
|
|
|
|
using v8::Handle;
|
|
|
|
using v8::HandleScope;
|
|
|
|
using v8::Integer;
|
|
|
|
using v8::Local;
|
|
|
|
using v8::Null;
|
|
|
|
using v8::Object;
|
|
|
|
using v8::Persistent;
|
|
|
|
using v8::String;
|
|
|
|
using v8::ThrowException;
|
|
|
|
using v8::Value;
|
|
|
|
|
|
|
|
|
|
|
|
// Forcibly clear OpenSSL's error stack on return. This stops stale errors
|
|
|
|
// from popping up later in the lifecycle of crypto operations where they
|
|
|
|
// would cause spurious failures. It's a rather blunt method, though.
|
|
|
|
// ERR_clear_error() isn't necessarily cheap either.
|
|
|
|
struct ClearErrorOnReturn {
|
|
|
|
~ClearErrorOnReturn() { ERR_clear_error(); }
|
|
|
|
};
|
|
|
|
|
|
|
|
static Cached<String> subject_symbol;
|
|
|
|
static Cached<String> subjectaltname_symbol;
|
|
|
|
static Cached<String> modulus_symbol;
|
|
|
|
static Cached<String> exponent_symbol;
|
|
|
|
static Cached<String> issuer_symbol;
|
|
|
|
static Cached<String> valid_from_symbol;
|
|
|
|
static Cached<String> valid_to_symbol;
|
|
|
|
static Cached<String> fingerprint_symbol;
|
|
|
|
static Cached<String> name_symbol;
|
|
|
|
static Cached<String> version_symbol;
|
|
|
|
static Cached<String> ext_key_usage_symbol;
|
|
|
|
static Cached<String> onhandshakestart_sym;
|
|
|
|
static Cached<String> onhandshakedone_sym;
|
|
|
|
static Cached<String> onclienthello_sym;
|
|
|
|
static Cached<String> onnewsession_sym;
|
|
|
|
static Cached<String> sessionid_sym;
|
|
|
|
|
|
|
|
static Persistent<FunctionTemplate> secure_context_constructor;
|
|
|
|
|
|
|
|
static uv_rwlock_t* locks;
|
|
|
|
|
|
|
|
|
|
|
|
static void crypto_threadid_cb(CRYPTO_THREADID* tid) {
|
|
|
|
CRYPTO_THREADID_set_numeric(tid, uv_thread_self());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void crypto_lock_init(void) {
|
|
|
|
int i, n;
|
|
|
|
|
|
|
|
n = CRYPTO_num_locks();
|
|
|
|
locks = new uv_rwlock_t[n];
|
|
|
|
|
|
|
|
for (i = 0; i < n; i++)
|
|
|
|
if (uv_rwlock_init(locks + i))
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void crypto_lock_cb(int mode, int n, const char* file, int line) {
|
|
|
|
assert((mode & CRYPTO_LOCK) || (mode & CRYPTO_UNLOCK));
|
|
|
|
assert((mode & CRYPTO_READ) || (mode & CRYPTO_WRITE));
|
|
|
|
|
|
|
|
if (mode & CRYPTO_LOCK) {
|
|
|
|
if (mode & CRYPTO_READ)
|
|
|
|
uv_rwlock_rdlock(locks + n);
|
|
|
|
else
|
|
|
|
uv_rwlock_wrlock(locks + n);
|
|
|
|
} else {
|
|
|
|
if (mode & CRYPTO_READ)
|
|
|
|
uv_rwlock_rdunlock(locks + n);
|
|
|
|
else
|
|
|
|
uv_rwlock_wrunlock(locks + n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ThrowCryptoErrorHelper(unsigned long err, bool is_type_error) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
char errmsg[128];
|
|
|
|
ERR_error_string_n(err, errmsg, sizeof(errmsg));
|
|
|
|
if (is_type_error)
|
|
|
|
ThrowTypeError(errmsg);
|
|
|
|
else
|
|
|
|
ThrowError(errmsg);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ThrowCryptoError(unsigned long err) {
|
|
|
|
ThrowCryptoErrorHelper(err, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ThrowCryptoTypeError(unsigned long err) {
|
|
|
|
ThrowCryptoErrorHelper(err, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::Initialize(Handle<Object> target) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Local<FunctionTemplate> t = FunctionTemplate::New(SecureContext::New);
|
|
|
|
secure_context_constructor.Reset(node_isolate, t);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
t->SetClassName(FIXED_ONE_BYTE_STRING(node_isolate, "SecureContext"));
|
|
|
|
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "init", SecureContext::Init);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setKey", SecureContext::SetKey);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setCert", SecureContext::SetCert);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "addCACert", SecureContext::AddCACert);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "addCRL", SecureContext::AddCRL);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "addRootCerts", SecureContext::AddRootCerts);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setCiphers", SecureContext::SetCiphers);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setOptions", SecureContext::SetOptions);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setSessionIdContext",
|
|
|
|
SecureContext::SetSessionIdContext);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setSessionTimeout",
|
|
|
|
SecureContext::SetSessionTimeout);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "close", SecureContext::Close);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "loadPKCS12", SecureContext::LoadPKCS12);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "getTicketKeys", SecureContext::GetTicketKeys);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setTicketKeys", SecureContext::SetTicketKeys);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "SecureContext"),
|
|
|
|
t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
SecureContext *p = new SecureContext();
|
|
|
|
p->Wrap(args.This());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.This());
|
|
|
|
|
|
|
|
OPENSSL_CONST SSL_METHOD *method = SSLv23_method();
|
|
|
|
|
|
|
|
if (args.Length() == 1 && args[0]->IsString()) {
|
|
|
|
const String::Utf8Value sslmethod(args[0]);
|
|
|
|
|
|
|
|
if (strcmp(*sslmethod, "SSLv2_method") == 0) {
|
|
|
|
#ifndef OPENSSL_NO_SSL2
|
|
|
|
method = SSLv2_method();
|
|
|
|
#else
|
|
|
|
return ThrowError("SSLv2 methods disabled");
|
|
|
|
#endif
|
|
|
|
} else if (strcmp(*sslmethod, "SSLv2_server_method") == 0) {
|
|
|
|
#ifndef OPENSSL_NO_SSL2
|
|
|
|
method = SSLv2_server_method();
|
|
|
|
#else
|
|
|
|
return ThrowError("SSLv2 methods disabled");
|
|
|
|
#endif
|
|
|
|
} else if (strcmp(*sslmethod, "SSLv2_client_method") == 0) {
|
|
|
|
#ifndef OPENSSL_NO_SSL2
|
|
|
|
method = SSLv2_client_method();
|
|
|
|
#else
|
|
|
|
return ThrowError("SSLv2 methods disabled");
|
|
|
|
#endif
|
|
|
|
} else if (strcmp(*sslmethod, "SSLv3_method") == 0) {
|
|
|
|
method = SSLv3_method();
|
|
|
|
} else if (strcmp(*sslmethod, "SSLv3_server_method") == 0) {
|
|
|
|
method = SSLv3_server_method();
|
|
|
|
} else if (strcmp(*sslmethod, "SSLv3_client_method") == 0) {
|
|
|
|
method = SSLv3_client_method();
|
|
|
|
} 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 {
|
|
|
|
return ThrowError("Unknown method");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sc->ctx_ = SSL_CTX_new(method);
|
|
|
|
|
|
|
|
// 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_, GetSessionCallback);
|
|
|
|
SSL_CTX_sess_set_new_cb(sc->ctx_, NewSessionCallback);
|
|
|
|
|
|
|
|
sc->ca_store_ = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SSL_SESSION* SecureContext::GetSessionCallback(SSL* s,
|
|
|
|
unsigned char* key,
|
|
|
|
int len,
|
|
|
|
int* copy) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection* p = static_cast<Connection*>(SSL_get_app_data(s));
|
|
|
|
|
|
|
|
*copy = 0;
|
|
|
|
SSL_SESSION* sess = p->next_sess_;
|
|
|
|
p->next_sess_ = NULL;
|
|
|
|
|
|
|
|
return sess;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int SecureContext::NewSessionCallback(SSL* s, SSL_SESSION* sess) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection* p = static_cast<Connection*>(SSL_get_app_data(s));
|
|
|
|
|
|
|
|
// Check if session is small enough to be stored
|
|
|
|
int size = i2d_SSL_SESSION(sess, NULL);
|
|
|
|
if (size > kMaxSessionSize) return 0;
|
|
|
|
|
|
|
|
// Serialize session
|
|
|
|
char* serialized = new char[size];
|
|
|
|
unsigned char* pserialized = reinterpret_cast<unsigned char*>(serialized);
|
|
|
|
memset(serialized, 0, size);
|
|
|
|
i2d_SSL_SESSION(sess, &pserialized);
|
|
|
|
|
|
|
|
Handle<Value> argv[2] = {
|
|
|
|
Buffer::New(reinterpret_cast<char*>(sess->session_id),
|
|
|
|
sess->session_id_length),
|
|
|
|
Buffer::Use(serialized, size)
|
|
|
|
};
|
|
|
|
|
|
|
|
if (onnewsession_sym.IsEmpty()) {
|
|
|
|
onnewsession_sym = FIXED_ONE_BYTE_STRING(node_isolate, "onnewsession");
|
|
|
|
}
|
|
|
|
|
|
|
|
MakeCallback(p->handle(node_isolate),
|
|
|
|
onnewsession_sym,
|
|
|
|
ARRAY_SIZE(argv),
|
|
|
|
argv);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Takes a string or buffer and loads it into a BIO.
|
|
|
|
// Caller responsible for BIO_free_all-ing the returned object.
|
|
|
|
static BIO* LoadBIO(Handle<Value> v) {
|
|
|
|
BIO *bio = BIO_new(NodeBIO::GetMethod());
|
|
|
|
if (!bio) return NULL;
|
|
|
|
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
int r = -1;
|
|
|
|
|
|
|
|
if (v->IsString()) {
|
|
|
|
const String::Utf8Value s(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 NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return bio;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Takes a string or buffer and loads it into an X509
|
|
|
|
// Caller responsible for X509_free-ing the returned object.
|
|
|
|
static X509* LoadX509(Handle<Value> v) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
BIO *bio = LoadBIO(v);
|
|
|
|
if (!bio) return NULL;
|
|
|
|
|
|
|
|
X509 * x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
|
|
|
if (!x509) {
|
|
|
|
BIO_free_all(bio);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
BIO_free_all(bio);
|
|
|
|
return x509;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetKey(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.This());
|
|
|
|
|
|
|
|
unsigned int len = args.Length();
|
|
|
|
if (len != 1 && len != 2) {
|
|
|
|
return ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
if (len == 2 && !args[1]->IsString()) {
|
|
|
|
return ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
BIO *bio = LoadBIO(args[0]);
|
|
|
|
if (!bio) return;
|
|
|
|
|
|
|
|
String::Utf8Value passphrase(args[1]);
|
|
|
|
|
|
|
|
EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, NULL, NULL,
|
|
|
|
len == 1 ? NULL : *passphrase);
|
|
|
|
|
|
|
|
if (!key) {
|
|
|
|
BIO_free_all(bio);
|
|
|
|
unsigned long err = ERR_get_error();
|
|
|
|
if (!err) {
|
|
|
|
return ThrowError("PEM_read_bio_PrivateKey");
|
|
|
|
}
|
|
|
|
return ThrowCryptoError(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
SSL_CTX_use_PrivateKey(sc->ctx_, key);
|
|
|
|
EVP_PKEY_free(key);
|
|
|
|
BIO_free_all(bio);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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 - editted for style.
|
|
|
|
int SSL_CTX_use_certificate_chain(SSL_CTX *ctx, BIO *in) {
|
|
|
|
int ret = 0;
|
|
|
|
X509 *x = NULL;
|
|
|
|
|
|
|
|
x = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
|
|
|
|
|
|
|
|
if (x == NULL) {
|
|
|
|
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_PEM_LIB);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = SSL_CTX_use_certificate(ctx, x);
|
|
|
|
|
|
|
|
if (ERR_peek_error() != 0) {
|
|
|
|
// Key/certificate mismatch doesn't imply ret==0 ...
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret) {
|
|
|
|
// If we could set up our certificate, now proceed to
|
|
|
|
// the CA certificates.
|
|
|
|
X509 *ca;
|
|
|
|
int r;
|
|
|
|
unsigned long err;
|
|
|
|
|
|
|
|
if (ctx->extra_certs != NULL) {
|
|
|
|
sk_X509_pop_free(ctx->extra_certs, X509_free);
|
|
|
|
ctx->extra_certs = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
|
|
|
|
r = SSL_CTX_add_extra_chain_cert(ctx, ca);
|
|
|
|
|
|
|
|
if (!r) {
|
|
|
|
X509_free(ca);
|
|
|
|
ret = 0;
|
|
|
|
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).
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
if (x != NULL) X509_free(x);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetCert(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.This());
|
|
|
|
|
|
|
|
if (args.Length() != 1) {
|
|
|
|
return ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
BIO* bio = LoadBIO(args[0]);
|
|
|
|
if (!bio) return;
|
|
|
|
|
|
|
|
int rv = SSL_CTX_use_certificate_chain(sc->ctx_, bio);
|
|
|
|
|
|
|
|
BIO_free_all(bio);
|
|
|
|
|
|
|
|
if (!rv) {
|
|
|
|
unsigned long err = ERR_get_error();
|
|
|
|
if (!err) {
|
|
|
|
return ThrowError("SSL_CTX_use_certificate_chain");
|
|
|
|
}
|
|
|
|
return ThrowCryptoError(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::AddCACert(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
bool newCAStore = false;
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.This());
|
|
|
|
|
|
|
|
if (args.Length() != 1) {
|
|
|
|
return ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!sc->ca_store_) {
|
|
|
|
sc->ca_store_ = X509_STORE_new();
|
|
|
|
newCAStore = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
X509* x509 = LoadX509(args[0]);
|
|
|
|
if (!x509) return;
|
|
|
|
|
|
|
|
X509_STORE_add_cert(sc->ca_store_, x509);
|
|
|
|
SSL_CTX_add_client_CA(sc->ctx_, x509);
|
|
|
|
|
|
|
|
X509_free(x509);
|
|
|
|
|
|
|
|
if (newCAStore) {
|
|
|
|
SSL_CTX_set_cert_store(sc->ctx_, sc->ca_store_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.This());
|
|
|
|
|
|
|
|
if (args.Length() != 1) {
|
|
|
|
return ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence compiler warning.
|
|
|
|
|
|
|
|
BIO *bio = LoadBIO(args[0]);
|
|
|
|
if (!bio) return;
|
|
|
|
|
|
|
|
X509_CRL *x509 = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL);
|
|
|
|
|
|
|
|
if (x509 == NULL) {
|
|
|
|
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) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.This());
|
|
|
|
|
|
|
|
assert(sc->ca_store_ == NULL);
|
|
|
|
|
|
|
|
if (!root_cert_store) {
|
|
|
|
root_cert_store = X509_STORE_new();
|
|
|
|
|
|
|
|
for (int i = 0; root_certs[i]; i++) {
|
|
|
|
BIO *bp = BIO_new(NodeBIO::GetMethod());
|
|
|
|
|
|
|
|
if (!BIO_write(bp, root_certs[i], strlen(root_certs[i]))) {
|
|
|
|
BIO_free_all(bp);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
X509 *x509 = PEM_read_bio_X509(bp, NULL, NULL, NULL);
|
|
|
|
|
|
|
|
if (x509 == NULL) {
|
|
|
|
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) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.This());
|
|
|
|
|
|
|
|
if (args.Length() != 1 || !args[0]->IsString()) {
|
|
|
|
return ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
const String::Utf8Value ciphers(args[0]);
|
|
|
|
SSL_CTX_set_cipher_list(sc->ctx_, *ciphers);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetOptions(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.This());
|
|
|
|
|
|
|
|
if (args.Length() != 1 || !args[0]->IntegerValue()) {
|
|
|
|
return ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
SSL_CTX_set_options(sc->ctx_, args[0]->IntegerValue());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetSessionIdContext(
|
|
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.This());
|
|
|
|
|
|
|
|
if (args.Length() != 1 || !args[0]->IsString()) {
|
|
|
|
return ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
const String::Utf8Value sessionIdContext(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 == NULL) {
|
|
|
|
message = FIXED_ONE_BYTE_STRING(node_isolate,
|
|
|
|
"SSL_CTX_set_session_id_context error");
|
|
|
|
} else {
|
|
|
|
ERR_print_errors(bio);
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
message = OneByteString(node_isolate, mem->data, mem->length);
|
|
|
|
BIO_free_all(bio);
|
|
|
|
}
|
|
|
|
|
|
|
|
ThrowException(Exception::TypeError(message));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::SetSessionTimeout(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.This());
|
|
|
|
|
|
|
|
if (args.Length() != 1 || !args[0]->IsInt32()) {
|
|
|
|
return ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t sessionTimeout = args[0]->Int32Value();
|
|
|
|
SSL_CTX_set_timeout(sc->ctx_, sessionTimeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::Close(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.This());
|
|
|
|
sc->FreeCTXMem();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Takes .pfx or .p12 and password in string or buffer format
|
|
|
|
void SecureContext::LoadPKCS12(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
BIO* in = NULL;
|
|
|
|
PKCS12* p12 = NULL;
|
|
|
|
EVP_PKEY* pkey = NULL;
|
|
|
|
X509* cert = NULL;
|
|
|
|
STACK_OF(X509)* extraCerts = NULL;
|
|
|
|
char* pass = NULL;
|
|
|
|
bool ret = false;
|
|
|
|
|
|
|
|
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.This());
|
|
|
|
|
|
|
|
if (args.Length() < 1) {
|
|
|
|
return ThrowTypeError("Bad parameter");
|
|
|
|
}
|
|
|
|
|
|
|
|
in = LoadBIO(args[0]);
|
|
|
|
if (in == NULL) {
|
|
|
|
return ThrowError("Unable to load BIO");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args.Length() >= 2) {
|
|
|
|
ASSERT_IS_BUFFER(args[1]);
|
|
|
|
|
|
|
|
int passlen = Buffer::Length(args[1]);
|
|
|
|
if (passlen < 0) {
|
|
|
|
BIO_free_all(in);
|
|
|
|
return ThrowTypeError("Bad password");
|
|
|
|
}
|
|
|
|
pass = new char[passlen + 1];
|
|
|
|
int pass_written = DecodeWrite(pass, passlen, args[1], BINARY);
|
|
|
|
|
|
|
|
assert(pass_written == passlen);
|
|
|
|
pass[passlen] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d2i_PKCS12_bio(in, &p12) &&
|
|
|
|
PKCS12_parse(p12, pass, &pkey, &cert, &extraCerts) &&
|
|
|
|
SSL_CTX_use_certificate(sc->ctx_, cert) &&
|
|
|
|
SSL_CTX_use_PrivateKey(sc->ctx_, pkey)) {
|
|
|
|
// set extra certs
|
|
|
|
while (X509* x509 = sk_X509_pop(extraCerts)) {
|
|
|
|
if (!sc->ca_store_) {
|
|
|
|
sc->ca_store_ = X509_STORE_new();
|
|
|
|
SSL_CTX_set_cert_store(sc->ctx_, sc->ca_store_);
|
|
|
|
}
|
|
|
|
|
|
|
|
X509_STORE_add_cert(sc->ca_store_, x509);
|
|
|
|
SSL_CTX_add_client_CA(sc->ctx_, x509);
|
|
|
|
X509_free(x509);
|
|
|
|
}
|
|
|
|
|
|
|
|
EVP_PKEY_free(pkey);
|
|
|
|
X509_free(cert);
|
|
|
|
sk_X509_free(extraCerts);
|
|
|
|
|
|
|
|
ret = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 ThrowError(str);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SecureContext::GetTicketKeys(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys)
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
SecureContext* wrap;
|
|
|
|
NODE_UNWRAP(args.This(), SecureContext, wrap);
|
|
|
|
|
|
|
|
Local<Object> buff = Buffer::New(48);
|
|
|
|
if (SSL_CTX_get_tlsext_ticket_keys(wrap->ctx_,
|
|
|
|
Buffer::Data(buff),
|
|
|
|
Buffer::Length(buff)) != 1) {
|
|
|
|
return 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)
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
if (args.Length() < 1 ||
|
|
|
|
!Buffer::HasInstance(args[0]) ||
|
|
|
|
Buffer::Length(args[0]) != 48) {
|
|
|
|
return ThrowTypeError("Bad argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
SecureContext* wrap;
|
|
|
|
NODE_UNWRAP(args.This(), SecureContext, wrap);
|
|
|
|
|
|
|
|
if (SSL_CTX_set_tlsext_ticket_keys(wrap->ctx_,
|
|
|
|
Buffer::Data(args[0]),
|
|
|
|
Buffer::Length(args[0])) != 1) {
|
|
|
|
return ThrowError("Failed to fetch tls ticket keys");
|
|
|
|
}
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(true);
|
|
|
|
#endif // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::OnClientHello(void* arg,
|
|
|
|
const ClientHelloParser::ClientHello& hello) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Connection* c = static_cast<Connection*>(arg);
|
|
|
|
|
|
|
|
if (onclienthello_sym.IsEmpty())
|
|
|
|
onclienthello_sym = FIXED_ONE_BYTE_STRING(node_isolate, "onclienthello");
|
|
|
|
if (sessionid_sym.IsEmpty())
|
|
|
|
sessionid_sym = FIXED_ONE_BYTE_STRING(node_isolate, "sessionId");
|
|
|
|
|
|
|
|
Local<Object> obj = Object::New();
|
|
|
|
obj->Set(sessionid_sym,
|
|
|
|
Buffer::New(reinterpret_cast<const char*>(hello.session_id()),
|
|
|
|
hello.session_size()));
|
|
|
|
|
|
|
|
Handle<Value> argv[1] = { obj };
|
|
|
|
MakeCallback(c->handle(node_isolate),
|
|
|
|
onclienthello_sym,
|
|
|
|
ARRAY_SIZE(argv),
|
|
|
|
argv);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::OnClientHelloParseEnd(void* arg) {
|
|
|
|
Connection* c = static_cast<Connection*>(arg);
|
|
|
|
|
|
|
|
// Write all accumulated data
|
|
|
|
int r = BIO_write(c->bio_read_,
|
|
|
|
reinterpret_cast<char*>(c->hello_data_),
|
|
|
|
c->hello_offset_);
|
|
|
|
c->HandleBIOError(c->bio_read_, "BIO_write", r);
|
|
|
|
c->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 {
|
|
|
|
static char ssl_error_buf[512];
|
|
|
|
ERR_error_string_n(rv, ssl_error_buf, sizeof(ssl_error_buf));
|
|
|
|
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Local<Value> exception =
|
|
|
|
Exception::Error(OneByteString(node_isolate, ssl_error_buf));
|
|
|
|
handle(node_isolate)->Set(
|
|
|
|
FIXED_ONE_BYTE_STRING(node_isolate, "error"), 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_ZERO_RETURN) {
|
|
|
|
Local<Value> exception =
|
|
|
|
Exception::Error(FIXED_ONE_BYTE_STRING(node_isolate, "ZERO_RETURN"));
|
|
|
|
handle(node_isolate)->Set(
|
|
|
|
FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception);
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
} else if ((err == SSL_ERROR_SYSCALL) && (ss == kIgnoreSyscall)) {
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
BUF_MEM* mem;
|
|
|
|
BIO *bio;
|
|
|
|
|
|
|
|
assert(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
|
|
|
|
if ((bio = BIO_new(BIO_s_mem()))) {
|
|
|
|
ERR_print_errors(bio);
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
Local<Value> exception =
|
|
|
|
Exception::Error(OneByteString(node_isolate, mem->data, mem->length));
|
|
|
|
handle(node_isolate)->Set(
|
|
|
|
FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception);
|
|
|
|
BIO_free_all(bio);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::ClearError() {
|
|
|
|
#ifndef NDEBUG
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
// We should clear the error in JS-land
|
|
|
|
Local<String> error_key = FIXED_ONE_BYTE_STRING(node_isolate, "error");
|
|
|
|
Local<Value> error = handle(node_isolate)->Get(error_key);
|
|
|
|
assert(error->BooleanValue() == false);
|
|
|
|
#endif // NDEBUG
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::SetShutdownFlags() {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
int flags = SSL_get_shutdown(ssl_);
|
|
|
|
|
|
|
|
if (flags & SSL_SENT_SHUTDOWN) {
|
|
|
|
Local<String> sent_shutdown_key =
|
|
|
|
FIXED_ONE_BYTE_STRING(node_isolate, "sentShutdown");
|
|
|
|
handle(node_isolate)->Set(sent_shutdown_key, True(node_isolate));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & SSL_RECEIVED_SHUTDOWN) {
|
|
|
|
Local<String> received_shutdown_key =
|
|
|
|
FIXED_ONE_BYTE_STRING(node_isolate, "receivedShutdown");
|
|
|
|
handle(node_isolate)->Set(received_shutdown_key, True(node_isolate));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::Initialize(Handle<Object> target) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Local<FunctionTemplate> t = FunctionTemplate::New(Connection::New);
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
t->SetClassName(FIXED_ONE_BYTE_STRING(node_isolate, "Connection"));
|
|
|
|
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "encIn", Connection::EncIn);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "clearOut", Connection::ClearOut);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "clearIn", Connection::ClearIn);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "encOut", Connection::EncOut);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "clearPending", Connection::ClearPending);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "encPending", Connection::EncPending);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t,
|
|
|
|
"getPeerCertificate",
|
|
|
|
Connection::GetPeerCertificate);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "getSession", Connection::GetSession);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setSession", Connection::SetSession);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "loadSession", Connection::LoadSession);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "isSessionReused", Connection::IsSessionReused);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "isInitFinished", Connection::IsInitFinished);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "verifyError", Connection::VerifyError);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t,
|
|
|
|
"getCurrentCipher",
|
|
|
|
Connection::GetCurrentCipher);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "start", Connection::Start);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Connection::Shutdown);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t,
|
|
|
|
"receivedShutdown",
|
|
|
|
Connection::ReceivedShutdown);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "close", Connection::Close);
|
|
|
|
|
|
|
|
#ifdef OPENSSL_NPN_NEGOTIATED
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t,
|
|
|
|
"getNegotiatedProtocol",
|
|
|
|
Connection::GetNegotiatedProto);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t,
|
|
|
|
"setNPNProtocols",
|
|
|
|
Connection::SetNPNProtocols);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "getServername", Connection::GetServername);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setSNICallback", Connection::SetSNICallback);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Connection"),
|
|
|
|
t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int VerifyCallback(int preverify_ok, X509_STORE_CTX *ctx) {
|
|
|
|
// Quoting SSL_set_verify(3ssl):
|
|
|
|
//
|
|
|
|
// The VerifyCallback function is used to control the behaviour when
|
|
|
|
// the SSL_VERIFY_PEER flag is set. It must be supplied by the
|
|
|
|
// application and receives two arguments: preverify_ok indicates,
|
|
|
|
// whether the verification of the certificate in question was passed
|
|
|
|
// (preverify_ok=1) or not (preverify_ok=0). x509_ctx is a pointer to
|
|
|
|
// the complete context used for the certificate chain verification.
|
|
|
|
//
|
|
|
|
// The certificate chain is checked starting with the deepest nesting
|
|
|
|
// level (the root CA certificate) and worked upward to the peer's
|
|
|
|
// certificate. At each level signatures and issuer attributes are
|
|
|
|
// checked. Whenever a verification error is found, the error number is
|
|
|
|
// stored in x509_ctx and VerifyCallback is called with preverify_ok=0.
|
|
|
|
// By applying X509_CTX_store_* functions VerifyCallback can locate the
|
|
|
|
// certificate in question and perform additional steps (see EXAMPLES).
|
|
|
|
// If no error is found for a certificate, VerifyCallback is called
|
|
|
|
// with preverify_ok=1 before advancing to the next level.
|
|
|
|
//
|
|
|
|
// The return value of VerifyCallback controls the strategy of the
|
|
|
|
// further verification process. If VerifyCallback returns 0, the
|
|
|
|
// verification process is immediately stopped with "verification
|
|
|
|
// failed" state. If SSL_VERIFY_PEER is set, a verification failure
|
|
|
|
// alert is sent to the peer and the TLS/SSL handshake is terminated. If
|
|
|
|
// VerifyCallback returns 1, the verification process is continued. If
|
|
|
|
// VerifyCallback always returns 1, the TLS/SSL handshake will not be
|
|
|
|
// terminated with respect to verification failures and the connection
|
|
|
|
// will be established. The calling process can however retrieve the
|
|
|
|
// error code of the last verification error using
|
|
|
|
// SSL_get_verify_result(3) or by maintaining its own error storage
|
|
|
|
// managed by VerifyCallback.
|
|
|
|
//
|
|
|
|
// If no VerifyCallback is specified, the default callback will be
|
|
|
|
// used. Its return value is identical to preverify_ok, so that any
|
|
|
|
// verification failure will lead to a termination of the TLS/SSL
|
|
|
|
// handshake with an alert message, if SSL_VERIFY_PEER is set.
|
|
|
|
//
|
|
|
|
// Since we cannot perform I/O quickly enough in this callback, we ignore
|
|
|
|
// all preverify_ok errors and let the handshake continue. It is
|
|
|
|
// imparative that the user use Connection::VerifyError after the
|
|
|
|
// 'secure' callback has been made.
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef OPENSSL_NPN_NEGOTIATED
|
|
|
|
|
|
|
|
int Connection::AdvertiseNextProtoCallback_(SSL *s,
|
|
|
|
const unsigned char** data,
|
|
|
|
unsigned int *len,
|
|
|
|
void *arg) {
|
|
|
|
Connection *p = static_cast<Connection*>(SSL_get_app_data(s));
|
|
|
|
|
|
|
|
if (p->npnProtos_.IsEmpty()) {
|
|
|
|
// No initialization - no NPN protocols
|
|
|
|
*data = reinterpret_cast<const unsigned char*>("");
|
|
|
|
*len = 0;
|
|
|
|
} else {
|
|
|
|
Local<Object> obj = PersistentToLocal(node_isolate, p->npnProtos_);
|
|
|
|
*data = reinterpret_cast<const unsigned char*>(Buffer::Data(obj));
|
|
|
|
*len = Buffer::Length(obj);
|
|
|
|
}
|
|
|
|
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Connection::SelectNextProtoCallback_(SSL *s,
|
|
|
|
unsigned char** out, unsigned char* outlen,
|
|
|
|
const unsigned char* in,
|
|
|
|
unsigned int inlen, void *arg) {
|
|
|
|
Connection *p = static_cast<Connection*> SSL_get_app_data(s);
|
|
|
|
|
|
|
|
// Release old protocol handler if present
|
|
|
|
p->selectedNPNProto_.Dispose();
|
|
|
|
|
|
|
|
if (p->npnProtos_.IsEmpty()) {
|
|
|
|
// 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
|
|
|
|
p->selectedNPNProto_.Reset(node_isolate, False(node_isolate));
|
|
|
|
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Object> obj = PersistentToLocal(node_isolate, p->npnProtos_);
|
|
|
|
const unsigned char* npnProtos =
|
|
|
|
reinterpret_cast<const unsigned char*>(Buffer::Data(obj));
|
|
|
|
|
|
|
|
int status = SSL_select_next_proto(out, outlen, in, inlen, npnProtos,
|
|
|
|
Buffer::Length(obj));
|
|
|
|
|
|
|
|
switch (status) {
|
|
|
|
case OPENSSL_NPN_UNSUPPORTED:
|
|
|
|
p->selectedNPNProto_.Reset(node_isolate, Null(node_isolate));
|
|
|
|
break;
|
|
|
|
case OPENSSL_NPN_NEGOTIATED:
|
|
|
|
{
|
|
|
|
Local<String> string = OneByteString(node_isolate, *out, *outlen);
|
|
|
|
p->selectedNPNProto_.Reset(node_isolate, string);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case OPENSSL_NPN_NO_OVERLAP:
|
|
|
|
p->selectedNPNProto_.Reset(node_isolate, False(node_isolate));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
|
|
int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *p = static_cast<Connection*> SSL_get_app_data(s);
|
|
|
|
|
|
|
|
const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
|
|
|
|
|
|
|
|
if (servername) {
|
|
|
|
p->servername_.Reset(node_isolate,
|
|
|
|
OneByteString(node_isolate, servername));
|
|
|
|
|
|
|
|
// Call the SNI callback and use its return value as context
|
|
|
|
if (!p->sniObject_.IsEmpty()) {
|
|
|
|
p->sniContext_.Dispose();
|
|
|
|
|
|
|
|
Local<Value> arg = PersistentToLocal(node_isolate, p->servername_);
|
|
|
|
Local<Value> ret = MakeCallback(p->sniObject_, "onselect", 1, &arg);
|
|
|
|
|
|
|
|
// If ret is SecureContext
|
|
|
|
if (HasInstance(secure_context_constructor, ret)) {
|
|
|
|
p->sniContext_.Reset(node_isolate, ret);
|
|
|
|
SecureContext* sc = ObjectWrap::Unwrap<SecureContext>(ret.As<Object>());
|
|
|
|
SSL_set_SSL_CTX(s, sc->ctx_);
|
|
|
|
} else {
|
|
|
|
return SSL_TLSEXT_ERR_NOACK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void Connection::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *p = new Connection();
|
|
|
|
p->Wrap(args.This());
|
|
|
|
|
|
|
|
if (args.Length() < 1 || !args[0]->IsObject()) {
|
|
|
|
return ThrowError("First argument must be a crypto module Credentials");
|
|
|
|
}
|
|
|
|
|
|
|
|
SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args[0]->ToObject());
|
|
|
|
|
|
|
|
bool is_server = args[1]->BooleanValue();
|
|
|
|
|
|
|
|
p->ssl_ = SSL_new(sc->ctx_);
|
|
|
|
p->bio_read_ = BIO_new(NodeBIO::GetMethod());
|
|
|
|
p->bio_write_ = BIO_new(NodeBIO::GetMethod());
|
|
|
|
|
|
|
|
SSL_set_app_data(p->ssl_, p);
|
|
|
|
|
|
|
|
if (is_server) SSL_set_info_callback(p->ssl_, SSLInfoCallback);
|
|
|
|
|
|
|
|
#ifdef OPENSSL_NPN_NEGOTIATED
|
|
|
|
if (is_server) {
|
|
|
|
// Server should advertise NPN protocols
|
|
|
|
SSL_CTX_set_next_protos_advertised_cb(sc->ctx_,
|
|
|
|
AdvertiseNextProtoCallback_,
|
|
|
|
NULL);
|
|
|
|
} else {
|
|
|
|
// Client should select protocol from advertised
|
|
|
|
// If server supports NPN
|
|
|
|
SSL_CTX_set_next_proto_select_cb(sc->ctx_,
|
|
|
|
SelectNextProtoCallback_,
|
|
|
|
NULL);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
|
|
if (is_server) {
|
|
|
|
SSL_CTX_set_tlsext_servername_callback(sc->ctx_, SelectSNIContextCallback_);
|
|
|
|
} else {
|
|
|
|
const String::Utf8Value servername(args[2]);
|
|
|
|
SSL_set_tlsext_host_name(p->ssl_, *servername);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
SSL_set_bio(p->ssl_, p->bio_read_, p->bio_write_);
|
|
|
|
|
|
|
|
#ifdef SSL_MODE_RELEASE_BUFFERS
|
|
|
|
long mode = SSL_get_mode(p->ssl_);
|
|
|
|
SSL_set_mode(p->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(p->ssl_, verify_mode, VerifyCallback);
|
|
|
|
|
|
|
|
if ((p->is_server_ = is_server)) {
|
|
|
|
SSL_set_accept_state(p->ssl_);
|
|
|
|
} else {
|
|
|
|
SSL_set_connect_state(p->ssl_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::SSLInfoCallback(const SSL *ssl_, int where, int ret) {
|
|
|
|
// 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_);
|
|
|
|
if (where & SSL_CB_HANDSHAKE_START) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Connection* c = static_cast<Connection*>(SSL_get_app_data(ssl));
|
|
|
|
if (onhandshakestart_sym.IsEmpty()) {
|
|
|
|
onhandshakestart_sym =
|
|
|
|
FIXED_ONE_BYTE_STRING(node_isolate, "onhandshakestart");
|
|
|
|
}
|
|
|
|
MakeCallback(c->handle(node_isolate), onhandshakestart_sym, 0, NULL);
|
|
|
|
}
|
|
|
|
if (where & SSL_CB_HANDSHAKE_DONE) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Connection* c = static_cast<Connection*>(SSL_get_app_data(ssl));
|
|
|
|
if (onhandshakedone_sym.IsEmpty()) {
|
|
|
|
onhandshakedone_sym =
|
|
|
|
FIXED_ONE_BYTE_STRING(node_isolate, "onhandshakedone");
|
|
|
|
}
|
|
|
|
MakeCallback(c->handle(node_isolate), onhandshakedone_sym, 0, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::EncIn(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (args.Length() < 3) {
|
|
|
|
return ThrowTypeError("Takes 3 parameters");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Buffer::HasInstance(args[0])) {
|
|
|
|
return 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 (off + len > buffer_length) {
|
|
|
|
return ThrowError("off + len > buffer.length");
|
|
|
|
}
|
|
|
|
|
|
|
|
int bytes_written;
|
|
|
|
char* data = buffer_data + off;
|
|
|
|
|
|
|
|
if (ss->is_server_ && !ss->hello_parser_.IsEnded()) {
|
|
|
|
// Just accumulate data, everything will be pushed to BIO later
|
|
|
|
if (ss->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(ss->hello_data_) - ss->hello_offset_;
|
|
|
|
size_t copied = len < available ? len : available;
|
|
|
|
memcpy(ss->hello_data_ + ss->hello_offset_, data, copied);
|
|
|
|
ss->hello_offset_ += copied;
|
|
|
|
|
|
|
|
ss->hello_parser_.Parse(ss->hello_data_, ss->hello_offset_);
|
|
|
|
bytes_written = copied;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
bytes_written = BIO_write(ss->bio_read_, data, len);
|
|
|
|
ss->HandleBIOError(ss->bio_read_, "BIO_write", bytes_written);
|
|
|
|
ss->SetShutdownFlags();
|
|
|
|
}
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(bytes_written);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::ClearOut(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (args.Length() < 3) {
|
|
|
|
return ThrowTypeError("Takes 3 parameters");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Buffer::HasInstance(args[0])) {
|
|
|
|
return 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 (off + len > buffer_length) {
|
|
|
|
return ThrowError("off + len > buffer.length");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!SSL_is_init_finished(ss->ssl_)) {
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
if (ss->is_server_) {
|
|
|
|
rv = SSL_accept(ss->ssl_);
|
|
|
|
ss->HandleSSLError("SSL_accept:ClearOut",
|
|
|
|
rv,
|
|
|
|
kZeroIsAnError,
|
|
|
|
kSyscallError);
|
|
|
|
} else {
|
|
|
|
rv = SSL_connect(ss->ssl_);
|
|
|
|
ss->HandleSSLError("SSL_connect:ClearOut",
|
|
|
|
rv,
|
|
|
|
kZeroIsAnError,
|
|
|
|
kSyscallError);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rv < 0) {
|
|
|
|
return args.GetReturnValue().Set(rv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int bytes_read = SSL_read(ss->ssl_, buffer_data + off, len);
|
|
|
|
ss->HandleSSLError("SSL_read:ClearOut",
|
|
|
|
bytes_read,
|
|
|
|
kZeroIsNotAnError,
|
|
|
|
kSyscallError);
|
|
|
|
ss->SetShutdownFlags();
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(bytes_read);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::ClearPending(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
int bytes_pending = BIO_pending(ss->bio_read_);
|
|
|
|
args.GetReturnValue().Set(bytes_pending);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::EncPending(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
int bytes_pending = BIO_pending(ss->bio_write_);
|
|
|
|
args.GetReturnValue().Set(bytes_pending);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::EncOut(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (args.Length() < 3) {
|
|
|
|
return ThrowTypeError("Takes 3 parameters");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Buffer::HasInstance(args[0])) {
|
|
|
|
return 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 (off + len > buffer_length) {
|
|
|
|
return ThrowError("off + len > buffer.length");
|
|
|
|
}
|
|
|
|
|
|
|
|
int bytes_read = BIO_read(ss->bio_write_, buffer_data + off, len);
|
|
|
|
|
|
|
|
ss->HandleBIOError(ss->bio_write_, "BIO_read:EncOut", bytes_read);
|
|
|
|
ss->SetShutdownFlags();
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(bytes_read);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::ClearIn(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (args.Length() < 3) {
|
|
|
|
return ThrowTypeError("Takes 3 parameters");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Buffer::HasInstance(args[0])) {
|
|
|
|
return 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 (off + len > buffer_length) {
|
|
|
|
return ThrowError("off + len > buffer.length");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!SSL_is_init_finished(ss->ssl_)) {
|
|
|
|
int rv;
|
|
|
|
if (ss->is_server_) {
|
|
|
|
rv = SSL_accept(ss->ssl_);
|
|
|
|
ss->HandleSSLError("SSL_accept:ClearIn",
|
|
|
|
rv,
|
|
|
|
kZeroIsAnError,
|
|
|
|
kSyscallError);
|
|
|
|
} else {
|
|
|
|
rv = SSL_connect(ss->ssl_);
|
|
|
|
ss->HandleSSLError("SSL_connect:ClearIn",
|
|
|
|
rv,
|
|
|
|
kZeroIsAnError,
|
|
|
|
kSyscallError);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rv < 0) {
|
|
|
|
return args.GetReturnValue().Set(rv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int bytes_written = SSL_write(ss->ssl_, buffer_data + off, len);
|
|
|
|
|
|
|
|
ss->HandleSSLError("SSL_write:ClearIn",
|
|
|
|
bytes_written,
|
|
|
|
len == 0 ? kZeroIsNotAnError : kZeroIsAnError,
|
|
|
|
kSyscallError);
|
|
|
|
ss->SetShutdownFlags();
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(bytes_written);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::GetPeerCertificate(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (ss->ssl_ == NULL) return;
|
|
|
|
Local<Object> info = Object::New();
|
|
|
|
X509* peer_cert = SSL_get_peer_certificate(ss->ssl_);
|
|
|
|
if (peer_cert != NULL) {
|
|
|
|
BIO* bio = BIO_new(BIO_s_mem());
|
|
|
|
BUF_MEM* mem;
|
|
|
|
if (X509_NAME_print_ex(bio, X509_get_subject_name(peer_cert), 0,
|
|
|
|
X509_NAME_FLAGS) > 0) {
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(subject_symbol,
|
|
|
|
OneByteString(node_isolate, mem->data, mem->length));
|
|
|
|
}
|
|
|
|
(void) BIO_reset(bio);
|
|
|
|
|
|
|
|
if (X509_NAME_print_ex(bio, X509_get_issuer_name(peer_cert), 0,
|
|
|
|
X509_NAME_FLAGS) > 0) {
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(issuer_symbol,
|
|
|
|
OneByteString(node_isolate, mem->data, mem->length));
|
|
|
|
}
|
|
|
|
(void) BIO_reset(bio);
|
|
|
|
|
|
|
|
int index = X509_get_ext_by_NID(peer_cert, NID_subject_alt_name, -1);
|
|
|
|
if (index >= 0) {
|
|
|
|
X509_EXTENSION* ext;
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
ext = X509_get_ext(peer_cert, index);
|
|
|
|
assert(ext != NULL);
|
|
|
|
|
|
|
|
rv = X509V3_EXT_print(bio, ext, 0, 0);
|
|
|
|
assert(rv == 1);
|
|
|
|
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(subjectaltname_symbol,
|
|
|
|
OneByteString(node_isolate, mem->data, mem->length));
|
|
|
|
|
|
|
|
(void) BIO_reset(bio);
|
|
|
|
}
|
|
|
|
|
|
|
|
EVP_PKEY *pkey = NULL;
|
|
|
|
RSA *rsa = NULL;
|
|
|
|
if (NULL != (pkey = X509_get_pubkey(peer_cert)) &&
|
|
|
|
NULL != (rsa = EVP_PKEY_get1_RSA(pkey))) {
|
|
|
|
BN_print(bio, rsa->n);
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(modulus_symbol,
|
|
|
|
OneByteString(node_isolate, mem->data, mem->length));
|
|
|
|
(void) BIO_reset(bio);
|
|
|
|
|
|
|
|
BN_print(bio, rsa->e);
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(exponent_symbol,
|
|
|
|
OneByteString(node_isolate, mem->data, mem->length));
|
|
|
|
(void) BIO_reset(bio);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pkey != NULL) {
|
|
|
|
EVP_PKEY_free(pkey);
|
|
|
|
pkey = NULL;
|
|
|
|
}
|
|
|
|
if (rsa != NULL) {
|
|
|
|
RSA_free(rsa);
|
|
|
|
rsa = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ASN1_TIME_print(bio, X509_get_notBefore(peer_cert));
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(valid_from_symbol,
|
|
|
|
OneByteString(node_isolate, mem->data, mem->length));
|
|
|
|
(void) BIO_reset(bio);
|
|
|
|
|
|
|
|
ASN1_TIME_print(bio, X509_get_notAfter(peer_cert));
|
|
|
|
BIO_get_mem_ptr(bio, &mem);
|
|
|
|
info->Set(valid_to_symbol,
|
|
|
|
OneByteString(node_isolate, mem->data, mem->length));
|
|
|
|
BIO_free_all(bio);
|
|
|
|
|
|
|
|
unsigned int md_size, i;
|
|
|
|
unsigned char md[EVP_MAX_MD_SIZE];
|
|
|
|
if (X509_digest(peer_cert, EVP_sha1(), md, &md_size)) {
|
|
|
|
const char hex[] = "0123456789ABCDEF";
|
|
|
|
char fingerprint[EVP_MAX_MD_SIZE * 3];
|
|
|
|
|
|
|
|
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(fingerprint_symbol, OneByteString(node_isolate, fingerprint));
|
|
|
|
}
|
|
|
|
|
|
|
|
STACK_OF(ASN1_OBJECT) *eku = (STACK_OF(ASN1_OBJECT) *)X509_get_ext_d2i(
|
|
|
|
peer_cert, NID_ext_key_usage, NULL, NULL);
|
|
|
|
if (eku != NULL) {
|
|
|
|
Local<Array> ext_key_usage = Array::New();
|
|
|
|
char buf[256];
|
|
|
|
|
|
|
|
for (int i = 0; i < sk_ASN1_OBJECT_num(eku); i++) {
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
OBJ_obj2txt(buf, sizeof(buf) - 1, sk_ASN1_OBJECT_value(eku, i), 1);
|
|
|
|
ext_key_usage->Set(i, OneByteString(node_isolate, buf));
|
|
|
|
}
|
|
|
|
|
|
|
|
sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free);
|
|
|
|
info->Set(ext_key_usage_symbol, ext_key_usage);
|
|
|
|
}
|
|
|
|
|
|
|
|
X509_free(peer_cert);
|
|
|
|
}
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(info);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::GetSession(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (ss->ssl_ == NULL) return;
|
|
|
|
|
|
|
|
SSL_SESSION* sess = SSL_get_session(ss->ssl_);
|
|
|
|
if (!sess) return;
|
|
|
|
|
|
|
|
int slen = i2d_SSL_SESSION(sess, NULL);
|
|
|
|
assert(slen > 0);
|
|
|
|
|
|
|
|
unsigned char* sbuf = new unsigned char[slen];
|
|
|
|
unsigned char* p = sbuf;
|
|
|
|
i2d_SSL_SESSION(sess, &p);
|
|
|
|
args.GetReturnValue().Set(Encode(sbuf, slen, BINARY));
|
|
|
|
delete[] sbuf;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::SetSession(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (args.Length() < 1 ||
|
|
|
|
(!args[0]->IsString() && !Buffer::HasInstance(args[0]))) {
|
|
|
|
return ThrowTypeError("Bad argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_IS_BUFFER(args[0]);
|
|
|
|
ssize_t slen = Buffer::Length(args[0]);
|
|
|
|
|
|
|
|
if (slen < 0) {
|
|
|
|
return ThrowTypeError("Bad argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
char* sbuf = new char[slen];
|
|
|
|
|
|
|
|
ssize_t wlen = DecodeWrite(sbuf, slen, args[0], BINARY);
|
|
|
|
assert(wlen == slen);
|
|
|
|
|
|
|
|
const unsigned char* p = reinterpret_cast<const unsigned char*>(sbuf);
|
|
|
|
SSL_SESSION* sess = d2i_SSL_SESSION(NULL, &p, wlen);
|
|
|
|
|
|
|
|
delete [] sbuf;
|
|
|
|
|
|
|
|
if (!sess) return;
|
|
|
|
|
|
|
|
int r = SSL_set_session(ss->ssl_, sess);
|
|
|
|
SSL_SESSION_free(sess);
|
|
|
|
|
|
|
|
if (!r) {
|
|
|
|
return ThrowError("SSL_set_session error");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::LoadSession(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (args.Length() >= 1 && Buffer::HasInstance(args[0])) {
|
|
|
|
ssize_t slen = Buffer::Length(args[0].As<Object>());
|
|
|
|
char* sbuf = Buffer::Data(args[0].As<Object>());
|
|
|
|
|
|
|
|
const unsigned char* p = reinterpret_cast<unsigned char*>(sbuf);
|
|
|
|
SSL_SESSION* sess = d2i_SSL_SESSION(NULL, &p, slen);
|
|
|
|
|
|
|
|
// Setup next session and move hello to the BIO buffer
|
|
|
|
if (ss->next_sess_ != NULL) {
|
|
|
|
SSL_SESSION_free(ss->next_sess_);
|
|
|
|
}
|
|
|
|
ss->next_sess_ = sess;
|
|
|
|
}
|
|
|
|
|
|
|
|
ss->hello_parser_.End();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::IsSessionReused(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
bool yes = ss->ssl_ && SSL_session_reused(ss->ssl_);
|
|
|
|
args.GetReturnValue().Set(yes);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::Start(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
int rv = 0;
|
|
|
|
if (!SSL_is_init_finished(ss->ssl_)) {
|
|
|
|
if (ss->is_server_) {
|
|
|
|
rv = SSL_accept(ss->ssl_);
|
|
|
|
ss->HandleSSLError("SSL_accept:Start", rv, kZeroIsAnError, kSyscallError);
|
|
|
|
} else {
|
|
|
|
rv = SSL_connect(ss->ssl_);
|
|
|
|
ss->HandleSSLError("SSL_connect:Start",
|
|
|
|
rv,
|
|
|
|
kZeroIsAnError,
|
|
|
|
kSyscallError);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
args.GetReturnValue().Set(rv);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::Shutdown(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (ss->ssl_ == NULL) {
|
|
|
|
return args.GetReturnValue().Set(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
int rv = SSL_shutdown(ss->ssl_);
|
|
|
|
ss->HandleSSLError("SSL_shutdown", rv, kZeroIsNotAnError, kIgnoreSyscall);
|
|
|
|
ss->SetShutdownFlags();
|
|
|
|
args.GetReturnValue().Set(rv);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::ReceivedShutdown(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
bool yes = ss->ssl_ && SSL_get_shutdown(ss->ssl_) == SSL_RECEIVED_SHUTDOWN;
|
|
|
|
args.GetReturnValue().Set(yes);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::IsInitFinished(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
bool yes = ss->ssl_ && SSL_is_init_finished(ss->ssl_);
|
|
|
|
args.GetReturnValue().Set(yes);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::VerifyError(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (ss->ssl_ == NULL) {
|
|
|
|
return args.GetReturnValue().SetNull();
|
|
|
|
}
|
|
|
|
|
|
|
|
// XXX Do this check in JS land?
|
|
|
|
X509* peer_cert = SSL_get_peer_certificate(ss->ssl_);
|
|
|
|
if (peer_cert == NULL) {
|
|
|
|
// We requested a certificate and they did not send us one.
|
|
|
|
// Definitely an error.
|
|
|
|
// XXX is this the right error message?
|
|
|
|
Local<String> error_message =
|
|
|
|
FIXED_ONE_BYTE_STRING(node_isolate, "UNABLE_TO_GET_ISSUER_CERT");
|
|
|
|
Local<Value> exception = Exception::Error(error_message);
|
|
|
|
return args.GetReturnValue().Set(exception);
|
|
|
|
}
|
|
|
|
X509_free(peer_cert);
|
|
|
|
|
|
|
|
long x509_verify_error = SSL_get_verify_result(ss->ssl_);
|
|
|
|
|
|
|
|
Local<String> s;
|
|
|
|
|
|
|
|
switch (x509_verify_error) {
|
|
|
|
case X509_V_OK:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "UNABLE_TO_GET_ISSUER_CERT");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_UNABLE_TO_GET_CRL:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "UNABLE_TO_GET_CRL");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate,
|
|
|
|
"UNABLE_TO_DECRYPT_CERT_SIGNATURE");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate,
|
|
|
|
"UNABLE_TO_DECRYPT_CRL_SIGNATURE");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate,
|
|
|
|
"UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_CERT_SIGNATURE_FAILURE:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_SIGNATURE_FAILURE");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_CRL_SIGNATURE_FAILURE:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "CRL_SIGNATURE_FAILURE");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_CERT_NOT_YET_VALID:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_NOT_YET_VALID");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_CERT_HAS_EXPIRED:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_HAS_EXPIRED");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_CRL_NOT_YET_VALID:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "CRL_NOT_YET_VALID");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_CRL_HAS_EXPIRED:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "CRL_HAS_EXPIRED");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "ERROR_IN_CERT_NOT_BEFORE_FIELD");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "ERROR_IN_CERT_NOT_AFTER_FIELD");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "ERROR_IN_CRL_LAST_UPDATE_FIELD");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "ERROR_IN_CRL_NEXT_UPDATE_FIELD");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_OUT_OF_MEM:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "OUT_OF_MEM");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "DEPTH_ZERO_SELF_SIGNED_CERT");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "SELF_SIGNED_CERT_IN_CHAIN");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate,
|
|
|
|
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate,
|
|
|
|
"UNABLE_TO_VERIFY_LEAF_SIGNATURE");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_CERT_CHAIN_TOO_LONG:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_CHAIN_TOO_LONG");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_CERT_REVOKED:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_REVOKED");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_INVALID_CA:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "INVALID_CA");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_PATH_LENGTH_EXCEEDED:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "PATH_LENGTH_EXCEEDED");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_INVALID_PURPOSE:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "INVALID_PURPOSE");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_CERT_UNTRUSTED:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_UNTRUSTED");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case X509_V_ERR_CERT_REJECTED:
|
|
|
|
s = FIXED_ONE_BYTE_STRING(node_isolate, "CERT_REJECTED");
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
s = OneByteString(node_isolate,
|
|
|
|
X509_verify_cert_error_string(x509_verify_error));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s.IsEmpty())
|
|
|
|
args.GetReturnValue().SetNull();
|
|
|
|
else
|
|
|
|
args.GetReturnValue().Set(Exception::Error(s));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::GetCurrentCipher(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
OPENSSL_CONST SSL_CIPHER *c;
|
|
|
|
|
|
|
|
if (ss->ssl_ == NULL) return;
|
|
|
|
c = SSL_get_current_cipher(ss->ssl_);
|
|
|
|
if (c == NULL) return;
|
|
|
|
Local<Object> info = Object::New();
|
|
|
|
const char* cipher_name = SSL_CIPHER_get_name(c);
|
|
|
|
info->Set(name_symbol, OneByteString(node_isolate, cipher_name));
|
|
|
|
const char* cipher_version = SSL_CIPHER_get_version(c);
|
|
|
|
info->Set(version_symbol, OneByteString(node_isolate, cipher_version));
|
|
|
|
args.GetReturnValue().Set(info);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::Close(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (ss->ssl_ != NULL) {
|
|
|
|
SSL_free(ss->ssl_);
|
|
|
|
ss->ssl_ = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef OPENSSL_NPN_NEGOTIATED
|
|
|
|
void Connection::GetNegotiatedProto(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (ss->is_server_) {
|
|
|
|
const unsigned char* npn_proto;
|
|
|
|
unsigned int npn_proto_len;
|
|
|
|
|
|
|
|
SSL_get0_next_proto_negotiated(ss->ssl_, &npn_proto, &npn_proto_len);
|
|
|
|
|
|
|
|
if (!npn_proto) {
|
|
|
|
return args.GetReturnValue().Set(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(
|
|
|
|
OneByteString(node_isolate, npn_proto, npn_proto_len));
|
|
|
|
} else {
|
|
|
|
args.GetReturnValue().Set(ss->selectedNPNProto_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::SetNPNProtocols(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (args.Length() < 1 || !Buffer::HasInstance(args[0])) {
|
|
|
|
return ThrowError("Must give a Buffer as first argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
ss->npnProtos_.Reset(node_isolate, args[0].As<Object>());
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
|
|
|
|
void Connection::GetServername(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (ss->is_server_ && !ss->servername_.IsEmpty()) {
|
|
|
|
args.GetReturnValue().Set(ss->servername_);
|
|
|
|
} else {
|
|
|
|
args.GetReturnValue().Set(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Connection::SetSNICallback(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Connection *ss = Connection::Unwrap(args);
|
|
|
|
|
|
|
|
if (args.Length() < 1 || !args[0]->IsFunction()) {
|
|
|
|
return ThrowError("Must give a Function as first argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Object> obj = Object::New();
|
|
|
|
obj->Set(FIXED_ONE_BYTE_STRING(node_isolate, "onselect"), args[0]);
|
|
|
|
ss->sniObject_.Reset(node_isolate, obj);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::Initialize(Handle<Object> target) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Local<FunctionTemplate> t = FunctionTemplate::New(New);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "init", Init);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "initiv", InitIv);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "update", Update);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "final", Final);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setAutoPadding", SetAutoPadding);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "CipherBase"),
|
|
|
|
t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
assert(args.IsConstructCall() == true);
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
CipherBase* cipher = new CipherBase(args[0]->IsTrue() ? kCipher : kDecipher);
|
|
|
|
cipher->Wrap(args.This());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::Init(const char* cipher_type,
|
|
|
|
const char* key_buf,
|
|
|
|
int key_buf_len) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
assert(cipher_ == NULL);
|
|
|
|
cipher_ = EVP_get_cipherbyname(cipher_type);
|
|
|
|
if (cipher_ == NULL) {
|
|
|
|
return 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(),
|
|
|
|
NULL,
|
|
|
|
reinterpret_cast<const unsigned char*>(key_buf),
|
|
|
|
key_buf_len,
|
|
|
|
1,
|
|
|
|
key,
|
|
|
|
iv);
|
|
|
|
|
|
|
|
EVP_CIPHER_CTX_init(&ctx_);
|
|
|
|
EVP_CipherInit_ex(&ctx_, cipher_, NULL, NULL, NULL, kind_ == kCipher);
|
|
|
|
if (!EVP_CIPHER_CTX_set_key_length(&ctx_, key_len)) {
|
|
|
|
EVP_CIPHER_CTX_cleanup(&ctx_);
|
|
|
|
return ThrowError("Invalid key length");
|
|
|
|
}
|
|
|
|
|
|
|
|
EVP_CipherInit_ex(&ctx_,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
reinterpret_cast<unsigned char*>(key),
|
|
|
|
reinterpret_cast<unsigned char*>(iv),
|
|
|
|
kind_ == kCipher);
|
|
|
|
initialised_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::Init(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
CipherBase* cipher = ObjectWrap::Unwrap<CipherBase>(args.This());
|
|
|
|
|
|
|
|
if (args.Length() < 2 ||
|
|
|
|
!(args[0]->IsString() && Buffer::HasInstance(args[1]))) {
|
|
|
|
return ThrowError("Must give cipher-type, key");
|
|
|
|
}
|
|
|
|
|
|
|
|
const String::Utf8Value cipher_type(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(node_isolate);
|
|
|
|
|
|
|
|
cipher_ = EVP_get_cipherbyname(cipher_type);
|
|
|
|
if (cipher_ == NULL) {
|
|
|
|
return 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 ThrowError("Invalid IV length");
|
|
|
|
}
|
|
|
|
EVP_CIPHER_CTX_init(&ctx_);
|
|
|
|
EVP_CipherInit_ex(&ctx_, cipher_, NULL, NULL, NULL, kind_ == kCipher);
|
|
|
|
if (!EVP_CIPHER_CTX_set_key_length(&ctx_, key_len)) {
|
|
|
|
EVP_CIPHER_CTX_cleanup(&ctx_);
|
|
|
|
return ThrowError("Invalid key length");
|
|
|
|
}
|
|
|
|
|
|
|
|
EVP_CipherInit_ex(&ctx_,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
reinterpret_cast<const unsigned char*>(key),
|
|
|
|
reinterpret_cast<const unsigned char*>(iv),
|
|
|
|
kind_ == kCipher);
|
|
|
|
initialised_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::InitIv(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
CipherBase* cipher = ObjectWrap::Unwrap<CipherBase>(args.This());
|
|
|
|
|
|
|
|
if (args.Length() < 3 || !args[0]->IsString()) {
|
|
|
|
return ThrowError("Must give cipher-type, key, and iv as argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_IS_BUFFER(args[1]);
|
|
|
|
ASSERT_IS_BUFFER(args[2]);
|
|
|
|
|
|
|
|
const String::Utf8Value cipher_type(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::Update(const char* data,
|
|
|
|
int len,
|
|
|
|
unsigned char** out,
|
|
|
|
int* out_len) {
|
|
|
|
if (!initialised_) return 0;
|
|
|
|
*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) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
CipherBase* cipher = ObjectWrap::Unwrap<CipherBase>(args.This());
|
|
|
|
|
|
|
|
ASSERT_IS_STRING_OR_BUFFER(args[0]);
|
|
|
|
|
|
|
|
unsigned char* out = NULL;
|
|
|
|
bool r;
|
|
|
|
int out_len = 0;
|
|
|
|
|
|
|
|
// Only copy the data if we have to, because it's a string
|
|
|
|
if (args[0]->IsString()) {
|
|
|
|
Local<String> string = args[0].As<String>();
|
|
|
|
enum encoding encoding = ParseEncoding(args[1], BINARY);
|
|
|
|
if (!StringBytes::IsValidString(string, encoding))
|
|
|
|
return ThrowTypeError("Bad input string");
|
|
|
|
size_t buflen = StringBytes::StorageSize(string, encoding);
|
|
|
|
char* buf = new char[buflen];
|
|
|
|
size_t written = StringBytes::Write(buf, buflen, string, encoding);
|
|
|
|
r = cipher->Update(buf, written, &out, &out_len);
|
|
|
|
delete[] buf;
|
|
|
|
} 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 ThrowCryptoTypeError(ERR_get_error());
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Object> buf = Buffer::New(reinterpret_cast<char*>(out), out_len);
|
|
|
|
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) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
CipherBase* cipher = ObjectWrap::Unwrap<CipherBase>(args.This());
|
|
|
|
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_)];
|
|
|
|
bool r = EVP_CipherFinal_ex(&ctx_, *out, out_len);
|
|
|
|
EVP_CIPHER_CTX_cleanup(&ctx_);
|
|
|
|
initialised_ = false;
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CipherBase::Final(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
CipherBase* cipher = ObjectWrap::Unwrap<CipherBase>(args.This());
|
|
|
|
|
|
|
|
unsigned char* out_value = NULL;
|
|
|
|
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 = NULL;
|
|
|
|
out_len = 0;
|
|
|
|
if (!r) return ThrowCryptoTypeError(ERR_get_error());
|
|
|
|
}
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(
|
|
|
|
Buffer::New(reinterpret_cast<char*>(out_value), out_len));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hmac::Initialize(v8::Handle<v8::Object> target) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Local<FunctionTemplate> t = FunctionTemplate::New(New);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "init", HmacInit);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "update", HmacUpdate);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "digest", HmacDigest);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Hmac"), t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hmac::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Hmac* hmac = new Hmac();
|
|
|
|
hmac->Wrap(args.This());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hmac::HmacInit(const char* hash_type, const char* key, int key_len) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
assert(md_ == NULL);
|
|
|
|
md_ = EVP_get_digestbyname(hash_type);
|
|
|
|
if (md_ == NULL) {
|
|
|
|
return ThrowError("Unknown message digest");
|
|
|
|
}
|
|
|
|
HMAC_CTX_init(&ctx_);
|
|
|
|
if (key_len == 0) {
|
|
|
|
HMAC_Init(&ctx_, "", 0, md_);
|
|
|
|
} else {
|
|
|
|
HMAC_Init(&ctx_, key, key_len, md_);
|
|
|
|
}
|
|
|
|
initialised_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hmac::HmacInit(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Hmac* hmac = ObjectWrap::Unwrap<Hmac>(args.This());
|
|
|
|
|
|
|
|
if (args.Length() < 2 || !args[0]->IsString()) {
|
|
|
|
return ThrowError("Must give hashtype string, key as arguments");
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_IS_BUFFER(args[1]);
|
|
|
|
|
|
|
|
const String::Utf8Value hash_type(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) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Hmac* hmac = ObjectWrap::Unwrap<Hmac>(args.This());
|
|
|
|
|
|
|
|
ASSERT_IS_STRING_OR_BUFFER(args[0]);
|
|
|
|
|
|
|
|
// Only copy the data if we have to, because it's a string
|
|
|
|
bool r;
|
|
|
|
if (args[0]->IsString()) {
|
|
|
|
Local<String> string = args[0].As<String>();
|
|
|
|
enum encoding encoding = ParseEncoding(args[1], BINARY);
|
|
|
|
if (!StringBytes::IsValidString(string, encoding))
|
|
|
|
return ThrowTypeError("Bad input string");
|
|
|
|
size_t buflen = StringBytes::StorageSize(string, encoding);
|
|
|
|
char* buf = new char[buflen];
|
|
|
|
size_t written = StringBytes::Write(buf, buflen, string, encoding);
|
|
|
|
r = hmac->HmacUpdate(buf, written);
|
|
|
|
delete[] buf;
|
|
|
|
} else {
|
|
|
|
char* buf = Buffer::Data(args[0]);
|
|
|
|
size_t buflen = Buffer::Length(args[0]);
|
|
|
|
r = hmac->HmacUpdate(buf, buflen);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!r) {
|
|
|
|
return 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) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Hmac* hmac = ObjectWrap::Unwrap<Hmac>(args.This());
|
|
|
|
|
|
|
|
enum encoding encoding = BUFFER;
|
|
|
|
if (args.Length() >= 1) {
|
|
|
|
encoding = ParseEncoding(args[0]->ToString(), BUFFER);
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned char* md_value = NULL;
|
|
|
|
unsigned int md_len = 0;
|
|
|
|
|
|
|
|
bool r = hmac->HmacDigest(&md_value, &md_len);
|
|
|
|
if (!r) {
|
|
|
|
md_value = NULL;
|
|
|
|
md_len = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Value> rc = StringBytes::Encode(
|
|
|
|
reinterpret_cast<const char*>(md_value), md_len, encoding);
|
|
|
|
delete[] md_value;
|
|
|
|
args.GetReturnValue().Set(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hash::Initialize(v8::Handle<v8::Object> target) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Local<FunctionTemplate> t = FunctionTemplate::New(New);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "update", HashUpdate);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "digest", HashDigest);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Hash"), t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hash::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
if (args.Length() == 0 || !args[0]->IsString()) {
|
|
|
|
return ThrowError("Must give hashtype string as argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
const String::Utf8Value hash_type(args[0]);
|
|
|
|
|
|
|
|
Hash* hash = new Hash();
|
|
|
|
if (!hash->HashInit(*hash_type)) {
|
|
|
|
delete hash;
|
|
|
|
return ThrowError("Digest method not supported");
|
|
|
|
}
|
|
|
|
|
|
|
|
hash->Wrap(args.This());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Hash::HashInit(const char* hash_type) {
|
|
|
|
assert(md_ == NULL);
|
|
|
|
md_ = EVP_get_digestbyname(hash_type);
|
|
|
|
if (md_ == NULL) return false;
|
|
|
|
EVP_MD_CTX_init(&mdctx_);
|
|
|
|
EVP_DigestInit_ex(&mdctx_, md_, NULL);
|
|
|
|
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) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Hash* hash = ObjectWrap::Unwrap<Hash>(args.This());
|
|
|
|
|
|
|
|
ASSERT_IS_STRING_OR_BUFFER(args[0]);
|
|
|
|
|
|
|
|
// Only copy the data if we have to, because it's a string
|
|
|
|
bool r;
|
|
|
|
if (args[0]->IsString()) {
|
|
|
|
Local<String> string = args[0].As<String>();
|
|
|
|
enum encoding encoding = ParseEncoding(args[1], BINARY);
|
|
|
|
if (!StringBytes::IsValidString(string, encoding))
|
|
|
|
return ThrowTypeError("Bad input string");
|
|
|
|
size_t buflen = StringBytes::StorageSize(string, encoding);
|
|
|
|
char* buf = new char[buflen];
|
|
|
|
size_t written = StringBytes::Write(buf, buflen, string, encoding);
|
|
|
|
r = hash->HashUpdate(buf, written);
|
|
|
|
delete[] buf;
|
|
|
|
} else {
|
|
|
|
char* buf = Buffer::Data(args[0]);
|
|
|
|
size_t buflen = Buffer::Length(args[0]);
|
|
|
|
r = hash->HashUpdate(buf, buflen);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!r) {
|
|
|
|
return ThrowTypeError("HashUpdate fail");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Hash* hash = ObjectWrap::Unwrap<Hash>(args.This());
|
|
|
|
|
|
|
|
if (!hash->initialised_) {
|
|
|
|
return ThrowError("Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
enum encoding encoding = BUFFER;
|
|
|
|
if (args.Length() >= 1) {
|
|
|
|
encoding = ParseEncoding(args[0]->ToString(), 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(
|
|
|
|
reinterpret_cast<const char*>(md_value), md_len, encoding);
|
|
|
|
args.GetReturnValue().Set(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Sign::Initialize(v8::Handle<v8::Object> target) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Local<FunctionTemplate> t = FunctionTemplate::New(New);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "init", SignInit);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "update", SignUpdate);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "sign", SignFinal);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Sign"), t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Sign::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Sign* sign = new Sign();
|
|
|
|
sign->Wrap(args.This());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Sign::SignInit(const char* sign_type) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
assert(md_ == NULL);
|
|
|
|
md_ = EVP_get_digestbyname(sign_type);
|
|
|
|
if (!md_) {
|
|
|
|
return ThrowError("Uknown message digest");
|
|
|
|
}
|
|
|
|
EVP_MD_CTX_init(&mdctx_);
|
|
|
|
EVP_SignInit_ex(&mdctx_, md_, NULL);
|
|
|
|
initialised_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Sign::SignInit(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Sign* sign = ObjectWrap::Unwrap<Sign>(args.This());
|
|
|
|
|
|
|
|
if (args.Length() == 0 || !args[0]->IsString()) {
|
|
|
|
return ThrowError("Must give signtype string as argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
const String::Utf8Value sign_type(args[0]);
|
|
|
|
sign->SignInit(*sign_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Sign::SignUpdate(const char* data, int len) {
|
|
|
|
if (!initialised_) return false;
|
|
|
|
EVP_SignUpdate(&mdctx_, data, len);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Sign::SignUpdate(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Sign* sign = ObjectWrap::Unwrap<Sign>(args.This());
|
|
|
|
|
|
|
|
ASSERT_IS_STRING_OR_BUFFER(args[0]);
|
|
|
|
|
|
|
|
// Only copy the data if we have to, because it's a string
|
|
|
|
int r;
|
|
|
|
if (args[0]->IsString()) {
|
|
|
|
Local<String> string = args[0].As<String>();
|
|
|
|
enum encoding encoding = ParseEncoding(args[1], BINARY);
|
|
|
|
if (!StringBytes::IsValidString(string, encoding))
|
|
|
|
return ThrowTypeError("Bad input string");
|
|
|
|
size_t buflen = StringBytes::StorageSize(string, encoding);
|
|
|
|
char* buf = new char[buflen];
|
|
|
|
size_t written = StringBytes::Write(buf, buflen, string, encoding);
|
|
|
|
r = sign->SignUpdate(buf, written);
|
|
|
|
delete[] buf;
|
|
|
|
} else {
|
|
|
|
char* buf = Buffer::Data(args[0]);
|
|
|
|
size_t buflen = Buffer::Length(args[0]);
|
|
|
|
r = sign->SignUpdate(buf, buflen);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!r) {
|
|
|
|
return ThrowTypeError("SignUpdate fail");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Sign::SignFinal(unsigned char** md_value,
|
|
|
|
unsigned int *md_len,
|
|
|
|
const char* key_pem,
|
|
|
|
int key_pem_len) {
|
|
|
|
if (!initialised_) return false;
|
|
|
|
|
|
|
|
BIO* bp = NULL;
|
|
|
|
EVP_PKEY* pkey = NULL;
|
|
|
|
bp = BIO_new(BIO_s_mem());
|
|
|
|
if (!BIO_write(bp, key_pem, key_pem_len)) return false;
|
|
|
|
|
|
|
|
pkey = PEM_read_bio_PrivateKey(bp, NULL, NULL, NULL);
|
|
|
|
if (pkey == NULL) return 0;
|
|
|
|
|
|
|
|
EVP_SignFinal(&mdctx_, *md_value, md_len, pkey);
|
|
|
|
EVP_MD_CTX_cleanup(&mdctx_);
|
|
|
|
initialised_ = false;
|
|
|
|
EVP_PKEY_free(pkey);
|
|
|
|
BIO_free_all(bp);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Sign* sign = ObjectWrap::Unwrap<Sign>(args.This());
|
|
|
|
|
|
|
|
unsigned char* md_value;
|
|
|
|
unsigned int md_len;
|
|
|
|
|
|
|
|
enum encoding encoding = BUFFER;
|
|
|
|
if (args.Length() >= 2) {
|
|
|
|
encoding = ParseEncoding(args[1]->ToString(), BUFFER);
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_IS_BUFFER(args[0]);
|
|
|
|
ssize_t 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];
|
|
|
|
|
|
|
|
bool r = sign->SignFinal(&md_value, &md_len, buf, len);
|
|
|
|
if (!r) {
|
|
|
|
delete[] md_value;
|
|
|
|
md_value = NULL;
|
|
|
|
md_len = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Value> rc = StringBytes::Encode(
|
|
|
|
reinterpret_cast<const char*>(md_value), md_len, encoding);
|
|
|
|
delete[] md_value;
|
|
|
|
args.GetReturnValue().Set(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Verify::Initialize(v8::Handle<v8::Object> target) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Local<FunctionTemplate> t = FunctionTemplate::New(New);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "init", VerifyInit);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "update", VerifyUpdate);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "verify", VerifyFinal);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Verify"), t->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Verify::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Verify* verify = new Verify();
|
|
|
|
verify->Wrap(args.This());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Verify::VerifyInit(const char* verify_type) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
assert(md_ == NULL);
|
|
|
|
md_ = EVP_get_digestbyname(verify_type);
|
|
|
|
if (md_ == NULL) {
|
|
|
|
return ThrowError("Unknown message digest");
|
|
|
|
}
|
|
|
|
|
|
|
|
EVP_MD_CTX_init(&mdctx_);
|
|
|
|
EVP_VerifyInit_ex(&mdctx_, md_, NULL);
|
|
|
|
initialised_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Verify::VerifyInit(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Verify* verify = ObjectWrap::Unwrap<Verify>(args.This());
|
|
|
|
|
|
|
|
if (args.Length() == 0 || !args[0]->IsString()) {
|
|
|
|
return ThrowError("Must give verifytype string as argument");
|
|
|
|
}
|
|
|
|
|
|
|
|
const String::Utf8Value verify_type(args[0]);
|
|
|
|
verify->VerifyInit(*verify_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Verify::VerifyUpdate(const char* data, int len) {
|
|
|
|
if (!initialised_) return false;
|
|
|
|
EVP_VerifyUpdate(&mdctx_, data, len);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Verify::VerifyUpdate(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Verify* verify = ObjectWrap::Unwrap<Verify>(args.This());
|
|
|
|
|
|
|
|
ASSERT_IS_STRING_OR_BUFFER(args[0]);
|
|
|
|
|
|
|
|
// Only copy the data if we have to, because it's a string
|
|
|
|
bool r;
|
|
|
|
if (args[0]->IsString()) {
|
|
|
|
Local<String> string = args[0].As<String>();
|
|
|
|
enum encoding encoding = ParseEncoding(args[1], BINARY);
|
|
|
|
if (!StringBytes::IsValidString(string, encoding))
|
|
|
|
return ThrowTypeError("Bad input string");
|
|
|
|
size_t buflen = StringBytes::StorageSize(string, encoding);
|
|
|
|
char* buf = new char[buflen];
|
|
|
|
size_t written = StringBytes::Write(buf, buflen, string, encoding);
|
|
|
|
r = verify->VerifyUpdate(buf, written);
|
|
|
|
delete[] buf;
|
|
|
|
} else {
|
|
|
|
char* buf = Buffer::Data(args[0]);
|
|
|
|
size_t buflen = Buffer::Length(args[0]);
|
|
|
|
r = verify->VerifyUpdate(buf, buflen);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!r) {
|
|
|
|
return ThrowTypeError("VerifyUpdate fail");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Verify::VerifyFinal(const char* key_pem,
|
|
|
|
int key_pem_len,
|
|
|
|
const char* sig,
|
|
|
|
int siglen) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
if (!initialised_) {
|
|
|
|
ThrowError("Verify not initalised");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
EVP_PKEY* pkey = NULL;
|
|
|
|
BIO* bp = NULL;
|
|
|
|
X509* x509 = NULL;
|
|
|
|
bool fatal = true;
|
|
|
|
int r = 0;
|
|
|
|
|
|
|
|
bp = BIO_new(BIO_s_mem());
|
|
|
|
if (bp == NULL)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
if (!BIO_write(bp, key_pem, key_pem_len))
|
|
|
|
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, NULL, NULL, NULL);
|
|
|
|
if (pkey == NULL)
|
|
|
|
goto exit;
|
|
|
|
} else if (strncmp(key_pem, PUBRSA_KEY_PFX, PUBRSA_KEY_PFX_LEN) == 0) {
|
|
|
|
RSA* rsa = PEM_read_bio_RSAPublicKey(bp, NULL, NULL, NULL);
|
|
|
|
if (rsa) {
|
|
|
|
pkey = EVP_PKEY_new();
|
|
|
|
if (pkey) EVP_PKEY_set1_RSA(pkey, rsa);
|
|
|
|
RSA_free(rsa);
|
|
|
|
}
|
|
|
|
if (pkey == NULL)
|
|
|
|
goto exit;
|
|
|
|
} else {
|
|
|
|
// X.509 fallback
|
|
|
|
x509 = PEM_read_bio_X509(bp, NULL, NULL, NULL);
|
|
|
|
if (x509 == NULL)
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
pkey = X509_get_pubkey(x509);
|
|
|
|
if (pkey == NULL)
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
fatal = false;
|
|
|
|
r = EVP_VerifyFinal(&mdctx_,
|
|
|
|
reinterpret_cast<const unsigned char*>(sig),
|
|
|
|
siglen,
|
|
|
|
pkey);
|
|
|
|
|
|
|
|
exit:
|
|
|
|
if (pkey != NULL)
|
|
|
|
EVP_PKEY_free(pkey);
|
|
|
|
if (bp != NULL)
|
|
|
|
BIO_free_all(bp);
|
|
|
|
if (x509 != NULL)
|
|
|
|
X509_free(x509);
|
|
|
|
|
|
|
|
EVP_MD_CTX_cleanup(&mdctx_);
|
|
|
|
initialised_ = false;
|
|
|
|
|
|
|
|
if (fatal) {
|
|
|
|
unsigned long err = ERR_get_error();
|
|
|
|
ThrowCryptoError(err);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return r == 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Verify* verify = ObjectWrap::Unwrap<Verify>(args.This());
|
|
|
|
|
|
|
|
ASSERT_IS_BUFFER(args[0]);
|
|
|
|
char* kbuf = Buffer::Data(args[0]);
|
|
|
|
ssize_t klen = Buffer::Length(args[0]);
|
|
|
|
|
|
|
|
ASSERT_IS_STRING_OR_BUFFER(args[1]);
|
|
|
|
// BINARY works for both buffers and binary strings.
|
|
|
|
enum encoding encoding = BINARY;
|
|
|
|
if (args.Length() >= 3) {
|
|
|
|
encoding = ParseEncoding(args[2]->ToString(), BINARY);
|
|
|
|
}
|
|
|
|
|
|
|
|
ssize_t hlen = StringBytes::Size(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(hbuf, hlen, args[1], encoding);
|
|
|
|
assert(hwritten == hlen);
|
|
|
|
} else {
|
|
|
|
hbuf = Buffer::Data(args[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool rc = verify->VerifyFinal(kbuf, klen, hbuf, hlen);
|
|
|
|
if (args[1]->IsString()) {
|
|
|
|
delete[] hbuf;
|
|
|
|
}
|
|
|
|
args.GetReturnValue().Set(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::Initialize(v8::Handle<v8::Object> target) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Local<FunctionTemplate> t = FunctionTemplate::New(New);
|
|
|
|
|
|
|
|
t->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "generateKeys", GenerateKeys);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "computeSecret", ComputeSecret);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "getPrime", GetPrime);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "getGenerator", GetGenerator);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "getPublicKey", GetPublicKey);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "getPrivateKey", GetPrivateKey);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setPublicKey", SetPublicKey);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t, "setPrivateKey", SetPrivateKey);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "DiffieHellman"),
|
|
|
|
t->GetFunction());
|
|
|
|
|
|
|
|
Local<FunctionTemplate> t2 = FunctionTemplate::New(DiffieHellmanGroup);
|
|
|
|
t2->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t2, "generateKeys", GenerateKeys);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t2, "computeSecret", ComputeSecret);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t2, "getPrime", GetPrime);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t2, "getGenerator", GetGenerator);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t2, "getPublicKey", GetPublicKey);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(t2, "getPrivateKey", GetPrivateKey);
|
|
|
|
|
|
|
|
target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "DiffieHellmanGroup"),
|
|
|
|
t2->GetFunction());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DiffieHellman::Init(int primeLength) {
|
|
|
|
dh = DH_new();
|
|
|
|
DH_generate_parameters_ex(dh, primeLength, DH_GENERATOR_2, 0);
|
|
|
|
bool result = VerifyContext();
|
|
|
|
if (!result) return false;
|
|
|
|
initialised_ = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DiffieHellman::Init(const char* p, int p_len) {
|
|
|
|
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, 2)) 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);
|
|
|
|
initialised_ = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::DiffieHellmanGroup(
|
|
|
|
const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman = new DiffieHellman();
|
|
|
|
|
|
|
|
if (args.Length() != 1 || !args[0]->IsString()) {
|
|
|
|
return ThrowError("No group name given");
|
|
|
|
}
|
|
|
|
|
|
|
|
const String::Utf8Value group_name(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;
|
|
|
|
|
|
|
|
diffieHellman->Init(it->prime,
|
|
|
|
it->prime_size,
|
|
|
|
it->gen,
|
|
|
|
it->gen_size);
|
|
|
|
diffieHellman->Wrap(args.This());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ThrowError("Unknown group");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::New(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman = new DiffieHellman();
|
|
|
|
bool initialized = false;
|
|
|
|
|
|
|
|
if (args.Length() > 0) {
|
|
|
|
if (args[0]->IsInt32()) {
|
|
|
|
initialized = diffieHellman->Init(args[0]->Int32Value());
|
|
|
|
} else {
|
|
|
|
initialized = diffieHellman->Init(Buffer::Data(args[0]),
|
|
|
|
Buffer::Length(args[0]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!initialized) {
|
|
|
|
return ThrowError("Initialization failed");
|
|
|
|
}
|
|
|
|
|
|
|
|
diffieHellman->Wrap(args.This());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman =
|
|
|
|
ObjectWrap::Unwrap<DiffieHellman>(args.This());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowError("Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!DH_generate_key(diffieHellman->dh)) {
|
|
|
|
return ThrowError("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(data, dataSize, BUFFER));
|
|
|
|
delete[] data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::GetPrime(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman =
|
|
|
|
ObjectWrap::Unwrap<DiffieHellman>(args.This());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowError("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(data, dataSize, BUFFER));
|
|
|
|
delete[] data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::GetGenerator(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman =
|
|
|
|
ObjectWrap::Unwrap<DiffieHellman>(args.This());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowError("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(data, dataSize, BUFFER));
|
|
|
|
delete[] data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::GetPublicKey(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman =
|
|
|
|
ObjectWrap::Unwrap<DiffieHellman>(args.This());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowError("Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (diffieHellman->dh->pub_key == NULL) {
|
|
|
|
return 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(data, dataSize, BUFFER));
|
|
|
|
delete[] data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman =
|
|
|
|
ObjectWrap::Unwrap<DiffieHellman>(args.This());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowError("Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (diffieHellman->dh->priv_key == NULL) {
|
|
|
|
return 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(data, dataSize, BUFFER));
|
|
|
|
delete[] data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman =
|
|
|
|
ObjectWrap::Unwrap<DiffieHellman>(args.This());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowError("Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
ClearErrorOnReturn clear_error_on_return;
|
|
|
|
(void) &clear_error_on_return; // Silence compiler warning.
|
|
|
|
BIGNUM* key = NULL;
|
|
|
|
|
|
|
|
if (args.Length() == 0) {
|
|
|
|
return ThrowError("First argument must be other party's public key");
|
|
|
|
} else {
|
|
|
|
ASSERT_IS_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 ThrowError("Invalid key");
|
|
|
|
} else if (checkResult) {
|
|
|
|
if (checkResult & DH_CHECK_PUBKEY_TOO_SMALL) {
|
|
|
|
return ThrowError("Supplied key is too small");
|
|
|
|
} else if (checkResult & DH_CHECK_PUBKEY_TOO_LARGE) {
|
|
|
|
return ThrowError("Supplied key is too large");
|
|
|
|
} else {
|
|
|
|
return ThrowError("Invalid key");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return ThrowError("Invalid key");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BN_free(key);
|
|
|
|
assert(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) {
|
|
|
|
assert(dataSize > size);
|
|
|
|
memmove(data + dataSize - size, data, size);
|
|
|
|
memset(data, 0, dataSize - size);
|
|
|
|
}
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(Encode(data, dataSize, BUFFER));
|
|
|
|
delete[] data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DiffieHellman::SetPublicKey(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman =
|
|
|
|
ObjectWrap::Unwrap<DiffieHellman>(args.This());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowError("Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args.Length() == 0) {
|
|
|
|
return ThrowError("First argument must be public key");
|
|
|
|
} else {
|
|
|
|
ASSERT_IS_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) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
DiffieHellman* diffieHellman =
|
|
|
|
ObjectWrap::Unwrap<DiffieHellman>(args.This());
|
|
|
|
|
|
|
|
if (!diffieHellman->initialised_) {
|
|
|
|
return ThrowError("Not initialized");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args.Length() == 0) {
|
|
|
|
return ThrowError("First argument must be private key");
|
|
|
|
} else {
|
|
|
|
ASSERT_IS_BUFFER(args[0]);
|
|
|
|
diffieHellman->dh->priv_key = BN_bin2bn(
|
|
|
|
reinterpret_cast<unsigned char*>(Buffer::Data(args[0])),
|
|
|
|
Buffer::Length(args[0]),
|
|
|
|
0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool DiffieHellman::VerifyContext() {
|
|
|
|
int codes;
|
|
|
|
if (!DH_check(dh, &codes)) return false;
|
|
|
|
if (codes & DH_CHECK_P_NOT_SAFE_PRIME) return false;
|
|
|
|
if (codes & DH_CHECK_P_NOT_PRIME) return false;
|
|
|
|
if (codes & DH_UNABLE_TO_CHECK_GENERATOR) return false;
|
|
|
|
if (codes & DH_NOT_SUITABLE_GENERATOR) return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct pbkdf2_req {
|
|
|
|
uv_work_t work_req;
|
|
|
|
int err;
|
|
|
|
char* pass;
|
|
|
|
size_t passlen;
|
|
|
|
char* salt;
|
|
|
|
size_t saltlen;
|
|
|
|
size_t iter;
|
|
|
|
char* key;
|
|
|
|
size_t keylen;
|
|
|
|
Persistent<Object> obj;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void EIO_PBKDF2(pbkdf2_req* req) {
|
|
|
|
req->err = PKCS5_PBKDF2_HMAC_SHA1(
|
|
|
|
req->pass,
|
|
|
|
req->passlen,
|
|
|
|
(unsigned char*)req->salt,
|
|
|
|
req->saltlen,
|
|
|
|
req->iter,
|
|
|
|
req->keylen,
|
|
|
|
(unsigned char*)req->key);
|
|
|
|
memset(req->pass, 0, req->passlen);
|
|
|
|
memset(req->salt, 0, req->saltlen);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void EIO_PBKDF2(uv_work_t* work_req) {
|
|
|
|
pbkdf2_req* req = container_of(work_req, pbkdf2_req, work_req);
|
|
|
|
EIO_PBKDF2(req);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void EIO_PBKDF2After(pbkdf2_req* req, Local<Value> argv[2]) {
|
|
|
|
if (req->err) {
|
|
|
|
argv[0] = Undefined(node_isolate);
|
|
|
|
argv[1] = Encode(req->key, req->keylen, BUFFER);
|
|
|
|
memset(req->key, 0, req->keylen);
|
|
|
|
} else {
|
|
|
|
argv[0] = Exception::Error(
|
|
|
|
FIXED_ONE_BYTE_STRING(node_isolate, "PBKDF2 error"));
|
|
|
|
argv[1] = Undefined(node_isolate);
|
|
|
|
}
|
|
|
|
|
|
|
|
delete[] req->pass;
|
|
|
|
delete[] req->salt;
|
|
|
|
delete[] req->key;
|
|
|
|
delete req;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void EIO_PBKDF2After(uv_work_t* work_req, int status) {
|
|
|
|
assert(status == 0);
|
|
|
|
pbkdf2_req* req = container_of(work_req, pbkdf2_req, work_req);
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
// Create a new Local that's associated with the current HandleScope.
|
|
|
|
// PersistentToLocal() returns a handle that gets zeroed when we call
|
|
|
|
// Dispose() so don't use that.
|
|
|
|
Local<Object> obj = Local<Object>::New(node_isolate, req->obj);
|
|
|
|
req->obj.Dispose();
|
|
|
|
Local<Value> argv[2];
|
|
|
|
EIO_PBKDF2After(req, argv);
|
|
|
|
MakeCallback(obj, "ondone", ARRAY_SIZE(argv), argv);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PBKDF2(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
const char* type_error = NULL;
|
|
|
|
char* pass = NULL;
|
|
|
|
char* salt = NULL;
|
|
|
|
ssize_t passlen = -1;
|
|
|
|
ssize_t saltlen = -1;
|
|
|
|
ssize_t keylen = -1;
|
|
|
|
ssize_t pass_written = -1;
|
|
|
|
ssize_t salt_written = -1;
|
|
|
|
ssize_t iter = -1;
|
|
|
|
pbkdf2_req* req = NULL;
|
|
|
|
|
|
|
|
if (args.Length() != 4 && args.Length() != 5) {
|
|
|
|
type_error = "Bad parameter";
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT_IS_BUFFER(args[0]);
|
|
|
|
passlen = Buffer::Length(args[0]);
|
|
|
|
if (passlen < 0) {
|
|
|
|
type_error = "Bad password";
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
pass = new char[passlen];
|
|
|
|
pass_written = DecodeWrite(pass, passlen, args[0], BINARY);
|
|
|
|
assert(pass_written == passlen);
|
|
|
|
|
|
|
|
ASSERT_IS_BUFFER(args[1]);
|
|
|
|
saltlen = Buffer::Length(args[1]);
|
|
|
|
if (saltlen < 0) {
|
|
|
|
type_error = "Bad salt";
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
salt = new char[saltlen];
|
|
|
|
salt_written = DecodeWrite(salt, saltlen, args[1], BINARY);
|
|
|
|
assert(salt_written == 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]->Int32Value();
|
|
|
|
if (keylen < 0) {
|
|
|
|
type_error = "Bad key length";
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
req = new pbkdf2_req;
|
|
|
|
req->err = 0;
|
|
|
|
req->pass = pass;
|
|
|
|
req->passlen = passlen;
|
|
|
|
req->salt = salt;
|
|
|
|
req->saltlen = saltlen;
|
|
|
|
req->iter = iter;
|
|
|
|
req->key = new char[keylen];
|
|
|
|
req->keylen = keylen;
|
|
|
|
|
|
|
|
if (args[4]->IsFunction()) {
|
|
|
|
Local<Object> obj = Object::New();
|
|
|
|
obj->Set(FIXED_ONE_BYTE_STRING(node_isolate, "ondone"), args[4]);
|
|
|
|
req->obj.Reset(node_isolate, obj);
|
|
|
|
uv_queue_work(uv_default_loop(),
|
|
|
|
&req->work_req,
|
|
|
|
EIO_PBKDF2,
|
|
|
|
EIO_PBKDF2After);
|
|
|
|
} else {
|
|
|
|
Local<Value> argv[2];
|
|
|
|
EIO_PBKDF2(req);
|
|
|
|
EIO_PBKDF2After(req, argv);
|
|
|
|
if (argv[0]->IsObject())
|
|
|
|
ThrowException(argv[0]);
|
|
|
|
else
|
|
|
|
args.GetReturnValue().Set(argv[1]);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
err:
|
|
|
|
delete[] salt;
|
|
|
|
delete[] pass;
|
|
|
|
return ThrowTypeError(type_error);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct RandomBytesRequest {
|
|
|
|
~RandomBytesRequest();
|
|
|
|
Persistent<Object> obj_;
|
|
|
|
unsigned long error_; // openssl error code or zero
|
|
|
|
uv_work_t work_req_;
|
|
|
|
size_t size_;
|
|
|
|
char* data_;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
RandomBytesRequest::~RandomBytesRequest() {
|
|
|
|
obj_.Dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <bool pseudoRandom>
|
|
|
|
void RandomBytesWork(uv_work_t* work_req) {
|
|
|
|
RandomBytesRequest* req = container_of(work_req,
|
|
|
|
RandomBytesRequest,
|
|
|
|
work_req_);
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if (pseudoRandom == true) {
|
|
|
|
r = RAND_pseudo_bytes(reinterpret_cast<unsigned char*>(req->data_),
|
|
|
|
req->size_);
|
|
|
|
} else {
|
|
|
|
r = RAND_bytes(reinterpret_cast<unsigned char*>(req->data_), req->size_);
|
|
|
|
}
|
|
|
|
|
|
|
|
// RAND_bytes() returns 0 on error. RAND_pseudo_bytes() returns 0 when the
|
|
|
|
// result is not cryptographically strong - but that's not an error.
|
|
|
|
if (r == 0 && pseudoRandom == false) {
|
|
|
|
req->error_ = ERR_get_error();
|
|
|
|
} else if (r == -1) {
|
|
|
|
req->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_ != (unsigned long) -1)
|
|
|
|
ERR_error_string_n(req->error_, errmsg, sizeof errmsg);
|
|
|
|
|
|
|
|
argv[0] = Exception::Error(OneByteString(node_isolate, errmsg));
|
|
|
|
argv[1] = Null(node_isolate);
|
|
|
|
} else {
|
|
|
|
argv[0] = Null(node_isolate);
|
|
|
|
argv[1] = Buffer::Use(req->data_, req->size_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void RandomBytesAfter(uv_work_t* work_req, int status) {
|
|
|
|
assert(status == 0);
|
|
|
|
RandomBytesRequest* req = container_of(work_req,
|
|
|
|
RandomBytesRequest,
|
|
|
|
work_req_);
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Local<Value> argv[2];
|
|
|
|
RandomBytesCheck(req, argv);
|
|
|
|
MakeCallback(req->obj_, "ondone", ARRAY_SIZE(argv), argv);
|
|
|
|
delete req;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <bool pseudoRandom>
|
|
|
|
void RandomBytes(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
// maybe allow a buffer to write to? cuts down on object creation
|
|
|
|
// when generating random data in a loop
|
|
|
|
if (!args[0]->IsUint32()) {
|
|
|
|
return ThrowTypeError("Argument #1 must be number > 0");
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint32_t size = args[0]->Uint32Value();
|
|
|
|
if (size > Buffer::kMaxLength) {
|
|
|
|
return ThrowTypeError("size > Buffer::kMaxLength");
|
|
|
|
}
|
|
|
|
|
|
|
|
RandomBytesRequest* req = new RandomBytesRequest();
|
|
|
|
req->error_ = 0;
|
|
|
|
req->data_ = new char[size];
|
|
|
|
req->size_ = size;
|
|
|
|
|
|
|
|
if (args[1]->IsFunction()) {
|
|
|
|
Local<Object> obj = Object::New();
|
|
|
|
obj->Set(FIXED_ONE_BYTE_STRING(node_isolate, "ondone"), args[1]);
|
|
|
|
req->obj_.Reset(node_isolate, obj);
|
|
|
|
|
|
|
|
uv_queue_work(uv_default_loop(),
|
|
|
|
&req->work_req_,
|
|
|
|
RandomBytesWork<pseudoRandom>,
|
|
|
|
RandomBytesAfter);
|
|
|
|
args.GetReturnValue().Set(obj);
|
|
|
|
} else {
|
|
|
|
Local<Value> argv[2];
|
|
|
|
RandomBytesWork<pseudoRandom>(&req->work_req_);
|
|
|
|
RandomBytesCheck(req, argv);
|
|
|
|
delete req;
|
|
|
|
|
|
|
|
if (!argv[0]->IsNull())
|
|
|
|
ThrowException(argv[0]);
|
|
|
|
else
|
|
|
|
args.GetReturnValue().Set(argv[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
SSL_CTX* ctx = SSL_CTX_new(TLSv1_server_method());
|
|
|
|
if (ctx == NULL) {
|
|
|
|
return ThrowError("SSL_CTX_new() failed.");
|
|
|
|
}
|
|
|
|
|
|
|
|
SSL* ssl = SSL_new(ctx);
|
|
|
|
if (ssl == NULL) {
|
|
|
|
SSL_CTX_free(ctx);
|
|
|
|
return ThrowError("SSL_new() failed.");
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Array> arr = Array::New();
|
|
|
|
STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(ssl);
|
|
|
|
|
|
|
|
for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); ++i) {
|
|
|
|
SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i);
|
|
|
|
arr->Set(i, OneByteString(node_isolate, SSL_CIPHER_get_name(cipher)));
|
|
|
|
}
|
|
|
|
|
|
|
|
SSL_free(ssl);
|
|
|
|
SSL_CTX_free(ctx);
|
|
|
|
|
|
|
|
args.GetReturnValue().Set(arr);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class TypeName>
|
|
|
|
static void array_push_back(const TypeName* md,
|
|
|
|
const char* from,
|
|
|
|
const char* to,
|
|
|
|
void* arg) {
|
|
|
|
Local<Array>& arr = *static_cast<Local<Array>*>(arg);
|
|
|
|
arr->Set(arr->Length(), OneByteString(node_isolate, from));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void GetCiphers(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Local<Array> arr = Array::New();
|
|
|
|
EVP_CIPHER_do_all_sorted(array_push_back<EVP_CIPHER>, &arr);
|
|
|
|
args.GetReturnValue().Set(arr);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void GetHashes(const FunctionCallbackInfo<Value>& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Local<Array> arr = Array::New();
|
|
|
|
EVP_MD_do_all_sorted(array_push_back<EVP_MD>, &arr);
|
|
|
|
args.GetReturnValue().Set(arr);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void InitCrypto(Handle<Object> target) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
SSL_library_init();
|
|
|
|
OpenSSL_add_all_algorithms();
|
|
|
|
OpenSSL_add_all_digests();
|
|
|
|
SSL_load_error_strings();
|
|
|
|
ERR_load_crypto_strings();
|
|
|
|
|
|
|
|
crypto_lock_init();
|
|
|
|
CRYPTO_set_locking_callback(crypto_lock_cb);
|
|
|
|
CRYPTO_THREADID_set_callback(crypto_threadid_cb);
|
|
|
|
|
|
|
|
// Turn off compression. Saves memory - do it in userland.
|
|
|
|
#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);
|
|
|
|
assert(sk_SSL_COMP_num(comp_methods) == 0);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
SecureContext::Initialize(target);
|
|
|
|
Connection::Initialize(target);
|
|
|
|
CipherBase::Initialize(target);
|
|
|
|
DiffieHellman::Initialize(target);
|
|
|
|
Hmac::Initialize(target);
|
|
|
|
Hash::Initialize(target);
|
|
|
|
Sign::Initialize(target);
|
|
|
|
Verify::Initialize(target);
|
|
|
|
|
|
|
|
NODE_SET_METHOD(target, "PBKDF2", PBKDF2);
|
|
|
|
NODE_SET_METHOD(target, "randomBytes", RandomBytes<false>);
|
|
|
|
NODE_SET_METHOD(target, "pseudoRandomBytes", RandomBytes<true>);
|
|
|
|
NODE_SET_METHOD(target, "getSSLCiphers", GetSSLCiphers);
|
|
|
|
NODE_SET_METHOD(target, "getCiphers", GetCiphers);
|
|
|
|
NODE_SET_METHOD(target, "getHashes", GetHashes);
|
|
|
|
|
|
|
|
subject_symbol = FIXED_ONE_BYTE_STRING(node_isolate, "subject");
|
|
|
|
issuer_symbol = FIXED_ONE_BYTE_STRING(node_isolate, "issuer");
|
|
|
|
valid_from_symbol = FIXED_ONE_BYTE_STRING(node_isolate, "valid_from");
|
|
|
|
valid_to_symbol = FIXED_ONE_BYTE_STRING(node_isolate, "valid_to");
|
|
|
|
subjectaltname_symbol = FIXED_ONE_BYTE_STRING(node_isolate, "subjectaltname");
|
|
|
|
modulus_symbol = FIXED_ONE_BYTE_STRING(node_isolate, "modulus");
|
|
|
|
exponent_symbol = FIXED_ONE_BYTE_STRING(node_isolate, "exponent");
|
|
|
|
fingerprint_symbol = FIXED_ONE_BYTE_STRING(node_isolate, "fingerprint");
|
|
|
|
name_symbol = FIXED_ONE_BYTE_STRING(node_isolate, "name");
|
|
|
|
version_symbol = FIXED_ONE_BYTE_STRING(node_isolate, "version");
|
|
|
|
ext_key_usage_symbol = FIXED_ONE_BYTE_STRING(node_isolate, "ext_key_usage");
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace crypto
|
|
|
|
} // namespace node
|
|
|
|
|
|
|
|
NODE_MODULE(node_crypto, node::crypto::InitCrypto)
|