| // Copyright Joyent, Inc. and other Node contributors. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a |
| // copy of this software and associated documentation files (the |
| // "Software"), to deal in the Software without restriction, including |
| // without limitation the rights to use, copy, modify, merge, publish, |
| // distribute, sublicense, and/or sell copies of the Software, and to permit |
| // persons to whom the Software is furnished to do so, subject to the |
| // following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included |
| // in all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
| // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| // USE OR OTHER DEALINGS IN THE SOFTWARE. |
| |
| #include "node_crypto.h" |
| #include "node_crypto_groups.h" |
| #include "v8.h" |
| |
| #include "node.h" |
| #include "node_buffer.h" |
| #include "string_bytes.h" |
| #include "node_root_certs.h" |
| |
| #include <string.h> |
| #ifdef _MSC_VER |
| #define strcasecmp _stricmp |
| #endif |
| |
| #include <stdlib.h> |
| #include <errno.h> |
| |
| #if OPENSSL_VERSION_NUMBER >= 0x10000000L |
| # define OPENSSL_CONST const |
| #else |
| # define OPENSSL_CONST |
| #endif |
| |
| #define ASSERT_IS_STRING_OR_BUFFER(val) \ |
| if (!Buffer::HasInstance(val) && !val->IsString()) { \ |
| return ThrowException(Exception::TypeError(String::New( \ |
| "Not a string or buffer"))); \ |
| } |
| |
| #define ASSERT_IS_BUFFER(val) \ |
| if (!Buffer::HasInstance(val)) { \ |
| return ThrowException(Exception::TypeError(String::New("Not a buffer"))); \ |
| } |
| |
| 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 namespace v8; |
| |
| // 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 Persistent<String> errno_symbol; |
| static Persistent<String> syscall_symbol; |
| static Persistent<String> subject_symbol; |
| static Persistent<String> subjectaltname_symbol; |
| static Persistent<String> modulus_symbol; |
| static Persistent<String> exponent_symbol; |
| static Persistent<String> issuer_symbol; |
| static Persistent<String> valid_from_symbol; |
| static Persistent<String> valid_to_symbol; |
| static Persistent<String> fingerprint_symbol; |
| static Persistent<String> name_symbol; |
| static Persistent<String> version_symbol; |
| static Persistent<String> ext_key_usage_symbol; |
| static Persistent<String> onhandshakestart_sym; |
| static Persistent<String> onhandshakedone_sym; |
| static Persistent<String> onclienthello_sym; |
| static Persistent<String> onnewsession_sym; |
| static Persistent<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); |
| } |
| } |
| |
| |
| Handle<Value> ThrowCryptoErrorHelper(unsigned long err, bool is_type_error) { |
| HandleScope scope; |
| char errmsg[128]; |
| ERR_error_string_n(err, errmsg, sizeof(errmsg)); |
| return is_type_error ? ThrowTypeError(errmsg) : ThrowError(errmsg); |
| } |
| |
| |
| Handle<Value> ThrowCryptoError(unsigned long err) { |
| return ThrowCryptoErrorHelper(err, false); |
| } |
| |
| |
| Handle<Value> ThrowCryptoTypeError(unsigned long err) { |
| return ThrowCryptoErrorHelper(err, true); |
| } |
| |
| |
| void SecureContext::Initialize(Handle<Object> target) { |
| HandleScope scope; |
| |
| Local<FunctionTemplate> t = FunctionTemplate::New(SecureContext::New); |
| secure_context_constructor = Persistent<FunctionTemplate>::New(t); |
| |
| t->InstanceTemplate()->SetInternalFieldCount(1); |
| t->SetClassName(String::NewSymbol("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, "close", SecureContext::Close); |
| NODE_SET_PROTOTYPE_METHOD(t, "loadPKCS12", SecureContext::LoadPKCS12); |
| |
| target->Set(String::NewSymbol("SecureContext"), t->GetFunction()); |
| } |
| |
| |
| Handle<Value> SecureContext::New(const Arguments& args) { |
| HandleScope scope; |
| SecureContext *p = new SecureContext(); |
| p->Wrap(args.Holder()); |
| return args.This(); |
| } |
| |
| |
| Handle<Value> SecureContext::Init(const Arguments& args) { |
| HandleScope scope; |
| |
| SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.Holder()); |
| |
| OPENSSL_CONST SSL_METHOD *method = SSLv23_method(); |
| |
| if (args.Length() == 1 && args[0]->IsString()) { |
| String::Utf8Value sslmethod(args[0]); |
| |
| if (strcmp(*sslmethod, "SSLv2_method") == 0) { |
| #ifndef OPENSSL_NO_SSL2 |
| method = SSLv2_method(); |
| #else |
| return ThrowException(Exception::Error(String::New("SSLv2 methods disabled"))); |
| #endif |
| } else if (strcmp(*sslmethod, "SSLv2_server_method") == 0) { |
| #ifndef OPENSSL_NO_SSL2 |
| method = SSLv2_server_method(); |
| #else |
| return ThrowException(Exception::Error(String::New("SSLv2 methods disabled"))); |
| #endif |
| } else if (strcmp(*sslmethod, "SSLv2_client_method") == 0) { |
| #ifndef OPENSSL_NO_SSL2 |
| method = SSLv2_client_method(); |
| #else |
| return ThrowException(Exception::Error(String::New("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 ThrowException(Exception::Error(String::New("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; |
| return True(); |
| } |
| |
| |
| SSL_SESSION* SecureContext::GetSessionCallback(SSL* s, |
| unsigned char* key, |
| int len, |
| int* copy) { |
| HandleScope scope; |
| |
| Connection* p = static_cast<Connection*>(SSL_get_app_data(s)); |
| |
| *copy = 0; |
| SSL_SESSION* sess = p->next_sess_; |
| p->next_sess_ = NULL; |
| |
| return sess; |
| } |
| |
| |
| void SessionDataFree(char* data, void* hint) { |
| delete[] data; |
| } |
| |
| |
| int SecureContext::NewSessionCallback(SSL* s, SSL_SESSION* sess) { |
| HandleScope scope; |
| |
| 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)->handle_, |
| Buffer::New(serialized, size, SessionDataFree, NULL)->handle_ |
| }; |
| |
| if (onnewsession_sym.IsEmpty()) { |
| onnewsession_sym = NODE_PSYMBOL("onnewsession"); |
| } |
| MakeCallback(p->handle_, onnewsession_sym, ARRAY_SIZE(argv), argv); |
| |
| return 0; |
| } |
| |
| |
| // Takes a string or buffer and loads it into a BIO. |
| // Caller responsible for BIO_free-ing the returned object. |
| static BIO* LoadBIO (Handle<Value> v) { |
| BIO *bio = BIO_new(BIO_s_mem()); |
| if (!bio) return NULL; |
| |
| HandleScope scope; |
| |
| int r = -1; |
| |
| if (v->IsString()) { |
| 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(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; // necessary? |
| |
| BIO *bio = LoadBIO(v); |
| if (!bio) return NULL; |
| |
| X509 * x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); |
| if (!x509) { |
| BIO_free(bio); |
| return NULL; |
| } |
| |
| BIO_free(bio); |
| return x509; |
| } |
| |
| |
| Handle<Value> SecureContext::SetKey(const Arguments& args) { |
| HandleScope scope; |
| |
| SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.Holder()); |
| |
| unsigned int len = args.Length(); |
| if (len != 1 && len != 2) { |
| return ThrowException(Exception::TypeError(String::New("Bad parameter"))); |
| } |
| if (len == 2 && !args[1]->IsString()) { |
| return ThrowException(Exception::TypeError(String::New("Bad parameter"))); |
| } |
| |
| BIO *bio = LoadBIO(args[0]); |
| if (!bio) return False(); |
| |
| String::Utf8Value passphrase(args[1]); |
| |
| EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, NULL, NULL, |
| len == 1 ? NULL : *passphrase); |
| |
| if (!key) { |
| BIO_free(bio); |
| unsigned long err = ERR_get_error(); |
| if (!err) { |
| return ThrowException(Exception::Error( |
| String::New("PEM_read_bio_PrivateKey"))); |
| } |
| return ThrowCryptoError(err); |
| } |
| |
| SSL_CTX_use_PrivateKey(sc->ctx_, key); |
| EVP_PKEY_free(key); |
| BIO_free(bio); |
| |
| return True(); |
| } |
| |
| |
| // 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; |
| } |
| |
| |
| Handle<Value> SecureContext::SetCert(const Arguments& args) { |
| HandleScope scope; |
| |
| SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.Holder()); |
| |
| if (args.Length() != 1) { |
| return ThrowException(Exception::TypeError( |
| String::New("Bad parameter"))); |
| } |
| |
| BIO* bio = LoadBIO(args[0]); |
| if (!bio) return False(); |
| |
| int rv = SSL_CTX_use_certificate_chain(sc->ctx_, bio); |
| |
| BIO_free(bio); |
| |
| if (!rv) { |
| unsigned long err = ERR_get_error(); |
| if (!err) { |
| return ThrowException(Exception::Error( |
| String::New("SSL_CTX_use_certificate_chain"))); |
| } |
| return ThrowCryptoError(err); |
| } |
| |
| return True(); |
| } |
| |
| |
| Handle<Value> SecureContext::AddCACert(const Arguments& args) { |
| bool newCAStore = false; |
| HandleScope scope; |
| |
| SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.Holder()); |
| |
| if (args.Length() != 1) { |
| return ThrowException(Exception::TypeError(String::New("Bad parameter"))); |
| } |
| |
| if (!sc->ca_store_) { |
| sc->ca_store_ = X509_STORE_new(); |
| newCAStore = true; |
| } |
| |
| X509* x509 = LoadX509(args[0]); |
| if (!x509) return False(); |
| |
| 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_); |
| } |
| |
| return True(); |
| } |
| |
| |
| Handle<Value> SecureContext::AddCRL(const Arguments& args) { |
| HandleScope scope; |
| |
| SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.Holder()); |
| |
| if (args.Length() != 1) { |
| return ThrowException(Exception::TypeError(String::New("Bad parameter"))); |
| } |
| |
| BIO *bio = LoadBIO(args[0]); |
| if (!bio) return False(); |
| |
| X509_CRL *x509 = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL); |
| |
| if (x509 == NULL) { |
| BIO_free(bio); |
| return False(); |
| } |
| |
| 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(bio); |
| X509_CRL_free(x509); |
| |
| return True(); |
| } |
| |
| |
| |
| Handle<Value> SecureContext::AddRootCerts(const Arguments& args) { |
| HandleScope scope; |
| |
| SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.Holder()); |
| |
| 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(BIO_s_mem()); |
| |
| if (!BIO_write(bp, root_certs[i], strlen(root_certs[i]))) { |
| BIO_free(bp); |
| return False(); |
| } |
| |
| X509 *x509 = PEM_read_bio_X509(bp, NULL, NULL, NULL); |
| |
| if (x509 == NULL) { |
| BIO_free(bp); |
| return False(); |
| } |
| |
| X509_STORE_add_cert(root_cert_store, x509); |
| |
| BIO_free(bp); |
| X509_free(x509); |
| } |
| } |
| |
| sc->ca_store_ = root_cert_store; |
| SSL_CTX_set_cert_store(sc->ctx_, sc->ca_store_); |
| |
| return True(); |
| } |
| |
| |
| Handle<Value> SecureContext::SetCiphers(const Arguments& args) { |
| HandleScope scope; |
| |
| SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.Holder()); |
| |
| if (args.Length() != 1 || !args[0]->IsString()) { |
| return ThrowException(Exception::TypeError(String::New("Bad parameter"))); |
| } |
| |
| String::Utf8Value ciphers(args[0]); |
| SSL_CTX_set_cipher_list(sc->ctx_, *ciphers); |
| |
| return True(); |
| } |
| |
| Handle<Value> SecureContext::SetOptions(const Arguments& args) { |
| HandleScope scope; |
| |
| SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.Holder()); |
| |
| if (args.Length() != 1 || !args[0]->IntegerValue()) { |
| return ThrowException(Exception::TypeError(String::New("Bad parameter"))); |
| } |
| |
| SSL_CTX_set_options(sc->ctx_, args[0]->IntegerValue()); |
| |
| return True(); |
| } |
| |
| Handle<Value> SecureContext::SetSessionIdContext(const Arguments& args) { |
| HandleScope scope; |
| |
| SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.Holder()); |
| |
| if (args.Length() != 1 || !args[0]->IsString()) { |
| return ThrowException(Exception::TypeError(String::New("Bad parameter"))); |
| } |
| |
| String::Utf8Value sessionIdContext(args[0]); |
| const unsigned char* sid_ctx = (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) { |
| Local<String> message; |
| BIO* bio; |
| BUF_MEM* mem; |
| if ((bio = BIO_new(BIO_s_mem()))) { |
| ERR_print_errors(bio); |
| BIO_get_mem_ptr(bio, &mem); |
| message = String::New(mem->data, mem->length); |
| BIO_free(bio); |
| } else { |
| message = String::New("SSL_CTX_set_session_id_context error"); |
| } |
| return ThrowException(Exception::TypeError(message)); |
| } |
| |
| return True(); |
| } |
| |
| Handle<Value> SecureContext::Close(const Arguments& args) { |
| HandleScope scope; |
| SecureContext *sc = ObjectWrap::Unwrap<SecureContext>(args.Holder()); |
| sc->FreeCTXMem(); |
| return False(); |
| } |
| |
| //Takes .pfx or .p12 and password in string or buffer format |
| Handle<Value> SecureContext::LoadPKCS12(const Arguments& args) { |
| HandleScope scope; |
| |
| 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.Holder()); |
| |
| if (args.Length() < 1) { |
| return ThrowException(Exception::TypeError( |
| String::New("Bad parameter"))); |
| } |
| |
| in = LoadBIO(args[0]); |
| if (in == NULL) { |
| return ThrowException(Exception::Error( |
| String::New("Unable to load BIO"))); |
| } |
| |
| if (args.Length() >= 2) { |
| ASSERT_IS_BUFFER(args[1]); |
| |
| int passlen = Buffer::Length(args[1]); |
| if (passlen < 0) { |
| BIO_free(in); |
| return ThrowException(Exception::TypeError( |
| String::New("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(in); |
| delete[] pass; |
| |
| if (!ret) { |
| unsigned long err = ERR_get_error(); |
| const char* str = ERR_reason_error_string(err); |
| return ThrowException(Exception::Error(String::New(str))); |
| } |
| |
| return True(); |
| } |
| |
| |
| size_t ClientHelloParser::Write(const uint8_t* data, size_t len) { |
| HandleScope scope; |
| |
| // Just accumulate data, everything will be pushed to BIO later |
| if (state_ == kPaused) return 0; |
| |
| // Copy incoming data to the internal buffer |
| // (which has a size of the biggest possible TLS frame) |
| size_t available = sizeof(data_) - offset_; |
| size_t copied = len < available ? len : available; |
| memcpy(data_ + offset_, data, copied); |
| offset_ += copied; |
| |
| // Vars for parsing hello |
| bool is_clienthello = false; |
| uint8_t session_size = -1; |
| uint8_t* session_id = NULL; |
| Local<Object> hello; |
| Handle<Value> argv[1]; |
| |
| switch (state_) { |
| case kWaiting: |
| // >= 5 bytes for header parsing |
| if (offset_ < 5) break; |
| |
| if (data_[0] == kChangeCipherSpec || data_[0] == kAlert || |
| data_[0] == kHandshake || data_[0] == kApplicationData) { |
| frame_len_ = (data_[3] << 8) + data_[4]; |
| state_ = kTLSHeader; |
| body_offset_ = 5; |
| } else { |
| frame_len_ = (data_[0] << 8) + data_[1]; |
| state_ = kSSLHeader; |
| if (*data_ & 0x40) { |
| // header with padding |
| body_offset_ = 3; |
| } else { |
| // without padding |
| body_offset_ = 2; |
| } |
| } |
| |
| // Sanity check (too big frame, or too small) |
| if (frame_len_ >= sizeof(data_)) { |
| // Let OpenSSL handle it |
| Finish(); |
| return copied; |
| } |
| case kTLSHeader: |
| case kSSLHeader: |
| // >= 5 + frame size bytes for frame parsing |
| if (offset_ < body_offset_ + frame_len_) break; |
| |
| // Skip unsupported frames and gather some data from frame |
| |
| // TODO: Check protocol version |
| if (data_[body_offset_] == kClientHello) { |
| is_clienthello = true; |
| uint8_t* body; |
| size_t session_offset; |
| |
| if (state_ == kTLSHeader) { |
| // Skip frame header, hello header, protocol version and random data |
| session_offset = body_offset_ + 4 + 2 + 32; |
| |
| if (session_offset + 1 < offset_) { |
| body = data_ + session_offset; |
| session_size = *body; |
| session_id = body + 1; |
| } |
| } else if (state_ == kSSLHeader) { |
| // Skip header, version |
| session_offset = body_offset_ + 3; |
| |
| if (session_offset + 4 < offset_) { |
| body = data_ + session_offset; |
| |
| int ciphers_size = (body[0] << 8) + body[1]; |
| |
| if (body + 4 + ciphers_size < data_ + offset_) { |
| session_size = (body[2] << 8) + body[3]; |
| session_id = body + 4 + ciphers_size; |
| } |
| } |
| } else { |
| // Whoa? How did we get here? |
| abort(); |
| } |
| |
| // Check if we overflowed (do not reply with any private data) |
| if (session_id == NULL || |
| session_size > 32 || |
| session_id + session_size > data_ + offset_) { |
| Finish(); |
| return copied; |
| } |
| |
| // TODO: Parse other things? |
| } |
| |
| // Not client hello - let OpenSSL handle it |
| if (!is_clienthello) { |
| Finish(); |
| return copied; |
| } |
| |
| // Parse frame, call javascript handler and |
| // move parser into the paused state |
| if (onclienthello_sym.IsEmpty()) { |
| onclienthello_sym = NODE_PSYMBOL("onclienthello"); |
| } |
| if (sessionid_sym.IsEmpty()) { |
| sessionid_sym = NODE_PSYMBOL("sessionId"); |
| } |
| |
| state_ = kPaused; |
| hello = Object::New(); |
| hello->Set(sessionid_sym, |
| Buffer::New(reinterpret_cast<char*>(session_id), |
| session_size)->handle_); |
| |
| argv[0] = hello; |
| MakeCallback(conn_->handle_, onclienthello_sym, 1, argv); |
| break; |
| case kEnded: |
| default: |
| break; |
| } |
| |
| return copied; |
| } |
| |
| |
| void ClientHelloParser::Finish() { |
| assert(state_ != kEnded); |
| state_ = kEnded; |
| |
| // Write all accumulated data |
| int r = BIO_write(conn_->bio_read_, reinterpret_cast<char*>(data_), offset_); |
| conn_->HandleBIOError(conn_->bio_read_, "BIO_write", r); |
| conn_->SetShutdownFlags(); |
| } |
| |
| |
| #ifdef SSL_PRINT_DEBUG |
| # define DEBUG_PRINT(...) fprintf (stderr, __VA_ARGS__) |
| #else |
| # define DEBUG_PRINT(...) |
| #endif |
| |
| |
| int Connection::HandleBIOError(BIO *bio, const char* func, int rv) { |
| if (rv >= 0) return rv; |
| |
| int retry = BIO_should_retry(bio); |
| (void) retry; // unused if !defined(SSL_PRINT_DEBUG) |
| |
| if (BIO_should_write(bio)) { |
| DEBUG_PRINT("[%p] BIO: %s want write. should retry %d\n", ssl_, func, retry); |
| return 0; |
| |
| } else if (BIO_should_read(bio)) { |
| DEBUG_PRINT("[%p] BIO: %s want read. should retry %d\n", ssl_, func, retry); |
| return 0; |
| |
| } else { |
| static char ssl_error_buf[512]; |
| ERR_error_string_n(rv, ssl_error_buf, sizeof(ssl_error_buf)); |
| |
| HandleScope scope; |
| Local<Value> e = Exception::Error(String::New(ssl_error_buf)); |
| handle_->Set(String::New("error"), e); |
| |
| 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) { |
| handle_->Set(String::New("error"), |
| Exception::Error(String::New("ZERO_RETURN"))); |
| return rv; |
| |
| } else if ((err == SSL_ERROR_SYSCALL) && (ss == kIgnoreSyscall)) { |
| return 0; |
| |
| } else { |
| HandleScope scope; |
| 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> e = Exception::Error(String::New(mem->data, mem->length)); |
| handle_->Set(String::New("error"), e); |
| BIO_free(bio); |
| } |
| |
| return rv; |
| } |
| |
| return 0; |
| } |
| |
| |
| void Connection::ClearError() { |
| #ifndef NDEBUG |
| HandleScope scope; |
| |
| // We should clear the error in JS-land |
| assert(handle_->Get(String::New("error"))->BooleanValue() == false); |
| #endif // NDEBUG |
| } |
| |
| |
| void Connection::SetShutdownFlags() { |
| HandleScope scope; |
| |
| int flags = SSL_get_shutdown(ssl_); |
| |
| if (flags & SSL_SENT_SHUTDOWN) { |
| handle_->Set(String::New("sentShutdown"), True()); |
| } |
| |
| if (flags & SSL_RECEIVED_SHUTDOWN) { |
| handle_->Set(String::New("receivedShutdown"), True()); |
| } |
| } |
| |
| |
| void Connection::Initialize(Handle<Object> target) { |
| HandleScope scope; |
| |
| Local<FunctionTemplate> t = FunctionTemplate::New(Connection::New); |
| t->InstanceTemplate()->SetInternalFieldCount(1); |
| t->SetClassName(String::NewSymbol("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, "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(String::NewSymbol("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 { |
| *data = reinterpret_cast<const unsigned char*>(Buffer::Data(p->npnProtos_)); |
| *len = Buffer::Length(p->npnProtos_); |
| } |
| |
| 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 |
| if (!p->selectedNPNProto_.IsEmpty()) { |
| 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_ = Persistent<Value>::New(False()); |
| |
| return SSL_TLSEXT_ERR_OK; |
| } |
| |
| const unsigned char* npnProtos = |
| reinterpret_cast<const unsigned char*>(Buffer::Data(p->npnProtos_)); |
| |
| int status = SSL_select_next_proto(out, outlen, in, inlen, npnProtos, |
| Buffer::Length(p->npnProtos_)); |
| |
| switch (status) { |
| case OPENSSL_NPN_UNSUPPORTED: |
| p->selectedNPNProto_ = Persistent<Value>::New(Null()); |
| break; |
| case OPENSSL_NPN_NEGOTIATED: |
| p->selectedNPNProto_ = Persistent<Value>::New(String::New( |
| reinterpret_cast<const char*>(*out), *outlen |
| )); |
| break; |
| case OPENSSL_NPN_NO_OVERLAP: |
| p->selectedNPNProto_ = Persistent<Value>::New(False()); |
| 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; |
| |
| Connection *p = static_cast<Connection*> SSL_get_app_data(s); |
| |
| const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); |
| |
| if (servername) { |
| if (!p->servername_.IsEmpty()) { |
| p->servername_.Dispose(); |
| } |
| p->servername_ = Persistent<String>::New(String::New(servername)); |
| |
| // Call the SNI callback and use its return value as context |
| if (!p->sniObject_.IsEmpty()) { |
| if (!p->sniContext_.IsEmpty()) { |
| p->sniContext_.Dispose(); |
| } |
| |
| // Get callback init args |
| Local<Value> argv[1] = {*p->servername_}; |
| |
| // Call it |
| Local<Value> ret = Local<Value>::New(MakeCallback(p->sniObject_, |
| "onselect", |
| ARRAY_SIZE(argv), |
| argv)); |
| |
| // If ret is SecureContext |
| if (secure_context_constructor->HasInstance(ret)) { |
| p->sniContext_ = Persistent<Value>::New(ret); |
| SecureContext *sc = ObjectWrap::Unwrap<SecureContext>( |
| Local<Object>::Cast(ret)); |
| p->InitNPN(sc, true); |
| SSL_set_SSL_CTX(s, sc->ctx_); |
| } else { |
| return SSL_TLSEXT_ERR_NOACK; |
| } |
| } |
| } |
| |
| return SSL_TLSEXT_ERR_OK; |
| } |
| #endif |
| |
| Handle<Value> Connection::New(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *p = new Connection(); |
| p->Wrap(args.Holder()); |
| |
| if (args.Length() < 1 || !args[0]->IsObject()) { |
| return ThrowException(Exception::Error(String::New( |
| "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(BIO_s_mem()); |
| p->bio_write_ = BIO_new(BIO_s_mem()); |
| |
| SSL_set_app_data(p->ssl_, p); |
| |
| if (is_server) SSL_set_info_callback(p->ssl_, SSLInfoCallback); |
| |
| p->InitNPN(sc, is_server); |
| |
| #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB |
| if (is_server) { |
| SSL_CTX_set_tlsext_servername_callback(sc->ctx_, SelectSNIContextCallback_); |
| } else { |
| 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_); |
| } |
| |
| return args.This(); |
| } |
| |
| |
| 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; |
| Connection* c = static_cast<Connection*>(SSL_get_app_data(ssl)); |
| if (onhandshakestart_sym.IsEmpty()) { |
| onhandshakestart_sym = NODE_PSYMBOL("onhandshakestart"); |
| } |
| MakeCallback(c->handle_, onhandshakestart_sym, 0, NULL); |
| } |
| if (where & SSL_CB_HANDSHAKE_DONE) { |
| HandleScope scope; |
| Connection* c = static_cast<Connection*>(SSL_get_app_data(ssl)); |
| if (onhandshakedone_sym.IsEmpty()) { |
| onhandshakedone_sym = NODE_PSYMBOL("onhandshakedone"); |
| } |
| MakeCallback(c->handle_, onhandshakedone_sym, 0, NULL); |
| } |
| } |
| |
| |
| Handle<Value> Connection::EncIn(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (args.Length() < 3) { |
| return ThrowException(Exception::TypeError( |
| String::New("Takes 3 parameters"))); |
| } |
| |
| if (!Buffer::HasInstance(args[0])) { |
| return ThrowException(Exception::TypeError( |
| String::New("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 ThrowException(Exception::Error( |
| String::New("off + len > buffer.length"))); |
| } |
| |
| int bytes_written; |
| char* data = buffer_data + off; |
| |
| if (ss->is_server_ && !ss->hello_parser_.ended()) { |
| bytes_written = ss->hello_parser_.Write(reinterpret_cast<uint8_t*>(data), |
| len); |
| } else { |
| bytes_written = BIO_write(ss->bio_read_, data, len); |
| ss->HandleBIOError(ss->bio_read_, "BIO_write", bytes_written); |
| ss->SetShutdownFlags(); |
| } |
| |
| return scope.Close(Integer::New(bytes_written)); |
| } |
| |
| |
| Handle<Value> Connection::ClearOut(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (args.Length() < 3) { |
| return ThrowException(Exception::TypeError( |
| String::New("Takes 3 parameters"))); |
| } |
| |
| if (!Buffer::HasInstance(args[0])) { |
| return ThrowException(Exception::TypeError( |
| String::New("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 ThrowException(Exception::Error( |
| String::New("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 scope.Close(Integer::New(rv)); |
| } |
| |
| int bytes_read = SSL_read(ss->ssl_, buffer_data + off, len); |
| ss->HandleSSLError("SSL_read:ClearOut", |
| bytes_read, |
| kZeroIsNotAnError, |
| kSyscallError); |
| ss->SetShutdownFlags(); |
| |
| return scope.Close(Integer::New(bytes_read)); |
| } |
| |
| |
| Handle<Value> Connection::ClearPending(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| int bytes_pending = BIO_pending(ss->bio_read_); |
| return scope.Close(Integer::New(bytes_pending)); |
| } |
| |
| |
| Handle<Value> Connection::EncPending(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| int bytes_pending = BIO_pending(ss->bio_write_); |
| return scope.Close(Integer::New(bytes_pending)); |
| } |
| |
| |
| Handle<Value> Connection::EncOut(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (args.Length() < 3) { |
| return ThrowException(Exception::TypeError( |
| String::New("Takes 3 parameters"))); |
| } |
| |
| if (!Buffer::HasInstance(args[0])) { |
| return ThrowException(Exception::TypeError( |
| String::New("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 ThrowException(Exception::Error( |
| String::New("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(); |
| |
| return scope.Close(Integer::New(bytes_read)); |
| } |
| |
| |
| Handle<Value> Connection::ClearIn(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (args.Length() < 3) { |
| return ThrowException(Exception::TypeError( |
| String::New("Takes 3 parameters"))); |
| } |
| |
| if (!Buffer::HasInstance(args[0])) { |
| return ThrowException(Exception::TypeError( |
| String::New("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 ThrowException(Exception::Error( |
| String::New("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 scope.Close(Integer::New(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(); |
| |
| return scope.Close(Integer::New(bytes_written)); |
| } |
| |
| |
| Handle<Value> Connection::GetPeerCertificate(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (ss->ssl_ == NULL) return Undefined(); |
| 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, String::New(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, String::New(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, String::New(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, String::New(mem->data, mem->length) ); |
| (void) BIO_reset(bio); |
| |
| BN_print(bio, rsa->e); |
| BIO_get_mem_ptr(bio, &mem); |
| info->Set(exponent_symbol, String::New(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, String::New(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, String::New(mem->data, mem->length)); |
| BIO_free(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, String::New(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(Integer::New(i), String::New(buf)); |
| } |
| |
| sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free); |
| info->Set(ext_key_usage_symbol, ext_key_usage); |
| } |
| |
| X509_free(peer_cert); |
| } |
| return scope.Close(info); |
| } |
| |
| Handle<Value> Connection::GetSession(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (ss->ssl_ == NULL) return Undefined(); |
| |
| SSL_SESSION* sess = SSL_get_session(ss->ssl_); |
| if (!sess) return Undefined(); |
| |
| int slen = i2d_SSL_SESSION(sess, NULL); |
| assert(slen > 0); |
| |
| if (slen > 0) { |
| unsigned char* sbuf = new unsigned char[slen]; |
| unsigned char* p = sbuf; |
| i2d_SSL_SESSION(sess, &p); |
| Local<Value> s = Encode(sbuf, slen, BINARY); |
| delete[] sbuf; |
| return scope.Close(s); |
| } |
| |
| return Null(); |
| } |
| |
| Handle<Value> Connection::SetSession(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (args.Length() < 1 || |
| (!args[0]->IsString() && !Buffer::HasInstance(args[0]))) { |
| Local<Value> exception = Exception::TypeError(String::New("Bad argument")); |
| return ThrowException(exception); |
| } |
| |
| ASSERT_IS_BUFFER(args[0]); |
| ssize_t slen = Buffer::Length(args[0]); |
| |
| if (slen < 0) { |
| Local<Value> exception = Exception::TypeError(String::New("Bad argument")); |
| return ThrowException(exception); |
| } |
| |
| 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 Undefined(); |
| |
| int r = SSL_set_session(ss->ssl_, sess); |
| SSL_SESSION_free(sess); |
| |
| if (!r) { |
| Local<String> eStr = String::New("SSL_set_session error"); |
| return ThrowException(Exception::Error(eStr)); |
| } |
| |
| return True(); |
| } |
| |
| Handle<Value> Connection::LoadSession(const Arguments& args) { |
| HandleScope scope; |
| |
| 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_.Finish(); |
| |
| return True(); |
| } |
| |
| Handle<Value> Connection::IsSessionReused(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (ss->ssl_ == NULL || SSL_session_reused(ss->ssl_) == false) { |
| return False(); |
| } |
| |
| return True(); |
| } |
| |
| |
| Handle<Value> Connection::Start(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (!SSL_is_init_finished(ss->ssl_)) { |
| int rv; |
| 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); |
| } |
| |
| return scope.Close(Integer::New(rv)); |
| } |
| |
| return scope.Close(Integer::New(0)); |
| } |
| |
| |
| Handle<Value> Connection::Shutdown(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (ss->ssl_ == NULL) return False(); |
| int rv = SSL_shutdown(ss->ssl_); |
| ss->HandleSSLError("SSL_shutdown", rv, kZeroIsNotAnError, kIgnoreSyscall); |
| ss->SetShutdownFlags(); |
| |
| return scope.Close(Integer::New(rv)); |
| } |
| |
| |
| Handle<Value> Connection::IsInitFinished(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (ss->ssl_ == NULL || SSL_is_init_finished(ss->ssl_) == false) { |
| return False(); |
| } |
| |
| return True(); |
| } |
| |
| |
| Handle<Value> Connection::VerifyError(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (ss->ssl_ == NULL) return Null(); |
| |
| |
| // 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? |
| return scope.Close(Exception::Error( |
| String::New("UNABLE_TO_GET_ISSUER_CERT"))); |
| } |
| 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: |
| return Null(); |
| |
| case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: |
| s = String::New("UNABLE_TO_GET_ISSUER_CERT"); |
| break; |
| |
| case X509_V_ERR_UNABLE_TO_GET_CRL: |
| s = String::New("UNABLE_TO_GET_CRL"); |
| break; |
| |
| case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: |
| s = String::New("UNABLE_TO_DECRYPT_CERT_SIGNATURE"); |
| break; |
| |
| case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: |
| s = String::New("UNABLE_TO_DECRYPT_CRL_SIGNATURE"); |
| break; |
| |
| case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: |
| s = String::New("UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY"); |
| break; |
| |
| case X509_V_ERR_CERT_SIGNATURE_FAILURE: |
| s = String::New("CERT_SIGNATURE_FAILURE"); |
| break; |
| |
| case X509_V_ERR_CRL_SIGNATURE_FAILURE: |
| s = String::New("CRL_SIGNATURE_FAILURE"); |
| break; |
| |
| case X509_V_ERR_CERT_NOT_YET_VALID: |
| s = String::New("CERT_NOT_YET_VALID"); |
| break; |
| |
| case X509_V_ERR_CERT_HAS_EXPIRED: |
| s = String::New("CERT_HAS_EXPIRED"); |
| break; |
| |
| case X509_V_ERR_CRL_NOT_YET_VALID: |
| s = String::New("CRL_NOT_YET_VALID"); |
| break; |
| |
| case X509_V_ERR_CRL_HAS_EXPIRED: |
| s = String::New("CRL_HAS_EXPIRED"); |
| break; |
| |
| case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: |
| s = String::New("ERROR_IN_CERT_NOT_BEFORE_FIELD"); |
| break; |
| |
| case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: |
| s = String::New("ERROR_IN_CERT_NOT_AFTER_FIELD"); |
| break; |
| |
| case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: |
| s = String::New("ERROR_IN_CRL_LAST_UPDATE_FIELD"); |
| break; |
| |
| case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: |
| s = String::New("ERROR_IN_CRL_NEXT_UPDATE_FIELD"); |
| break; |
| |
| case X509_V_ERR_OUT_OF_MEM: |
| s = String::New("OUT_OF_MEM"); |
| break; |
| |
| case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: |
| s = String::New("DEPTH_ZERO_SELF_SIGNED_CERT"); |
| break; |
| |
| case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: |
| s = String::New("SELF_SIGNED_CERT_IN_CHAIN"); |
| break; |
| |
| case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: |
| s = String::New("UNABLE_TO_GET_ISSUER_CERT_LOCALLY"); |
| break; |
| |
| case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: |
| s = String::New("UNABLE_TO_VERIFY_LEAF_SIGNATURE"); |
| break; |
| |
| case X509_V_ERR_CERT_CHAIN_TOO_LONG: |
| s = String::New("CERT_CHAIN_TOO_LONG"); |
| break; |
| |
| case X509_V_ERR_CERT_REVOKED: |
| s = String::New("CERT_REVOKED"); |
| break; |
| |
| case X509_V_ERR_INVALID_CA: |
| s = String::New("INVALID_CA"); |
| break; |
| |
| case X509_V_ERR_PATH_LENGTH_EXCEEDED: |
| s = String::New("PATH_LENGTH_EXCEEDED"); |
| break; |
| |
| case X509_V_ERR_INVALID_PURPOSE: |
| s = String::New("INVALID_PURPOSE"); |
| break; |
| |
| case X509_V_ERR_CERT_UNTRUSTED: |
| s = String::New("CERT_UNTRUSTED"); |
| break; |
| |
| case X509_V_ERR_CERT_REJECTED: |
| s = String::New("CERT_REJECTED"); |
| break; |
| |
| default: |
| s = String::New(X509_verify_cert_error_string(x509_verify_error)); |
| break; |
| } |
| |
| return scope.Close(Exception::Error(s)); |
| } |
| |
| |
| Handle<Value> Connection::GetCurrentCipher(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| OPENSSL_CONST SSL_CIPHER *c; |
| |
| if ( ss->ssl_ == NULL ) return Undefined(); |
| c = SSL_get_current_cipher(ss->ssl_); |
| if ( c == NULL ) return Undefined(); |
| Local<Object> info = Object::New(); |
| const char* cipher_name = SSL_CIPHER_get_name(c); |
| info->Set(name_symbol, String::New(cipher_name)); |
| const char* cipher_version = SSL_CIPHER_get_version(c); |
| info->Set(version_symbol, String::New(cipher_version)); |
| return scope.Close(info); |
| } |
| |
| Handle<Value> Connection::Close(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (ss->ssl_ != NULL) { |
| SSL_free(ss->ssl_); |
| ss->ssl_ = NULL; |
| } |
| return True(); |
| } |
| |
| |
| void Connection::InitNPN(SecureContext* sc, bool is_server) { |
| #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 OPENSSL_NPN_NEGOTIATED |
| Handle<Value> Connection::GetNegotiatedProto(const Arguments& args) { |
| HandleScope scope; |
| |
| 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 False(); |
| } |
| |
| return scope.Close(String::New(reinterpret_cast<const char*>(npn_proto), |
| npn_proto_len)); |
| } else { |
| return ss->selectedNPNProto_; |
| } |
| } |
| |
| Handle<Value> Connection::SetNPNProtocols(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (args.Length() < 1 || !Buffer::HasInstance(args[0])) { |
| return ThrowException(Exception::Error(String::New( |
| "Must give a Buffer as first argument"))); |
| } |
| |
| // Release old handle |
| if (!ss->npnProtos_.IsEmpty()) { |
| ss->npnProtos_.Dispose(); |
| } |
| ss->npnProtos_ = Persistent<Object>::New(args[0]->ToObject()); |
| |
| return True(); |
| }; |
| #endif |
| |
| #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB |
| Handle<Value> Connection::GetServername(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (ss->is_server_ && !ss->servername_.IsEmpty()) { |
| return ss->servername_; |
| } else { |
| return False(); |
| } |
| } |
| |
| Handle<Value> Connection::SetSNICallback(const Arguments& args) { |
| HandleScope scope; |
| |
| Connection *ss = Connection::Unwrap(args); |
| |
| if (args.Length() < 1 || !args[0]->IsFunction()) { |
| return ThrowException(Exception::Error(String::New( |
| "Must give a Function as first argument"))); |
| } |
| |
| // Release old handle |
| if (!ss->sniObject_.IsEmpty()) { |
| ss->sniObject_.Dispose(); |
| } |
| ss->sniObject_ = Persistent<Object>::New(Object::New()); |
| ss->sniObject_->Set(String::New("onselect"), args[0]); |
| |
| return True(); |
| } |
| #endif |
| |
| |
| class Cipher : public ObjectWrap { |
| public: |
| static void Initialize (v8::Handle<v8::Object> target) { |
| HandleScope scope; |
| |
| Local<FunctionTemplate> t = FunctionTemplate::New(New); |
| |
| t->InstanceTemplate()->SetInternalFieldCount(1); |
| |
| NODE_SET_PROTOTYPE_METHOD(t, "init", CipherInit); |
| NODE_SET_PROTOTYPE_METHOD(t, "initiv", CipherInitIv); |
| NODE_SET_PROTOTYPE_METHOD(t, "update", CipherUpdate); |
| NODE_SET_PROTOTYPE_METHOD(t, "setAutoPadding", SetAutoPadding); |
| NODE_SET_PROTOTYPE_METHOD(t, "final", CipherFinal); |
| |
| target->Set(String::NewSymbol("Cipher"), t->GetFunction()); |
| } |
| |
| |
| bool CipherInit(char* cipherType, char* key_buf, int key_buf_len) { |
| cipher = EVP_get_cipherbyname(cipherType); |
| if(!cipher) { |
| fprintf(stderr, "node-crypto : Unknown cipher %s\n", cipherType); |
| return false; |
| } |
| |
| unsigned char key[EVP_MAX_KEY_LENGTH],iv[EVP_MAX_IV_LENGTH]; |
| int key_len = EVP_BytesToKey(cipher, EVP_md5(), NULL, |
| (unsigned char*) key_buf, key_buf_len, 1, key, iv); |
| |
| EVP_CIPHER_CTX_init(&ctx); |
| EVP_CipherInit_ex(&ctx, cipher, NULL, NULL, NULL, true); |
| if (!EVP_CIPHER_CTX_set_key_length(&ctx, key_len)) { |
| fprintf(stderr, "node-crypto : Invalid key length %d\n", key_len); |
| EVP_CIPHER_CTX_cleanup(&ctx); |
| return false; |
| } |
| EVP_CipherInit_ex(&ctx, NULL, NULL, |
| (unsigned char*)key, |
| (unsigned char*)iv, true); |
| initialised_ = true; |
| return true; |
| } |
| |
| |
| bool CipherInitIv(char* cipherType, |
| char* key, |
| int key_len, |
| char* iv, |
| int iv_len) { |
| cipher = EVP_get_cipherbyname(cipherType); |
| if(!cipher) { |
| fprintf(stderr, "node-crypto : Unknown cipher %s\n", cipherType); |
| return false; |
| } |
| /* 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)) { |
| fprintf(stderr, "node-crypto : Invalid IV length %d\n", iv_len); |
| return false; |
| } |
| EVP_CIPHER_CTX_init(&ctx); |
| EVP_CipherInit_ex(&ctx, cipher, NULL, NULL, NULL, true); |
| if (!EVP_CIPHER_CTX_set_key_length(&ctx, key_len)) { |
| fprintf(stderr, "node-crypto : Invalid key length %d\n", key_len); |
| EVP_CIPHER_CTX_cleanup(&ctx); |
| return false; |
| } |
| EVP_CipherInit_ex(&ctx, NULL, NULL, |
| (unsigned char*)key, |
| (unsigned char*)iv, true); |
| initialised_ = true; |
| return true; |
| } |
| |
| int CipherUpdate(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, (unsigned char*)data, len); |
| } |
| |
| int SetAutoPadding(bool auto_padding) { |
| if (!initialised_) return 0; |
| return EVP_CIPHER_CTX_set_padding(&ctx, auto_padding ? 1 : 0); |
| } |
| |
| int CipherFinal(unsigned char** out, int *out_len) { |
| if (!initialised_) return 0; |
| *out = new unsigned char[EVP_CIPHER_CTX_block_size(&ctx)]; |
| int r = EVP_CipherFinal_ex(&ctx,*out, out_len); |
| EVP_CIPHER_CTX_cleanup(&ctx); |
| initialised_ = false; |
| return r; |
| } |
| |
| |
| protected: |
| |
| static Handle<Value> New(const Arguments& args) { |
| HandleScope scope; |
| |
| Cipher *cipher = new Cipher(); |
| cipher->Wrap(args.This()); |
| return args.This(); |
| } |
| |
| static Handle<Value> CipherInit(const Arguments& args) { |
| HandleScope scope; |
| |
| Cipher *cipher = ObjectWrap::Unwrap<Cipher>(args.This()); |
| |
| if (args.Length() <= 1 |
| || !args[0]->IsString() |
| || !(args[1]->IsString() || Buffer::HasInstance(args[1]))) |
| { |
| return ThrowException(Exception::Error(String::New( |
| "Must give cipher-type, key"))); |
| } |
| |
| ASSERT_IS_BUFFER(args[1]); |
| ssize_t key_buf_len = Buffer::Length(args[1]); |
| |
| if (key_buf_len < 0) { |
| Local<Value> exception = Exception::TypeError(String::New("Bad argument")); |
| return ThrowException(exception); |
| } |
| |
| char* key_buf = new char[key_buf_len]; |
| ssize_t key_written = DecodeWrite(key_buf, key_buf_len, args[1], BINARY); |
| assert(key_written == key_buf_len); |
| |
| String::Utf8Value cipherType(args[0]); |
| |
| bool r = cipher->CipherInit(*cipherType, key_buf, key_buf_len); |
| |
| delete [] key_buf; |
| |
| if (!r) return ThrowCryptoError(ERR_get_error()); |
| |
| return args.This(); |
| } |
| |
| |
| static Handle<Value> CipherInitIv(const Arguments& args) { |
| Cipher *cipher = ObjectWrap::Unwrap<Cipher>(args.This()); |
| |
| HandleScope scope; |
| |
| |
| if (args.Length() <= 2 |
| || !args[0]->IsString() |
| || !(args[1]->IsString() || Buffer::HasInstance(args[1])) |
| || !(args[2]->IsString() || Buffer::HasInstance(args[2]))) |
| { |
| return ThrowException(Exception::Error(String::New( |
| "Must give cipher-type, key, and iv as argument"))); |
| } |
| |
| ASSERT_IS_BUFFER(args[1]); |
| ssize_t key_len = Buffer::Length(args[1]); |
| |
| if (key_len < 0) { |
| Local<Value> exception = Exception::TypeError(String::New("Bad argument")); |
| return ThrowException(exception); |
| } |
| |
| ASSERT_IS_BUFFER(args[2]); |
| ssize_t iv_len = Buffer::Length(args[2]); |
| |
| if (iv_len < 0) { |
| Local<Value> exception = Exception::TypeError(String::New("Bad argument")); |
| return ThrowException(exception); |
| } |
| |
| char* key_buf = new char[key_len]; |
| ssize_t key_written = DecodeWrite(key_buf, key_len, args[1], BINARY); |
| assert(key_written == key_len); |
| |
| char* iv_buf = new char[iv_len]; |
| ssize_t iv_written = DecodeWrite(iv_buf, iv_len, args[2], BINARY); |
| assert(iv_written == iv_len); |
| |
| String::Utf8Value cipherType(args[0]); |
| |
| bool r = cipher->CipherInitIv(*cipherType, key_buf,key_len,iv_buf,iv_len); |
| |
| delete [] key_buf; |
| delete [] iv_buf; |
| |
| if (!r) return ThrowCryptoError(ERR_get_error()); |
| |
| return args.This(); |
| } |
| |
| static Handle<Value> CipherUpdate(const Arguments& args) { |
| Cipher *cipher = ObjectWrap::Unwrap<Cipher>(args.This()); |
| |
| HandleScope scope; |
| |
| ASSERT_IS_STRING_OR_BUFFER(args[0]); |
| |
| // Only copy the data if we have to, because it's a string |
| unsigned char* out = 0; |
| int out_len = 0, 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 = cipher->CipherUpdate(buf, written, &out, &out_len); |
| delete[] buf; |
| } else { |
| char* buf = Buffer::Data(args[0]); |
| size_t buflen = Buffer::Length(args[0]); |
| r = cipher->CipherUpdate(buf, buflen, &out, &out_len); |
| } |
| |
| if (r == 0) { |
| delete[] out; |
| return ThrowCryptoTypeError(ERR_get_error()); |
| } |
| |
| Local<Value> outString; |
| outString = Encode(out, out_len, BUFFER); |
| |
| if (out) delete[] out; |
| |
| return scope.Close(outString); |
| } |
| |
| static Handle<Value> SetAutoPadding(const Arguments& args) { |
| HandleScope scope; |
| Cipher *cipher = ObjectWrap::Unwrap<Cipher>(args.This()); |
| |
| cipher->SetAutoPadding(args.Length() < 1 || args[0]->BooleanValue()); |
| |
| return Undefined(); |
| } |
| |
| static Handle<Value> CipherFinal(const Arguments& args) { |
| Cipher *cipher = ObjectWrap::Unwrap<Cipher>(args.This()); |
| |
| HandleScope scope; |
| |
| unsigned char* out_value = NULL; |
| int out_len = -1; |
| Local<Value> outString ; |
| |
| int r = cipher->CipherFinal(&out_value, &out_len); |
| |
| if (out_len <= 0 || r == 0) { |
| delete[] out_value; |
| out_value = NULL; |
| if (r == 0) return ThrowCryptoTypeError(ERR_get_error()); |
| } |
| |
| outString = Encode(out_value, out_len, BUFFER); |
| |
| delete [] out_value; |
| return scope.Close(outString); |
| } |
| |
| Cipher () : ObjectWrap () |
| { |
| initialised_ = false; |
| } |
| |
| ~Cipher () { |
| if (initialised_) { |
| EVP_CIPHER_CTX_cleanup(&ctx); |
| } |
| } |
| |
| private: |
| |
| EVP_CIPHER_CTX ctx; /* coverity[member_decl] */ |
| const EVP_CIPHER *cipher; /* coverity[member_decl] */ |
| bool initialised_; |
| }; |
| |
| |
| |
| class Decipher : public ObjectWrap { |
| public: |
| static void |
| Initialize (v8::Handle<v8::Object> target) |
| { |
| HandleScope scope; |
| |
| Local<FunctionTemplate> t = FunctionTemplate::New(New); |
| |
| t->InstanceTemplate()->SetInternalFieldCount(1); |
| |
| NODE_SET_PROTOTYPE_METHOD(t, "init", DecipherInit); |
| NODE_SET_PROTOTYPE_METHOD(t, "initiv", DecipherInitIv); |
| NODE_SET_PROTOTYPE_METHOD(t, "update", DecipherUpdate); |
| NODE_SET_PROTOTYPE_METHOD(t, "final", DecipherFinal); |
| NODE_SET_PROTOTYPE_METHOD(t, "finaltol", DecipherFinal); // remove someday |
| NODE_SET_PROTOTYPE_METHOD(t, "setAutoPadding", SetAutoPadding); |
| |
| target->Set(String::NewSymbol("Decipher"), t->GetFunction()); |
| } |
| |
| bool DecipherInit(char* cipherType, char* key_buf, int key_buf_len) { |
| cipher_ = EVP_get_cipherbyname(cipherType); |
| |
| if(!cipher_) { |
| fprintf(stderr, "node-crypto : Unknown cipher %s\n", cipherType); |
| return false; |
| } |
| |
| unsigned char key[EVP_MAX_KEY_LENGTH],iv[EVP_MAX_IV_LENGTH]; |
| int key_len = EVP_BytesToKey(cipher_, |
| EVP_md5(), |
| NULL, |
| (unsigned char*)(key_buf), |
| key_buf_len, |
| 1, |
| key, |
| iv); |
| |
| EVP_CIPHER_CTX_init(&ctx); |
| EVP_CipherInit_ex(&ctx, cipher_, NULL, NULL, NULL, false); |
| if (!EVP_CIPHER_CTX_set_key_length(&ctx, key_len)) { |
| fprintf(stderr, "node-crypto : Invalid key length %d\n", key_len); |
| EVP_CIPHER_CTX_cleanup(&ctx); |
| return false; |
| } |
| EVP_CipherInit_ex(&ctx, NULL, NULL, |
| (unsigned char*)key, |
| (unsigned char*)iv, false); |
| initialised_ = true; |
| return true; |
| } |
| |
| |
| bool DecipherInitIv(char* cipherType, |
| char* key, |
| int key_len, |
| char* iv, |
| int iv_len) { |
| cipher_ = EVP_get_cipherbyname(cipherType); |
| if(!cipher_) { |
| fprintf(stderr, "node-crypto : Unknown cipher %s\n", cipherType); |
| return false; |
| } |
| /* 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)) { |
| fprintf(stderr, "node-crypto : Invalid IV length %d\n", iv_len); |
| return false; |
| } |
| EVP_CIPHER_CTX_init(&ctx); |
| EVP_CipherInit_ex(&ctx, cipher_, NULL, NULL, NULL, false); |
| if (!EVP_CIPHER_CTX_set_key_length(&ctx, key_len)) { |
| fprintf(stderr, "node-crypto : Invalid key length %d\n", key_len); |
| EVP_CIPHER_CTX_cleanup(&ctx); |
| return false; |
| } |
| EVP_CipherInit_ex(&ctx, NULL, NULL, |
| (unsigned char*)key, |
| (unsigned char*)iv, false); |
| initialised_ = true; |
| return true; |
| } |
| |
| int DecipherUpdate(char* data, int len, unsigned char** out, int* out_len) { |
| if (!initialised_) { |
| *out_len = 0; |
| *out = NULL; |
| return 0; |
| } |
| |
| *out_len = len+EVP_CIPHER_CTX_block_size(&ctx); |
| *out = new unsigned char[*out_len]; |
| |
| return EVP_CipherUpdate(&ctx, *out, out_len, (unsigned char*)data, len); |
| } |
| |
| int SetAutoPadding(bool auto_padding) { |
| if (!initialised_) return 0; |
| return EVP_CIPHER_CTX_set_padding(&ctx, auto_padding ? 1 : 0); |
| } |
| |
| // coverity[alloc_arg] |
| int DecipherFinal(unsigned char** out, int *out_len) { |
| int r; |
| |
| if (!initialised_) { |
| *out_len = 0; |
| *out = NULL; |
| return 0; |
| } |
| |
| *out = new unsigned char[EVP_CIPHER_CTX_block_size(&ctx)]; |
| r = EVP_CipherFinal_ex(&ctx,*out,out_len); |
| EVP_CIPHER_CTX_cleanup(&ctx); |
| initialised_ = false; |
| return r; |
| } |
| |
| |
| protected: |
| |
| static Handle<Value> New (const Arguments& args) { |
| HandleScope scope; |
| |
| Decipher *cipher = new Decipher(); |
| cipher->Wrap(args.This()); |
| return args.This(); |
| } |
| |
| static Handle<Value> DecipherInit(const Arguments& args) { |
| Decipher *cipher = ObjectWrap::Unwrap<Decipher>(args.This()); |
| |
| HandleScope scope; |
| |
| if (args.Length() <= 1 |
| || !args[0]->IsString() |
| || !(args[1]->IsString() || Buffer::HasInstance(args[1]))) |
| { |
| return ThrowException(Exception::Error(String::New( |
| "Must give cipher-type, key as argument"))); |
| } |
| |
| ASSERT_IS_BUFFER(args[1]); |
| ssize_t key_len = Buffer::Length(args[1]); |
| |
| if (key_len < 0) { |
| Local<Value> exception = Exception::TypeError(String::New("Bad argument")); |
| return ThrowException(exception); |
| } |
| |
| char* key_buf = new char[key_len]; |
| ssize_t key_written = DecodeWrite(key_buf, key_len, args[1], BINARY); |
| assert(key_written == key_len); |
| |
| String::Utf8Value cipherType(args[0]); |
| |
| bool r = cipher->DecipherInit(*cipherType, key_buf,key_len); |
| |
| delete [] key_buf; |
| |
| if (!r) { |
| return ThrowException(Exception::Error(String::New("DecipherInit error"))); |
| } |
| |
| return args.This(); |
| } |
| |
| static Handle<Value> DecipherInitIv(const Arguments& args) { |
| Decipher *cipher = ObjectWrap::Unwrap<Decipher>(args.This()); |
| |
| HandleScope scope; |
| |
| if (args.Length() <= 2 |
| || !args[0]->IsString() |
| || !(args[1]->IsString() || Buffer::HasInstance(args[1])) |
| || !(args[2]->IsString() || Buffer::HasInstance(args[2]))) |
| { |
| return ThrowException(Exception::Error(String::New( |
| "Must give cipher-type, key, and iv as argument"))); |
| } |
| |
| ASSERT_IS_BUFFER(args[1]); |
| ssize_t key_len = Buffer::Length(args[1]); |
| |
| if (key_len < 0) { |
| Local<Value> exception = Exception::TypeError(String::New("Bad argument")); |
| return ThrowException(exception); |
| } |
| |
| ASSERT_IS_BUFFER(args[2]); |
| ssize_t iv_len = Buffer::Length(args[2]); |
| |
| if (iv_len < 0) { |
| Local<Value> exception = Exception::TypeError(String::New("Bad argument")); |
| return ThrowException(exception); |
| } |
| |
| char* key_buf = new char[key_len]; |
| ssize_t key_written = DecodeWrite(key_buf, key_len, args[1], BINARY); |
| assert(key_written == key_len); |
| |
| char* iv_buf = new char[iv_len]; |
| ssize_t iv_written = DecodeWrite(iv_buf, iv_len, args[2], BINARY); |
| assert(iv_written == iv_len); |
| |
| String::Utf8Value cipherType(args[0]); |
| |
| bool r = cipher->DecipherInitIv(*cipherType, key_buf,key_len,iv_buf,iv_len); |
| |
| delete [] key_buf; |
| delete [] iv_buf; |
| |
| if (!r) { |
| return ThrowException(Exception::Error(String::New("DecipherInitIv error"))); |
| } |
| |
| return args.This(); |
| } |
| |
| static Handle<Value> DecipherUpdate(const Arguments& args) { |
| HandleScope scope; |
| |
| Decipher *cipher = ObjectWrap::Unwrap<Decipher>(args.This()); |
| |
| ASSERT_IS_STRING_OR_BUFFER(args[0]); |
| |
| // Only copy the data if we have to, because it's a string |
| unsigned char* out = 0; |
| int out_len = 0, 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 = cipher->DecipherUpdate(buf, written, &out, &out_len); |
| delete[] buf; |
| } else { |
| char* buf = Buffer::Data(args[0]); |
| size_t buflen = Buffer::Length(args[0]); |
| r = cipher->DecipherUpdate(buf, buflen, &out, &out_len); |
| } |
| |
| if (r == 0) { |
| delete[] out; |
| return ThrowCryptoTypeError(ERR_get_error()); |
| } |
| |
| Local<Value> outString; |
| outString = Encode(out, out_len, BUFFER); |
| |
| if (out) delete [] out; |
| |
| return scope.Close(outString); |
| } |
| |
| static Handle<Value> SetAutoPadding(const Arguments& args) { |
| HandleScope scope; |
| Decipher *cipher = ObjectWrap::Unwrap<Decipher>(args.This()); |
| |
| cipher->SetAutoPadding(args.Length() < 1 || args[0]->BooleanValue()); |
| |
| return Undefined(); |
| } |
| |
| static Handle<Value> DecipherFinal(const Arguments& args) { |
| HandleScope scope; |
| |
| Decipher *cipher = ObjectWrap::Unwrap<Decipher>(args.This()); |
| |
| unsigned char* out_value = NULL; |
| int out_len = -1; |
| Local<Value> outString; |
| |
| int r = cipher->DecipherFinal(&out_value, &out_len); |
| |
| if (out_len <= 0 || r == 0) { |
| delete [] out_value; // allocated even if out_len == 0 |
| out_value = NULL; |
| if (r == 0) return ThrowCryptoTypeError(ERR_get_error()); |
| } |
| |
| outString = Encode(out_value, out_len, BUFFER); |
| delete [] out_value; |
| return scope.Close(outString); |
| } |
| |
| Decipher () : ObjectWrap () { |
| initialised_ = false; |
| } |
| |
| ~Decipher () { |
| if (initialised_) { |
| EVP_CIPHER_CTX_cleanup(&ctx); |
| } |
| } |
| |
| private: |
| |
| EVP_CIPHER_CTX ctx; |
| const EVP_CIPHER *cipher_; |
| bool initialised_; |
| }; |
| |
| |
| |
| |
| class Hmac : public ObjectWrap { |
| public: |
| static void Initialize (v8::Handle<v8::Object> target) { |
| HandleScope scope; |
| |
| 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(String::NewSymbol("Hmac"), t->GetFunction()); |
| } |
| |
| bool HmacInit(char* hashType, char* key, int key_len) { |
| md = EVP_get_digestbyname(hashType); |
| if(!md) { |
| fprintf(stderr, "node-crypto : Unknown message digest %s\n", hashType); |
| return false; |
| } |
| HMAC_CTX_init(&ctx); |
| if (key_len == 0) { |
| HMAC_Init(&ctx, "", 0, md); |
| } else { |
| HMAC_Init(&ctx, key, key_len, md); |
| } |
| initialised_ = true; |
| return true; |
| |
| } |
| |
| int HmacUpdate(char* data, int len) { |
| if (!initialised_) return 0; |
| HMAC_Update(&ctx, (unsigned char*)data, len); |
| return 1; |
| } |
| |
| int HmacDigest(unsigned char** md_value, unsigned int *md_len) { |
| if (!initialised_) return 0; |
| *md_value = new unsigned char[EVP_MAX_MD_SIZE]; |
| HMAC_Final(&ctx, *md_value, md_len); |
| HMAC_CTX_cleanup(&ctx); |
| initialised_ = false; |
| return 1; |
| } |
| |
| |
| protected: |
| |
| static Handle<Value> New (const Arguments& args) { |
| HandleScope scope; |
| |
| Hmac *hmac = new Hmac(); |
| hmac->Wrap(args.This()); |
| return args.This(); |
| } |
| |
| static Handle<Value> HmacInit(const Arguments& args) { |
| Hmac *hmac = ObjectWrap::Unwrap<Hmac>(args.This()); |
| |
| HandleScope scope; |
| |
| if (args.Length() == 0 || !args[0]->IsString()) { |
| return ThrowException(Exception::Error(String::New( |
| "Must give hashtype string as argument"))); |
| } |
| |
| ASSERT_IS_BUFFER(args[1]); |
| ssize_t len = Buffer::Length(args[1]); |
| |
| if (len < 0) { |
| Local<Value> exception = Exception::TypeError(String::New("Bad argument")); |
| return ThrowException(exception); |
| } |
| |
| String::Utf8Value hashType(args[0]); |
| |
| bool r; |
| |
| if( Buffer::HasInstance(args[1])) { |
| char* buffer_data = Buffer::Data(args[1]); |
| size_t buffer_length = Buffer::Length(args[1]); |
| |
| r = hmac->HmacInit(*hashType, buffer_data, buffer_length); |
| } else { |
| char* buf = new char[len]; |
| ssize_t written = DecodeWrite(buf, len, args[1], BINARY); |
| assert(written == len); |
| |
| r = hmac->HmacInit(*hashType, buf, len); |
| |
| delete [] buf; |
| } |
| |
| if (!r) { |
| return ThrowException(Exception::Error(String::New("hmac error"))); |
| } |
| |
| return args.This(); |
| } |
| |
| static Handle<Value> HmacUpdate(const Arguments& args) { |
| Hmac *hmac = ObjectWrap::Unwrap<Hmac>(args.This()); |
| |
| HandleScope scope; |
| |
| 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 = 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) { |
| Local<Value> exception = Exception::TypeError(String::New("HmacUpdate fail")); |
| return ThrowException(exception); |
| } |
| |
| return args.This(); |
| } |
| |
| static Handle<Value> HmacDigest(const Arguments& args) { |
| Hmac *hmac = ObjectWrap::Unwrap<Hmac>(args.This()); |
| |
| HandleScope scope; |
| |
| enum encoding encoding = BUFFER; |
| if (args.Length() >= 1) { |
| encoding = ParseEncoding(args[0]->ToString(), BUFFER); |
| } |
| |
| unsigned char* md_value = NULL; |
| unsigned int md_len = 0; |
| Local<Value> outString; |
| |
| int r = hmac->HmacDigest(&md_value, &md_len); |
| if (r == 0) { |
| md_value = NULL; |
| md_len = 0; |
| } |
| |
| outString = StringBytes::Encode( |
| reinterpret_cast<const char*>(md_value), md_len, encoding); |
| |
| delete[] md_value; |
| return scope.Close(outString); |
| } |
| |
| Hmac () : ObjectWrap () { |
| initialised_ = false; |
| } |
| |
| ~Hmac () { |
| if (initialised_) { |
| HMAC_CTX_cleanup(&ctx); |
| } |
| } |
| |
| private: |
| |
| HMAC_CTX ctx; /* coverity[member_decl] */ |
| const EVP_MD *md; /* coverity[member_decl] */ |
| bool initialised_; |
| }; |
| |
| |
| class Hash : public ObjectWrap { |
| public: |
| static void Initialize (v8::Handle<v8::Object> target) { |
| HandleScope scope; |
| |
| 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(String::NewSymbol("Hash"), t->GetFunction()); |
| } |
| |
| bool HashInit (const char* hashType) { |
| md = EVP_get_digestbyname(hashType); |
| if(!md) return false; |
| EVP_MD_CTX_init(&mdctx); |
| EVP_DigestInit_ex(&mdctx, md, NULL); |
| initialised_ = true; |
| return true; |
| } |
| |
| int HashUpdate(char* data, int len) { |
| if (!initialised_) return 0; |
| EVP_DigestUpdate(&mdctx, data, len); |
| return 1; |
| } |
| |
| |
| protected: |
| |
| static Handle<Value> New (const Arguments& args) { |
| HandleScope scope; |
| |
| if (args.Length() == 0 || !args[0]->IsString()) { |
| return ThrowException(Exception::Error(String::New( |
| "Must give hashtype string as argument"))); |
| } |
| |
| String::Utf8Value hashType(args[0]); |
| |
| Hash *hash = new Hash(); |
| if (!hash->HashInit(*hashType)) { |
| delete hash; |
| return ThrowException(Exception::Error(String::New( |
| "Digest method not supported"))); |
| } |
| |
| hash->Wrap(args.This()); |
| return args.This(); |
| } |
| |
| static Handle<Value> HashUpdate(const Arguments& args) { |
| HandleScope scope; |
| |
| 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 |
| 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 = 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) { |
| Local<Value> exception = Exception::TypeError(String::New("HashUpdate fail")); |
| return ThrowException(exception); |
| } |
| |
| return args.This(); |
| } |
| |
| static Handle<Value> HashDigest(const Arguments& args) { |
| HandleScope scope; |
| |
| Hash *hash = ObjectWrap::Unwrap<Hash>(args.This()); |
| |
| if (!hash->initialised_) { |
| return ThrowException(Exception::Error(String::New("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; |
| |
| return scope.Close(StringBytes::Encode( |
| reinterpret_cast<const char*>(md_value), md_len, encoding)); |
| } |
| |
| Hash () : ObjectWrap () { |
| initialised_ = false; |
| } |
| |
| ~Hash () { |
| if (initialised_) { |
| EVP_MD_CTX_cleanup(&mdctx); |
| } |
| } |
| |
| private: |
| |
| EVP_MD_CTX mdctx; /* coverity[member_decl] */ |
| const EVP_MD *md; /* coverity[member_decl] */ |
| bool initialised_; |
| }; |
| |
| class Sign : public ObjectWrap { |
| public: |
| static void |
| Initialize (v8::Handle<v8::Object> target) { |
| HandleScope scope; |
| |
| 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(String::NewSymbol("Sign"), t->GetFunction()); |
| } |
| |
| bool SignInit (const char* signType) { |
| md = EVP_get_digestbyname(signType); |
| if(!md) { |
| printf("Unknown message digest %s\n", signType); |
| return false; |
| } |
| EVP_MD_CTX_init(&mdctx); |
| EVP_SignInit_ex(&mdctx, md, NULL); |
| initialised_ = true; |
| return true; |
| |
| } |
| |
| int SignUpdate(char* data, int len) { |
| if (!initialised_) return 0; |
| EVP_SignUpdate(&mdctx, data, len); |
| return 1; |
| } |
| |
| int SignFinal(unsigned char** md_value, |
| unsigned int *md_len, |
| char* key_pem, |
| int key_pemLen) { |
| if (!initialised_) return 0; |
| |
| BIO *bp = NULL; |
| EVP_PKEY* pkey; |
| bp = BIO_new(BIO_s_mem()); |
| if(!BIO_write(bp, key_pem, key_pemLen)) return 0; |
| |
| 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(bp); |
| return 1; |
| } |
| |
| |
| protected: |
| |
| static Handle<Value> New (const Arguments& args) { |
| HandleScope scope; |
| |
| Sign *sign = new Sign(); |
| sign->Wrap(args.This()); |
| |
| return args.This(); |
| } |
| |
| static Handle<Value> SignInit(const Arguments& args) { |
| HandleScope scope; |
| |
| Sign *sign = ObjectWrap::Unwrap<Sign>(args.This()); |
| |
| if (args.Length() == 0 || !args[0]->IsString()) { |
| return ThrowException(Exception::Error(String::New( |
| "Must give signtype string as argument"))); |
| } |
| |
| String::Utf8Value signType(args[0]); |
| |
| bool r = sign->SignInit(*signType); |
| |
| if (!r) { |
| return ThrowException(Exception::Error(String::New("SignInit error"))); |
| } |
| |
| return args.This(); |
| } |
| |
| static Handle<Value> SignUpdate(const Arguments& args) { |
| Sign *sign = ObjectWrap::Unwrap<Sign>(args.This()); |
| |
| HandleScope scope; |
| |
| 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) { |
| Local<Value> exception = Exception::TypeError(String::New("SignUpdate fail")); |
| return ThrowException(exception); |
| } |
| |
| return args.This(); |
| } |
| |
| static Handle<Value> SignFinal(const Arguments& args) { |
| Sign *sign = ObjectWrap::Unwrap<Sign>(args.This()); |
| |
| HandleScope scope; |
| |
| unsigned char* md_value; |
| unsigned int md_len; |
| Local<Value> outString; |
| |
| ASSERT_IS_BUFFER(args[0]); |
| ssize_t len = Buffer::Length(args[0]); |
| |
| enum encoding encoding = BUFFER; |
| if (args.Length() >= 2) { |
| encoding = ParseEncoding(args[1]->ToString(), BUFFER); |
| } |
| |
| char* buf = new char[len]; |
| ssize_t written = DecodeWrite(buf, len, args[0], BUFFER); |
| assert(written == len); |
| |
| md_len = 8192; // Maximum key size is 8192 bits |
| md_value = new unsigned char[md_len]; |
| |
| int r = sign->SignFinal(&md_value, &md_len, buf, len); |
| if (r == 0) { |
| md_value = NULL; |
| md_len = r; |
| } |
| |
| delete [] buf; |
| |
| outString = StringBytes::Encode( |
| reinterpret_cast<const char*>(md_value), md_len, encoding); |
| |
| delete [] md_value; |
| return scope.Close(outString); |
| } |
| |
| Sign () : ObjectWrap () { |
| initialised_ = false; |
| } |
| |
| ~Sign () { |
| if (initialised_) { |
| EVP_MD_CTX_cleanup(&mdctx); |
| } |
| } |
| |
| private: |
| |
| EVP_MD_CTX mdctx; /* coverity[member_decl] */ |
| const EVP_MD *md; /* coverity[member_decl] */ |
| bool initialised_; |
| }; |
| |
| class Verify : public ObjectWrap { |
| public: |
| static void Initialize (v8::Handle<v8::Object> target) { |
| HandleScope scope; |
| |
| 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(String::NewSymbol("Verify"), t->GetFunction()); |
| } |
| |
| |
| bool VerifyInit (const char* verifyType) { |
| md = EVP_get_digestbyname(verifyType); |
| if(!md) { |
| fprintf(stderr, "node-crypto : Unknown message digest %s\n", verifyType); |
| return false; |
| } |
| EVP_MD_CTX_init(&mdctx); |
| EVP_VerifyInit_ex(&mdctx, md, NULL); |
| initialised_ = true; |
| return true; |
| } |
| |
| |
| int VerifyUpdate(char* data, int len) { |
| if (!initialised_) return 0; |
| EVP_VerifyUpdate(&mdctx, data, len); |
| return 1; |
| } |
| |
| |
| int VerifyFinal(char* key_pem, int key_pemLen, unsigned char* sig, int siglen) { |
| if (!initialised_) return 0; |
| |
| ClearErrorOnReturn clear_error_on_return; |
| (void) &clear_error_on_return; // Silence compiler warning. |
| |
| EVP_PKEY* pkey = NULL; |
| BIO *bp = NULL; |
| X509 *x509 = NULL; |
| int r = 0; |
| |
| bp = BIO_new(BIO_s_mem()); |
| if (bp == NULL) { |
| ERR_print_errors_fp(stderr); |
| return 0; |
| } |
| if(!BIO_write(bp, key_pem, key_pemLen)) { |
| ERR_print_errors_fp(stderr); |
| return 0; |
| } |
| |
| // 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) { |
| ERR_print_errors_fp(stderr); |
| return 0; |
| } |
| } 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) { |
| ERR_print_errors_fp(stderr); |
| return 0; |
| } |
| } else { |
| // X.509 fallback |
| x509 = PEM_read_bio_X509(bp, NULL, NULL, NULL); |
| if (x509 == NULL) { |
| ERR_print_errors_fp(stderr); |
| return 0; |
| } |
| |
| pkey = X509_get_pubkey(x509); |
| if (pkey == NULL) { |
| ERR_print_errors_fp(stderr); |
| return 0; |
| } |
| } |
| |
| r = EVP_VerifyFinal(&mdctx, sig, siglen, pkey); |
| |
| if(pkey != NULL) |
| EVP_PKEY_free (pkey); |
| if (x509 != NULL) |
| X509_free(x509); |
| if (bp != NULL) |
| BIO_free(bp); |
| EVP_MD_CTX_cleanup(&mdctx); |
| initialised_ = false; |
| |
| return r; |
| } |
| |
| |
| protected: |
| |
| static Handle<Value> New (const Arguments& args) { |
| HandleScope scope; |
| |
| Verify *verify = new Verify(); |
| verify->Wrap(args.This()); |
| |
| return args.This(); |
| } |
| |
| |
| static Handle<Value> VerifyInit(const Arguments& args) { |
| Verify *verify = ObjectWrap::Unwrap<Verify>(args.This()); |
| |
| HandleScope scope; |
| |
| if (args.Length() == 0 || !args[0]->IsString()) { |
| return ThrowException(Exception::Error(String::New( |
| "Must give verifytype string as argument"))); |
| } |
| |
| String::Utf8Value verifyType(args[0]); |
| |
| bool r = verify->VerifyInit(*verifyType); |
| |
| if (!r) { |
| return ThrowException(Exception::Error(String::New("VerifyInit error"))); |
| } |
| |
| return args.This(); |
| } |
| |
| |
| static Handle<Value> VerifyUpdate(const Arguments& args) { |
| HandleScope scope; |
| |
| 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 |
| 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 = 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) { |
| Local<Value> exception = Exception::TypeError(String::New("VerifyUpdate fail")); |
| return ThrowException(exception); |
| } |
| |
| return args.This(); |
| } |
| |
| |
| static Handle<Value> VerifyFinal(const Arguments& args) { |
| HandleScope scope; |
| |
| Verify *verify = ObjectWrap::Unwrap<Verify>(args.This()); |
| |
| ASSERT_IS_BUFFER(args[0]); |
| ssize_t klen = Buffer::Length(args[0]); |
| |
| if (klen < 0) { |
| Local<Value> exception = Exception::TypeError(String::New("Bad argument")); |
| return ThrowException(exception); |
| } |
| |
| char* kbuf = new char[klen]; |
| ssize_t kwritten = DecodeWrite(kbuf, klen, args[0], BINARY); |
| assert(kwritten == klen); |
| |
| 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); |
| |
| if (hlen < 0) { |
| delete[] kbuf; |
| Local<Value> exception = Exception::TypeError(String::New("Bad argument")); |
| return ThrowException(exception); |
| } |
| |
| unsigned char* hbuf = new unsigned char[hlen]; |
| ssize_t hwritten = StringBytes::Write( |
| reinterpret_cast<char*>(hbuf), hlen, args[1], encoding); |
| assert(hwritten == hlen); |
| |
| int r; |
| r = verify->VerifyFinal(kbuf, klen, hbuf, hlen); |
| |
| delete[] kbuf; |
| delete[] hbuf; |
| |
| return Boolean::New(r && r != -1); |
| } |
| |
| Verify () : ObjectWrap () { |
| initialised_ = false; |
| } |
| |
| ~Verify () { |
| if (initialised_) { |
| EVP_MD_CTX_cleanup(&mdctx); |
| } |
| } |
| |
| private: |
| |
| EVP_MD_CTX mdctx; /* coverity[member_decl] */ |
| const EVP_MD *md; /* coverity[member_decl] */ |
| bool initialised_; |
| |
| }; |
| |
| class DiffieHellman : public ObjectWrap { |
| public: |
| static void Initialize(v8::Handle<v8::Object> target) { |
| HandleScope scope; |
| |
| 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(String::NewSymbol("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(String::NewSymbol("DiffieHellmanGroup"), t2->GetFunction()); |
| } |
| |
| bool 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 Init(unsigned char* p, int p_len) { |
| dh = DH_new(); |
| dh->p = BN_bin2bn(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 Init(unsigned char* p, int p_len, unsigned char* g, int g_len) { |
| dh = DH_new(); |
| dh->p = BN_bin2bn(p, p_len, 0); |
| dh->g = BN_bin2bn(g, g_len, 0); |
| initialised_ = true; |
| return true; |
| } |
| |
| protected: |
| static Handle<Value> DiffieHellmanGroup(const Arguments& args) { |
| HandleScope scope; |
| |
| DiffieHellman* diffieHellman = new DiffieHellman(); |
| |
| if (args.Length() != 1 || !args[0]->IsString()) { |
| return ThrowException(Exception::Error( |
| String::New("No group name given"))); |
| } |
| |
| String::Utf8Value group_name(args[0]); |
| |
| modp_group* it = modp_groups; |
| |
| while(it->name != NULL) { |
| if (!strcasecmp(*group_name, it->name)) |
| break; |
| it++; |
| } |
| |
| if (it->name != NULL) { |
| diffieHellman->Init(it->prime, it->prime_size, |
| it->gen, it->gen_size); |
| } else { |
| return ThrowException(Exception::Error( |
| String::New("Unknown group"))); |
| } |
| |
| diffieHellman->Wrap(args.This()); |
| |
| return args.This(); |
| } |
| |
| static Handle<Value> New(const Arguments& args) { |
| HandleScope scope; |
| |
| 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( |
| reinterpret_cast<unsigned char*>(Buffer::Data(args[0])), |
| Buffer::Length(args[0])); |
| } |
| } |
| |
| if (!initialized) { |
| return ThrowException(Exception::Error( |
| String::New("Initialization failed"))); |
| } |
| |
| diffieHellman->Wrap(args.This()); |
| |
| return args.This(); |
| } |
| |
| static Handle<Value> GenerateKeys(const Arguments& args) { |
| DiffieHellman* diffieHellman = |
| ObjectWrap::Unwrap<DiffieHellman>(args.This()); |
| |
| HandleScope scope; |
| |
| if (!diffieHellman->initialised_) { |
| return ThrowException(Exception::Error( |
| String::New("Not initialized"))); |
| } |
| |
| if (!DH_generate_key(diffieHellman->dh)) { |
| return ThrowException(Exception::Error( |
| String::New("Key generation failed"))); |
| } |
| |
| Local<Value> outString; |
| |
| 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)); |
| |
| outString = Encode(data, dataSize, BUFFER); |
| delete[] data; |
| |
| return scope.Close(outString); |
| } |
| |
| static Handle<Value> GetPrime(const Arguments& args) { |
| DiffieHellman* diffieHellman = |
| ObjectWrap::Unwrap<DiffieHellman>(args.This()); |
| |
| HandleScope scope; |
| |
| if (!diffieHellman->initialised_) { |
| return ThrowException(Exception::Error(String::New("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)); |
| |
| Local<Value> outString; |
| |
| outString = Encode(data, dataSize, BUFFER); |
| |
| delete[] data; |
| |
| return scope.Close(outString); |
| } |
| |
| static Handle<Value> GetGenerator(const Arguments& args) { |
| DiffieHellman* diffieHellman = |
| ObjectWrap::Unwrap<DiffieHellman>(args.This()); |
| |
| HandleScope scope; |
| |
| if (!diffieHellman->initialised_) { |
| return ThrowException(Exception::Error(String::New("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)); |
| |
| Local<Value> outString; |
| |
| outString = Encode(data, dataSize, BUFFER); |
| |
| delete[] data; |
| |
| return scope.Close(outString); |
| } |
| |
| static Handle<Value> GetPublicKey(const Arguments& args) { |
| DiffieHellman* diffieHellman = |
| ObjectWrap::Unwrap<DiffieHellman>(args.This()); |
| |
| HandleScope scope; |
| |
| if (!diffieHellman->initialised_) { |
| return ThrowException(Exception::Error(String::New("Not initialized"))); |
| } |
| |
| if (diffieHellman->dh->pub_key == NULL) { |
| return ThrowException(Exception::Error( |
| String::New("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)); |
| |
| Local<Value> outString; |
| |
| outString = Encode(data, dataSize, BUFFER); |
| |
| delete[] data; |
| |
| return scope.Close(outString); |
| } |
| |
| static Handle<Value> GetPrivateKey(const Arguments& args) { |
| DiffieHellman* diffieHellman = |
| ObjectWrap::Unwrap<DiffieHellman>(args.This()); |
| |
| HandleScope scope; |
| |
| if (!diffieHellman->initialised_) { |
| return ThrowException(Exception::Error(String::New("Not initialized"))); |
| } |
| |
| if (diffieHellman->dh->priv_key == NULL) { |
| return ThrowException(Exception::Error( |
| String::New("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)); |
| |
| Local<Value> outString; |
| |
| outString = Encode(data, dataSize, BUFFER); |
| |
| delete[] data; |
| |
| return scope.Close(outString); |
| } |
| |
| static Handle<Value> ComputeSecret(const Arguments& args) { |
| HandleScope scope; |
| |
| DiffieHellman* diffieHellman = |
| ObjectWrap::Unwrap<DiffieHellman>(args.This()); |
| |
| if (!diffieHellman->initialised_) { |
| return ThrowException(Exception::Error(String::New("Not initialized"))); |
| } |
| |
| ClearErrorOnReturn clear_error_on_return; |
| (void) &clear_error_on_return; // Silence compiler warning. |
| BIGNUM* key = 0; |
| |
| if (args.Length() == 0) { |
| return ThrowException(Exception::Error( |
| String::New("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 ThrowException(Exception::Error(String::New("Invalid key"))); |
| } else if (checkResult) { |
| if (checkResult & DH_CHECK_PUBKEY_TOO_SMALL) { |
| return ThrowException(Exception::Error( |
| String::New("Supplied key is too small"))); |
| } else if (checkResult & DH_CHECK_PUBKEY_TOO_LARGE) { |
| return ThrowException(Exception::Error( |
| String::New("Supplied key is too large"))); |
| } else { |
| return ThrowException(Exception::Error(String::New("Invalid key"))); |
| } |
| } else { |
| return ThrowException(Exception::Error(String::New("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); |
| } |
| |
| Local<Value> outString; |
| |
| outString = Encode(data, dataSize, BUFFER); |
| |
| delete[] data; |
| return scope.Close(outString); |
| } |
| |
| static Handle<Value> SetPublicKey(const Arguments& args) { |
| HandleScope scope; |
| |
| DiffieHellman* diffieHellman = |
| ObjectWrap::Unwrap<DiffieHellman>(args.This()); |
| |
| if (!diffieHellman->initialised_) { |
| return ThrowException(Exception::Error(String::New("Not initialized"))); |
| } |
| |
| if (args.Length() == 0) { |
| return ThrowException(Exception::Error( |
| String::New("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); |
| } |
| |
| return args.This(); |
| } |
| |
| static Handle<Value> SetPrivateKey(const Arguments& args) { |
| HandleScope scope; |
| |
| DiffieHellman* diffieHellman = |
| ObjectWrap::Unwrap<DiffieHellman>(args.This()); |
| |
| if (!diffieHellman->initialised_) { |
| return ThrowException(Exception::Error( |
| String::New("Not initialized"))); |
| } |
| |
| if (args.Length() == 0) { |
| return ThrowException(Exception::Error( |
| String::New("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); |
| } |
| |
| return args.This(); |
| } |
| |
| DiffieHellman() : ObjectWrap() { |
| initialised_ = false; |
| dh = NULL; |
| } |
| |
| ~DiffieHellman() { |
| if (dh != NULL) { |
| DH_free(dh); |
| } |
| } |
| |
| private: |
| bool 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; |
| } |
| |
| bool initialised_; |
| DH* dh; |
| }; |
| |
| |
| 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] = Local<Value>::New(Undefined()); |
| argv[1] = Encode(req->key, req->keylen, BUFFER); |
| memset(req->key, 0, req->keylen); |
| } else { |
| argv[0] = Exception::Error(String::New("PBKDF2 error")); |
| argv[1] = Local<Value>::New(Undefined()); |
| } |
| |
| 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; |
| Local<Value> argv[2]; |
| Persistent<Object> obj = req->obj; |
| EIO_PBKDF2After(req, argv); |
| MakeCallback(obj, "ondone", ARRAY_SIZE(argv), argv); |
| obj.Dispose(); |
| } |
| |
| |
| Handle<Value> PBKDF2(const Arguments& args) { |
| HandleScope scope; |
| |
| 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()) { |
| req->obj = Persistent<Object>::New(Object::New()); |
| req->obj->Set(String::New("ondone"), args[4]); |
| uv_queue_work(uv_default_loop(), |
| &req->work_req, |
| EIO_PBKDF2, |
| EIO_PBKDF2After); |
| return Undefined(); |
| } else { |
| Local<Value> argv[2]; |
| EIO_PBKDF2(req); |
| EIO_PBKDF2After(req, argv); |
| if (argv[0]->IsObject()) return ThrowException(argv[0]); |
| return scope.Close(argv[1]); |
| } |
| |
| err: |
| delete[] salt; |
| delete[] pass; |
| return ThrowException(Exception::TypeError(String::New(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() { |
| if (obj_.IsEmpty()) return; |
| obj_.Dispose(); |
| obj_.Clear(); |
| } |
| |
| |
| void RandomBytesFree(char* data, void* hint) { |
| delete[] data; |
| } |
| |
| |
| 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(String::New(errmsg)); |
| argv[1] = Local<Value>::New(Null()); |
| } |
| else { |
| // avoids the malloc + memcpy |
| Buffer* buffer = Buffer::New(req->data_, req->size_, RandomBytesFree, NULL); |
| argv[0] = Local<Value>::New(Null()); |
| argv[1] = Local<Object>::New(buffer->handle_); |
| req->data_ = NULL; |
| } |
| free(req->data_); |
| } |
| |
| |
| void RandomBytesAfter(uv_work_t* work_req, int status) { |
| assert(status == 0); |
| RandomBytesRequest* req = container_of(work_req, |
| RandomBytesRequest, |
| work_req_); |
| HandleScope scope; |
| Local<Value> argv[2]; |
| RandomBytesCheck(req, argv); |
| MakeCallback(req->obj_, "ondone", ARRAY_SIZE(argv), argv); |
| delete req; |
| } |
| |
| |
| template <bool pseudoRandom> |
| Handle<Value> RandomBytes(const Arguments& args) { |
| HandleScope scope; |
| |
| // 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()) { |
| req->obj_ = Persistent<Object>::New(Object::New()); |
| req->obj_->Set(String::New("ondone"), args[1]); |
| |
| uv_queue_work(uv_default_loop(), |
| &req->work_req_, |
| RandomBytesWork<pseudoRandom>, |
| RandomBytesAfter); |
| |
| return req->obj_; |
| } |
| else { |
| Local<Value> argv[2]; |
| RandomBytesWork<pseudoRandom>(&req->work_req_); |
| RandomBytesCheck(req, argv); |
| delete req; |
| |
| if (!argv[0]->IsNull()) |
| return ThrowException(argv[0]); |
| else |
| return argv[1]; |
| } |
| } |
| |
| |
| Handle<Value> GetSSLCiphers(const Arguments& args) { |
| HandleScope scope; |
| |
| 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, String::New(SSL_CIPHER_get_name(cipher))); |
| } |
| |
| SSL_free(ssl); |
| SSL_CTX_free(ctx); |
| |
| return scope.Close(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(), String::New(from)); |
| } |
| |
| |
| Handle<Value> GetCiphers(const Arguments& args) { |
| HandleScope scope; |
| Local<Array> arr = Array::New(); |
| EVP_CIPHER_do_all_sorted(array_push_back<EVP_CIPHER>, &arr); |
| return scope.Close(arr); |
| } |
| |
| |
| Handle<Value> GetHashes(const Arguments& args) { |
| HandleScope scope; |
| Local<Array> arr = Array::New(); |
| EVP_MD_do_all_sorted(array_push_back<EVP_MD>, &arr); |
| return scope.Close(arr); |
| } |
| |
| |
| void InitCrypto(Handle<Object> target) { |
| HandleScope scope; |
| |
| 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) |
| STACK_OF(SSL_COMP)* comp_methods = |
| #if OPENSSL_VERSION_NUMBER < 0x00908000L |
| SSL_COMP_get_compression_method() |
| #else |
| 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); |
| Cipher::Initialize(target); |
| Decipher::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 = NODE_PSYMBOL("subject"); |
| issuer_symbol = NODE_PSYMBOL("issuer"); |
| valid_from_symbol = NODE_PSYMBOL("valid_from"); |
| valid_to_symbol = NODE_PSYMBOL("valid_to"); |
| subjectaltname_symbol = NODE_PSYMBOL("subjectaltname"); |
| modulus_symbol = NODE_PSYMBOL("modulus"); |
| exponent_symbol = NODE_PSYMBOL("exponent"); |
| fingerprint_symbol = NODE_PSYMBOL("fingerprint"); |
| name_symbol = NODE_PSYMBOL("name"); |
| version_symbol = NODE_PSYMBOL("version"); |
| ext_key_usage_symbol = NODE_PSYMBOL("ext_key_usage"); |
| } |
| |
| } // namespace crypto |
| } // namespace node |
| |
| NODE_MODULE(node_crypto, node::crypto::InitCrypto) |