1212#include " util.h"
1313#include " v8.h"
1414
15- #include < openssl/x509 .h>
15+ #include < openssl/comp .h>
1616#include < openssl/pkcs12.h>
1717#include < openssl/rand.h>
18+ #include < openssl/x509.h>
1819#ifndef OPENSSL_NO_ENGINE
1920#include < openssl/engine.h>
2021#endif // !OPENSSL_NO_ENGINE
@@ -1308,6 +1309,8 @@ Local<FunctionTemplate> SecureContext::GetConstructorTemplate(
13081309 SetProtoMethod (isolate, tmpl, " setOptions" , SetOptions);
13091310 SetProtoMethod (isolate, tmpl, " setSessionIdContext" , SetSessionIdContext);
13101311 SetProtoMethod (isolate, tmpl, " setSessionTimeout" , SetSessionTimeout);
1312+ SetProtoMethod (
1313+ isolate, tmpl, " setCertificateCompression" , SetCertificateCompression);
13111314 SetProtoMethod (isolate, tmpl, " close" , Close);
13121315 SetProtoMethod (isolate, tmpl, " loadPKCS12" , LoadPKCS12);
13131316 SetProtoMethod (isolate, tmpl, " setTicketKeys" , SetTicketKeys);
@@ -1372,6 +1375,10 @@ void SecureContext::Initialize(Environment* env, Local<Object> target) {
13721375 target,
13731376 " startLoadingCertificatesOffThread" ,
13741377 StartLoadingCertificatesOffThread);
1378+ SetMethodNoSideEffect (context,
1379+ target,
1380+ " getCertificateCompressionAlgorithms" ,
1381+ GetCertificateCompressionAlgorithms);
13751382}
13761383
13771384void SecureContext::RegisterExternalReferences (
@@ -1396,6 +1403,7 @@ void SecureContext::RegisterExternalReferences(
13961403 registry->Register (SetOptions);
13971404 registry->Register (SetSessionIdContext);
13981405 registry->Register (SetSessionTimeout);
1406+ registry->Register (SetCertificateCompression);
13991407 registry->Register (Close);
14001408 registry->Register (LoadPKCS12);
14011409 registry->Register (SetTicketKeys);
@@ -1417,6 +1425,7 @@ void SecureContext::RegisterExternalReferences(
14171425 registry->Register (ResetRootCertStore);
14181426 registry->Register (GetUserRootCertificates);
14191427 registry->Register (StartLoadingCertificatesOffThread);
1428+ registry->Register (GetCertificateCompressionAlgorithms);
14201429}
14211430
14221431SecureContext* SecureContext::Create (Environment* env) {
@@ -1554,6 +1563,14 @@ void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
15541563 env->external_memory_accounter ()->Increase (env->isolate (), kExternalSize );
15551564 SSL_CTX_set_app_data (sc->ctx_ .get (), sc);
15561565
1566+ // OpenSSL populates cert_comp_prefs with all available algorithms by
1567+ // default when compression libraries are linked. Clear them so that
1568+ // certificate compression (RFC 8879) is always opt-in for now, via
1569+ // the certificateCompression option.
1570+ #ifdef NODE_OPENSSL_HAS_CERT_COMP
1571+ SSL_CTX_set1_cert_comp_preference (sc->ctx_ .get (), nullptr , 0 );
1572+ #endif
1573+
15571574 // Disable SSLv2 in the case when method == TLS_method() and the
15581575 // cipher list contains SSLv2 ciphers (not the default, should be rare.)
15591576 // The bundled OpenSSL doesn't have SSLv2 support but the system OpenSSL may.
@@ -2059,6 +2076,100 @@ void SecureContext::SetSessionTimeout(const FunctionCallbackInfo<Value>& args) {
20592076 SSL_CTX_set_timeout (sc->ctx_ .get (), sessionTimeout);
20602077}
20612078
2079+ void SecureContext::SetCertificateCompression (
2080+ const FunctionCallbackInfo<Value>& args) {
2081+ SecureContext* sc;
2082+ ASSIGN_OR_RETURN_UNWRAP (&sc, args.This ());
2083+ Environment* env = sc->env ();
2084+
2085+ CHECK_GE (args.Length (), 1 );
2086+ CHECK (args[0 ]->IsUint32 ());
2087+
2088+ // Cert compression requires TLS 1.3:
2089+ long max_proto = // NOLINT(runtime/int)
2090+ SSL_CTX_get_max_proto_version (sc->ctx_ .get ());
2091+ if (max_proto != 0 && max_proto < TLS1_3_VERSION ) {
2092+ return THROW_ERR_INVALID_ARG_VALUE (
2093+ env,
2094+ " certificateCompression requires a TLS protocol range that includes "
2095+ " TLSv1.3" );
2096+ }
2097+
2098+ #ifdef NODE_OPENSSL_HAS_CERT_COMP
2099+ // JS packs (length | alg0<<8 | alg1<<16 | alg2<<24) into a single Uint32.
2100+ // IDs match TLSEXT_comp_cert_zlib (1), _brotli (2), _zstd (3).
2101+ uint32_t packed = args[0 ].As <v8::Uint32>()->Value ();
2102+ size_t len = packed & 0xff ;
2103+
2104+ // TLSEXT_comp_cert_limit is the limit for a zero-terminated algs array,
2105+ // total number of available algs is one fewer.
2106+ constexpr size_t kMaxCompAlgs = TLSEXT_comp_cert_limit - 1 ;
2107+ if (len == 0 || len > kMaxCompAlgs ) {
2108+ return THROW_ERR_INVALID_ARG_VALUE (
2109+ env,
2110+ " certificateCompression must specify fewer than %d algorithms" ,
2111+ static_cast <int >(kMaxCompAlgs ));
2112+ }
2113+
2114+ int algs[kMaxCompAlgs ];
2115+ for (size_t i = 0 ; i < len; i++) {
2116+ algs[i] = (packed >> (8 * (i + 1 ))) & 0xff ;
2117+ }
2118+ if (!SSL_CTX_set1_cert_comp_preference (
2119+ sc->ctx_ .get (), algs, static_cast <size_t >(len))) {
2120+ return THROW_ERR_CRYPTO_OPERATION_FAILED (
2121+ env, " Failed to set certificate compression preference" );
2122+ }
2123+
2124+ // Pre-compress the loaded certificate(s) for all supported algorithms.
2125+ // Returns 0 when no certificate is loaded (e.g. client-only context) or
2126+ // when compression did not reduce size - both are non-fatal.
2127+ constexpr int kCompressAllAlgs = 0 ;
2128+ SSL_CTX_compress_certs (sc->ctx_ .get (), kCompressAllAlgs );
2129+
2130+ // Store preferences for propagation during SNI context switches.
2131+ memcpy (sc->cert_comp_prefs_ , algs, sizeof (int ) * len);
2132+ sc->cert_comp_prefs_len_ = len;
2133+
2134+ // Cache pre-compressed cert data for SNI context switches.
2135+ // setSniContext uses SSL_use_certificate which doesn't carry comp_cert data,
2136+ // so we extract it here and re-apply via SSL_set1_compressed_cert later.
2137+ sc->compressed_certs_ .clear ();
2138+ for (size_t i = 0 ; i < len; i++) {
2139+ unsigned char * data = nullptr ;
2140+ size_t orig_len = 0 ;
2141+ size_t comp_len =
2142+ SSL_CTX_get1_compressed_cert (sc->ctx_ .get (), algs[i], &data, &orig_len);
2143+ ncrypto::DataPointer comp (data, comp_len);
2144+ if (comp_len > 0 && data != nullptr ) {
2145+ sc->compressed_certs_ .push_back (
2146+ {algs[i],
2147+ std::vector<unsigned char >(data, data + comp_len),
2148+ orig_len});
2149+ }
2150+ }
2151+ #else
2152+ return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION (
2153+ env, " Certificate compression is not supported by this OpenSSL build" );
2154+ #endif
2155+ }
2156+
2157+ void SecureContext::GetCertificateCompressionAlgorithms (
2158+ const FunctionCallbackInfo<Value>& args) {
2159+ Environment* env = Environment::GetCurrent (args);
2160+ LocalVector<Value> algs (env->isolate ());
2161+ #ifdef NODE_OPENSSL_HAS_CERT_COMP
2162+ if (BIO_f_zlib () != nullptr )
2163+ algs.push_back (FIXED_ONE_BYTE_STRING (env->isolate (), " zlib" ));
2164+ if (BIO_f_brotli () != nullptr )
2165+ algs.push_back (FIXED_ONE_BYTE_STRING (env->isolate (), " brotli" ));
2166+ if (BIO_f_zstd () != nullptr )
2167+ algs.push_back (FIXED_ONE_BYTE_STRING (env->isolate (), " zstd" ));
2168+ #endif
2169+ args.GetReturnValue ().Set (
2170+ Array::New (env->isolate (), algs.data (), algs.size ()));
2171+ }
2172+
20622173void SecureContext::Close (const FunctionCallbackInfo<Value>& args) {
20632174 SecureContext* sc;
20642175 ASSIGN_OR_RETURN_UNWRAP (&sc, args.This ());
0 commit comments