From debb4b9acbe4942973e05aab1b312245a60def19 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Tue, 30 May 2017 09:17:50 +0200 Subject: [PATCH 1/7] crypto: move crypto sources to src/crypto_impl --- Makefile | 2 ++ node.gypi | 16 ++++++++-------- src/{ => crypto_impl}/node_crypto.cc | 0 src/{ => crypto_impl}/node_crypto.h | 6 +++--- src/{ => crypto_impl}/node_crypto_bio.cc | 0 src/{ => crypto_impl}/node_crypto_bio.h | 6 +++--- .../node_crypto_clienthello-inl.h | 6 +++--- src/{ => crypto_impl}/node_crypto_clienthello.cc | 0 src/{ => crypto_impl}/node_crypto_clienthello.h | 6 +++--- src/{ => crypto_impl}/node_crypto_groups.h | 6 +++--- src/{ => crypto_impl}/tls_wrap.cc | 0 src/{ => crypto_impl}/tls_wrap.h | 6 +++--- src/inspector_io.cc | 2 +- src/node.cc | 2 +- 14 files changed, 30 insertions(+), 28 deletions(-) rename src/{ => crypto_impl}/node_crypto.cc (100%) rename src/{ => crypto_impl}/node_crypto.h (99%) rename src/{ => crypto_impl}/node_crypto_bio.cc (100%) rename src/{ => crypto_impl}/node_crypto_bio.h (97%) rename src/{ => crypto_impl}/node_crypto_clienthello-inl.h (93%) rename src/{ => crypto_impl}/node_crypto_clienthello.cc (100%) rename src/{ => crypto_impl}/node_crypto_clienthello.h (96%) rename src/{ => crypto_impl}/node_crypto_groups.h (99%) rename src/{ => crypto_impl}/tls_wrap.cc (100%) rename src/{ => crypto_impl}/tls_wrap.h (98%) 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/node.gypi b/node.gypi index a926d9a8e7ff26..851b6d8797083e 100644 --- a/node.gypi +++ b/node.gypi @@ -98,14 +98,14 @@ [ '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/crypto_impl/node_crypto.cc', + 'src/crypto_impl/node_crypto_bio.cc', + 'src/crypto_impl/node_crypto_clienthello.cc', + 'src/crypto_impl/node_crypto.h', + 'src/crypto_impl/node_crypto_bio.h', + 'src/crypto_impl/node_crypto_clienthello.h', + 'src/crypto_impl/tls_wrap.cc', + 'src/crypto_impl/tls_wrap.h' ], 'conditions': [ ['openssl_fips != ""', { diff --git a/src/node_crypto.cc b/src/crypto_impl/node_crypto.cc similarity index 100% rename from src/node_crypto.cc rename to src/crypto_impl/node_crypto.cc diff --git a/src/node_crypto.h b/src/crypto_impl/node_crypto.h similarity index 99% rename from src/node_crypto.h rename to src/crypto_impl/node_crypto.h index 33c9cf783ecedb..31227da56ff187 100644 --- a/src/node_crypto.h +++ b/src/crypto_impl/node_crypto.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_H_ -#define SRC_NODE_CRYPTO_H_ +#ifndef SRC_CRYPTO_IMPL_NODE_CRYPTO_H_ +#define SRC_CRYPTO_IMPL_NODE_CRYPTO_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS @@ -752,4 +752,4 @@ void InitCrypto(v8::Local target); #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#endif // SRC_NODE_CRYPTO_H_ +#endif // SRC_CRYPTO_IMPL_NODE_CRYPTO_H_ diff --git a/src/node_crypto_bio.cc b/src/crypto_impl/node_crypto_bio.cc similarity index 100% rename from src/node_crypto_bio.cc rename to src/crypto_impl/node_crypto_bio.cc diff --git a/src/node_crypto_bio.h b/src/crypto_impl/node_crypto_bio.h similarity index 97% rename from src/node_crypto_bio.h rename to src/crypto_impl/node_crypto_bio.h index 4699d1e6e342a4..a1e53f90dc399f 100644 --- a/src/node_crypto_bio.h +++ b/src/crypto_impl/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/node_crypto_clienthello-inl.h similarity index 93% rename from src/node_crypto_clienthello-inl.h rename to src/crypto_impl/node_crypto_clienthello-inl.h index 4044b0cde246df..e22582ec5e9004 100644 --- a/src/node_crypto_clienthello-inl.h +++ b/src/crypto_impl/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/node_crypto_clienthello.cc similarity index 100% rename from src/node_crypto_clienthello.cc rename to src/crypto_impl/node_crypto_clienthello.cc diff --git a/src/node_crypto_clienthello.h b/src/crypto_impl/node_crypto_clienthello.h similarity index 96% rename from src/node_crypto_clienthello.h rename to src/crypto_impl/node_crypto_clienthello.h index e52d6c3f9f8358..569661c02045a9 100644 --- a/src/node_crypto_clienthello.h +++ b/src/crypto_impl/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/node_crypto_groups.h similarity index 99% rename from src/node_crypto_groups.h rename to src/crypto_impl/node_crypto_groups.h index d22fdc7f966f9a..23508e1e2ce2eb 100644 --- a/src/node_crypto_groups.h +++ b/src/crypto_impl/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/tls_wrap.cc b/src/crypto_impl/tls_wrap.cc similarity index 100% rename from src/tls_wrap.cc rename to src/crypto_impl/tls_wrap.cc diff --git a/src/tls_wrap.h b/src/crypto_impl/tls_wrap.h similarity index 98% rename from src/tls_wrap.h rename to src/crypto_impl/tls_wrap.h index 19633ea8667fff..d06b86f0d966af 100644 --- a/src/tls_wrap.h +++ b/src/crypto_impl/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/inspector_io.cc b/src/inspector_io.cc index 69eed62ab4729a..17b1c2cc432230 100644 --- a/src/inspector_io.cc +++ b/src/inspector_io.cc @@ -4,7 +4,7 @@ #include "env.h" #include "env-inl.h" #include "node.h" -#include "node_crypto.h" +#include "crypto_impl/node_crypto.h" #include "node_mutex.h" #include "v8-inspector.h" #include "util.h" diff --git a/src/node.cc b/src/node.cc index bbce10220fe175..ab3d771008ad89 100644 --- a/src/node.cc +++ b/src/node.cc @@ -33,7 +33,7 @@ #endif #if HAVE_OPENSSL -#include "node_crypto.h" +#include "crypto_impl/node_crypto.h" #endif #if defined(NODE_HAVE_I18N_SUPPORT) From d3e9cc08cb909ed05cc4c7b6586c4ce779de437d Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Mon, 29 May 2017 14:04:33 +0200 Subject: [PATCH 2/7] crypto: add Crypto and CryptoFactory This commit adds an abstract base class for Crypto to enable different crypto implementations/version to be supported. This work is very much a proof of concept to find out if this approach will work and have code to discuss around. The goal here is to decouple node.cc from OpenSSL and move that code into a concrete implementation of Crypto. Currently the only member functions that exist in Crypto are those that I found in node.cc and allowed the project to be built and the test to run successfully. --- configure | 13 ++++ node.gyp | 4 +- node.gypi | 5 ++ src/crypto_impl/openssl.h | 26 ++++++++ src/crypto_impl/openssl_1_0_2e.cc | 71 ++++++++++++++++++++++ src/node.cc | 72 +++++++++-------------- src/node_crypto.h | 56 ++++++++++++++++++ src/node_crypto_factory.cc | 46 +++++++++++++++ src/node_crypto_factory.h | 39 ++++++++++++ test/cctest/test_crypto_factory.cc | 12 ++++ test/cctest/test_crypto_openssl_1_0_2e.cc | 34 +++++++++++ 11 files changed, 332 insertions(+), 46 deletions(-) create mode 100644 src/crypto_impl/openssl.h create mode 100644 src/crypto_impl/openssl_1_0_2e.cc create mode 100644 src/node_crypto.h create mode 100644 src/node_crypto_factory.cc create mode 100644 src/node_crypto_factory.h create mode 100644 test/cctest/test_crypto_factory.cc create mode 100644 test/cctest/test_crypto_openssl_1_0_2e.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/node.gyp b/node.gyp index 93ee7d64ddb287..d14fec8d481b90 100644 --- a/node.gyp +++ b/node.gyp @@ -652,7 +652,9 @@ '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', + 'test/cctest/test_crypto_openssl_1_0_2e.cc', ], 'sources!': [ diff --git a/node.gypi b/node.gypi index 851b6d8797083e..305f136e5556c9 100644 --- a/node.gypi +++ b/node.gypi @@ -98,6 +98,11 @@ [ 'node_use_openssl=="true"', { 'defines': [ 'HAVE_OPENSSL=1' ], 'sources': [ + 'src/node_crypto.h', + 'src/node_crypto_factory.h', + 'src/node_crypto_factory.cc', + 'src/crypto_impl/openssl.h', + 'src/crypto_impl/openssl_1_0_2e.cc', 'src/crypto_impl/node_crypto.cc', 'src/crypto_impl/node_crypto_bio.cc', 'src/crypto_impl/node_crypto_clienthello.cc', diff --git a/src/crypto_impl/openssl.h b/src/crypto_impl/openssl.h new file mode 100644 index 00000000000000..1a360ae8fc037c --- /dev/null +++ b/src/crypto_impl/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/crypto_impl/openssl_1_0_2e.cc b/src/crypto_impl/openssl_1_0_2e.cc new file mode 100644 index 00000000000000..5dcc7076c64c8a --- /dev/null +++ b/src/crypto_impl/openssl_1_0_2e.cc @@ -0,0 +1,71 @@ +#include "openssl.h" +#include "../node_crypto_factory.h" +#include "node_crypto.h" + +namespace node { +namespace crypto { + +constexpr char version_[] = "openssl_1_0_2e"; + +const std::string OpenSSL::Version() { + return version_; +} + +void Crypto::RegisterCrypto() { + CryptoFactory::Register(version_, []() -> Crypto* { + return new OpenSSL(); + }); +} + +void Crypto::UnregisterCrypto() { + CryptoFactory::Unregister(version_); +} + +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/node.cc b/src/node.cc index ab3d771008ad89..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 "crypto_impl/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 new file mode 100644 index 00000000000000..c9ddaf56bd440a --- /dev/null +++ b/src/node_crypto.h @@ -0,0 +1,56 @@ +#ifndef SRC_NODE_CRYPTO_H_ +#define SRC_NODE_CRYPTO_H_ +#include +#include "v8.h" // NOLINT(build/include_order) + +namespace node { +namespace crypto { + +class Crypto { + public: + virtual ~Crypto() {} + __attribute__((constructor)) virtual void RegisterCrypto(); + __attribute__((destructor)) virtual void UnregisterCrypto(); + /** + * 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; +}; + +} // 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..2a7ef34f8731da --- /dev/null +++ b/src/node_crypto_factory.cc @@ -0,0 +1,46 @@ +#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; + +void CryptoFactory::Register(const std::string& version, + CreateCallback cb) { + if (!crypto_libs.count(version)) { + std::shared_ptr crypto{cb()}; + crypto_libs.insert(CryptoPair(version, crypto)); + } +} + +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..68aed93db3dcc6 --- /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 void 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_crypto_openssl_1_0_2e.cc b/test/cctest/test_crypto_openssl_1_0_2e.cc new file mode 100644 index 00000000000000..6d491ecc070bb4 --- /dev/null +++ b/test/cctest/test_crypto_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.h" + +using node::crypto::CryptoFactory; +using node::crypto::Crypto; +using node::crypto::OpenSSL; + +const char version[] = "openssl_1_0_2e"; + +TEST(CryptFactoryTest, Version) { + EXPECT_EQ(CryptoFactory::Get(version)->Version(), version); +} + +TEST(CryptFactoryTest, Name) { + EXPECT_EQ(CryptoFactory::Get(version)->Name(), "openssl"); +} + +TEST(CryptFactoryTest, HasSNI) { + EXPECT_TRUE(CryptoFactory::Get(version)->HasSNI()); +} + +TEST(CryptFactoryTest, HasNPN) { + EXPECT_TRUE(CryptoFactory::Get(version)->HasNPN()); +} + +TEST(CryptFactoryTest, HasALPN) { + EXPECT_FALSE(CryptoFactory::Get(version)->HasALPN()); +} + +TEST(CryptFactoryTest, HasOCSP) { + EXPECT_TRUE(CryptoFactory::Get(version)->HasOCSP()); +} From 39fac636d3df7d12373807672ef5b8e9d560907a Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Wed, 7 Jun 2017 12:07:08 +0200 Subject: [PATCH 3/7] crypto: add openssl_1_1_0f implementation --- node.gyp | 7 +- node.gypi | 7 +- src/crypto_impl/openssl_1_1_0f.cc | 71 +++++++++++++++++++ ...enssl_1_0_2e.cc => test_openssl_1_0_2e.cc} | 12 ++-- test/cctest/test_openssl_1_1_0f.cc | 34 +++++++++ 5 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 src/crypto_impl/openssl_1_1_0f.cc rename test/cctest/{test_crypto_openssl_1_0_2e.cc => test_openssl_1_0_2e.cc} (75%) create mode 100644 test/cctest/test_openssl_1_1_0f.cc diff --git a/node.gyp b/node.gyp index d14fec8d481b90..b39b0ead70ee6c 100644 --- a/node.gyp +++ b/node.gyp @@ -654,7 +654,6 @@ 'test/cctest/test_util.cc', 'test/cctest/test_url.cc', 'test/cctest/test_crypto_factory.cc', - 'test/cctest/test_crypto_openssl_1_0_2e.cc', ], 'sources!': [ @@ -662,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 305f136e5556c9..1648b13d2e4634 100644 --- a/node.gypi +++ b/node.gypi @@ -102,7 +102,6 @@ 'src/node_crypto_factory.h', 'src/node_crypto_factory.cc', 'src/crypto_impl/openssl.h', - 'src/crypto_impl/openssl_1_0_2e.cc', 'src/crypto_impl/node_crypto.cc', 'src/crypto_impl/node_crypto_bio.cc', 'src/crypto_impl/node_crypto_clienthello.cc', @@ -113,6 +112,12 @@ 'src/crypto_impl/tls_wrap.h' ], 'conditions': [ + [ 'node_crypto_version=="openssl_1_0_2e"', { + 'sources+': ['src/crypto_impl/openssl_1_0_2e.cc'], + }], + [ 'node_crypto_version=="openssl_1_1_0f"', { + 'sources+': ['src/crypto_impl/openssl_1_1_0f.cc'], + }], ['openssl_fips != ""', { 'defines': [ 'NODE_FIPS_MODE' ], }], diff --git a/src/crypto_impl/openssl_1_1_0f.cc b/src/crypto_impl/openssl_1_1_0f.cc new file mode 100644 index 00000000000000..ef6bf7c042d5d8 --- /dev/null +++ b/src/crypto_impl/openssl_1_1_0f.cc @@ -0,0 +1,71 @@ +#include "openssl.h" +#include "../node_crypto_factory.h" +#include "node_crypto.h" + +namespace node { +namespace crypto { + +constexpr char version_[] = "openssl_1_1_0f"; + +const std::string OpenSSL::Version() { + return version_; +} + +void Crypto::RegisterCrypto() { + CryptoFactory::Register(version_, []() -> Crypto* { + return new OpenSSL(); + }); +} + +void Crypto::UnregisterCrypto() { + CryptoFactory::Unregister(version_); +} + +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/test/cctest/test_crypto_openssl_1_0_2e.cc b/test/cctest/test_openssl_1_0_2e.cc similarity index 75% rename from test/cctest/test_crypto_openssl_1_0_2e.cc rename to test/cctest/test_openssl_1_0_2e.cc index 6d491ecc070bb4..a695207f8ec2b2 100644 --- a/test/cctest/test_crypto_openssl_1_0_2e.cc +++ b/test/cctest/test_openssl_1_0_2e.cc @@ -9,26 +9,26 @@ using node::crypto::OpenSSL; const char version[] = "openssl_1_0_2e"; -TEST(CryptFactoryTest, Version) { +TEST(OpenSSL_1_0_2e, Version) { EXPECT_EQ(CryptoFactory::Get(version)->Version(), version); } -TEST(CryptFactoryTest, Name) { +TEST(OpenSSL_1_0_2e, Name) { EXPECT_EQ(CryptoFactory::Get(version)->Name(), "openssl"); } -TEST(CryptFactoryTest, HasSNI) { +TEST(OpenSSL_1_0_2e, HasSNI) { EXPECT_TRUE(CryptoFactory::Get(version)->HasSNI()); } -TEST(CryptFactoryTest, HasNPN) { +TEST(OpenSSL_1_0_2e, HasNPN) { EXPECT_TRUE(CryptoFactory::Get(version)->HasNPN()); } -TEST(CryptFactoryTest, HasALPN) { +TEST(OpenSSL_1_0_2e, HasALPN) { EXPECT_FALSE(CryptoFactory::Get(version)->HasALPN()); } -TEST(CryptFactoryTest, HasOCSP) { +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..971d332cba88dd --- /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.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(), version); +} + +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()); +} From 45677cae204ad14ebf083cab994e89eb32809d2e Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Thu, 8 Jun 2017 15:22:28 +0200 Subject: [PATCH 4/7] crypto: split crypto into two implementations On implementation for openssl_1_0_2e and one for openssl_1_1_0f. This is bascally copying the existing impl and updating to compile when linking to a shared-openssl. Building for OpenSSL 1.1.0f: ./configure --debug --shared-openssl --shared-openssl-libpath=/Users/danielbevenius/work/security/build_1_1_0f/lib --shared-openssl-includes=/Users/danielbevenius/work/security/build_1_1_0f/include --crypto-version=openssl_1_1_0f && make -j8 --- lib/_tls_common.js | 1 - node.gypi | 35 +- .../{ => openssl/1_0_2e}/node_crypto.cc | 5 +- .../{ => openssl/1_0_2e}/node_crypto.h | 0 .../{ => openssl/1_0_2e}/node_crypto_bio.cc | 0 .../{ => openssl/1_0_2e}/node_crypto_bio.h | 0 .../1_0_2e}/node_crypto_clienthello-inl.h | 0 .../1_0_2e}/node_crypto_clienthello.cc | 0 .../1_0_2e}/node_crypto_clienthello.h | 0 .../{ => openssl/1_0_2e}/node_crypto_groups.h | 0 .../{ => openssl/1_0_2e}/openssl_1_0_2e.cc | 12 +- .../{ => openssl/1_0_2e}/tls_wrap.cc | 0 .../{ => openssl/1_0_2e}/tls_wrap.h | 0 src/crypto_impl/openssl/1_1_0f/node_crypto.cc | 6331 +++++++++++++++++ src/crypto_impl/openssl/1_1_0f/node_crypto.h | 745 ++ .../openssl/1_1_0f/node_crypto_bio.cc | 519 ++ .../openssl/1_1_0f/node_crypto_bio.h | 164 + .../1_1_0f/node_crypto_clienthello-inl.h | 81 + .../openssl/1_1_0f/node_crypto_clienthello.cc | 247 + .../openssl/1_1_0f/node_crypto_clienthello.h | 140 + .../openssl/1_1_0f/node_crypto_groups.h | 416 ++ .../{ => openssl/1_1_0f}/openssl_1_1_0f.cc | 11 +- src/crypto_impl/openssl/1_1_0f/tls_wrap.cc | 969 +++ src/crypto_impl/openssl/1_1_0f/tls_wrap.h | 195 + src/crypto_impl/{ => openssl}/openssl.h | 2 +- src/inspector_io.cc | 11 +- src/inspector_io.h | 2 + test/cctest/test_openssl_1_0_2e.cc | 4 +- test/cctest/test_openssl_1_1_0f.cc | 4 +- test/parallel/test-crypto-binary-default.js | 4 +- test/parallel/test-crypto-rsa-dsa.js | 10 +- .../test-https-agent-session-eviction.js | 15 + test/parallel/test-tls-basic-validations.js | 2 +- test/parallel/test-tls-close-notify.js | 15 +- test/parallel/test-tls-ecdh-disable.js | 5 + test/parallel/test-tls-no-sslv3.js | 5 + test/parallel/test-tls-set-ciphers.js | 2 +- 37 files changed, 9909 insertions(+), 43 deletions(-) rename src/crypto_impl/{ => openssl/1_0_2e}/node_crypto.cc (99%) rename src/crypto_impl/{ => openssl/1_0_2e}/node_crypto.h (100%) rename src/crypto_impl/{ => openssl/1_0_2e}/node_crypto_bio.cc (100%) rename src/crypto_impl/{ => openssl/1_0_2e}/node_crypto_bio.h (100%) rename src/crypto_impl/{ => openssl/1_0_2e}/node_crypto_clienthello-inl.h (100%) rename src/crypto_impl/{ => openssl/1_0_2e}/node_crypto_clienthello.cc (100%) rename src/crypto_impl/{ => openssl/1_0_2e}/node_crypto_clienthello.h (100%) rename src/crypto_impl/{ => openssl/1_0_2e}/node_crypto_groups.h (100%) rename src/crypto_impl/{ => openssl/1_0_2e}/openssl_1_0_2e.cc (78%) rename src/crypto_impl/{ => openssl/1_0_2e}/tls_wrap.cc (100%) rename src/crypto_impl/{ => openssl/1_0_2e}/tls_wrap.h (100%) create mode 100644 src/crypto_impl/openssl/1_1_0f/node_crypto.cc create mode 100644 src/crypto_impl/openssl/1_1_0f/node_crypto.h create mode 100644 src/crypto_impl/openssl/1_1_0f/node_crypto_bio.cc create mode 100644 src/crypto_impl/openssl/1_1_0f/node_crypto_bio.h create mode 100644 src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello-inl.h create mode 100644 src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello.cc create mode 100644 src/crypto_impl/openssl/1_1_0f/node_crypto_clienthello.h create mode 100644 src/crypto_impl/openssl/1_1_0f/node_crypto_groups.h rename src/crypto_impl/{ => openssl/1_1_0f}/openssl_1_1_0f.cc (79%) create mode 100644 src/crypto_impl/openssl/1_1_0f/tls_wrap.cc create mode 100644 src/crypto_impl/openssl/1_1_0f/tls_wrap.h rename src/crypto_impl/{ => openssl}/openssl.h (94%) 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.gypi b/node.gypi index 1648b13d2e4634..fda0e9a4fdbde0 100644 --- a/node.gypi +++ b/node.gypi @@ -101,22 +101,35 @@ 'src/node_crypto.h', 'src/node_crypto_factory.h', 'src/node_crypto_factory.cc', - 'src/crypto_impl/openssl.h', - 'src/crypto_impl/node_crypto.cc', - 'src/crypto_impl/node_crypto_bio.cc', - 'src/crypto_impl/node_crypto_clienthello.cc', - 'src/crypto_impl/node_crypto.h', - 'src/crypto_impl/node_crypto_bio.h', - 'src/crypto_impl/node_crypto_clienthello.h', - 'src/crypto_impl/tls_wrap.cc', - 'src/crypto_impl/tls_wrap.h' ], 'conditions': [ [ 'node_crypto_version=="openssl_1_0_2e"', { - 'sources+': ['src/crypto_impl/openssl_1_0_2e.cc'], + '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.cc'], + '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/crypto_impl/node_crypto.cc b/src/crypto_impl/openssl/1_0_2e/node_crypto.cc similarity index 99% rename from src/crypto_impl/node_crypto.cc rename to src/crypto_impl/openssl/1_0_2e/node_crypto.cc index 579ba3c65da640..597a8bd8d11575 100644 --- a/src/crypto_impl/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/node_crypto.h b/src/crypto_impl/openssl/1_0_2e/node_crypto.h similarity index 100% rename from src/crypto_impl/node_crypto.h rename to src/crypto_impl/openssl/1_0_2e/node_crypto.h diff --git a/src/crypto_impl/node_crypto_bio.cc b/src/crypto_impl/openssl/1_0_2e/node_crypto_bio.cc similarity index 100% rename from src/crypto_impl/node_crypto_bio.cc rename to src/crypto_impl/openssl/1_0_2e/node_crypto_bio.cc diff --git a/src/crypto_impl/node_crypto_bio.h b/src/crypto_impl/openssl/1_0_2e/node_crypto_bio.h similarity index 100% rename from src/crypto_impl/node_crypto_bio.h rename to src/crypto_impl/openssl/1_0_2e/node_crypto_bio.h diff --git a/src/crypto_impl/node_crypto_clienthello-inl.h b/src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello-inl.h similarity index 100% rename from src/crypto_impl/node_crypto_clienthello-inl.h rename to src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello-inl.h diff --git a/src/crypto_impl/node_crypto_clienthello.cc b/src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello.cc similarity index 100% rename from src/crypto_impl/node_crypto_clienthello.cc rename to src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello.cc diff --git a/src/crypto_impl/node_crypto_clienthello.h b/src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello.h similarity index 100% rename from src/crypto_impl/node_crypto_clienthello.h rename to src/crypto_impl/openssl/1_0_2e/node_crypto_clienthello.h diff --git a/src/crypto_impl/node_crypto_groups.h b/src/crypto_impl/openssl/1_0_2e/node_crypto_groups.h similarity index 100% rename from src/crypto_impl/node_crypto_groups.h rename to src/crypto_impl/openssl/1_0_2e/node_crypto_groups.h diff --git a/src/crypto_impl/openssl_1_0_2e.cc b/src/crypto_impl/openssl/1_0_2e/openssl_1_0_2e.cc similarity index 78% rename from src/crypto_impl/openssl_1_0_2e.cc rename to src/crypto_impl/openssl/1_0_2e/openssl_1_0_2e.cc index 5dcc7076c64c8a..9e780b0133aeea 100644 --- a/src/crypto_impl/openssl_1_0_2e.cc +++ b/src/crypto_impl/openssl/1_0_2e/openssl_1_0_2e.cc @@ -1,24 +1,26 @@ -#include "openssl.h" -#include "../node_crypto_factory.h" +#include "../openssl.h" +#include "../../../node_crypto_factory.h" #include "node_crypto.h" +#include namespace node { namespace crypto { -constexpr char version_[] = "openssl_1_0_2e"; +constexpr char version_[] = "1.0.2e"; +constexpr char typeversion_[] = "openssl_1_0_2e"; const std::string OpenSSL::Version() { return version_; } void Crypto::RegisterCrypto() { - CryptoFactory::Register(version_, []() -> Crypto* { + CryptoFactory::Register(typeversion_, []() -> Crypto* { return new OpenSSL(); }); } void Crypto::UnregisterCrypto() { - CryptoFactory::Unregister(version_); + CryptoFactory::Unregister(typeversion_); } bool OpenSSL::HasSNI() { diff --git a/src/crypto_impl/tls_wrap.cc b/src/crypto_impl/openssl/1_0_2e/tls_wrap.cc similarity index 100% rename from src/crypto_impl/tls_wrap.cc rename to src/crypto_impl/openssl/1_0_2e/tls_wrap.cc diff --git a/src/crypto_impl/tls_wrap.h b/src/crypto_impl/openssl/1_0_2e/tls_wrap.h similarity index 100% rename from src/crypto_impl/tls_wrap.h rename to src/crypto_impl/openssl/1_0_2e/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..60f36e084e11d4 --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto.cc @@ -0,0 +1,6331 @@ +// 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, "setFreeListLength", SecureContext::SetFreeListLength); + 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) +} + + +void SecureContext::SetFreeListLength(const FunctionCallbackInfo& args) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(OPENSSL_IS_BORINGSSL) + // |freelist_max_len| was removed in OpenSSL 1.1.0. In that version OpenSSL + // mallocs and frees buffers directly, without the use of a freelist. + SecureContext* wrap; + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); + + wrap->ctx_->freelist_max_len = args[0]->Int32Value(); +#endif +} + + +// 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_test_flags(mdctx, EVP_MD_FLAG_DIGALGID_MASK)) { + 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_data(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_data(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..46d8993d303830 --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto.h @@ -0,0 +1,745 @@ +// 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 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); + } + + 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); + } + + 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); + } + + 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); + } + + 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_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..a2c14e02869739 --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto_bio.cc @@ -0,0 +1,519 @@ +// 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 { + +const BIO_METHOD* NodeBIO::CreateBioMethod() { + BIO_METHOD* biom = BIO_meth_new(BIO_TYPE_MEM, "node.js SSL buffer"); + BIO_meth_set_write(biom, Write); + BIO_meth_set_read(biom, NodeBIO::Read); + BIO_meth_set_puts(biom, NodeBIO::Puts); + BIO_meth_set_gets(biom, NodeBIO::Gets); + BIO_meth_set_ctrl(biom, NodeBIO::Ctrl); + BIO_meth_set_create(biom, NodeBIO::New); + BIO_meth_set_destroy(biom, NodeBIO::Free); + return biom; +} +const BIO_METHOD* NodeBIO::method = NodeBIO::CreateBioMethod(); +/* +BIO_TYPE_MEM, + "node.js SSL buffer", + NodeBIO::Write, + NodeBIO::Read, + NodeBIO::Puts, + NodeBIO::Gets, + NodeBIO::Ctrl, + NodeBIO::New, + NodeBIO::Free, + nullptr +}; +{ + if (method_tls_corrupt == NULL) { + method_tls_corrupt = BIO_meth_new(BIO_TYPE_CUSTOM_FILTER, "node.js SSL buffer"); + if ( method_tls_corrupt == NULL + || !BIO_meth_set_write(method_tls_corrupt, tls_corrupt_write) + || !BIO_meth_set_read(method_tls_corrupt, tls_corrupt_read) + || !BIO_meth_set_puts(method_tls_corrupt, tls_corrupt_puts) + || !BIO_meth_set_gets(method_tls_corrupt, tls_corrupt_gets) + || !BIO_meth_set_ctrl(method_tls_corrupt, tls_corrupt_ctrl) + || !BIO_meth_set_create(method_tls_corrupt, tls_corrupt_new) + || !BIO_meth_set_destroy(method_tls_corrupt, tls_corrupt_free)) + return NULL; + } + return method_tls_corrupt; +} +*/ + + +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; +} + + +int NodeBIO::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; +} + + +int NodeBIO::Free(BIO* bio) { + if (bio == nullptr) + return 0; + + if (BIO_get_shutdown(bio)) { + if (BIO_get_init(bio) && BIO_get_data(bio) != nullptr) { + delete FromBIO(bio); + BIO_set_data(bio, nullptr); + } + } + + return 1; +} + + +int NodeBIO::Read(BIO* bio, char* out, int len) { + int bytes; + BIO_clear_retry_flags(bio); + + bytes = FromBIO(bio)->Read(out, len); + + if (bytes == 0) { + //bytes = bio->num; + //if (bytes != 0) { + if (BIO_should_retry(bio)) { + 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; +} + + +int NodeBIO::Write(BIO* bio, const char* data, int len) { + BIO_clear_retry_flags(bio); + + FromBIO(bio)->Write(data, len); + + return len; +} + + +int NodeBIO::Puts(BIO* bio, const char* str) { + return Write(bio, str, strlen(str)); +} + + +int NodeBIO::Gets(BIO* bio, char* out, int size) { + NodeBIO* nbio = 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; +} + + +long NodeBIO::Ctrl(BIO* bio, int cmd, long num, // NOLINT(runtime/int) + void* ptr) { + NodeBIO* nbio; + long ret; // NOLINT(runtime/int) + + nbio = 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); + 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..230c9483fb9f60 --- /dev/null +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto_bio.h @@ -0,0 +1,164 @@ +// 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), + 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; + } + + static inline NodeBIO* FromBIO(BIO* bio) { + CHECK_NE(BIO_get_data(bio), nullptr); + return static_cast(BIO_get_data(bio)); + } + + private: + 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); + + // Enough to handle the most of the client hellos + static const size_t kInitialBufferLength = 1024; + static const size_t kThroughputBufferLength = 16384; + + static const BIO_METHOD* CreateBioMethod(); + static const BIO_METHOD* method; + + 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_; + 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.cc b/src/crypto_impl/openssl/1_1_0f/openssl_1_1_0f.cc similarity index 79% rename from src/crypto_impl/openssl_1_1_0f.cc rename to src/crypto_impl/openssl/1_1_0f/openssl_1_1_0f.cc index ef6bf7c042d5d8..7213ee9ad6ce68 100644 --- a/src/crypto_impl/openssl_1_1_0f.cc +++ b/src/crypto_impl/openssl/1_1_0f/openssl_1_1_0f.cc @@ -1,24 +1,25 @@ -#include "openssl.h" -#include "../node_crypto_factory.h" +#include "../openssl.h" +#include "../../../node_crypto_factory.h" #include "node_crypto.h" namespace node { namespace crypto { -constexpr char version_[] = "openssl_1_1_0f"; +constexpr char version_[] = "1.1.0f"; +constexpr char typeversion_[] = "openssl_1_1_0f"; const std::string OpenSSL::Version() { return version_; } void Crypto::RegisterCrypto() { - CryptoFactory::Register(version_, []() -> Crypto* { + CryptoFactory::Register(typeversion_, []() -> Crypto* { return new OpenSSL(); }); } void Crypto::UnregisterCrypto() { - CryptoFactory::Unregister(version_); + CryptoFactory::Unregister(typeversion_); } bool OpenSSL::HasSNI() { 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.h b/src/crypto_impl/openssl/openssl.h similarity index 94% rename from src/crypto_impl/openssl.h rename to src/crypto_impl/openssl/openssl.h index 1a360ae8fc037c..70bf32ee20e85b 100644 --- a/src/crypto_impl/openssl.h +++ b/src/crypto_impl/openssl/openssl.h @@ -1,7 +1,7 @@ #ifndef SRC_CRYPTO_IMPL_OPENSSL_H_ #define SRC_CRYPTO_IMPL_OPENSSL_H_ -#include "../node_crypto.h" +#include "../../node_crypto.h" namespace node { namespace crypto { diff --git a/src/inspector_io.cc b/src/inspector_io.cc index 17b1c2cc432230..7f28e4ec92a179 100644 --- a/src/inspector_io.cc +++ b/src/inspector_io.cc @@ -4,7 +4,8 @@ #include "env.h" #include "env-inl.h" #include "node.h" -#include "crypto_impl/node_crypto.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/test/cctest/test_openssl_1_0_2e.cc b/test/cctest/test_openssl_1_0_2e.cc index a695207f8ec2b2..cd1409db419f5e 100644 --- a/test/cctest/test_openssl_1_0_2e.cc +++ b/test/cctest/test_openssl_1_0_2e.cc @@ -1,7 +1,7 @@ #include "gtest/gtest.h" #include "libplatform/libplatform.h" #include "node_crypto_factory.h" -#include "crypto_impl/openssl.h" +#include "crypto_impl/openssl/openssl.h" using node::crypto::CryptoFactory; using node::crypto::Crypto; @@ -10,7 +10,7 @@ using node::crypto::OpenSSL; const char version[] = "openssl_1_0_2e"; TEST(OpenSSL_1_0_2e, Version) { - EXPECT_EQ(CryptoFactory::Get(version)->Version(), version); + EXPECT_EQ(CryptoFactory::Get(version)->Version(), "1.0.2e"); } TEST(OpenSSL_1_0_2e, Name) { diff --git a/test/cctest/test_openssl_1_1_0f.cc b/test/cctest/test_openssl_1_1_0f.cc index 971d332cba88dd..b7e53578d7a748 100644 --- a/test/cctest/test_openssl_1_1_0f.cc +++ b/test/cctest/test_openssl_1_1_0f.cc @@ -1,7 +1,7 @@ #include "gtest/gtest.h" #include "libplatform/libplatform.h" #include "node_crypto_factory.h" -#include "crypto_impl/openssl.h" +#include "crypto_impl/openssl/openssl.h" using node::crypto::CryptoFactory; using node::crypto::Crypto; @@ -10,7 +10,7 @@ using node::crypto::OpenSSL; const char version[] = "openssl_1_1_0f"; TEST(OpenSSL_1_1_0e, Version) { - EXPECT_EQ(CryptoFactory::Get(version)->Version(), version); + EXPECT_EQ(CryptoFactory::Get(version)->Version(), "1.1.0f"); } TEST(OpenSSL_1_1_0e, Name) { 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-https-agent-session-eviction.js b/test/parallel/test-https-agent-session-eviction.js index acead806ed066e..da2147d53ce88d 100644 --- a/test/parallel/test-https-agent-session-eviction.js +++ b/test/parallel/test-https-agent-session-eviction.js @@ -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-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-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-ecdh-disable.js b/test/parallel/test-tls-ecdh-disable.js index 732ebe4d1bdafc..195ca529395fba 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; diff --git a/test/parallel/test-tls-no-sslv3.js b/test/parallel/test-tls-no-sslv3.js index 2c2c51eb9be5fd..fe0c4fe7eabe14 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()); diff --git a/test/parallel/test-tls-set-ciphers.js b/test/parallel/test-tls-set-ciphers.js index 4b7923891bd5b4..e5c3a419bc6099 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 From 8fde1f2ff90050d898688936f106d713a9fcd703 Mon Sep 17 00:00:00 2001 From: Shigeki Ohtsu Date: Fri, 10 Mar 2017 17:41:13 +0900 Subject: [PATCH 5/7] test: fix tests work on both openssl102 and 110 This fixes that tests can run and pass on both built with OpenSSL-1.0.2 and 1.1.0. Only the test of `test/parallel/test-tls-econnreset.js` is skipped because there are no way to leads TLS handshake failure and send ECONNRESET from the tls client to tls server with using OpenSSL-1.1.0. --- src/crypto_impl/openssl/1_1_0f/node_crypto.cc | 19 +- src/crypto_impl/openssl/1_1_0f/node_crypto.h | 6 +- .../openssl/1_1_0f/node_crypto_bio.cc | 95 ++++----- .../openssl/1_1_0f/node_crypto_bio.h | 22 +- test/common/index.js | 3 + test/parallel/test-crypto.js | 12 +- .../test-https-agent-session-eviction.js | 2 +- .../test-https-agent-session-reuse.js | 4 +- .../test-https-connect-address-family.js | 11 +- test/parallel/test-https-foafssl.js | 2 +- test/parallel/test-tls-alert.js | 2 +- test/parallel/test-tls-alpn-server-client.js | 197 +++++++++++------- test/parallel/test-tls-cert-regression.js | 52 +++-- .../test-tls-connect-address-family.js | 11 +- test/parallel/test-tls-dhe.js | 2 +- test/parallel/test-tls-ecdh-disable.js | 4 +- test/parallel/test-tls-ecdh.js | 2 +- test/parallel/test-tls-econnreset.js | 12 ++ test/parallel/test-tls-junk-server.js | 5 +- test/parallel/test-tls-multi-key.js | 6 +- test/parallel/test-tls-no-sslv3.js | 2 +- test/parallel/test-tls-securepair-server.js | 2 +- ...rver-failed-handshake-emits-clienterror.js | 7 +- test/parallel/test-tls-server-verify.js | 2 +- test/parallel/test-tls-session-cache.js | 2 +- test/parallel/test-tls-set-ciphers.js | 2 +- ...tls-socket-failed-handshake-emits-error.js | 8 +- test/parallel/test-tls-ticket.js | 5 +- 28 files changed, 280 insertions(+), 219 deletions(-) diff --git a/src/crypto_impl/openssl/1_1_0f/node_crypto.cc b/src/crypto_impl/openssl/1_1_0f/node_crypto.cc index 60f36e084e11d4..16f62764da2741 100644 --- a/src/crypto_impl/openssl/1_1_0f/node_crypto.cc +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto.cc @@ -332,7 +332,6 @@ void SecureContext::Initialize(Environment* env, Local target) { env->SetProtoMethod(t, "loadPKCS12", SecureContext::LoadPKCS12); env->SetProtoMethod(t, "getTicketKeys", SecureContext::GetTicketKeys); env->SetProtoMethod(t, "setTicketKeys", SecureContext::SetTicketKeys); - env->SetProtoMethod(t, "setFreeListLength", SecureContext::SetFreeListLength); env->SetProtoMethod(t, "enableTicketKeyCallback", SecureContext::EnableTicketKeyCallback); @@ -1196,18 +1195,6 @@ void SecureContext::SetTicketKeys(const FunctionCallbackInfo& args) { } -void SecureContext::SetFreeListLength(const FunctionCallbackInfo& args) { -#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(OPENSSL_IS_BORINGSSL) - // |freelist_max_len| was removed in OpenSSL 1.1.0. In that version OpenSSL - // mallocs and frees buffers directly, without the use of a freelist. - SecureContext* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); - - wrap->ctx_->freelist_max_len = args[0]->Int32Value(); -#endif -} - - // Currently, EnableTicketKeyCallback and TicketKeyCallback are only present for // the regression test in test/parallel/test-https-resume-after-renew.js. void SecureContext::EnableTicketKeyCallback( @@ -4147,7 +4134,7 @@ static int Node_SignFinal(EVP_MD_CTX* mdctx, unsigned char* md, if (!EVP_DigestFinal_ex(mdctx, m, &m_len)) return rv; - if (EVP_MD_CTX_test_flags(mdctx, EVP_MD_FLAG_DIGALGID_MASK)) { + 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) @@ -4156,7 +4143,7 @@ static int Node_SignFinal(EVP_MD_CTX* mdctx, unsigned char* md, goto err; if (!ApplyRSAOptions(pkey, pkctx, padding, pss_salt_len)) goto err; - if (EVP_PKEY_CTX_set_signature_md(pkctx, EVP_MD_CTX_md_data(mdctx)) <= 0) + 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; @@ -4457,7 +4444,7 @@ SignBase::Error Verify::VerifyFinal(const char* key_pem, goto err; if (!ApplyRSAOptions(pkey, pkctx, padding, saltlen)) goto err; - if (EVP_PKEY_CTX_set_signature_md(pkctx, EVP_MD_CTX_md_data(mdctx_)) <= 0) + if (EVP_PKEY_CTX_set_signature_md(pkctx, EVP_MD_CTX_md(mdctx_)) <= 0) goto err; r = EVP_PKEY_verify(pkctx, reinterpret_cast(sig), diff --git a/src/crypto_impl/openssl/1_1_0f/node_crypto.h b/src/crypto_impl/openssl/1_1_0f/node_crypto.h index 46d8993d303830..fe1d7df16a4c93 100644 --- a/src/crypto_impl/openssl/1_1_0f/node_crypto.h +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto.h @@ -128,8 +128,6 @@ class SecureContext : public BaseObject { 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, @@ -466,6 +464,7 @@ class CipherBase : public BaseObject { auth_tag_(nullptr), auth_tag_len_(0) { MakeWeak(this); + ctx_ = EVP_CIPHER_CTX_new(); } private: @@ -501,6 +500,7 @@ class Hmac : public BaseObject { : BaseObject(env, wrap), initialised_(false) { MakeWeak(this); + ctx_ = HMAC_CTX_new(); } private: @@ -530,6 +530,7 @@ class Hash : public BaseObject { : BaseObject(env, wrap), initialised_(false) { MakeWeak(this); + mdctx_ = EVP_MD_CTX_new(); } private: @@ -553,6 +554,7 @@ class SignBase : public BaseObject { SignBase(Environment* env, v8::Local wrap) : BaseObject(env, wrap), initialised_(false) { + mdctx_ = EVP_MD_CTX_new(); } ~SignBase() override { 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 index a2c14e02869739..d27f4689755988 100644 --- a/src/crypto_impl/openssl/1_1_0f/node_crypto_bio.cc +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto_bio.cc @@ -28,47 +28,28 @@ namespace node { -const BIO_METHOD* NodeBIO::CreateBioMethod() { - BIO_METHOD* biom = BIO_meth_new(BIO_TYPE_MEM, "node.js SSL buffer"); - BIO_meth_set_write(biom, Write); - BIO_meth_set_read(biom, NodeBIO::Read); - BIO_meth_set_puts(biom, NodeBIO::Puts); - BIO_meth_set_gets(biom, NodeBIO::Gets); - BIO_meth_set_ctrl(biom, NodeBIO::Ctrl); - BIO_meth_set_create(biom, NodeBIO::New); - BIO_meth_set_destroy(biom, NodeBIO::Free); - return biom; +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* NodeBIO::method = NodeBIO::CreateBioMethod(); -/* -BIO_TYPE_MEM, - "node.js SSL buffer", - NodeBIO::Write, - NodeBIO::Read, - NodeBIO::Puts, - NodeBIO::Gets, - NodeBIO::Ctrl, - NodeBIO::New, - NodeBIO::Free, - nullptr -}; -{ - if (method_tls_corrupt == NULL) { - method_tls_corrupt = BIO_meth_new(BIO_TYPE_CUSTOM_FILTER, "node.js SSL buffer"); - if ( method_tls_corrupt == NULL - || !BIO_meth_set_write(method_tls_corrupt, tls_corrupt_write) - || !BIO_meth_set_read(method_tls_corrupt, tls_corrupt_read) - || !BIO_meth_set_puts(method_tls_corrupt, tls_corrupt_puts) - || !BIO_meth_set_gets(method_tls_corrupt, tls_corrupt_gets) - || !BIO_meth_set_ctrl(method_tls_corrupt, tls_corrupt_ctrl) - || !BIO_meth_set_create(method_tls_corrupt, tls_corrupt_new) - || !BIO_meth_set_destroy(method_tls_corrupt, tls_corrupt_free)) - return NULL; - } - return method_tls_corrupt; -} -*/ +const BIO_METHOD* method = GetBioMethod(); BIO* NodeBIO::New() { // The const_cast doesn't violate const correctness. OpenSSL's usage of @@ -97,7 +78,7 @@ void NodeBIO::AssignEnvironment(Environment* env) { } -int NodeBIO::New(BIO* bio) { +static int New(BIO* bio) { BIO_set_data(bio, new NodeBIO()); // XXX Why am I doing it?! @@ -108,13 +89,13 @@ int NodeBIO::New(BIO* bio) { } -int NodeBIO::Free(BIO* bio) { +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 FromBIO(bio); + delete NodeBIO::FromBIO(bio); BIO_set_data(bio, nullptr); } } @@ -123,16 +104,17 @@ int NodeBIO::Free(BIO* bio) { } -int NodeBIO::Read(BIO* bio, char* out, int len) { +static int Read(BIO* bio, char* out, int len) { int bytes; + NodeBIO* nbio = NodeBIO::FromBIO(bio); + BIO_clear_retry_flags(bio); - bytes = FromBIO(bio)->Read(out, len); + bytes = nbio->Read(out, len); if (bytes == 0) { - //bytes = bio->num; - //if (bytes != 0) { - if (BIO_should_retry(bio)) { + bytes = nbio->eof_return(); + if (bytes != 0) { BIO_set_retry_read(bio); } } @@ -174,22 +156,22 @@ size_t NodeBIO::PeekMultiple(char** out, size_t* size, size_t* count) { } -int NodeBIO::Write(BIO* bio, const char* data, int len) { +static int Write(BIO* bio, const char* data, int len) { BIO_clear_retry_flags(bio); - FromBIO(bio)->Write(data, len); + NodeBIO::FromBIO(bio)->Write(data, len); return len; } -int NodeBIO::Puts(BIO* bio, const char* str) { +static int Puts(BIO* bio, const char* str) { return Write(bio, str, strlen(str)); } -int NodeBIO::Gets(BIO* bio, char* out, int size) { - NodeBIO* nbio = FromBIO(bio); +static int Gets(BIO* bio, char* out, int size) { + NodeBIO* nbio = NodeBIO::FromBIO(bio); if (nbio->Length() == 0) return 0; @@ -213,12 +195,12 @@ int NodeBIO::Gets(BIO* bio, char* out, int size) { } -long NodeBIO::Ctrl(BIO* bio, int cmd, long num, // NOLINT(runtime/int) - void* ptr) { +static long Ctrl(BIO* bio, int cmd, long num, // NOLINT(runtime/int) + void* ptr) { NodeBIO* nbio; long ret; // NOLINT(runtime/int) - nbio = FromBIO(bio); + nbio = NodeBIO::FromBIO(bio); ret = 1; switch (cmd) { @@ -229,7 +211,8 @@ long NodeBIO::Ctrl(BIO* bio, int cmd, long num, // NOLINT(runtime/int) ret = nbio->Length() == 0; break; case BIO_C_SET_BUF_MEM_EOF_RETURN: - BIO_set_mem_eof_return(bio, num); + //BIO_set_mem_eof_return(bio, num); + nbio->set_eof_return(num); break; case BIO_CTRL_INFO: ret = nbio->Length(); 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 index 230c9483fb9f60..b67b22d69b7600 100644 --- a/src/crypto_impl/openssl/1_1_0f/node_crypto_bio.h +++ b/src/crypto_impl/openssl/1_1_0f/node_crypto_bio.h @@ -38,6 +38,7 @@ class NodeBIO { NodeBIO() : env_(nullptr), initial_(kInitialBufferLength), length_(0), + eof_return_(-1), read_head_(nullptr), write_head_(nullptr) { } @@ -100,28 +101,24 @@ class NodeBIO { 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: - 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); - // Enough to handle the most of the client hellos static const size_t kInitialBufferLength = 1024; static const size_t kThroughputBufferLength = 16384; - static const BIO_METHOD* CreateBioMethod(); - static const BIO_METHOD* method; - class Buffer { public: Buffer(Environment* env, size_t len) : env_(env), @@ -153,6 +150,7 @@ class NodeBIO { Environment* env_; size_t initial_; size_t length_; + int eof_return_; Buffer* read_head_; Buffer* write_head_; }; 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.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 da2147d53ce88d..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); }); } 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-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-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 195ca529395fba..1ed6ab83a91bd6 100644 --- a/test/parallel/test-tls-ecdh-disable.js +++ b/test/parallel/test-tls-ecdh-disable.js @@ -46,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 }; @@ -57,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 fe0c4fe7eabe14..206f2d9312e8d1 100644 --- a/test/parallel/test-tls-no-sslv3.js +++ b/test/parallel/test-tls-no-sslv3.js @@ -34,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 e5c3a419bc6099..38925457b9e8c3 100644 --- a/test/parallel/test-tls-set-ciphers.js +++ b/test/parallel/test-tls-set-ciphers.js @@ -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) { From 5252a4f69b76beed00c2055929b4de5e205ca9ed Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Tue, 13 Jun 2017 15:14:25 +0200 Subject: [PATCH 6/7] move __attributes__ to after function signature --- src/node_crypto.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node_crypto.h b/src/node_crypto.h index c9ddaf56bd440a..b5282bc417befa 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -9,8 +9,8 @@ namespace crypto { class Crypto { public: virtual ~Crypto() {} - __attribute__((constructor)) virtual void RegisterCrypto(); - __attribute__((destructor)) virtual void UnregisterCrypto(); + virtual void RegisterCrypto() __attribute__((constructor)); + virtual void UnregisterCrypto() __attribute__((destructor)); /** * Allows a crypto implementation to do any additional initializations * required. From 8ee2cd1d9755723b99e67f52134ddfc8f703ef00 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Wed, 14 Jun 2017 09:37:29 +0200 Subject: [PATCH 7/7] remove ctor/dtor __attributes__ --- src/crypto_impl/openssl/1_0_2e/openssl_1_0_2e.cc | 16 +++------------- src/crypto_impl/openssl/1_1_0f/openssl_1_1_0f.cc | 16 +++------------- src/node_crypto.h | 5 +++-- src/node_crypto_factory.cc | 3 ++- src/node_crypto_factory.h | 2 +- 5 files changed, 12 insertions(+), 30 deletions(-) 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 index 9e780b0133aeea..a75834669fd110 100644 --- 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 @@ -6,21 +6,11 @@ namespace node { namespace crypto { -constexpr char version_[] = "1.0.2e"; -constexpr char typeversion_[] = "openssl_1_0_2e"; +const std::string id_ = CryptoFactory::Register("openssl_1_0_2e", + []() -> Crypto* { return new OpenSSL(); }); const std::string OpenSSL::Version() { - return version_; -} - -void Crypto::RegisterCrypto() { - CryptoFactory::Register(typeversion_, []() -> Crypto* { - return new OpenSSL(); - }); -} - -void Crypto::UnregisterCrypto() { - CryptoFactory::Unregister(typeversion_); + return "1.0.2e"; } bool OpenSSL::HasSNI() { 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 index 7213ee9ad6ce68..f34ab8855e8546 100644 --- 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 @@ -5,21 +5,11 @@ namespace node { namespace crypto { -constexpr char version_[] = "1.1.0f"; -constexpr char typeversion_[] = "openssl_1_1_0f"; +const std::string id_ = CryptoFactory::Register("openssl_1_1_0f", + []() -> Crypto* { return new OpenSSL(); }); const std::string OpenSSL::Version() { - return version_; -} - -void Crypto::RegisterCrypto() { - CryptoFactory::Register(typeversion_, []() -> Crypto* { - return new OpenSSL(); - }); -} - -void Crypto::UnregisterCrypto() { - CryptoFactory::Unregister(typeversion_); + return "1.1.0f"; } bool OpenSSL::HasSNI() { diff --git a/src/node_crypto.h b/src/node_crypto.h index b5282bc417befa..4e95aaeda9758b 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -9,8 +9,6 @@ namespace crypto { class Crypto { public: virtual ~Crypto() {} - virtual void RegisterCrypto() __attribute__((constructor)); - virtual void UnregisterCrypto() __attribute__((destructor)); /** * Allows a crypto implementation to do any additional initializations * required. @@ -48,6 +46,9 @@ class Crypto { * Returns true if Online Certificate Status Protocol is supported. */ virtual bool HasOCSP() = 0; + + protected: + static std::string id_; }; } // namespace crypto diff --git a/src/node_crypto_factory.cc b/src/node_crypto_factory.cc index 2a7ef34f8731da..91ccfe58afed32 100644 --- a/src/node_crypto_factory.cc +++ b/src/node_crypto_factory.cc @@ -12,12 +12,13 @@ CryptoFactory::CallbackMap CryptoFactory::crypto_libs; typedef std::pair> CryptoPair; -void CryptoFactory::Register(const std::string& version, +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) { diff --git a/src/node_crypto_factory.h b/src/node_crypto_factory.h index 68aed93db3dcc6..e416f6c58190f0 100644 --- a/src/node_crypto_factory.h +++ b/src/node_crypto_factory.h @@ -18,7 +18,7 @@ class CryptoFactory { /** * Registers a Crypto implementation for the specified version. */ - static void Register(const std::string& version, CreateCallback cb); + static std::string Register(const std::string& version, CreateCallback cb); /** * Unregisters a the Crypto implementation identified with version. */