@@ -1319,6 +1319,8 @@ Local<FunctionTemplate> SecureContext::GetConstructorTemplate(
13191319 SetProtoMethod (isolate, tmpl, " setOptions" , SetOptions);
13201320 SetProtoMethod (isolate, tmpl, " setSessionIdContext" , SetSessionIdContext);
13211321 SetProtoMethod (isolate, tmpl, " setSessionTimeout" , SetSessionTimeout);
1322+ SetProtoMethod (
1323+ isolate, tmpl, " setCertificateCompression" , SetCertificateCompression);
13221324 SetProtoMethod (isolate, tmpl, " close" , Close);
13231325 SetProtoMethod (isolate, tmpl, " loadPKCS12" , LoadPKCS12);
13241326 SetProtoMethod (isolate, tmpl, " setTicketKeys" , SetTicketKeys);
@@ -1407,6 +1409,7 @@ void SecureContext::RegisterExternalReferences(
14071409 registry->Register (SetOptions);
14081410 registry->Register (SetSessionIdContext);
14091411 registry->Register (SetSessionTimeout);
1412+ registry->Register (SetCertificateCompression);
14101413 registry->Register (Close);
14111414 registry->Register (LoadPKCS12);
14121415 registry->Register (SetTicketKeys);
@@ -1565,6 +1568,14 @@ void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
15651568 env->external_memory_accounter ()->Increase (env->isolate (), kExternalSize );
15661569 SSL_CTX_set_app_data (sc->ctx_ .get (), sc);
15671570
1571+ // OpenSSL populates cert_comp_prefs with all available algorithms by
1572+ // default when compression libraries are linked. Clear them so that
1573+ // certificate compression (RFC 8879) is always opt-in for now, via
1574+ // the certificateCompression option.
1575+ #ifndef OPENSSL_NO_COMP_ALG
1576+ SSL_CTX_set1_cert_comp_preference (sc->ctx_ .get (), nullptr , 0 );
1577+ #endif
1578+
15681579 // Disable SSLv2 in the case when method == TLS_method() and the
15691580 // cipher list contains SSLv2 ciphers (not the default, should be rare.)
15701581 // The bundled OpenSSL doesn't have SSLv2 support but the system OpenSSL may.
@@ -2067,6 +2078,84 @@ void SecureContext::SetSessionTimeout(const FunctionCallbackInfo<Value>& args) {
20672078 SSL_CTX_set_timeout (sc->ctx_ .get (), sessionTimeout);
20682079}
20692080
2081+ void SecureContext::SetCertificateCompression (
2082+ const FunctionCallbackInfo<Value>& args) {
2083+ SecureContext* sc;
2084+ ASSIGN_OR_RETURN_UNWRAP (&sc, args.This ());
2085+ Environment* env = sc->env ();
2086+
2087+ CHECK_GE (args.Length (), 1 );
2088+ CHECK (args[0 ]->IsArray ());
2089+
2090+ Local<Array> arr = args[0 ].As <Array>();
2091+ uint32_t len = arr->Length ();
2092+
2093+ if (len == 0 || len > TLSEXT_comp_cert_limit) {
2094+ return THROW_ERR_INVALID_ARG_VALUE (
2095+ env, " certificateCompression must contain 1 to 3 algorithm names" );
2096+ }
2097+
2098+ #ifndef OPENSSL_NO_COMP_ALG
2099+ int algs[TLSEXT_comp_cert_limit];
2100+ for (uint32_t i = 0 ; i < len; i++) {
2101+ Local<Value> val;
2102+ if (!arr->Get (env->context (), i).ToLocal (&val) || !val->IsString ()) {
2103+ return THROW_ERR_INVALID_ARG_VALUE (
2104+ env, " certificateCompression entries must be strings" );
2105+ }
2106+ Utf8Value name (env->isolate (), val);
2107+ if (strcmp (*name, " zlib" ) == 0 ) {
2108+ algs[i] = TLSEXT_comp_cert_zlib;
2109+ } else if (strcmp (*name, " brotli" ) == 0 ) {
2110+ algs[i] = TLSEXT_comp_cert_brotli;
2111+ } else if (strcmp (*name, " zstd" ) == 0 ) {
2112+ algs[i] = TLSEXT_comp_cert_zstd;
2113+ } else {
2114+ return THROW_ERR_INVALID_ARG_VALUE (
2115+ env,
2116+ " certificateCompression algorithm must be 'zlib', 'brotli', or "
2117+ " 'zstd'" );
2118+ }
2119+ }
2120+ if (!SSL_CTX_set1_cert_comp_preference (
2121+ sc->ctx_ .get (), algs, static_cast <size_t >(len))) {
2122+ return THROW_ERR_CRYPTO_OPERATION_FAILED (
2123+ env, " Failed to set certificate compression preference" );
2124+ }
2125+
2126+ // Pre-compress the loaded certificate(s) for all supported algorithms, where
2127+ // 0 arg means 'compress with all algorithms in the preference list'.
2128+ // Returns 0 when no certificate is loaded (e.g. client-only context) or
2129+ // when compression did not reduce size — both are non-fatal.
2130+ SSL_CTX_compress_certs (sc->ctx_ .get (), 0 );
2131+
2132+ // Store preferences for propagation during SNI context switches.
2133+ memcpy (sc->cert_comp_prefs_ , algs, sizeof (int ) * len);
2134+ sc->cert_comp_prefs_len_ = len;
2135+
2136+ // Cache pre-compressed cert data for SNI context switches.
2137+ // setSniContext uses SSL_use_certificate which doesn't carry comp_cert data,
2138+ // so we extract it here and re-apply via SSL_set1_compressed_cert later.
2139+ sc->compressed_certs_ .clear ();
2140+ for (uint32_t i = 0 ; i < len; i++) {
2141+ unsigned char * data = nullptr ;
2142+ size_t orig_len = 0 ;
2143+ size_t comp_len =
2144+ SSL_CTX_get1_compressed_cert (sc->ctx_ .get (), algs[i], &data, &orig_len);
2145+ if (comp_len > 0 && data != nullptr ) {
2146+ sc->compressed_certs_ .push_back (
2147+ {algs[i],
2148+ std::vector<unsigned char >(data, data + comp_len),
2149+ orig_len});
2150+ OPENSSL_free (data);
2151+ }
2152+ }
2153+ #else
2154+ return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION (
2155+ env, " Certificate compression is not supported by this OpenSSL build" );
2156+ #endif
2157+ }
2158+
20702159void SecureContext::Close (const FunctionCallbackInfo<Value>& args) {
20712160 SecureContext* sc;
20722161 ASSIGN_OR_RETURN_UNWRAP (&sc, args.This ());
0 commit comments