diff --git a/Makefile b/Makefile index da37eab4550069..06a1a671566b12 100644 --- a/Makefile +++ b/Makefile @@ -887,6 +887,8 @@ CPPLINT_FILES = $(filter-out $(CPPLINT_EXCLUDE), $(wildcard \ src/*.c \ src/*.cc \ src/*.h \ + src/crypto_impl/*.h \ + src/crypto_impl/*.cc \ test/addons/*/*.cc \ test/addons/*/*.h \ test/cctest/*.cc \ diff --git a/configure b/configure index 01aacb8ed0f131..f40f925c987d18 100755 --- a/configure +++ b/configure @@ -159,6 +159,11 @@ parser.add_option('--openssl-use-def-ca-store', dest='use_openssl_ca_store', help='Use OpenSSL supplied CA store instead of compiled-in Mozilla CA copy.') +parser.add_option("--crypto-version", + action="store", + dest="crypto_version", + help="The version of the crypto implementation.") + shared_optgroup.add_option('--shared-http-parser', action='store_true', dest='shared_http_parser', @@ -985,6 +990,14 @@ def configure_openssl(o): else: o['variables']['openssl_fips'] = '' + if options.crypto_version is None: + options.crypto_version = "openssl_1_0_2e" + print('Using default crypto_version = %s Please specify a specific ' + 'version if this does not match the version of the crypto library.' + % options.crypto_version) + + o['variables']['node_crypto_version'] = options.crypto_version + o['defines'] += ['NODE_CRYPTO_VERSION=%s' % options.crypto_version] if options.without_ssl: def without_ssl_error(option): diff --git a/lib/_tls_common.js b/lib/_tls_common.js index 2f0b17b111a420..66af357fe2c905 100644 --- a/lib/_tls_common.js +++ b/lib/_tls_common.js @@ -159,7 +159,6 @@ exports.createSecureContext = function createSecureContext(options, context) { // freelist.) if (options.singleUse) { c.singleUse = true; - c.context.setFreeListLength(0); } return c; diff --git a/node.gyp b/node.gyp index 93ee7d64ddb287..b39b0ead70ee6c 100644 --- a/node.gyp +++ b/node.gyp @@ -652,7 +652,8 @@ 'test/cctest/test_base64.cc', 'test/cctest/test_environment.cc', 'test/cctest/test_util.cc', - 'test/cctest/test_url.cc' + 'test/cctest/test_url.cc', + 'test/cctest/test_crypto_factory.cc', ], 'sources!': [ @@ -660,6 +661,12 @@ ], 'conditions': [ + [ 'node_crypto_version=="openssl_1_0_2e"', { + 'sources+': ['test/cctest/test_openssl_1_0_2e.cc'], + }], + [ 'node_crypto_version=="openssl_1_1_0f"', { + 'sources+': ['test/cctest/test_openssl_1_1_0f.cc'], + }], ['v8_enable_inspector==1', { 'sources': [ 'test/cctest/test_inspector_socket.cc', diff --git a/node.gypi b/node.gypi index a926d9a8e7ff26..fda0e9a4fdbde0 100644 --- a/node.gypi +++ b/node.gypi @@ -98,16 +98,39 @@ [ 'node_use_openssl=="true"', { 'defines': [ 'HAVE_OPENSSL=1' ], 'sources': [ - 'src/node_crypto.cc', - 'src/node_crypto_bio.cc', - 'src/node_crypto_clienthello.cc', 'src/node_crypto.h', - 'src/node_crypto_bio.h', - 'src/node_crypto_clienthello.h', - 'src/tls_wrap.cc', - 'src/tls_wrap.h' + 'src/node_crypto_factory.h', + 'src/node_crypto_factory.cc', ], 'conditions': [ + [ 'node_crypto_version=="openssl_1_0_2e"', { + 'sources+': [ + 'src/crypto_impl/openssl/1_0_2e/node_crypto.cc', + 'src/crypto_impl/openssl/1_0_2e/node_crypto_bio.cc', + 'src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello.cc', + 'src/crypto_impl/openssl/1_0_2e/node_crypto.h', + 'src/crypto_impl/openssl/1_0_2e/node_crypto_bio.h', + 'src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello.h', + 'src/crypto_impl/openssl/1_0_2e/tls_wrap.cc', + 'src/crypto_impl/openssl/1_0_2e/tls_wrap.h', + 'src/crypto_impl/openssl/openssl.h', + 'src/crypto_impl/openssl/1_0_2e/openssl_1_0_2e.cc', + ], + }], + [ 'node_crypto_version=="openssl_1_1_0f"', { + 'sources+': [ + 'src/crypto_impl/openssl/1_1_0f/node_crypto.cc', + 'src/crypto_impl/openssl/1_1_0f/node_crypto_bio.cc', + 'src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello.cc', + 'src/crypto_impl/openssl/1_1_0f/node_crypto.h', + 'src/crypto_impl/openssl/1_1_0f/node_crypto_bio.h', + 'src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello.h', + 'src/crypto_impl/openssl/1_1_0f/tls_wrap.cc', + 'src/crypto_impl/openssl/1_1_0f/tls_wrap.h', + 'src/crypto_impl/openssl/openssl.h', + 'src/crypto_impl/openssl/1_1_0f/openssl_1_1_0f.cc', + ], + }], ['openssl_fips != ""', { 'defines': [ 'NODE_FIPS_MODE' ], }], diff --git a/src/node_crypto.cc b/src/crypto_impl/openssl/1_0_2e/node_crypto.cc similarity index 99% rename from src/node_crypto.cc rename to src/crypto_impl/openssl/1_0_2e/node_crypto.cc index 579ba3c65da640..597a8bd8d11575 100644 --- a/src/node_crypto.cc +++ b/src/crypto_impl/openssl/1_0_2e/node_crypto.cc @@ -1175,8 +1175,9 @@ void SecureContext::SetTicketKeys(const FunctionCallbackInfo& args) { THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Ticket keys"); - if (Buffer::Length(args[0]) != 48) { - return env->ThrowTypeError("Ticket keys length must be 48 bytes"); + long length = SSL_CTX_get_tlsext_ticket_keys(wrap->ctx_, NULL, 0); + if (Buffer::Length(args[0]) != (size_t)length) { + return env->ThrowTypeError("Ticket keys length incorrect"); } if (SSL_CTX_set_tlsext_ticket_keys(wrap->ctx_, diff --git a/src/crypto_impl/openssl/1_0_2e/node_crypto.h b/src/crypto_impl/openssl/1_0_2e/node_crypto.h new file mode 100644 index 00000000000000..31227da56ff187 --- /dev/null +++ b/src/crypto_impl/openssl/1_0_2e/node_crypto.h @@ -0,0 +1,755 @@ +// 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. + +#ifndef SRC_CRYPTO_IMPL_NODE_CRYPTO_H_ +#define SRC_CRYPTO_IMPL_NODE_CRYPTO_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node.h" +#include "node_crypto_clienthello.h" // ClientHelloParser +#include "node_crypto_clienthello-inl.h" + +#include "node_buffer.h" + +#include "env.h" +#include "async-wrap.h" +#include "async-wrap-inl.h" +#include "base-object.h" +#include "base-object-inl.h" + +#include "v8.h" + +#include +#include +#include +#ifndef OPENSSL_NO_ENGINE +# include +#endif // !OPENSSL_NO_ENGINE +#include +#include +#include +#include +#include +#include +#include +#include + +#define EVP_F_EVP_DECRYPTFINAL 101 + +#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) +# define NODE__HAVE_TLSEXT_STATUS_CB +#endif // !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) + +namespace node { +namespace crypto { + +// 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(); } +}; + +// Pop errors from OpenSSL's error stack that were added +// between when this was constructed and destructed. +struct MarkPopErrorOnReturn { + MarkPopErrorOnReturn() { ERR_set_mark(); } + ~MarkPopErrorOnReturn() { ERR_pop_to_mark(); } +}; + +enum CheckResult { + CHECK_CERT_REVOKED = 0, + CHECK_OK = 1 +}; + +extern int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx); + +extern void UseExtraCaCerts(const std::string& file); + +class SecureContext : public BaseObject { + public: + ~SecureContext() override { + FreeCTXMem(); + } + + static void Initialize(Environment* env, v8::Local target); + + SSL_CTX* ctx_; + X509* cert_; + X509* issuer_; + + static const int kMaxSessionSize = 10 * 1024; + + // See TicketKeyCallback + static const int kTicketKeyReturnIndex = 0; + static const int kTicketKeyHMACIndex = 1; + static const int kTicketKeyAESIndex = 2; + static const int kTicketKeyNameIndex = 3; + static const int kTicketKeyIVIndex = 4; + + protected: + static const int64_t kExternalSize = sizeof(SSL_CTX); + + static void New(const v8::FunctionCallbackInfo& args); + static void Init(const v8::FunctionCallbackInfo& args); + static void SetKey(const v8::FunctionCallbackInfo& args); + static void SetCert(const v8::FunctionCallbackInfo& args); + static void AddCACert(const v8::FunctionCallbackInfo& args); + static void AddCRL(const v8::FunctionCallbackInfo& args); + static void AddRootCerts(const v8::FunctionCallbackInfo& args); + static void SetCiphers(const v8::FunctionCallbackInfo& args); + static void SetECDHCurve(const v8::FunctionCallbackInfo& args); + static void SetDHParam(const v8::FunctionCallbackInfo& args); + static void SetOptions(const v8::FunctionCallbackInfo& args); + static void SetSessionIdContext( + const v8::FunctionCallbackInfo& args); + static void SetSessionTimeout( + const v8::FunctionCallbackInfo& args); + static void Close(const v8::FunctionCallbackInfo& args); + static void LoadPKCS12(const v8::FunctionCallbackInfo& args); + static void GetTicketKeys(const v8::FunctionCallbackInfo& args); + static void SetTicketKeys(const v8::FunctionCallbackInfo& args); + static void SetFreeListLength( + const v8::FunctionCallbackInfo& args); + static void EnableTicketKeyCallback( + const v8::FunctionCallbackInfo& args); + static void CtxGetter(v8::Local property, + const v8::PropertyCallbackInfo& info); + + template + static void GetCertificate(const v8::FunctionCallbackInfo& args); + + static int TicketKeyCallback(SSL* ssl, + unsigned char* name, + unsigned char* iv, + EVP_CIPHER_CTX* ectx, + HMAC_CTX* hctx, + int enc); + + SecureContext(Environment* env, v8::Local wrap) + : BaseObject(env, wrap), + ctx_(nullptr), + cert_(nullptr), + issuer_(nullptr) { + MakeWeak(this); + env->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize); + } + + void FreeCTXMem() { + if (!ctx_) { + return; + } + + env()->isolate()->AdjustAmountOfExternalAllocatedMemory(-kExternalSize); + SSL_CTX_free(ctx_); + if (cert_ != nullptr) + X509_free(cert_); + if (issuer_ != nullptr) + X509_free(issuer_); + ctx_ = nullptr; + cert_ = nullptr; + issuer_ = nullptr; + } +}; + +// SSLWrap implicitly depends on the inheriting class' handle having an +// internal pointer to the Base class. +template +class SSLWrap { + public: + enum Kind { + kClient, + kServer + }; + + SSLWrap(Environment* env, SecureContext* sc, Kind kind) + : env_(env), + kind_(kind), + next_sess_(nullptr), + session_callbacks_(false), + new_session_wait_(false), + cert_cb_(nullptr), + cert_cb_arg_(nullptr), + cert_cb_running_(false) { + ssl_ = SSL_new(sc->ctx_); + env_->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize); + CHECK_NE(ssl_, nullptr); + } + + virtual ~SSLWrap() { + DestroySSL(); + if (next_sess_ != nullptr) { + SSL_SESSION_free(next_sess_); + next_sess_ = nullptr; + } + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + sni_context_.Reset(); +#endif + +#ifdef NODE__HAVE_TLSEXT_STATUS_CB + ocsp_response_.Reset(); +#endif // NODE__HAVE_TLSEXT_STATUS_CB + } + + inline SSL* ssl() const { return ssl_; } + inline void enable_session_callbacks() { session_callbacks_ = true; } + inline bool is_server() const { return kind_ == kServer; } + inline bool is_client() const { return kind_ == kClient; } + inline bool is_waiting_new_session() const { return new_session_wait_; } + inline bool is_waiting_cert_cb() const { return cert_cb_ != nullptr; } + + protected: + typedef void (*CertCb)(void* arg); + + // Size allocated by OpenSSL: one for SSL structure, one for SSL3_STATE and + // some for buffers. + // NOTE: Actually it is much more than this + static const int64_t kExternalSize = + sizeof(SSL) + sizeof(SSL3_STATE) + 42 * 1024; + + static void InitNPN(SecureContext* sc); + static void AddMethods(Environment* env, v8::Local t); + + static SSL_SESSION* GetSessionCallback(SSL* s, + unsigned char* key, + int len, + int* copy); + static int NewSessionCallback(SSL* s, SSL_SESSION* sess); + static void OnClientHello(void* arg, + const ClientHelloParser::ClientHello& hello); + + static void GetPeerCertificate( + const v8::FunctionCallbackInfo& args); + static void GetSession(const v8::FunctionCallbackInfo& args); + static void SetSession(const v8::FunctionCallbackInfo& args); + static void LoadSession(const v8::FunctionCallbackInfo& args); + static void IsSessionReused(const v8::FunctionCallbackInfo& args); + static void IsInitFinished(const v8::FunctionCallbackInfo& args); + static void VerifyError(const v8::FunctionCallbackInfo& args); + static void GetCurrentCipher(const v8::FunctionCallbackInfo& args); + static void EndParser(const v8::FunctionCallbackInfo& args); + static void CertCbDone(const v8::FunctionCallbackInfo& args); + static void Renegotiate(const v8::FunctionCallbackInfo& args); + static void Shutdown(const v8::FunctionCallbackInfo& args); + static void GetTLSTicket(const v8::FunctionCallbackInfo& args); + static void NewSessionDone(const v8::FunctionCallbackInfo& args); + static void SetOCSPResponse(const v8::FunctionCallbackInfo& args); + static void RequestOCSP(const v8::FunctionCallbackInfo& args); + static void GetEphemeralKeyInfo( + const v8::FunctionCallbackInfo& args); + static void GetProtocol(const v8::FunctionCallbackInfo& args); + +#ifdef SSL_set_max_send_fragment + static void SetMaxSendFragment( + const v8::FunctionCallbackInfo& args); +#endif // SSL_set_max_send_fragment + +#ifndef OPENSSL_NO_NEXTPROTONEG + static void GetNegotiatedProto( + const v8::FunctionCallbackInfo& args); + static void SetNPNProtocols(const v8::FunctionCallbackInfo& args); + static int AdvertiseNextProtoCallback(SSL* s, + const unsigned char** data, + unsigned int* len, + void* arg); + static int SelectNextProtoCallback(SSL* s, + unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg); +#endif // OPENSSL_NO_NEXTPROTONEG + + static void GetALPNNegotiatedProto( + const v8::FunctionCallbackInfo& args); + static void SetALPNProtocols(const v8::FunctionCallbackInfo& args); + static int SelectALPNCallback(SSL* s, + const unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg); + static int TLSExtStatusCallback(SSL* s, void* arg); + static int SSLCertCallback(SSL* s, void* arg); + static void SSLGetter(v8::Local property, + const v8::PropertyCallbackInfo& info); + + void DestroySSL(); + void WaitForCertCb(CertCb cb, void* arg); + void SetSNIContext(SecureContext* sc); + int SetCACerts(SecureContext* sc); + + inline Environment* ssl_env() const { + return env_; + } + + Environment* const env_; + Kind kind_; + SSL_SESSION* next_sess_; + SSL* ssl_; + bool session_callbacks_; + bool new_session_wait_; + + // SSL_set_cert_cb + CertCb cert_cb_; + void* cert_cb_arg_; + bool cert_cb_running_; + + ClientHelloParser hello_parser_; + +#ifdef NODE__HAVE_TLSEXT_STATUS_CB + v8::Persistent ocsp_response_; +#endif // NODE__HAVE_TLSEXT_STATUS_CB + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + v8::Persistent sni_context_; +#endif + + friend class SecureContext; +}; + +// Connection inherits from AsyncWrap because SSLWrap makes calls to +// MakeCallback, but SSLWrap doesn't store the handle itself. Instead it +// assumes that any args.This() called will be the handle from Connection. +class Connection : public AsyncWrap, public SSLWrap { + public: + ~Connection() override { +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + sniObject_.Reset(); + servername_.Reset(); +#endif + } + + static void Initialize(Environment* env, v8::Local target); + void NewSessionDoneCb(); + +#ifndef OPENSSL_NO_NEXTPROTONEG + v8::Persistent npnProtos_; + v8::Persistent selectedNPNProto_; +#endif + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + v8::Persistent sniObject_; + v8::Persistent servername_; +#endif + + size_t self_size() const override { return sizeof(*this); } + + protected: + static void New(const v8::FunctionCallbackInfo& args); + static void EncIn(const v8::FunctionCallbackInfo& args); + static void ClearOut(const v8::FunctionCallbackInfo& args); + static void ClearPending(const v8::FunctionCallbackInfo& args); + static void EncPending(const v8::FunctionCallbackInfo& args); + static void EncOut(const v8::FunctionCallbackInfo& args); + static void ClearIn(const v8::FunctionCallbackInfo& args); + static void Start(const v8::FunctionCallbackInfo& args); + static void Close(const v8::FunctionCallbackInfo& args); + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + // SNI + static void GetServername(const v8::FunctionCallbackInfo& args); + static void SetSNICallback(const v8::FunctionCallbackInfo& args); + static int SelectSNIContextCallback_(SSL* s, int* ad, void* arg); +#endif + + static void OnClientHelloParseEnd(void* arg); + + int HandleBIOError(BIO* bio, const char* func, int rv); + + enum ZeroStatus { + kZeroIsNotAnError, + kZeroIsAnError + }; + + enum SyscallStatus { + kIgnoreSyscall, + kSyscallError + }; + + int HandleSSLError(const char* func, int rv, ZeroStatus zs, SyscallStatus ss); + + void ClearError(); + void SetShutdownFlags(); + + Connection(Environment* env, + v8::Local wrap, + SecureContext* sc, + SSLWrap::Kind kind) + : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_SSLCONNECTION), + SSLWrap(env, sc, kind), + bio_read_(nullptr), + bio_write_(nullptr), + hello_offset_(0) { + MakeWeak(this); + Wrap(wrap, this); + hello_parser_.Start(SSLWrap::OnClientHello, + OnClientHelloParseEnd, + this); + enable_session_callbacks(); + } + + private: + static void SSLInfoCallback(const SSL *ssl, int where, int ret); + + BIO *bio_read_; + BIO *bio_write_; + + uint8_t hello_data_[18432]; + size_t hello_offset_; + + friend class ClientHelloParser; + friend class SecureContext; +}; + +class CipherBase : public BaseObject { + public: + ~CipherBase() override { + if (!initialised_) + return; + delete[] auth_tag_; + EVP_CIPHER_CTX_cleanup(&ctx_); + } + + static void Initialize(Environment* env, v8::Local target); + + protected: + enum CipherKind { + kCipher, + kDecipher + }; + + void Init(const char* cipher_type, const char* key_buf, int key_buf_len); + void InitIv(const char* cipher_type, + const char* key, + int key_len, + const char* iv, + int iv_len); + bool Update(const char* data, int len, unsigned char** out, int* out_len); + bool Final(unsigned char** out, int *out_len); + bool SetAutoPadding(bool auto_padding); + + bool IsAuthenticatedMode() const; + bool GetAuthTag(char** out, unsigned int* out_len) const; + bool SetAuthTag(const char* data, unsigned int len); + bool SetAAD(const char* data, unsigned int len); + + static void New(const v8::FunctionCallbackInfo& args); + static void Init(const v8::FunctionCallbackInfo& args); + static void InitIv(const v8::FunctionCallbackInfo& args); + static void Update(const v8::FunctionCallbackInfo& args); + static void Final(const v8::FunctionCallbackInfo& args); + static void SetAutoPadding(const v8::FunctionCallbackInfo& args); + + static void GetAuthTag(const v8::FunctionCallbackInfo& args); + static void SetAuthTag(const v8::FunctionCallbackInfo& args); + static void SetAAD(const v8::FunctionCallbackInfo& args); + + CipherBase(Environment* env, + v8::Local wrap, + CipherKind kind) + : BaseObject(env, wrap), + cipher_(nullptr), + initialised_(false), + kind_(kind), + auth_tag_(nullptr), + auth_tag_len_(0) { + MakeWeak(this); + } + + private: + EVP_CIPHER_CTX ctx_; /* coverity[member_decl] */ + const EVP_CIPHER* cipher_; /* coverity[member_decl] */ + bool initialised_; + CipherKind kind_; + char* auth_tag_; + unsigned int auth_tag_len_; +}; + +class Hmac : public BaseObject { + public: + ~Hmac() override { + if (!initialised_) + return; + HMAC_CTX_cleanup(&ctx_); + } + + static void Initialize(Environment* env, v8::Local target); + + protected: + void HmacInit(const char* hash_type, const char* key, int key_len); + bool HmacUpdate(const char* data, int len); + bool HmacDigest(unsigned char** md_value, unsigned int* md_len); + + static void New(const v8::FunctionCallbackInfo& args); + static void HmacInit(const v8::FunctionCallbackInfo& args); + static void HmacUpdate(const v8::FunctionCallbackInfo& args); + static void HmacDigest(const v8::FunctionCallbackInfo& args); + + Hmac(Environment* env, v8::Local wrap) + : BaseObject(env, wrap), + initialised_(false) { + MakeWeak(this); + } + + private: + HMAC_CTX ctx_; /* coverity[member_decl] */ + bool initialised_; +}; + +class Hash : public BaseObject { + public: + ~Hash() override { + if (!initialised_) + return; + EVP_MD_CTX_cleanup(&mdctx_); + } + + static void Initialize(Environment* env, v8::Local target); + + bool HashInit(const char* hash_type); + bool HashUpdate(const char* data, int len); + + protected: + static void New(const v8::FunctionCallbackInfo& args); + static void HashUpdate(const v8::FunctionCallbackInfo& args); + static void HashDigest(const v8::FunctionCallbackInfo& args); + + Hash(Environment* env, v8::Local wrap) + : BaseObject(env, wrap), + initialised_(false) { + MakeWeak(this); + } + + private: + EVP_MD_CTX mdctx_; /* coverity[member_decl] */ + bool initialised_; + bool finalized_; +}; + +class SignBase : public BaseObject { + public: + typedef enum { + kSignOk, + kSignUnknownDigest, + kSignInit, + kSignNotInitialised, + kSignUpdate, + kSignPrivateKey, + kSignPublicKey + } Error; + + SignBase(Environment* env, v8::Local wrap) + : BaseObject(env, wrap), + initialised_(false) { + } + + ~SignBase() override { + if (!initialised_) + return; + EVP_MD_CTX_cleanup(&mdctx_); + } + + protected: + void CheckThrow(Error error); + + EVP_MD_CTX mdctx_; /* coverity[member_decl] */ + bool initialised_; +}; + +class Sign : public SignBase { + public: + static void Initialize(Environment* env, v8::Local target); + + Error SignInit(const char* sign_type); + Error SignUpdate(const char* data, int len); + Error SignFinal(const char* key_pem, + int key_pem_len, + const char* passphrase, + unsigned char** sig, + unsigned int *sig_len, + int padding, + int saltlen); + + protected: + static void New(const v8::FunctionCallbackInfo& args); + static void SignInit(const v8::FunctionCallbackInfo& args); + static void SignUpdate(const v8::FunctionCallbackInfo& args); + static void SignFinal(const v8::FunctionCallbackInfo& args); + + Sign(Environment* env, v8::Local wrap) : SignBase(env, wrap) { + MakeWeak(this); + } +}; + +class Verify : public SignBase { + public: + static void Initialize(Environment* env, v8::Local target); + + Error VerifyInit(const char* verify_type); + Error VerifyUpdate(const char* data, int len); + Error VerifyFinal(const char* key_pem, + int key_pem_len, + const char* sig, + int siglen, + int padding, + int saltlen, + bool* verify_result); + + protected: + static void New(const v8::FunctionCallbackInfo& args); + static void VerifyInit(const v8::FunctionCallbackInfo& args); + static void VerifyUpdate(const v8::FunctionCallbackInfo& args); + static void VerifyFinal(const v8::FunctionCallbackInfo& args); + + Verify(Environment* env, v8::Local wrap) : SignBase(env, wrap) { + MakeWeak(this); + } +}; + +class PublicKeyCipher { + public: + typedef int (*EVP_PKEY_cipher_init_t)(EVP_PKEY_CTX *ctx); + typedef int (*EVP_PKEY_cipher_t)(EVP_PKEY_CTX *ctx, + unsigned char *out, size_t *outlen, + const unsigned char *in, size_t inlen); + + enum Operation { + kPublic, + kPrivate + }; + + template + static bool Cipher(const char* key_pem, + int key_pem_len, + const char* passphrase, + int padding, + const unsigned char* data, + int len, + unsigned char** out, + size_t* out_len); + + template + static void Cipher(const v8::FunctionCallbackInfo& args); +}; + +class DiffieHellman : public BaseObject { + public: + ~DiffieHellman() override { + if (dh != nullptr) { + DH_free(dh); + } + } + + static void Initialize(Environment* env, v8::Local target); + + bool Init(int primeLength, int g); + bool Init(const char* p, int p_len, int g); + bool Init(const char* p, int p_len, const char* g, int g_len); + + protected: + static void DiffieHellmanGroup( + const v8::FunctionCallbackInfo& args); + static void New(const v8::FunctionCallbackInfo& args); + static void GenerateKeys(const v8::FunctionCallbackInfo& args); + static void GetPrime(const v8::FunctionCallbackInfo& args); + static void GetGenerator(const v8::FunctionCallbackInfo& args); + static void GetPublicKey(const v8::FunctionCallbackInfo& args); + static void GetPrivateKey(const v8::FunctionCallbackInfo& args); + static void ComputeSecret(const v8::FunctionCallbackInfo& args); + static void SetPublicKey(const v8::FunctionCallbackInfo& args); + static void SetPrivateKey(const v8::FunctionCallbackInfo& args); + static void VerifyErrorGetter( + v8::Local property, + const v8::PropertyCallbackInfo& args); + + DiffieHellman(Environment* env, v8::Local wrap) + : BaseObject(env, wrap), + initialised_(false), + verifyError_(0), + dh(nullptr) { + MakeWeak(this); + } + + private: + bool VerifyContext(); + + bool initialised_; + int verifyError_; + DH* dh; +}; + +class ECDH : public BaseObject { + public: + ~ECDH() override { + if (key_ != nullptr) + EC_KEY_free(key_); + key_ = nullptr; + group_ = nullptr; + } + + static void Initialize(Environment* env, v8::Local target); + + protected: + ECDH(Environment* env, v8::Local wrap, EC_KEY* key) + : BaseObject(env, wrap), + key_(key), + group_(EC_KEY_get0_group(key_)) { + MakeWeak(this); + ASSERT_NE(group_, nullptr); + } + + static void New(const v8::FunctionCallbackInfo& args); + static void GenerateKeys(const v8::FunctionCallbackInfo& args); + static void ComputeSecret(const v8::FunctionCallbackInfo& args); + static void GetPrivateKey(const v8::FunctionCallbackInfo& args); + static void SetPrivateKey(const v8::FunctionCallbackInfo& args); + static void GetPublicKey(const v8::FunctionCallbackInfo& args); + static void SetPublicKey(const v8::FunctionCallbackInfo& args); + + EC_POINT* BufferToPoint(char* data, size_t len); + + bool IsKeyPairValid(); + bool IsKeyValidForCurve(const BIGNUM* private_key); + + EC_KEY* key_; + const EC_GROUP* group_; +}; + +bool EntropySource(unsigned char* buffer, size_t length); +#ifndef OPENSSL_NO_ENGINE +void SetEngine(const v8::FunctionCallbackInfo& args); +#endif // !OPENSSL_NO_ENGINE +void InitCrypto(v8::Local target); + +} // namespace crypto +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_CRYPTO_IMPL_NODE_CRYPTO_H_ diff --git a/src/node_crypto_bio.cc b/src/crypto_impl/openssl/1_0_2e/node_crypto_bio.cc similarity index 100% rename from src/node_crypto_bio.cc rename to src/crypto_impl/openssl/1_0_2e/node_crypto_bio.cc diff --git a/src/node_crypto_bio.h b/src/crypto_impl/openssl/1_0_2e/node_crypto_bio.h similarity index 97% rename from src/node_crypto_bio.h rename to src/crypto_impl/openssl/1_0_2e/node_crypto_bio.h index 4699d1e6e342a4..a1e53f90dc399f 100644 --- a/src/node_crypto_bio.h +++ b/src/crypto_impl/openssl/1_0_2e/node_crypto_bio.h @@ -19,8 +19,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -#ifndef SRC_NODE_CRYPTO_BIO_H_ -#define SRC_NODE_CRYPTO_BIO_H_ +#ifndef SRC_CRYPTO_IMPL_NODE_CRYPTO_BIO_H_ +#define SRC_CRYPTO_IMPL_NODE_CRYPTO_BIO_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS @@ -160,4 +160,4 @@ class NodeBIO { #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#endif // SRC_NODE_CRYPTO_BIO_H_ +#endif // SRC_CRYPTO_IMPL_NODE_CRYPTO_BIO_H_ diff --git a/src/node_crypto_clienthello-inl.h b/src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello-inl.h similarity index 93% rename from src/node_crypto_clienthello-inl.h rename to src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello-inl.h index 4044b0cde246df..e22582ec5e9004 100644 --- a/src/node_crypto_clienthello-inl.h +++ b/src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello-inl.h @@ -19,8 +19,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -#ifndef SRC_NODE_CRYPTO_CLIENTHELLO_INL_H_ -#define SRC_NODE_CRYPTO_CLIENTHELLO_INL_H_ +#ifndef SRC_CRYPTO_IMPL_NODE_CRYPTO_CLIENTHELLO_INL_H_ +#define SRC_CRYPTO_IMPL_NODE_CRYPTO_CLIENTHELLO_INL_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS @@ -78,4 +78,4 @@ inline bool ClientHelloParser::IsPaused() const { #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#endif // SRC_NODE_CRYPTO_CLIENTHELLO_INL_H_ +#endif // SRC_CRYPTO_IMPL_NODE_CRYPTO_CLIENTHELLO_INL_H_ diff --git a/src/node_crypto_clienthello.cc b/src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello.cc similarity index 100% rename from src/node_crypto_clienthello.cc rename to src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello.cc diff --git a/src/node_crypto_clienthello.h b/src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello.h similarity index 96% rename from src/node_crypto_clienthello.h rename to src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello.h index e52d6c3f9f8358..569661c02045a9 100644 --- a/src/node_crypto_clienthello.h +++ b/src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello.h @@ -19,8 +19,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -#ifndef SRC_NODE_CRYPTO_CLIENTHELLO_H_ -#define SRC_NODE_CRYPTO_CLIENTHELLO_H_ +#ifndef SRC_CRYPTO_IMPL_NODE_CRYPTO_CLIENTHELLO_H_ +#define SRC_CRYPTO_IMPL_NODE_CRYPTO_CLIENTHELLO_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS @@ -137,4 +137,4 @@ class ClientHelloParser { #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#endif // SRC_NODE_CRYPTO_CLIENTHELLO_H_ +#endif // SRC_CRYPTO_IMPL_NODE_CRYPTO_CLIENTHELLO_H_ diff --git a/src/node_crypto_groups.h b/src/crypto_impl/openssl/1_0_2e/node_crypto_groups.h similarity index 99% rename from src/node_crypto_groups.h rename to src/crypto_impl/openssl/1_0_2e/node_crypto_groups.h index d22fdc7f966f9a..23508e1e2ce2eb 100644 --- a/src/node_crypto_groups.h +++ b/src/crypto_impl/openssl/1_0_2e/node_crypto_groups.h @@ -19,8 +19,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -#ifndef SRC_NODE_CRYPTO_GROUPS_H_ -#define SRC_NODE_CRYPTO_GROUPS_H_ +#ifndef SRC_CRYPTO_IMPL_NODE_CRYPTO_GROUPS_H_ +#define SRC_CRYPTO_IMPL_NODE_CRYPTO_GROUPS_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS @@ -413,4 +413,4 @@ static const modp_group modp_groups[] = { #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#endif // SRC_NODE_CRYPTO_GROUPS_H_ +#endif // SRC_CRYPTO_IMPL_NODE_CRYPTO_GROUPS_H_ diff --git a/src/crypto_impl/openssl/1_0_2e/openssl_1_0_2e.cc b/src/crypto_impl/openssl/1_0_2e/openssl_1_0_2e.cc new file mode 100644 index 00000000000000..a75834669fd110 --- /dev/null +++ b/src/crypto_impl/openssl/1_0_2e/openssl_1_0_2e.cc @@ -0,0 +1,63 @@ +#include "../openssl.h" +#include "../../../node_crypto_factory.h" +#include "node_crypto.h" +#include + +namespace node { +namespace crypto { + +const std::string id_ = CryptoFactory::Register("openssl_1_0_2e", + []() -> Crypto* { return new OpenSSL(); }); + +const std::string OpenSSL::Version() { + return "1.0.2e"; +} + +bool OpenSSL::HasSNI() { +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + return true; +#else + return false; +#endif +} + +bool OpenSSL::HasNPN() { +#ifndef OPENSSL_NO_NEXTPROTONEG + return true; +#else + return false; +#endif +} + +bool OpenSSL::HasALPN() { +#ifndef TLSEXT_TYPE_application_layer_protocol_negotiation + return true; +#else + return false; +#endif +} + +bool OpenSSL::HasOCSP() { +#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) + return true; +#else + return false; +#endif +} + +OpenSSL::OpenSSL() { +} + +OpenSSL::~OpenSSL() { +} + +void OpenSSL::UseExtraCaCerts(const std::string& file) { + crypto::UseExtraCaCerts(file); +} + +v8::EntropySource OpenSSL::GetEntropySource() { + return crypto::EntropySource; +} + +} // namespace crypto +} // namespace node diff --git a/src/tls_wrap.cc b/src/crypto_impl/openssl/1_0_2e/tls_wrap.cc similarity index 100% rename from src/tls_wrap.cc rename to src/crypto_impl/openssl/1_0_2e/tls_wrap.cc diff --git a/src/tls_wrap.h b/src/crypto_impl/openssl/1_0_2e/tls_wrap.h similarity index 98% rename from src/tls_wrap.h rename to src/crypto_impl/openssl/1_0_2e/tls_wrap.h index 19633ea8667fff..d06b86f0d966af 100644 --- a/src/tls_wrap.h +++ b/src/crypto_impl/openssl/1_0_2e/tls_wrap.h @@ -19,8 +19,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -#ifndef SRC_TLS_WRAP_H_ -#define SRC_TLS_WRAP_H_ +#ifndef SRC_CRYPTO_IMPL_TLS_WRAP_H_ +#define SRC_CRYPTO_IMPL_TLS_WRAP_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS @@ -192,4 +192,4 @@ class TLSWrap : public AsyncWrap, #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#endif // SRC_TLS_WRAP_H_ +#endif // SRC_CRYPTO_IMPL_TLS_WRAP_H_ diff --git a/src/crypto_impl/openssl/1_1_0f/node_crypto.cc b/src/crypto_impl/openssl/1_1_0f/node_crypto.cc new file mode 100644 index 00000000000000..16f62764da2741 --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto.cc @@ -0,0 +1,6318 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "node.h" +#include "node_buffer.h" +#include "node_constants.h" +#include "node_crypto.h" +#include "node_crypto_bio.h" +#include "node_crypto_groups.h" +#include "node_mutex.h" +#include "tls_wrap.h" // TLSWrap + +#include "async-wrap.h" +#include "async-wrap-inl.h" +#include "env.h" +#include "env-inl.h" +#include "string_bytes.h" +#include "util.h" +#include "util-inl.h" +#include "v8.h" +// CNNIC Hash WhiteList is taken from +// https://hg.mozilla.org/mozilla-central/raw-file/98820360ab66/security/ +// certverifier/CNNICHashWhitelist.inc +#include "CNNICHashWhitelist.inc" +// StartCom and WoSign root CA list is taken from +// https://hg.mozilla.org/mozilla-central/file/tip/security/certverifier/ +// StartComAndWoSignData.inc +#include "StartComAndWoSignData.inc" + +#include +#include // INT_MAX +#include +#include +#include + +#define THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(val, prefix) \ + do { \ + if (!Buffer::HasInstance(val) && !val->IsString()) { \ + return env->ThrowTypeError(prefix " must be a string or a buffer"); \ + } \ + } while (0) + +#define THROW_AND_RETURN_IF_NOT_BUFFER(val, prefix) \ + do { \ + if (!Buffer::HasInstance(val)) { \ + return env->ThrowTypeError(prefix " must be a buffer"); \ + } \ + } while (0) + +#define THROW_AND_RETURN_IF_NOT_STRING(val, prefix) \ + do { \ + if (!val->IsString()) { \ + return env->ThrowTypeError(prefix " must be a string"); \ + } \ + } while (0) + +static const char PUBLIC_KEY_PFX[] = "-----BEGIN PUBLIC KEY-----"; +static const int PUBLIC_KEY_PFX_LEN = sizeof(PUBLIC_KEY_PFX) - 1; +static const char PUBRSA_KEY_PFX[] = "-----BEGIN RSA PUBLIC KEY-----"; +static const int PUBRSA_KEY_PFX_LEN = sizeof(PUBRSA_KEY_PFX) - 1; +static const char CERTIFICATE_PFX[] = "-----BEGIN CERTIFICATE-----"; +static const int CERTIFICATE_PFX_LEN = sizeof(CERTIFICATE_PFX) - 1; + +static const int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL + | ASN1_STRFLGS_UTF8_CONVERT + | XN_FLAG_SEP_MULTILINE + | XN_FLAG_FN_SN; + +namespace node { +namespace crypto { + +using v8::AccessorSignature; +using v8::Array; +using v8::Boolean; +using v8::Context; +using v8::DEFAULT; +using v8::DontDelete; +using v8::EscapableHandleScope; +using v8::Exception; +using v8::External; +using v8::False; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::HandleScope; +using v8::Integer; +using v8::Isolate; +using v8::Local; +using v8::Maybe; +using v8::MaybeLocal; +using v8::Null; +using v8::Object; +using v8::ObjectTemplate; +using v8::Persistent; +using v8::PropertyAttribute; +using v8::PropertyCallbackInfo; +using v8::ReadOnly; +using v8::String; +using v8::Value; + + +// Subject DER of CNNIC ROOT CA and CNNIC EV ROOT CA are taken from +// https://hg.mozilla.org/mozilla-central/file/98820360ab66/security/ +// certverifier/NSSCertDBTrustDomain.cpp#l672 +// C = CN, O = CNNIC, CN = CNNIC ROOT +static const uint8_t CNNIC_ROOT_CA_SUBJECT_DATA[] = + "\x30\x32\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x0E\x30" + "\x0C\x06\x03\x55\x04\x0A\x13\x05\x43\x4E\x4E\x49\x43\x31\x13\x30\x11\x06" + "\x03\x55\x04\x03\x13\x0A\x43\x4E\x4E\x49\x43\x20\x52\x4F\x4F\x54"; +static const uint8_t* cnnic_p = CNNIC_ROOT_CA_SUBJECT_DATA; +static X509_NAME* cnnic_name = + d2i_X509_NAME(nullptr, &cnnic_p, sizeof(CNNIC_ROOT_CA_SUBJECT_DATA)-1); + +// C = CN, O = China Internet Network Information Center, CN = China +// Internet Network Information Center EV Certificates Root +static const uint8_t CNNIC_EV_ROOT_CA_SUBJECT_DATA[] = + "\x30\x81\x8A\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x32" + "\x30\x30\x06\x03\x55\x04\x0A\x0C\x29\x43\x68\x69\x6E\x61\x20\x49\x6E\x74" + "\x65\x72\x6E\x65\x74\x20\x4E\x65\x74\x77\x6F\x72\x6B\x20\x49\x6E\x66\x6F" + "\x72\x6D\x61\x74\x69\x6F\x6E\x20\x43\x65\x6E\x74\x65\x72\x31\x47\x30\x45" + "\x06\x03\x55\x04\x03\x0C\x3E\x43\x68\x69\x6E\x61\x20\x49\x6E\x74\x65\x72" + "\x6E\x65\x74\x20\x4E\x65\x74\x77\x6F\x72\x6B\x20\x49\x6E\x66\x6F\x72\x6D" + "\x61\x74\x69\x6F\x6E\x20\x43\x65\x6E\x74\x65\x72\x20\x45\x56\x20\x43\x65" + "\x72\x74\x69\x66\x69\x63\x61\x74\x65\x73\x20\x52\x6F\x6F\x74"; +static const uint8_t* cnnic_ev_p = CNNIC_EV_ROOT_CA_SUBJECT_DATA; +static X509_NAME *cnnic_ev_name = + d2i_X509_NAME(nullptr, &cnnic_ev_p, + sizeof(CNNIC_EV_ROOT_CA_SUBJECT_DATA)-1); + +static Mutex* mutexes; + +static const char* const root_certs[] = { +#include "node_root_certs.h" // NOLINT(build/include_order) +}; + +static std::string extra_root_certs_file; // NOLINT(runtime/string) + +static X509_STORE* root_cert_store; + +// Just to generate static methods +template void SSLWrap::AddMethods(Environment* env, + Local t); +template void SSLWrap::InitNPN(SecureContext* sc); +template void SSLWrap::SetSNIContext(SecureContext* sc); +template int SSLWrap::SetCACerts(SecureContext* sc); +template SSL_SESSION* SSLWrap::GetSessionCallback( + SSL* s, + const unsigned char* key, + int len, + int* copy); +template int SSLWrap::NewSessionCallback(SSL* s, + SSL_SESSION* sess); +template void SSLWrap::OnClientHello( + void* arg, + const ClientHelloParser::ClientHello& hello); + +#ifndef OPENSSL_NO_NEXTPROTONEG +template int SSLWrap::AdvertiseNextProtoCallback( + SSL* s, + const unsigned char** data, + unsigned int* len, + void* arg); +template int SSLWrap::SelectNextProtoCallback( + SSL* s, + unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg); +#endif + +#ifdef NODE__HAVE_TLSEXT_STATUS_CB +template int SSLWrap::TLSExtStatusCallback(SSL* s, void* arg); +#endif + +template void SSLWrap::DestroySSL(); +template int SSLWrap::SSLCertCallback(SSL* s, void* arg); +template void SSLWrap::WaitForCertCb(CertCb cb, void* arg); + +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation +template int SSLWrap::SelectALPNCallback( + SSL* s, + const unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg); +#endif // TLSEXT_TYPE_application_layer_protocol_negotiation + +static void crypto_threadid_cb(CRYPTO_THREADID* tid) { + static_assert(sizeof(uv_thread_t) <= sizeof(void*), + "uv_thread_t does not fit in a pointer"); + CRYPTO_THREADID_set_pointer(tid, reinterpret_cast(uv_thread_self())); +} + + +static void crypto_lock_init(void) { + mutexes = new Mutex[CRYPTO_num_locks()]; +} + + +static void crypto_lock_cb(int mode, int n, const char* file, int line) { + CHECK(!(mode & CRYPTO_LOCK) ^ !(mode & CRYPTO_UNLOCK)); + CHECK(!(mode & CRYPTO_READ) ^ !(mode & CRYPTO_WRITE)); + + auto mutex = &mutexes[n]; + if (mode & CRYPTO_LOCK) + mutex->Lock(); + else + mutex->Unlock(); +} + + +static int PasswordCallback(char *buf, int size, int rwflag, void *u) { + if (u) { + size_t buflen = static_cast(size); + size_t len = strlen(static_cast(u)); + len = len > buflen ? buflen : len; + memcpy(buf, u, len); + return len; + } + + return 0; +} + + +// This callback is used to avoid the default passphrase callback in OpenSSL +// which will typically prompt for the passphrase. The prompting is designed +// for the OpenSSL CLI, but works poorly for Node.js because it involves +// synchronous interaction with the controlling terminal, something we never +// want, and use this function to avoid it. +static int NoPasswordCallback(char *buf, int size, int rwflag, void *u) { + return 0; +} + + +void ThrowCryptoError(Environment* env, + unsigned long err, // NOLINT(runtime/int) + const char* default_message = nullptr) { + HandleScope scope(env->isolate()); + if (err != 0 || default_message == nullptr) { + char errmsg[128] = { 0 }; + ERR_error_string_n(err, errmsg, sizeof(errmsg)); + env->ThrowError(errmsg); + } else { + env->ThrowError(default_message); + } +} + + +// Ensure that OpenSSL has enough entropy (at least 256 bits) for its PRNG. +// The entropy pool starts out empty and needs to fill up before the PRNG +// can be used securely. Once the pool is filled, it never dries up again; +// its contents is stirred and reused when necessary. +// +// OpenSSL normally fills the pool automatically but not when someone starts +// generating random numbers before the pool is full: in that case OpenSSL +// keeps lowering the entropy estimate to thwart attackers trying to guess +// the initial state of the PRNG. +// +// When that happens, we will have to wait until enough entropy is available. +// That should normally never take longer than a few milliseconds. +// +// OpenSSL draws from /dev/random and /dev/urandom. While /dev/random may +// block pending "true" randomness, /dev/urandom is a CSPRNG that doesn't +// block under normal circumstances. +// +// The only time when /dev/urandom may conceivably block is right after boot, +// when the whole system is still low on entropy. That's not something we can +// do anything about. +inline void CheckEntropy() { + for (;;) { + int status = RAND_status(); + CHECK_GE(status, 0); // Cannot fail. + if (status != 0) + break; + + // Give up, RAND_poll() not supported. + if (RAND_poll() == 0) + break; + } +} + + +bool EntropySource(unsigned char* buffer, size_t length) { + // Ensure that OpenSSL's PRNG is properly seeded. + CheckEntropy(); + // RAND_bytes() can return 0 to indicate that the entropy data is not truly + // random. That's okay, it's still better than V8's stock source of entropy, + // which is /dev/urandom on UNIX platforms and the current time on Windows. + return RAND_bytes(buffer, length) != -1; +} + + +void SecureContext::Initialize(Environment* env, Local target) { + Local t = env->NewFunctionTemplate(SecureContext::New); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "SecureContext")); + + env->SetProtoMethod(t, "init", SecureContext::Init); + env->SetProtoMethod(t, "setKey", SecureContext::SetKey); + env->SetProtoMethod(t, "setCert", SecureContext::SetCert); + env->SetProtoMethod(t, "addCACert", SecureContext::AddCACert); + env->SetProtoMethod(t, "addCRL", SecureContext::AddCRL); + env->SetProtoMethod(t, "addRootCerts", SecureContext::AddRootCerts); + env->SetProtoMethod(t, "setCiphers", SecureContext::SetCiphers); + env->SetProtoMethod(t, "setECDHCurve", SecureContext::SetECDHCurve); + env->SetProtoMethod(t, "setDHParam", SecureContext::SetDHParam); + env->SetProtoMethod(t, "setOptions", SecureContext::SetOptions); + env->SetProtoMethod(t, "setSessionIdContext", + SecureContext::SetSessionIdContext); + env->SetProtoMethod(t, "setSessionTimeout", + SecureContext::SetSessionTimeout); + env->SetProtoMethod(t, "close", SecureContext::Close); + env->SetProtoMethod(t, "loadPKCS12", SecureContext::LoadPKCS12); + env->SetProtoMethod(t, "getTicketKeys", SecureContext::GetTicketKeys); + env->SetProtoMethod(t, "setTicketKeys", SecureContext::SetTicketKeys); + env->SetProtoMethod(t, + "enableTicketKeyCallback", + SecureContext::EnableTicketKeyCallback); + env->SetProtoMethod(t, "getCertificate", SecureContext::GetCertificate); + env->SetProtoMethod(t, "getIssuer", SecureContext::GetCertificate); + + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyReturnIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyReturnIndex)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyHMACIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyHMACIndex)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyAESIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyAESIndex)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyNameIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyNameIndex)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyIVIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyIVIndex)); + + t->PrototypeTemplate()->SetAccessor( + FIXED_ONE_BYTE_STRING(env->isolate(), "_external"), + CtxGetter, + nullptr, + env->as_external(), + DEFAULT, + static_cast(ReadOnly | DontDelete), + AccessorSignature::New(env->isolate(), t)); + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "SecureContext"), + t->GetFunction()); + env->set_secure_context_constructor_template(t); +} + + +void SecureContext::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + new SecureContext(env, args.This()); +} + + +void SecureContext::Init(const FunctionCallbackInfo& args) { + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); + Environment* env = sc->env(); + + const SSL_METHOD* method = SSLv23_method(); + + if (args.Length() == 1 && args[0]->IsString()) { + const node::Utf8Value sslmethod(env->isolate(), args[0]); + + // Note that SSLv2 and SSLv3 are disallowed but SSLv2_method and friends + // are still accepted. They are OpenSSL's way of saying that all known + // protocols are supported unless explicitly disabled (which we do below + // for SSLv2 and SSLv3.) + if (strcmp(*sslmethod, "SSLv2_method") == 0) { + return env->ThrowError("SSLv2 methods disabled"); + } else if (strcmp(*sslmethod, "SSLv2_server_method") == 0) { + return env->ThrowError("SSLv2 methods disabled"); + } else if (strcmp(*sslmethod, "SSLv2_client_method") == 0) { + return env->ThrowError("SSLv2 methods disabled"); + } else if (strcmp(*sslmethod, "SSLv3_method") == 0) { + return env->ThrowError("SSLv3 methods disabled"); + } else if (strcmp(*sslmethod, "SSLv3_server_method") == 0) { + return env->ThrowError("SSLv3 methods disabled"); + } else if (strcmp(*sslmethod, "SSLv3_client_method") == 0) { + return env->ThrowError("SSLv3 methods disabled"); + } else if (strcmp(*sslmethod, "SSLv23_method") == 0) { + method = SSLv23_method(); + } else if (strcmp(*sslmethod, "SSLv23_server_method") == 0) { + method = SSLv23_server_method(); + } else if (strcmp(*sslmethod, "SSLv23_client_method") == 0) { + method = SSLv23_client_method(); + } else if (strcmp(*sslmethod, "TLSv1_method") == 0) { + method = TLSv1_method(); + } else if (strcmp(*sslmethod, "TLSv1_server_method") == 0) { + method = TLSv1_server_method(); + } else if (strcmp(*sslmethod, "TLSv1_client_method") == 0) { + method = TLSv1_client_method(); + } else if (strcmp(*sslmethod, "TLSv1_1_method") == 0) { + method = TLSv1_1_method(); + } else if (strcmp(*sslmethod, "TLSv1_1_server_method") == 0) { + method = TLSv1_1_server_method(); + } else if (strcmp(*sslmethod, "TLSv1_1_client_method") == 0) { + method = TLSv1_1_client_method(); + } else if (strcmp(*sslmethod, "TLSv1_2_method") == 0) { + method = TLSv1_2_method(); + } else if (strcmp(*sslmethod, "TLSv1_2_server_method") == 0) { + method = TLSv1_2_server_method(); + } else if (strcmp(*sslmethod, "TLSv1_2_client_method") == 0) { + method = TLSv1_2_client_method(); + } else { + return env->ThrowError("Unknown method"); + } + } + + sc->ctx_ = SSL_CTX_new(method); + SSL_CTX_set_app_data(sc->ctx_, sc); + + // Disable SSLv2 in the case when method == SSLv23_method() and the + // cipher list contains SSLv2 ciphers (not the default, should be rare.) + // The bundled OpenSSL doesn't have SSLv2 support but the system OpenSSL may. + // SSLv3 is disabled because it's susceptible to downgrade attacks (POODLE.) + SSL_CTX_set_options(sc->ctx_, SSL_OP_NO_SSLv2); + SSL_CTX_set_options(sc->ctx_, SSL_OP_NO_SSLv3); + + // SSL session cache configuration + SSL_CTX_set_session_cache_mode(sc->ctx_, + SSL_SESS_CACHE_SERVER | + SSL_SESS_CACHE_NO_INTERNAL | + SSL_SESS_CACHE_NO_AUTO_CLEAR); + SSL_CTX_sess_set_get_cb(sc->ctx_, SSLWrap::GetSessionCallback); + SSL_CTX_sess_set_new_cb(sc->ctx_, SSLWrap::NewSessionCallback); +} + + +// Takes a string or buffer and loads it into a BIO. +// Caller responsible for BIO_free_all-ing the returned object. +static BIO* LoadBIO(Environment* env, Local v) { + HandleScope scope(env->isolate()); + + if (v->IsString()) { + const node::Utf8Value s(env->isolate(), v); + return NodeBIO::NewFixed(*s, s.length()); + } + + if (Buffer::HasInstance(v)) { + return NodeBIO::NewFixed(Buffer::Data(v), Buffer::Length(v)); + } + + return nullptr; +} + + +void SecureContext::SetKey(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); + + unsigned int len = args.Length(); + if (len < 1) { + return env->ThrowError("Private key argument is mandatory"); + } + + if (len > 2) { + return env->ThrowError("Only private key and pass phrase are expected"); + } + + if (len == 2) { + if (args[1]->IsUndefined() || args[1]->IsNull()) + len = 1; + else + THROW_AND_RETURN_IF_NOT_STRING(args[1], "Pass phrase"); + } + + BIO *bio = LoadBIO(env, args[0]); + if (!bio) + return; + + node::Utf8Value passphrase(env->isolate(), args[1]); + + EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, + nullptr, + PasswordCallback, + len == 1 ? nullptr : *passphrase); + + if (!key) { + BIO_free_all(bio); + unsigned long err = ERR_get_error(); // NOLINT(runtime/int) + if (!err) { + return env->ThrowError("PEM_read_bio_PrivateKey"); + } + return ThrowCryptoError(env, err); + } + + int rv = SSL_CTX_use_PrivateKey(sc->ctx_, key); + EVP_PKEY_free(key); + BIO_free_all(bio); + + if (!rv) { + unsigned long err = ERR_get_error(); // NOLINT(runtime/int) + if (!err) + return env->ThrowError("SSL_CTX_use_PrivateKey"); + return ThrowCryptoError(env, err); + } +} + + +int SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert, X509** issuer) { + int ret; + + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + X509_STORE_CTX* store_ctx = X509_STORE_CTX_new(); + + if (store_ctx == nullptr) { + return 0; + } + + ret = X509_STORE_CTX_init(store_ctx, store, nullptr, nullptr); + if (!ret) + goto end; + + ret = X509_STORE_CTX_get1_issuer(issuer, store_ctx, cert); + X509_STORE_CTX_free(store_ctx); + + end: + return ret; +} + + +int SSL_CTX_use_certificate_chain(SSL_CTX* ctx, + X509* x, + STACK_OF(X509)* extra_certs, + X509** cert, + X509** issuer) { + CHECK_EQ(*issuer, nullptr); + CHECK_EQ(*cert, nullptr); + + int ret = SSL_CTX_use_certificate(ctx, x); + + if (ret) { + // If we could set up our certificate, now proceed to + // the CA certificates. + int r; + + SSL_CTX_clear_extra_chain_certs(ctx); + + for (int i = 0; i < sk_X509_num(extra_certs); i++) { + X509* ca = sk_X509_value(extra_certs, i); + + // NOTE: Increments reference count on `ca` + r = SSL_CTX_add1_chain_cert(ctx, ca); + + if (!r) { + ret = 0; + *issuer = nullptr; + goto end; + } + // Note that we must not free r if it was successfully + // added to the chain (while we must free the main + // certificate, since its reference count is increased + // by SSL_CTX_use_certificate). + + // Find issuer + if (*issuer != nullptr || X509_check_issued(ca, x) != X509_V_OK) + continue; + + *issuer = ca; + } + } + + // Try getting issuer from a cert store + if (ret) { + if (*issuer == nullptr) { + ret = SSL_CTX_get_issuer(ctx, x, issuer); + ret = ret < 0 ? 0 : 1; + // NOTE: get_cert_store doesn't increment reference count, + // no need to free `store` + } else { + // Increment issuer reference count + *issuer = X509_dup(*issuer); + if (*issuer == nullptr) { + ret = 0; + goto end; + } + } + } + + end: + if (ret && x != nullptr) { + *cert = X509_dup(x); + if (*cert == nullptr) + ret = 0; + } + return ret; +} + + +// Read a file that contains our certificate in "PEM" format, +// possibly followed by a sequence of CA certificates that should be +// sent to the peer in the Certificate message. +// +// Taken from OpenSSL - edited for style. +int SSL_CTX_use_certificate_chain(SSL_CTX* ctx, + BIO* in, + X509** cert, + X509** issuer) { + X509* x = nullptr; + + // Just to ensure that `ERR_peek_last_error` below will return only errors + // that we are interested in + ERR_clear_error(); + + x = PEM_read_bio_X509_AUX(in, nullptr, NoPasswordCallback, nullptr); + + if (x == nullptr) { + return 0; + } + + X509* extra = nullptr; + int ret = 0; + unsigned long err = 0; // NOLINT(runtime/int) + + // Read extra certs + STACK_OF(X509)* extra_certs = sk_X509_new_null(); + if (extra_certs == nullptr) { + goto done; + } + + while ((extra = PEM_read_bio_X509(in, + nullptr, + NoPasswordCallback, + nullptr))) { + if (sk_X509_push(extra_certs, extra)) + continue; + + // Failure, free all certs + goto done; + } + extra = nullptr; + + // When the while loop ends, it's usually just EOF. + err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PEM && + ERR_GET_REASON(err) == PEM_R_NO_START_LINE) { + ERR_clear_error(); + } else { + // some real error + goto done; + } + + ret = SSL_CTX_use_certificate_chain(ctx, x, extra_certs, cert, issuer); + if (!ret) + goto done; + + done: + if (extra_certs != nullptr) + sk_X509_pop_free(extra_certs, X509_free); + if (extra != nullptr) + X509_free(extra); + if (x != nullptr) + X509_free(x); + + return ret; +} + + +void SecureContext::SetCert(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); + + if (args.Length() != 1) { + return env->ThrowTypeError("Certificate argument is mandatory"); + } + + BIO* bio = LoadBIO(env, args[0]); + if (!bio) + return; + + // Free previous certs + if (sc->issuer_ != nullptr) { + X509_free(sc->issuer_); + sc->issuer_ = nullptr; + } + if (sc->cert_ != nullptr) { + X509_free(sc->cert_); + sc->cert_ = nullptr; + } + + int rv = SSL_CTX_use_certificate_chain(sc->ctx_, + bio, + &sc->cert_, + &sc->issuer_); + + BIO_free_all(bio); + + if (!rv) { + unsigned long err = ERR_get_error(); // NOLINT(runtime/int) + if (!err) { + return env->ThrowError("SSL_CTX_use_certificate_chain"); + } + return ThrowCryptoError(env, err); + } +} + + +#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(OPENSSL_IS_BORINGSSL) +// This section contains OpenSSL 1.1.0 functions reimplemented for OpenSSL +// 1.0.2 so that the following code can be written without lots of #if lines. + +static int X509_STORE_up_ref(X509_STORE* store) { + CRYPTO_add(&store->references, 1, CRYPTO_LOCK_X509_STORE); + return 1; +} + +static int X509_up_ref(X509* cert) { + CRYPTO_add(&cert->references, 1, CRYPTO_LOCK_X509); + return 1; +} +#endif // OPENSSL_VERSION_NUMBER < 0x10100000L && !OPENSSL_IS_BORINGSSL + + +static X509_STORE* NewRootCertStore() { + static std::vector root_certs_vector; + if (root_certs_vector.empty()) { + for (size_t i = 0; i < arraysize(root_certs); i++) { + BIO* bp = NodeBIO::NewFixed(root_certs[i], strlen(root_certs[i])); + X509 *x509 = PEM_read_bio_X509(bp, nullptr, NoPasswordCallback, nullptr); + BIO_free(bp); + + // Parse errors from the built-in roots are fatal. + CHECK_NE(x509, nullptr); + + root_certs_vector.push_back(x509); + } + } + + X509_STORE* store = X509_STORE_new(); + if (ssl_openssl_cert_store) { + X509_STORE_set_default_paths(store); + } else { + for (X509 *cert : root_certs_vector) { + X509_up_ref(cert); + X509_STORE_add_cert(store, cert); + } + } + + return store; +} + + +void SecureContext::AddCACert(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence compiler warning. + + if (args.Length() != 1) { + return env->ThrowTypeError("CA certificate argument is mandatory"); + } + + BIO* bio = LoadBIO(env, args[0]); + if (!bio) { + return; + } + + X509_STORE* cert_store = SSL_CTX_get_cert_store(sc->ctx_); + while (X509* x509 = + PEM_read_bio_X509(bio, nullptr, NoPasswordCallback, nullptr)) { + if (cert_store == root_cert_store) { + cert_store = NewRootCertStore(); + SSL_CTX_set_cert_store(sc->ctx_, cert_store); + } + X509_STORE_add_cert(cert_store, x509); + SSL_CTX_add_client_CA(sc->ctx_, x509); + X509_free(x509); + } + + BIO_free_all(bio); +} + + +void SecureContext::AddCRL(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); + + if (args.Length() != 1) { + return env->ThrowTypeError("CRL argument is mandatory"); + } + + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence compiler warning. + + BIO *bio = LoadBIO(env, args[0]); + if (!bio) + return; + + X509_CRL* crl = + PEM_read_bio_X509_CRL(bio, nullptr, NoPasswordCallback, nullptr); + + if (crl == nullptr) { + BIO_free_all(bio); + return env->ThrowError("Failed to parse CRL"); + } + + X509_STORE* cert_store = SSL_CTX_get_cert_store(sc->ctx_); + if (cert_store == root_cert_store) { + cert_store = NewRootCertStore(); + SSL_CTX_set_cert_store(sc->ctx_, cert_store); + } + + X509_STORE_add_crl(cert_store, crl); + X509_STORE_set_flags(cert_store, + X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + + BIO_free_all(bio); + X509_CRL_free(crl); +} + + +void UseExtraCaCerts(const std::string& file) { + extra_root_certs_file = file; +} + + +static unsigned long AddCertsFromFile( // NOLINT(runtime/int) + X509_STORE* store, + const char* file) { + ERR_clear_error(); + MarkPopErrorOnReturn mark_pop_error_on_return; + + BIO* bio = BIO_new_file(file, "r"); + if (!bio) { + return ERR_get_error(); + } + + while (X509* x509 = + PEM_read_bio_X509(bio, nullptr, NoPasswordCallback, nullptr)) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + } + BIO_free_all(bio); + + unsigned long err = ERR_peek_error(); // NOLINT(runtime/int) + // Ignore error if its EOF/no start line found. + if (ERR_GET_LIB(err) == ERR_LIB_PEM && + ERR_GET_REASON(err) == PEM_R_NO_START_LINE) { + return 0; + } + + return err; +} + +void SecureContext::AddRootCerts(const FunctionCallbackInfo& args) { + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence compiler warning. + + if (!root_cert_store) { + root_cert_store = NewRootCertStore(); + + if (!extra_root_certs_file.empty()) { + unsigned long err = AddCertsFromFile( // NOLINT(runtime/int) + root_cert_store, + extra_root_certs_file.c_str()); + if (err) { + ProcessEmitWarning(sc->env(), + "Ignoring extra certs from `%s`, load failed: %s\n", + extra_root_certs_file.c_str(), + ERR_error_string(err, nullptr)); + } + } + } + + // Increment reference count so global store is not deleted along with CTX. + X509_STORE_up_ref(root_cert_store); + SSL_CTX_set_cert_store(sc->ctx_, root_cert_store); +} + + +void SecureContext::SetCiphers(const FunctionCallbackInfo& args) { + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); + Environment* env = sc->env(); + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence compiler warning. + + if (args.Length() != 1) { + return env->ThrowTypeError("Ciphers argument is mandatory"); + } + + THROW_AND_RETURN_IF_NOT_STRING(args[0], "Ciphers"); + + const node::Utf8Value ciphers(args.GetIsolate(), args[0]); + SSL_CTX_set_cipher_list(sc->ctx_, *ciphers); +} + + +void SecureContext::SetECDHCurve(const FunctionCallbackInfo& args) { + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); + Environment* env = sc->env(); + + if (args.Length() != 1) + return env->ThrowTypeError("ECDH curve name argument is mandatory"); + + THROW_AND_RETURN_IF_NOT_STRING(args[0], "ECDH curve name"); + + node::Utf8Value curve(env->isolate(), args[0]); + + int nid = OBJ_sn2nid(*curve); + + if (nid == NID_undef) + return env->ThrowTypeError("First argument should be a valid curve name"); + + EC_KEY* ecdh = EC_KEY_new_by_curve_name(nid); + + if (ecdh == nullptr) + return env->ThrowTypeError("First argument should be a valid curve name"); + + SSL_CTX_set_options(sc->ctx_, SSL_OP_SINGLE_ECDH_USE); + SSL_CTX_set_tmp_ecdh(sc->ctx_, ecdh); + + EC_KEY_free(ecdh); +} + + +void SecureContext::SetDHParam(const FunctionCallbackInfo& args) { + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.This()); + Environment* env = sc->env(); + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence compiler warning. + + // Auto DH is not supported in openssl 1.0.1, so dhparam needs + // to be specified explicitly + if (args.Length() != 1) + return env->ThrowTypeError("DH argument is mandatory"); + + // Invalid dhparam is silently discarded and DHE is no longer used. + BIO* bio = LoadBIO(env, args[0]); + if (!bio) + return; + + DH* dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); + BIO_free_all(bio); + + if (dh == nullptr) + return; + + const BIGNUM *p; + DH_get0_pqg(dh, &p, NULL, NULL); + const int size = BN_num_bits(p); + if (size < 1024) { + return env->ThrowError("DH parameter is less than 1024 bits"); + } else if (size < 2048) { + args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING( + env->isolate(), "DH parameter is less than 2048 bits")); + } + + SSL_CTX_set_options(sc->ctx_, SSL_OP_SINGLE_DH_USE); + int r = SSL_CTX_set_tmp_dh(sc->ctx_, dh); + DH_free(dh); + + if (!r) + return env->ThrowTypeError("Error setting temp DH parameter"); +} + + +void SecureContext::SetOptions(const FunctionCallbackInfo& args) { + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); + + if (args.Length() != 1 || !args[0]->IntegerValue()) { + return sc->env()->ThrowTypeError("Options must be an integer value"); + } + + SSL_CTX_set_options( + sc->ctx_, + static_cast(args[0]->IntegerValue())); // NOLINT(runtime/int) +} + + +void SecureContext::SetSessionIdContext( + const FunctionCallbackInfo& args) { + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); + Environment* env = sc->env(); + + if (args.Length() != 1) { + return env->ThrowTypeError("Session ID context argument is mandatory"); + } + + THROW_AND_RETURN_IF_NOT_STRING(args[0], "Session ID context"); + + const node::Utf8Value sessionIdContext(args.GetIsolate(), args[0]); + const unsigned char* sid_ctx = + reinterpret_cast(*sessionIdContext); + unsigned int sid_ctx_len = sessionIdContext.length(); + + int r = SSL_CTX_set_session_id_context(sc->ctx_, sid_ctx, sid_ctx_len); + if (r == 1) + return; + + BIO* bio; + BUF_MEM* mem; + Local message; + + bio = BIO_new(BIO_s_mem()); + if (bio == nullptr) { + message = FIXED_ONE_BYTE_STRING(args.GetIsolate(), + "SSL_CTX_set_session_id_context error"); + } else { + ERR_print_errors(bio); + BIO_get_mem_ptr(bio, &mem); + message = OneByteString(args.GetIsolate(), mem->data, mem->length); + BIO_free_all(bio); + } + + args.GetIsolate()->ThrowException(Exception::TypeError(message)); +} + + +void SecureContext::SetSessionTimeout(const FunctionCallbackInfo& args) { + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); + + if (args.Length() != 1 || !args[0]->IsInt32()) { + return sc->env()->ThrowTypeError( + "Session timeout must be a 32-bit integer"); + } + + int32_t sessionTimeout = args[0]->Int32Value(); + SSL_CTX_set_timeout(sc->ctx_, sessionTimeout); +} + + +void SecureContext::Close(const FunctionCallbackInfo& args) { + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); + sc->FreeCTXMem(); +} + + +// Takes .pfx or .p12 and password in string or buffer format +void SecureContext::LoadPKCS12(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + BIO* in = nullptr; + PKCS12* p12 = nullptr; + EVP_PKEY* pkey = nullptr; + X509* cert = nullptr; + STACK_OF(X509)* extra_certs = nullptr; + char* pass = nullptr; + bool ret = false; + + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder()); + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence compiler warning. + + if (args.Length() < 1) { + return env->ThrowTypeError("PFX certificate argument is mandatory"); + } + + in = LoadBIO(env, args[0]); + if (in == nullptr) { + return env->ThrowError("Unable to load BIO"); + } + + if (args.Length() >= 2) { + THROW_AND_RETURN_IF_NOT_BUFFER(args[1], "Pass phrase"); + size_t passlen = Buffer::Length(args[1]); + pass = new char[passlen + 1]; + memcpy(pass, Buffer::Data(args[1]), passlen); + pass[passlen] = '\0'; + } + + // Free previous certs + if (sc->issuer_ != nullptr) { + X509_free(sc->issuer_); + sc->issuer_ = nullptr; + } + if (sc->cert_ != nullptr) { + X509_free(sc->cert_); + sc->cert_ = nullptr; + } + + X509_STORE* cert_store = SSL_CTX_get_cert_store(sc->ctx_); + + if (d2i_PKCS12_bio(in, &p12) && + PKCS12_parse(p12, pass, &pkey, &cert, &extra_certs) && + SSL_CTX_use_certificate_chain(sc->ctx_, + cert, + extra_certs, + &sc->cert_, + &sc->issuer_) && + SSL_CTX_use_PrivateKey(sc->ctx_, pkey)) { + // Add CA certs too + for (int i = 0; i < sk_X509_num(extra_certs); i++) { + X509* ca = sk_X509_value(extra_certs, i); + + if (cert_store == root_cert_store) { + cert_store = NewRootCertStore(); + SSL_CTX_set_cert_store(sc->ctx_, cert_store); + } + X509_STORE_add_cert(cert_store, ca); + SSL_CTX_add_client_CA(sc->ctx_, ca); + } + ret = true; + } + + if (pkey != nullptr) + EVP_PKEY_free(pkey); + if (cert != nullptr) + X509_free(cert); + if (extra_certs != nullptr) + sk_X509_pop_free(extra_certs, X509_free); + + PKCS12_free(p12); + BIO_free_all(in); + delete[] pass; + + if (!ret) { + unsigned long err = ERR_get_error(); // NOLINT(runtime/int) + const char* str = ERR_reason_error_string(err); + return env->ThrowError(str); + } +} + + +void SecureContext::GetTicketKeys(const FunctionCallbackInfo& args) { +#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys) + + SecureContext* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + long length = SSL_CTX_get_tlsext_ticket_keys(wrap->ctx_, NULL, 0); + Local buff = Buffer::New(wrap->env(), length).ToLocalChecked(); + if (SSL_CTX_get_tlsext_ticket_keys(wrap->ctx_, + Buffer::Data(buff), + Buffer::Length(buff)) != 1) { + return wrap->env()->ThrowError("Failed to fetch tls ticket keys"); + } + + args.GetReturnValue().Set(buff); +#endif // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys) +} + + +void SecureContext::SetTicketKeys(const FunctionCallbackInfo& args) { +#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys) + SecureContext* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + Environment* env = wrap->env(); + + if (args.Length() < 1) { + return env->ThrowTypeError("Ticket keys argument is mandatory"); + } + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Ticket keys"); + + long length = SSL_CTX_get_tlsext_ticket_keys(wrap->ctx_, NULL, 0); + if (Buffer::Length(args[0]) != (size_t)length) { + return env->ThrowTypeError("Ticket keys length incorrect"); + } + + if (SSL_CTX_set_tlsext_ticket_keys(wrap->ctx_, + Buffer::Data(args[0]), + Buffer::Length(args[0])) != 1) { + return env->ThrowError("Failed to fetch tls ticket keys"); + } + + args.GetReturnValue().Set(true); +#endif // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys) +} + + +// Currently, EnableTicketKeyCallback and TicketKeyCallback are only present for +// the regression test in test/parallel/test-https-resume-after-renew.js. +void SecureContext::EnableTicketKeyCallback( + const FunctionCallbackInfo& args) { + SecureContext* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + SSL_CTX_set_tlsext_ticket_key_cb(wrap->ctx_, TicketKeyCallback); +} + + +int SecureContext::TicketKeyCallback(SSL* ssl, + unsigned char* name, + unsigned char* iv, + EVP_CIPHER_CTX* ectx, + HMAC_CTX* hctx, + int enc) { + static const int kTicketPartSize = 16; + + SecureContext* sc = static_cast( + SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl))); + + Environment* env = sc->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local argv[] = { + Buffer::Copy(env, + reinterpret_cast(name), + kTicketPartSize).ToLocalChecked(), + Buffer::Copy(env, + reinterpret_cast(iv), + kTicketPartSize).ToLocalChecked(), + Boolean::New(env->isolate(), enc != 0) + }; + + Local ret = node::MakeCallback(env->isolate(), + sc->object(), + env->ticketkeycallback_string(), + arraysize(argv), + argv, + 0, 0).ToLocalChecked(); + Local arr = ret.As(); + + int r = arr->Get(kTicketKeyReturnIndex)->Int32Value(); + if (r < 0) + return r; + + Local hmac = arr->Get(kTicketKeyHMACIndex); + Local aes = arr->Get(kTicketKeyAESIndex); + if (Buffer::Length(aes) != kTicketPartSize) + return -1; + + if (enc) { + Local name_val = arr->Get(kTicketKeyNameIndex); + Local iv_val = arr->Get(kTicketKeyIVIndex); + + if (Buffer::Length(name_val) != kTicketPartSize || + Buffer::Length(iv_val) != kTicketPartSize) { + return -1; + } + + memcpy(name, Buffer::Data(name_val), kTicketPartSize); + memcpy(iv, Buffer::Data(iv_val), kTicketPartSize); + } + + HMAC_Init_ex(hctx, + Buffer::Data(hmac), + Buffer::Length(hmac), + EVP_sha256(), + nullptr); + + const unsigned char* aes_key = + reinterpret_cast(Buffer::Data(aes)); + if (enc) { + EVP_EncryptInit_ex(ectx, + EVP_aes_128_cbc(), + nullptr, + aes_key, + iv); + } else { + EVP_DecryptInit_ex(ectx, + EVP_aes_128_cbc(), + nullptr, + aes_key, + iv); + } + + return r; +} + + + + +void SecureContext::CtxGetter(Local property, + const PropertyCallbackInfo& info) { + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, info.This()); + Local ext = External::New(info.GetIsolate(), sc->ctx_); + info.GetReturnValue().Set(ext); +} + + +template +void SecureContext::GetCertificate(const FunctionCallbackInfo& args) { + SecureContext* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + Environment* env = wrap->env(); + X509* cert; + + if (primary) + cert = wrap->cert_; + else + cert = wrap->issuer_; + if (cert == nullptr) + return args.GetReturnValue().Set(Null(env->isolate())); + + int size = i2d_X509(cert, nullptr); + Local buff = Buffer::New(env, size).ToLocalChecked(); + unsigned char* serialized = reinterpret_cast( + Buffer::Data(buff)); + i2d_X509(cert, &serialized); + + args.GetReturnValue().Set(buff); +} + + +template +void SSLWrap::AddMethods(Environment* env, Local t) { + HandleScope scope(env->isolate()); + + env->SetProtoMethod(t, "getPeerCertificate", GetPeerCertificate); + env->SetProtoMethod(t, "getSession", GetSession); + env->SetProtoMethod(t, "setSession", SetSession); + env->SetProtoMethod(t, "loadSession", LoadSession); + env->SetProtoMethod(t, "isSessionReused", IsSessionReused); + env->SetProtoMethod(t, "isInitFinished", IsInitFinished); + env->SetProtoMethod(t, "verifyError", VerifyError); + env->SetProtoMethod(t, "getCurrentCipher", GetCurrentCipher); + env->SetProtoMethod(t, "endParser", EndParser); + env->SetProtoMethod(t, "certCbDone", CertCbDone); + env->SetProtoMethod(t, "renegotiate", Renegotiate); + env->SetProtoMethod(t, "shutdownSSL", Shutdown); + env->SetProtoMethod(t, "getTLSTicket", GetTLSTicket); + env->SetProtoMethod(t, "newSessionDone", NewSessionDone); + env->SetProtoMethod(t, "setOCSPResponse", SetOCSPResponse); + env->SetProtoMethod(t, "requestOCSP", RequestOCSP); + env->SetProtoMethod(t, "getEphemeralKeyInfo", GetEphemeralKeyInfo); + env->SetProtoMethod(t, "getProtocol", GetProtocol); + +#ifdef SSL_set_max_send_fragment + env->SetProtoMethod(t, "setMaxSendFragment", SetMaxSendFragment); +#endif // SSL_set_max_send_fragment + +#ifndef OPENSSL_NO_NEXTPROTONEG + env->SetProtoMethod(t, "getNegotiatedProtocol", GetNegotiatedProto); +#endif // OPENSSL_NO_NEXTPROTONEG + +#ifndef OPENSSL_NO_NEXTPROTONEG + env->SetProtoMethod(t, "setNPNProtocols", SetNPNProtocols); +#endif + + env->SetProtoMethod(t, "getALPNNegotiatedProtocol", GetALPNNegotiatedProto); + env->SetProtoMethod(t, "setALPNProtocols", SetALPNProtocols); + + t->PrototypeTemplate()->SetAccessor( + FIXED_ONE_BYTE_STRING(env->isolate(), "_external"), + SSLGetter, + nullptr, + env->as_external(), + DEFAULT, + static_cast(ReadOnly | DontDelete), + AccessorSignature::New(env->isolate(), t)); +} + + +template +void SSLWrap::InitNPN(SecureContext* sc) { +#ifndef OPENSSL_NO_NEXTPROTONEG + // Server should advertise NPN protocols + SSL_CTX_set_next_protos_advertised_cb(sc->ctx_, + AdvertiseNextProtoCallback, + nullptr); + // Client should select protocol from list of advertised + // If server supports NPN + SSL_CTX_set_next_proto_select_cb(sc->ctx_, SelectNextProtoCallback, nullptr); +#endif // OPENSSL_NO_NEXTPROTONEG + +#ifdef NODE__HAVE_TLSEXT_STATUS_CB + // OCSP stapling + SSL_CTX_set_tlsext_status_cb(sc->ctx_, TLSExtStatusCallback); + SSL_CTX_set_tlsext_status_arg(sc->ctx_, nullptr); +#endif // NODE__HAVE_TLSEXT_STATUS_CB +} + + +template +SSL_SESSION* SSLWrap::GetSessionCallback(SSL* s, + const unsigned char* key, + int len, + int* copy) { + Base* w = static_cast(SSL_get_app_data(s)); + + *copy = 0; + SSL_SESSION* sess = w->next_sess_; + w->next_sess_ = nullptr; + + return sess; +} + + +template +int SSLWrap::NewSessionCallback(SSL* s, SSL_SESSION* sess) { + Base* w = static_cast(SSL_get_app_data(s)); + Environment* env = w->ssl_env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + if (!w->session_callbacks_) + return 0; + + // Check if session is small enough to be stored + int size = i2d_SSL_SESSION(sess, nullptr); + if (size > SecureContext::kMaxSessionSize) + return 0; + + // Serialize session + Local buff = Buffer::New(env, size).ToLocalChecked(); + unsigned char* serialized = reinterpret_cast( + Buffer::Data(buff)); + memset(serialized, 0, size); + i2d_SSL_SESSION(sess, &serialized); + + unsigned int session_id_length; + const unsigned char *session_id = SSL_SESSION_get_id(sess, &session_id_length); + + Local session = Buffer::Copy( + env, + reinterpret_cast(session_id), + session_id_length).ToLocalChecked(); + Local argv[] = { session, buff }; + w->new_session_wait_ = true; + w->MakeCallback(env->onnewsession_string(), arraysize(argv), argv); + + return 0; +} + + +template +void SSLWrap::OnClientHello(void* arg, + const ClientHelloParser::ClientHello& hello) { + Base* w = static_cast(arg); + Environment* env = w->ssl_env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local hello_obj = Object::New(env->isolate()); + Local buff = Buffer::Copy( + env, + reinterpret_cast(hello.session_id()), + hello.session_size()).ToLocalChecked(); + hello_obj->Set(env->session_id_string(), buff); + if (hello.servername() == nullptr) { + hello_obj->Set(env->servername_string(), String::Empty(env->isolate())); + } else { + Local servername = OneByteString(env->isolate(), + hello.servername(), + hello.servername_size()); + hello_obj->Set(env->servername_string(), servername); + } + hello_obj->Set(env->tls_ticket_string(), + Boolean::New(env->isolate(), hello.has_ticket())); + hello_obj->Set(env->ocsp_request_string(), + Boolean::New(env->isolate(), hello.ocsp_request())); + + Local argv[] = { hello_obj }; + w->MakeCallback(env->onclienthello_string(), arraysize(argv), argv); +} + + +static bool SafeX509ExtPrint(BIO* out, X509_EXTENSION* ext) { + const X509V3_EXT_METHOD* method = X509V3_EXT_get(ext); + + if (method != X509V3_EXT_get_nid(NID_subject_alt_name)) + return false; + + ASN1_OCTET_STRING* data = X509_EXTENSION_get_data(ext); + // TODO: (danbev) What is p used for? + const unsigned char* p = data->data; + GENERAL_NAMES* names = reinterpret_cast(ASN1_item_d2i( + NULL, + &p, + data->length, + ASN1_ITEM_ptr(method->it))); + if (names == NULL) + return false; + + for (int i = 0; i < sk_GENERAL_NAME_num(names); i++) { + GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i); + + if (i != 0) + BIO_write(out, ", ", 2); + + if (gen->type == GEN_DNS) { + ASN1_IA5STRING* name = gen->d.dNSName; + + BIO_write(out, "DNS:", 4); + BIO_write(out, name->data, name->length); + } else { + STACK_OF(CONF_VALUE)* nval = i2v_GENERAL_NAME( + const_cast(method), gen, NULL); + if (nval == NULL) + return false; + X509V3_EXT_val_prn(out, nval, 0, 0); + sk_CONF_VALUE_pop_free(nval, X509V3_conf_free); + } + } + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + + return true; +} + + +static Local X509ToObject(Environment* env, X509* cert) { + EscapableHandleScope scope(env->isolate()); + + Local info = Object::New(env->isolate()); + + BIO* bio = BIO_new(BIO_s_mem()); + BUF_MEM* mem; + if (X509_NAME_print_ex(bio, + X509_get_subject_name(cert), + 0, + X509_NAME_FLAGS) > 0) { + BIO_get_mem_ptr(bio, &mem); + info->Set(env->subject_string(), + String::NewFromUtf8(env->isolate(), mem->data, + String::kNormalString, mem->length)); + } + (void) BIO_reset(bio); + + X509_NAME* issuer_name = X509_get_issuer_name(cert); + if (X509_NAME_print_ex(bio, issuer_name, 0, X509_NAME_FLAGS) > 0) { + BIO_get_mem_ptr(bio, &mem); + info->Set(env->issuer_string(), + String::NewFromUtf8(env->isolate(), mem->data, + String::kNormalString, mem->length)); + } + (void) BIO_reset(bio); + + int nids[] = { NID_subject_alt_name, NID_info_access }; + Local keys[] = { env->subjectaltname_string(), + env->infoaccess_string() }; + CHECK_EQ(arraysize(nids), arraysize(keys)); + for (size_t i = 0; i < arraysize(nids); i++) { + int index = X509_get_ext_by_NID(cert, nids[i], -1); + if (index < 0) + continue; + + X509_EXTENSION* ext; + int rv; + + ext = X509_get_ext(cert, index); + CHECK_NE(ext, nullptr); + + if (!SafeX509ExtPrint(bio, ext)) { + rv = X509V3_EXT_print(bio, ext, 0, 0); + CHECK_EQ(rv, 1); + } + + BIO_get_mem_ptr(bio, &mem); + info->Set(keys[i], + String::NewFromUtf8(env->isolate(), mem->data, + String::kNormalString, mem->length)); + + (void) BIO_reset(bio); + } + + EVP_PKEY* pkey = X509_get_pubkey(cert); + RSA* rsa = nullptr; + if (pkey != nullptr) + rsa = EVP_PKEY_get1_RSA(pkey); + + if (rsa != nullptr) { + const BIGNUM* n,* e; + RSA_get0_key(rsa, &n, &e, NULL); + BN_print(bio, n); + BIO_get_mem_ptr(bio, &mem); + info->Set(env->modulus_string(), + String::NewFromUtf8(env->isolate(), mem->data, + String::kNormalString, mem->length)); + (void) BIO_reset(bio); + + uint64_t exponent_word = static_cast(BN_get_word(e)); + uint32_t lo = static_cast(exponent_word); + uint32_t hi = static_cast(exponent_word >> 32); + if (hi == 0) { + BIO_printf(bio, "0x%x", lo); + } else { + BIO_printf(bio, "0x%x%08x", hi, lo); + } + BIO_get_mem_ptr(bio, &mem); + info->Set(env->exponent_string(), + String::NewFromUtf8(env->isolate(), mem->data, + String::kNormalString, mem->length)); + (void) BIO_reset(bio); + } + + if (pkey != nullptr) { + EVP_PKEY_free(pkey); + pkey = nullptr; + } + if (rsa != nullptr) { + RSA_free(rsa); + rsa = nullptr; + } + + ASN1_TIME_print(bio, X509_get_notBefore(cert)); + BIO_get_mem_ptr(bio, &mem); + info->Set(env->valid_from_string(), + String::NewFromUtf8(env->isolate(), mem->data, + String::kNormalString, mem->length)); + (void) BIO_reset(bio); + + ASN1_TIME_print(bio, X509_get_notAfter(cert)); + BIO_get_mem_ptr(bio, &mem); + info->Set(env->valid_to_string(), + String::NewFromUtf8(env->isolate(), mem->data, + String::kNormalString, mem->length)); + BIO_free_all(bio); + + unsigned int md_size, i; + unsigned char md[EVP_MAX_MD_SIZE]; + if (X509_digest(cert, EVP_sha1(), md, &md_size)) { + const char hex[] = "0123456789ABCDEF"; + char fingerprint[EVP_MAX_MD_SIZE * 3]; + + // TODO(indutny): Unify it with buffer's code + for (i = 0; i < md_size; i++) { + fingerprint[3*i] = hex[(md[i] & 0xf0) >> 4]; + fingerprint[(3*i)+1] = hex[(md[i] & 0x0f)]; + fingerprint[(3*i)+2] = ':'; + } + + if (md_size > 0) { + fingerprint[(3*(md_size-1))+2] = '\0'; + } else { + fingerprint[0] = '\0'; + } + + info->Set(env->fingerprint_string(), + OneByteString(env->isolate(), fingerprint)); + } + + STACK_OF(ASN1_OBJECT)* eku = static_cast( + X509_get_ext_d2i(cert, NID_ext_key_usage, nullptr, nullptr)); + if (eku != nullptr) { + Local ext_key_usage = Array::New(env->isolate()); + char buf[256]; + + int j = 0; + for (int i = 0; i < sk_ASN1_OBJECT_num(eku); i++) { + if (OBJ_obj2txt(buf, sizeof(buf), sk_ASN1_OBJECT_value(eku, i), 1) >= 0) + ext_key_usage->Set(j++, OneByteString(env->isolate(), buf)); + } + + sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free); + info->Set(env->ext_key_usage_string(), ext_key_usage); + } + + if (ASN1_INTEGER* serial_number = X509_get_serialNumber(cert)) { + if (BIGNUM* bn = ASN1_INTEGER_to_BN(serial_number, nullptr)) { + if (char* buf = BN_bn2hex(bn)) { + info->Set(env->serial_number_string(), + OneByteString(env->isolate(), buf)); + OPENSSL_free(buf); + } + BN_free(bn); + } + } + + // Raw DER certificate + int size = i2d_X509(cert, nullptr); + Local buff = Buffer::New(env, size).ToLocalChecked(); + unsigned char* serialized = reinterpret_cast( + Buffer::Data(buff)); + i2d_X509(cert, &serialized); + info->Set(env->raw_string(), buff); + + return scope.Escape(info); +} + + +// TODO(indutny): Split it into multiple smaller functions +template +void SSLWrap::GetPeerCertificate( + const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = w->ssl_env(); + + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence unused variable warning. + + Local result; + Local info; + + // NOTE: This is because of the odd OpenSSL behavior. On client `cert_chain` + // contains the `peer_certificate`, but on server it doesn't + X509* cert = w->is_server() ? SSL_get_peer_certificate(w->ssl_) : nullptr; + STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(w->ssl_); + STACK_OF(X509)* peer_certs = nullptr; + if (cert == nullptr && ssl_certs == nullptr) + goto done; + + if (cert == nullptr && sk_X509_num(ssl_certs) == 0) + goto done; + + // Short result requested + if (args.Length() < 1 || !args[0]->IsTrue()) { + result = X509ToObject(env, + cert == nullptr ? sk_X509_value(ssl_certs, 0) : cert); + goto done; + } + + // Clone `ssl_certs`, because we are going to destruct it + peer_certs = sk_X509_new(nullptr); + if (cert != nullptr) + sk_X509_push(peer_certs, cert); + for (int i = 0; i < sk_X509_num(ssl_certs); i++) { + cert = X509_dup(sk_X509_value(ssl_certs, i)); + if (cert == nullptr) + goto done; + if (!sk_X509_push(peer_certs, cert)) + goto done; + } + + // First and main certificate + cert = sk_X509_value(peer_certs, 0); + result = X509ToObject(env, cert); + info = result; + + // Put issuer inside the object + cert = sk_X509_delete(peer_certs, 0); + while (sk_X509_num(peer_certs) > 0) { + int i; + for (i = 0; i < sk_X509_num(peer_certs); i++) { + X509* ca = sk_X509_value(peer_certs, i); + if (X509_check_issued(ca, cert) != X509_V_OK) + continue; + + Local ca_info = X509ToObject(env, ca); + info->Set(env->issuercert_string(), ca_info); + info = ca_info; + + // NOTE: Intentionally freeing cert that is not used anymore + X509_free(cert); + + // Delete cert and continue aggregating issuers + cert = sk_X509_delete(peer_certs, i); + break; + } + + // Issuer not found, break out of the loop + if (i == sk_X509_num(peer_certs)) + break; + } + + // Last certificate should be self-signed + while (X509_check_issued(cert, cert) != X509_V_OK) { + X509* ca; + if (SSL_CTX_get_issuer(SSL_get_SSL_CTX(w->ssl_), cert, &ca) <= 0) + break; + + Local ca_info = X509ToObject(env, ca); + info->Set(env->issuercert_string(), ca_info); + info = ca_info; + + // NOTE: Intentionally freeing cert that is not used anymore + X509_free(cert); + + // Delete cert and continue aggregating issuers + cert = ca; + } + + // Self-issued certificate + if (X509_check_issued(cert, cert) == X509_V_OK) + info->Set(env->issuercert_string(), info); + + CHECK_NE(cert, nullptr); + + done: + if (cert != nullptr) + X509_free(cert); + if (peer_certs != nullptr) + sk_X509_pop_free(peer_certs, X509_free); + if (result.IsEmpty()) + result = Object::New(env->isolate()); + args.GetReturnValue().Set(result); +} + + +template +void SSLWrap::GetSession(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + SSL_SESSION* sess = SSL_get_session(w->ssl_); + if (sess == nullptr) + return; + + int slen = i2d_SSL_SESSION(sess, nullptr); + CHECK_GT(slen, 0); + + char* sbuf = new char[slen]; + unsigned char* p = reinterpret_cast(sbuf); + i2d_SSL_SESSION(sess, &p); + args.GetReturnValue().Set(Encode(env->isolate(), sbuf, slen, BUFFER)); + delete[] sbuf; +} + + +template +void SSLWrap::SetSession(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + if (args.Length() < 1) { + return env->ThrowError("Session argument is mandatory"); + } + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Session"); + size_t slen = Buffer::Length(args[0]); + char* sbuf = new char[slen]; + memcpy(sbuf, Buffer::Data(args[0]), slen); + + const unsigned char* p = reinterpret_cast(sbuf); + SSL_SESSION* sess = d2i_SSL_SESSION(nullptr, &p, slen); + + delete[] sbuf; + + if (sess == nullptr) + return; + + int r = SSL_set_session(w->ssl_, sess); + SSL_SESSION_free(sess); + + if (!r) + return env->ThrowError("SSL_set_session error"); +} + + +template +void SSLWrap::LoadSession(const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + if (args.Length() >= 1 && Buffer::HasInstance(args[0])) { + ssize_t slen = Buffer::Length(args[0]); + char* sbuf = Buffer::Data(args[0]); + + const unsigned char* p = reinterpret_cast(sbuf); + SSL_SESSION* sess = d2i_SSL_SESSION(nullptr, &p, slen); + + // Setup next session and move hello to the BIO buffer + if (w->next_sess_ != nullptr) + SSL_SESSION_free(w->next_sess_); + w->next_sess_ = sess; + } +} + + +template +void SSLWrap::IsSessionReused(const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + bool yes = SSL_session_reused(w->ssl_); + args.GetReturnValue().Set(yes); +} + + +template +void SSLWrap::EndParser(const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + w->hello_parser_.End(); +} + + +template +void SSLWrap::Renegotiate(const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence unused variable warning. + + bool yes = SSL_renegotiate(w->ssl_) == 1; + args.GetReturnValue().Set(yes); +} + + +template +void SSLWrap::Shutdown(const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + int rv = SSL_shutdown(w->ssl_); + args.GetReturnValue().Set(rv); +} + + +template +void SSLWrap::GetTLSTicket(const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = w->ssl_env(); + + SSL_SESSION* sess = SSL_get_session(w->ssl_); + if (sess == nullptr) + return; + + const unsigned char *ticket; + size_t length; + SSL_SESSION_get0_ticket(sess, &ticket, &length); + if (ticket == nullptr) { + return; + } + + Local buff = Buffer::Copy( + env, + reinterpret_cast(ticket), + length).ToLocalChecked(); + + args.GetReturnValue().Set(buff); +} + + +template +void SSLWrap::NewSessionDone(const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + w->new_session_wait_ = false; + w->NewSessionDoneCb(); +} + + +template +void SSLWrap::SetOCSPResponse( + const v8::FunctionCallbackInfo& args) { +#ifdef NODE__HAVE_TLSEXT_STATUS_CB + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = w->env(); + + if (args.Length() < 1) + return env->ThrowTypeError("OCSP response argument is mandatory"); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "OCSP response"); + + w->ocsp_response_.Reset(args.GetIsolate(), args[0].As()); +#endif // NODE__HAVE_TLSEXT_STATUS_CB +} + + +template +void SSLWrap::RequestOCSP( + const v8::FunctionCallbackInfo& args) { +#ifdef NODE__HAVE_TLSEXT_STATUS_CB + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + SSL_set_tlsext_status_type(w->ssl_, TLSEXT_STATUSTYPE_ocsp); +#endif // NODE__HAVE_TLSEXT_STATUS_CB +} + + +template +void SSLWrap::GetEphemeralKeyInfo( + const v8::FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = Environment::GetCurrent(args); + + CHECK_NE(w->ssl_, nullptr); + + // tmp key is available on only client + if (w->is_server()) + return args.GetReturnValue().SetNull(); + + Local info = Object::New(env->isolate()); + + EVP_PKEY* key; + + if (SSL_get_server_tmp_key(w->ssl_, &key)) { + switch (EVP_PKEY_id(key)) { + case EVP_PKEY_DH: + info->Set(env->type_string(), + FIXED_ONE_BYTE_STRING(env->isolate(), "DH")); + info->Set(env->size_string(), + Integer::New(env->isolate(), EVP_PKEY_bits(key))); + break; + case EVP_PKEY_EC: + { + EC_KEY* ec = EVP_PKEY_get1_EC_KEY(key); + int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); + EC_KEY_free(ec); + info->Set(env->type_string(), + FIXED_ONE_BYTE_STRING(env->isolate(), "ECDH")); + info->Set(env->name_string(), + OneByteString(args.GetIsolate(), OBJ_nid2sn(nid))); + info->Set(env->size_string(), + Integer::New(env->isolate(), EVP_PKEY_bits(key))); + } + } + EVP_PKEY_free(key); + } + + return args.GetReturnValue().Set(info); +} + + +#ifdef SSL_set_max_send_fragment +template +void SSLWrap::SetMaxSendFragment( + const v8::FunctionCallbackInfo& args) { + CHECK(args.Length() >= 1 && args[0]->IsNumber()); + + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + int rv = SSL_set_max_send_fragment(w->ssl_, args[0]->Int32Value()); + args.GetReturnValue().Set(rv); +} +#endif // SSL_set_max_send_fragment + + +template +void SSLWrap::IsInitFinished(const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + bool yes = SSL_is_init_finished(w->ssl_); + args.GetReturnValue().Set(yes); +} + + +template +void SSLWrap::VerifyError(const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + // XXX(bnoordhuis) The UNABLE_TO_GET_ISSUER_CERT error when there is no + // peer certificate is questionable but it's compatible with what was + // here before. + long x509_verify_error = // NOLINT(runtime/int) + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT; + if (X509* peer_cert = SSL_get_peer_certificate(w->ssl_)) { + X509_free(peer_cert); + x509_verify_error = SSL_get_verify_result(w->ssl_); + } + + if (x509_verify_error == X509_V_OK) + return args.GetReturnValue().SetNull(); + + // XXX(bnoordhuis) X509_verify_cert_error_string() is not actually thread-safe + // in the presence of invalid error codes. Probably academical but something + // to keep in mind if/when node ever grows multi-isolate capabilities. + const char* reason = X509_verify_cert_error_string(x509_verify_error); + const char* code = reason; +#define CASE_X509_ERR(CODE) case X509_V_ERR_##CODE: code = #CODE; break; + switch (x509_verify_error) { + CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT) + CASE_X509_ERR(UNABLE_TO_GET_CRL) + CASE_X509_ERR(UNABLE_TO_DECRYPT_CERT_SIGNATURE) + CASE_X509_ERR(UNABLE_TO_DECRYPT_CRL_SIGNATURE) + CASE_X509_ERR(UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY) + CASE_X509_ERR(CERT_SIGNATURE_FAILURE) + CASE_X509_ERR(CRL_SIGNATURE_FAILURE) + CASE_X509_ERR(CERT_NOT_YET_VALID) + CASE_X509_ERR(CERT_HAS_EXPIRED) + CASE_X509_ERR(CRL_NOT_YET_VALID) + CASE_X509_ERR(CRL_HAS_EXPIRED) + CASE_X509_ERR(ERROR_IN_CERT_NOT_BEFORE_FIELD) + CASE_X509_ERR(ERROR_IN_CERT_NOT_AFTER_FIELD) + CASE_X509_ERR(ERROR_IN_CRL_LAST_UPDATE_FIELD) + CASE_X509_ERR(ERROR_IN_CRL_NEXT_UPDATE_FIELD) + CASE_X509_ERR(OUT_OF_MEM) + CASE_X509_ERR(DEPTH_ZERO_SELF_SIGNED_CERT) + CASE_X509_ERR(SELF_SIGNED_CERT_IN_CHAIN) + CASE_X509_ERR(UNABLE_TO_GET_ISSUER_CERT_LOCALLY) + CASE_X509_ERR(UNABLE_TO_VERIFY_LEAF_SIGNATURE) + CASE_X509_ERR(CERT_CHAIN_TOO_LONG) + CASE_X509_ERR(CERT_REVOKED) + CASE_X509_ERR(INVALID_CA) + CASE_X509_ERR(PATH_LENGTH_EXCEEDED) + CASE_X509_ERR(INVALID_PURPOSE) + CASE_X509_ERR(CERT_UNTRUSTED) + CASE_X509_ERR(CERT_REJECTED) + } +#undef CASE_X509_ERR + + Isolate* isolate = args.GetIsolate(); + Local reason_string = OneByteString(isolate, reason); + Local exception_value = Exception::Error(reason_string); + Local exception_object = exception_value->ToObject(isolate); + exception_object->Set(w->env()->code_string(), OneByteString(isolate, code)); + args.GetReturnValue().Set(exception_object); +} + + +template +void SSLWrap::GetCurrentCipher(const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = w->ssl_env(); + + const SSL_CIPHER* c = SSL_get_current_cipher(w->ssl_); + if (c == nullptr) + return; + + Local info = Object::New(env->isolate()); + const char* cipher_name = SSL_CIPHER_get_name(c); + info->Set(env->name_string(), OneByteString(args.GetIsolate(), cipher_name)); + const char* cipher_version = SSL_CIPHER_get_version(c); + info->Set(env->version_string(), + OneByteString(args.GetIsolate(), cipher_version)); + args.GetReturnValue().Set(info); +} + + +template +void SSLWrap::GetProtocol(const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + const char* tls_version = SSL_get_version(w->ssl_); + args.GetReturnValue().Set(OneByteString(args.GetIsolate(), tls_version)); +} + + +#ifndef OPENSSL_NO_NEXTPROTONEG +template +int SSLWrap::AdvertiseNextProtoCallback(SSL* s, + const unsigned char** data, + unsigned int* len, + void* arg) { + Base* w = static_cast(SSL_get_app_data(s)); + Environment* env = w->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + auto npn_buffer = + w->object()->GetPrivate( + env->context(), + env->npn_buffer_private_symbol()).ToLocalChecked(); + + if (npn_buffer->IsUndefined()) { + // No initialization - no NPN protocols + *data = reinterpret_cast(""); + *len = 0; + } else { + CHECK(Buffer::HasInstance(npn_buffer)); + *data = reinterpret_cast(Buffer::Data(npn_buffer)); + *len = Buffer::Length(npn_buffer); + } + + return SSL_TLSEXT_ERR_OK; +} + + +template +int SSLWrap::SelectNextProtoCallback(SSL* s, + unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg) { + Base* w = static_cast(SSL_get_app_data(s)); + Environment* env = w->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + auto npn_buffer = + w->object()->GetPrivate( + env->context(), + env->npn_buffer_private_symbol()).ToLocalChecked(); + + if (npn_buffer->IsUndefined()) { + // We should at least select one protocol + // If server is using NPN + *out = reinterpret_cast(const_cast("http/1.1")); + *outlen = 8; + + // set status: unsupported + CHECK( + w->object()->SetPrivate( + env->context(), + env->selected_npn_buffer_private_symbol(), + False(env->isolate())).FromJust()); + + return SSL_TLSEXT_ERR_OK; + } + + CHECK(Buffer::HasInstance(npn_buffer)); + const unsigned char* npn_protos = + reinterpret_cast(Buffer::Data(npn_buffer)); + size_t len = Buffer::Length(npn_buffer); + + int status = SSL_select_next_proto(out, outlen, in, inlen, npn_protos, len); + Local result; + switch (status) { + case OPENSSL_NPN_UNSUPPORTED: + result = Null(env->isolate()); + break; + case OPENSSL_NPN_NEGOTIATED: + result = OneByteString(env->isolate(), *out, *outlen); + break; + case OPENSSL_NPN_NO_OVERLAP: + result = False(env->isolate()); + break; + default: + break; + } + + CHECK( + w->object()->SetPrivate( + env->context(), + env->selected_npn_buffer_private_symbol(), + result).FromJust()); + + return SSL_TLSEXT_ERR_OK; +} + + +template +void SSLWrap::GetNegotiatedProto( + const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = w->env(); + + if (w->is_client()) { + auto selected_npn_buffer = + w->object()->GetPrivate( + env->context(), + env->selected_npn_buffer_private_symbol()).ToLocalChecked(); + args.GetReturnValue().Set(selected_npn_buffer); + return; + } + + const unsigned char* npn_proto; + unsigned int npn_proto_len; + + SSL_get0_next_proto_negotiated(w->ssl_, &npn_proto, &npn_proto_len); + + if (!npn_proto) + return args.GetReturnValue().Set(false); + + args.GetReturnValue().Set( + OneByteString(args.GetIsolate(), npn_proto, npn_proto_len)); +} + + +template +void SSLWrap::SetNPNProtocols(const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = w->env(); + + if (args.Length() < 1) + return env->ThrowTypeError("NPN protocols argument is mandatory"); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "NPN protocols"); + + CHECK( + w->object()->SetPrivate( + env->context(), + env->npn_buffer_private_symbol(), + args[0]).FromJust()); +} +#endif // OPENSSL_NO_NEXTPROTONEG + +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation +template +int SSLWrap::SelectALPNCallback(SSL* s, + const unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg) { + Base* w = static_cast(SSL_get_app_data(s)); + Environment* env = w->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local alpn_buffer = + w->object()->GetPrivate( + env->context(), + env->alpn_buffer_private_symbol()).ToLocalChecked(); + CHECK(Buffer::HasInstance(alpn_buffer)); + const unsigned char* alpn_protos = + reinterpret_cast(Buffer::Data(alpn_buffer)); + unsigned alpn_protos_len = Buffer::Length(alpn_buffer); + int status = SSL_select_next_proto(const_cast(out), outlen, + alpn_protos, alpn_protos_len, in, inlen); + + switch (status) { + case OPENSSL_NPN_NO_OVERLAP: + // According to 3.2. Protocol Selection of RFC7301, + // fatal no_application_protocol alert shall be sent + // but current openssl does not support it yet. See + // https://rt.openssl.org/Ticket/Display.html?id=3463&user=guest&pass=guest + // Instead, we send a warning alert for now. + return SSL_TLSEXT_ERR_ALERT_WARNING; + case OPENSSL_NPN_NEGOTIATED: + return SSL_TLSEXT_ERR_OK; + default: + return SSL_TLSEXT_ERR_ALERT_FATAL; + } +} +#endif // TLSEXT_TYPE_application_layer_protocol_negotiation + + +template +void SSLWrap::GetALPNNegotiatedProto( + const FunctionCallbackInfo& args) { +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + + const unsigned char* alpn_proto; + unsigned int alpn_proto_len; + + SSL_get0_alpn_selected(w->ssl_, &alpn_proto, &alpn_proto_len); + + if (!alpn_proto) + return args.GetReturnValue().Set(false); + + args.GetReturnValue().Set( + OneByteString(args.GetIsolate(), alpn_proto, alpn_proto_len)); +#endif // TLSEXT_TYPE_application_layer_protocol_negotiation +} + + +template +void SSLWrap::SetALPNProtocols( + const FunctionCallbackInfo& args) { +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = w->env(); + if (args.Length() < 1 || !Buffer::HasInstance(args[0])) + return env->ThrowTypeError("Must give a Buffer as first argument"); + + if (w->is_client()) { + const unsigned char* alpn_protos = + reinterpret_cast(Buffer::Data(args[0])); + unsigned alpn_protos_len = Buffer::Length(args[0]); + int r = SSL_set_alpn_protos(w->ssl_, alpn_protos, alpn_protos_len); + CHECK_EQ(r, 0); + } else { + CHECK( + w->object()->SetPrivate( + env->context(), + env->alpn_buffer_private_symbol(), + args[0]).FromJust()); + // Server should select ALPN protocol from list of advertised by client + SSL_CTX_set_alpn_select_cb(SSL_get_SSL_CTX(w->ssl_), SelectALPNCallback, + nullptr); + } +#endif // TLSEXT_TYPE_application_layer_protocol_negotiation +} + + +#ifdef NODE__HAVE_TLSEXT_STATUS_CB +template +int SSLWrap::TLSExtStatusCallback(SSL* s, void* arg) { + Base* w = static_cast(SSL_get_app_data(s)); + Environment* env = w->env(); + HandleScope handle_scope(env->isolate()); + + if (w->is_client()) { + // Incoming response + const unsigned char* resp; + int len = SSL_get_tlsext_status_ocsp_resp(s, &resp); + Local arg; + if (resp == nullptr) { + arg = Null(env->isolate()); + } else { + arg = Buffer::Copy( + env, + reinterpret_cast(const_cast(resp)), + len).ToLocalChecked(); + } + + w->MakeCallback(env->onocspresponse_string(), 1, &arg); + + // Somehow, client is expecting different return value here + return 1; + } else { + // Outgoing response + if (w->ocsp_response_.IsEmpty()) + return SSL_TLSEXT_ERR_NOACK; + + Local obj = PersistentToLocal(env->isolate(), w->ocsp_response_); + char* resp = Buffer::Data(obj); + size_t len = Buffer::Length(obj); + + // OpenSSL takes control of the pointer after accepting it + char* data = node::Malloc(len); + memcpy(data, resp, len); + + if (!SSL_set_tlsext_status_ocsp_resp(s, data, len)) + free(data); + w->ocsp_response_.Reset(); + + return SSL_TLSEXT_ERR_OK; + } +} +#endif // NODE__HAVE_TLSEXT_STATUS_CB + + +template +void SSLWrap::WaitForCertCb(CertCb cb, void* arg) { + cert_cb_ = cb; + cert_cb_arg_ = arg; +} + + +template +int SSLWrap::SSLCertCallback(SSL* s, void* arg) { + Base* w = static_cast(SSL_get_app_data(s)); + + if (!w->is_server()) + return 1; + + if (!w->is_waiting_cert_cb()) + return 1; + + if (w->cert_cb_running_) + return -1; + + Environment* env = w->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + w->cert_cb_running_ = true; + + Local info = Object::New(env->isolate()); + + const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + if (servername == nullptr) { + info->Set(env->servername_string(), String::Empty(env->isolate())); + } else { + Local str = OneByteString(env->isolate(), servername, + strlen(servername)); + info->Set(env->servername_string(), str); + } + + bool ocsp = false; +#ifdef NODE__HAVE_TLSEXT_STATUS_CB + ocsp = SSL_get_tlsext_status_type(s) == TLSEXT_STATUSTYPE_ocsp; +#endif + + info->Set(env->ocsp_request_string(), Boolean::New(env->isolate(), ocsp)); + + Local argv[] = { info }; + w->MakeCallback(env->oncertcb_string(), arraysize(argv), argv); + + if (!w->cert_cb_running_) + return 1; + + // Performing async action, wait... + return -1; +} + + +template +void SSLWrap::CertCbDone(const FunctionCallbackInfo& args) { + Base* w; + ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder()); + Environment* env = w->env(); + + CHECK(w->is_waiting_cert_cb() && w->cert_cb_running_); + + Local object = w->object(); + Local ctx = object->Get(env->sni_context_string()); + Local cons = env->secure_context_constructor_template(); + + // Not an object, probably undefined or null + if (!ctx->IsObject()) + goto fire_cb; + + if (cons->HasInstance(ctx)) { + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, ctx.As()); + w->sni_context_.Reset(); + w->sni_context_.Reset(env->isolate(), ctx); + + int rv; + + // NOTE: reference count is not increased by this API methods + X509* x509 = SSL_CTX_get0_certificate(sc->ctx_); + EVP_PKEY* pkey = SSL_CTX_get0_privatekey(sc->ctx_); + STACK_OF(X509)* chain; + + rv = SSL_CTX_get0_chain_certs(sc->ctx_, &chain); + if (rv) + rv = SSL_use_certificate(w->ssl_, x509); + if (rv) + rv = SSL_use_PrivateKey(w->ssl_, pkey); + if (rv && chain != nullptr) + rv = SSL_set1_chain(w->ssl_, chain); + if (rv) + rv = w->SetCACerts(sc); + if (!rv) { + unsigned long err = ERR_get_error(); // NOLINT(runtime/int) + if (!err) + return env->ThrowError("CertCbDone"); + return ThrowCryptoError(env, err); + } + } else { + // Failure: incorrect SNI context object + Local err = Exception::TypeError(env->sni_context_err_string()); + w->MakeCallback(env->onerror_string(), 1, &err); + return; + } + + fire_cb: + CertCb cb; + void* arg; + + cb = w->cert_cb_; + arg = w->cert_cb_arg_; + + w->cert_cb_running_ = false; + w->cert_cb_ = nullptr; + w->cert_cb_arg_ = nullptr; + + cb(arg); +} + + +template +void SSLWrap::SSLGetter(Local property, + const PropertyCallbackInfo& info) { + Base* base; + ASSIGN_OR_RETURN_UNWRAP(&base, info.This()); + SSL* ssl = base->ssl_; + Local ext = External::New(info.GetIsolate(), ssl); + info.GetReturnValue().Set(ext); +} + + +template +void SSLWrap::DestroySSL() { + if (ssl_ == nullptr) + return; + + SSL_free(ssl_); + ssl_ = nullptr; +} + + +template +void SSLWrap::SetSNIContext(SecureContext* sc) { + InitNPN(sc); + CHECK_EQ(SSL_set_SSL_CTX(ssl_, sc->ctx_), sc->ctx_); + + SetCACerts(sc); +} + + +template +int SSLWrap::SetCACerts(SecureContext* sc) { + int err = SSL_set1_verify_cert_store(ssl_, SSL_CTX_get_cert_store(sc->ctx_)); + if (err != 1) + return err; + + STACK_OF(X509_NAME)* list = SSL_dup_CA_list( + SSL_CTX_get_client_CA_list(sc->ctx_)); + + // NOTE: `SSL_set_client_CA_list` takes the ownership of `list` + SSL_set_client_CA_list(ssl_, list); + return 1; +} + + +void Connection::OnClientHelloParseEnd(void* arg) { + Connection* conn = static_cast(arg); + + // Write all accumulated data + int r = BIO_write(conn->bio_read_, + reinterpret_cast(conn->hello_data_), + conn->hello_offset_); + conn->HandleBIOError(conn->bio_read_, "BIO_write", r); + conn->SetShutdownFlags(); +} + + +#ifdef SSL_PRINT_DEBUG +# define DEBUG_PRINT(...) fprintf (stderr, __VA_ARGS__) +#else +# define DEBUG_PRINT(...) +#endif + + +int Connection::HandleBIOError(BIO *bio, const char* func, int rv) { + if (rv >= 0) + return rv; + + int retry = BIO_should_retry(bio); + (void) retry; // unused if !defined(SSL_PRINT_DEBUG) + + if (BIO_should_write(bio)) { + DEBUG_PRINT("[%p] BIO: %s want write. should retry %d\n", + ssl_, + func, + retry); + return 0; + + } else if (BIO_should_read(bio)) { + DEBUG_PRINT("[%p] BIO: %s want read. should retry %d\n", ssl_, func, retry); + return 0; + + } else { + char ssl_error_buf[512]; + ERR_error_string_n(rv, ssl_error_buf, sizeof(ssl_error_buf)); + + HandleScope scope(ssl_env()->isolate()); + Local exception = + Exception::Error(OneByteString(ssl_env()->isolate(), ssl_error_buf)); + object()->Set(ssl_env()->error_string(), exception); + + DEBUG_PRINT("[%p] BIO: %s failed: (%d) %s\n", + ssl_, + func, + rv, + ssl_error_buf); + + return rv; + } + + return 0; +} + + +int Connection::HandleSSLError(const char* func, + int rv, + ZeroStatus zs, + SyscallStatus ss) { + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence unused variable warning. + + if (rv > 0) + return rv; + if (rv == 0 && zs == kZeroIsNotAnError) + return rv; + + int err = SSL_get_error(ssl_, rv); + + if (err == SSL_ERROR_NONE) { + return 0; + + } else if (err == SSL_ERROR_WANT_WRITE) { + DEBUG_PRINT("[%p] SSL: %s want write\n", ssl_, func); + return 0; + + } else if (err == SSL_ERROR_WANT_READ) { + DEBUG_PRINT("[%p] SSL: %s want read\n", ssl_, func); + return 0; + + } else if (err == SSL_ERROR_WANT_X509_LOOKUP) { + DEBUG_PRINT("[%p] SSL: %s want x509 lookup\n", ssl_, func); + return 0; + + } else if (err == SSL_ERROR_ZERO_RETURN) { + HandleScope scope(ssl_env()->isolate()); + + Local exception = + Exception::Error(ssl_env()->zero_return_string()); + object()->Set(ssl_env()->error_string(), exception); + return rv; + + } else if (err == SSL_ERROR_SYSCALL && ss == kIgnoreSyscall) { + return 0; + + } else { + HandleScope scope(ssl_env()->isolate()); + BUF_MEM* mem; + BIO *bio; + + CHECK(err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL); + + // XXX We need to drain the error queue for this thread or else OpenSSL + // has the possibility of blocking connections? This problem is not well + // understood. And we should be somehow propagating these errors up + // into JavaScript. There is no test which demonstrates this problem. + // https://github.com/joyent/node/issues/1719 + bio = BIO_new(BIO_s_mem()); + if (bio != nullptr) { + ERR_print_errors(bio); + BIO_get_mem_ptr(bio, &mem); + Local exception = Exception::Error( + OneByteString(ssl_env()->isolate(), + mem->data, + mem->length)); + object()->Set(ssl_env()->error_string(), exception); + BIO_free_all(bio); + } + + return rv; + } + + return 0; +} + + +void Connection::ClearError() { +#ifndef NDEBUG + HandleScope scope(ssl_env()->isolate()); + + // We should clear the error in JS-land + Local error_key = ssl_env()->error_string(); + Local error = object()->Get(error_key); + CHECK_EQ(error->BooleanValue(), false); +#endif // NDEBUG +} + + +void Connection::SetShutdownFlags() { + HandleScope scope(ssl_env()->isolate()); + + int flags = SSL_get_shutdown(ssl_); + + if (flags & SSL_SENT_SHUTDOWN) { + Local sent_shutdown_key = ssl_env()->sent_shutdown_string(); + object()->Set(sent_shutdown_key, True(ssl_env()->isolate())); + } + + if (flags & SSL_RECEIVED_SHUTDOWN) { + Local received_shutdown_key = ssl_env()->received_shutdown_string(); + object()->Set(received_shutdown_key, True(ssl_env()->isolate())); + } +} + + +void Connection::NewSessionDoneCb() { + HandleScope scope(env()->isolate()); + + MakeCallback(env()->onnewsessiondone_string(), 0, nullptr); +} + + +void Connection::Initialize(Environment* env, Local target) { + Local t = env->NewFunctionTemplate(Connection::New); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Connection")); + + env->SetProtoMethod(t, "getAsyncId", AsyncWrap::GetAsyncId); + env->SetProtoMethod(t, "encIn", Connection::EncIn); + env->SetProtoMethod(t, "clearOut", Connection::ClearOut); + env->SetProtoMethod(t, "clearIn", Connection::ClearIn); + env->SetProtoMethod(t, "encOut", Connection::EncOut); + env->SetProtoMethod(t, "clearPending", Connection::ClearPending); + env->SetProtoMethod(t, "encPending", Connection::EncPending); + env->SetProtoMethod(t, "start", Connection::Start); + env->SetProtoMethod(t, "close", Connection::Close); + + SSLWrap::AddMethods(env, t); + + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + env->SetProtoMethod(t, "getServername", Connection::GetServername); + env->SetProtoMethod(t, "setSNICallback", Connection::SetSNICallback); +#endif + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Connection"), + t->GetFunction()); +} + + +inline int compar(const void* a, const void* b) { + return memcmp(a, b, CNNIC_WHITELIST_HASH_LEN); +} + + +inline int IsSelfSigned(X509* cert) { + return X509_NAME_cmp(X509_get_subject_name(cert), + X509_get_issuer_name(cert)) == 0; +} + + +inline X509* FindRoot(STACK_OF(X509)* sk) { + for (int i = 0; i < sk_X509_num(sk); i++) { + X509* cert = sk_X509_value(sk, i); + if (IsSelfSigned(cert)) + return cert; + } + return nullptr; +} + + +inline bool CertIsStartComOrWoSign(X509_NAME* name) { + const unsigned char* startcom_wosign_data; + X509_NAME* startcom_wosign_name; + + for (const auto& dn : StartComAndWoSignDNs) { + startcom_wosign_data = dn.data; + startcom_wosign_name = d2i_X509_NAME(nullptr, &startcom_wosign_data, + dn.len); + int cmp = X509_NAME_cmp(name, startcom_wosign_name); + X509_NAME_free(startcom_wosign_name); + if (cmp == 0) + return true; + } + + return false; +} + +// Revoke the certificates issued by StartCom or WoSign that has +// notBefore after 00:00:00 on October 21, 2016 (1477008000 in epoch). +inline bool CheckStartComOrWoSign(X509_NAME* root_name, X509* cert) { + if (!CertIsStartComOrWoSign(root_name)) + return true; + + time_t october_21_2016 = static_cast(1477008000); + if (X509_cmp_time(X509_get_notBefore(cert), &october_21_2016) < 0) + return true; + + return false; +} + + +// Whitelist check for certs issued by CNNIC, StartCom and WoSign. See +// https://blog.mozilla.org/security/2015/04/02 +// /distrusting-new-cnnic-certificates/ and +// https://blog.mozilla.org/security/2016/10/24/ +// distrusting-new-wosign-and-startcom-certificates +inline CheckResult CheckWhitelistedServerCert(X509_STORE_CTX* ctx) { + unsigned char hash[CNNIC_WHITELIST_HASH_LEN]; + unsigned int hashlen = CNNIC_WHITELIST_HASH_LEN; + + STACK_OF(X509)* chain = X509_STORE_CTX_get1_chain(ctx); + CHECK_NE(chain, nullptr); + CHECK_GT(sk_X509_num(chain), 0); + + // Take the last cert as root at the first time. + X509* root_cert = sk_X509_value(chain, sk_X509_num(chain)-1); + X509_NAME* root_name = X509_get_subject_name(root_cert); + + if (!IsSelfSigned(root_cert)) { + root_cert = FindRoot(chain); + CHECK_NE(root_cert, nullptr); + root_name = X509_get_subject_name(root_cert); + } + + X509* leaf_cert = sk_X509_value(chain, 0); + if (!CheckStartComOrWoSign(root_name, leaf_cert)) { + sk_X509_pop_free(chain, X509_free); + return CHECK_CERT_REVOKED; + } + + // When the cert is issued from either CNNNIC ROOT CA or CNNNIC EV + // ROOT CA, check a hash of its leaf cert if it is in the whitelist. + if (X509_NAME_cmp(root_name, cnnic_name) == 0 || + X509_NAME_cmp(root_name, cnnic_ev_name) == 0) { + int ret = X509_digest(leaf_cert, EVP_sha256(), hash, + &hashlen); + CHECK(ret); + + void* result = bsearch(hash, WhitelistedCNNICHashes, + arraysize(WhitelistedCNNICHashes), + CNNIC_WHITELIST_HASH_LEN, compar); + if (result == nullptr) { + sk_X509_pop_free(chain, X509_free); + return CHECK_CERT_REVOKED; + } + } + + sk_X509_pop_free(chain, X509_free); + return CHECK_OK; +} + + +inline int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) { + // Failure on verification of the cert is handled in + // Connection::VerifyError. + if (preverify_ok == 0 || X509_STORE_CTX_get_error(ctx) != X509_V_OK) + return CHECK_OK; + + // Server does not need to check the whitelist. + SSL* ssl = static_cast( + X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); + + if (SSL_is_server(ssl)) + return CHECK_OK; + + // Client needs to check if the server cert is listed in the + // whitelist when it is issued by the specific rootCAs. + CheckResult ret = CheckWhitelistedServerCert(ctx); + if (ret == CHECK_CERT_REVOKED) + X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED); + + return ret; +} + + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB +int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) { + Connection* conn = static_cast(SSL_get_app_data(s)); + Environment* env = conn->env(); + HandleScope scope(env->isolate()); + + const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + + if (servername) { + conn->servername_.Reset(env->isolate(), + OneByteString(env->isolate(), servername)); + + // Call the SNI callback and use its return value as context + if (!conn->sniObject_.IsEmpty()) { + conn->sni_context_.Reset(); + + Local sni_obj = PersistentToLocal(env->isolate(), + conn->sniObject_); + + Local arg = PersistentToLocal(env->isolate(), conn->servername_); + Local ret = node::MakeCallback(env->isolate(), + sni_obj, + env->onselect_string(), + 1, + &arg); + + // If ret is SecureContext + Local secure_context_constructor_template = + env->secure_context_constructor_template(); + if (secure_context_constructor_template->HasInstance(ret)) { + conn->sni_context_.Reset(env->isolate(), ret); + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, ret.As(), SSL_TLSEXT_ERR_NOACK); + conn->SetSNIContext(sc); + } else { + return SSL_TLSEXT_ERR_NOACK; + } + } + } + + return SSL_TLSEXT_ERR_OK; +} +#endif + +void Connection::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (args.Length() < 1 || !args[0]->IsObject()) { + env->ThrowError("First argument must be a tls module SecureContext"); + return; + } + + SecureContext* sc; + ASSIGN_OR_RETURN_UNWRAP(&sc, args[0].As()); + + bool is_server = args[1]->BooleanValue(); + + SSLWrap::Kind kind = + is_server ? SSLWrap::kServer : SSLWrap::kClient; + Connection* conn = new Connection(env, args.This(), sc, kind); + conn->bio_read_ = NodeBIO::New(); + conn->bio_write_ = NodeBIO::New(); + + SSL_set_app_data(conn->ssl_, conn); + + if (is_server) + SSL_set_info_callback(conn->ssl_, SSLInfoCallback); + + InitNPN(sc); + + SSL_set_cert_cb(conn->ssl_, SSLWrap::SSLCertCallback, conn); + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + if (is_server) { + SSL_CTX_set_tlsext_servername_callback(sc->ctx_, SelectSNIContextCallback_); + } else if (args[2]->IsString()) { + const node::Utf8Value servername(env->isolate(), args[2]); + SSL_set_tlsext_host_name(conn->ssl_, *servername); + } +#endif + + SSL_set_bio(conn->ssl_, conn->bio_read_, conn->bio_write_); + +#ifdef SSL_MODE_RELEASE_BUFFERS + long mode = SSL_get_mode(conn->ssl_); // NOLINT(runtime/int) + SSL_set_mode(conn->ssl_, mode | SSL_MODE_RELEASE_BUFFERS); +#endif + + + int verify_mode; + if (is_server) { + bool request_cert = args[2]->BooleanValue(); + if (!request_cert) { + // Note reject_unauthorized ignored. + verify_mode = SSL_VERIFY_NONE; + } else { + bool reject_unauthorized = args[3]->BooleanValue(); + verify_mode = SSL_VERIFY_PEER; + if (reject_unauthorized) + verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + } + } else { + // Note request_cert and reject_unauthorized are ignored for clients. + verify_mode = SSL_VERIFY_NONE; + } + + + // Always allow a connection. We'll reject in javascript. + SSL_set_verify(conn->ssl_, verify_mode, VerifyCallback); + + if (is_server) { + SSL_set_accept_state(conn->ssl_); + } else { + SSL_set_connect_state(conn->ssl_); + } +} + + +void Connection::SSLInfoCallback(const SSL *ssl_, int where, int ret) { + if (!(where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE))) + return; + + // Be compatible with older versions of OpenSSL. SSL_get_app_data() wants + // a non-const SSL* in OpenSSL <= 0.9.7e. + SSL* ssl = const_cast(ssl_); + Connection* conn = static_cast(SSL_get_app_data(ssl)); + Environment* env = conn->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + if (where & SSL_CB_HANDSHAKE_START) { + conn->MakeCallback(env->onhandshakestart_string(), 0, nullptr); + } + + if (where & SSL_CB_HANDSHAKE_DONE) { + conn->MakeCallback(env->onhandshakedone_string(), 0, nullptr); + } +} + + +void Connection::EncIn(const FunctionCallbackInfo& args) { + Connection* conn; + ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder()); + Environment* env = conn->env(); + + if (args.Length() < 3) { + return env->ThrowTypeError( + "Data, offset, and length arguments are mandatory"); + } + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Data"); + + char* buffer_data = Buffer::Data(args[0]); + size_t buffer_length = Buffer::Length(args[0]); + + size_t off = args[1]->Int32Value(); + size_t len = args[2]->Int32Value(); + + if (!Buffer::IsWithinBounds(off, len, buffer_length)) + return env->ThrowRangeError("offset + length > buffer.length"); + + int bytes_written; + char* data = buffer_data + off; + + if (conn->is_server() && !conn->hello_parser_.IsEnded()) { + // Just accumulate data, everything will be pushed to BIO later + if (conn->hello_parser_.IsPaused()) { + bytes_written = 0; + } else { + // Copy incoming data to the internal buffer + // (which has a size of the biggest possible TLS frame) + size_t available = sizeof(conn->hello_data_) - conn->hello_offset_; + size_t copied = len < available ? len : available; + memcpy(conn->hello_data_ + conn->hello_offset_, data, copied); + conn->hello_offset_ += copied; + + conn->hello_parser_.Parse(conn->hello_data_, conn->hello_offset_); + bytes_written = copied; + } + } else { + bytes_written = BIO_write(conn->bio_read_, data, len); + conn->HandleBIOError(conn->bio_read_, "BIO_write", bytes_written); + conn->SetShutdownFlags(); + } + + args.GetReturnValue().Set(bytes_written); +} + + +void Connection::ClearOut(const FunctionCallbackInfo& args) { + Connection* conn; + ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder()); + Environment* env = conn->env(); + + if (args.Length() < 3) { + return env->ThrowTypeError( + "Data, offset, and length arguments are mandatory"); + } + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Data"); + + char* buffer_data = Buffer::Data(args[0]); + size_t buffer_length = Buffer::Length(args[0]); + + size_t off = args[1]->Int32Value(); + size_t len = args[2]->Int32Value(); + + if (!Buffer::IsWithinBounds(off, len, buffer_length)) + return env->ThrowRangeError("offset + length > buffer.length"); + + if (!SSL_is_init_finished(conn->ssl_)) { + int rv; + + if (conn->is_server()) { + rv = SSL_accept(conn->ssl_); + conn->HandleSSLError("SSL_accept:ClearOut", + rv, + kZeroIsAnError, + kSyscallError); + } else { + rv = SSL_connect(conn->ssl_); + conn->HandleSSLError("SSL_connect:ClearOut", + rv, + kZeroIsAnError, + kSyscallError); + } + + if (rv < 0) { + return args.GetReturnValue().Set(rv); + } + } + + int bytes_read = SSL_read(conn->ssl_, buffer_data + off, len); + conn->HandleSSLError("SSL_read:ClearOut", + bytes_read, + kZeroIsNotAnError, + kSyscallError); + conn->SetShutdownFlags(); + + args.GetReturnValue().Set(bytes_read); +} + + +void Connection::ClearPending(const FunctionCallbackInfo& args) { + Connection* conn; + ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder()); + int bytes_pending = BIO_pending(conn->bio_read_); + args.GetReturnValue().Set(bytes_pending); +} + + +void Connection::EncPending(const FunctionCallbackInfo& args) { + Connection* conn; + ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder()); + int bytes_pending = BIO_pending(conn->bio_write_); + args.GetReturnValue().Set(bytes_pending); +} + + +void Connection::EncOut(const FunctionCallbackInfo& args) { + Connection* conn; + ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder()); + Environment* env = conn->env(); + + if (args.Length() < 3) { + return env->ThrowTypeError( + "Data, offset, and length arguments are mandatory"); + } + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Data"); + + char* buffer_data = Buffer::Data(args[0]); + size_t buffer_length = Buffer::Length(args[0]); + + size_t off = args[1]->Int32Value(); + size_t len = args[2]->Int32Value(); + + if (!Buffer::IsWithinBounds(off, len, buffer_length)) + return env->ThrowRangeError("offset + length > buffer.length"); + + int bytes_read = BIO_read(conn->bio_write_, buffer_data + off, len); + + conn->HandleBIOError(conn->bio_write_, "BIO_read:EncOut", bytes_read); + conn->SetShutdownFlags(); + + args.GetReturnValue().Set(bytes_read); +} + + +void Connection::ClearIn(const FunctionCallbackInfo& args) { + Connection* conn; + ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder()); + Environment* env = conn->env(); + + if (args.Length() < 3) { + return env->ThrowTypeError( + "Data, offset, and length arguments are mandatory"); + } + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Data"); + + char* buffer_data = Buffer::Data(args[0]); + size_t buffer_length = Buffer::Length(args[0]); + + size_t off = args[1]->Int32Value(); + size_t len = args[2]->Int32Value(); + + if (!Buffer::IsWithinBounds(off, len, buffer_length)) + return env->ThrowRangeError("offset + length > buffer.length"); + + if (!SSL_is_init_finished(conn->ssl_)) { + int rv; + if (conn->is_server()) { + rv = SSL_accept(conn->ssl_); + conn->HandleSSLError("SSL_accept:ClearIn", + rv, + kZeroIsAnError, + kSyscallError); + } else { + rv = SSL_connect(conn->ssl_); + conn->HandleSSLError("SSL_connect:ClearIn", + rv, + kZeroIsAnError, + kSyscallError); + } + + if (rv < 0) { + return args.GetReturnValue().Set(rv); + } + } + + int bytes_written = SSL_write(conn->ssl_, buffer_data + off, len); + + conn->HandleSSLError("SSL_write:ClearIn", + bytes_written, + len == 0 ? kZeroIsNotAnError : kZeroIsAnError, + kSyscallError); + conn->SetShutdownFlags(); + + args.GetReturnValue().Set(bytes_written); +} + + +void Connection::Start(const FunctionCallbackInfo& args) { + Connection* conn; + ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder()); + + int rv = 0; + if (!SSL_is_init_finished(conn->ssl_)) { + if (conn->is_server()) { + rv = SSL_accept(conn->ssl_); + conn->HandleSSLError("SSL_accept:Start", + rv, + kZeroIsAnError, + kSyscallError); + } else { + rv = SSL_connect(conn->ssl_); + conn->HandleSSLError("SSL_connect:Start", + rv, + kZeroIsAnError, + kSyscallError); + } + } + args.GetReturnValue().Set(rv); +} + + +void Connection::Close(const FunctionCallbackInfo& args) { + Connection* conn; + ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder()); + conn->DestroySSL(); +} + + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB +void Connection::GetServername(const FunctionCallbackInfo& args) { + Connection* conn; + ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder()); + + if (conn->is_server() && !conn->servername_.IsEmpty()) { + args.GetReturnValue().Set(conn->servername_); + } else { + args.GetReturnValue().Set(false); + } +} + + +void Connection::SetSNICallback(const FunctionCallbackInfo& args) { + Connection* conn; + ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder()); + Environment* env = conn->env(); + + if (args.Length() < 1 || !args[0]->IsFunction()) { + return env->ThrowError("Must give a Function as first argument"); + } + + Local obj = Object::New(env->isolate()); + obj->Set(env->onselect_string(), args[0]); + conn->sniObject_.Reset(args.GetIsolate(), obj); +} +#endif + + +void CipherBase::Initialize(Environment* env, Local target) { + Local t = env->NewFunctionTemplate(New); + + t->InstanceTemplate()->SetInternalFieldCount(1); + + env->SetProtoMethod(t, "init", Init); + env->SetProtoMethod(t, "initiv", InitIv); + env->SetProtoMethod(t, "update", Update); + env->SetProtoMethod(t, "final", Final); + env->SetProtoMethod(t, "setAutoPadding", SetAutoPadding); + env->SetProtoMethod(t, "getAuthTag", GetAuthTag); + env->SetProtoMethod(t, "setAuthTag", SetAuthTag); + env->SetProtoMethod(t, "setAAD", SetAAD); + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "CipherBase"), + t->GetFunction()); +} + + +void CipherBase::New(const FunctionCallbackInfo& args) { + CHECK_EQ(args.IsConstructCall(), true); + CipherKind kind = args[0]->IsTrue() ? kCipher : kDecipher; + Environment* env = Environment::GetCurrent(args); + new CipherBase(env, args.This(), kind); +} + + +void CipherBase::Init(const char* cipher_type, + const char* key_buf, + int key_buf_len) { + HandleScope scope(env()->isolate()); + +#ifdef NODE_FIPS_MODE + if (FIPS_mode()) { + return env()->ThrowError( + "crypto.createCipher() is not supported in FIPS mode."); + } +#endif // NODE_FIPS_MODE + + CHECK_EQ(cipher_, nullptr); + cipher_ = EVP_get_cipherbyname(cipher_type); + if (cipher_ == nullptr) { + return env()->ThrowError("Unknown cipher"); + } + + unsigned char key[EVP_MAX_KEY_LENGTH]; + unsigned char iv[EVP_MAX_IV_LENGTH]; + + int key_len = EVP_BytesToKey(cipher_, + EVP_md5(), + nullptr, + reinterpret_cast(key_buf), + key_buf_len, + 1, + key, + iv); + + EVP_CIPHER_CTX_init(ctx_); + const bool encrypt = (kind_ == kCipher); + EVP_CipherInit_ex(ctx_, cipher_, nullptr, nullptr, nullptr, encrypt); + if (!EVP_CIPHER_CTX_set_key_length(ctx_, key_len)) { + EVP_CIPHER_CTX_cleanup(ctx_); + return env()->ThrowError("Invalid key length"); + } + + EVP_CipherInit_ex(ctx_, + nullptr, + nullptr, + reinterpret_cast(key), + reinterpret_cast(iv), + kind_ == kCipher); + initialised_ = true; +} + + +void CipherBase::Init(const FunctionCallbackInfo& args) { + CipherBase* cipher; + ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); + Environment* env = cipher->env(); + + if (args.Length() < 2) { + return env->ThrowError("Cipher type and key arguments are mandatory"); + } + + THROW_AND_RETURN_IF_NOT_STRING(args[0], "Cipher type"); + THROW_AND_RETURN_IF_NOT_BUFFER(args[1], "Key"); + + const node::Utf8Value cipher_type(args.GetIsolate(), args[0]); + const char* key_buf = Buffer::Data(args[1]); + ssize_t key_buf_len = Buffer::Length(args[1]); + cipher->Init(*cipher_type, key_buf, key_buf_len); +} + + +void CipherBase::InitIv(const char* cipher_type, + const char* key, + int key_len, + const char* iv, + int iv_len) { + HandleScope scope(env()->isolate()); + + cipher_ = EVP_get_cipherbyname(cipher_type); + if (cipher_ == nullptr) { + return env()->ThrowError("Unknown cipher"); + } + + const int expected_iv_len = EVP_CIPHER_iv_length(cipher_); + const bool is_gcm_mode = (EVP_CIPH_GCM_MODE == EVP_CIPHER_mode(cipher_)); + + if (is_gcm_mode == false && iv_len != expected_iv_len) { + return env()->ThrowError("Invalid IV length"); + } + + EVP_CIPHER_CTX_init(ctx_); + const bool encrypt = (kind_ == kCipher); + EVP_CipherInit_ex(ctx_, cipher_, nullptr, nullptr, nullptr, encrypt); + + if (is_gcm_mode && + !EVP_CIPHER_CTX_ctrl(ctx_, EVP_CTRL_GCM_SET_IVLEN, iv_len, nullptr)) { + EVP_CIPHER_CTX_cleanup(ctx_); + return env()->ThrowError("Invalid IV length"); + } + + if (!EVP_CIPHER_CTX_set_key_length(ctx_, key_len)) { + EVP_CIPHER_CTX_cleanup(ctx_); + return env()->ThrowError("Invalid key length"); + } + + EVP_CipherInit_ex(ctx_, + nullptr, + nullptr, + reinterpret_cast(key), + reinterpret_cast(iv), + kind_ == kCipher); + initialised_ = true; +} + + +void CipherBase::InitIv(const FunctionCallbackInfo& args) { + CipherBase* cipher; + ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); + Environment* env = cipher->env(); + + if (args.Length() < 3) { + return env->ThrowError("Cipher type, key, and IV arguments are mandatory"); + } + + THROW_AND_RETURN_IF_NOT_STRING(args[0], "Cipher type"); + THROW_AND_RETURN_IF_NOT_BUFFER(args[1], "Key"); + THROW_AND_RETURN_IF_NOT_BUFFER(args[2], "IV"); + + const node::Utf8Value cipher_type(env->isolate(), args[0]); + ssize_t key_len = Buffer::Length(args[1]); + const char* key_buf = Buffer::Data(args[1]); + ssize_t iv_len = Buffer::Length(args[2]); + const char* iv_buf = Buffer::Data(args[2]); + cipher->InitIv(*cipher_type, key_buf, key_len, iv_buf, iv_len); +} + + +bool CipherBase::IsAuthenticatedMode() const { + // check if this cipher operates in an AEAD mode that we support. + if (!cipher_) + return false; + int mode = EVP_CIPHER_mode(cipher_); + return mode == EVP_CIPH_GCM_MODE; +} + + +bool CipherBase::GetAuthTag(char** out, unsigned int* out_len) const { + // only callable after Final and if encrypting. + if (initialised_ || kind_ != kCipher || !auth_tag_) + return false; + *out_len = auth_tag_len_; + *out = node::Malloc(auth_tag_len_); + memcpy(*out, auth_tag_, auth_tag_len_); + return true; +} + + +void CipherBase::GetAuthTag(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CipherBase* cipher; + ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); + + char* out = nullptr; + unsigned int out_len = 0; + + if (cipher->GetAuthTag(&out, &out_len)) { + Local buf = Buffer::New(env, out, out_len).ToLocalChecked(); + args.GetReturnValue().Set(buf); + } else { + env->ThrowError("Attempting to get auth tag in unsupported state"); + } +} + + +bool CipherBase::SetAuthTag(const char* data, unsigned int len) { + if (!initialised_ || !IsAuthenticatedMode() || kind_ != kDecipher) + return false; + delete[] auth_tag_; + auth_tag_len_ = len; + auth_tag_ = new char[len]; + memcpy(auth_tag_, data, len); + return true; +} + + +void CipherBase::SetAuthTag(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Auth tag"); + + CipherBase* cipher; + ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); + + if (!cipher->SetAuthTag(Buffer::Data(args[0]), Buffer::Length(args[0]))) + env->ThrowError("Attempting to set auth tag in unsupported state"); +} + + +bool CipherBase::SetAAD(const char* data, unsigned int len) { + if (!initialised_ || !IsAuthenticatedMode()) + return false; + int outlen; + if (!EVP_CipherUpdate(ctx_, + nullptr, + &outlen, + reinterpret_cast(data), + len)) { + return false; + } + return true; +} + + +void CipherBase::SetAAD(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "AAD"); + + CipherBase* cipher; + ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); + + if (!cipher->SetAAD(Buffer::Data(args[0]), Buffer::Length(args[0]))) + env->ThrowError("Attempting to set AAD in unsupported state"); +} + + +bool CipherBase::Update(const char* data, + int len, + unsigned char** out, + int* out_len) { + if (!initialised_) + return 0; + + // on first update: + if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_ != nullptr) { + EVP_CIPHER_CTX_ctrl(ctx_, + EVP_CTRL_GCM_SET_TAG, + auth_tag_len_, + reinterpret_cast(auth_tag_)); + delete[] auth_tag_; + auth_tag_ = nullptr; + } + + *out_len = len + EVP_CIPHER_CTX_block_size(ctx_); + *out = new unsigned char[*out_len]; + return EVP_CipherUpdate(ctx_, + *out, + out_len, + reinterpret_cast(data), + len); +} + + +void CipherBase::Update(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CipherBase* cipher; + ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); + + THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0], "Cipher data"); + + unsigned char* out = nullptr; + bool r; + int out_len = 0; + + // Only copy the data if we have to, because it's a string + if (args[0]->IsString()) { + StringBytes::InlineDecoder decoder; + if (!decoder.Decode(env, args[0].As(), args[1], UTF8)) + return; + r = cipher->Update(decoder.out(), decoder.size(), &out, &out_len); + } else { + char* buf = Buffer::Data(args[0]); + size_t buflen = Buffer::Length(args[0]); + r = cipher->Update(buf, buflen, &out, &out_len); + } + + if (!r) { + delete[] out; + return ThrowCryptoError(env, + ERR_get_error(), + "Trying to add data in unsupported state"); + } + + CHECK(out != nullptr || out_len == 0); + Local buf = + Buffer::Copy(env, reinterpret_cast(out), out_len).ToLocalChecked(); + if (out) + delete[] out; + + args.GetReturnValue().Set(buf); +} + + +bool CipherBase::SetAutoPadding(bool auto_padding) { + if (!initialised_) + return false; + return EVP_CIPHER_CTX_set_padding(ctx_, auto_padding); +} + + +void CipherBase::SetAutoPadding(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CipherBase* cipher; + ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); + + if (!cipher->SetAutoPadding(args.Length() < 1 || args[0]->BooleanValue())) + env->ThrowError("Attempting to set auto padding in unsupported state"); +} + + +bool CipherBase::Final(unsigned char** out, int *out_len) { + if (!initialised_) + return false; + + *out = new unsigned char[EVP_CIPHER_CTX_block_size(ctx_)]; + int r = EVP_CipherFinal_ex(ctx_, *out, out_len); + + if (r && kind_ == kCipher) { + delete[] auth_tag_; + auth_tag_ = nullptr; + if (IsAuthenticatedMode()) { + auth_tag_len_ = EVP_GCM_TLS_TAG_LEN; // use default tag length + auth_tag_ = new char[auth_tag_len_]; + memset(auth_tag_, 0, auth_tag_len_); + EVP_CIPHER_CTX_ctrl(ctx_, + EVP_CTRL_GCM_GET_TAG, + auth_tag_len_, + reinterpret_cast(auth_tag_)); + } + } + + EVP_CIPHER_CTX_cleanup(ctx_); + initialised_ = false; + + return r == 1; +} + + +void CipherBase::Final(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CipherBase* cipher; + ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); + + unsigned char* out_value = nullptr; + int out_len = -1; + Local outString; + + bool r = cipher->Final(&out_value, &out_len); + + if (out_len <= 0 || !r) { + delete[] out_value; + out_value = nullptr; + out_len = 0; + if (!r) { + const char* msg = cipher->IsAuthenticatedMode() ? + "Unsupported state or unable to authenticate data" : + "Unsupported state"; + + return ThrowCryptoError(env, + ERR_get_error(), + msg); + } + } + + Local buf = Buffer::Copy( + env, + reinterpret_cast(out_value), + out_len).ToLocalChecked(); + args.GetReturnValue().Set(buf); + delete[] out_value; +} + + +void Hmac::Initialize(Environment* env, v8::Local target) { + Local t = env->NewFunctionTemplate(New); + + t->InstanceTemplate()->SetInternalFieldCount(1); + + env->SetProtoMethod(t, "init", HmacInit); + env->SetProtoMethod(t, "update", HmacUpdate); + env->SetProtoMethod(t, "digest", HmacDigest); + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Hmac"), t->GetFunction()); +} + + +void Hmac::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + new Hmac(env, args.This()); +} + + +void Hmac::HmacInit(const char* hash_type, const char* key, int key_len) { + HandleScope scope(env()->isolate()); + + CHECK_EQ(initialised_, false); + const EVP_MD* md = EVP_get_digestbyname(hash_type); + if (md == nullptr) { + return env()->ThrowError("Unknown message digest"); + } + HMAC_CTX_reset(ctx_); + if (key_len == 0) { + key = ""; + } + if (!HMAC_Init_ex(ctx_, key, key_len, md, nullptr)) { + return ThrowCryptoError(env(), ERR_get_error()); + } + initialised_ = true; +} + + +void Hmac::HmacInit(const FunctionCallbackInfo& args) { + Hmac* hmac; + ASSIGN_OR_RETURN_UNWRAP(&hmac, args.Holder()); + Environment* env = hmac->env(); + + if (args.Length() < 2) { + return env->ThrowError("Hash type and key arguments are mandatory"); + } + + THROW_AND_RETURN_IF_NOT_STRING(args[0], "Hash type"); + THROW_AND_RETURN_IF_NOT_BUFFER(args[1], "Key"); + + const node::Utf8Value hash_type(env->isolate(), args[0]); + const char* buffer_data = Buffer::Data(args[1]); + size_t buffer_length = Buffer::Length(args[1]); + hmac->HmacInit(*hash_type, buffer_data, buffer_length); +} + + +bool Hmac::HmacUpdate(const char* data, int len) { + if (!initialised_) + return false; + int r = HMAC_Update(ctx_, reinterpret_cast(data), len); + return r == 1; +} + + +void Hmac::HmacUpdate(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + Hmac* hmac; + ASSIGN_OR_RETURN_UNWRAP(&hmac, args.Holder()); + + THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0], "Data"); + + // Only copy the data if we have to, because it's a string + bool r; + if (args[0]->IsString()) { + StringBytes::InlineDecoder decoder; + if (!decoder.Decode(env, args[0].As(), args[1], UTF8)) + return; + r = hmac->HmacUpdate(decoder.out(), decoder.size()); + } else { + char* buf = Buffer::Data(args[0]); + size_t buflen = Buffer::Length(args[0]); + r = hmac->HmacUpdate(buf, buflen); + } + + if (!r) { + return env->ThrowTypeError("HmacUpdate fail"); + } +} + + +bool Hmac::HmacDigest(unsigned char** md_value, unsigned int* md_len) { + if (!initialised_) + return false; + *md_value = new unsigned char[EVP_MAX_MD_SIZE]; + HMAC_Final(ctx_, *md_value, md_len); + HMAC_CTX_reset(ctx_); + initialised_ = false; + return true; +} + + +void Hmac::HmacDigest(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + Hmac* hmac; + ASSIGN_OR_RETURN_UNWRAP(&hmac, args.Holder()); + + enum encoding encoding = BUFFER; + if (args.Length() >= 1) { + CHECK(args[0]->IsString()); + encoding = ParseEncoding(env->isolate(), args[0], BUFFER); + } + + if (encoding == UCS2) { + return env->ThrowError("hmac.digest() does not support UTF-16"); + } + + unsigned char* md_value = nullptr; + unsigned int md_len = 0; + + bool r = hmac->HmacDigest(&md_value, &md_len); + if (!r) { + md_value = nullptr; + md_len = 0; + } + + Local error; + MaybeLocal rc = + StringBytes::Encode(env->isolate(), + reinterpret_cast(md_value), + md_len, + encoding, + &error); + delete[] md_value; + if (rc.IsEmpty()) { + CHECK(!error.IsEmpty()); + env->isolate()->ThrowException(error); + return; + } + args.GetReturnValue().Set(rc.ToLocalChecked()); +} + + +void Hash::Initialize(Environment* env, v8::Local target) { + Local t = env->NewFunctionTemplate(New); + + t->InstanceTemplate()->SetInternalFieldCount(1); + + env->SetProtoMethod(t, "update", HashUpdate); + env->SetProtoMethod(t, "digest", HashDigest); + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Hash"), t->GetFunction()); +} + + +void Hash::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (args.Length() == 0 || !args[0]->IsString()) { + return env->ThrowError("Must give hashtype string as argument"); + } + + const node::Utf8Value hash_type(env->isolate(), args[0]); + + Hash* hash = new Hash(env, args.This()); + if (!hash->HashInit(*hash_type)) { + return ThrowCryptoError(env, ERR_get_error(), + "Digest method not supported"); + } +} + + +bool Hash::HashInit(const char* hash_type) { + CHECK_EQ(initialised_, false); + const EVP_MD* md = EVP_get_digestbyname(hash_type); + if (md == nullptr) + return false; + EVP_MD_CTX_init(mdctx_); + if (EVP_DigestInit_ex(mdctx_, md, nullptr) <= 0) { + return false; + } + initialised_ = true; + finalized_ = false; + return true; +} + + +bool Hash::HashUpdate(const char* data, int len) { + if (!initialised_) + return false; + EVP_DigestUpdate(mdctx_, data, len); + return true; +} + + +void Hash::HashUpdate(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + Hash* hash; + ASSIGN_OR_RETURN_UNWRAP(&hash, args.Holder()); + + THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0], "Data"); + + if (!hash->initialised_) { + return env->ThrowError("Not initialized"); + } + if (hash->finalized_) { + return env->ThrowError("Digest already called"); + } + + // Only copy the data if we have to, because it's a string + bool r; + if (args[0]->IsString()) { + StringBytes::InlineDecoder decoder; + if (!decoder.Decode(env, args[0].As(), args[1], UTF8)) + return; + r = hash->HashUpdate(decoder.out(), decoder.size()); + } else { + char* buf = Buffer::Data(args[0]); + size_t buflen = Buffer::Length(args[0]); + r = hash->HashUpdate(buf, buflen); + } + + if (!r) { + return env->ThrowTypeError("HashUpdate fail"); + } +} + + +void Hash::HashDigest(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + Hash* hash; + ASSIGN_OR_RETURN_UNWRAP(&hash, args.Holder()); + + if (!hash->initialised_) { + return env->ThrowError("Not initialized"); + } + if (hash->finalized_) { + return env->ThrowError("Digest already called"); + } + + enum encoding encoding = BUFFER; + if (args.Length() >= 1) { + CHECK(args[0]->IsString()); + encoding = ParseEncoding(env->isolate(), args[0], BUFFER); + } + + if (encoding == UCS2) { + return env->ThrowError("hash.digest() does not support UTF-16"); + } + + unsigned char md_value[EVP_MAX_MD_SIZE]; + unsigned int md_len; + + EVP_DigestFinal_ex(hash->mdctx_, md_value, &md_len); + EVP_MD_CTX_reset(hash->mdctx_); + hash->finalized_ = true; + + Local error; + MaybeLocal rc = + StringBytes::Encode(env->isolate(), + reinterpret_cast(md_value), + md_len, + encoding, + &error); + if (rc.IsEmpty()) { + CHECK(!error.IsEmpty()); + env->isolate()->ThrowException(error); + return; + } + args.GetReturnValue().Set(rc.ToLocalChecked()); +} + + +void SignBase::CheckThrow(SignBase::Error error) { + HandleScope scope(env()->isolate()); + + switch (error) { + case kSignUnknownDigest: + return env()->ThrowError("Unknown message digest"); + + case kSignNotInitialised: + return env()->ThrowError("Not initialised"); + + case kSignInit: + case kSignUpdate: + case kSignPrivateKey: + case kSignPublicKey: + { + unsigned long err = ERR_get_error(); // NOLINT(runtime/int) + if (err) + return ThrowCryptoError(env(), err); + switch (error) { + case kSignInit: + return env()->ThrowError("EVP_SignInit_ex failed"); + case kSignUpdate: + return env()->ThrowError("EVP_SignUpdate failed"); + case kSignPrivateKey: + return env()->ThrowError("PEM_read_bio_PrivateKey failed"); + case kSignPublicKey: + return env()->ThrowError("PEM_read_bio_PUBKEY failed"); + default: + ABORT(); + } + } + + case kSignOk: + return; + } +} + +static bool ApplyRSAOptions(EVP_PKEY* pkey, EVP_PKEY_CTX* pkctx, int padding, + int salt_len) { + int type = EVP_PKEY_base_id(pkey); + if (type == EVP_PKEY_RSA || type == EVP_PKEY_RSA2) { + if (EVP_PKEY_CTX_set_rsa_padding(pkctx, padding) <= 0) + return false; + if (padding == RSA_PKCS1_PSS_PADDING) { + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkctx, salt_len) <= 0) + return false; + } + } + + return true; +} + + + +void Sign::Initialize(Environment* env, v8::Local target) { + Local t = env->NewFunctionTemplate(New); + + t->InstanceTemplate()->SetInternalFieldCount(1); + + env->SetProtoMethod(t, "init", SignInit); + env->SetProtoMethod(t, "update", SignUpdate); + env->SetProtoMethod(t, "sign", SignFinal); + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Sign"), t->GetFunction()); +} + + +void Sign::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + new Sign(env, args.This()); +} + + +SignBase::Error Sign::SignInit(const char* sign_type) { + CHECK_EQ(initialised_, false); + const EVP_MD* md = EVP_get_digestbyname(sign_type); + if (md == nullptr) + return kSignUnknownDigest; + + EVP_MD_CTX_init(mdctx_); + if (!EVP_DigestInit_ex(mdctx_, md, nullptr)) + return kSignInit; + initialised_ = true; + + return kSignOk; +} + + +void Sign::SignInit(const FunctionCallbackInfo& args) { + Sign* sign; + ASSIGN_OR_RETURN_UNWRAP(&sign, args.Holder()); + Environment* env = sign->env(); + + if (args.Length() == 0) { + return env->ThrowError("Sign type argument is mandatory"); + } + + THROW_AND_RETURN_IF_NOT_STRING(args[0], "Sign type"); + + const node::Utf8Value sign_type(args.GetIsolate(), args[0]); + sign->CheckThrow(sign->SignInit(*sign_type)); +} + + +SignBase::Error Sign::SignUpdate(const char* data, int len) { + if (!initialised_) + return kSignNotInitialised; + if (!EVP_DigestUpdate(mdctx_, data, len)) + return kSignUpdate; + return kSignOk; +} + + +void Sign::SignUpdate(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + Sign* sign; + ASSIGN_OR_RETURN_UNWRAP(&sign, args.Holder()); + + THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0], "Data"); + + // Only copy the data if we have to, because it's a string + Error err; + if (args[0]->IsString()) { + StringBytes::InlineDecoder decoder; + if (!decoder.Decode(env, args[0].As(), args[1], UTF8)) + return; + err = sign->SignUpdate(decoder.out(), decoder.size()); + } else { + char* buf = Buffer::Data(args[0]); + size_t buflen = Buffer::Length(args[0]); + err = sign->SignUpdate(buf, buflen); + } + + sign->CheckThrow(err); +} + +static int Node_SignFinal(EVP_MD_CTX* mdctx, unsigned char* md, + unsigned int* sig_len, EVP_PKEY* pkey, int padding, + int pss_salt_len) { + unsigned char m[EVP_MAX_MD_SIZE]; + unsigned int m_len; + int rv = 0; + EVP_PKEY_CTX* pkctx = nullptr; + + *sig_len = 0; + if (!EVP_DigestFinal_ex(mdctx, m, &m_len)) + return rv; + + if(EVP_MD_CTX_pkey_ctx(mdctx) == nullptr) { + size_t sltmp = static_cast(EVP_PKEY_size(pkey)); + pkctx = EVP_PKEY_CTX_new(pkey, nullptr); + if (pkctx == nullptr) + goto err; + if (EVP_PKEY_sign_init(pkctx) <= 0) + goto err; + if (!ApplyRSAOptions(pkey, pkctx, padding, pss_salt_len)) + goto err; + if (EVP_PKEY_CTX_set_signature_md(pkctx, EVP_MD_CTX_md(mdctx)) <= 0) + goto err; + if (EVP_PKEY_sign(pkctx, md, &sltmp, m, m_len) <= 0) + goto err; + *sig_len = sltmp; + rv = 1; + err: + EVP_PKEY_CTX_free(pkctx); + return rv; + } + + if (EVP_MD_CTX_md_data(mdctx) == nullptr) { + return 0; + } + + return EVP_SignFinal(mdctx, m, sig_len, pkey); +} + +SignBase::Error Sign::SignFinal(const char* key_pem, + int key_pem_len, + const char* passphrase, + unsigned char** sig, + unsigned int* sig_len, + int padding, + int salt_len) { + if (!initialised_) + return kSignNotInitialised; + + BIO* bp = nullptr; + EVP_PKEY* pkey = nullptr; + bool fatal = true; + + bp = BIO_new_mem_buf(const_cast(key_pem), key_pem_len); + if (bp == nullptr) + goto exit; + + pkey = PEM_read_bio_PrivateKey(bp, + nullptr, + PasswordCallback, + const_cast(passphrase)); + + // Errors might be injected into OpenSSL's error stack + // without `pkey` being set to nullptr; + // cf. the test of `test_bad_rsa_privkey.pem` for an example. + if (pkey == nullptr || 0 != ERR_peek_error()) + goto exit; + +#ifdef NODE_FIPS_MODE + /* Validate DSA2 parameters from FIPS 186-4 */ + if (FIPS_mode() && EVP_PKEY_DSA == pkey->type) { + size_t L = BN_num_bits(pkey->pkey.dsa->p); + size_t N = BN_num_bits(pkey->pkey.dsa->q); + bool result = false; + + if (L == 1024 && N == 160) + result = true; + else if (L == 2048 && N == 224) + result = true; + else if (L == 2048 && N == 256) + result = true; + else if (L == 3072 && N == 256) + result = true; + + if (!result) { + fatal = true; + goto exit; + } + } +#endif // NODE_FIPS_MODE + + if (Node_SignFinal(mdctx_, *sig, sig_len, pkey, padding, salt_len)) + fatal = false; + + initialised_ = false; + + exit: + if (pkey != nullptr) + EVP_PKEY_free(pkey); + if (bp != nullptr) + BIO_free_all(bp); + + EVP_MD_CTX_reset(mdctx_); + + if (fatal) + return kSignPrivateKey; + + return kSignOk; +} + + +void Sign::SignFinal(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + Sign* sign; + ASSIGN_OR_RETURN_UNWRAP(&sign, args.Holder()); + + unsigned char* md_value; + unsigned int md_len; + + unsigned int len = args.Length(); + + node::Utf8Value passphrase(env->isolate(), args[1]); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Data"); + size_t buf_len = Buffer::Length(args[0]); + char* buf = Buffer::Data(args[0]); + + CHECK(args[2]->IsInt32()); + Maybe maybe_padding = args[2]->Int32Value(env->context()); + CHECK(maybe_padding.IsJust()); + int padding = maybe_padding.ToChecked(); + + CHECK(args[3]->IsInt32()); + Maybe maybe_salt_len = args[3]->Int32Value(env->context()); + CHECK(maybe_salt_len.IsJust()); + int salt_len = maybe_salt_len.ToChecked(); + + md_len = 8192; // Maximum key size is 8192 bits + md_value = new unsigned char[md_len]; + + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence compiler warning. + + Error err = sign->SignFinal( + buf, + buf_len, + len >= 2 && !args[1]->IsNull() ? *passphrase : nullptr, + &md_value, + &md_len, + padding, + salt_len); + if (err != kSignOk) { + delete[] md_value; + md_value = nullptr; + md_len = 0; + return sign->CheckThrow(err); + } + + Local rc = Buffer::Copy(env->isolate(), + reinterpret_cast(md_value), + md_len).ToLocalChecked(); + delete[] md_value; + args.GetReturnValue().Set(rc); +} + + +void Verify::Initialize(Environment* env, v8::Local target) { + Local t = env->NewFunctionTemplate(New); + + t->InstanceTemplate()->SetInternalFieldCount(1); + + env->SetProtoMethod(t, "init", VerifyInit); + env->SetProtoMethod(t, "update", VerifyUpdate); + env->SetProtoMethod(t, "verify", VerifyFinal); + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Verify"), + t->GetFunction()); +} + + +void Verify::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + new Verify(env, args.This()); +} + + +SignBase::Error Verify::VerifyInit(const char* verify_type) { + CHECK_EQ(initialised_, false); + const EVP_MD* md = EVP_get_digestbyname(verify_type); + if (md == nullptr) + return kSignUnknownDigest; + + EVP_MD_CTX_init(mdctx_); + if (!EVP_DigestInit_ex(mdctx_, md, nullptr)) + return kSignInit; + initialised_ = true; + + return kSignOk; +} + + +void Verify::VerifyInit(const FunctionCallbackInfo& args) { + Verify* verify; + ASSIGN_OR_RETURN_UNWRAP(&verify, args.Holder()); + Environment* env = verify->env(); + + if (args.Length() == 0) { + return env->ThrowError("Verify type argument is mandatory"); + } + + THROW_AND_RETURN_IF_NOT_STRING(args[0], "Verify type"); + + const node::Utf8Value verify_type(args.GetIsolate(), args[0]); + verify->CheckThrow(verify->VerifyInit(*verify_type)); +} + + +SignBase::Error Verify::VerifyUpdate(const char* data, int len) { + if (!initialised_) + return kSignNotInitialised; + + if (!EVP_DigestUpdate(mdctx_, data, len)) + return kSignUpdate; + + return kSignOk; +} + + +void Verify::VerifyUpdate(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + Verify* verify; + ASSIGN_OR_RETURN_UNWRAP(&verify, args.Holder()); + + THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[0], "Data"); + + // Only copy the data if we have to, because it's a string + Error err; + if (args[0]->IsString()) { + StringBytes::InlineDecoder decoder; + if (!decoder.Decode(env, args[0].As(), args[1], UTF8)) + return; + err = verify->VerifyUpdate(decoder.out(), decoder.size()); + } else { + char* buf = Buffer::Data(args[0]); + size_t buflen = Buffer::Length(args[0]); + err = verify->VerifyUpdate(buf, buflen); + } + + verify->CheckThrow(err); +} + + +SignBase::Error Verify::VerifyFinal(const char* key_pem, + int key_pem_len, + const char* sig, + int siglen, + int padding, + int saltlen, + bool* verify_result) { + if (!initialised_) + return kSignNotInitialised; + + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence compiler warning. + + EVP_PKEY* pkey = nullptr; + BIO* bp = nullptr; + X509* x509 = nullptr; + bool fatal = true; + unsigned char m[EVP_MAX_MD_SIZE]; + unsigned int m_len; + int r = 0; + EVP_PKEY_CTX* pkctx = nullptr; + + bp = BIO_new_mem_buf(const_cast(key_pem), key_pem_len); + if (bp == nullptr) + goto exit; + + // Check if this is a PKCS#8 or RSA public key before trying as X.509. + // Split this out into a separate function once we have more than one + // consumer of public keys. + if (strncmp(key_pem, PUBLIC_KEY_PFX, PUBLIC_KEY_PFX_LEN) == 0) { + pkey = PEM_read_bio_PUBKEY(bp, nullptr, NoPasswordCallback, nullptr); + if (pkey == nullptr) + goto exit; + } else if (strncmp(key_pem, PUBRSA_KEY_PFX, PUBRSA_KEY_PFX_LEN) == 0) { + RSA* rsa = + PEM_read_bio_RSAPublicKey(bp, nullptr, PasswordCallback, nullptr); + if (rsa) { + pkey = EVP_PKEY_new(); + if (pkey) + EVP_PKEY_set1_RSA(pkey, rsa); + RSA_free(rsa); + } + if (pkey == nullptr) + goto exit; + } else { + // X.509 fallback + x509 = PEM_read_bio_X509(bp, nullptr, NoPasswordCallback, nullptr); + if (x509 == nullptr) + goto exit; + + pkey = X509_get_pubkey(x509); + if (pkey == nullptr) + goto exit; + } + + if (!EVP_DigestFinal_ex(mdctx_, m, &m_len)) { + goto exit; + } + + fatal = false; + + pkctx = EVP_PKEY_CTX_new(pkey, nullptr); + if (pkctx == nullptr) + goto err; + if (EVP_PKEY_verify_init(pkctx) <= 0) + goto err; + if (!ApplyRSAOptions(pkey, pkctx, padding, saltlen)) + goto err; + if (EVP_PKEY_CTX_set_signature_md(pkctx, EVP_MD_CTX_md(mdctx_)) <= 0) + goto err; + r = EVP_PKEY_verify(pkctx, + reinterpret_cast(sig), + siglen, + m, + m_len); + + err: + EVP_PKEY_CTX_free(pkctx); + + exit: + if (pkey != nullptr) + EVP_PKEY_free(pkey); + if (bp != nullptr) + BIO_free_all(bp); + if (x509 != nullptr) + X509_free(x509); + + EVP_MD_CTX_reset(mdctx_); + initialised_ = false; + + if (fatal) + return kSignPublicKey; + + *verify_result = r == 1; + return kSignOk; +} + + +void Verify::VerifyFinal(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + Verify* verify; + ASSIGN_OR_RETURN_UNWRAP(&verify, args.Holder()); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Key"); + char* kbuf = Buffer::Data(args[0]); + ssize_t klen = Buffer::Length(args[0]); + + THROW_AND_RETURN_IF_NOT_STRING_OR_BUFFER(args[1], "Hash"); + + char* hbuf = Buffer::Data(args[1]); + ssize_t hlen = Buffer::Length(args[1]); + + CHECK(args[2]->IsInt32()); + Maybe maybe_padding = args[2]->Int32Value(env->context()); + CHECK(maybe_padding.IsJust()); + int padding = maybe_padding.ToChecked(); + + CHECK(args[3]->IsInt32()); + Maybe maybe_salt_len = args[3]->Int32Value(env->context()); + CHECK(maybe_salt_len.IsJust()); + int salt_len = maybe_salt_len.ToChecked(); + + bool verify_result; + Error err = verify->VerifyFinal(kbuf, klen, hbuf, hlen, padding, salt_len, + &verify_result); + if (err != kSignOk) + return verify->CheckThrow(err); + args.GetReturnValue().Set(verify_result); +} + + +template +bool PublicKeyCipher::Cipher(const char* key_pem, + int key_pem_len, + const char* passphrase, + int padding, + const unsigned char* data, + int len, + unsigned char** out, + size_t* out_len) { + EVP_PKEY* pkey = nullptr; + EVP_PKEY_CTX* ctx = nullptr; + BIO* bp = nullptr; + X509* x509 = nullptr; + bool fatal = true; + + bp = BIO_new_mem_buf(const_cast(key_pem), key_pem_len); + if (bp == nullptr) + goto exit; + + // Check if this is a PKCS#8 or RSA public key before trying as X.509 and + // private key. + if (operation == kPublic && + strncmp(key_pem, PUBLIC_KEY_PFX, PUBLIC_KEY_PFX_LEN) == 0) { + pkey = PEM_read_bio_PUBKEY(bp, nullptr, nullptr, nullptr); + if (pkey == nullptr) + goto exit; + } else if (operation == kPublic && + strncmp(key_pem, PUBRSA_KEY_PFX, PUBRSA_KEY_PFX_LEN) == 0) { + RSA* rsa = PEM_read_bio_RSAPublicKey(bp, nullptr, nullptr, nullptr); + if (rsa) { + pkey = EVP_PKEY_new(); + if (pkey) + EVP_PKEY_set1_RSA(pkey, rsa); + RSA_free(rsa); + } + if (pkey == nullptr) + goto exit; + } else if (operation == kPublic && + strncmp(key_pem, CERTIFICATE_PFX, CERTIFICATE_PFX_LEN) == 0) { + x509 = PEM_read_bio_X509(bp, nullptr, NoPasswordCallback, nullptr); + if (x509 == nullptr) + goto exit; + + pkey = X509_get_pubkey(x509); + if (pkey == nullptr) + goto exit; + } else { + pkey = PEM_read_bio_PrivateKey(bp, + nullptr, + PasswordCallback, + const_cast(passphrase)); + if (pkey == nullptr) + goto exit; + } + + ctx = EVP_PKEY_CTX_new(pkey, nullptr); + if (!ctx) + goto exit; + if (EVP_PKEY_cipher_init(ctx) <= 0) + goto exit; + if (EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0) + goto exit; + + if (EVP_PKEY_cipher(ctx, nullptr, out_len, data, len) <= 0) + goto exit; + + *out = new unsigned char[*out_len]; + + if (EVP_PKEY_cipher(ctx, *out, out_len, data, len) <= 0) + goto exit; + + fatal = false; + + exit: + if (x509 != nullptr) + X509_free(x509); + if (pkey != nullptr) + EVP_PKEY_free(pkey); + if (bp != nullptr) + BIO_free_all(bp); + if (ctx != nullptr) + EVP_PKEY_CTX_free(ctx); + + return !fatal; +} + + +template +void PublicKeyCipher::Cipher(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Key"); + char* kbuf = Buffer::Data(args[0]); + ssize_t klen = Buffer::Length(args[0]); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[1], "Data"); + char* buf = Buffer::Data(args[1]); + ssize_t len = Buffer::Length(args[1]); + + int padding = args[2]->Uint32Value(); + + String::Utf8Value passphrase(args[3]); + + unsigned char* out_value = nullptr; + size_t out_len = 0; + + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence compiler warning. + + bool r = Cipher( + kbuf, + klen, + args.Length() >= 3 && !args[2]->IsNull() ? *passphrase : nullptr, + padding, + reinterpret_cast(buf), + len, + &out_value, + &out_len); + + if (out_len == 0 || !r) { + delete[] out_value; + out_value = nullptr; + out_len = 0; + if (!r) { + return ThrowCryptoError(env, + ERR_get_error()); + } + } + + Local vbuf = Buffer::Copy( + env, + reinterpret_cast(out_value), + out_len).ToLocalChecked(); + args.GetReturnValue().Set(vbuf); + delete[] out_value; +} + + +void DiffieHellman::Initialize(Environment* env, Local target) { + Local t = env->NewFunctionTemplate(New); + + const PropertyAttribute attributes = + static_cast(v8::ReadOnly | v8::DontDelete); + + t->InstanceTemplate()->SetInternalFieldCount(1); + + env->SetProtoMethod(t, "generateKeys", GenerateKeys); + env->SetProtoMethod(t, "computeSecret", ComputeSecret); + env->SetProtoMethod(t, "getPrime", GetPrime); + env->SetProtoMethod(t, "getGenerator", GetGenerator); + env->SetProtoMethod(t, "getPublicKey", GetPublicKey); + env->SetProtoMethod(t, "getPrivateKey", GetPrivateKey); + env->SetProtoMethod(t, "setPublicKey", SetPublicKey); + env->SetProtoMethod(t, "setPrivateKey", SetPrivateKey); + + t->InstanceTemplate()->SetAccessor( + env->verify_error_string(), + DiffieHellman::VerifyErrorGetter, + nullptr, + env->as_external(), + DEFAULT, + attributes, + AccessorSignature::New(env->isolate(), t)); + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellman"), + t->GetFunction()); + + Local t2 = env->NewFunctionTemplate(DiffieHellmanGroup); + t2->InstanceTemplate()->SetInternalFieldCount(1); + + env->SetProtoMethod(t2, "generateKeys", GenerateKeys); + env->SetProtoMethod(t2, "computeSecret", ComputeSecret); + env->SetProtoMethod(t2, "getPrime", GetPrime); + env->SetProtoMethod(t2, "getGenerator", GetGenerator); + env->SetProtoMethod(t2, "getPublicKey", GetPublicKey); + env->SetProtoMethod(t2, "getPrivateKey", GetPrivateKey); + + t2->InstanceTemplate()->SetAccessor( + env->verify_error_string(), + DiffieHellman::VerifyErrorGetter, + nullptr, + env->as_external(), + DEFAULT, + attributes, + AccessorSignature::New(env->isolate(), t2)); + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellmanGroup"), + t2->GetFunction()); +} + + +bool DiffieHellman::Init(int primeLength, int g) { + dh = DH_new(); + if (!DH_generate_parameters_ex(dh, primeLength, g, 0)) + return false; + bool result = VerifyContext(); + if (!result) + return false; + initialised_ = true; + return true; +} + + +bool DiffieHellman::Init(const char* p, int p_len, int g) { + dh = DH_new(); + BIGNUM *bn_p = BN_bin2bn(reinterpret_cast(p), p_len, 0); + BIGNUM *bn_g = BN_new(); + if (!BN_set_word(bn_g, g) || !DH_set0_pqg(dh, bn_p, NULL, bn_g)) { + BN_free(bn_p); + BN_free(bn_p); + return false; + } + bool result = VerifyContext(); + if (!result) + return false; + initialised_ = true; + return true; +} + + +bool DiffieHellman::Init(const char* p, int p_len, const char* g, int g_len) { + dh = DH_new(); + BIGNUM *bn_p = BN_bin2bn(reinterpret_cast(p), p_len, 0); + BIGNUM *bn_g = BN_bin2bn(reinterpret_cast(g), g_len, 0); + if (!DH_set0_pqg(dh, bn_p, NULL, bn_g)) { + BN_free(bn_p); + BN_free(bn_g); + return false; + } + bool result = VerifyContext(); + if (!result) + return false; + initialised_ = true; + return true; +} + + +void DiffieHellman::DiffieHellmanGroup( + const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + DiffieHellman* diffieHellman = new DiffieHellman(env, args.This()); + + if (args.Length() != 1) { + return env->ThrowError("Group name argument is mandatory"); + } + + THROW_AND_RETURN_IF_NOT_STRING(args[0], "Group name"); + + bool initialized = false; + + const node::Utf8Value group_name(env->isolate(), args[0]); + for (size_t i = 0; i < arraysize(modp_groups); ++i) { + const modp_group* it = modp_groups + i; + + if (!StringEqualNoCase(*group_name, it->name)) + continue; + + initialized = diffieHellman->Init(it->prime, + it->prime_size, + it->gen, + it->gen_size); + if (!initialized) + env->ThrowError("Initialization failed"); + return; + } + + env->ThrowError("Unknown group"); +} + + +void DiffieHellman::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + DiffieHellman* diffieHellman = + new DiffieHellman(env, args.This()); + bool initialized = false; + + if (args.Length() == 2) { + if (args[0]->IsInt32()) { + if (args[1]->IsInt32()) { + initialized = diffieHellman->Init(args[0]->Int32Value(), + args[1]->Int32Value()); + } + } else { + if (args[1]->IsInt32()) { + initialized = diffieHellman->Init(Buffer::Data(args[0]), + Buffer::Length(args[0]), + args[1]->Int32Value()); + } else { + initialized = diffieHellman->Init(Buffer::Data(args[0]), + Buffer::Length(args[0]), + Buffer::Data(args[1]), + Buffer::Length(args[1])); + } + } + } + + if (!initialized) { + return ThrowCryptoError(env, ERR_get_error(), "Initialization failed"); + } +} + + +void DiffieHellman::GenerateKeys(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + DiffieHellman* diffieHellman; + ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder()); + + if (!diffieHellman->initialised_) { + return ThrowCryptoError(env, ERR_get_error(), "Not initialized"); + } + + if (!DH_generate_key(diffieHellman->dh)) { + return ThrowCryptoError(env, ERR_get_error(), "Key generation failed"); + } + + const BIGNUM *pub_key; + DH_get0_key(diffieHellman->dh, &pub_key, NULL); + int dataSize = BN_num_bytes(pub_key); + char* data = new char[dataSize]; + BN_bn2bin(pub_key, reinterpret_cast(data)); + + args.GetReturnValue().Set(Encode(env->isolate(), data, dataSize, BUFFER)); + delete[] data; +} + + +void DiffieHellman::GetPrime(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + DiffieHellman* diffieHellman; + ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder()); + + if (!diffieHellman->initialised_) { + return ThrowCryptoError(env, ERR_get_error(), "Not initialized"); + } + + const BIGNUM *p; + DH_get0_pqg(diffieHellman->dh, &p, NULL, NULL); + int dataSize = BN_num_bytes(p); + char* data = new char[dataSize]; + BN_bn2bin(p, reinterpret_cast(data)); + + args.GetReturnValue().Set(Encode(env->isolate(), data, dataSize, BUFFER)); + delete[] data; +} + + +void DiffieHellman::GetGenerator(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + DiffieHellman* diffieHellman; + ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder()); + + if (!diffieHellman->initialised_) { + return ThrowCryptoError(env, ERR_get_error(), "Not initialized"); + } + + const BIGNUM *g; + DH_get0_pqg(diffieHellman->dh, NULL, NULL, &g); + int dataSize = BN_num_bytes(g); + char* data = new char[dataSize]; + BN_bn2bin(g, reinterpret_cast(data)); + + args.GetReturnValue().Set(Encode(env->isolate(), data, dataSize, BUFFER)); + delete[] data; +} + + +void DiffieHellman::GetPublicKey(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + DiffieHellman* diffieHellman; + ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder()); + + if (!diffieHellman->initialised_) { + return ThrowCryptoError(env, ERR_get_error(), "Not initialized"); + } + + const BIGNUM *pub_key; + DH_get0_key(diffieHellman->dh, &pub_key, NULL); + if (pub_key == nullptr) { + return env->ThrowError("No public key - did you forget to generate one?"); + } + + int dataSize = BN_num_bytes(pub_key); + char* data = new char[dataSize]; + BN_bn2bin(pub_key, reinterpret_cast(data)); + + args.GetReturnValue().Set(Encode(env->isolate(), data, dataSize, BUFFER)); + delete[] data; +} + + +void DiffieHellman::GetPrivateKey(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + DiffieHellman* diffieHellman; + ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder()); + + if (!diffieHellman->initialised_) { + return ThrowCryptoError(env, ERR_get_error(), "Not initialized"); + } + + const BIGNUM *priv_key; + DH_get0_key(diffieHellman->dh, NULL, &priv_key); + if (priv_key == nullptr) { + return env->ThrowError("No private key - did you forget to generate one?"); + } + + int dataSize = BN_num_bytes(priv_key); + char* data = new char[dataSize]; + BN_bn2bin(priv_key, reinterpret_cast(data)); + + args.GetReturnValue().Set(Encode(env->isolate(), data, dataSize, BUFFER)); + delete[] data; +} + + +void DiffieHellman::ComputeSecret(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + DiffieHellman* diffieHellman; + ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder()); + + if (!diffieHellman->initialised_) { + return ThrowCryptoError(env, ERR_get_error(), "Not initialized"); + } + + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence compiler warning. + BIGNUM* key = nullptr; + + if (args.Length() == 0) { + return env->ThrowError("Other party's public key argument is mandatory"); + } else { + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Other party's public key"); + key = BN_bin2bn( + reinterpret_cast(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(data), + key, + diffieHellman->dh); + + if (size == -1) { + int checkResult; + int checked; + + checked = DH_check_pub_key(diffieHellman->dh, key, &checkResult); + BN_free(key); + delete[] data; + + if (!checked) { + return ThrowCryptoError(env, ERR_get_error(), "Invalid Key"); + } else if (checkResult) { + if (checkResult & DH_CHECK_PUBKEY_TOO_SMALL) { + return env->ThrowError("Supplied key is too small"); + } else if (checkResult & DH_CHECK_PUBKEY_TOO_LARGE) { + return env->ThrowError("Supplied key is too large"); + } else { + return env->ThrowError("Invalid key"); + } + } else { + return env->ThrowError("Invalid key"); + } + } + + BN_free(key); + CHECK_GE(size, 0); + + // DH_size returns number of bytes in a prime number + // DH_compute_key returns number of bytes in a remainder of exponent, which + // may have less bytes than a prime number. Therefore add 0-padding to the + // allocated buffer. + if (size != dataSize) { + CHECK(dataSize > size); + memmove(data + dataSize - size, data, size); + memset(data, 0, dataSize - size); + } + + args.GetReturnValue().Set(Encode(env->isolate(), data, dataSize, BUFFER)); + delete[] data; +} + + +void DiffieHellman::SetPublicKey(const FunctionCallbackInfo& args) { + DiffieHellman* diffieHellman; + ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder()); + Environment* env = diffieHellman->env(); + + if (!diffieHellman->initialised_) { + return ThrowCryptoError(env, ERR_get_error(), "Not initialized"); + } + + if (args.Length() == 0) { + return env->ThrowError("Public key argument is mandatory"); + } else { + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Public key"); + BIGNUM *pub_key = BN_bin2bn( + reinterpret_cast(Buffer::Data(args[0])), + Buffer::Length(args[0]), 0); + DH_set0_key(diffieHellman->dh, pub_key, NULL); + } +} + + +void DiffieHellman::SetPrivateKey(const FunctionCallbackInfo& args) { + DiffieHellman* diffieHellman; + ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder()); + Environment* env = diffieHellman->env(); + + if (!diffieHellman->initialised_) { + return ThrowCryptoError(env, ERR_get_error(), "Not initialized"); + } + + if (args.Length() == 0) { + return env->ThrowError("Private key argument is mandatory"); + } else { + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Private key"); + BIGNUM *priv_key = BN_bin2bn( + reinterpret_cast(Buffer::Data(args[0])), + Buffer::Length(args[0]), + 0); + DH_set0_key(diffieHellman->dh, NULL, priv_key); + } +} + + +void DiffieHellman::VerifyErrorGetter(Local property, + const PropertyCallbackInfo& args) { + HandleScope scope(args.GetIsolate()); + + DiffieHellman* diffieHellman; + ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder()); + + if (!diffieHellman->initialised_) + return ThrowCryptoError(diffieHellman->env(), ERR_get_error(), + "Not initialized"); + + args.GetReturnValue().Set(diffieHellman->verifyError_); +} + + +bool DiffieHellman::VerifyContext() { + int codes; + if (!DH_check(dh, &codes)) + return false; + verifyError_ = codes; + return true; +} + + +void ECDH::Initialize(Environment* env, Local target) { + HandleScope scope(env->isolate()); + + Local t = env->NewFunctionTemplate(New); + + t->InstanceTemplate()->SetInternalFieldCount(1); + + env->SetProtoMethod(t, "generateKeys", GenerateKeys); + env->SetProtoMethod(t, "computeSecret", ComputeSecret); + env->SetProtoMethod(t, "getPublicKey", GetPublicKey); + env->SetProtoMethod(t, "getPrivateKey", GetPrivateKey); + env->SetProtoMethod(t, "setPublicKey", SetPublicKey); + env->SetProtoMethod(t, "setPrivateKey", SetPrivateKey); + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "ECDH"), + t->GetFunction()); +} + + +void ECDH::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + MarkPopErrorOnReturn mark_pop_error_on_return; + + // TODO(indutny): Support raw curves? + THROW_AND_RETURN_IF_NOT_STRING(args[0], "ECDH curve name"); + node::Utf8Value curve(env->isolate(), args[0]); + + int nid = OBJ_sn2nid(*curve); + if (nid == NID_undef) + return env->ThrowTypeError("First argument should be a valid curve name"); + + EC_KEY* key = EC_KEY_new_by_curve_name(nid); + if (key == nullptr) + return env->ThrowError("Failed to create EC_KEY using curve name"); + + new ECDH(env, args.This(), key); +} + + +void ECDH::GenerateKeys(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + ECDH* ecdh; + ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder()); + + if (!EC_KEY_generate_key(ecdh->key_)) + return env->ThrowError("Failed to generate EC_KEY"); +} + + +EC_POINT* ECDH::BufferToPoint(char* data, size_t len) { + EC_POINT* pub; + int r; + + pub = EC_POINT_new(group_); + if (pub == nullptr) { + env()->ThrowError("Failed to allocate EC_POINT for a public key"); + return nullptr; + } + + r = EC_POINT_oct2point( + group_, + pub, + reinterpret_cast(data), + len, + nullptr); + if (!r) { + env()->ThrowError("Failed to translate Buffer to a EC_POINT"); + goto fatal; + } + + return pub; + + fatal: + EC_POINT_free(pub); + return nullptr; +} + + +void ECDH::ComputeSecret(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Data"); + + ECDH* ecdh; + ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder()); + + MarkPopErrorOnReturn mark_pop_error_on_return; + + if (!ecdh->IsKeyPairValid()) + return env->ThrowError("Invalid key pair"); + + EC_POINT* pub = ecdh->BufferToPoint(Buffer::Data(args[0]), + Buffer::Length(args[0])); + if (pub == nullptr) + return; + + // NOTE: field_size is in bits + int field_size = EC_GROUP_get_degree(ecdh->group_); + size_t out_len = (field_size + 7) / 8; + char* out = node::Malloc(out_len); + + int r = ECDH_compute_key(out, out_len, pub, ecdh->key_, nullptr); + EC_POINT_free(pub); + if (!r) { + free(out); + return env->ThrowError("Failed to compute ECDH key"); + } + + Local buf = Buffer::New(env, out, out_len).ToLocalChecked(); + args.GetReturnValue().Set(buf); +} + + +void ECDH::GetPublicKey(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + // Conversion form + CHECK_EQ(args.Length(), 1); + + ECDH* ecdh; + ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder()); + + const EC_POINT* pub = EC_KEY_get0_public_key(ecdh->key_); + if (pub == nullptr) + return env->ThrowError("Failed to get ECDH public key"); + + int size; + point_conversion_form_t form = + static_cast(args[0]->Uint32Value()); + + size = EC_POINT_point2oct(ecdh->group_, pub, form, nullptr, 0, nullptr); + if (size == 0) + return env->ThrowError("Failed to get public key length"); + + unsigned char* out = node::Malloc(size); + + int r = EC_POINT_point2oct(ecdh->group_, pub, form, out, size, nullptr); + if (r != size) { + free(out); + return env->ThrowError("Failed to get public key"); + } + + Local buf = + Buffer::New(env, reinterpret_cast(out), size).ToLocalChecked(); + args.GetReturnValue().Set(buf); +} + + +void ECDH::GetPrivateKey(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + ECDH* ecdh; + ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder()); + + const BIGNUM* b = EC_KEY_get0_private_key(ecdh->key_); + if (b == nullptr) + return env->ThrowError("Failed to get ECDH private key"); + + int size = BN_num_bytes(b); + unsigned char* out = node::Malloc(size); + + if (size != BN_bn2bin(b, out)) { + free(out); + return env->ThrowError("Failed to convert ECDH private key to Buffer"); + } + + Local buf = + Buffer::New(env, reinterpret_cast(out), size).ToLocalChecked(); + args.GetReturnValue().Set(buf); +} + + +void ECDH::SetPrivateKey(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + ECDH* ecdh; + ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder()); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Private key"); + + BIGNUM* priv = BN_bin2bn( + reinterpret_cast(Buffer::Data(args[0].As())), + Buffer::Length(args[0].As()), + nullptr); + if (priv == nullptr) + return env->ThrowError("Failed to convert Buffer to BN"); + + if (!ecdh->IsKeyValidForCurve(priv)) { + BN_free(priv); + return env->ThrowError("Private key is not valid for specified curve."); + } + + int result = EC_KEY_set_private_key(ecdh->key_, priv); + BN_free(priv); + + if (!result) { + return env->ThrowError("Failed to convert BN to a private key"); + } + + // To avoid inconsistency, clear the current public key in-case computing + // the new one fails for some reason. + EC_KEY_set_public_key(ecdh->key_, nullptr); + + MarkPopErrorOnReturn mark_pop_error_on_return; + (void) &mark_pop_error_on_return; // Silence compiler warning. + + const BIGNUM* priv_key = EC_KEY_get0_private_key(ecdh->key_); + CHECK_NE(priv_key, nullptr); + + EC_POINT* pub = EC_POINT_new(ecdh->group_); + CHECK_NE(pub, nullptr); + + if (!EC_POINT_mul(ecdh->group_, pub, priv_key, nullptr, nullptr, nullptr)) { + EC_POINT_free(pub); + return env->ThrowError("Failed to generate ECDH public key"); + } + + if (!EC_KEY_set_public_key(ecdh->key_, pub)) { + EC_POINT_free(pub); + return env->ThrowError("Failed to set generated public key"); + } + + EC_POINT_free(pub); +} + + +void ECDH::SetPublicKey(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + ECDH* ecdh; + ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder()); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Public key"); + + MarkPopErrorOnReturn mark_pop_error_on_return; + + EC_POINT* pub = ecdh->BufferToPoint(Buffer::Data(args[0].As()), + Buffer::Length(args[0].As())); + if (pub == nullptr) + return env->ThrowError("Failed to convert Buffer to EC_POINT"); + + int r = EC_KEY_set_public_key(ecdh->key_, pub); + EC_POINT_free(pub); + if (!r) + return env->ThrowError("Failed to set EC_POINT as the public key"); +} + + +bool ECDH::IsKeyValidForCurve(const BIGNUM* private_key) { + ASSERT_NE(group_, nullptr); + CHECK_NE(private_key, nullptr); + // Private keys must be in the range [1, n-1]. + // Ref: Section 3.2.1 - http://www.secg.org/sec1-v2.pdf + if (BN_cmp(private_key, BN_value_one()) < 0) { + return false; + } + BIGNUM* order = BN_new(); + CHECK_NE(order, nullptr); + bool result = EC_GROUP_get_order(group_, order, nullptr) && + BN_cmp(private_key, order) < 0; + BN_free(order); + return result; +} + + +bool ECDH::IsKeyPairValid() { + MarkPopErrorOnReturn mark_pop_error_on_return; + (void) &mark_pop_error_on_return; // Silence compiler warning. + return 1 == EC_KEY_check_key(key_); +} + + +class PBKDF2Request : public AsyncWrap { + public: + PBKDF2Request(Environment* env, + Local object, + const EVP_MD* digest, + int passlen, + char* pass, + int saltlen, + char* salt, + int iter, + int keylen) + : AsyncWrap(env, object, AsyncWrap::PROVIDER_PBKDF2REQUEST), + digest_(digest), + error_(0), + passlen_(passlen), + pass_(pass), + saltlen_(saltlen), + salt_(salt), + keylen_(keylen), + key_(node::Malloc(keylen)), + iter_(iter) { + Wrap(object, this); + } + + ~PBKDF2Request() override { + release(); + ClearWrap(object()); + persistent().Reset(); + } + + uv_work_t* work_req() { + return &work_req_; + } + + inline const EVP_MD* digest() const { + return digest_; + } + + inline int passlen() const { + return passlen_; + } + + inline char* pass() const { + return pass_; + } + + inline int saltlen() const { + return saltlen_; + } + + inline char* salt() const { + return salt_; + } + + inline int keylen() const { + return keylen_; + } + + inline char* key() const { + return key_; + } + + inline int iter() const { + return iter_; + } + + inline void release() { + free(pass_); + pass_ = nullptr; + passlen_ = 0; + + free(salt_); + salt_ = nullptr; + saltlen_ = 0; + + free(key_); + key_ = nullptr; + keylen_ = 0; + } + + inline int error() const { + return error_; + } + + inline void set_error(int err) { + error_ = err; + } + + size_t self_size() const override { return sizeof(*this); } + + uv_work_t work_req_; + + private: + const EVP_MD* digest_; + int error_; + int passlen_; + char* pass_; + int saltlen_; + char* salt_; + int keylen_; + char* key_; + int iter_; +}; + + +void EIO_PBKDF2(PBKDF2Request* req) { + req->set_error(PKCS5_PBKDF2_HMAC( + req->pass(), + req->passlen(), + reinterpret_cast(req->salt()), + req->saltlen(), + req->iter(), + req->digest(), + req->keylen(), + reinterpret_cast(req->key()))); + OPENSSL_cleanse(req->pass(), req->passlen()); + OPENSSL_cleanse(req->salt(), req->saltlen()); +} + + +void EIO_PBKDF2(uv_work_t* work_req) { + PBKDF2Request* req = ContainerOf(&PBKDF2Request::work_req_, work_req); + EIO_PBKDF2(req); +} + + +void EIO_PBKDF2After(PBKDF2Request* req, Local argv[2]) { + if (req->error()) { + argv[0] = Undefined(req->env()->isolate()); + argv[1] = Encode(req->env()->isolate(), req->key(), req->keylen(), BUFFER); + OPENSSL_cleanse(req->key(), req->keylen()); + } else { + argv[0] = Exception::Error(req->env()->pbkdf2_error_string()); + argv[1] = Undefined(req->env()->isolate()); + } +} + + +void EIO_PBKDF2After(uv_work_t* work_req, int status) { + CHECK_EQ(status, 0); + PBKDF2Request* req = ContainerOf(&PBKDF2Request::work_req_, work_req); + Environment* env = req->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + Local argv[2]; + EIO_PBKDF2After(req, argv); + req->MakeCallback(env->ondone_string(), arraysize(argv), argv); + delete req; +} + + +void PBKDF2(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + const EVP_MD* digest = nullptr; + const char* type_error = nullptr; + char* pass = nullptr; + char* salt = nullptr; + int passlen = -1; + int saltlen = -1; + double raw_keylen = -1; + int keylen = -1; + int iter = -1; + PBKDF2Request* req = nullptr; + Local obj; + + if (args.Length() != 5 && args.Length() != 6) { + type_error = "Bad parameter"; + goto err; + } + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Pass phrase"); + passlen = Buffer::Length(args[0]); + if (passlen < 0) { + type_error = "Bad password"; + goto err; + } + + THROW_AND_RETURN_IF_NOT_BUFFER(args[1], "Salt"); + + pass = node::Malloc(passlen); + memcpy(pass, Buffer::Data(args[0]), passlen); + + saltlen = Buffer::Length(args[1]); + if (saltlen < 0) { + type_error = "Bad salt"; + goto err; + } + + salt = node::Malloc(saltlen); + memcpy(salt, Buffer::Data(args[1]), saltlen); + + if (!args[2]->IsNumber()) { + type_error = "Iterations not a number"; + goto err; + } + + iter = args[2]->Int32Value(); + if (iter < 0) { + type_error = "Bad iterations"; + goto err; + } + + if (!args[3]->IsNumber()) { + type_error = "Key length not a number"; + goto err; + } + + raw_keylen = args[3]->NumberValue(); + if (raw_keylen < 0.0 || isnan(raw_keylen) || isinf(raw_keylen) || + raw_keylen > INT_MAX) { + type_error = "Bad key length"; + goto err; + } + + keylen = static_cast(raw_keylen); + + if (args[4]->IsString()) { + node::Utf8Value digest_name(env->isolate(), args[4]); + digest = EVP_get_digestbyname(*digest_name); + if (digest == nullptr) { + type_error = "Bad digest name"; + goto err; + } + } + + if (digest == nullptr) { + digest = EVP_sha1(); + } + + obj = env->pbkdf2_constructor_template()-> + NewInstance(env->context()).ToLocalChecked(); + req = new PBKDF2Request(env, + obj, + digest, + passlen, + pass, + saltlen, + salt, + iter, + keylen); + + if (args[5]->IsFunction()) { + obj->Set(env->ondone_string(), args[5]); + + if (env->in_domain()) + obj->Set(env->domain_string(), env->domain_array()->Get(0)); + uv_queue_work(env->event_loop(), + req->work_req(), + EIO_PBKDF2, + EIO_PBKDF2After); + } else { + env->PrintSyncTrace(); + Local argv[2]; + EIO_PBKDF2(req); + EIO_PBKDF2After(req, argv); + + delete req; + + if (argv[0]->IsObject()) + env->isolate()->ThrowException(argv[0]); + else + args.GetReturnValue().Set(argv[1]); + } + return; + + err: + free(salt); + free(pass); + return env->ThrowTypeError(type_error); +} + + +// Only instantiate within a valid HandleScope. +class RandomBytesRequest : public AsyncWrap { + public: + enum FreeMode { FREE_DATA, DONT_FREE_DATA }; + + RandomBytesRequest(Environment* env, + Local object, + size_t size, + char* data, + FreeMode free_mode) + : AsyncWrap(env, object, AsyncWrap::PROVIDER_RANDOMBYTESREQUEST), + error_(0), + size_(size), + data_(data), + free_mode_(free_mode) { + Wrap(object, this); + } + + ~RandomBytesRequest() override { + ClearWrap(object()); + persistent().Reset(); + } + + uv_work_t* work_req() { + return &work_req_; + } + + inline size_t size() const { + return size_; + } + + inline char* data() const { + return data_; + } + + inline void set_data(char* data) { + data_ = data; + } + + inline void release() { + size_ = 0; + if (free_mode_ == FREE_DATA) { + free(data_); + } + } + + inline void return_memory(char** d, size_t* len) { + *d = data_; + data_ = nullptr; + *len = size_; + size_ = 0; + } + + inline unsigned long error() const { // NOLINT(runtime/int) + return error_; + } + + inline void set_error(unsigned long err) { // NOLINT(runtime/int) + error_ = err; + } + + size_t self_size() const override { return sizeof(*this); } + + uv_work_t work_req_; + + private: + unsigned long error_; // NOLINT(runtime/int) + size_t size_; + char* data_; + const FreeMode free_mode_; +}; + + +void RandomBytesWork(uv_work_t* work_req) { + RandomBytesRequest* req = + ContainerOf(&RandomBytesRequest::work_req_, work_req); + + // Ensure that OpenSSL's PRNG is properly seeded. + CheckEntropy(); + + const int r = RAND_bytes(reinterpret_cast(req->data()), + req->size()); + + // RAND_bytes() returns 0 on error. + if (r == 0) { + req->set_error(ERR_get_error()); // NOLINT(runtime/int) + } else if (r == -1) { + req->set_error(static_cast(-1)); // NOLINT(runtime/int) + } +} + + +// don't call this function without a valid HandleScope +void RandomBytesCheck(RandomBytesRequest* req, Local argv[2]) { + if (req->error()) { + char errmsg[256] = "Operation not supported"; + + if (req->error() != static_cast(-1)) // NOLINT(runtime/int) + ERR_error_string_n(req->error(), errmsg, sizeof errmsg); + + argv[0] = Exception::Error(OneByteString(req->env()->isolate(), errmsg)); + argv[1] = Null(req->env()->isolate()); + req->release(); + } else { + char* data = nullptr; + size_t size; + req->return_memory(&data, &size); + argv[0] = Null(req->env()->isolate()); + Local buffer = + req->object()->Get(req->env()->context(), + req->env()->buffer_string()).ToLocalChecked(); + + if (buffer->IsUint8Array()) { + CHECK_LE(req->size(), Buffer::Length(buffer)); + char* buf = Buffer::Data(buffer); + memcpy(buf, data, req->size()); + argv[1] = buffer; + } else { + argv[1] = Buffer::New(req->env(), data, size).ToLocalChecked(); + } + } +} + + +void RandomBytesAfter(uv_work_t* work_req, int status) { + CHECK_EQ(status, 0); + RandomBytesRequest* req = + ContainerOf(&RandomBytesRequest::work_req_, work_req); + Environment* env = req->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + Local argv[2]; + RandomBytesCheck(req, argv); + req->MakeCallback(env->ondone_string(), arraysize(argv), argv); + delete req; +} + + +void RandomBytesProcessSync(Environment* env, + RandomBytesRequest* req, + Local argv[2]) { + env->PrintSyncTrace(); + RandomBytesWork(req->work_req()); + RandomBytesCheck(req, argv); + delete req; + + if (!argv[0]->IsNull()) + env->isolate()->ThrowException(argv[0]); +} + + +void RandomBytes(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (!args[0]->IsUint32()) { + return env->ThrowTypeError("size must be a number >= 0"); + } + + const int64_t size = args[0]->IntegerValue(); + if (size < 0 || size > Buffer::kMaxLength) + return env->ThrowRangeError("size is not a valid Smi"); + + Local obj = env->randombytes_constructor_template()-> + NewInstance(env->context()).ToLocalChecked(); + char* data = node::Malloc(size); + RandomBytesRequest* req = + new RandomBytesRequest(env, + obj, + size, + data, + RandomBytesRequest::FREE_DATA); + + if (args[1]->IsFunction()) { + obj->Set(env->ondone_string(), args[1]); + + if (env->in_domain()) + obj->Set(env->domain_string(), env->domain_array()->Get(0)); + uv_queue_work(env->event_loop(), + req->work_req(), + RandomBytesWork, + RandomBytesAfter); + args.GetReturnValue().Set(obj); + } else { + Local argv[2]; + RandomBytesProcessSync(env, req, argv); + if (argv[0]->IsNull()) + args.GetReturnValue().Set(argv[1]); + } +} + + +void RandomBytesBuffer(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK(args[0]->IsUint8Array()); + CHECK(args[1]->IsUint32()); + CHECK(args[2]->IsUint32()); + + int64_t offset = args[1]->IntegerValue(); + int64_t size = args[2]->IntegerValue(); + + Local obj = env->randombytes_constructor_template()-> + NewInstance(env->context()).ToLocalChecked(); + obj->Set(env->context(), env->buffer_string(), args[0]).FromJust(); + char* data = Buffer::Data(args[0]); + data += offset; + + RandomBytesRequest* req = + new RandomBytesRequest(env, + obj, + size, + data, + RandomBytesRequest::DONT_FREE_DATA); + if (args[3]->IsFunction()) { + obj->Set(env->context(), + FIXED_ONE_BYTE_STRING(args.GetIsolate(), "ondone"), + args[3]).FromJust(); + + if (env->in_domain()) { + obj->Set(env->context(), + env->domain_string(), + env->domain_array()->Get(0)).FromJust(); + } + + uv_queue_work(env->event_loop(), + req->work_req(), + RandomBytesWork, + RandomBytesAfter); + args.GetReturnValue().Set(obj); + } else { + Local argv[2]; + RandomBytesProcessSync(env, req, argv); + if (argv[0]->IsNull()) + args.GetReturnValue().Set(argv[1]); + } +} + + +void GetSSLCiphers(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + SSL_CTX* ctx = SSL_CTX_new(TLSv1_server_method()); + if (ctx == nullptr) { + return env->ThrowError("SSL_CTX_new() failed."); + } + + SSL* ssl = SSL_new(ctx); + if (ssl == nullptr) { + SSL_CTX_free(ctx); + return env->ThrowError("SSL_new() failed."); + } + + Local arr = Array::New(env->isolate()); + STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(ssl); + + for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); ++i) { + const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i); + arr->Set(i, OneByteString(args.GetIsolate(), SSL_CIPHER_get_name(cipher))); + } + + SSL_free(ssl); + SSL_CTX_free(ctx); + + args.GetReturnValue().Set(arr); +} + + +class CipherPushContext { + public: + explicit CipherPushContext(Environment* env) + : arr(Array::New(env->isolate())), + env_(env) { + } + + inline Environment* env() const { return env_; } + + Local arr; + + private: + Environment* env_; +}; + + +template +static void array_push_back(const TypeName* md, + const char* from, + const char* to, + void* arg) { + CipherPushContext* ctx = static_cast(arg); + ctx->arr->Set(ctx->arr->Length(), OneByteString(ctx->env()->isolate(), from)); +} + + +void GetCiphers(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CipherPushContext ctx(env); + EVP_CIPHER_do_all_sorted(array_push_back, &ctx); + args.GetReturnValue().Set(ctx.arr); +} + + +void GetHashes(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CipherPushContext ctx(env); + EVP_MD_do_all_sorted(array_push_back, &ctx); + args.GetReturnValue().Set(ctx.arr); +} + + +void GetCurves(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + const size_t num_curves = EC_get_builtin_curves(nullptr, 0); + Local arr = Array::New(env->isolate(), num_curves); + EC_builtin_curve* curves; + + if (num_curves) { + curves = node::Malloc(num_curves); + + if (EC_get_builtin_curves(curves, num_curves)) { + for (size_t i = 0; i < num_curves; i++) { + arr->Set(i, OneByteString(env->isolate(), OBJ_nid2sn(curves[i].nid))); + } + } + + free(curves); + } + + args.GetReturnValue().Set(arr); +} + + +bool VerifySpkac(const char* data, unsigned int len) { + bool i = 0; + EVP_PKEY* pkey = nullptr; + NETSCAPE_SPKI* spki = nullptr; + + spki = NETSCAPE_SPKI_b64_decode(data, len); + if (spki == nullptr) + goto exit; + + pkey = X509_PUBKEY_get(spki->spkac->pubkey); + if (pkey == nullptr) + goto exit; + + i = NETSCAPE_SPKI_verify(spki, pkey) > 0; + + exit: + if (pkey != nullptr) + EVP_PKEY_free(pkey); + + if (spki != nullptr) + NETSCAPE_SPKI_free(spki); + + return i; +} + + +void VerifySpkac(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + bool i = false; + + if (args.Length() < 1) + return env->ThrowTypeError("Data argument is mandatory"); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Data"); + + size_t length = Buffer::Length(args[0]); + if (length == 0) + return args.GetReturnValue().Set(i); + + char* data = Buffer::Data(args[0]); + CHECK_NE(data, nullptr); + + i = VerifySpkac(data, length); + + args.GetReturnValue().Set(i); +} + + +const char* ExportPublicKey(const char* data, int len) { + char* buf = nullptr; + EVP_PKEY* pkey = nullptr; + NETSCAPE_SPKI* spki = nullptr; + + BIO* bio = BIO_new(BIO_s_mem()); + if (bio == nullptr) + goto exit; + + spki = NETSCAPE_SPKI_b64_decode(data, len); + if (spki == nullptr) + goto exit; + + pkey = NETSCAPE_SPKI_get_pubkey(spki); + if (pkey == nullptr) + goto exit; + + if (PEM_write_bio_PUBKEY(bio, pkey) <= 0) + goto exit; + + BIO_write(bio, "\0", 1); + BUF_MEM* ptr; + BIO_get_mem_ptr(bio, &ptr); + + buf = new char[ptr->length]; + memcpy(buf, ptr->data, ptr->length); + + exit: + if (pkey != nullptr) + EVP_PKEY_free(pkey); + + if (spki != nullptr) + NETSCAPE_SPKI_free(spki); + + if (bio != nullptr) + BIO_free_all(bio); + + return buf; +} + + +void ExportPublicKey(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (args.Length() < 1) + return env->ThrowTypeError("Public key argument is mandatory"); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Public key"); + + size_t length = Buffer::Length(args[0]); + if (length == 0) + return args.GetReturnValue().SetEmptyString(); + + char* data = Buffer::Data(args[0]); + CHECK_NE(data, nullptr); + + const char* pkey = ExportPublicKey(data, length); + if (pkey == nullptr) + return args.GetReturnValue().SetEmptyString(); + + Local out = Encode(env->isolate(), pkey, strlen(pkey), BUFFER); + + delete[] pkey; + + args.GetReturnValue().Set(out); +} + + +const char* ExportChallenge(const char* data, int len) { + NETSCAPE_SPKI* sp = nullptr; + + sp = NETSCAPE_SPKI_b64_decode(data, len); + if (sp == nullptr) + return nullptr; + + unsigned char* buf = nullptr; + ASN1_STRING_to_UTF8(&buf, sp->spkac->challenge); + + NETSCAPE_SPKI_free(sp); + + return reinterpret_cast(buf); +} + + +void ExportChallenge(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (args.Length() < 1) + return env->ThrowTypeError("Challenge argument is mandatory"); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Challenge"); + + size_t len = Buffer::Length(args[0]); + if (len == 0) + return args.GetReturnValue().SetEmptyString(); + + char* data = Buffer::Data(args[0]); + CHECK_NE(data, nullptr); + + const char* cert = ExportChallenge(data, len); + if (cert == nullptr) + return args.GetReturnValue().SetEmptyString(); + + Local outString = Encode(env->isolate(), cert, strlen(cert), BUFFER); + + OPENSSL_free(const_cast(cert)); + + args.GetReturnValue().Set(outString); +} + +void TimingSafeEqual(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "First argument"); + THROW_AND_RETURN_IF_NOT_BUFFER(args[1], "Second argument"); + + size_t buf_length = Buffer::Length(args[0]); + if (buf_length != Buffer::Length(args[1])) { + return env->ThrowTypeError("Input buffers must have the same length"); + } + + const char* buf1 = Buffer::Data(args[0]); + const char* buf2 = Buffer::Data(args[1]); + + return args.GetReturnValue().Set(CRYPTO_memcmp(buf1, buf2, buf_length) == 0); +} + +void InitCryptoOnce() { + SSL_load_error_strings(); + OPENSSL_no_config(); + + // --openssl-config=... + if (!openssl_config.empty()) { + OPENSSL_load_builtin_modules(); +#ifndef OPENSSL_NO_ENGINE + ENGINE_load_builtin_engines(); +#endif + ERR_clear_error(); + CONF_modules_load_file( + openssl_config.c_str(), + nullptr, + CONF_MFLAGS_DEFAULT_SECTION); + int err = ERR_get_error(); + if (0 != err) { + fprintf(stderr, + "openssl config failed: %s\n", + ERR_error_string(err, NULL)); + CHECK_NE(err, 0); + } + } + + SSL_library_init(); + OpenSSL_add_all_algorithms(); + + crypto_lock_init(); + CRYPTO_set_locking_callback(crypto_lock_cb); + CRYPTO_THREADID_set_callback(crypto_threadid_cb); + +#ifdef NODE_FIPS_MODE + /* Override FIPS settings in cnf file, if needed. */ + unsigned long err = 0; // NOLINT(runtime/int) + if (enable_fips_crypto || force_fips_crypto) { + if (0 == FIPS_mode() && !FIPS_mode_set(1)) { + err = ERR_get_error(); + } + } + if (0 != err) { + fprintf(stderr, "openssl fips failed: %s\n", ERR_error_string(err, NULL)); + UNREACHABLE(); + } +#endif // NODE_FIPS_MODE + + + // Turn off compression. Saves memory and protects against CRIME attacks. + // No-op with OPENSSL_NO_COMP builds of OpenSSL. + sk_SSL_COMP_zero(SSL_COMP_get_compression_methods()); + +#ifndef OPENSSL_NO_ENGINE + ERR_load_ENGINE_strings(); + ENGINE_load_builtin_engines(); +#endif // !OPENSSL_NO_ENGINE +} + + +#ifndef OPENSSL_NO_ENGINE +void SetEngine(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args.Length() >= 2 && args[0]->IsString()); + unsigned int flags = args[1]->Uint32Value(); + + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence compiler warning. + + const node::Utf8Value engine_id(env->isolate(), args[0]); + ENGINE* engine = ENGINE_by_id(*engine_id); + + // Engine not found, try loading dynamically + if (engine == nullptr) { + engine = ENGINE_by_id("dynamic"); + if (engine != nullptr) { + if (!ENGINE_ctrl_cmd_string(engine, "SO_PATH", *engine_id, 0) || + !ENGINE_ctrl_cmd_string(engine, "LOAD", nullptr, 0)) { + ENGINE_free(engine); + engine = nullptr; + } + } + } + + if (engine == nullptr) { + int err = ERR_get_error(); + if (err == 0) { + char tmp[1024]; + snprintf(tmp, sizeof(tmp), "Engine \"%s\" was not found", *engine_id); + return env->ThrowError(tmp); + } else { + return ThrowCryptoError(env, err); + } + } + + int r = ENGINE_set_default(engine, flags); + ENGINE_free(engine); + if (r == 0) + return ThrowCryptoError(env, ERR_get_error()); +} +#endif // !OPENSSL_NO_ENGINE + +void GetFipsCrypto(const FunctionCallbackInfo& args) { + if (FIPS_mode()) { + args.GetReturnValue().Set(1); + } else { + args.GetReturnValue().Set(0); + } +} + +void SetFipsCrypto(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); +#ifdef NODE_FIPS_MODE + bool mode = args[0]->BooleanValue(); + if (force_fips_crypto) { + return env->ThrowError( + "Cannot set FIPS mode, it was forced with --force-fips at startup."); + } else if (!FIPS_mode_set(mode)) { + unsigned long err = ERR_get_error(); // NOLINT(runtime/int) + return ThrowCryptoError(env, err); + } +#else + return env->ThrowError("Cannot set FIPS mode in a non-FIPS build."); +#endif /* NODE_FIPS_MODE */ +} + +void InitCrypto(Local target, + Local unused, + Local context, + void* priv) { + static uv_once_t init_once = UV_ONCE_INIT; + uv_once(&init_once, InitCryptoOnce); + + Environment* env = Environment::GetCurrent(context); + SecureContext::Initialize(env, target); + Connection::Initialize(env, target); + CipherBase::Initialize(env, target); + DiffieHellman::Initialize(env, target); + ECDH::Initialize(env, target); + Hmac::Initialize(env, target); + Hash::Initialize(env, target); + Sign::Initialize(env, target); + Verify::Initialize(env, target); + + env->SetMethod(target, "certVerifySpkac", VerifySpkac); + env->SetMethod(target, "certExportPublicKey", ExportPublicKey); + env->SetMethod(target, "certExportChallenge", ExportChallenge); +#ifndef OPENSSL_NO_ENGINE + env->SetMethod(target, "setEngine", SetEngine); +#endif // !OPENSSL_NO_ENGINE + env->SetMethod(target, "getFipsCrypto", GetFipsCrypto); + env->SetMethod(target, "setFipsCrypto", SetFipsCrypto); + env->SetMethod(target, "PBKDF2", PBKDF2); + env->SetMethod(target, "randomBytes", RandomBytes); + env->SetMethod(target, "randomFill", RandomBytesBuffer); + env->SetMethod(target, "timingSafeEqual", TimingSafeEqual); + env->SetMethod(target, "getSSLCiphers", GetSSLCiphers); + env->SetMethod(target, "getCiphers", GetCiphers); + env->SetMethod(target, "getHashes", GetHashes); + env->SetMethod(target, "getCurves", GetCurves); + env->SetMethod(target, "publicEncrypt", + PublicKeyCipher::Cipher); + env->SetMethod(target, "privateDecrypt", + PublicKeyCipher::Cipher); + env->SetMethod(target, "privateEncrypt", + PublicKeyCipher::Cipher); + env->SetMethod(target, "publicDecrypt", + PublicKeyCipher::Cipher); + + Local pb = FunctionTemplate::New(env->isolate()); + pb->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "PBKDF2")); + env->SetProtoMethod(pb, "getAsyncId", AsyncWrap::GetAsyncId); + Local pbt = pb->InstanceTemplate(); + pbt->SetInternalFieldCount(1); + env->set_pbkdf2_constructor_template(pbt); + + Local rb = FunctionTemplate::New(env->isolate()); + rb->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "RandomBytes")); + env->SetProtoMethod(rb, "getAsyncId", AsyncWrap::GetAsyncId); + Local rbt = rb->InstanceTemplate(); + rbt->SetInternalFieldCount(1); + env->set_randombytes_constructor_template(rbt); +} + +} // namespace crypto +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(crypto, node::crypto::InitCrypto) diff --git a/src/crypto_impl/openssl/1_1_0f/node_crypto.h b/src/crypto_impl/openssl/1_1_0f/node_crypto.h new file mode 100644 index 00000000000000..fe1d7df16a4c93 --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto.h @@ -0,0 +1,747 @@ +// 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. + +#ifndef SRC_CRYPTO_IMPL_NODE_CRYPTO_H_ +#define SRC_CRYPTO_IMPL_NODE_CRYPTO_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node.h" +#include "node_crypto_clienthello.h" // ClientHelloParser +#include "node_crypto_clienthello-inl.h" + +#include "node_buffer.h" + +#include "env.h" +#include "async-wrap.h" +#include "async-wrap-inl.h" +#include "base-object.h" +#include "base-object-inl.h" + +#include "v8.h" + +#include +#include +#include +#ifndef OPENSSL_NO_ENGINE +# include +#endif // !OPENSSL_NO_ENGINE +#include +#include +#include +#include +#include +#include +#include +#include + +#define EVP_F_EVP_DECRYPTFINAL 101 + +#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) +# define NODE__HAVE_TLSEXT_STATUS_CB +#endif // !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) + +namespace node { +namespace crypto { + +// 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(); } +}; + +// Pop errors from OpenSSL's error stack that were added +// between when this was constructed and destructed. +struct MarkPopErrorOnReturn { + MarkPopErrorOnReturn() { ERR_set_mark(); } + ~MarkPopErrorOnReturn() { ERR_pop_to_mark(); } +}; + +enum CheckResult { + CHECK_CERT_REVOKED = 0, + CHECK_OK = 1 +}; + +extern int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx); + +extern void UseExtraCaCerts(const std::string& file); + +class SecureContext : public BaseObject { + public: + ~SecureContext() override { + FreeCTXMem(); + } + + static void Initialize(Environment* env, v8::Local target); + + SSL_CTX* ctx_; + X509* cert_; + X509* issuer_; + + static const int kMaxSessionSize = 10 * 1024; + + // See TicketKeyCallback + static const int kTicketKeyReturnIndex = 0; + static const int kTicketKeyHMACIndex = 1; + static const int kTicketKeyAESIndex = 2; + static const int kTicketKeyNameIndex = 3; + static const int kTicketKeyIVIndex = 4; + + protected: + + static void New(const v8::FunctionCallbackInfo& args); + static void Init(const v8::FunctionCallbackInfo& args); + static void SetKey(const v8::FunctionCallbackInfo& args); + static void SetCert(const v8::FunctionCallbackInfo& args); + static void AddCACert(const v8::FunctionCallbackInfo& args); + static void AddCRL(const v8::FunctionCallbackInfo& args); + static void AddRootCerts(const v8::FunctionCallbackInfo& args); + static void SetCiphers(const v8::FunctionCallbackInfo& args); + static void SetECDHCurve(const v8::FunctionCallbackInfo& args); + static void SetDHParam(const v8::FunctionCallbackInfo& args); + static void SetOptions(const v8::FunctionCallbackInfo& args); + static void SetSessionIdContext( + const v8::FunctionCallbackInfo& args); + static void SetSessionTimeout( + const v8::FunctionCallbackInfo& args); + static void Close(const v8::FunctionCallbackInfo& args); + static void LoadPKCS12(const v8::FunctionCallbackInfo& args); + static void GetTicketKeys(const v8::FunctionCallbackInfo& args); + static void SetTicketKeys(const v8::FunctionCallbackInfo& args); + static void EnableTicketKeyCallback( + const v8::FunctionCallbackInfo& args); + static void CtxGetter(v8::Local property, + const v8::PropertyCallbackInfo& info); + + template + static void GetCertificate(const v8::FunctionCallbackInfo& args); + + static int TicketKeyCallback(SSL* ssl, + unsigned char* name, + unsigned char* iv, + EVP_CIPHER_CTX* ectx, + HMAC_CTX* hctx, + int enc); + + SecureContext(Environment* env, v8::Local wrap) + : BaseObject(env, wrap), + ctx_(nullptr), + cert_(nullptr), + issuer_(nullptr) { + MakeWeak(this); + } + + void FreeCTXMem() { + if (!ctx_) { + return; + } + + SSL_CTX_free(ctx_); + if (cert_ != nullptr) + X509_free(cert_); + if (issuer_ != nullptr) + X509_free(issuer_); + ctx_ = nullptr; + cert_ = nullptr; + issuer_ = nullptr; + } +}; + +// SSLWrap implicitly depends on the inheriting class' handle having an +// internal pointer to the Base class. +template +class SSLWrap { + public: + enum Kind { + kClient, + kServer + }; + + SSLWrap(Environment* env, SecureContext* sc, Kind kind) + : env_(env), + kind_(kind), + next_sess_(nullptr), + session_callbacks_(false), + new_session_wait_(false), + cert_cb_(nullptr), + cert_cb_arg_(nullptr), + cert_cb_running_(false) { + ssl_ = SSL_new(sc->ctx_); + CHECK_NE(ssl_, nullptr); + } + + virtual ~SSLWrap() { + DestroySSL(); + if (next_sess_ != nullptr) { + SSL_SESSION_free(next_sess_); + next_sess_ = nullptr; + } + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + sni_context_.Reset(); +#endif + +#ifdef NODE__HAVE_TLSEXT_STATUS_CB + ocsp_response_.Reset(); +#endif // NODE__HAVE_TLSEXT_STATUS_CB + } + + inline SSL* ssl() const { return ssl_; } + inline void enable_session_callbacks() { session_callbacks_ = true; } + inline bool is_server() const { return kind_ == kServer; } + inline bool is_client() const { return kind_ == kClient; } + inline bool is_waiting_new_session() const { return new_session_wait_; } + inline bool is_waiting_cert_cb() const { return cert_cb_ != nullptr; } + + protected: + typedef void (*CertCb)(void* arg); + + static void InitNPN(SecureContext* sc); + static void AddMethods(Environment* env, v8::Local t); + + static SSL_SESSION* GetSessionCallback(SSL* s, + const unsigned char* key, + int len, + int* copy); + static int NewSessionCallback(SSL* s, SSL_SESSION* sess); + static void OnClientHello(void* arg, + const ClientHelloParser::ClientHello& hello); + + static void GetPeerCertificate( + const v8::FunctionCallbackInfo& args); + static void GetSession(const v8::FunctionCallbackInfo& args); + static void SetSession(const v8::FunctionCallbackInfo& args); + static void LoadSession(const v8::FunctionCallbackInfo& args); + static void IsSessionReused(const v8::FunctionCallbackInfo& args); + static void IsInitFinished(const v8::FunctionCallbackInfo& args); + static void VerifyError(const v8::FunctionCallbackInfo& args); + static void GetCurrentCipher(const v8::FunctionCallbackInfo& args); + static void EndParser(const v8::FunctionCallbackInfo& args); + static void CertCbDone(const v8::FunctionCallbackInfo& args); + static void Renegotiate(const v8::FunctionCallbackInfo& args); + static void Shutdown(const v8::FunctionCallbackInfo& args); + static void GetTLSTicket(const v8::FunctionCallbackInfo& args); + static void NewSessionDone(const v8::FunctionCallbackInfo& args); + static void SetOCSPResponse(const v8::FunctionCallbackInfo& args); + static void RequestOCSP(const v8::FunctionCallbackInfo& args); + static void GetEphemeralKeyInfo( + const v8::FunctionCallbackInfo& args); + static void GetProtocol(const v8::FunctionCallbackInfo& args); + +#ifdef SSL_set_max_send_fragment + static void SetMaxSendFragment( + const v8::FunctionCallbackInfo& args); +#endif // SSL_set_max_send_fragment + +#ifndef OPENSSL_NO_NEXTPROTONEG + static void GetNegotiatedProto( + const v8::FunctionCallbackInfo& args); + static void SetNPNProtocols(const v8::FunctionCallbackInfo& args); + static int AdvertiseNextProtoCallback(SSL* s, + const unsigned char** data, + unsigned int* len, + void* arg); + static int SelectNextProtoCallback(SSL* s, + unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg); +#endif // OPENSSL_NO_NEXTPROTONEG + + static void GetALPNNegotiatedProto( + const v8::FunctionCallbackInfo& args); + static void SetALPNProtocols(const v8::FunctionCallbackInfo& args); + static int SelectALPNCallback(SSL* s, + const unsigned char** out, + unsigned char* outlen, + const unsigned char* in, + unsigned int inlen, + void* arg); + static int TLSExtStatusCallback(SSL* s, void* arg); + static int SSLCertCallback(SSL* s, void* arg); + static void SSLGetter(v8::Local property, + const v8::PropertyCallbackInfo& info); + + void DestroySSL(); + void WaitForCertCb(CertCb cb, void* arg); + void SetSNIContext(SecureContext* sc); + int SetCACerts(SecureContext* sc); + + inline Environment* ssl_env() const { + return env_; + } + + Environment* const env_; + Kind kind_; + SSL_SESSION* next_sess_; + SSL* ssl_; + bool session_callbacks_; + bool new_session_wait_; + + // SSL_set_cert_cb + CertCb cert_cb_; + void* cert_cb_arg_; + bool cert_cb_running_; + + ClientHelloParser hello_parser_; + +#ifdef NODE__HAVE_TLSEXT_STATUS_CB + v8::Persistent ocsp_response_; +#endif // NODE__HAVE_TLSEXT_STATUS_CB + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + v8::Persistent sni_context_; +#endif + + friend class SecureContext; +}; + +// Connection inherits from AsyncWrap because SSLWrap makes calls to +// MakeCallback, but SSLWrap doesn't store the handle itself. Instead it +// assumes that any args.This() called will be the handle from Connection. +class Connection : public AsyncWrap, public SSLWrap { + public: + ~Connection() override { +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + sniObject_.Reset(); + servername_.Reset(); +#endif + } + + static void Initialize(Environment* env, v8::Local target); + void NewSessionDoneCb(); + +#ifndef OPENSSL_NO_NEXTPROTONEG + v8::Persistent npnProtos_; + v8::Persistent selectedNPNProto_; +#endif + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + v8::Persistent sniObject_; + v8::Persistent servername_; +#endif + + size_t self_size() const override { return sizeof(*this); } + + protected: + static void New(const v8::FunctionCallbackInfo& args); + static void EncIn(const v8::FunctionCallbackInfo& args); + static void ClearOut(const v8::FunctionCallbackInfo& args); + static void ClearPending(const v8::FunctionCallbackInfo& args); + static void EncPending(const v8::FunctionCallbackInfo& args); + static void EncOut(const v8::FunctionCallbackInfo& args); + static void ClearIn(const v8::FunctionCallbackInfo& args); + static void Start(const v8::FunctionCallbackInfo& args); + static void Close(const v8::FunctionCallbackInfo& args); + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + // SNI + static void GetServername(const v8::FunctionCallbackInfo& args); + static void SetSNICallback(const v8::FunctionCallbackInfo& args); + static int SelectSNIContextCallback_(SSL* s, int* ad, void* arg); +#endif + + static void OnClientHelloParseEnd(void* arg); + + int HandleBIOError(BIO* bio, const char* func, int rv); + + enum ZeroStatus { + kZeroIsNotAnError, + kZeroIsAnError + }; + + enum SyscallStatus { + kIgnoreSyscall, + kSyscallError + }; + + int HandleSSLError(const char* func, int rv, ZeroStatus zs, SyscallStatus ss); + + void ClearError(); + void SetShutdownFlags(); + + Connection(Environment* env, + v8::Local wrap, + SecureContext* sc, + SSLWrap::Kind kind) + : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_SSLCONNECTION), + SSLWrap(env, sc, kind), + bio_read_(nullptr), + bio_write_(nullptr), + hello_offset_(0) { + MakeWeak(this); + Wrap(wrap, this); + hello_parser_.Start(SSLWrap::OnClientHello, + OnClientHelloParseEnd, + this); + enable_session_callbacks(); + } + + private: + static void SSLInfoCallback(const SSL *ssl, int where, int ret); + + BIO *bio_read_; + BIO *bio_write_; + + uint8_t hello_data_[18432]; + size_t hello_offset_; + + friend class ClientHelloParser; + friend class SecureContext; +}; + +class CipherBase : public BaseObject { + public: + ~CipherBase() override { + if (!initialised_) + return; + delete[] auth_tag_; + EVP_CIPHER_CTX_free(ctx_); + } + + static void Initialize(Environment* env, v8::Local target); + + protected: + enum CipherKind { + kCipher, + kDecipher + }; + + void Init(const char* cipher_type, const char* key_buf, int key_buf_len); + void InitIv(const char* cipher_type, + const char* key, + int key_len, + const char* iv, + int iv_len); + bool Update(const char* data, int len, unsigned char** out, int* out_len); + bool Final(unsigned char** out, int *out_len); + bool SetAutoPadding(bool auto_padding); + + bool IsAuthenticatedMode() const; + bool GetAuthTag(char** out, unsigned int* out_len) const; + bool SetAuthTag(const char* data, unsigned int len); + bool SetAAD(const char* data, unsigned int len); + + static void New(const v8::FunctionCallbackInfo& args); + static void Init(const v8::FunctionCallbackInfo& args); + static void InitIv(const v8::FunctionCallbackInfo& args); + static void Update(const v8::FunctionCallbackInfo& args); + static void Final(const v8::FunctionCallbackInfo& args); + static void SetAutoPadding(const v8::FunctionCallbackInfo& args); + + static void GetAuthTag(const v8::FunctionCallbackInfo& args); + static void SetAuthTag(const v8::FunctionCallbackInfo& args); + static void SetAAD(const v8::FunctionCallbackInfo& args); + + CipherBase(Environment* env, + v8::Local wrap, + CipherKind kind) + : BaseObject(env, wrap), + cipher_(nullptr), + initialised_(false), + kind_(kind), + auth_tag_(nullptr), + auth_tag_len_(0) { + MakeWeak(this); + ctx_ = EVP_CIPHER_CTX_new(); + } + + private: + EVP_CIPHER_CTX* ctx_; /* coverity[member_decl] */ + const EVP_CIPHER* cipher_; /* coverity[member_decl] */ + bool initialised_; + CipherKind kind_; + char* auth_tag_; + unsigned int auth_tag_len_; +}; + +class Hmac : public BaseObject { + public: + ~Hmac() override { + if (!initialised_) + return; + HMAC_CTX_free(ctx_); + } + + static void Initialize(Environment* env, v8::Local target); + + protected: + void HmacInit(const char* hash_type, const char* key, int key_len); + bool HmacUpdate(const char* data, int len); + bool HmacDigest(unsigned char** md_value, unsigned int* md_len); + + static void New(const v8::FunctionCallbackInfo& args); + static void HmacInit(const v8::FunctionCallbackInfo& args); + static void HmacUpdate(const v8::FunctionCallbackInfo& args); + static void HmacDigest(const v8::FunctionCallbackInfo& args); + + Hmac(Environment* env, v8::Local wrap) + : BaseObject(env, wrap), + initialised_(false) { + MakeWeak(this); + ctx_ = HMAC_CTX_new(); + } + + private: + HMAC_CTX* ctx_; /* coverity[member_decl] */ + bool initialised_; +}; + +class Hash : public BaseObject { + public: + ~Hash() override { + if (!initialised_) + return; + EVP_MD_CTX_free(mdctx_); + } + + static void Initialize(Environment* env, v8::Local target); + + bool HashInit(const char* hash_type); + bool HashUpdate(const char* data, int len); + + protected: + static void New(const v8::FunctionCallbackInfo& args); + static void HashUpdate(const v8::FunctionCallbackInfo& args); + static void HashDigest(const v8::FunctionCallbackInfo& args); + + Hash(Environment* env, v8::Local wrap) + : BaseObject(env, wrap), + initialised_(false) { + MakeWeak(this); + mdctx_ = EVP_MD_CTX_new(); + } + + private: + EVP_MD_CTX* mdctx_; /* coverity[member_decl] */ + bool initialised_; + bool finalized_; +}; + +class SignBase : public BaseObject { + public: + typedef enum { + kSignOk, + kSignUnknownDigest, + kSignInit, + kSignNotInitialised, + kSignUpdate, + kSignPrivateKey, + kSignPublicKey + } Error; + + SignBase(Environment* env, v8::Local wrap) + : BaseObject(env, wrap), + initialised_(false) { + mdctx_ = EVP_MD_CTX_new(); + } + + ~SignBase() override { + if (!initialised_) + return; + EVP_MD_CTX_free(mdctx_); + } + + protected: + void CheckThrow(Error error); + + EVP_MD_CTX* mdctx_; /* coverity[member_decl] */ + bool initialised_; +}; + +class Sign : public SignBase { + public: + static void Initialize(Environment* env, v8::Local target); + + Error SignInit(const char* sign_type); + Error SignUpdate(const char* data, int len); + Error SignFinal(const char* key_pem, + int key_pem_len, + const char* passphrase, + unsigned char** sig, + unsigned int *sig_len, + int padding, + int saltlen); + + protected: + static void New(const v8::FunctionCallbackInfo& args); + static void SignInit(const v8::FunctionCallbackInfo& args); + static void SignUpdate(const v8::FunctionCallbackInfo& args); + static void SignFinal(const v8::FunctionCallbackInfo& args); + + Sign(Environment* env, v8::Local wrap) : SignBase(env, wrap) { + MakeWeak(this); + } +}; + +class Verify : public SignBase { + public: + static void Initialize(Environment* env, v8::Local target); + + Error VerifyInit(const char* verify_type); + Error VerifyUpdate(const char* data, int len); + Error VerifyFinal(const char* key_pem, + int key_pem_len, + const char* sig, + int siglen, + int padding, + int saltlen, + bool* verify_result); + + protected: + static void New(const v8::FunctionCallbackInfo& args); + static void VerifyInit(const v8::FunctionCallbackInfo& args); + static void VerifyUpdate(const v8::FunctionCallbackInfo& args); + static void VerifyFinal(const v8::FunctionCallbackInfo& args); + + Verify(Environment* env, v8::Local wrap) : SignBase(env, wrap) { + MakeWeak(this); + } +}; + +class PublicKeyCipher { + public: + typedef int (*EVP_PKEY_cipher_init_t)(EVP_PKEY_CTX *ctx); + typedef int (*EVP_PKEY_cipher_t)(EVP_PKEY_CTX *ctx, + unsigned char *out, size_t *outlen, + const unsigned char *in, size_t inlen); + + enum Operation { + kPublic, + kPrivate + }; + + template + static bool Cipher(const char* key_pem, + int key_pem_len, + const char* passphrase, + int padding, + const unsigned char* data, + int len, + unsigned char** out, + size_t* out_len); + + template + static void Cipher(const v8::FunctionCallbackInfo& args); +}; + +class DiffieHellman : public BaseObject { + public: + ~DiffieHellman() override { + if (dh != nullptr) { + DH_free(dh); + } + } + + static void Initialize(Environment* env, v8::Local target); + + bool Init(int primeLength, int g); + bool Init(const char* p, int p_len, int g); + bool Init(const char* p, int p_len, const char* g, int g_len); + + protected: + static void DiffieHellmanGroup( + const v8::FunctionCallbackInfo& args); + static void New(const v8::FunctionCallbackInfo& args); + static void GenerateKeys(const v8::FunctionCallbackInfo& args); + static void GetPrime(const v8::FunctionCallbackInfo& args); + static void GetGenerator(const v8::FunctionCallbackInfo& args); + static void GetPublicKey(const v8::FunctionCallbackInfo& args); + static void GetPrivateKey(const v8::FunctionCallbackInfo& args); + static void ComputeSecret(const v8::FunctionCallbackInfo& args); + static void SetPublicKey(const v8::FunctionCallbackInfo& args); + static void SetPrivateKey(const v8::FunctionCallbackInfo& args); + static void VerifyErrorGetter( + v8::Local property, + const v8::PropertyCallbackInfo& args); + + DiffieHellman(Environment* env, v8::Local wrap) + : BaseObject(env, wrap), + initialised_(false), + verifyError_(0), + dh(nullptr) { + MakeWeak(this); + } + + private: + bool VerifyContext(); + + bool initialised_; + int verifyError_; + DH* dh; +}; + +class ECDH : public BaseObject { + public: + ~ECDH() override { + if (key_ != nullptr) + EC_KEY_free(key_); + key_ = nullptr; + group_ = nullptr; + } + + static void Initialize(Environment* env, v8::Local target); + + protected: + ECDH(Environment* env, v8::Local wrap, EC_KEY* key) + : BaseObject(env, wrap), + key_(key), + group_(EC_KEY_get0_group(key_)) { + MakeWeak(this); + ASSERT_NE(group_, nullptr); + } + + static void New(const v8::FunctionCallbackInfo& args); + static void GenerateKeys(const v8::FunctionCallbackInfo& args); + static void ComputeSecret(const v8::FunctionCallbackInfo& args); + static void GetPrivateKey(const v8::FunctionCallbackInfo& args); + static void SetPrivateKey(const v8::FunctionCallbackInfo& args); + static void GetPublicKey(const v8::FunctionCallbackInfo& args); + static void SetPublicKey(const v8::FunctionCallbackInfo& args); + + EC_POINT* BufferToPoint(char* data, size_t len); + + bool IsKeyPairValid(); + bool IsKeyValidForCurve(const BIGNUM* private_key); + + EC_KEY* key_; + const EC_GROUP* group_; +}; + +bool EntropySource(unsigned char* buffer, size_t length); +#ifndef OPENSSL_NO_ENGINE +void SetEngine(const v8::FunctionCallbackInfo& args); +#endif // !OPENSSL_NO_ENGINE +void InitCrypto(v8::Local target); + +} // namespace crypto +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_CRYPTO_IMPL_NODE_CRYPTO_H_ diff --git a/src/crypto_impl/openssl/1_1_0f/node_crypto_bio.cc b/src/crypto_impl/openssl/1_1_0f/node_crypto_bio.cc new file mode 100644 index 00000000000000..d27f4689755988 --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto_bio.cc @@ -0,0 +1,502 @@ +// 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_bio.h" +#include "openssl/bio.h" +#include "util.h" +#include "util-inl.h" +#include +#include + +namespace node { + +static int New(BIO* bio); +static int Free(BIO* bio); +static int Read(BIO* bio, char* out, int len); +static int Write(BIO* bio, const char* data, int len); +static int Puts(BIO* bio, const char* str); +static int Gets(BIO* bio, char* out, int size); +static long Ctrl(BIO* bio, int cmd, long num, // NOLINT(runtime/int) + void* ptr); + +BIO_METHOD* GetBioMethod() { + BIO_METHOD* method = BIO_meth_new(BIO_TYPE_MEM, "node.js SSL buffer"); + BIO_meth_set_write(method, Write); + BIO_meth_set_read(method, Read); + BIO_meth_set_puts(method, Puts); + BIO_meth_set_gets(method, Gets); + BIO_meth_set_ctrl(method, Ctrl); + BIO_meth_set_create(method, New); + BIO_meth_set_destroy(method, Free); + return method; +} + +const BIO_METHOD* method = GetBioMethod(); + +BIO* NodeBIO::New() { + // The const_cast doesn't violate const correctness. OpenSSL's usage of + // BIO_METHOD is effectively const but BIO_new() takes a non-const argument. + return BIO_new(const_cast(method)); +} + + +BIO* NodeBIO::NewFixed(const char* data, size_t len) { + BIO* bio = New(); + + if (bio == nullptr || + len > INT_MAX || + BIO_write(bio, data, len) != static_cast(len) || + BIO_set_mem_eof_return(bio, 0) != 1) { + BIO_free(bio); + return nullptr; + } + + return bio; +} + + +void NodeBIO::AssignEnvironment(Environment* env) { + env_ = env; +} + + +static int New(BIO* bio) { + BIO_set_data(bio, new NodeBIO()); + + // XXX Why am I doing it?! + BIO_set_shutdown(bio, 1); + BIO_set_init(bio, 1); + + return 1; +} + + +static int Free(BIO* bio) { + if (bio == nullptr) + return 0; + + if (BIO_get_shutdown(bio)) { + if (BIO_get_init(bio) && BIO_get_data(bio) != nullptr) { + delete NodeBIO::FromBIO(bio); + BIO_set_data(bio, nullptr); + } + } + + return 1; +} + + +static int Read(BIO* bio, char* out, int len) { + int bytes; + NodeBIO* nbio = NodeBIO::FromBIO(bio); + + BIO_clear_retry_flags(bio); + + bytes = nbio->Read(out, len); + + if (bytes == 0) { + bytes = nbio->eof_return(); + if (bytes != 0) { + BIO_set_retry_read(bio); + } + } + + return bytes; +} + + +char* NodeBIO::Peek(size_t* size) { + *size = read_head_->write_pos_ - read_head_->read_pos_; + return read_head_->data_ + read_head_->read_pos_; +} + + +size_t NodeBIO::PeekMultiple(char** out, size_t* size, size_t* count) { + Buffer* pos = read_head_; + size_t max = *count; + size_t total = 0; + + size_t i; + for (i = 0; i < max; i++) { + size[i] = pos->write_pos_ - pos->read_pos_; + total += size[i]; + out[i] = pos->data_ + pos->read_pos_; + + /* Don't get past write head */ + if (pos == write_head_) + break; + else + pos = pos->next_; + } + + if (i == max) + *count = i; + else + *count = i + 1; + + return total; +} + + +static int Write(BIO* bio, const char* data, int len) { + BIO_clear_retry_flags(bio); + + NodeBIO::FromBIO(bio)->Write(data, len); + + return len; +} + + +static int Puts(BIO* bio, const char* str) { + return Write(bio, str, strlen(str)); +} + + +static int Gets(BIO* bio, char* out, int size) { + NodeBIO* nbio = NodeBIO::FromBIO(bio); + + if (nbio->Length() == 0) + return 0; + + int i = nbio->IndexOf('\n', size); + + // Include '\n', if it's there. If not, don't read off the end. + if (i < size && i >= 0 && static_cast(i) < nbio->Length()) + i++; + + // Shift `i` a bit to nullptr-terminate string later + if (size == i) + i--; + + // Flush read data + nbio->Read(out, i); + + out[i] = 0; + + return i; +} + + +static long Ctrl(BIO* bio, int cmd, long num, // NOLINT(runtime/int) + void* ptr) { + NodeBIO* nbio; + long ret; // NOLINT(runtime/int) + + nbio = NodeBIO::FromBIO(bio); + ret = 1; + + switch (cmd) { + case BIO_CTRL_RESET: + nbio->Reset(); + break; + case BIO_CTRL_EOF: + ret = nbio->Length() == 0; + break; + case BIO_C_SET_BUF_MEM_EOF_RETURN: + //BIO_set_mem_eof_return(bio, num); + nbio->set_eof_return(num); + break; + case BIO_CTRL_INFO: + ret = nbio->Length(); + if (ptr != nullptr) + *reinterpret_cast(ptr) = nullptr; + break; + case BIO_C_SET_BUF_MEM: + CHECK(0 && "Can't use SET_BUF_MEM_PTR with NodeBIO"); + break; + case BIO_C_GET_BUF_MEM_PTR: + CHECK(0 && "Can't use GET_BUF_MEM_PTR with NodeBIO"); + ret = 0; + break; + case BIO_CTRL_GET_CLOSE: + ret = BIO_get_shutdown(bio); + break; + case BIO_CTRL_SET_CLOSE: + BIO_set_shutdown(bio, num); + break; + case BIO_CTRL_WPENDING: + ret = 0; + break; + case BIO_CTRL_PENDING: + ret = nbio->Length(); + break; + case BIO_CTRL_DUP: + case BIO_CTRL_FLUSH: + ret = 1; + break; + case BIO_CTRL_PUSH: + case BIO_CTRL_POP: + default: + ret = 0; + break; + } + return ret; +} + + +void NodeBIO::TryMoveReadHead() { + // `read_pos_` and `write_pos_` means the position of the reader and writer + // inside the buffer, respectively. When they're equal - its safe to reset + // them, because both reader and writer will continue doing their stuff + // from new (zero) positions. + while (read_head_->read_pos_ != 0 && + read_head_->read_pos_ == read_head_->write_pos_) { + // Reset positions + read_head_->read_pos_ = 0; + read_head_->write_pos_ = 0; + + // Move read_head_ forward, just in case if there're still some data to + // read in the next buffer. + if (read_head_ != write_head_) + read_head_ = read_head_->next_; + } +} + + +size_t NodeBIO::Read(char* out, size_t size) { + size_t bytes_read = 0; + size_t expected = Length() > size ? size : Length(); + size_t offset = 0; + size_t left = size; + + while (bytes_read < expected) { + CHECK_LE(read_head_->read_pos_, read_head_->write_pos_); + size_t avail = read_head_->write_pos_ - read_head_->read_pos_; + if (avail > left) + avail = left; + + // Copy data + if (out != nullptr) + memcpy(out + offset, read_head_->data_ + read_head_->read_pos_, avail); + read_head_->read_pos_ += avail; + + // Move pointers + bytes_read += avail; + offset += avail; + left -= avail; + + TryMoveReadHead(); + } + CHECK_EQ(expected, bytes_read); + length_ -= bytes_read; + + // Free all empty buffers, but write_head's child + FreeEmpty(); + + return bytes_read; +} + + +void NodeBIO::FreeEmpty() { + if (write_head_ == nullptr) + return; + Buffer* child = write_head_->next_; + if (child == write_head_ || child == read_head_) + return; + Buffer* cur = child->next_; + if (cur == write_head_ || cur == read_head_) + return; + + Buffer* prev = child; + while (cur != read_head_) { + CHECK_NE(cur, write_head_); + CHECK_EQ(cur->write_pos_, cur->read_pos_); + + Buffer* next = cur->next_; + delete cur; + cur = next; + } + prev->next_ = cur; +} + + +size_t NodeBIO::IndexOf(char delim, size_t limit) { + size_t bytes_read = 0; + size_t max = Length() > limit ? limit : Length(); + size_t left = limit; + Buffer* current = read_head_; + + while (bytes_read < max) { + CHECK_LE(current->read_pos_, current->write_pos_); + size_t avail = current->write_pos_ - current->read_pos_; + if (avail > left) + avail = left; + + // Walk through data + char* tmp = current->data_ + current->read_pos_; + size_t off = 0; + while (off < avail && *tmp != delim) { + off++; + tmp++; + } + + // Move pointers + bytes_read += off; + left -= off; + + // Found `delim` + if (off != avail) { + return bytes_read; + } + + // Move to next buffer + if (current->read_pos_ + avail == current->len_) { + current = current->next_; + } + } + CHECK_EQ(max, bytes_read); + + return max; +} + + +void NodeBIO::Write(const char* data, size_t size) { + size_t offset = 0; + size_t left = size; + + // Allocate initial buffer if the ring is empty + TryAllocateForWrite(left); + + while (left > 0) { + size_t to_write = left; + CHECK_LE(write_head_->write_pos_, write_head_->len_); + size_t avail = write_head_->len_ - write_head_->write_pos_; + + if (to_write > avail) + to_write = avail; + + // Copy data + memcpy(write_head_->data_ + write_head_->write_pos_, + data + offset, + to_write); + + // Move pointers + left -= to_write; + offset += to_write; + length_ += to_write; + write_head_->write_pos_ += to_write; + CHECK_LE(write_head_->write_pos_, write_head_->len_); + + // Go to next buffer if there still are some bytes to write + if (left != 0) { + CHECK_EQ(write_head_->write_pos_, write_head_->len_); + TryAllocateForWrite(left); + write_head_ = write_head_->next_; + + // Additionally, since we're moved to the next buffer, read head + // may be moved as well. + TryMoveReadHead(); + } + } + CHECK_EQ(left, 0); +} + + +char* NodeBIO::PeekWritable(size_t* size) { + TryAllocateForWrite(*size); + + size_t available = write_head_->len_ - write_head_->write_pos_; + if (*size != 0 && available > *size) + available = *size; + else + *size = available; + + return write_head_->data_ + write_head_->write_pos_; +} + + +void NodeBIO::Commit(size_t size) { + write_head_->write_pos_ += size; + length_ += size; + CHECK_LE(write_head_->write_pos_, write_head_->len_); + + // Allocate new buffer if write head is full, + // and there're no other place to go + TryAllocateForWrite(0); + if (write_head_->write_pos_ == write_head_->len_) { + write_head_ = write_head_->next_; + + // Additionally, since we're moved to the next buffer, read head + // may be moved as well. + TryMoveReadHead(); + } +} + + +void NodeBIO::TryAllocateForWrite(size_t hint) { + Buffer* w = write_head_; + Buffer* r = read_head_; + // If write head is full, next buffer is either read head or not empty. + if (w == nullptr || + (w->write_pos_ == w->len_ && + (w->next_ == r || w->next_->write_pos_ != 0))) { + size_t len = w == nullptr ? initial_ : + kThroughputBufferLength; + if (len < hint) + len = hint; + Buffer* next = new Buffer(env_, len); + + if (w == nullptr) { + next->next_ = next; + write_head_ = next; + read_head_ = next; + } else { + next->next_ = w->next_; + w->next_ = next; + } + } +} + + +void NodeBIO::Reset() { + if (read_head_ == nullptr) + return; + + while (read_head_->read_pos_ != read_head_->write_pos_) { + CHECK(read_head_->write_pos_ > read_head_->read_pos_); + + length_ -= read_head_->write_pos_ - read_head_->read_pos_; + read_head_->write_pos_ = 0; + read_head_->read_pos_ = 0; + + read_head_ = read_head_->next_; + } + write_head_ = read_head_; + CHECK_EQ(length_, 0); +} + + +NodeBIO::~NodeBIO() { + if (read_head_ == nullptr) + return; + + Buffer* current = read_head_; + do { + Buffer* next = current->next_; + delete current; + current = next; + } while (current != read_head_); + + read_head_ = nullptr; + write_head_ = nullptr; +} + +} // namespace node diff --git a/src/crypto_impl/openssl/1_1_0f/node_crypto_bio.h b/src/crypto_impl/openssl/1_1_0f/node_crypto_bio.h new file mode 100644 index 00000000000000..b67b22d69b7600 --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto_bio.h @@ -0,0 +1,162 @@ +// 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. + +#ifndef SRC_CRYPTO_IMPL_NODE_CRYPTO_BIO_H_ +#define SRC_CRYPTO_IMPL_NODE_CRYPTO_BIO_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "openssl/bio.h" +#include "env.h" +#include "env-inl.h" +#include "util.h" +#include "util-inl.h" +#include "v8.h" + +namespace node { + +class NodeBIO { + public: + NodeBIO() : env_(nullptr), + initial_(kInitialBufferLength), + length_(0), + eof_return_(-1), + read_head_(nullptr), + write_head_(nullptr) { + } + + ~NodeBIO(); + + static BIO* New(); + + // NewFixed takes a copy of `len` bytes from `data` and returns a BIO that, + // when read from, returns those bytes followed by EOF. + static BIO* NewFixed(const char* data, size_t len); + + void AssignEnvironment(Environment* env); + + // Move read head to next buffer if needed + void TryMoveReadHead(); + + // Allocate new buffer for write if needed + void TryAllocateForWrite(size_t hint); + + // Read `len` bytes maximum into `out`, return actual number of read bytes + size_t Read(char* out, size_t size); + + // Memory optimization: + // Deallocate children of write head's child if they're empty + void FreeEmpty(); + + // Return pointer to internal data and amount of + // contiguous data available to read + char* Peek(size_t* size); + + // Return pointers and sizes of multiple internal data chunks available for + // reading + size_t PeekMultiple(char** out, size_t* size, size_t* count); + + // Find first appearance of `delim` in buffer or `limit` if `delim` + // wasn't found. + size_t IndexOf(char delim, size_t limit); + + // Discard all available data + void Reset(); + + // Put `len` bytes from `data` into buffer + void Write(const char* data, size_t size); + + // Return pointer to internal data and amount of + // contiguous data available for future writes + char* PeekWritable(size_t* size); + + // Commit reserved data + void Commit(size_t size); + + + // Return size of buffer in bytes + inline size_t Length() const { + return length_; + } + + inline void set_initial(size_t initial) { + initial_ = initial; + } + + inline void set_eof_return(int num) { + eof_return_ = num; + } + + inline int eof_return() { + return eof_return_; + } + + static inline NodeBIO* FromBIO(BIO* bio) { + CHECK_NE(BIO_get_data(bio), nullptr); + return static_cast(BIO_get_data(bio)); + } + + private: + // Enough to handle the most of the client hellos + static const size_t kInitialBufferLength = 1024; + static const size_t kThroughputBufferLength = 16384; + + class Buffer { + public: + Buffer(Environment* env, size_t len) : env_(env), + read_pos_(0), + write_pos_(0), + len_(len), + next_(nullptr) { + data_ = new char[len]; + if (env_ != nullptr) + env_->isolate()->AdjustAmountOfExternalAllocatedMemory(len); + } + + ~Buffer() { + delete[] data_; + if (env_ != nullptr) { + const int64_t len = static_cast(len_); + env_->isolate()->AdjustAmountOfExternalAllocatedMemory(-len); + } + } + + Environment* env_; + size_t read_pos_; + size_t write_pos_; + size_t len_; + Buffer* next_; + char* data_; + }; + + Environment* env_; + size_t initial_; + size_t length_; + int eof_return_; + Buffer* read_head_; + Buffer* write_head_; +}; + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_CRYPTO_IMPL_NODE_CRYPTO_BIO_H_ diff --git a/src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello-inl.h b/src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello-inl.h new file mode 100644 index 00000000000000..e22582ec5e9004 --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello-inl.h @@ -0,0 +1,81 @@ +// 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. + +#ifndef SRC_CRYPTO_IMPL_NODE_CRYPTO_CLIENTHELLO_INL_H_ +#define SRC_CRYPTO_IMPL_NODE_CRYPTO_CLIENTHELLO_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "util.h" +#include "util-inl.h" + +namespace node { + +inline void ClientHelloParser::Reset() { + frame_len_ = 0; + body_offset_ = 0; + extension_offset_ = 0; + session_size_ = 0; + session_id_ = nullptr; + tls_ticket_size_ = -1; + tls_ticket_ = nullptr; + servername_size_ = 0; + servername_ = nullptr; +} + +inline void ClientHelloParser::Start(ClientHelloParser::OnHelloCb onhello_cb, + ClientHelloParser::OnEndCb onend_cb, + void* onend_arg) { + if (!IsEnded()) + return; + Reset(); + + CHECK_NE(onhello_cb, nullptr); + + state_ = kWaiting; + onhello_cb_ = onhello_cb; + onend_cb_ = onend_cb; + cb_arg_ = onend_arg; +} + +inline void ClientHelloParser::End() { + if (state_ == kEnded) + return; + state_ = kEnded; + if (onend_cb_ != nullptr) { + onend_cb_(cb_arg_); + onend_cb_ = nullptr; + } +} + +inline bool ClientHelloParser::IsEnded() const { + return state_ == kEnded; +} + +inline bool ClientHelloParser::IsPaused() const { + return state_ == kPaused; +} + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_CRYPTO_IMPL_NODE_CRYPTO_CLIENTHELLO_INL_H_ diff --git a/src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello.cc b/src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello.cc new file mode 100644 index 00000000000000..794d877e387258 --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello.cc @@ -0,0 +1,247 @@ +// 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_clienthello.h" +#include "node_crypto_clienthello-inl.h" + +namespace node { + +void ClientHelloParser::Parse(const uint8_t* data, size_t avail) { + switch (state_) { + case kWaiting: + if (!ParseRecordHeader(data, avail)) + break; + // Fall through + case kTLSHeader: + ParseHeader(data, avail); + break; + case kPaused: + // Just nop + case kEnded: + // Already ended, just ignore it + break; + default: + break; + } +} + + +bool ClientHelloParser::ParseRecordHeader(const uint8_t* data, size_t avail) { + // >= 5 bytes for header parsing + if (avail < 5) + return false; + + 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 { + End(); + return false; + } + + // Sanity check (too big frame, or too small) + // Let OpenSSL handle it + if (frame_len_ >= kMaxTLSFrameLen) { + End(); + return false; + } + + return true; +} + + +void ClientHelloParser::ParseHeader(const uint8_t* data, size_t avail) { + ClientHello hello; + + // >= 5 + frame size bytes for frame parsing + if (body_offset_ + frame_len_ > avail) + return; + + // Check hello protocol version. Protocol tuples that we know about: + // + // (3,1) TLS v1.0 + // (3,2) TLS v1.1 + // (3,3) TLS v1.2 + // + if (data[body_offset_ + 4] != 0x03 || + data[body_offset_ + 5] < 0x01 || + data[body_offset_ + 5] > 0x03) { + goto fail; + } + + if (data[body_offset_] == kClientHello) { + if (state_ == kTLSHeader) { + if (!ParseTLSClientHello(data, avail)) + goto fail; + } else { + // We couldn't get here, but whatever + goto fail; + } + + // Check if we overflowed (do not reply with any private data) + if (session_id_ == nullptr || + session_size_ > 32 || + session_id_ + session_size_ > data + avail) { + goto fail; + } + } + + state_ = kPaused; + hello.session_id_ = session_id_; + hello.session_size_ = session_size_; + hello.has_ticket_ = tls_ticket_ != nullptr && tls_ticket_size_ != 0; + hello.ocsp_request_ = ocsp_request_; + hello.servername_ = servername_; + hello.servername_size_ = static_cast(servername_size_); + onhello_cb_(cb_arg_, hello); + return; + + fail: + End(); +} + + +void ClientHelloParser::ParseExtension(const uint16_t type, + const uint8_t* data, + size_t len) { + // NOTE: In case of anything we're just returning back, ignoring the problem. + // That's because we're heavily relying on OpenSSL to solve any problem with + // incoming data. + switch (type) { + case kServerName: + { + if (len < 2) + return; + uint32_t server_names_len = (data[0] << 8) + data[1]; + if (server_names_len + 2 > len) + return; + for (size_t offset = 2; offset < 2 + server_names_len; ) { + if (offset + 3 > len) + return; + uint8_t name_type = data[offset]; + if (name_type != kServernameHostname) + return; + uint16_t name_len = (data[offset + 1] << 8) + data[offset + 2]; + offset += 3; + if (offset + name_len > len) + return; + servername_ = data + offset; + servername_size_ = name_len; + offset += name_len; + } + } + break; + case kStatusRequest: + // We are ignoring any data, just indicating the presence of extension + if (len < kMinStatusRequestSize) + return; + + // Unknown type, ignore it + if (data[0] != kStatusRequestOCSP) + break; + + // Ignore extensions, they won't work with caching on backend anyway + ocsp_request_ = 1; + break; + case kTLSSessionTicket: + tls_ticket_size_ = len; + tls_ticket_ = data + len; + break; + default: + // Ignore + break; + } +} + + +bool ClientHelloParser::ParseTLSClientHello(const uint8_t* data, size_t avail) { + const uint8_t* body; + + // Skip frame header, hello header, protocol version and random data + size_t session_offset = body_offset_ + 4 + 2 + 32; + + if (session_offset + 1 >= avail) + return false; + + body = data + session_offset; + session_size_ = *body; + session_id_ = body + 1; + + size_t cipher_offset = session_offset + 1 + session_size_; + + // Session OOB failure + if (cipher_offset + 1 >= avail) + return false; + + uint16_t cipher_len = + (data[cipher_offset] << 8) + data[cipher_offset + 1]; + size_t comp_offset = cipher_offset + 2 + cipher_len; + + // Cipher OOB failure + if (comp_offset >= avail) + return false; + + uint8_t comp_len = data[comp_offset]; + size_t extension_offset = comp_offset + 1 + comp_len; + + // Compression OOB failure + if (extension_offset > avail) + return false; + + // No extensions present + if (extension_offset == avail) + return true; + + size_t ext_off = extension_offset + 2; + + // Parse known extensions + while (ext_off < avail) { + // Extension OOB + if (ext_off + 4 > avail) + return false; + + uint16_t ext_type = (data[ext_off] << 8) + data[ext_off + 1]; + uint16_t ext_len = (data[ext_off + 2] << 8) + data[ext_off + 3]; + ext_off += 4; + + // Extension OOB + if (ext_off + ext_len > avail) + return false; + + ParseExtension(ext_type, + data + ext_off, + ext_len); + + ext_off += ext_len; + } + + // Extensions OOB failure + if (ext_off > avail) + return false; + + return true; +} + +} // namespace node diff --git a/src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello.h b/src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello.h new file mode 100644 index 00000000000000..569661c02045a9 --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello.h @@ -0,0 +1,140 @@ +// 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. + +#ifndef SRC_CRYPTO_IMPL_NODE_CRYPTO_CLIENTHELLO_H_ +#define SRC_CRYPTO_IMPL_NODE_CRYPTO_CLIENTHELLO_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node.h" + +#include // size_t +#include // nullptr + +namespace node { + +class ClientHelloParser { + public: + ClientHelloParser() : state_(kEnded), + onhello_cb_(nullptr), + onend_cb_(nullptr), + cb_arg_(nullptr), + session_size_(0), + session_id_(nullptr), + servername_size_(0), + servername_(nullptr), + ocsp_request_(0), + tls_ticket_size_(0), + tls_ticket_(nullptr) { + Reset(); + } + + class ClientHello { + public: + inline uint8_t session_size() const { return session_size_; } + inline const uint8_t* session_id() const { return session_id_; } + inline bool has_ticket() const { return has_ticket_; } + inline uint8_t servername_size() const { return servername_size_; } + inline const uint8_t* servername() const { return servername_; } + inline int ocsp_request() const { return ocsp_request_; } + + private: + uint8_t session_size_; + const uint8_t* session_id_; + bool has_ticket_; + uint8_t servername_size_; + const uint8_t* servername_; + int ocsp_request_; + + friend class ClientHelloParser; + }; + + typedef void (*OnHelloCb)(void* arg, const ClientHello& hello); + typedef void (*OnEndCb)(void* arg); + + void Parse(const uint8_t* data, size_t avail); + + inline void Reset(); + inline void Start(OnHelloCb onhello_cb, OnEndCb onend_cb, void* onend_arg); + inline void End(); + inline bool IsPaused() const; + inline bool IsEnded() const; + + private: + static const size_t kMaxTLSFrameLen = 16 * 1024 + 5; + static const size_t kMaxSSLExFrameLen = 32 * 1024; + static const uint8_t kServernameHostname = 0; + static const uint8_t kStatusRequestOCSP = 1; + static const size_t kMinStatusRequestSize = 5; + + enum ParseState { + kWaiting, + kTLSHeader, + kPaused, + kEnded + }; + + enum FrameType { + kChangeCipherSpec = 20, + kAlert = 21, + kHandshake = 22, + kApplicationData = 23, + kOther = 255 + }; + + enum HandshakeType { + kClientHello = 1 + }; + + enum ExtensionType { + kServerName = 0, + kStatusRequest = 5, + kTLSSessionTicket = 35 + }; + + bool ParseRecordHeader(const uint8_t* data, size_t avail); + void ParseHeader(const uint8_t* data, size_t avail); + void ParseExtension(const uint16_t type, + const uint8_t* data, + size_t len); + bool ParseTLSClientHello(const uint8_t* data, size_t avail); + + ParseState state_; + OnHelloCb onhello_cb_; + OnEndCb onend_cb_; + void* cb_arg_; + size_t frame_len_; + size_t body_offset_; + size_t extension_offset_; + uint8_t session_size_; + const uint8_t* session_id_; + uint16_t servername_size_; + const uint8_t* servername_; + uint8_t ocsp_request_; + uint16_t tls_ticket_size_; + const uint8_t* tls_ticket_; +}; + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_CRYPTO_IMPL_NODE_CRYPTO_CLIENTHELLO_H_ diff --git a/src/crypto_impl/openssl/1_1_0f/node_crypto_groups.h b/src/crypto_impl/openssl/1_1_0f/node_crypto_groups.h new file mode 100644 index 00000000000000..23508e1e2ce2eb --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto_groups.h @@ -0,0 +1,416 @@ +// 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. + +#ifndef SRC_CRYPTO_IMPL_NODE_CRYPTO_GROUPS_H_ +#define SRC_CRYPTO_IMPL_NODE_CRYPTO_GROUPS_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +/* + These modular groups were literally taken from: + * RFC 2412 (groups 1 and 2) + * RFC 3526 (groups 5, 14, 15, 16, 17 and 18) + They all use 2 as a generator. +*/ + + +static const unsigned char two_generator[] = { 2 }; + +static const unsigned char group_modp1[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, + 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, + 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, + 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, + 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, 0xef, 0x95, + 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, + 0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, + 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, + 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +static const unsigned char group_modp2[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, + 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, + 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, + 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, + 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, 0xef, 0x95, + 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, + 0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, + 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, + 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, + 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed, 0xee, 0x38, 0x6b, 0xfb, + 0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11, 0x7c, 0x4b, + 0x1f, 0xe6, 0x49, 0x28, 0x66, 0x51, 0xec, 0xe6, 0x53, 0x81, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +static const unsigned char group_modp5[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, + 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, + 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, + 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, + 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, 0xef, 0x95, + 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, + 0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, + 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, + 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, + 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed, 0xee, 0x38, 0x6b, 0xfb, + 0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11, 0x7c, 0x4b, + 0x1f, 0xe6, 0x49, 0x28, 0x66, 0x51, 0xec, 0xe4, 0x5b, 0x3d, + 0xc2, 0x00, 0x7c, 0xb8, 0xa1, 0x63, 0xbf, 0x05, 0x98, 0xda, + 0x48, 0x36, 0x1c, 0x55, 0xd3, 0x9a, 0x69, 0x16, 0x3f, 0xa8, + 0xfd, 0x24, 0xcf, 0x5f, 0x83, 0x65, 0x5d, 0x23, 0xdc, 0xa3, + 0xad, 0x96, 0x1c, 0x62, 0xf3, 0x56, 0x20, 0x85, 0x52, 0xbb, + 0x9e, 0xd5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6d, 0x67, 0x0c, + 0x35, 0x4e, 0x4a, 0xbc, 0x98, 0x04, 0xf1, 0x74, 0x6c, 0x08, + 0xca, 0x23, 0x73, 0x27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff }; + +static const unsigned char group_modp14[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, + 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, + 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, + 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, + 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, 0xef, 0x95, + 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, + 0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, + 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, + 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, + 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed, 0xee, 0x38, 0x6b, 0xfb, + 0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11, 0x7c, 0x4b, + 0x1f, 0xe6, 0x49, 0x28, 0x66, 0x51, 0xec, 0xe4, 0x5b, 0x3d, + 0xc2, 0x00, 0x7c, 0xb8, 0xa1, 0x63, 0xbf, 0x05, 0x98, 0xda, + 0x48, 0x36, 0x1c, 0x55, 0xd3, 0x9a, 0x69, 0x16, 0x3f, 0xa8, + 0xfd, 0x24, 0xcf, 0x5f, 0x83, 0x65, 0x5d, 0x23, 0xdc, 0xa3, + 0xad, 0x96, 0x1c, 0x62, 0xf3, 0x56, 0x20, 0x85, 0x52, 0xbb, + 0x9e, 0xd5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6d, 0x67, 0x0c, + 0x35, 0x4e, 0x4a, 0xbc, 0x98, 0x04, 0xf1, 0x74, 0x6c, 0x08, + 0xca, 0x18, 0x21, 0x7c, 0x32, 0x90, 0x5e, 0x46, 0x2e, 0x36, + 0xce, 0x3b, 0xe3, 0x9e, 0x77, 0x2c, 0x18, 0x0e, 0x86, 0x03, + 0x9b, 0x27, 0x83, 0xa2, 0xec, 0x07, 0xa2, 0x8f, 0xb5, 0xc5, + 0x5d, 0xf0, 0x6f, 0x4c, 0x52, 0xc9, 0xde, 0x2b, 0xcb, 0xf6, + 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7c, 0xea, 0x95, + 0x6a, 0xe5, 0x15, 0xd2, 0x26, 0x18, 0x98, 0xfa, 0x05, 0x10, + 0x15, 0x72, 0x8e, 0x5a, 0x8a, 0xac, 0xaa, 0x68, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +static const unsigned char group_modp15[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, + 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, + 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, + 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, + 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, 0xef, 0x95, + 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, + 0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, + 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, + 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, + 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed, 0xee, 0x38, 0x6b, 0xfb, + 0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11, 0x7c, 0x4b, + 0x1f, 0xe6, 0x49, 0x28, 0x66, 0x51, 0xec, 0xe4, 0x5b, 0x3d, + 0xc2, 0x00, 0x7c, 0xb8, 0xa1, 0x63, 0xbf, 0x05, 0x98, 0xda, + 0x48, 0x36, 0x1c, 0x55, 0xd3, 0x9a, 0x69, 0x16, 0x3f, 0xa8, + 0xfd, 0x24, 0xcf, 0x5f, 0x83, 0x65, 0x5d, 0x23, 0xdc, 0xa3, + 0xad, 0x96, 0x1c, 0x62, 0xf3, 0x56, 0x20, 0x85, 0x52, 0xbb, + 0x9e, 0xd5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6d, 0x67, 0x0c, + 0x35, 0x4e, 0x4a, 0xbc, 0x98, 0x04, 0xf1, 0x74, 0x6c, 0x08, + 0xca, 0x18, 0x21, 0x7c, 0x32, 0x90, 0x5e, 0x46, 0x2e, 0x36, + 0xce, 0x3b, 0xe3, 0x9e, 0x77, 0x2c, 0x18, 0x0e, 0x86, 0x03, + 0x9b, 0x27, 0x83, 0xa2, 0xec, 0x07, 0xa2, 0x8f, 0xb5, 0xc5, + 0x5d, 0xf0, 0x6f, 0x4c, 0x52, 0xc9, 0xde, 0x2b, 0xcb, 0xf6, + 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7c, 0xea, 0x95, + 0x6a, 0xe5, 0x15, 0xd2, 0x26, 0x18, 0x98, 0xfa, 0x05, 0x10, + 0x15, 0x72, 0x8e, 0x5a, 0x8a, 0xaa, 0xc4, 0x2d, 0xad, 0x33, + 0x17, 0x0d, 0x04, 0x50, 0x7a, 0x33, 0xa8, 0x55, 0x21, 0xab, + 0xdf, 0x1c, 0xba, 0x64, 0xec, 0xfb, 0x85, 0x04, 0x58, 0xdb, + 0xef, 0x0a, 0x8a, 0xea, 0x71, 0x57, 0x5d, 0x06, 0x0c, 0x7d, + 0xb3, 0x97, 0x0f, 0x85, 0xa6, 0xe1, 0xe4, 0xc7, 0xab, 0xf5, + 0xae, 0x8c, 0xdb, 0x09, 0x33, 0xd7, 0x1e, 0x8c, 0x94, 0xe0, + 0x4a, 0x25, 0x61, 0x9d, 0xce, 0xe3, 0xd2, 0x26, 0x1a, 0xd2, + 0xee, 0x6b, 0xf1, 0x2f, 0xfa, 0x06, 0xd9, 0x8a, 0x08, 0x64, + 0xd8, 0x76, 0x02, 0x73, 0x3e, 0xc8, 0x6a, 0x64, 0x52, 0x1f, + 0x2b, 0x18, 0x17, 0x7b, 0x20, 0x0c, 0xbb, 0xe1, 0x17, 0x57, + 0x7a, 0x61, 0x5d, 0x6c, 0x77, 0x09, 0x88, 0xc0, 0xba, 0xd9, + 0x46, 0xe2, 0x08, 0xe2, 0x4f, 0xa0, 0x74, 0xe5, 0xab, 0x31, + 0x43, 0xdb, 0x5b, 0xfc, 0xe0, 0xfd, 0x10, 0x8e, 0x4b, 0x82, + 0xd1, 0x20, 0xa9, 0x3a, 0xd2, 0xca, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff }; + +static const unsigned char group_modp16[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, + 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, + 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, + 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, + 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, 0xef, 0x95, + 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, + 0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, + 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, + 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, + 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed, 0xee, 0x38, 0x6b, 0xfb, + 0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11, 0x7c, 0x4b, + 0x1f, 0xe6, 0x49, 0x28, 0x66, 0x51, 0xec, 0xe4, 0x5b, 0x3d, + 0xc2, 0x00, 0x7c, 0xb8, 0xa1, 0x63, 0xbf, 0x05, 0x98, 0xda, + 0x48, 0x36, 0x1c, 0x55, 0xd3, 0x9a, 0x69, 0x16, 0x3f, 0xa8, + 0xfd, 0x24, 0xcf, 0x5f, 0x83, 0x65, 0x5d, 0x23, 0xdc, 0xa3, + 0xad, 0x96, 0x1c, 0x62, 0xf3, 0x56, 0x20, 0x85, 0x52, 0xbb, + 0x9e, 0xd5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6d, 0x67, 0x0c, + 0x35, 0x4e, 0x4a, 0xbc, 0x98, 0x04, 0xf1, 0x74, 0x6c, 0x08, + 0xca, 0x18, 0x21, 0x7c, 0x32, 0x90, 0x5e, 0x46, 0x2e, 0x36, + 0xce, 0x3b, 0xe3, 0x9e, 0x77, 0x2c, 0x18, 0x0e, 0x86, 0x03, + 0x9b, 0x27, 0x83, 0xa2, 0xec, 0x07, 0xa2, 0x8f, 0xb5, 0xc5, + 0x5d, 0xf0, 0x6f, 0x4c, 0x52, 0xc9, 0xde, 0x2b, 0xcb, 0xf6, + 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7c, 0xea, 0x95, + 0x6a, 0xe5, 0x15, 0xd2, 0x26, 0x18, 0x98, 0xfa, 0x05, 0x10, + 0x15, 0x72, 0x8e, 0x5a, 0x8a, 0xaa, 0xc4, 0x2d, 0xad, 0x33, + 0x17, 0x0d, 0x04, 0x50, 0x7a, 0x33, 0xa8, 0x55, 0x21, 0xab, + 0xdf, 0x1c, 0xba, 0x64, 0xec, 0xfb, 0x85, 0x04, 0x58, 0xdb, + 0xef, 0x0a, 0x8a, 0xea, 0x71, 0x57, 0x5d, 0x06, 0x0c, 0x7d, + 0xb3, 0x97, 0x0f, 0x85, 0xa6, 0xe1, 0xe4, 0xc7, 0xab, 0xf5, + 0xae, 0x8c, 0xdb, 0x09, 0x33, 0xd7, 0x1e, 0x8c, 0x94, 0xe0, + 0x4a, 0x25, 0x61, 0x9d, 0xce, 0xe3, 0xd2, 0x26, 0x1a, 0xd2, + 0xee, 0x6b, 0xf1, 0x2f, 0xfa, 0x06, 0xd9, 0x8a, 0x08, 0x64, + 0xd8, 0x76, 0x02, 0x73, 0x3e, 0xc8, 0x6a, 0x64, 0x52, 0x1f, + 0x2b, 0x18, 0x17, 0x7b, 0x20, 0x0c, 0xbb, 0xe1, 0x17, 0x57, + 0x7a, 0x61, 0x5d, 0x6c, 0x77, 0x09, 0x88, 0xc0, 0xba, 0xd9, + 0x46, 0xe2, 0x08, 0xe2, 0x4f, 0xa0, 0x74, 0xe5, 0xab, 0x31, + 0x43, 0xdb, 0x5b, 0xfc, 0xe0, 0xfd, 0x10, 0x8e, 0x4b, 0x82, + 0xd1, 0x20, 0xa9, 0x21, 0x08, 0x01, 0x1a, 0x72, 0x3c, 0x12, + 0xa7, 0x87, 0xe6, 0xd7, 0x88, 0x71, 0x9a, 0x10, 0xbd, 0xba, + 0x5b, 0x26, 0x99, 0xc3, 0x27, 0x18, 0x6a, 0xf4, 0xe2, 0x3c, + 0x1a, 0x94, 0x68, 0x34, 0xb6, 0x15, 0x0b, 0xda, 0x25, 0x83, + 0xe9, 0xca, 0x2a, 0xd4, 0x4c, 0xe8, 0xdb, 0xbb, 0xc2, 0xdb, + 0x04, 0xde, 0x8e, 0xf9, 0x2e, 0x8e, 0xfc, 0x14, 0x1f, 0xbe, + 0xca, 0xa6, 0x28, 0x7c, 0x59, 0x47, 0x4e, 0x6b, 0xc0, 0x5d, + 0x99, 0xb2, 0x96, 0x4f, 0xa0, 0x90, 0xc3, 0xa2, 0x23, 0x3b, + 0xa1, 0x86, 0x51, 0x5b, 0xe7, 0xed, 0x1f, 0x61, 0x29, 0x70, + 0xce, 0xe2, 0xd7, 0xaf, 0xb8, 0x1b, 0xdd, 0x76, 0x21, 0x70, + 0x48, 0x1c, 0xd0, 0x06, 0x91, 0x27, 0xd5, 0xb0, 0x5a, 0xa9, + 0x93, 0xb4, 0xea, 0x98, 0x8d, 0x8f, 0xdd, 0xc1, 0x86, 0xff, + 0xb7, 0xdc, 0x90, 0xa6, 0xc0, 0x8f, 0x4d, 0xf4, 0x35, 0xc9, + 0x34, 0x06, 0x31, 0x99, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff }; + +static const unsigned char group_modp17[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, + 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, + 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, + 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, + 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, 0xef, 0x95, + 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, + 0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, + 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, + 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, + 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed, 0xee, 0x38, 0x6b, 0xfb, + 0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11, 0x7c, 0x4b, + 0x1f, 0xe6, 0x49, 0x28, 0x66, 0x51, 0xec, 0xe4, 0x5b, 0x3d, + 0xc2, 0x00, 0x7c, 0xb8, 0xa1, 0x63, 0xbf, 0x05, 0x98, 0xda, + 0x48, 0x36, 0x1c, 0x55, 0xd3, 0x9a, 0x69, 0x16, 0x3f, 0xa8, + 0xfd, 0x24, 0xcf, 0x5f, 0x83, 0x65, 0x5d, 0x23, 0xdc, 0xa3, + 0xad, 0x96, 0x1c, 0x62, 0xf3, 0x56, 0x20, 0x85, 0x52, 0xbb, + 0x9e, 0xd5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6d, 0x67, 0x0c, + 0x35, 0x4e, 0x4a, 0xbc, 0x98, 0x04, 0xf1, 0x74, 0x6c, 0x08, + 0xca, 0x18, 0x21, 0x7c, 0x32, 0x90, 0x5e, 0x46, 0x2e, 0x36, + 0xce, 0x3b, 0xe3, 0x9e, 0x77, 0x2c, 0x18, 0x0e, 0x86, 0x03, + 0x9b, 0x27, 0x83, 0xa2, 0xec, 0x07, 0xa2, 0x8f, 0xb5, 0xc5, + 0x5d, 0xf0, 0x6f, 0x4c, 0x52, 0xc9, 0xde, 0x2b, 0xcb, 0xf6, + 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7c, 0xea, 0x95, + 0x6a, 0xe5, 0x15, 0xd2, 0x26, 0x18, 0x98, 0xfa, 0x05, 0x10, + 0x15, 0x72, 0x8e, 0x5a, 0x8a, 0xaa, 0xc4, 0x2d, 0xad, 0x33, + 0x17, 0x0d, 0x04, 0x50, 0x7a, 0x33, 0xa8, 0x55, 0x21, 0xab, + 0xdf, 0x1c, 0xba, 0x64, 0xec, 0xfb, 0x85, 0x04, 0x58, 0xdb, + 0xef, 0x0a, 0x8a, 0xea, 0x71, 0x57, 0x5d, 0x06, 0x0c, 0x7d, + 0xb3, 0x97, 0x0f, 0x85, 0xa6, 0xe1, 0xe4, 0xc7, 0xab, 0xf5, + 0xae, 0x8c, 0xdb, 0x09, 0x33, 0xd7, 0x1e, 0x8c, 0x94, 0xe0, + 0x4a, 0x25, 0x61, 0x9d, 0xce, 0xe3, 0xd2, 0x26, 0x1a, 0xd2, + 0xee, 0x6b, 0xf1, 0x2f, 0xfa, 0x06, 0xd9, 0x8a, 0x08, 0x64, + 0xd8, 0x76, 0x02, 0x73, 0x3e, 0xc8, 0x6a, 0x64, 0x52, 0x1f, + 0x2b, 0x18, 0x17, 0x7b, 0x20, 0x0c, 0xbb, 0xe1, 0x17, 0x57, + 0x7a, 0x61, 0x5d, 0x6c, 0x77, 0x09, 0x88, 0xc0, 0xba, 0xd9, + 0x46, 0xe2, 0x08, 0xe2, 0x4f, 0xa0, 0x74, 0xe5, 0xab, 0x31, + 0x43, 0xdb, 0x5b, 0xfc, 0xe0, 0xfd, 0x10, 0x8e, 0x4b, 0x82, + 0xd1, 0x20, 0xa9, 0x21, 0x08, 0x01, 0x1a, 0x72, 0x3c, 0x12, + 0xa7, 0x87, 0xe6, 0xd7, 0x88, 0x71, 0x9a, 0x10, 0xbd, 0xba, + 0x5b, 0x26, 0x99, 0xc3, 0x27, 0x18, 0x6a, 0xf4, 0xe2, 0x3c, + 0x1a, 0x94, 0x68, 0x34, 0xb6, 0x15, 0x0b, 0xda, 0x25, 0x83, + 0xe9, 0xca, 0x2a, 0xd4, 0x4c, 0xe8, 0xdb, 0xbb, 0xc2, 0xdb, + 0x04, 0xde, 0x8e, 0xf9, 0x2e, 0x8e, 0xfc, 0x14, 0x1f, 0xbe, + 0xca, 0xa6, 0x28, 0x7c, 0x59, 0x47, 0x4e, 0x6b, 0xc0, 0x5d, + 0x99, 0xb2, 0x96, 0x4f, 0xa0, 0x90, 0xc3, 0xa2, 0x23, 0x3b, + 0xa1, 0x86, 0x51, 0x5b, 0xe7, 0xed, 0x1f, 0x61, 0x29, 0x70, + 0xce, 0xe2, 0xd7, 0xaf, 0xb8, 0x1b, 0xdd, 0x76, 0x21, 0x70, + 0x48, 0x1c, 0xd0, 0x06, 0x91, 0x27, 0xd5, 0xb0, 0x5a, 0xa9, + 0x93, 0xb4, 0xea, 0x98, 0x8d, 0x8f, 0xdd, 0xc1, 0x86, 0xff, + 0xb7, 0xdc, 0x90, 0xa6, 0xc0, 0x8f, 0x4d, 0xf4, 0x35, 0xc9, + 0x34, 0x02, 0x84, 0x92, 0x36, 0xc3, 0xfa, 0xb4, 0xd2, 0x7c, + 0x70, 0x26, 0xc1, 0xd4, 0xdc, 0xb2, 0x60, 0x26, 0x46, 0xde, + 0xc9, 0x75, 0x1e, 0x76, 0x3d, 0xba, 0x37, 0xbd, 0xf8, 0xff, + 0x94, 0x06, 0xad, 0x9e, 0x53, 0x0e, 0xe5, 0xdb, 0x38, 0x2f, + 0x41, 0x30, 0x01, 0xae, 0xb0, 0x6a, 0x53, 0xed, 0x90, 0x27, + 0xd8, 0x31, 0x17, 0x97, 0x27, 0xb0, 0x86, 0x5a, 0x89, 0x18, + 0xda, 0x3e, 0xdb, 0xeb, 0xcf, 0x9b, 0x14, 0xed, 0x44, 0xce, + 0x6c, 0xba, 0xce, 0xd4, 0xbb, 0x1b, 0xdb, 0x7f, 0x14, 0x47, + 0xe6, 0xcc, 0x25, 0x4b, 0x33, 0x20, 0x51, 0x51, 0x2b, 0xd7, + 0xaf, 0x42, 0x6f, 0xb8, 0xf4, 0x01, 0x37, 0x8c, 0xd2, 0xbf, + 0x59, 0x83, 0xca, 0x01, 0xc6, 0x4b, 0x92, 0xec, 0xf0, 0x32, + 0xea, 0x15, 0xd1, 0x72, 0x1d, 0x03, 0xf4, 0x82, 0xd7, 0xce, + 0x6e, 0x74, 0xfe, 0xf6, 0xd5, 0x5e, 0x70, 0x2f, 0x46, 0x98, + 0x0c, 0x82, 0xb5, 0xa8, 0x40, 0x31, 0x90, 0x0b, 0x1c, 0x9e, + 0x59, 0xe7, 0xc9, 0x7f, 0xbe, 0xc7, 0xe8, 0xf3, 0x23, 0xa9, + 0x7a, 0x7e, 0x36, 0xcc, 0x88, 0xbe, 0x0f, 0x1d, 0x45, 0xb7, + 0xff, 0x58, 0x5a, 0xc5, 0x4b, 0xd4, 0x07, 0xb2, 0x2b, 0x41, + 0x54, 0xaa, 0xcc, 0x8f, 0x6d, 0x7e, 0xbf, 0x48, 0xe1, 0xd8, + 0x14, 0xcc, 0x5e, 0xd2, 0x0f, 0x80, 0x37, 0xe0, 0xa7, 0x97, + 0x15, 0xee, 0xf2, 0x9b, 0xe3, 0x28, 0x06, 0xa1, 0xd5, 0x8b, + 0xb7, 0xc5, 0xda, 0x76, 0xf5, 0x50, 0xaa, 0x3d, 0x8a, 0x1f, + 0xbf, 0xf0, 0xeb, 0x19, 0xcc, 0xb1, 0xa3, 0x13, 0xd5, 0x5c, + 0xda, 0x56, 0xc9, 0xec, 0x2e, 0xf2, 0x96, 0x32, 0x38, 0x7f, + 0xe8, 0xd7, 0x6e, 0x3c, 0x04, 0x68, 0x04, 0x3e, 0x8f, 0x66, + 0x3f, 0x48, 0x60, 0xee, 0x12, 0xbf, 0x2d, 0x5b, 0x0b, 0x74, + 0x74, 0xd6, 0xe6, 0x94, 0xf9, 0x1e, 0x6d, 0xcc, 0x40, 0x24, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +static const unsigned char group_modp18[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, + 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, + 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, + 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, + 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, 0xef, 0x95, + 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, + 0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, + 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, + 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, + 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed, 0xee, 0x38, 0x6b, 0xfb, + 0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11, 0x7c, 0x4b, + 0x1f, 0xe6, 0x49, 0x28, 0x66, 0x51, 0xec, 0xe4, 0x5b, 0x3d, + 0xc2, 0x00, 0x7c, 0xb8, 0xa1, 0x63, 0xbf, 0x05, 0x98, 0xda, + 0x48, 0x36, 0x1c, 0x55, 0xd3, 0x9a, 0x69, 0x16, 0x3f, 0xa8, + 0xfd, 0x24, 0xcf, 0x5f, 0x83, 0x65, 0x5d, 0x23, 0xdc, 0xa3, + 0xad, 0x96, 0x1c, 0x62, 0xf3, 0x56, 0x20, 0x85, 0x52, 0xbb, + 0x9e, 0xd5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6d, 0x67, 0x0c, + 0x35, 0x4e, 0x4a, 0xbc, 0x98, 0x04, 0xf1, 0x74, 0x6c, 0x08, + 0xca, 0x18, 0x21, 0x7c, 0x32, 0x90, 0x5e, 0x46, 0x2e, 0x36, + 0xce, 0x3b, 0xe3, 0x9e, 0x77, 0x2c, 0x18, 0x0e, 0x86, 0x03, + 0x9b, 0x27, 0x83, 0xa2, 0xec, 0x07, 0xa2, 0x8f, 0xb5, 0xc5, + 0x5d, 0xf0, 0x6f, 0x4c, 0x52, 0xc9, 0xde, 0x2b, 0xcb, 0xf6, + 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7c, 0xea, 0x95, + 0x6a, 0xe5, 0x15, 0xd2, 0x26, 0x18, 0x98, 0xfa, 0x05, 0x10, + 0x15, 0x72, 0x8e, 0x5a, 0x8a, 0xaa, 0xc4, 0x2d, 0xad, 0x33, + 0x17, 0x0d, 0x04, 0x50, 0x7a, 0x33, 0xa8, 0x55, 0x21, 0xab, + 0xdf, 0x1c, 0xba, 0x64, 0xec, 0xfb, 0x85, 0x04, 0x58, 0xdb, + 0xef, 0x0a, 0x8a, 0xea, 0x71, 0x57, 0x5d, 0x06, 0x0c, 0x7d, + 0xb3, 0x97, 0x0f, 0x85, 0xa6, 0xe1, 0xe4, 0xc7, 0xab, 0xf5, + 0xae, 0x8c, 0xdb, 0x09, 0x33, 0xd7, 0x1e, 0x8c, 0x94, 0xe0, + 0x4a, 0x25, 0x61, 0x9d, 0xce, 0xe3, 0xd2, 0x26, 0x1a, 0xd2, + 0xee, 0x6b, 0xf1, 0x2f, 0xfa, 0x06, 0xd9, 0x8a, 0x08, 0x64, + 0xd8, 0x76, 0x02, 0x73, 0x3e, 0xc8, 0x6a, 0x64, 0x52, 0x1f, + 0x2b, 0x18, 0x17, 0x7b, 0x20, 0x0c, 0xbb, 0xe1, 0x17, 0x57, + 0x7a, 0x61, 0x5d, 0x6c, 0x77, 0x09, 0x88, 0xc0, 0xba, 0xd9, + 0x46, 0xe2, 0x08, 0xe2, 0x4f, 0xa0, 0x74, 0xe5, 0xab, 0x31, + 0x43, 0xdb, 0x5b, 0xfc, 0xe0, 0xfd, 0x10, 0x8e, 0x4b, 0x82, + 0xd1, 0x20, 0xa9, 0x21, 0x08, 0x01, 0x1a, 0x72, 0x3c, 0x12, + 0xa7, 0x87, 0xe6, 0xd7, 0x88, 0x71, 0x9a, 0x10, 0xbd, 0xba, + 0x5b, 0x26, 0x99, 0xc3, 0x27, 0x18, 0x6a, 0xf4, 0xe2, 0x3c, + 0x1a, 0x94, 0x68, 0x34, 0xb6, 0x15, 0x0b, 0xda, 0x25, 0x83, + 0xe9, 0xca, 0x2a, 0xd4, 0x4c, 0xe8, 0xdb, 0xbb, 0xc2, 0xdb, + 0x04, 0xde, 0x8e, 0xf9, 0x2e, 0x8e, 0xfc, 0x14, 0x1f, 0xbe, + 0xca, 0xa6, 0x28, 0x7c, 0x59, 0x47, 0x4e, 0x6b, 0xc0, 0x5d, + 0x99, 0xb2, 0x96, 0x4f, 0xa0, 0x90, 0xc3, 0xa2, 0x23, 0x3b, + 0xa1, 0x86, 0x51, 0x5b, 0xe7, 0xed, 0x1f, 0x61, 0x29, 0x70, + 0xce, 0xe2, 0xd7, 0xaf, 0xb8, 0x1b, 0xdd, 0x76, 0x21, 0x70, + 0x48, 0x1c, 0xd0, 0x06, 0x91, 0x27, 0xd5, 0xb0, 0x5a, 0xa9, + 0x93, 0xb4, 0xea, 0x98, 0x8d, 0x8f, 0xdd, 0xc1, 0x86, 0xff, + 0xb7, 0xdc, 0x90, 0xa6, 0xc0, 0x8f, 0x4d, 0xf4, 0x35, 0xc9, + 0x34, 0x02, 0x84, 0x92, 0x36, 0xc3, 0xfa, 0xb4, 0xd2, 0x7c, + 0x70, 0x26, 0xc1, 0xd4, 0xdc, 0xb2, 0x60, 0x26, 0x46, 0xde, + 0xc9, 0x75, 0x1e, 0x76, 0x3d, 0xba, 0x37, 0xbd, 0xf8, 0xff, + 0x94, 0x06, 0xad, 0x9e, 0x53, 0x0e, 0xe5, 0xdb, 0x38, 0x2f, + 0x41, 0x30, 0x01, 0xae, 0xb0, 0x6a, 0x53, 0xed, 0x90, 0x27, + 0xd8, 0x31, 0x17, 0x97, 0x27, 0xb0, 0x86, 0x5a, 0x89, 0x18, + 0xda, 0x3e, 0xdb, 0xeb, 0xcf, 0x9b, 0x14, 0xed, 0x44, 0xce, + 0x6c, 0xba, 0xce, 0xd4, 0xbb, 0x1b, 0xdb, 0x7f, 0x14, 0x47, + 0xe6, 0xcc, 0x25, 0x4b, 0x33, 0x20, 0x51, 0x51, 0x2b, 0xd7, + 0xaf, 0x42, 0x6f, 0xb8, 0xf4, 0x01, 0x37, 0x8c, 0xd2, 0xbf, + 0x59, 0x83, 0xca, 0x01, 0xc6, 0x4b, 0x92, 0xec, 0xf0, 0x32, + 0xea, 0x15, 0xd1, 0x72, 0x1d, 0x03, 0xf4, 0x82, 0xd7, 0xce, + 0x6e, 0x74, 0xfe, 0xf6, 0xd5, 0x5e, 0x70, 0x2f, 0x46, 0x98, + 0x0c, 0x82, 0xb5, 0xa8, 0x40, 0x31, 0x90, 0x0b, 0x1c, 0x9e, + 0x59, 0xe7, 0xc9, 0x7f, 0xbe, 0xc7, 0xe8, 0xf3, 0x23, 0xa9, + 0x7a, 0x7e, 0x36, 0xcc, 0x88, 0xbe, 0x0f, 0x1d, 0x45, 0xb7, + 0xff, 0x58, 0x5a, 0xc5, 0x4b, 0xd4, 0x07, 0xb2, 0x2b, 0x41, + 0x54, 0xaa, 0xcc, 0x8f, 0x6d, 0x7e, 0xbf, 0x48, 0xe1, 0xd8, + 0x14, 0xcc, 0x5e, 0xd2, 0x0f, 0x80, 0x37, 0xe0, 0xa7, 0x97, + 0x15, 0xee, 0xf2, 0x9b, 0xe3, 0x28, 0x06, 0xa1, 0xd5, 0x8b, + 0xb7, 0xc5, 0xda, 0x76, 0xf5, 0x50, 0xaa, 0x3d, 0x8a, 0x1f, + 0xbf, 0xf0, 0xeb, 0x19, 0xcc, 0xb1, 0xa3, 0x13, 0xd5, 0x5c, + 0xda, 0x56, 0xc9, 0xec, 0x2e, 0xf2, 0x96, 0x32, 0x38, 0x7f, + 0xe8, 0xd7, 0x6e, 0x3c, 0x04, 0x68, 0x04, 0x3e, 0x8f, 0x66, + 0x3f, 0x48, 0x60, 0xee, 0x12, 0xbf, 0x2d, 0x5b, 0x0b, 0x74, + 0x74, 0xd6, 0xe6, 0x94, 0xf9, 0x1e, 0x6d, 0xbe, 0x11, 0x59, + 0x74, 0xa3, 0x92, 0x6f, 0x12, 0xfe, 0xe5, 0xe4, 0x38, 0x77, + 0x7c, 0xb6, 0xa9, 0x32, 0xdf, 0x8c, 0xd8, 0xbe, 0xc4, 0xd0, + 0x73, 0xb9, 0x31, 0xba, 0x3b, 0xc8, 0x32, 0xb6, 0x8d, 0x9d, + 0xd3, 0x00, 0x74, 0x1f, 0xa7, 0xbf, 0x8a, 0xfc, 0x47, 0xed, + 0x25, 0x76, 0xf6, 0x93, 0x6b, 0xa4, 0x24, 0x66, 0x3a, 0xab, + 0x63, 0x9c, 0x5a, 0xe4, 0xf5, 0x68, 0x34, 0x23, 0xb4, 0x74, + 0x2b, 0xf1, 0xc9, 0x78, 0x23, 0x8f, 0x16, 0xcb, 0xe3, 0x9d, + 0x65, 0x2d, 0xe3, 0xfd, 0xb8, 0xbe, 0xfc, 0x84, 0x8a, 0xd9, + 0x22, 0x22, 0x2e, 0x04, 0xa4, 0x03, 0x7c, 0x07, 0x13, 0xeb, + 0x57, 0xa8, 0x1a, 0x23, 0xf0, 0xc7, 0x34, 0x73, 0xfc, 0x64, + 0x6c, 0xea, 0x30, 0x6b, 0x4b, 0xcb, 0xc8, 0x86, 0x2f, 0x83, + 0x85, 0xdd, 0xfa, 0x9d, 0x4b, 0x7f, 0xa2, 0xc0, 0x87, 0xe8, + 0x79, 0x68, 0x33, 0x03, 0xed, 0x5b, 0xdd, 0x3a, 0x06, 0x2b, + 0x3c, 0xf5, 0xb3, 0xa2, 0x78, 0xa6, 0x6d, 0x2a, 0x13, 0xf8, + 0x3f, 0x44, 0xf8, 0x2d, 0xdf, 0x31, 0x0e, 0xe0, 0x74, 0xab, + 0x6a, 0x36, 0x45, 0x97, 0xe8, 0x99, 0xa0, 0x25, 0x5d, 0xc1, + 0x64, 0xf3, 0x1c, 0xc5, 0x08, 0x46, 0x85, 0x1d, 0xf9, 0xab, + 0x48, 0x19, 0x5d, 0xed, 0x7e, 0xa1, 0xb1, 0xd5, 0x10, 0xbd, + 0x7e, 0xe7, 0x4d, 0x73, 0xfa, 0xf3, 0x6b, 0xc3, 0x1e, 0xcf, + 0xa2, 0x68, 0x35, 0x90, 0x46, 0xf4, 0xeb, 0x87, 0x9f, 0x92, + 0x40, 0x09, 0x43, 0x8b, 0x48, 0x1c, 0x6c, 0xd7, 0x88, 0x9a, + 0x00, 0x2e, 0xd5, 0xee, 0x38, 0x2b, 0xc9, 0x19, 0x0d, 0xa6, + 0xfc, 0x02, 0x6e, 0x47, 0x95, 0x58, 0xe4, 0x47, 0x56, 0x77, + 0xe9, 0xaa, 0x9e, 0x30, 0x50, 0xe2, 0x76, 0x56, 0x94, 0xdf, + 0xc8, 0x1f, 0x56, 0xe8, 0x80, 0xb9, 0x6e, 0x71, 0x60, 0xc9, + 0x80, 0xdd, 0x98, 0xed, 0xd3, 0xdf, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff }; + +typedef struct { + const char* name; + const char* prime; + unsigned int prime_size; + const char* gen; + unsigned int gen_size; +} modp_group; + +static const modp_group modp_groups[] = { +#define V(var) reinterpret_cast(var) + { "modp1", V(group_modp1), sizeof(group_modp1), V(two_generator), 1 }, + { "modp2", V(group_modp2), sizeof(group_modp2), V(two_generator), 1 }, + { "modp5", V(group_modp5), sizeof(group_modp5), V(two_generator), 1 }, + { "modp14", V(group_modp14), sizeof(group_modp14), V(two_generator), 1 }, + { "modp15", V(group_modp15), sizeof(group_modp15), V(two_generator), 1 }, + { "modp16", V(group_modp16), sizeof(group_modp16), V(two_generator), 1 }, + { "modp17", V(group_modp17), sizeof(group_modp17), V(two_generator), 1 }, + { "modp18", V(group_modp18), sizeof(group_modp18), V(two_generator), 1 } +#undef V +}; + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_CRYPTO_IMPL_NODE_CRYPTO_GROUPS_H_ diff --git a/src/crypto_impl/openssl/1_1_0f/openssl_1_1_0f.cc b/src/crypto_impl/openssl/1_1_0f/openssl_1_1_0f.cc new file mode 100644 index 00000000000000..f34ab8855e8546 --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/openssl_1_1_0f.cc @@ -0,0 +1,62 @@ +#include "../openssl.h" +#include "../../../node_crypto_factory.h" +#include "node_crypto.h" + +namespace node { +namespace crypto { + +const std::string id_ = CryptoFactory::Register("openssl_1_1_0f", + []() -> Crypto* { return new OpenSSL(); }); + +const std::string OpenSSL::Version() { + return "1.1.0f"; +} + +bool OpenSSL::HasSNI() { +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + return true; +#else + return false; +#endif +} + +bool OpenSSL::HasNPN() { +#ifndef OPENSSL_NO_NEXTPROTONEG + return true; +#else + return false; +#endif +} + +bool OpenSSL::HasALPN() { +#ifndef TLSEXT_TYPE_application_layer_protocol_negotiation + return true; +#else + return false; +#endif +} + +bool OpenSSL::HasOCSP() { +#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) + return true; +#else + return false; +#endif +} + +OpenSSL::OpenSSL() { +} + +OpenSSL::~OpenSSL() { +} + +void OpenSSL::UseExtraCaCerts(const std::string& file) { + crypto::UseExtraCaCerts(file); +} + +v8::EntropySource OpenSSL::GetEntropySource() { + return crypto::EntropySource; +} + +} // namespace crypto +} // namespace node diff --git a/src/crypto_impl/openssl/1_1_0f/tls_wrap.cc b/src/crypto_impl/openssl/1_1_0f/tls_wrap.cc new file mode 100644 index 00000000000000..351f8a34fe0884 --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/tls_wrap.cc @@ -0,0 +1,969 @@ +// 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 "tls_wrap.h" +#include "async-wrap.h" +#include "async-wrap-inl.h" +#include "node_buffer.h" // Buffer +#include "node_crypto.h" // SecureContext +#include "node_crypto_bio.h" // NodeBIO +#include "node_crypto_clienthello.h" // ClientHelloParser +#include "node_crypto_clienthello-inl.h" +#include "node_counters.h" +#include "node_internals.h" +#include "stream_base.h" +#include "stream_base-inl.h" +#include "util.h" +#include "util-inl.h" + +namespace node { + +using crypto::SecureContext; +using crypto::SSLWrap; +using v8::Context; +using v8::EscapableHandleScope; +using v8::Exception; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; + +TLSWrap::TLSWrap(Environment* env, + Kind kind, + StreamBase* stream, + SecureContext* sc) + : AsyncWrap(env, + env->tls_wrap_constructor_function() + ->NewInstance(env->context()).ToLocalChecked(), + AsyncWrap::PROVIDER_TLSWRAP), + SSLWrap(env, sc, kind), + StreamBase(env), + sc_(sc), + stream_(stream), + enc_in_(nullptr), + enc_out_(nullptr), + clear_in_(nullptr), + write_size_(0), + started_(false), + established_(false), + shutdown_(false), + error_(nullptr), + cycle_depth_(0), + eof_(false) { + node::Wrap(object(), this); + MakeWeak(this); + + // sc comes from an Unwrap. Make sure it was assigned. + CHECK_NE(sc, nullptr); + + // We've our own session callbacks + SSL_CTX_sess_set_get_cb(sc_->ctx_, SSLWrap::GetSessionCallback); + SSL_CTX_sess_set_new_cb(sc_->ctx_, SSLWrap::NewSessionCallback); + + stream_->Consume(); + stream_->set_after_write_cb({ OnAfterWriteImpl, this }); + stream_->set_alloc_cb({ OnAllocImpl, this }); + stream_->set_read_cb({ OnReadImpl, this }); + stream_->set_destruct_cb({ OnDestructImpl, this }); + + set_alloc_cb({ OnAllocSelf, this }); + set_read_cb({ OnReadSelf, this }); + + InitSSL(); +} + + +TLSWrap::~TLSWrap() { + enc_in_ = nullptr; + enc_out_ = nullptr; + delete clear_in_; + clear_in_ = nullptr; + + sc_ = nullptr; + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + sni_context_.Reset(); +#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + + ClearError(); +} + + +void TLSWrap::MakePending() { + write_item_queue_.MoveBack(&pending_write_items_); +} + + +bool TLSWrap::InvokeQueued(int status, const char* error_str) { + if (pending_write_items_.IsEmpty()) + return false; + + // Process old queue + WriteItemList queue; + pending_write_items_.MoveBack(&queue); + while (WriteItem* wi = queue.PopFront()) { + wi->w_->Done(status, error_str); + delete wi; + } + + return true; +} + + +void TLSWrap::NewSessionDoneCb() { + Cycle(); +} + + +void TLSWrap::InitSSL() { + // Initialize SSL + enc_in_ = NodeBIO::New(); + enc_out_ = NodeBIO::New(); + NodeBIO::FromBIO(enc_in_)->AssignEnvironment(env()); + NodeBIO::FromBIO(enc_out_)->AssignEnvironment(env()); + + SSL_set_bio(ssl_, enc_in_, enc_out_); + + // NOTE: This could be overridden in SetVerifyMode + SSL_set_verify(ssl_, SSL_VERIFY_NONE, crypto::VerifyCallback); + +#ifdef SSL_MODE_RELEASE_BUFFERS + long mode = SSL_get_mode(ssl_); // NOLINT(runtime/int) + SSL_set_mode(ssl_, mode | SSL_MODE_RELEASE_BUFFERS); +#endif // SSL_MODE_RELEASE_BUFFERS + + SSL_set_app_data(ssl_, this); + SSL_set_info_callback(ssl_, SSLInfoCallback); + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + if (is_server()) { + SSL_CTX_set_tlsext_servername_callback(sc_->ctx_, SelectSNIContextCallback); + } +#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + + InitNPN(sc_); + + SSL_set_cert_cb(ssl_, SSLWrap::SSLCertCallback, this); + + if (is_server()) { + SSL_set_accept_state(ssl_); + } else if (is_client()) { + // Enough space for server response (hello, cert) + NodeBIO::FromBIO(enc_in_)->set_initial(kInitialClientBufferLength); + SSL_set_connect_state(ssl_); + } else { + // Unexpected + ABORT(); + } + + // Initialize ring for queud clear data + clear_in_ = new NodeBIO(); + clear_in_->AssignEnvironment(env()); +} + + +void TLSWrap::Wrap(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (args.Length() < 1 || !args[0]->IsObject()) { + return env->ThrowTypeError( + "First argument should be a StreamWrap instance"); + } + if (args.Length() < 2 || !args[1]->IsObject()) { + return env->ThrowTypeError( + "Second argument should be a SecureContext instance"); + } + if (args.Length() < 3 || !args[2]->IsBoolean()) + return env->ThrowTypeError("Third argument should be boolean"); + + Local stream_obj = args[0].As(); + Local sc = args[1].As(); + Kind kind = args[2]->IsTrue() ? SSLWrap::kServer : + SSLWrap::kClient; + + StreamBase* stream = static_cast(stream_obj->Value()); + CHECK_NE(stream, nullptr); + + TLSWrap* res = new TLSWrap(env, kind, stream, Unwrap(sc)); + + args.GetReturnValue().Set(res->object()); +} + + +void TLSWrap::Receive(const FunctionCallbackInfo& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + CHECK(Buffer::HasInstance(args[0])); + char* data = Buffer::Data(args[0]); + size_t len = Buffer::Length(args[0]); + + uv_buf_t buf; + + // Copy given buffer entirely or partiall if handle becomes closed + while (len > 0 && wrap->IsAlive() && !wrap->IsClosing()) { + wrap->stream_->OnAlloc(len, &buf); + size_t copy = buf.len > len ? len : buf.len; + memcpy(buf.base, data, copy); + buf.len = copy; + wrap->stream_->OnRead(buf.len, &buf); + + data += copy; + len -= copy; + } +} + + +void TLSWrap::Start(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + if (wrap->started_) + return env->ThrowError("Already started."); + wrap->started_ = true; + + // Send ClientHello handshake + CHECK(wrap->is_client()); + wrap->ClearOut(); + wrap->EncOut(); +} + + +void TLSWrap::SSLInfoCallback(const SSL* ssl_, int where, int ret) { + if (!(where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE))) + return; + + // Be compatible with older versions of OpenSSL. SSL_get_app_data() wants + // a non-const SSL* in OpenSSL <= 0.9.7e. + SSL* ssl = const_cast(ssl_); + TLSWrap* c = static_cast(SSL_get_app_data(ssl)); + Environment* env = c->env(); + Local object = c->object(); + + if (where & SSL_CB_HANDSHAKE_START) { + Local callback = object->Get(env->onhandshakestart_string()); + if (callback->IsFunction()) { + c->MakeCallback(callback.As(), 0, nullptr); + } + } + + if (where & SSL_CB_HANDSHAKE_DONE) { + c->established_ = true; + Local callback = object->Get(env->onhandshakedone_string()); + if (callback->IsFunction()) { + c->MakeCallback(callback.As(), 0, nullptr); + } + } +} + + +void TLSWrap::EncOut() { + // Ignore cycling data if ClientHello wasn't yet parsed + if (!hello_parser_.IsEnded()) + return; + + // Write in progress + if (write_size_ != 0) + return; + + // Wait for `newSession` callback to be invoked + if (is_waiting_new_session()) + return; + + // Split-off queue + if (established_ && !write_item_queue_.IsEmpty()) + MakePending(); + + if (ssl_ == nullptr) + return; + + // No data to write + if (BIO_pending(enc_out_) == 0) { + if (clear_in_->Length() == 0) + InvokeQueued(0); + return; + } + + char* data[kSimultaneousBufferCount]; + size_t size[arraysize(data)]; + size_t count = arraysize(data); + write_size_ = NodeBIO::FromBIO(enc_out_)->PeekMultiple(data, size, &count); + CHECK(write_size_ != 0 && count != 0); + + Local req_wrap_obj = + env()->write_wrap_constructor_function() + ->NewInstance(env()->context()).ToLocalChecked(); + WriteWrap* write_req = WriteWrap::New(env(), + req_wrap_obj, + this, + EncOutCb); + + uv_buf_t buf[arraysize(data)]; + for (size_t i = 0; i < count; i++) + buf[i] = uv_buf_init(data[i], size[i]); + int err = stream_->DoWrite(write_req, buf, count, nullptr); + + // Ignore errors, this should be already handled in js + if (err) { + write_req->Dispose(); + InvokeQueued(err); + } else { + NODE_COUNT_NET_BYTES_SENT(write_size_); + } +} + + +void TLSWrap::EncOutCb(WriteWrap* req_wrap, int status) { + TLSWrap* wrap = req_wrap->wrap()->Cast(); + req_wrap->Dispose(); + + // We should not be getting here after `DestroySSL`, because all queued writes + // must be invoked with UV_ECANCELED + CHECK_NE(wrap->ssl_, nullptr); + + // Handle error + if (status) { + // Ignore errors after shutdown + if (wrap->shutdown_) + return; + + // Notify about error + wrap->InvokeQueued(status); + return; + } + + // Commit + NodeBIO::FromBIO(wrap->enc_out_)->Read(nullptr, wrap->write_size_); + + // Ensure that the progress will be made and `InvokeQueued` will be called. + wrap->ClearIn(); + + // Try writing more data + wrap->write_size_ = 0; + wrap->EncOut(); +} + + +Local TLSWrap::GetSSLError(int status, int* err, const char** msg) { + EscapableHandleScope scope(env()->isolate()); + + // ssl_ is already destroyed in reading EOF by close notify alert. + if (ssl_ == nullptr) + return Local(); + + *err = SSL_get_error(ssl_, status); + switch (*err) { + case SSL_ERROR_NONE: + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_X509_LOOKUP: + break; + case SSL_ERROR_ZERO_RETURN: + return scope.Escape(env()->zero_return_string()); + break; + default: + { + CHECK(*err == SSL_ERROR_SSL || *err == SSL_ERROR_SYSCALL); + + BIO* bio = BIO_new(BIO_s_mem()); + ERR_print_errors(bio); + + BUF_MEM* mem; + BIO_get_mem_ptr(bio, &mem); + + Local message = + OneByteString(env()->isolate(), mem->data, mem->length); + Local exception = Exception::Error(message); + + if (msg != nullptr) { + CHECK_EQ(*msg, nullptr); + char* const buf = new char[mem->length + 1]; + memcpy(buf, mem->data, mem->length); + buf[mem->length] = '\0'; + *msg = buf; + } + BIO_free_all(bio); + + return scope.Escape(exception); + } + } + return Local(); +} + + +void TLSWrap::ClearOut() { + // Ignore cycling data if ClientHello wasn't yet parsed + if (!hello_parser_.IsEnded()) + return; + + // No reads after EOF + if (eof_) + return; + + if (ssl_ == nullptr) + return; + + crypto::MarkPopErrorOnReturn mark_pop_error_on_return; + + char out[kClearOutChunkSize]; + int read; + for (;;) { + read = SSL_read(ssl_, out, sizeof(out)); + + if (read <= 0) + break; + + char* current = out; + while (read > 0) { + int avail = read; + + uv_buf_t buf; + OnAlloc(avail, &buf); + if (static_cast(buf.len) < avail) + avail = buf.len; + memcpy(buf.base, current, avail); + OnRead(avail, &buf); + + // Caveat emptor: OnRead() calls into JS land which can result in + // the SSL context object being destroyed. We have to carefully + // check that ssl_ != nullptr afterwards. + if (ssl_ == nullptr) + return; + + read -= avail; + current += avail; + } + } + + int flags = SSL_get_shutdown(ssl_); + if (!eof_ && flags & SSL_RECEIVED_SHUTDOWN) { + eof_ = true; + OnRead(UV_EOF, nullptr); + } + + // We need to check whether an error occurred or the connection was + // shutdown cleanly (SSL_ERROR_ZERO_RETURN) even when read == 0. + // See node#1642 and SSL_read(3SSL) for details. + if (read <= 0) { + int err; + Local arg = GetSSLError(read, &err, nullptr); + + // Ignore ZERO_RETURN after EOF, it is basically not a error + if (err == SSL_ERROR_ZERO_RETURN && eof_) + return; + + if (!arg.IsEmpty()) { + // When TLS Alert are stored in wbio, + // it should be flushed to socket before destroyed. + if (BIO_pending(enc_out_) != 0) + EncOut(); + + MakeCallback(env()->onerror_string(), 1, &arg); + } + } +} + + +bool TLSWrap::ClearIn() { + // Ignore cycling data if ClientHello wasn't yet parsed + if (!hello_parser_.IsEnded()) + return false; + + if (ssl_ == nullptr) + return false; + + crypto::MarkPopErrorOnReturn mark_pop_error_on_return; + + int written = 0; + while (clear_in_->Length() > 0) { + size_t avail = 0; + char* data = clear_in_->Peek(&avail); + written = SSL_write(ssl_, data, avail); + CHECK(written == -1 || written == static_cast(avail)); + if (written == -1) + break; + clear_in_->Read(nullptr, avail); + } + + // All written + if (clear_in_->Length() == 0) { + CHECK_GE(written, 0); + return true; + } + + // Error or partial write + int err; + const char* error_str = nullptr; + Local arg = GetSSLError(written, &err, &error_str); + if (!arg.IsEmpty()) { + MakePending(); + InvokeQueued(UV_EPROTO, error_str); + delete[] error_str; + clear_in_->Reset(); + } + + return false; +} + + +void* TLSWrap::Cast() { + return reinterpret_cast(this); +} + + +AsyncWrap* TLSWrap::GetAsyncWrap() { + return static_cast(this); +} + + +bool TLSWrap::IsIPCPipe() { + return stream_->IsIPCPipe(); +} + + +int TLSWrap::GetFD() { + return stream_->GetFD(); +} + + +bool TLSWrap::IsAlive() { + return ssl_ != nullptr && stream_ != nullptr && stream_->IsAlive(); +} + + +bool TLSWrap::IsClosing() { + return stream_->IsClosing(); +} + + +int TLSWrap::ReadStart() { + return stream_->ReadStart(); +} + + +int TLSWrap::ReadStop() { + return stream_->ReadStop(); +} + + +const char* TLSWrap::Error() const { + return error_; +} + + +void TLSWrap::ClearError() { + delete[] error_; + error_ = nullptr; +} + + +int TLSWrap::DoWrite(WriteWrap* w, + uv_buf_t* bufs, + size_t count, + uv_stream_t* send_handle) { + CHECK_EQ(send_handle, nullptr); + CHECK_NE(ssl_, nullptr); + + bool empty = true; + + // Empty writes should not go through encryption process + size_t i; + for (i = 0; i < count; i++) + if (bufs[i].len > 0) { + empty = false; + break; + } + if (empty) { + ClearOut(); + // However, if there is any data that should be written to the socket, + // the callback should not be invoked immediately + if (BIO_pending(enc_out_) == 0) + return stream_->DoWrite(w, bufs, count, send_handle); + } + + // Queue callback to execute it on next tick + write_item_queue_.PushBack(new WriteItem(w)); + w->Dispatched(); + + // Write queued data + if (empty) { + EncOut(); + return 0; + } + + // Process enqueued data first + if (!ClearIn()) { + // If there're still data to process - enqueue current one + for (i = 0; i < count; i++) + clear_in_->Write(bufs[i].base, bufs[i].len); + return 0; + } + + if (ssl_ == nullptr) { + ClearError(); + + static char msg[] = "Write after DestroySSL"; + char* tmp = new char[sizeof(msg)]; + memcpy(tmp, msg, sizeof(msg)); + error_ = tmp; + return UV_EPROTO; + } + + crypto::MarkPopErrorOnReturn mark_pop_error_on_return; + + int written = 0; + for (i = 0; i < count; i++) { + written = SSL_write(ssl_, bufs[i].base, bufs[i].len); + CHECK(written == -1 || written == static_cast(bufs[i].len)); + if (written == -1) + break; + } + + if (i != count) { + int err; + Local arg = GetSSLError(written, &err, &error_); + if (!arg.IsEmpty()) + return UV_EPROTO; + + // No errors, queue rest + for (; i < count; i++) + clear_in_->Write(bufs[i].base, bufs[i].len); + } + + // Try writing data immediately + EncOut(); + + return 0; +} + + +void TLSWrap::OnAfterWriteImpl(WriteWrap* w, void* ctx) { + // Intentionally empty +} + + +void TLSWrap::OnAllocImpl(size_t suggested_size, uv_buf_t* buf, void* ctx) { + TLSWrap* wrap = static_cast(ctx); + + if (wrap->ssl_ == nullptr) { + *buf = uv_buf_init(nullptr, 0); + return; + } + + size_t size = 0; + buf->base = NodeBIO::FromBIO(wrap->enc_in_)->PeekWritable(&size); + buf->len = size; +} + + +void TLSWrap::OnReadImpl(ssize_t nread, + const uv_buf_t* buf, + uv_handle_type pending, + void* ctx) { + TLSWrap* wrap = static_cast(ctx); + wrap->DoRead(nread, buf, pending); +} + + +void TLSWrap::OnDestructImpl(void* ctx) { + TLSWrap* wrap = static_cast(ctx); + wrap->clear_stream(); +} + + +void TLSWrap::OnAllocSelf(size_t suggested_size, uv_buf_t* buf, void* ctx) { + buf->base = node::Malloc(suggested_size); + buf->len = suggested_size; +} + + +void TLSWrap::OnReadSelf(ssize_t nread, + const uv_buf_t* buf, + uv_handle_type pending, + void* ctx) { + TLSWrap* wrap = static_cast(ctx); + Local buf_obj; + if (buf != nullptr) + buf_obj = Buffer::New(wrap->env(), buf->base, buf->len).ToLocalChecked(); + wrap->EmitData(nread, buf_obj, Local()); +} + + +void TLSWrap::DoRead(ssize_t nread, + const uv_buf_t* buf, + uv_handle_type pending) { + if (nread < 0) { + // Error should be emitted only after all data was read + ClearOut(); + + // Ignore EOF if received close_notify + if (nread == UV_EOF) { + if (eof_) + return; + eof_ = true; + } + + OnRead(nread, nullptr); + return; + } + + // Only client connections can receive data + if (ssl_ == nullptr) { + OnRead(UV_EPROTO, nullptr); + return; + } + + // Commit read data + NodeBIO* enc_in = NodeBIO::FromBIO(enc_in_); + enc_in->Commit(nread); + + // Parse ClientHello first + if (!hello_parser_.IsEnded()) { + size_t avail = 0; + uint8_t* data = reinterpret_cast(enc_in->Peek(&avail)); + CHECK(avail == 0 || data != nullptr); + return hello_parser_.Parse(data, avail); + } + + // Cycle OpenSSL's state + Cycle(); +} + + +int TLSWrap::DoShutdown(ShutdownWrap* req_wrap) { + crypto::MarkPopErrorOnReturn mark_pop_error_on_return; + + if (ssl_ != nullptr && SSL_shutdown(ssl_) == 0) + SSL_shutdown(ssl_); + + shutdown_ = true; + EncOut(); + return stream_->DoShutdown(req_wrap); +} + + +void TLSWrap::SetVerifyMode(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + if (args.Length() < 2 || !args[0]->IsBoolean() || !args[1]->IsBoolean()) + return env->ThrowTypeError("Bad arguments, expected two booleans"); + + if (wrap->ssl_ == nullptr) + return env->ThrowTypeError("SetVerifyMode after destroySSL"); + + int verify_mode; + if (wrap->is_server()) { + bool request_cert = args[0]->IsTrue(); + if (!request_cert) { + // Note reject_unauthorized ignored. + verify_mode = SSL_VERIFY_NONE; + } else { + bool reject_unauthorized = args[1]->IsTrue(); + 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(wrap->ssl_, verify_mode, crypto::VerifyCallback); +} + + +void TLSWrap::EnableSessionCallbacks( + const FunctionCallbackInfo& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + if (wrap->ssl_ == nullptr) { + return wrap->env()->ThrowTypeError( + "EnableSessionCallbacks after destroySSL"); + } + wrap->enable_session_callbacks(); + NodeBIO::FromBIO(wrap->enc_in_)->set_initial(kMaxHelloLength); + wrap->hello_parser_.Start(SSLWrap::OnClientHello, + OnClientHelloParseEnd, + wrap); +} + + +void TLSWrap::DestroySSL(const FunctionCallbackInfo& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + // Move all writes to pending + wrap->MakePending(); + + // And destroy + wrap->InvokeQueued(UV_ECANCELED, "Canceled because of SSL destruction"); + + // Destroy the SSL structure and friends + wrap->SSLWrap::DestroySSL(); + + delete wrap->clear_in_; + wrap->clear_in_ = nullptr; +} + + +void TLSWrap::EnableCertCb(const FunctionCallbackInfo& args) { + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + wrap->WaitForCertCb(OnClientHelloParseEnd, wrap); +} + + +void TLSWrap::OnClientHelloParseEnd(void* arg) { + TLSWrap* c = static_cast(arg); + c->Cycle(); +} + + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB +void TLSWrap::GetServername(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + CHECK_NE(wrap->ssl_, nullptr); + + const char* servername = SSL_get_servername(wrap->ssl_, + TLSEXT_NAMETYPE_host_name); + if (servername != nullptr) { + args.GetReturnValue().Set(OneByteString(env->isolate(), servername)); + } else { + args.GetReturnValue().Set(false); + } +} + + +void TLSWrap::SetServername(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + TLSWrap* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + if (args.Length() < 1 || !args[0]->IsString()) + return env->ThrowTypeError("First argument should be a string"); + + if (wrap->started_) + return env->ThrowError("Already started."); + + if (!wrap->is_client()) + return; + + CHECK_NE(wrap->ssl_, nullptr); + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + node::Utf8Value servername(env->isolate(), args[0].As()); + SSL_set_tlsext_host_name(wrap->ssl_, *servername); +#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB +} + + +int TLSWrap::SelectSNIContextCallback(SSL* s, int* ad, void* arg) { + TLSWrap* p = static_cast(SSL_get_app_data(s)); + Environment* env = p->env(); + + const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + + if (servername == nullptr) + return SSL_TLSEXT_ERR_OK; + + // Call the SNI callback and use its return value as context + Local object = p->object(); + Local ctx = object->Get(env->sni_context_string()); + + // Not an object, probably undefined or null + if (!ctx->IsObject()) + return SSL_TLSEXT_ERR_NOACK; + + Local cons = env->secure_context_constructor_template(); + if (!cons->HasInstance(ctx)) { + // Failure: incorrect SNI context object + Local err = Exception::TypeError(env->sni_context_err_string()); + p->MakeCallback(env->onerror_string(), 1, &err); + return SSL_TLSEXT_ERR_NOACK; + } + + p->sni_context_.Reset(); + p->sni_context_.Reset(env->isolate(), ctx); + + SecureContext* sc = Unwrap(ctx.As()); + CHECK_NE(sc, nullptr); + p->SetSNIContext(sc); + return SSL_TLSEXT_ERR_OK; +} +#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + + +void TLSWrap::Initialize(Local target, + Local unused, + Local context) { + Environment* env = Environment::GetCurrent(context); + + env->SetMethod(target, "wrap", TLSWrap::Wrap); + + auto constructor = [](const FunctionCallbackInfo& args) { + CHECK(args.IsConstructCall()); + args.This()->SetAlignedPointerInInternalField(0, nullptr); + }; + auto t = env->NewFunctionTemplate(constructor); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "TLSWrap")); + + env->SetProtoMethod(t, "getAsyncId", AsyncWrap::GetAsyncId); + env->SetProtoMethod(t, "asyncReset", AsyncWrap::AsyncReset); + env->SetProtoMethod(t, "receive", Receive); + env->SetProtoMethod(t, "start", Start); + env->SetProtoMethod(t, "setVerifyMode", SetVerifyMode); + env->SetProtoMethod(t, "enableSessionCallbacks", EnableSessionCallbacks); + env->SetProtoMethod(t, "destroySSL", DestroySSL); + env->SetProtoMethod(t, "enableCertCb", EnableCertCb); + + StreamBase::AddMethods(env, t, StreamBase::kFlagHasWritev); + SSLWrap::AddMethods(env, t); + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + env->SetProtoMethod(t, "getServername", GetServername); + env->SetProtoMethod(t, "setServername", SetServername); +#endif // SSL_CRT_SET_TLSEXT_SERVERNAME_CB + + env->set_tls_wrap_constructor_template(t); + env->set_tls_wrap_constructor_function(t->GetFunction()); + + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "TLSWrap"), + t->GetFunction()); +} + +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(tls_wrap, node::TLSWrap::Initialize) diff --git a/src/crypto_impl/openssl/1_1_0f/tls_wrap.h b/src/crypto_impl/openssl/1_1_0f/tls_wrap.h new file mode 100644 index 00000000000000..d06b86f0d966af --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/tls_wrap.h @@ -0,0 +1,195 @@ +// 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. + +#ifndef SRC_CRYPTO_IMPL_TLS_WRAP_H_ +#define SRC_CRYPTO_IMPL_TLS_WRAP_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node.h" +#include "node_crypto.h" // SSLWrap + +#include "async-wrap.h" +#include "env.h" +#include "stream_wrap.h" +#include "util.h" +#include "v8.h" + +#include + +namespace node { + +// Forward-declarations +class NodeBIO; +class WriteWrap; +namespace crypto { +class SecureContext; +} + +class TLSWrap : public AsyncWrap, + public crypto::SSLWrap, + public StreamBase { + public: + ~TLSWrap() override; + + static void Initialize(v8::Local target, + v8::Local unused, + v8::Local context); + + void* Cast() override; + int GetFD() override; + bool IsAlive() override; + bool IsClosing() override; + + // JavaScript functions + int ReadStart() override; + int ReadStop() override; + + int DoShutdown(ShutdownWrap* req_wrap) override; + int DoWrite(WriteWrap* w, + uv_buf_t* bufs, + size_t count, + uv_stream_t* send_handle) override; + const char* Error() const override; + void ClearError() override; + + void NewSessionDoneCb(); + + size_t self_size() const override { return sizeof(*this); } + + void clear_stream() { stream_ = nullptr; } + + protected: + static const int kClearOutChunkSize = 16384; + + // Maximum number of bytes for hello parser + static const int kMaxHelloLength = 16384; + + // Usual ServerHello + Certificate size + static const int kInitialClientBufferLength = 4096; + + // Maximum number of buffers passed to uv_write() + static const int kSimultaneousBufferCount = 10; + + // Write callback queue's item + class WriteItem { + public: + explicit WriteItem(WriteWrap* w) : w_(w) { + } + ~WriteItem() { + w_ = nullptr; + } + + WriteWrap* w_; + ListNode member_; + }; + + TLSWrap(Environment* env, + Kind kind, + StreamBase* stream, + crypto::SecureContext* sc); + + static void SSLInfoCallback(const SSL* ssl_, int where, int ret); + void InitSSL(); + void EncOut(); + static void EncOutCb(WriteWrap* req_wrap, int status); + bool ClearIn(); + void ClearOut(); + void MakePending(); + bool InvokeQueued(int status, const char* error_str = nullptr); + + inline void Cycle() { + // Prevent recursion + if (++cycle_depth_ > 1) + return; + + for (; cycle_depth_ > 0; cycle_depth_--) { + ClearIn(); + ClearOut(); + EncOut(); + } + } + + AsyncWrap* GetAsyncWrap() override; + bool IsIPCPipe() override; + + // Resource implementation + static void OnAfterWriteImpl(WriteWrap* w, void* ctx); + static void OnAllocImpl(size_t size, uv_buf_t* buf, void* ctx); + static void OnReadImpl(ssize_t nread, + const uv_buf_t* buf, + uv_handle_type pending, + void* ctx); + static void OnAfterWriteSelf(WriteWrap* w, void* ctx); + static void OnAllocSelf(size_t size, uv_buf_t* buf, void* ctx); + static void OnReadSelf(ssize_t nread, + const uv_buf_t* buf, + uv_handle_type pending, + void* ctx); + static void OnDestructImpl(void* ctx); + + void DoRead(ssize_t nread, const uv_buf_t* buf, uv_handle_type pending); + + // If |msg| is not nullptr, caller is responsible for calling `delete[] *msg`. + v8::Local GetSSLError(int status, int* err, const char** msg); + + static void OnClientHelloParseEnd(void* arg); + static void Wrap(const v8::FunctionCallbackInfo& args); + static void Receive(const v8::FunctionCallbackInfo& args); + static void Start(const v8::FunctionCallbackInfo& args); + static void SetVerifyMode(const v8::FunctionCallbackInfo& args); + static void EnableSessionCallbacks( + const v8::FunctionCallbackInfo& args); + static void EnableCertCb( + const v8::FunctionCallbackInfo& args); + static void DestroySSL(const v8::FunctionCallbackInfo& args); + +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + static void GetServername(const v8::FunctionCallbackInfo& args); + static void SetServername(const v8::FunctionCallbackInfo& args); + static int SelectSNIContextCallback(SSL* s, int* ad, void* arg); +#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + + crypto::SecureContext* sc_; + StreamBase* stream_; + BIO* enc_in_; + BIO* enc_out_; + NodeBIO* clear_in_; + size_t write_size_; + typedef ListHead WriteItemList; + WriteItemList write_item_queue_; + WriteItemList pending_write_items_; + bool started_; + bool established_; + bool shutdown_; + const char* error_; + int cycle_depth_; + + // If true - delivered EOF to the js-land, either after `close_notify`, or + // after the `UV_EOF` on socket. + bool eof_; +}; + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_CRYPTO_IMPL_TLS_WRAP_H_ diff --git a/src/crypto_impl/openssl/openssl.h b/src/crypto_impl/openssl/openssl.h new file mode 100644 index 00000000000000..70bf32ee20e85b --- /dev/null +++ b/src/crypto_impl/openssl/openssl.h @@ -0,0 +1,26 @@ +#ifndef SRC_CRYPTO_IMPL_OPENSSL_H_ +#define SRC_CRYPTO_IMPL_OPENSSL_H_ + +#include "../../node_crypto.h" + +namespace node { +namespace crypto { + +class OpenSSL : public Crypto { + public: + OpenSSL(); + ~OpenSSL(); + const std::string Version(); + const std::string Name() { return "openssl"; } + void UseExtraCaCerts(const std::string& file); + v8::EntropySource GetEntropySource(); + bool HasSNI(); + bool HasNPN(); + bool HasALPN(); + bool HasOCSP(); +}; + +} // namespace crypto +} // namespace node + +#endif // SRC_CRYPTO_IMPL_OPENSSL_H_ diff --git a/src/inspector_io.cc b/src/inspector_io.cc index 69eed62ab4729a..7f28e4ec92a179 100644 --- a/src/inspector_io.cc +++ b/src/inspector_io.cc @@ -5,6 +5,7 @@ #include "env-inl.h" #include "node.h" #include "node_crypto.h" +#include "node_crypto_factory.h" #include "node_mutex.h" #include "v8-inspector.h" #include "util.h" @@ -18,12 +19,14 @@ namespace node { +extern std::string crypto_version; namespace inspector { namespace { using AsyncAndAgent = std::pair; using v8_inspector::StringBuffer; using v8_inspector::StringView; + template using TransportAndIo = std::pair; @@ -58,8 +61,10 @@ std::string ScriptPath(uv_loop_t* loop, const std::string& script_name) { // Used ver 4 - with numbers std::string GenerateID() { uint16_t buffer[8]; - CHECK(crypto::EntropySource(reinterpret_cast(buffer), - sizeof(buffer))); + auto crypto = crypto::CryptoFactory::Get(crypto_version); + auto entropy_source = crypto->GetEntropySource(); + CHECK(entropy_source(reinterpret_cast(buffer), + sizeof(buffer))); char uuid[256]; snprintf(uuid, sizeof(uuid), "%04x%04x-%04x-%04x-%04x-%04x%04x%04x", diff --git a/src/inspector_io.h b/src/inspector_io.h index 6ef2ea54c4745d..ee2a76cd3462da 100644 --- a/src/inspector_io.h +++ b/src/inspector_io.h @@ -18,6 +18,7 @@ // Forward declaration to break recursive dependency chain with src/env.h. namespace node { class Environment; +extern std::string crypto_version; } // namespace node namespace v8_inspector { @@ -28,6 +29,7 @@ class StringView; namespace node { namespace inspector { + class InspectorIoDelegate; enum class InspectorAction { diff --git a/src/node.cc b/src/node.cc index bbce10220fe175..ff5ba48c8d6f56 100644 --- a/src/node.cc +++ b/src/node.cc @@ -27,13 +27,14 @@ #include "node_internals.h" #include "node_revert.h" #include "node_debug_options.h" +#include #if defined HAVE_PERFCTR #include "node_counters.h" #endif #if HAVE_OPENSSL -#include "node_crypto.h" +#include "node_crypto_factory.h" #endif #if defined(NODE_HAVE_I18N_SUPPORT) @@ -208,6 +209,12 @@ bool enable_fips_crypto = false; bool force_fips_crypto = false; # endif // NODE_FIPS_MODE std::string openssl_config; // NOLINT(runtime/string) + +#define MACRO_VALUE(s) TO_STRING(s) +#define TO_STRING(s) #s + +// NOLINTNEXTLINE(runtime/string) +std::string crypto_version = MACRO_VALUE(NODE_CRYPTO_VERSION); #endif // HAVE_OPENSSL // true if process warnings should be suppressed @@ -3017,33 +3024,19 @@ static Local GetFeatures(Environment* env) { // TODO(bnoordhuis) ping libuv obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "ipv6"), True(env->isolate())); -#ifndef OPENSSL_NO_NEXTPROTONEG - Local tls_npn = True(env->isolate()); -#else - Local tls_npn = False(env->isolate()); -#endif - obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_npn"), tls_npn); + auto crypto = crypto::CryptoFactory::Get(crypto_version, env); -#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation - Local tls_alpn = True(env->isolate()); -#else - Local tls_alpn = False(env->isolate()); -#endif - obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_alpn"), tls_alpn); + obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_npn"), + Boolean::New(env->isolate(), crypto->HasNPN())); -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - Local tls_sni = True(env->isolate()); -#else - Local tls_sni = False(env->isolate()); -#endif - obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_sni"), tls_sni); + obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_alpn"), + Boolean::New(env->isolate(), crypto->HasALPN())); -#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) - Local tls_ocsp = True(env->isolate()); -#else - Local tls_ocsp = False(env->isolate()); -#endif // !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) - obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_ocsp"), tls_ocsp); + obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_sni"), + Boolean::New(env->isolate(), crypto->HasSNI())); + + obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_ocsp"), + Boolean::New(env->isolate(), crypto->HasOCSP())); obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls"), Boolean::New(env->isolate(), @@ -3225,25 +3218,12 @@ void SetupProcessObject(Environment* env, v8::kPromiseHandlerAddedAfterReject)); #if HAVE_OPENSSL - // Stupid code to slice out the version string. { // NOLINT(whitespace/braces) - size_t i, j, k; - int c; - for (i = j = 0, k = sizeof(OPENSSL_VERSION_TEXT) - 1; i < k; ++i) { - c = OPENSSL_VERSION_TEXT[i]; - if ('0' <= c && c <= '9') { - for (j = i + 1; j < k; ++j) { - c = OPENSSL_VERSION_TEXT[j]; - if (c == ' ') - break; - } - break; - } - } + auto crypto = crypto::CryptoFactory::Get(crypto_version, env); READONLY_PROPERTY( versions, - "openssl", - OneByteString(env->isolate(), &OPENSSL_VERSION_TEXT[i], j - i)); + crypto->Name().c_str(), + OneByteString(env->isolate(), crypto->Version().c_str())); } #endif @@ -4619,19 +4599,21 @@ int Start(int argc, char** argv) { Init(&argc, const_cast(argv), &exec_argc, &exec_argv); #if HAVE_OPENSSL + auto crypto = crypto::CryptoFactory::Get(crypto_version); { std::string extra_ca_certs; - if (SafeGetenv("NODE_EXTRA_CA_CERTS", &extra_ca_certs)) - crypto::UseExtraCaCerts(extra_ca_certs); + if (SafeGetenv("NODE_EXTRA_CA_CERTS", &extra_ca_certs)) { + crypto->UseExtraCaCerts(extra_ca_certs); + } } #ifdef NODE_FIPS_MODE // In the case of FIPS builds we should make sure // the random source is properly initialized first. - OPENSSL_init(); + crypto->init(); #endif // NODE_FIPS_MODE // V8 on Windows doesn't have a good source of entropy. Seed it from // OpenSSL's pool. - V8::SetEntropySource(crypto::EntropySource); + V8::SetEntropySource(crypto->GetEntropySource()); #endif // HAVE_OPENSSL v8_platform.Initialize(v8_thread_pool_size); diff --git a/src/node_crypto.h b/src/node_crypto.h index 33c9cf783ecedb..4e95aaeda9758b 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -1,755 +1,57 @@ -// 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. - #ifndef SRC_NODE_CRYPTO_H_ #define SRC_NODE_CRYPTO_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "node.h" -#include "node_crypto_clienthello.h" // ClientHelloParser -#include "node_crypto_clienthello-inl.h" - -#include "node_buffer.h" - -#include "env.h" -#include "async-wrap.h" -#include "async-wrap-inl.h" -#include "base-object.h" -#include "base-object-inl.h" - -#include "v8.h" - -#include -#include -#include -#ifndef OPENSSL_NO_ENGINE -# include -#endif // !OPENSSL_NO_ENGINE -#include -#include -#include -#include -#include -#include -#include -#include - -#define EVP_F_EVP_DECRYPTFINAL 101 - -#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) -# define NODE__HAVE_TLSEXT_STATUS_CB -#endif // !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb) +#include +#include "v8.h" // NOLINT(build/include_order) namespace node { namespace crypto { -// 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(); } -}; - -// Pop errors from OpenSSL's error stack that were added -// between when this was constructed and destructed. -struct MarkPopErrorOnReturn { - MarkPopErrorOnReturn() { ERR_set_mark(); } - ~MarkPopErrorOnReturn() { ERR_pop_to_mark(); } -}; - -enum CheckResult { - CHECK_CERT_REVOKED = 0, - CHECK_OK = 1 -}; - -extern int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx); - -extern void UseExtraCaCerts(const std::string& file); - -class SecureContext : public BaseObject { - public: - ~SecureContext() override { - FreeCTXMem(); - } - - static void Initialize(Environment* env, v8::Local target); - - SSL_CTX* ctx_; - X509* cert_; - X509* issuer_; - - static const int kMaxSessionSize = 10 * 1024; - - // See TicketKeyCallback - static const int kTicketKeyReturnIndex = 0; - static const int kTicketKeyHMACIndex = 1; - static const int kTicketKeyAESIndex = 2; - static const int kTicketKeyNameIndex = 3; - static const int kTicketKeyIVIndex = 4; - - protected: - static const int64_t kExternalSize = sizeof(SSL_CTX); - - static void New(const v8::FunctionCallbackInfo& args); - static void Init(const v8::FunctionCallbackInfo& args); - static void SetKey(const v8::FunctionCallbackInfo& args); - static void SetCert(const v8::FunctionCallbackInfo& args); - static void AddCACert(const v8::FunctionCallbackInfo& args); - static void AddCRL(const v8::FunctionCallbackInfo& args); - static void AddRootCerts(const v8::FunctionCallbackInfo& args); - static void SetCiphers(const v8::FunctionCallbackInfo& args); - static void SetECDHCurve(const v8::FunctionCallbackInfo& args); - static void SetDHParam(const v8::FunctionCallbackInfo& args); - static void SetOptions(const v8::FunctionCallbackInfo& args); - static void SetSessionIdContext( - const v8::FunctionCallbackInfo& args); - static void SetSessionTimeout( - const v8::FunctionCallbackInfo& args); - static void Close(const v8::FunctionCallbackInfo& args); - static void LoadPKCS12(const v8::FunctionCallbackInfo& args); - static void GetTicketKeys(const v8::FunctionCallbackInfo& args); - static void SetTicketKeys(const v8::FunctionCallbackInfo& args); - static void SetFreeListLength( - const v8::FunctionCallbackInfo& args); - static void EnableTicketKeyCallback( - const v8::FunctionCallbackInfo& args); - static void CtxGetter(v8::Local property, - const v8::PropertyCallbackInfo& info); - - template - static void GetCertificate(const v8::FunctionCallbackInfo& args); - - static int TicketKeyCallback(SSL* ssl, - unsigned char* name, - unsigned char* iv, - EVP_CIPHER_CTX* ectx, - HMAC_CTX* hctx, - int enc); - - SecureContext(Environment* env, v8::Local wrap) - : BaseObject(env, wrap), - ctx_(nullptr), - cert_(nullptr), - issuer_(nullptr) { - MakeWeak(this); - env->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize); - } - - void FreeCTXMem() { - if (!ctx_) { - return; - } - - env()->isolate()->AdjustAmountOfExternalAllocatedMemory(-kExternalSize); - SSL_CTX_free(ctx_); - if (cert_ != nullptr) - X509_free(cert_); - if (issuer_ != nullptr) - X509_free(issuer_); - ctx_ = nullptr; - cert_ = nullptr; - issuer_ = nullptr; - } -}; - -// SSLWrap implicitly depends on the inheriting class' handle having an -// internal pointer to the Base class. -template -class SSLWrap { - public: - enum Kind { - kClient, - kServer - }; - - SSLWrap(Environment* env, SecureContext* sc, Kind kind) - : env_(env), - kind_(kind), - next_sess_(nullptr), - session_callbacks_(false), - new_session_wait_(false), - cert_cb_(nullptr), - cert_cb_arg_(nullptr), - cert_cb_running_(false) { - ssl_ = SSL_new(sc->ctx_); - env_->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize); - CHECK_NE(ssl_, nullptr); - } - - virtual ~SSLWrap() { - DestroySSL(); - if (next_sess_ != nullptr) { - SSL_SESSION_free(next_sess_); - next_sess_ = nullptr; - } - -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - sni_context_.Reset(); -#endif - -#ifdef NODE__HAVE_TLSEXT_STATUS_CB - ocsp_response_.Reset(); -#endif // NODE__HAVE_TLSEXT_STATUS_CB - } - - inline SSL* ssl() const { return ssl_; } - inline void enable_session_callbacks() { session_callbacks_ = true; } - inline bool is_server() const { return kind_ == kServer; } - inline bool is_client() const { return kind_ == kClient; } - inline bool is_waiting_new_session() const { return new_session_wait_; } - inline bool is_waiting_cert_cb() const { return cert_cb_ != nullptr; } - - protected: - typedef void (*CertCb)(void* arg); - - // Size allocated by OpenSSL: one for SSL structure, one for SSL3_STATE and - // some for buffers. - // NOTE: Actually it is much more than this - static const int64_t kExternalSize = - sizeof(SSL) + sizeof(SSL3_STATE) + 42 * 1024; - - static void InitNPN(SecureContext* sc); - static void AddMethods(Environment* env, v8::Local t); - - static SSL_SESSION* GetSessionCallback(SSL* s, - unsigned char* key, - int len, - int* copy); - static int NewSessionCallback(SSL* s, SSL_SESSION* sess); - static void OnClientHello(void* arg, - const ClientHelloParser::ClientHello& hello); - - static void GetPeerCertificate( - const v8::FunctionCallbackInfo& args); - static void GetSession(const v8::FunctionCallbackInfo& args); - static void SetSession(const v8::FunctionCallbackInfo& args); - static void LoadSession(const v8::FunctionCallbackInfo& args); - static void IsSessionReused(const v8::FunctionCallbackInfo& args); - static void IsInitFinished(const v8::FunctionCallbackInfo& args); - static void VerifyError(const v8::FunctionCallbackInfo& args); - static void GetCurrentCipher(const v8::FunctionCallbackInfo& args); - static void EndParser(const v8::FunctionCallbackInfo& args); - static void CertCbDone(const v8::FunctionCallbackInfo& args); - static void Renegotiate(const v8::FunctionCallbackInfo& args); - static void Shutdown(const v8::FunctionCallbackInfo& args); - static void GetTLSTicket(const v8::FunctionCallbackInfo& args); - static void NewSessionDone(const v8::FunctionCallbackInfo& args); - static void SetOCSPResponse(const v8::FunctionCallbackInfo& args); - static void RequestOCSP(const v8::FunctionCallbackInfo& args); - static void GetEphemeralKeyInfo( - const v8::FunctionCallbackInfo& args); - static void GetProtocol(const v8::FunctionCallbackInfo& args); - -#ifdef SSL_set_max_send_fragment - static void SetMaxSendFragment( - const v8::FunctionCallbackInfo& args); -#endif // SSL_set_max_send_fragment - -#ifndef OPENSSL_NO_NEXTPROTONEG - static void GetNegotiatedProto( - const v8::FunctionCallbackInfo& args); - static void SetNPNProtocols(const v8::FunctionCallbackInfo& args); - static int AdvertiseNextProtoCallback(SSL* s, - const unsigned char** data, - unsigned int* len, - void* arg); - static int SelectNextProtoCallback(SSL* s, - unsigned char** out, - unsigned char* outlen, - const unsigned char* in, - unsigned int inlen, - void* arg); -#endif // OPENSSL_NO_NEXTPROTONEG - - static void GetALPNNegotiatedProto( - const v8::FunctionCallbackInfo& args); - static void SetALPNProtocols(const v8::FunctionCallbackInfo& args); - static int SelectALPNCallback(SSL* s, - const unsigned char** out, - unsigned char* outlen, - const unsigned char* in, - unsigned int inlen, - void* arg); - static int TLSExtStatusCallback(SSL* s, void* arg); - static int SSLCertCallback(SSL* s, void* arg); - static void SSLGetter(v8::Local property, - const v8::PropertyCallbackInfo& info); - - void DestroySSL(); - void WaitForCertCb(CertCb cb, void* arg); - void SetSNIContext(SecureContext* sc); - int SetCACerts(SecureContext* sc); - - inline Environment* ssl_env() const { - return env_; - } - - Environment* const env_; - Kind kind_; - SSL_SESSION* next_sess_; - SSL* ssl_; - bool session_callbacks_; - bool new_session_wait_; - - // SSL_set_cert_cb - CertCb cert_cb_; - void* cert_cb_arg_; - bool cert_cb_running_; - - ClientHelloParser hello_parser_; - -#ifdef NODE__HAVE_TLSEXT_STATUS_CB - v8::Persistent ocsp_response_; -#endif // NODE__HAVE_TLSEXT_STATUS_CB - -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - v8::Persistent sni_context_; -#endif - - friend class SecureContext; -}; - -// Connection inherits from AsyncWrap because SSLWrap makes calls to -// MakeCallback, but SSLWrap doesn't store the handle itself. Instead it -// assumes that any args.This() called will be the handle from Connection. -class Connection : public AsyncWrap, public SSLWrap { - public: - ~Connection() override { -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - sniObject_.Reset(); - servername_.Reset(); -#endif - } - - static void Initialize(Environment* env, v8::Local target); - void NewSessionDoneCb(); - -#ifndef OPENSSL_NO_NEXTPROTONEG - v8::Persistent npnProtos_; - v8::Persistent selectedNPNProto_; -#endif - -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - v8::Persistent sniObject_; - v8::Persistent servername_; -#endif - - size_t self_size() const override { return sizeof(*this); } - - protected: - static void New(const v8::FunctionCallbackInfo& args); - static void EncIn(const v8::FunctionCallbackInfo& args); - static void ClearOut(const v8::FunctionCallbackInfo& args); - static void ClearPending(const v8::FunctionCallbackInfo& args); - static void EncPending(const v8::FunctionCallbackInfo& args); - static void EncOut(const v8::FunctionCallbackInfo& args); - static void ClearIn(const v8::FunctionCallbackInfo& args); - static void Start(const v8::FunctionCallbackInfo& args); - static void Close(const v8::FunctionCallbackInfo& args); - -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - // SNI - static void GetServername(const v8::FunctionCallbackInfo& args); - static void SetSNICallback(const v8::FunctionCallbackInfo& args); - static int SelectSNIContextCallback_(SSL* s, int* ad, void* arg); -#endif - - static void OnClientHelloParseEnd(void* arg); - - int HandleBIOError(BIO* bio, const char* func, int rv); - - enum ZeroStatus { - kZeroIsNotAnError, - kZeroIsAnError - }; - - enum SyscallStatus { - kIgnoreSyscall, - kSyscallError - }; - - int HandleSSLError(const char* func, int rv, ZeroStatus zs, SyscallStatus ss); - - void ClearError(); - void SetShutdownFlags(); - - Connection(Environment* env, - v8::Local wrap, - SecureContext* sc, - SSLWrap::Kind kind) - : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_SSLCONNECTION), - SSLWrap(env, sc, kind), - bio_read_(nullptr), - bio_write_(nullptr), - hello_offset_(0) { - MakeWeak(this); - Wrap(wrap, this); - hello_parser_.Start(SSLWrap::OnClientHello, - OnClientHelloParseEnd, - this); - enable_session_callbacks(); - } - - private: - static void SSLInfoCallback(const SSL *ssl, int where, int ret); - - BIO *bio_read_; - BIO *bio_write_; - - uint8_t hello_data_[18432]; - size_t hello_offset_; - - friend class ClientHelloParser; - friend class SecureContext; -}; - -class CipherBase : public BaseObject { - public: - ~CipherBase() override { - if (!initialised_) - return; - delete[] auth_tag_; - EVP_CIPHER_CTX_cleanup(&ctx_); - } - - static void Initialize(Environment* env, v8::Local target); - - protected: - enum CipherKind { - kCipher, - kDecipher - }; - - void Init(const char* cipher_type, const char* key_buf, int key_buf_len); - void InitIv(const char* cipher_type, - const char* key, - int key_len, - const char* iv, - int iv_len); - bool Update(const char* data, int len, unsigned char** out, int* out_len); - bool Final(unsigned char** out, int *out_len); - bool SetAutoPadding(bool auto_padding); - - bool IsAuthenticatedMode() const; - bool GetAuthTag(char** out, unsigned int* out_len) const; - bool SetAuthTag(const char* data, unsigned int len); - bool SetAAD(const char* data, unsigned int len); - - static void New(const v8::FunctionCallbackInfo& args); - static void Init(const v8::FunctionCallbackInfo& args); - static void InitIv(const v8::FunctionCallbackInfo& args); - static void Update(const v8::FunctionCallbackInfo& args); - static void Final(const v8::FunctionCallbackInfo& args); - static void SetAutoPadding(const v8::FunctionCallbackInfo& args); - - static void GetAuthTag(const v8::FunctionCallbackInfo& args); - static void SetAuthTag(const v8::FunctionCallbackInfo& args); - static void SetAAD(const v8::FunctionCallbackInfo& args); - - CipherBase(Environment* env, - v8::Local wrap, - CipherKind kind) - : BaseObject(env, wrap), - cipher_(nullptr), - initialised_(false), - kind_(kind), - auth_tag_(nullptr), - auth_tag_len_(0) { - MakeWeak(this); - } - - private: - EVP_CIPHER_CTX ctx_; /* coverity[member_decl] */ - const EVP_CIPHER* cipher_; /* coverity[member_decl] */ - bool initialised_; - CipherKind kind_; - char* auth_tag_; - unsigned int auth_tag_len_; -}; - -class Hmac : public BaseObject { - public: - ~Hmac() override { - if (!initialised_) - return; - HMAC_CTX_cleanup(&ctx_); - } - - static void Initialize(Environment* env, v8::Local target); - - protected: - void HmacInit(const char* hash_type, const char* key, int key_len); - bool HmacUpdate(const char* data, int len); - bool HmacDigest(unsigned char** md_value, unsigned int* md_len); - - static void New(const v8::FunctionCallbackInfo& args); - static void HmacInit(const v8::FunctionCallbackInfo& args); - static void HmacUpdate(const v8::FunctionCallbackInfo& args); - static void HmacDigest(const v8::FunctionCallbackInfo& args); - - Hmac(Environment* env, v8::Local wrap) - : BaseObject(env, wrap), - initialised_(false) { - MakeWeak(this); - } - - private: - HMAC_CTX ctx_; /* coverity[member_decl] */ - bool initialised_; -}; - -class Hash : public BaseObject { - public: - ~Hash() override { - if (!initialised_) - return; - EVP_MD_CTX_cleanup(&mdctx_); - } - - static void Initialize(Environment* env, v8::Local target); - - bool HashInit(const char* hash_type); - bool HashUpdate(const char* data, int len); - - protected: - static void New(const v8::FunctionCallbackInfo& args); - static void HashUpdate(const v8::FunctionCallbackInfo& args); - static void HashDigest(const v8::FunctionCallbackInfo& args); - - Hash(Environment* env, v8::Local wrap) - : BaseObject(env, wrap), - initialised_(false) { - MakeWeak(this); - } - - private: - EVP_MD_CTX mdctx_; /* coverity[member_decl] */ - bool initialised_; - bool finalized_; -}; - -class SignBase : public BaseObject { - public: - typedef enum { - kSignOk, - kSignUnknownDigest, - kSignInit, - kSignNotInitialised, - kSignUpdate, - kSignPrivateKey, - kSignPublicKey - } Error; - - SignBase(Environment* env, v8::Local wrap) - : BaseObject(env, wrap), - initialised_(false) { - } - - ~SignBase() override { - if (!initialised_) - return; - EVP_MD_CTX_cleanup(&mdctx_); - } - - protected: - void CheckThrow(Error error); - - EVP_MD_CTX mdctx_; /* coverity[member_decl] */ - bool initialised_; -}; - -class Sign : public SignBase { - public: - static void Initialize(Environment* env, v8::Local target); - - Error SignInit(const char* sign_type); - Error SignUpdate(const char* data, int len); - Error SignFinal(const char* key_pem, - int key_pem_len, - const char* passphrase, - unsigned char** sig, - unsigned int *sig_len, - int padding, - int saltlen); - - protected: - static void New(const v8::FunctionCallbackInfo& args); - static void SignInit(const v8::FunctionCallbackInfo& args); - static void SignUpdate(const v8::FunctionCallbackInfo& args); - static void SignFinal(const v8::FunctionCallbackInfo& args); - - Sign(Environment* env, v8::Local wrap) : SignBase(env, wrap) { - MakeWeak(this); - } -}; - -class Verify : public SignBase { - public: - static void Initialize(Environment* env, v8::Local target); - - Error VerifyInit(const char* verify_type); - Error VerifyUpdate(const char* data, int len); - Error VerifyFinal(const char* key_pem, - int key_pem_len, - const char* sig, - int siglen, - int padding, - int saltlen, - bool* verify_result); - - protected: - static void New(const v8::FunctionCallbackInfo& args); - static void VerifyInit(const v8::FunctionCallbackInfo& args); - static void VerifyUpdate(const v8::FunctionCallbackInfo& args); - static void VerifyFinal(const v8::FunctionCallbackInfo& args); - - Verify(Environment* env, v8::Local wrap) : SignBase(env, wrap) { - MakeWeak(this); - } -}; - -class PublicKeyCipher { - public: - typedef int (*EVP_PKEY_cipher_init_t)(EVP_PKEY_CTX *ctx); - typedef int (*EVP_PKEY_cipher_t)(EVP_PKEY_CTX *ctx, - unsigned char *out, size_t *outlen, - const unsigned char *in, size_t inlen); - - enum Operation { - kPublic, - kPrivate - }; - - template - static bool Cipher(const char* key_pem, - int key_pem_len, - const char* passphrase, - int padding, - const unsigned char* data, - int len, - unsigned char** out, - size_t* out_len); - - template - static void Cipher(const v8::FunctionCallbackInfo& args); -}; - -class DiffieHellman : public BaseObject { - public: - ~DiffieHellman() override { - if (dh != nullptr) { - DH_free(dh); - } - } - - static void Initialize(Environment* env, v8::Local target); - - bool Init(int primeLength, int g); - bool Init(const char* p, int p_len, int g); - bool Init(const char* p, int p_len, const char* g, int g_len); - - protected: - static void DiffieHellmanGroup( - const v8::FunctionCallbackInfo& args); - static void New(const v8::FunctionCallbackInfo& args); - static void GenerateKeys(const v8::FunctionCallbackInfo& args); - static void GetPrime(const v8::FunctionCallbackInfo& args); - static void GetGenerator(const v8::FunctionCallbackInfo& args); - static void GetPublicKey(const v8::FunctionCallbackInfo& args); - static void GetPrivateKey(const v8::FunctionCallbackInfo& args); - static void ComputeSecret(const v8::FunctionCallbackInfo& args); - static void SetPublicKey(const v8::FunctionCallbackInfo& args); - static void SetPrivateKey(const v8::FunctionCallbackInfo& args); - static void VerifyErrorGetter( - v8::Local property, - const v8::PropertyCallbackInfo& args); - - DiffieHellman(Environment* env, v8::Local wrap) - : BaseObject(env, wrap), - initialised_(false), - verifyError_(0), - dh(nullptr) { - MakeWeak(this); - } - - private: - bool VerifyContext(); - - bool initialised_; - int verifyError_; - DH* dh; -}; - -class ECDH : public BaseObject { - public: - ~ECDH() override { - if (key_ != nullptr) - EC_KEY_free(key_); - key_ = nullptr; - group_ = nullptr; - } - - static void Initialize(Environment* env, v8::Local target); - - protected: - ECDH(Environment* env, v8::Local wrap, EC_KEY* key) - : BaseObject(env, wrap), - key_(key), - group_(EC_KEY_get0_group(key_)) { - MakeWeak(this); - ASSERT_NE(group_, nullptr); - } - - static void New(const v8::FunctionCallbackInfo& args); - static void GenerateKeys(const v8::FunctionCallbackInfo& args); - static void ComputeSecret(const v8::FunctionCallbackInfo& args); - static void GetPrivateKey(const v8::FunctionCallbackInfo& args); - static void SetPrivateKey(const v8::FunctionCallbackInfo& args); - static void GetPublicKey(const v8::FunctionCallbackInfo& args); - static void SetPublicKey(const v8::FunctionCallbackInfo& args); - - EC_POINT* BufferToPoint(char* data, size_t len); - - bool IsKeyPairValid(); - bool IsKeyValidForCurve(const BIGNUM* private_key); - - EC_KEY* key_; - const EC_GROUP* group_; -}; - -bool EntropySource(unsigned char* buffer, size_t length); -#ifndef OPENSSL_NO_ENGINE -void SetEngine(const v8::FunctionCallbackInfo& args); -#endif // !OPENSSL_NO_ENGINE -void InitCrypto(v8::Local target); - -} // namespace crypto -} // namespace node - -#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#endif // SRC_NODE_CRYPTO_H_ +class Crypto { + public: + virtual ~Crypto() {} + /** + * Allows a crypto implementation to do any additional initializations + * required. + */ + virtual void Init() {} + /** + */ + virtual void UseExtraCaCerts(const std::string& file) = 0; + /** + */ + virtual v8::EntropySource GetEntropySource() = 0; + /** + * The version of the underlying Crypto library + */ + virtual const std::string Version() = 0; + /** + * The name of this crypto implemenetion. + * This is currently used as the attribute name on the process object. + */ + virtual const std::string Name() = 0; + /** + * Returns true if TLS Name Server Indication (SNI) is supported. + */ + virtual bool HasSNI() = 0; + /** + * Returns true if the TLS Next Protocol Negotiation extension is supprted. + */ + virtual bool HasNPN() = 0; + /** + * Returns true if the TLS Application-Layer Protocol Negotiation extension + * is supprted. + */ + virtual bool HasALPN() = 0; + /** + * Returns true if Online Certificate Status Protocol is supported. + */ + virtual bool HasOCSP() = 0; + + protected: + static std::string id_; +}; + +} // namespace crypto +} // namespace node + +#endif // SRC_NODE_CRYPTO_H_ diff --git a/src/node_crypto_factory.cc b/src/node_crypto_factory.cc new file mode 100644 index 00000000000000..91ccfe58afed32 --- /dev/null +++ b/src/node_crypto_factory.cc @@ -0,0 +1,47 @@ +#include "node_crypto_factory.h" +#include "node_crypto.h" +#include "env.h" +#include "env-inl.h" +#include +#include + +namespace node { +namespace crypto { + +CryptoFactory::CallbackMap CryptoFactory::crypto_libs; + +typedef std::pair> CryptoPair; + +std::string CryptoFactory::Register(const std::string& version, + CreateCallback cb) { + if (!crypto_libs.count(version)) { + std::shared_ptr crypto{cb()}; + crypto_libs.insert(CryptoPair(version, crypto)); + } + return version; +} + +void CryptoFactory::Unregister(const std::string& version) { + crypto_libs.erase(version); +} + +std::shared_ptr CryptoFactory::Get(const std::string& version, + Environment* env) { + CallbackMap::iterator it = crypto_libs.find(version); + if (it != crypto_libs.end()) { + return it->second; + } + + if (env == nullptr) { + fprintf(stderr, + "node: could not find a crypto implementation for %s", + version.c_str()); + exit(9); + } + env->ThrowError( + ("Could not find a crypto implementation for " + version).c_str()); + return nullptr; +} + +} // namespace crypto +} // namespace node diff --git a/src/node_crypto_factory.h b/src/node_crypto_factory.h new file mode 100644 index 00000000000000..e416f6c58190f0 --- /dev/null +++ b/src/node_crypto_factory.h @@ -0,0 +1,39 @@ +#ifndef SRC_NODE_CRYPTO_FACTORY_H_ +#define SRC_NODE_CRYPTO_FACTORY_H_ +#include "node_crypto.h" +#include "env.h" +#include +#include + +namespace node { +namespace crypto { + +/** + * A Factory responsible for registering, unregistering and creating + * concrete Crypto instances. + */ +class CryptoFactory { + public: + typedef Crypto* (*CreateCallback)(); + /** + * Registers a Crypto implementation for the specified version. + */ + static std::string Register(const std::string& version, CreateCallback cb); + /** + * Unregisters a the Crypto implementation identified with version. + */ + static void Unregister(const std::string& version); + /** + * Returns a crypto instance for the specified version. + */ + static std::shared_ptr Get(const std::string& version, + Environment* env = nullptr); + private: + typedef std::map> CallbackMap; + static CallbackMap crypto_libs; +}; + +} // namespace crypto +} // namespace node + +#endif // SRC_NODE_CRYPTO_FACTORY_H_ diff --git a/test/cctest/test_crypto_factory.cc b/test/cctest/test_crypto_factory.cc new file mode 100644 index 00000000000000..450158d1c34087 --- /dev/null +++ b/test/cctest/test_crypto_factory.cc @@ -0,0 +1,12 @@ +#include "gtest/gtest.h" +#include "libplatform/libplatform.h" +#include "node_crypto_factory.h" + +using node::crypto::CryptoFactory; +using node::crypto::Crypto; + +TEST(CryptFactoryDeathTest, GetUnexistingCrypto) { + ASSERT_EXIT(CryptoFactory::Get("bugus"), + ::testing::ExitedWithCode(9), + "node: could not find a crypto implementation for bugus"); +} diff --git a/test/cctest/test_openssl_1_0_2e.cc b/test/cctest/test_openssl_1_0_2e.cc new file mode 100644 index 00000000000000..cd1409db419f5e --- /dev/null +++ b/test/cctest/test_openssl_1_0_2e.cc @@ -0,0 +1,34 @@ +#include "gtest/gtest.h" +#include "libplatform/libplatform.h" +#include "node_crypto_factory.h" +#include "crypto_impl/openssl/openssl.h" + +using node::crypto::CryptoFactory; +using node::crypto::Crypto; +using node::crypto::OpenSSL; + +const char version[] = "openssl_1_0_2e"; + +TEST(OpenSSL_1_0_2e, Version) { + EXPECT_EQ(CryptoFactory::Get(version)->Version(), "1.0.2e"); +} + +TEST(OpenSSL_1_0_2e, Name) { + EXPECT_EQ(CryptoFactory::Get(version)->Name(), "openssl"); +} + +TEST(OpenSSL_1_0_2e, HasSNI) { + EXPECT_TRUE(CryptoFactory::Get(version)->HasSNI()); +} + +TEST(OpenSSL_1_0_2e, HasNPN) { + EXPECT_TRUE(CryptoFactory::Get(version)->HasNPN()); +} + +TEST(OpenSSL_1_0_2e, HasALPN) { + EXPECT_FALSE(CryptoFactory::Get(version)->HasALPN()); +} + +TEST(OpenSSL_1_0_2e, HasOCSP) { + EXPECT_TRUE(CryptoFactory::Get(version)->HasOCSP()); +} diff --git a/test/cctest/test_openssl_1_1_0f.cc b/test/cctest/test_openssl_1_1_0f.cc new file mode 100644 index 00000000000000..b7e53578d7a748 --- /dev/null +++ b/test/cctest/test_openssl_1_1_0f.cc @@ -0,0 +1,34 @@ +#include "gtest/gtest.h" +#include "libplatform/libplatform.h" +#include "node_crypto_factory.h" +#include "crypto_impl/openssl/openssl.h" + +using node::crypto::CryptoFactory; +using node::crypto::Crypto; +using node::crypto::OpenSSL; + +const char version[] = "openssl_1_1_0f"; + +TEST(OpenSSL_1_1_0e, Version) { + EXPECT_EQ(CryptoFactory::Get(version)->Version(), "1.1.0f"); +} + +TEST(OpenSSL_1_1_0e, Name) { + EXPECT_EQ(CryptoFactory::Get(version)->Name(), "openssl"); +} + +TEST(OpenSSL_1_1_0e, HasSNI) { + EXPECT_TRUE(CryptoFactory::Get(version)->HasSNI()); +} + +TEST(OpenSSL_1_1_0e, HasNPN) { + EXPECT_TRUE(CryptoFactory::Get(version)->HasNPN()); +} + +TEST(OpenSSL_1_1_0e, HasALPN) { + EXPECT_FALSE(CryptoFactory::Get(version)->HasALPN()); +} + +TEST(OpenSSL_1_1_0e, HasOCSP) { + EXPECT_TRUE(CryptoFactory::Get(version)->HasOCSP()); +} diff --git a/test/common/index.js b/test/common/index.js index 75a1edd447a534..d8aae53b6e769c 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -223,6 +223,9 @@ Object.defineProperty(exports, 'localhostIPv4', { } }); +exports.isOpenSSL10 = !!process.versions.openssl.match(/^1\.0\./); +exports.needNoRandScreen = exports.isOpenSSL10 && exports.isWindows; + // opensslCli defined lazily to reduce overhead of spawnSync Object.defineProperty(exports, 'opensslCli', {get: function() { if (opensslCli !== null) return opensslCli; diff --git a/test/parallel/test-crypto-binary-default.js b/test/parallel/test-crypto-binary-default.js index e92f70035bde78..63caae4de093f8 100644 --- a/test/parallel/test-crypto-binary-default.js +++ b/test/parallel/test-crypto-binary-default.js @@ -654,11 +654,11 @@ assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true); // DSA signatures vary across runs so there is no static string to verify // against - const sign = crypto.createSign('DSS1'); + const sign = crypto.createSign('sha1'); sign.update(input); const signature = sign.sign(privateKey, 'hex'); - const verify = crypto.createVerify('DSS1'); + const verify = crypto.createVerify('sha1'); verify.update(input); assert.strictEqual(verify.verify(publicKey, signature, 'hex'), true); diff --git a/test/parallel/test-crypto-rsa-dsa.js b/test/parallel/test-crypto-rsa-dsa.js index daa818705b8488..eb42055d574463 100644 --- a/test/parallel/test-crypto-rsa-dsa.js +++ b/test/parallel/test-crypto-rsa-dsa.js @@ -210,11 +210,11 @@ assert.throws(() => { // DSA signatures vary across runs so there is no static string to verify // against - const sign = crypto.createSign('DSS1'); + const sign = crypto.createSign('sha1'); sign.update(input); const signature = sign.sign(dsaKeyPem, 'hex'); - const verify = crypto.createVerify('DSS1'); + const verify = crypto.createVerify('sha1'); verify.update(input); assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true); @@ -227,7 +227,7 @@ assert.throws(() => { const input = 'I AM THE WALRUS'; { - const sign = crypto.createSign('DSS1'); + const sign = crypto.createSign('sha1'); sign.update(input); assert.throws(() => { sign.sign({ key: dsaKeyPemEncrypted, passphrase: 'wrong' }, 'hex'); @@ -237,7 +237,7 @@ const input = 'I AM THE WALRUS'; { // DSA signatures vary across runs so there is no static string to verify // against - const sign = crypto.createSign('DSS1'); + const sign = crypto.createSign('sha1'); sign.update(input); let signature; @@ -246,7 +246,7 @@ const input = 'I AM THE WALRUS'; signature = sign.sign(signOptions, 'hex'); }); - const verify = crypto.createVerify('DSS1'); + const verify = crypto.createVerify('sha1'); verify.update(input); assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true); diff --git a/test/parallel/test-crypto.js b/test/parallel/test-crypto.js index 19fb6f2687e087..9cd5e5fa571ecf 100644 --- a/test/parallel/test-crypto.js +++ b/test/parallel/test-crypto.js @@ -27,6 +27,8 @@ if (!common.hasCrypto) { return; } +const isOpenSSL10 = common.isOpenSSL10; + const assert = require('assert'); const crypto = require('crypto'); const fs = require('fs'); @@ -105,7 +107,9 @@ validateList(tlsCiphers); // Assert that we have sha and sha1 but not SHA and SHA1. assert.notStrictEqual(0, crypto.getHashes().length); assert(crypto.getHashes().includes('sha1')); -assert(crypto.getHashes().includes('sha')); +if (isOpenSSL10) + assert(crypto.getHashes().includes('sha')); + assert(!crypto.getHashes().includes('SHA1')); assert(!crypto.getHashes().includes('SHA')); assert(crypto.getHashes().includes('RSA-SHA1')); @@ -167,6 +171,10 @@ assert.throws(function() { crypto.createSign('RSA-SHA256').update('test').sign(priv); }, /digest too big for rsa key$/); +const err_msg = isOpenSSL10 ? + /asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag/ : + /asn1 encoding routines:asn1_check_tlen:wrong tag/; + assert.throws(function() { // The correct header inside `test_bad_rsa_privkey.pem` should have been // -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- @@ -181,7 +189,7 @@ assert.throws(function() { `${common.fixturesDir}/test_bad_rsa_privkey.pem`, 'ascii'); // this would inject errors onto OpenSSL's error stack crypto.createSign('sha1').sign(sha1_privateKey); -}, /asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag/); +}, err_msg); // Make sure memory isn't released before being returned console.log(crypto.randomBytes(16)); diff --git a/test/parallel/test-https-agent-session-eviction.js b/test/parallel/test-https-agent-session-eviction.js index acead806ed066e..cfbfd0c606d642 100644 --- a/test/parallel/test-https-agent-session-eviction.js +++ b/test/parallel/test-https-agent-session-eviction.js @@ -47,7 +47,7 @@ function faultyServer(port) { https.createServer(options, function(req, res) { res.end('hello faulty'); }).listen(port, function() { - second(this); + common.isOpenSSL10 ? second(this) : forth(this); }); } @@ -84,3 +84,18 @@ function third(server) { req.on('error', common.mustNotCall()); req.end(); } + +// Attempt to request using cached session but resumption gets faild +// and it leads full handshake +function forth(server) { + const req = https.request({ + port: server.address().port, + rejectUnauthorized: false + }, function(res) { + res.resume(); + assert(!req.socket.isSessionReused()); + server.close(); + }); + req.on('error', common.mustNotCall()); + req.end(); +} diff --git a/test/parallel/test-https-agent-session-reuse.js b/test/parallel/test-https-agent-session-reuse.js index a9977d8ce9a915..43699a6f63e968 100644 --- a/test/parallel/test-https-agent-session-reuse.js +++ b/test/parallel/test-https-agent-session-reuse.js @@ -26,9 +26,11 @@ const agent = new https.Agent({ maxCachedSessions: 1 }); +const ticketSize = common.isOpenSSL10 ? 48 : 80; + const server = https.createServer(options, function(req, res) { if (req.url === '/drop-key') - server.setTicketKeys(crypto.randomBytes(48)); + server.setTicketKeys(crypto.randomBytes(ticketSize)); serverRequests++; res.end('ok'); diff --git a/test/parallel/test-https-connect-address-family.js b/test/parallel/test-https-connect-address-family.js index e7f41ce861cb27..782157f4a7789f 100644 --- a/test/parallel/test-https-connect-address-family.js +++ b/test/parallel/test-https-connect-address-family.js @@ -13,10 +13,16 @@ if (!common.hasIPv6) { const assert = require('assert'); const https = require('https'); const dns = require('dns'); +const fs = require('fs'); + +const opts = { + key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'), + cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem'), + ca: fs.readFileSync(common.fixturesDir + '/keys/ca1-cert.pem') +}; function runTest() { - const ciphers = 'AECDH-NULL-SHA'; - https.createServer({ ciphers }, common.mustCall(function(req, res) { + https.createServer(opts, common.mustCall(function(req, res) { this.close(); res.end(); })).listen(0, '::1', common.mustCall(function() { @@ -24,7 +30,6 @@ function runTest() { host: 'localhost', port: this.address().port, family: 6, - ciphers: ciphers, rejectUnauthorized: false, }; // Will fail with ECONNREFUSED if the address family is not honored. diff --git a/test/parallel/test-https-foafssl.js b/test/parallel/test-https-foafssl.js index 9900cf7a643c10..7095ee2a6b1b6b 100644 --- a/test/parallel/test-https-foafssl.js +++ b/test/parallel/test-https-foafssl.js @@ -80,7 +80,7 @@ server.listen(0, function() { '-key', join(common.fixturesDir, 'foafssl.key')]; // for the performance and stability issue in s_client on Windows - if (common.isWindows) + if (common.needNoRandScreen) args.push('-no_rand_screen'); const client = spawn(common.opensslCli, args); diff --git a/test/parallel/test-tls-alert.js b/test/parallel/test-tls-alert.js index d12d45f529cfd4..316ab559292734 100644 --- a/test/parallel/test-tls-alert.js +++ b/test/parallel/test-tls-alert.js @@ -56,7 +56,7 @@ const server = tls.Server({ '-connect', `127.0.0.1:${this.address().port}`]; // for the performance and stability issue in s_client on Windows - if (common.isWindows) + if (common.needNoRandScreen) args.push('-no_rand_screen'); const client = spawn(common.opensslCli, args); diff --git a/test/parallel/test-tls-alpn-server-client.js b/test/parallel/test-tls-alpn-server-client.js index a397550d96863a..47c236595e8699 100644 --- a/test/parallel/test-tls-alpn-server-client.js +++ b/test/parallel/test-tls-alpn-server-client.js @@ -12,6 +12,8 @@ if (!process.features.tls_alpn || !process.features.tls_npn) { return; } +const isOpenSSL10 = common.isOpenSSL10; + const assert = require('assert'); const fs = require('fs'); const tls = require('tls'); @@ -26,17 +28,22 @@ function loadPEM(n) { const serverIP = common.localhostIPv4; -function checkResults(result, expected) { - assert.strictEqual(result.server.ALPN, expected.server.ALPN); - assert.strictEqual(result.server.NPN, expected.server.NPN); - assert.strictEqual(result.client.ALPN, expected.client.ALPN); - assert.strictEqual(result.client.NPN, expected.client.NPN); +function checkResults(result, expected, error) { + if (!error) { + assert.strictEqual(result.server.ALPN, expected.server.ALPN); + assert.strictEqual(result.server.NPN, expected.server.NPN); + assert.strictEqual(result.client.ALPN, expected.client.ALPN); + assert.strictEqual(result.client.NPN, expected.client.NPN); + } else { + assert(error.message.match(/socket hang up/)); + } } function runTest(clientsOptions, serverOptions, cb) { serverOptions.key = loadPEM('agent2-key'); serverOptions.cert = loadPEM('agent2-cert'); const results = []; + const errors = []; let index = 0; const server = tls.createServer(serverOptions, function(c) { results[index].server = {ALPN: c.alpnProtocol, NPN: c.npnProtocol}; @@ -46,27 +53,33 @@ function runTest(clientsOptions, serverOptions, cb) { connectClient(clientsOptions); }); + function runNext(options) { + if (options.length) { + index++; + connectClient(options); + } else { + server.close(); + cb(results, errors); + } + } + function connectClient(options) { const opt = options.shift(); opt.port = server.address().port; opt.host = serverIP; opt.rejectUnauthorized = false; - results[index] = {}; const client = tls.connect(opt, function() { results[index].client = {ALPN: client.alpnProtocol, NPN: client.npnProtocol}; client.destroy(); - if (options.length) { - index++; - connectClient(options); - } else { - server.close(); - cb(results); - } + runNext(options); + }); + client.on('error', function(e) { + errors[index] = e; + runNext(options); }); } - } // Server: ALPN/NPN, Client: ALPN/NPN @@ -87,19 +100,23 @@ function Test1() { NPNProtocols: ['first-priority-unsupported', 'x', 'y'] }]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // 'a' is selected by ALPN checkResults(results[0], {server: {ALPN: 'a', NPN: false}, - client: {ALPN: 'a', NPN: undefined}}); + client: {ALPN: 'a', NPN: undefined}}, errors[0]); // 'b' is selected by ALPN checkResults(results[1], {server: {ALPN: 'b', NPN: false}, - client: {ALPN: 'b', NPN: undefined}}); + client: {ALPN: 'b', NPN: undefined}}, errors[1]); // nothing is selected by ALPN - checkResults(results[2], - {server: {ALPN: false, NPN: 'first-priority-unsupported'}, - client: {ALPN: false, NPN: false}}); + const expected_openssl10 = {server: {ALPN: false, + NPN: 'first-priority-unsupported'}, + client: {ALPN: false, NPN: false}}; + const expected_openssl11 = {}; + const expected = isOpenSSL10 ? expected_openssl10 : expected_openssl11; + checkResults(results[2], expected, errors[2]); + // execute next test Test2(); }); @@ -120,19 +137,23 @@ function Test2() { ALPNProtocols: ['first-priority-unsupported', 'x', 'y'] }]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // 'a' is selected by ALPN checkResults(results[0], {server: {ALPN: 'a', NPN: false}, - client: {ALPN: 'a', NPN: undefined}}); + client: {ALPN: 'a', NPN: undefined}}, errors[0]); // 'b' is selected by ALPN checkResults(results[1], {server: {ALPN: 'b', NPN: false}, - client: {ALPN: 'b', NPN: undefined}}); + client: {ALPN: 'b', NPN: undefined}}, errors[1]); // nothing is selected by ALPN - checkResults(results[2], - {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + const expected_openssl10 = {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}; + const expected_openssl11 = {}; + const expected = isOpenSSL10 ? expected_openssl10 : expected_openssl11; + checkResults(results[2], expected, errors[2]); + + // execute next test Test3(); }); @@ -153,19 +174,19 @@ function Test3() { NPPNProtocols: ['first-priority-unsupported', 'x', 'y'] }]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // 'a' is selected by NPN checkResults(results[0], {server: {ALPN: false, NPN: 'a'}, - client: {ALPN: false, NPN: 'a'}}); + client: {ALPN: false, NPN: 'a'}}, errors[0]); // nothing is selected by ALPN checkResults(results[1], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[1]); // nothing is selected by ALPN checkResults(results[2], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[2]); // execute next test Test4(); }); @@ -215,17 +236,21 @@ function Test5() { NPNProtocols: ['first-priority-unsupported', 'x', 'y'] }]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // 'a' is selected by ALPN checkResults(results[0], {server: {ALPN: 'a', NPN: false}, - client: {ALPN: 'a', NPN: undefined}}); + client: {ALPN: 'a', NPN: undefined}}, errors[0]); // 'b' is selected by ALPN checkResults(results[1], {server: {ALPN: 'b', NPN: false}, - client: {ALPN: 'b', NPN: undefined}}); + client: {ALPN: 'b', NPN: undefined}}, errors[1]); // nothing is selected by ALPN - checkResults(results[2], {server: {ALPN: false, - NPN: 'first-priority-unsupported'}, - client: {ALPN: false, NPN: false}}); + const expected_openssl10 = {server: {ALPN: false, + NPN: 'first-priority-unsupported'}, + client: {ALPN: false, NPN: false}}; + const expected_openssl11 = {}; + const expected = isOpenSSL10 ? expected_openssl10 : expected_openssl11; + + checkResults(results[2], expected, errors[2]); // execute next test Test6(); }); @@ -245,16 +270,19 @@ function Test6() { ALPNProtocols: ['first-priority-unsupported', 'x', 'y'] }]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // 'a' is selected by ALPN checkResults(results[0], {server: {ALPN: 'a', NPN: false}, - client: {ALPN: 'a', NPN: undefined}}); + client: {ALPN: 'a', NPN: undefined}}, errors[0]); // 'b' is selected by ALPN checkResults(results[1], {server: {ALPN: 'b', NPN: false}, - client: {ALPN: 'b', NPN: undefined}}); + client: {ALPN: 'b', NPN: undefined}}, errors[1]); // nothing is selected by ALPN - checkResults(results[2], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + const expected_openssl10 = {server: {ALPN: false, NPN: 'http/1.1'}, + client: {ALPN: false, NPN: false}}; + const expected_openssl11 = {}; + const expected = isOpenSSL10 ? expected_openssl10 : expected_openssl11; + checkResults(results[2], expected, errors[2]); // execute next test Test7(); }); @@ -274,17 +302,20 @@ function Test7() { NPNProtocols: ['first-priority-unsupported', 'x', 'y'] }]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // nothing is selected by ALPN checkResults(results[0], {server: {ALPN: false, NPN: 'a'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, + errors[0]); // nothing is selected by ALPN checkResults(results[1], {server: {ALPN: false, NPN: 'c'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, + errors[1]); // nothing is selected by ALPN checkResults(results[2], {server: {ALPN: false, NPN: 'first-priority-unsupported'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, + errors[2]); // execute next test Test8(); }); @@ -298,17 +329,20 @@ function Test8() { const clientsOptions = [{}, {}, {}]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // nothing is selected by ALPN checkResults(results[0], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, + errors[0]); // nothing is selected by ALPN checkResults(results[1], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, + errors[1]); // nothing is selected by ALPN checkResults(results[2], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, + errors[2]); // execute next test Test9(); }); @@ -331,17 +365,18 @@ function Test9() { NPNProtocols: ['first-priority-unsupported', 'x', 'y'] }]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // 'a' is selected by NPN checkResults(results[0], {server: {ALPN: false, NPN: 'a'}, - client: {ALPN: false, NPN: 'a'}}); + client: {ALPN: false, NPN: 'a'}}, errors[0]); // 'b' is selected by NPN checkResults(results[1], {server: {ALPN: false, NPN: 'b'}, - client: {ALPN: false, NPN: 'b'}}); + client: {ALPN: false, NPN: 'b'}}, errors[1]); // nothing is selected checkResults(results[2], {server: {ALPN: false, NPN: 'first-priority-unsupported'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, + errors[2]); // execute next test Test10(); }); @@ -361,16 +396,16 @@ function Test10() { ALPNProtocols: ['first-priority-unsupported', 'x', 'y'] }]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // nothing is selected checkResults(results[0], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[0]); // nothing is selected checkResults(results[1], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[1]); // nothing is selected checkResults(results[2], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[2]); // execute next test Test11(); }); @@ -390,17 +425,17 @@ function Test11() { NPNProtocols: ['first-priority-unsupported', 'x', 'y'] }]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // 'a' is selected by NPN checkResults(results[0], {server: {ALPN: false, NPN: 'a'}, - client: {ALPN: false, NPN: 'a'}}); + client: {ALPN: false, NPN: 'a'}}, errors[0]); // 'b' is selected by NPN checkResults(results[1], {server: {ALPN: false, NPN: 'b'}, - client: {ALPN: false, NPN: 'b'}}); + client: {ALPN: false, NPN: 'b'}}, errors[1]); // nothing is selected checkResults(results[2], {server: {ALPN: false, NPN: 'first-priority-unsupported'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[2]); // execute next test Test12(); }); @@ -414,17 +449,17 @@ function Test12() { const clientsOptions = [{}, {}, {}]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // nothing is selected checkResults(results[0], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[0]); // nothing is selected checkResults(results[1], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[1]); // nothing is selected checkResults(results[2], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[2]); // execute next test Test13(); }); @@ -445,17 +480,17 @@ function Test13() { NPNProtocols: ['first-priority-unsupported', 'x', 'y'] }]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // nothing is selected checkResults(results[0], {server: {ALPN: false, NPN: 'a'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[0]); // nothing is selected checkResults(results[1], {server: {ALPN: false, NPN: 'c'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[1]); // nothing is selected checkResults(results[2], {server: {ALPN: false, NPN: 'first-priority-unsupported'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[2]); // execute next test Test14(); }); @@ -473,17 +508,17 @@ function Test14() { ALPNProtocols: ['first-priority-unsupported', 'x', 'y'] }]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // nothing is selected checkResults(results[0], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[0]); // nothing is selected checkResults(results[1], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[1]); // nothing is selected checkResults(results[2], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[2]); // execute next test Test15(); }); @@ -501,17 +536,17 @@ function Test15() { NPNProtocols: ['first-priority-unsupported', 'x', 'y'] }]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // nothing is selected checkResults(results[0], {server: {ALPN: false, NPN: 'a'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[0]); // nothing is selected checkResults(results[1], {server: {ALPN: false, NPN: 'c'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[1]); // nothing is selected checkResults(results[2], {server: {ALPN: false, NPN: 'first-priority-unsupported'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[2]); // execute next test Test16(); }); @@ -523,17 +558,17 @@ function Test16() { const clientsOptions = [{}, {}, {}]; - runTest(clientsOptions, serverOptions, function(results) { + runTest(clientsOptions, serverOptions, function(results, errors) { // nothing is selected checkResults(results[0], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[0]); // nothing is selected checkResults(results[1], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[1]); // nothing is selected checkResults(results[2], {server: {ALPN: false, NPN: 'http/1.1'}, - client: {ALPN: false, NPN: false}}); + client: {ALPN: false, NPN: false}}, errors[2]); }); } diff --git a/test/parallel/test-tls-basic-validations.js b/test/parallel/test-tls-basic-validations.js index 0b5d87ddc88ff2..1d8fae609f2fb4 100644 --- a/test/parallel/test-tls-basic-validations.js +++ b/test/parallel/test-tls-basic-validations.js @@ -34,7 +34,7 @@ assert.throws(() => tls.createServer({ticketKeys: 'abcd'}), /TypeError: Ticket keys must be a buffer/); assert.throws(() => tls.createServer({ticketKeys: new Buffer(0)}), - /TypeError: Ticket keys length must be 48 bytes/); + /TypeError: Ticket keys length incorrect/); assert.throws(() => tls.createSecurePair({}), /Error: First argument must be a tls module SecureContext/); diff --git a/test/parallel/test-tls-cert-regression.js b/test/parallel/test-tls-cert-regression.js index 0a128275c3874d..2f97fa4ac38c16 100644 --- a/test/parallel/test-tls-cert-regression.js +++ b/test/parallel/test-tls-cert-regression.js @@ -28,29 +28,39 @@ if (!common.hasCrypto) { } const tls = require('tls'); - -const cert = -`-----BEGIN CERTIFICATE----- -MIIBfjCCASgCCQDmmNjAojbDQjANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB -VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 -cyBQdHkgTHRkMCAXDTE0MDExNjE3NTMxM1oYDzIyODcxMDMxMTc1MzEzWjBFMQsw -CQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJu -ZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKwlfMX -6HGZIt1xm7fna72eWcOYfUfSxSugghvqYgJt2Oi3lH+wsU1O9FzRIVmpeIjDXhbp -Mjsa1HtzSiccPXsCAwEAATANBgkqhkiG9w0BAQUFAANBAHOoKy0NkyfiYH7Ne5ka -uvCyndyeB4d24FlfqEUlkfaWCZlNKRaV9YhLDiEg3BcIreFo4brtKQfZzTRs0GVm -KHg= +// taken from test/fixtures/keys/agent2-cert.pem +const cert = `-----BEGIN CERTIFICATE----- +MIICcTCCAdoCCQDTgzSLdDTF0TANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJV +UzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQKEwZKb3llbnQxEDAO +BgNVBAsTB05vZGUuanMxDzANBgNVBAMTBmFnZW50MjEgMB4GCSqGSIb3DQEJARYR +cnlAdGlueWNsb3Vkcy5vcmcwHhcNMTMwODAxMTExOTAwWhcNNDAxMjE2MTExOTAw +WjB9MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYD +VQQKEwZKb3llbnQxEDAOBgNVBAsTB05vZGUuanMxDzANBgNVBAMTBmFnZW50MjEg +MB4GCSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBAKGYRnu2BdY2R8flqKPLICWO/7NoRVGH4KZBY1uBF/VYXyA2 +VT5O7461mt6oA372BItGyNxdbMEvQBRcLiXTueKF5D+KYu30bWem6A/AxxYvnqU4 +tP+uhsXNuGNQTp8i0vBDM/nUx7QGeP1Kda6C936PCNt7wbGPKPNyACNMbnptAgMB +AAEwDQYJKoZIhvcNAQEFBQADgYEATzjDAPocPA2Jm8wrLBW+fOC478wMo9gT3Y3N +ZU6fnF2dEPFLNETCMtDxnKhi4hnBpaiZ0fu0oaR1cSDRIVtlyW4azNjny4495C0F +JLuP5P5pz+rJe+ImKw+mO1ARA9fUAL3VN6/kVXY/EspwWJcLbJ5jdsDmkRbV52hX +Th4jkAI= -----END CERTIFICATE-----`; -const key = -`-----BEGIN RSA PRIVATE KEY----- -MIIBPQIBAAJBAPKwlfMX6HGZIt1xm7fna72eWcOYfUfSxSugghvqYgJt2Oi3lH+w -sU1O9FzRIVmpeIjDXhbpMjsa1HtzSiccPXsCAwEAAQJBAM4uU9aJE0OfdE1p/X+K -LrCT3XMdFCJ24GgmHyOURtwDy18upQJecDVdcZp16fjtOPmaW95GoYRyifB3R4I5 -RxECIQD7jRM9slCSVV8xp9kOJQNpHjhRQYVGBn+pyllS2sb+RQIhAPb7Y+BIccri -NWnuhwCW8hA7Fkj/kaBdAwyW7L3Tvui/AiEAiqLCovMecre4Yi6GcsQ1b/6mvSmm -IOS+AT6zIfXPTB0CIQCJKGR3ymN/Qw5crL1GQ41cHCQtF9ickOq/lBUW+j976wIh -AOaJnkQrmurlRdePX6LvN/LgGAQoxwovfjcOYNnZsIVY +// taken from test/fixtures/keys/agent2-key.pem +const key = `-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQChmEZ7tgXWNkfH5aijyyAljv+zaEVRh+CmQWNbgRf1WF8gNlU+ +Tu+OtZreqAN+9gSLRsjcXWzBL0AUXC4l07niheQ/imLt9G1npugPwMcWL56lOLT/ +robFzbhjUE6fItLwQzP51Me0Bnj9SnWugvd+jwjbe8GxjyjzcgAjTG56bQIDAQAB +AoGAd19C6g5731N30T5hRqY+GCC72a90TZc/p/Fz0Vva8/4VP3mDnSS4qMaVIlgh +RP++OZjPtqI5PbiG8MNrv7vZe0UXlV7oZE0IA+jomUXsplbwMFf6pkrqdyHi+cbm +rBudhmKeLUgNA6peMGVA83C5g2SMqU5kB+tWzZT7Rs9rsyECQQDWpXxZgULqbFZv +wjpIDGWjOpQZrv123bJ9TQ+VoskCu4vlyDJqDJPwnscl8NnzpFJriDARn0WrB2sd +8GCX1yEpAkEAwLo/MYG5elkNRsE5/vINSIo04Gu6tP/Sd7EBtHYAPHUPjs/MhhVX +tMIGtACheHMwjGRPyr8pboEp2LEap4GjpQJBALNsy+CJ0+TfwPVU96EIc+GZcvlx +NMErGyvwwclEtSDKo2vmCHZrozLtlu1ZQueOgbMPuZbRe8w2vEzfhe8HTtkCQAYy +NrPlwsvPLyEWN0IeEBVD9D0+2WrWSrL0auSdYpaPAOgLgDzTVNWH42VIG+jeczIg +S3xuNuvJlUnVL9Ew1s0CQQCly+gduXtvOYip1/Stm/65kT7d8ICQgjh0XSPw/kUC +llVMQY3z1iFCaj/z0Csr0t0kJ534bH7GP3LOoNruV0p9 -----END RSA PRIVATE KEY-----`; function test(cert, key, cb) { diff --git a/test/parallel/test-tls-close-notify.js b/test/parallel/test-tls-close-notify.js index 625909c9c5edab..98637b7ada9dff 100644 --- a/test/parallel/test-tls-close-notify.js +++ b/test/parallel/test-tls-close-notify.js @@ -30,13 +30,24 @@ const tls = require('tls'); const fs = require('fs'); +// In OpenSSL 1.0.x SSL_shutdown would return 1 even if it was in the +// initialize stage without any error being set. In 1.1.x this will +// instead be -1 and en error will be set (SSL_R_SHUTDOWN_WHILE_IN_INIT) +// which this test will have to handle. +const shutdownRet = common.isOpenSSL10 ? 1 : -1; + const server = tls.createServer({ key: fs.readFileSync(`${common.fixturesDir}/keys/agent1-key.pem`), cert: fs.readFileSync(`${common.fixturesDir}/keys/agent1-cert.pem`) }, function(c) { - // Send close-notify without shutting down TCP socket - if (c._handle.shutdownSSL() !== 1) + if (c._handle.shutdownSSL() !== shutdownRet) { c._handle.shutdownSSL(); + } + if (!common.isOpenSSL10) { + c.on('error', function(err) { + c.destroy(); + }); + } }).listen(0, common.mustCall(function() { const c = tls.connect(this.address().port, { rejectUnauthorized: false diff --git a/test/parallel/test-tls-connect-address-family.js b/test/parallel/test-tls-connect-address-family.js index f22831f395a8dd..ac9a610700057f 100644 --- a/test/parallel/test-tls-connect-address-family.js +++ b/test/parallel/test-tls-connect-address-family.js @@ -13,17 +13,22 @@ if (!common.hasIPv6) { const assert = require('assert'); const tls = require('tls'); const dns = require('dns'); +const fs = require('fs'); + +const opts = { + key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'), + cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem'), + ca: fs.readFileSync(common.fixturesDir + '/keys/ca1-cert.pem') +}; function runTest() { - const ciphers = 'AECDH-NULL-SHA'; - tls.createServer({ ciphers }, common.mustCall(function() { + tls.createServer(opts, common.mustCall(function() { this.close(); })).listen(0, '::1', common.mustCall(function() { const options = { host: 'localhost', port: this.address().port, family: 6, - ciphers: ciphers, rejectUnauthorized: false, }; // Will fail with ECONNREFUSED if the address family is not honored. diff --git a/test/parallel/test-tls-dhe.js b/test/parallel/test-tls-dhe.js index 2f86e82be7fc96..ad12688bb0bac7 100644 --- a/test/parallel/test-tls-dhe.js +++ b/test/parallel/test-tls-dhe.js @@ -76,7 +76,7 @@ function test(keylen, expectedCipher, cb) { '-cipher', ciphers]; // for the performance and stability issue in s_client on Windows - if (common.isWindows) + if (common.needNoRandScreen) args.push('-no_rand_screen'); const client = spawn(common.opensslCli, args); diff --git a/test/parallel/test-tls-ecdh-disable.js b/test/parallel/test-tls-ecdh-disable.js index 732ebe4d1bdafc..1ed6ab83a91bd6 100644 --- a/test/parallel/test-tls-ecdh-disable.js +++ b/test/parallel/test-tls-ecdh-disable.js @@ -33,6 +33,11 @@ if (!common.opensslCli) { return; } +if (!common.isOpenSSL10) { + common.skip('OpenSSL version is > 1.0.x'); + return; +} + const tls = require('tls'); const exec = require('child_process').exec; @@ -41,7 +46,7 @@ const fs = require('fs'); const options = { key: fs.readFileSync(`${common.fixturesDir}/keys/agent2-key.pem`), cert: fs.readFileSync(`${common.fixturesDir}/keys/agent2-cert.pem`), - ciphers: 'ECDHE-RSA-RC4-SHA', + ciphers: 'ECDHE-RSA-AES128-GCM-SHA256', ecdhCurve: false }; @@ -52,7 +57,7 @@ server.listen(0, '127.0.0.1', common.mustCall(function() { options.ciphers} -connect 127.0.0.1:${this.address().port}`; // for the performance and stability issue in s_client on Windows - if (common.isWindows) + if (common.needNoRandScreen) cmd += ' -no_rand_screen'; exec(cmd, common.mustCall(function(err, stdout, stderr) { diff --git a/test/parallel/test-tls-ecdh.js b/test/parallel/test-tls-ecdh.js index 32e77456bdc045..0db9580d496911 100644 --- a/test/parallel/test-tls-ecdh.js +++ b/test/parallel/test-tls-ecdh.js @@ -56,7 +56,7 @@ server.listen(0, '127.0.0.1', common.mustCall(function() { options.ciphers} -connect 127.0.0.1:${this.address().port}`; // for the performance and stability issue in s_client on Windows - if (common.isWindows) + if (common.needNoRandScreen) cmd += ' -no_rand_screen'; exec(cmd, common.mustCall(function(err, stdout, stderr) { diff --git a/test/parallel/test-tls-econnreset.js b/test/parallel/test-tls-econnreset.js index 798c10ca4c141c..494a85c65e0e60 100644 --- a/test/parallel/test-tls-econnreset.js +++ b/test/parallel/test-tls-econnreset.js @@ -23,10 +23,22 @@ const common = require('../common'); const assert = require('assert'); +// Check if a tls server can handle clientError properly. +// The ECONRESET error is triggered from a tls client due to TLS +// handshake error with using 384 bits RSA certs and RSA key exchange +// because its RSA size is too small to carry premaster secret. + if (!common.hasCrypto) { common.skip('missing crypto'); return; } + +// 384 bits RSA key cannot be accepted in openssl-1.1.x +if (!common.isOpenSSL10) { + common.skip('due to openssl-' + process.versions.openssl); + return; +} + const tls = require('tls'); const cacert = diff --git a/test/parallel/test-tls-junk-server.js b/test/parallel/test-tls-junk-server.js index 9b5ab6fdcc649d..a884cb613554ea 100644 --- a/test/parallel/test-tls-junk-server.js +++ b/test/parallel/test-tls-junk-server.js @@ -18,12 +18,15 @@ const server = net.createServer(function(s) { }); }); +const expected_err = common.isOpenSSL10 ? /unknown protocol/ : + /wrong version number/; + server.listen(0, function() { const req = https.request({ port: this.address().port }); req.end(); req.once('error', common.mustCall(function(err) { - assert(/unknown protocol/.test(err.message)); + assert(expected_err.test(err.message)); server.close(); })); }); diff --git a/test/parallel/test-tls-multi-key.js b/test/parallel/test-tls-multi-key.js index 6158f7d4057657..42acf26700727d 100644 --- a/test/parallel/test-tls-multi-key.js +++ b/test/parallel/test-tls-multi-key.js @@ -63,12 +63,14 @@ const server = tls.createServer(options, function(conn) { }); }); +const version = common.isOpenSSL10 ? 'TLSv1/SSLv3' : 'TLSv1.2'; + process.on('exit', function() { assert.deepStrictEqual(ciphers, [{ name: 'ECDHE-ECDSA-AES256-GCM-SHA384', - version: 'TLSv1/SSLv3' + version: version }, { name: 'ECDHE-RSA-AES256-GCM-SHA384', - version: 'TLSv1/SSLv3' + version: version }]); }); diff --git a/test/parallel/test-tls-no-sslv3.js b/test/parallel/test-tls-no-sslv3.js index 2c2c51eb9be5fd..206f2d9312e8d1 100644 --- a/test/parallel/test-tls-no-sslv3.js +++ b/test/parallel/test-tls-no-sslv3.js @@ -16,6 +16,11 @@ if (common.opensslCli === false) { return; } +if (!common.isOpenSSL10) { + common.skip('node compiled without OpenSSL CLI version > 1.1.x.'); + return; +} + const cert = fs.readFileSync(`${common.fixturesDir}/test_cert.pem`); const key = fs.readFileSync(`${common.fixturesDir}/test_key.pem`); const server = tls.createServer({ cert: cert, key: key }, common.mustNotCall()); @@ -29,7 +34,7 @@ server.listen(0, '127.0.0.1', function() { '-connect', address]; // for the performance and stability issue in s_client on Windows - if (common.isWindows) + if (common.needNoRandScreen) args.push('-no_rand_screen'); const client = spawn(common.opensslCli, args, { stdio: 'pipe' }); diff --git a/test/parallel/test-tls-securepair-server.js b/test/parallel/test-tls-securepair-server.js index 00e8cd591ff2c9..227258f078dca2 100644 --- a/test/parallel/test-tls-securepair-server.js +++ b/test/parallel/test-tls-securepair-server.js @@ -121,7 +121,7 @@ server.listen(0, common.mustCall(function() { const args = ['s_client', '-connect', `127.0.0.1:${this.address().port}`]; // for the performance and stability issue in s_client on Windows - if (common.isWindows) + if (common.needNoRandScreen) args.push('-no_rand_screen'); const client = spawn(common.opensslCli, args); diff --git a/test/parallel/test-tls-server-failed-handshake-emits-clienterror.js b/test/parallel/test-tls-server-failed-handshake-emits-clienterror.js index 1ff7decf3cf9cc..0ce9e488dc9316 100644 --- a/test/parallel/test-tls-server-failed-handshake-emits-clienterror.js +++ b/test/parallel/test-tls-server-failed-handshake-emits-clienterror.js @@ -11,6 +11,9 @@ const assert = require('assert'); const bonkers = Buffer.alloc(1024, 42); +const expected = common.isOpenSSL10 ? + /SSL routines:SSL23_GET_CLIENT_HELLO:unknown protocol/ : + /wrong version/; const server = tls.createServer({}) .listen(0, function() { @@ -21,9 +24,7 @@ const server = tls.createServer({}) }).on('tlsClientError', common.mustCall(function(e) { assert.ok(e instanceof Error, 'Instance of Error should be passed to error handler'); - assert.ok(e.message.match( - /SSL routines:SSL23_GET_CLIENT_HELLO:unknown protocol/), - 'Expecting SSL unknown protocol'); + assert.ok(e.message.match(expected), 'Expecting SSL unknown protocol'); server.close(); })); diff --git a/test/parallel/test-tls-server-verify.js b/test/parallel/test-tls-server-verify.js index 2d7323dc5a840d..8de24d7344c3e6 100644 --- a/test/parallel/test-tls-server-verify.js +++ b/test/parallel/test-tls-server-verify.js @@ -157,7 +157,7 @@ function runClient(prefix, port, options, cb) { const args = ['s_client', '-connect', `127.0.0.1:${port}`]; // for the performance issue in s_client on Windows - if (common.isWindows) + if (common.needNoRandScreen) args.push('-no_rand_screen'); console.log(`${prefix} connecting with`, options.name); diff --git a/test/parallel/test-tls-session-cache.js b/test/parallel/test-tls-session-cache.js index 5a380597f5078a..8629f6e2746693 100644 --- a/test/parallel/test-tls-session-cache.js +++ b/test/parallel/test-tls-session-cache.js @@ -118,7 +118,7 @@ function doTest(testOptions, callback) { ].concat(testOptions.tickets ? [] : '-no_ticket'); // for the performance and stability issue in s_client on Windows - if (common.isWindows) + if (common.needNoRandScreen) args.push('-no_rand_screen'); function spawnClient() { diff --git a/test/parallel/test-tls-set-ciphers.js b/test/parallel/test-tls-set-ciphers.js index 4b7923891bd5b4..38925457b9e8c3 100644 --- a/test/parallel/test-tls-set-ciphers.js +++ b/test/parallel/test-tls-set-ciphers.js @@ -40,7 +40,7 @@ const fs = require('fs'); const options = { key: fs.readFileSync(`${common.fixturesDir}/keys/agent2-key.pem`), cert: fs.readFileSync(`${common.fixturesDir}/keys/agent2-cert.pem`), - ciphers: 'DES-CBC3-SHA' + ciphers: 'AES256-SHA' }; const reply = 'I AM THE WALRUS'; // something recognizable @@ -59,7 +59,7 @@ server.listen(0, '127.0.0.1', function() { options.ciphers} -connect 127.0.0.1:${this.address().port}`; // for the performance and stability issue in s_client on Windows - if (common.isWindows) + if (common.needNoRandScreen) cmd += ' -no_rand_screen'; exec(cmd, function(err, stdout, stderr) { diff --git a/test/parallel/test-tls-socket-failed-handshake-emits-error.js b/test/parallel/test-tls-socket-failed-handshake-emits-error.js index ffeb42c8ebd8da..299aa07e4cfba9 100644 --- a/test/parallel/test-tls-socket-failed-handshake-emits-error.js +++ b/test/parallel/test-tls-socket-failed-handshake-emits-error.js @@ -11,6 +11,10 @@ const assert = require('assert'); const bonkers = Buffer.alloc(1024, 42); +const expected_err = common.isOpenSSL10 ? + /SSL routines:SSL23_GET_CLIENT_HELLO:unknown protocol/ : + /wrong version number/; + const server = net.createServer(function(c) { setTimeout(function() { const s = new tls.TLSSocket(c, { @@ -19,10 +23,10 @@ const server = net.createServer(function(c) { }); s.on('error', common.mustCall(function(e) { + assert.ok(e instanceof Error, 'Instance of Error should be passed to error handler'); - assert.ok(e.message.match( - /SSL routines:SSL23_GET_CLIENT_HELLO:unknown protocol/), + assert.ok(e.message.match(expected_err), 'Expecting SSL unknown protocol'); })); diff --git a/test/parallel/test-tls-ticket.js b/test/parallel/test-tls-ticket.js index b2541e06ab8872..eec7b8b56f2b53 100644 --- a/test/parallel/test-tls-ticket.js +++ b/test/parallel/test-tls-ticket.js @@ -33,7 +33,8 @@ const fs = require('fs'); const net = require('net'); const crypto = require('crypto'); -const keys = crypto.randomBytes(48); +const ticketKeySize = common.isOpenSSL10 ? 48 : 80; +const keys = crypto.randomBytes(ticketKeySize); const serverLog = []; const ticketLog = []; @@ -57,7 +58,7 @@ function createServer() { // Rotate ticket keys if (counter === 1) { previousKey = server.getTicketKeys(); - server.setTicketKeys(crypto.randomBytes(48)); + server.setTicketKeys(crypto.randomBytes(ticketKeySize)); } else if (counter === 2) { server.setTicketKeys(previousKey); } else if (counter === 3) {