diff --git a/CHANGES.md b/CHANGES.md index fac9d80cc0..2fc80041a3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,11 @@ OpenSSL 3.2 ### Changes between 3.0 and 3.2 [xx XXX xxxx] + * Add support for certificate compression (RFC8879), including + library support for Brotli and Zstandard compression. + + *Todd Short* + * Add the ability to add custom attributes to PKCS12 files. Add a new API PKCS12_create_ex2, identical to the existing PKCS12_create_ex but allows for a user specified callback and optional argument. diff --git a/NEWS.md b/NEWS.md index 3df3bbc007..b934aaa0a8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -21,6 +21,8 @@ OpenSSL 3.2 ### Major changes between OpenSSL 3.0 and OpenSSL 3.2 [under development] + * Added support for certificate compression (RFC8879), including + library support for Brotli and Zstandard compression. * Subject or issuer names in X.509 objects are now displayed as UTF-8 strings by default. * TCP Fast Open (RFC7413) support is available on Linux, macOS, and FreeBSD diff --git a/apps/include/opt.h b/apps/include/opt.h index 849e10c09e..787dac5468 100644 --- a/apps/include/opt.h +++ b/apps/include/opt.h @@ -164,7 +164,10 @@ OPT_S_RECORD_PADDING, OPT_S_DEBUGBROKE, OPT_S_COMP, \ OPT_S_MINPROTO, OPT_S_MAXPROTO, \ OPT_S_NO_RENEGOTIATION, OPT_S_NO_MIDDLEBOX, OPT_S_NO_ETM, \ - OPT_S_NO_EMS, OPT_S__LAST + OPT_S_NO_EMS, \ + OPT_S_NO_TX_CERT_COMP, \ + OPT_S_NO_RX_CERT_COMP, \ + OPT_S__LAST # define OPT_S_OPTIONS \ OPT_SECTION("TLS/SSL"), \ @@ -176,6 +179,8 @@ {"bugs", OPT_S_BUGS, '-', "Turn on SSL bug compatibility"}, \ {"no_comp", OPT_S_NO_COMP, '-', "Disable SSL/TLS compression (default)" }, \ {"comp", OPT_S_COMP, '-', "Use SSL/TLS-level compression" }, \ + {"no_tx_cert_comp", OPT_S_NO_TX_CERT_COMP, '-', "Disable sending TLSv1.3 compressed certificates" }, \ + {"no_rx_cert_comp", OPT_S_NO_RX_CERT_COMP, '-', "Disable receiving TLSv1.3 compressed certificates" }, \ {"no_ticket", OPT_S_NOTICKET, '-', \ "Disable use of TLS session tickets"}, \ {"serverpref", OPT_S_SERVERPREF, '-', "Use server's cipher preferences"}, \ @@ -233,6 +238,8 @@ case OPT_S_BUGS: \ case OPT_S_NO_COMP: \ case OPT_S_COMP: \ + case OPT_S_NO_TX_CERT_COMP: \ + case OPT_S_NO_RX_CERT_COMP: \ case OPT_S_NOTICKET: \ case OPT_S_SERVERPREF: \ case OPT_S_LEGACYRENEG: \ diff --git a/apps/lib/s_cb.c b/apps/lib/s_cb.c index 8f20113cd3..8325e7371f 100644 --- a/apps/lib/s_cb.c +++ b/apps/lib/s_cb.c @@ -559,6 +559,7 @@ static STRINT_PAIR handshakes[] = { {", CertificateStatus", SSL3_MT_CERTIFICATE_STATUS}, {", SupplementalData", SSL3_MT_SUPPLEMENTAL_DATA}, {", KeyUpdate", SSL3_MT_KEY_UPDATE}, + {", CompressedCertificate", SSL3_MT_COMPRESSED_CERTIFICATE}, #ifndef OPENSSL_NO_NEXTPROTONEG {", NextProto", SSL3_MT_NEXT_PROTO}, #endif @@ -685,6 +686,7 @@ static STRINT_PAIR tlsext_types[] = { #ifdef TLSEXT_TYPE_extended_master_secret {"extended master secret", TLSEXT_TYPE_extended_master_secret}, #endif + {"compress certificate", TLSEXT_TYPE_compress_certificate}, {"key share", TLSEXT_TYPE_key_share}, {"supported versions", TLSEXT_TYPE_supported_versions}, {"psk", TLSEXT_TYPE_psk}, diff --git a/apps/s_server.c b/apps/s_server.c index 8a573d4414..f519505ade 100644 --- a/apps/s_server.c +++ b/apps/s_server.c @@ -716,7 +716,7 @@ typedef enum OPTION_choice { OPT_KEYLOG_FILE, OPT_MAX_EARLY, OPT_RECV_MAX_EARLY, OPT_EARLY_DATA, OPT_S_NUM_TICKETS, OPT_ANTI_REPLAY, OPT_NO_ANTI_REPLAY, OPT_SCTP_LABEL_BUG, OPT_HTTP_SERVER_BINMODE, OPT_NOCANAMES, OPT_IGNORE_UNEXPECTED_EOF, OPT_KTLS, - OPT_TFO, + OPT_TFO, OPT_CERT_COMP, OPT_R_ENUM, OPT_S_ENUM, OPT_V_ENUM, @@ -843,6 +843,9 @@ const OPTIONS s_server_options[] = { "No verify output except verify errors"}, {"ign_eof", OPT_IGN_EOF, '-', "Ignore input EOF (default when -quiet)"}, {"no_ign_eof", OPT_NO_IGN_EOF, '-', "Do not ignore input EOF"}, +#ifndef OPENSSL_NO_COMP_ALG + {"cert_comp", OPT_CERT_COMP, '-', "Pre-compress server certificates"}, +#endif #ifndef OPENSSL_NO_OCSP OPT_SECTION("OCSP"), @@ -1061,6 +1064,7 @@ int s_server_main(int argc, char *argv[]) int enable_ktls = 0; #endif int tfo = 0; + int cert_comp = 0; /* Init of few remaining global variables */ local_argc = argc; @@ -1658,6 +1662,9 @@ int s_server_main(int argc, char *argv[]) case OPT_TFO: tfo = 1; break; + case OPT_CERT_COMP: + cert_comp = 1; + break; } } @@ -2243,6 +2250,14 @@ int s_server_main(int argc, char *argv[]) if (recv_max_early_data >= 0) SSL_CTX_set_recv_max_early_data(ctx, recv_max_early_data); + if (cert_comp) { + BIO_printf(bio_s_out, "Compressing certificates\n"); + if (!SSL_CTX_compress_certs(ctx, 0)) + BIO_printf(bio_s_out, "Error compressing certs on ctx\n"); + if (ctx2 != NULL && !SSL_CTX_compress_certs(ctx2, 0)) + BIO_printf(bio_s_out, "Error compressing certs on ctx2\n"); + } + if (rev) server_cb = rev_body; else if (www) diff --git a/crypto/comp/c_brotli.c b/crypto/comp/c_brotli.c index ace6f221b8..377ea2b8d0 100644 --- a/crypto/comp/c_brotli.c +++ b/crypto/comp/c_brotli.c @@ -419,10 +419,10 @@ static const BIO_METHOD bio_meth_brotli = { const BIO_METHOD *BIO_f_brotli(void) { #ifndef OPENSSL_NO_BROTLI - return &bio_meth_brotli; -#else - return NULL; + if (RUN_ONCE(&brotli_once, ossl_comp_brotli_init)) + return &bio_meth_brotli; #endif + return NULL; } #ifndef OPENSSL_NO_BROTLI diff --git a/crypto/comp/c_zstd.c b/crypto/comp/c_zstd.c index 99d326219e..15b826c589 100644 --- a/crypto/comp/c_zstd.c +++ b/crypto/comp/c_zstd.c @@ -481,10 +481,10 @@ static const BIO_METHOD bio_meth_zstd = { const BIO_METHOD *BIO_f_zstd(void) { #ifndef OPENSSL_NO_ZSTD - return &bio_meth_zstd; -#else - return NULL; + if (RUN_ONCE(&zstd_once, ossl_comp_zstd_init)) + return &bio_meth_zstd; #endif + return NULL; } #ifndef OPENSSL_NO_ZSTD diff --git a/crypto/comp/comp_err.c b/crypto/comp/comp_err.c index 10a9e66860..2345da693e 100644 --- a/crypto/comp/comp_err.c +++ b/crypto/comp/comp_err.c @@ -19,12 +19,8 @@ static const ERR_STRING_DATA COMP_str_reasons[] = { {ERR_PACK(ERR_LIB_COMP, 0, COMP_R_BROTLI_DECODE_ERROR), "brotli decode error"}, - {ERR_PACK(ERR_LIB_COMP, 0, COMP_R_BROTLI_DEFLATE_ERROR), - "brotli deflate error"}, {ERR_PACK(ERR_LIB_COMP, 0, COMP_R_BROTLI_ENCODE_ERROR), "brotli encode error"}, - {ERR_PACK(ERR_LIB_COMP, 0, COMP_R_BROTLI_INFLATE_ERROR), - "brotli inflate error"}, {ERR_PACK(ERR_LIB_COMP, 0, COMP_R_BROTLI_NOT_SUPPORTED), "brotli not supported"}, {ERR_PACK(ERR_LIB_COMP, 0, COMP_R_ZLIB_DEFLATE_ERROR), diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt index ef02a097e4..653b775330 100644 --- a/crypto/err/openssl.txt +++ b/crypto/err/openssl.txt @@ -1271,6 +1271,7 @@ SSL_R_AT_LEAST_TLS_1_2_NEEDED_IN_SUITEB_MODE:158:\ at least (D)TLS 1.2 needed in Suite B mode SSL_R_BAD_CHANGE_CIPHER_SPEC:103:bad change cipher spec SSL_R_BAD_CIPHER:186:bad cipher +SSL_R_BAD_COMPRESSION_ALGORITHM:326:bad compression algorithm SSL_R_BAD_DATA:390:bad data SSL_R_BAD_DATA_RETURNED_BY_CALLBACK:106:bad data returned by callback SSL_R_BAD_DECOMPRESSION:107:bad decompression diff --git a/doc/build.info b/doc/build.info index 8884b0dd5f..71c7f3aa40 100644 --- a/doc/build.info +++ b/doc/build.info @@ -2167,6 +2167,10 @@ DEPEND[html/man3/SSL_CTX_set0_CA_list.html]=man3/SSL_CTX_set0_CA_list.pod GENERATE[html/man3/SSL_CTX_set0_CA_list.html]=man3/SSL_CTX_set0_CA_list.pod DEPEND[man/man3/SSL_CTX_set0_CA_list.3]=man3/SSL_CTX_set0_CA_list.pod GENERATE[man/man3/SSL_CTX_set0_CA_list.3]=man3/SSL_CTX_set0_CA_list.pod +DEPEND[html/man3/SSL_CTX_set1_cert_comp_preference.html]=man3/SSL_CTX_set1_cert_comp_preference.pod +GENERATE[html/man3/SSL_CTX_set1_cert_comp_preference.html]=man3/SSL_CTX_set1_cert_comp_preference.pod +DEPEND[man/man3/SSL_CTX_set1_cert_comp_preference.3]=man3/SSL_CTX_set1_cert_comp_preference.pod +GENERATE[man/man3/SSL_CTX_set1_cert_comp_preference.3]=man3/SSL_CTX_set1_cert_comp_preference.pod DEPEND[html/man3/SSL_CTX_set1_curves.html]=man3/SSL_CTX_set1_curves.pod GENERATE[html/man3/SSL_CTX_set1_curves.html]=man3/SSL_CTX_set1_curves.pod DEPEND[man/man3/SSL_CTX_set1_curves.3]=man3/SSL_CTX_set1_curves.pod @@ -3313,6 +3317,7 @@ html/man3/SSL_CTX_sess_set_cache_size.html \ html/man3/SSL_CTX_sess_set_get_cb.html \ html/man3/SSL_CTX_sessions.html \ html/man3/SSL_CTX_set0_CA_list.html \ +html/man3/SSL_CTX_set1_cert_comp_preference.html \ html/man3/SSL_CTX_set1_curves.html \ html/man3/SSL_CTX_set1_sigalgs.html \ html/man3/SSL_CTX_set1_verify_cert_store.html \ @@ -3918,6 +3923,7 @@ man/man3/SSL_CTX_sess_set_cache_size.3 \ man/man3/SSL_CTX_sess_set_get_cb.3 \ man/man3/SSL_CTX_sessions.3 \ man/man3/SSL_CTX_set0_CA_list.3 \ +man/man3/SSL_CTX_set1_cert_comp_preference.3 \ man/man3/SSL_CTX_set1_curves.3 \ man/man3/SSL_CTX_set1_sigalgs.3 \ man/man3/SSL_CTX_set1_verify_cert_store.3 \ diff --git a/doc/man1/openssl-s_client.pod.in b/doc/man1/openssl-s_client.pod.in index 5f6f74c51d..86b9aff91e 100644 --- a/doc/man1/openssl-s_client.pod.in +++ b/doc/man1/openssl-s_client.pod.in @@ -83,6 +83,8 @@ B B [B<-read_buf>] [B<-ignore_unexpected_eof>] [B<-bugs>] +[B<-no_tx_cert_comp>] +[B<-no_rx_cert_comp>] [B<-comp>] [B<-no_comp>] [B<-brief>] @@ -601,6 +603,14 @@ For more information on shutting down a connection, see L. There are several known bugs in SSL and TLS implementations. Adding this option enables various workarounds. +=item B<-no_tx_cert_comp> + +Disables support for sending TLSv1.3 compressed certificates. + +=item B<-no_rx_cert_comp> + +Disables support for receiving TLSv1.3 compressed certificate. + =item B<-comp> Enables support for SSL/TLS compression. @@ -930,7 +940,9 @@ The B<-certform> option has become obsolete in OpenSSL 3.0.0 and has no effect. The B<-engine> option was deprecated in OpenSSL 3.0. -The -tfo option was added in OpenSSL 3.2. + +The B<-tfo>, B<-no_tx_cert_comp>, and B<-no_rx_cert_comp> options were added +in OpenSSL 3.2. =head1 COPYRIGHT diff --git a/doc/man1/openssl-s_server.pod.in b/doc/man1/openssl-s_server.pod.in index 8fa041c2fe..94f3b4b46c 100644 --- a/doc/man1/openssl-s_server.pod.in +++ b/doc/man1/openssl-s_server.pod.in @@ -92,6 +92,8 @@ B B [B<-naccept> I<+int>] [B<-read_buf> I<+int>] [B<-bugs>] +[B<-no_tx_cert_comp>] +[B<-no_rx_cert_comp>] [B<-no_comp>] [B<-comp>] [B<-no_ticket>] @@ -139,6 +141,7 @@ B B [B<-no_anti_replay>] [B<-num_tickets>] [B<-tfo>] +[B<-cert_comp>] {- $OpenSSL::safe::opt_name_synopsis -} {- $OpenSSL::safe::opt_version_synopsis -} {- $OpenSSL::safe::opt_v_synopsis -} @@ -604,6 +607,14 @@ further information). There are several known bugs in SSL and TLS implementations. Adding this option enables various workarounds. +=item B<-no_tx_cert_comp> + +Disables support for sending TLSv1.3 compressed certificates. + +=item B<-no_rx_cert_comp> + +Disables support for receiving TLSv1.3 compressed certificates. + =item B<-no_comp> Disable negotiation of TLS compression. @@ -820,6 +831,9 @@ data that was sent will be rejected. Enable acceptance of TCP Fast Open (RFC7413) connections. +=item B<-cert_comp> + +Pre-compresses certificates (RFC8879) that will be sent during the handshake. {- $OpenSSL::safe::opt_name_item -} @@ -947,7 +961,8 @@ The The B<-srpvfile>, B<-srpuserseed>, and B<-engine> option were deprecated in OpenSSL 3.0. -The -tfo option was added in OpenSSL 3.2. +The B<-tfo>, B<-no_tx_cert_comp>, and B<-no_rx_cert_comp> options were added +in OpenSSL 3.2. =head1 COPYRIGHT diff --git a/doc/man3/COMP_CTX_new.pod b/doc/man3/COMP_CTX_new.pod index 1fe1d691df..7e1c8c4a83 100644 --- a/doc/man3/COMP_CTX_new.pod +++ b/doc/man3/COMP_CTX_new.pod @@ -98,7 +98,7 @@ COMP_zstd_oneshot() returns a B for one-shot Zstandard compression. BIO_f_zlib(), BIO_f_brotli() BIO_f_zstd() each return a B that may be used to create a B via B to read and write compressed files or streams. The functions are only available if the corresponding algorithm is compiled into -the OpenSSL library. +the OpenSSL library. NULL may be returned if the algorithm fails to load dynamically. =head1 NOTES @@ -123,11 +123,12 @@ L functions. Compression is also used to support certificate compression as described in RFC8879 L. -It may be disabled via the SSL_OP_NO_CERTIFICATE_COMPRESSION option of -the L or L functions. +It may be disabled via the SSL_OP_NO_TX_CERTIFICATE_COMPRESSION and +SSL_OP_NO_RX_CERTIFICATE_COMPRESSION options of the +L or L functions. COMP_zlib(), COMP_brotli() and COMP_zstd() are stream-based compression methods. -Internal state (including compression dictionary) is maintained between calls. +Internal state (including compression dictionary) is maintained between calls. If an error is returned, the stream is corrupted, and should be closed. COMP_brotli_oneshot() and COMP_zstd_oneshot() are not stream-based. These @@ -152,7 +153,8 @@ bytes stored in the output buffer I. This may be 0. On failure, COMP_get_name() returns a B that must not be freed on success, or NULL on failure. -BIO_f_zlib(), BIO_f_brotli() and BIO_f_zstd() return a B. +BIO_f_zlib(), BIO_f_brotli() and BIO_f_zstd() return NULL on error, and +a B on success. =head1 SEE ALSO diff --git a/doc/man3/SSL_CONF_cmd.pod b/doc/man3/SSL_CONF_cmd.pod index 400bd223c6..c20df37e3b 100644 --- a/doc/man3/SSL_CONF_cmd.pod +++ b/doc/man3/SSL_CONF_cmd.pod @@ -162,6 +162,24 @@ This is a synonym for the B<-groups> command. This sets the temporary curve used for ephemeral ECDH modes. Only used by servers. +=item B<-tx_cert_comp> + +Enables support for sending TLSv1.3 compressed certificates. + +=item B<-no_tx_cert_comp> + +Disables support for sending TLSv1.3 compressed certificates. + +=item B<-rx_cert_comp> + +Enables support for receiving TLSv1.3 compressed certificates. + +=item B<-no_rx_cert_comp> + +Disables support for receiving TLSv1.3 compressed certificates. + +=item B<-comp> + The B argument is a curve name or the special value B which picks an appropriate curve based on client and server preferences. The curve can be either the B name (e.g. B) or an OpenSSL OID name @@ -535,6 +553,14 @@ B. B: Enable strict certificate checking. Equivalent to setting B with SSL_CTX_set_cert_flags(). +B: support sending compressed certificates, enabled by +default. Inverse of B: that is, +B<-TxCertificateCompression> is the same as setting B. + +B: support receiving compressed certificates, enabled by +default. Inverse of B: that is, +B<-RxCertificateCompression> is the same as setting B. + =item B The B argument is a comma separated list of flags to set. @@ -736,6 +762,9 @@ B and B were added in OpenSSL 1.1.1. The B option is no longer set by default from OpenSSL 3.0. +The B and B options were +added in OpenSSL 3.2. + =head1 COPYRIGHT Copyright 2012-2022 The OpenSSL Project Authors. All Rights Reserved. diff --git a/doc/man3/SSL_CTX_set1_cert_comp_preference.pod b/doc/man3/SSL_CTX_set1_cert_comp_preference.pod new file mode 100644 index 0000000000..2fe35873f7 --- /dev/null +++ b/doc/man3/SSL_CTX_set1_cert_comp_preference.pod @@ -0,0 +1,160 @@ +=pod + +=head1 NAME + +SSL_CTX_set1_cert_comp_preference, +SSL_set1_cert_comp_preference, +SSL_CTX_compress_certs, +SSL_compress_certs, +SSL_CTX_get1_compressed_cert, +SSL_get1_compressed_cert, +SSL_CTX_set1_compressed_cert, +SSL_set1_compressed_cert - Certificate compression functions + +=head1 SYNOPSIS + + #include + + int SSL_CTX_set1_cert_comp_preference(SSL_CTX *ctx, int *algs, size_t len); + int SSL_set1_cert_comp_preference(SSL *ssl, int *algs, size_t len); + + int SSL_CTX_compress_certs(SSL_CTX *ctx, int alg); + int SSL_compress_certs(SSL *ssl, int alg); + + size_t SSL_CTX_get1_compressed_cert(SSL_CTX *ctx, int alg, unsigned char **data, + size_t *orig_len); + size_t SSL_get1_compressed_cert(SSL *ssl, int alg, unsigned char **data, + size_t *orig_len); + + int SSL_CTX_set1_compressed_cert(SSL_CTX *ctx, int alg, + unsigned char *comp_data, + size_t comp_length, size_t orig_length); + int SSL_set1_compressed_cert(SSL *ssl, int alg, unsigned char *comp_data, + size_t comp_length, size_t orig_length); + + +=head1 DESCRIPTION + +These functions control the certificate compression feature. Certificate +compression is only available for TLSv1.3 as defined in RFC8879. + +SSL_CTX_set1_cert_comp_preference() and SSL_set1_cert_comp_preference() are used +to specify the preferred compression algorithms. The B argument is an array +of algorithms, and B is number of elements in the B array. Only +those algorithms enabled in the library will be accepted in B, unknown +algorithms in B are ignored. On an error, the preference order is left +unmodified. + +The following compression algorithms (B arguments) may be used: + +=over 4 + +=item * TLSEXT_comp_cert_brotli + +=item * TLSEXT_comp_cert_zlib + +=item * TLSEXT_comp_cert_zstd + +=back + +The above is also the default preference order. If a preference order is not +specified, then the default preference order is sent to the peer and the +received peer's preference order will be used when compressing a certificate. +Otherwise, the configured preference order is sent to the peer and is used +to filter the peer's preference order. + +SSL_CTX_compress_certs() and SSL_compress_certs() are used to pre-compress all +the configured certificates on an SSL_CTX/SSL object with algorithm B. If +B is 0, then the certificates are compressed with the algorithms specified +in the preference list. Calling these functions on a client SSL_CTX/SSL object +will result in an error, as only server certificates may be pre-compressed. + +SSL_CTX_get1_compressed_cert() and SSL_get1_compressed_cert() are used to get +the pre-compressed certificate most recently set that may be stored for later +use. Calling these functions on a client SSL_CTX/SSL object will result in an +error, as only server certificates may be pre-compressed. The B and +B arguments are required. + +The compressed certificate data may be passed to SSL_CTX_set1_compressed_cert() +or SSL_set1_compressed_cert() to provide a pre-compressed version of the +most recently set certificate. This pre-compressed certificate can only be used +by a server. + +=head1 NOTES + +Each side of the connection sends their compression algorithm preference list +to their peer indicating compressed certificate support. The received preference +list is filtered by the configured preference list (i.e. the intersection is +saved). As the default list includes all the enabled algorithms, not specifying +a preference will allow any enabled algorithm by the peer. The filtered peer's +preference order is used to determine what algorithm to use when sending a +compressed certificate. + +Only server certificates may be pre-compressed. Calling any of these functions +(except SSL_CTX_set1_cert_comp_preference()/SSL_set1_cert_comp_preference()) +on a client SSL_CTX/SSL object will return an error. Client certificates are +compressed on-demand as unique context data from the server is compressed along +with the certificate. + +For SSL_CTX_set1_cert_comp_preference() and SSL_set1_cert_comp_preference() +the B argument is the size of the B argument in bytes. + +The compressed certificate returned by SSL_CTX_get1_compressed_cert() and +SSL_get1_compressed_cert() is the last certificate set on the SSL_CTX/SSL object. +The certificate is copied by the function and the caller must free B<*data> via +OPENSSL_free(). + +The compressed certificate data set by SSL_CTX_set1_compressed_cert() and +SSL_set1_compressed_cert() is copied into the SSL_CTX/SSL object. + +SSL_CTX_compress_certs() and SSL_compress_certs() return an error under the +following conditions: + +=over 4 + +=item * If no certificates have been configured. + +=item * If the specified algorithm B is not enabled. + +=item * If B is 0 and no compression algorithms are enabled. + +=back + +Sending compressed certificates may be disabled on a connection via the +SSL_OP_NO_TX_CERTIFICATE_COMPRESSION option. Receiving compressed certificates +may be disabled on a connection via the SSL_OP_NO_RX_CERTIFICATE_COMPRESSION +option. + +=head1 RETURN VALUES + +SSL_CTX_set1_cert_comp_preference(), +SSL_set1_cert_comp_preference(), +SSL_CTX_compress_certs(), +SSL_compress_certs(), +SSL_CTX_set1_compressed_cert(), and +SSL_set1_compressed_cert() +return 1 for success and 0 on error. + +SSL_CTX_get1_compressed_cert() and +SSL_get1_compressed_cert() +return the length of the allocated memory on success and 0 on error. + +=head1 SEE ALSO + +L, +L + +=head1 HISTORY + +These functions were added in OpenSSL 3.2. + +=head1 COPYRIGHT + +Copyright 2022 The OpenSSL Project Authors. All Rights Reserved. + +Licensed under the Apache License 2.0 (the "License"). You may not use +this file except in compliance with the License. You can obtain a copy +in the file LICENSE in the source distribution or at +L. + +=cut diff --git a/doc/man3/SSL_CTX_set_options.pod b/doc/man3/SSL_CTX_set_options.pod index 08522522cd..1da057adb8 100644 --- a/doc/man3/SSL_CTX_set_options.pod +++ b/doc/man3/SSL_CTX_set_options.pod @@ -214,6 +214,22 @@ functionality is not required. Those applications can turn this feature off by setting this option. This is a server-side opton only. It is ignored by clients. +=item SSL_OP_NO_TX_CERTIFICATE_COMPRESSION + +Normally clients and servers will transparently attempt to negotiate the +RFC8879 certificate compression option on TLSv1.3 connections. + +If this option is set, the certificate compression extension is ignored +upon receipt and compressed certificates will not be sent to the peer. + +=item SSL_OP_NO_RX_CERTIFICATE_COMPRESSION + +Normally clients and servers will transparently attempt to negotiate the +RFC8879 certificate compression option on TLSv1.3 connections. + +If this option is set, the certificate compression extension will not be sent +and compressed certificates will not be accepted from the peer. + =item SSL_OP_NO_COMPRESSION Do not use compression even if it is supported. This option is set by default. diff --git a/include/openssl/comperr.h b/include/openssl/comperr.h index e075506e68..1948d37f1a 100644 --- a/include/openssl/comperr.h +++ b/include/openssl/comperr.h @@ -24,17 +24,15 @@ * COMP reason codes. */ # define COMP_R_BROTLI_DECODE_ERROR 102 -# define COMP_R_BROTLI_DEFLATE_ERROR 103 -# define COMP_R_BROTLI_ENCODE_ERROR 106 -# define COMP_R_BROTLI_INFLATE_ERROR 104 -# define COMP_R_BROTLI_NOT_SUPPORTED 105 +# define COMP_R_BROTLI_ENCODE_ERROR 103 +# define COMP_R_BROTLI_NOT_SUPPORTED 104 # define COMP_R_ZLIB_DEFLATE_ERROR 99 # define COMP_R_ZLIB_INFLATE_ERROR 100 # define COMP_R_ZLIB_NOT_SUPPORTED 101 -# define COMP_R_ZSTD_COMPRESS_ERROR 107 -# define COMP_R_ZSTD_DECODE_ERROR 108 -# define COMP_R_ZSTD_DECOMPRESS_ERROR 109 -# define COMP_R_ZSTD_NOT_SUPPORTED 110 +# define COMP_R_ZSTD_COMPRESS_ERROR 105 +# define COMP_R_ZSTD_DECODE_ERROR 106 +# define COMP_R_ZSTD_DECOMPRESS_ERROR 107 +# define COMP_R_ZSTD_NOT_SUPPORTED 108 # endif #endif diff --git a/include/openssl/configuration.h.in b/include/openssl/configuration.h.in index b84dc1dfe3..86077d0a6f 100644 --- a/include/openssl/configuration.h.in +++ b/include/openssl/configuration.h.in @@ -62,6 +62,12 @@ extern "C" { # define RC4_INT {- $config{rc4_int} -} +# if defined(OPENSSL_NO_COMP) || (defined(OPENSSL_NO_BROTLI) && defined(OPENSSL_NO_ZSTD) && defined(OPENSSL_NO_ZLIB)) +# define OPENSSL_NO_COMP_ALG +# else +# undef OPENSSL_NO_COMP_ALG +# endif + # ifdef __cplusplus } # endif diff --git a/include/openssl/ssl.h.in b/include/openssl/ssl.h.in index d2cad44b91..4ae96cd1ee 100644 --- a/include/openssl/ssl.h.in +++ b/include/openssl/ssl.h.in @@ -410,6 +410,15 @@ typedef int (*SSL_async_callback_fn)(SSL *s, void *arg); * interoperability with CryptoPro CSP 3.x */ # define SSL_OP_CRYPTOPRO_TLSEXT_BUG SSL_OP_BIT(31) +/* + * Disable RFC8879 certificate compression + * SSL_OP_NO_TX_CERTIFICATE_COMPRESSION: don't send compressed certificates, + * and ignore the extension when received. + * SSL_OP_NO_RX_CERTIFICATE_COMPRESSION: don't send the extension, and + * subsequently indicating that receiving is not supported + */ +# define SSL_OP_NO_TX_CERTIFICATE_COMPRESSION SSL_OP_BIT(32) +# define SSL_OP_NO_RX_CERTIFICATE_COMPRESSION SSL_OP_BIT(33) /* * Option "collections." @@ -998,6 +1007,7 @@ typedef enum { DTLS_ST_CR_HELLO_VERIFY_REQUEST, TLS_ST_CR_SRVR_HELLO, TLS_ST_CR_CERT, + TLS_ST_CR_COMP_CERT, TLS_ST_CR_CERT_STATUS, TLS_ST_CR_KEY_EXCH, TLS_ST_CR_CERT_REQ, @@ -1007,6 +1017,7 @@ typedef enum { TLS_ST_CR_FINISHED, TLS_ST_CW_CLNT_HELLO, TLS_ST_CW_CERT, + TLS_ST_CW_COMP_CERT, TLS_ST_CW_KEY_EXCH, TLS_ST_CW_CERT_VRFY, TLS_ST_CW_CHANGE, @@ -1017,10 +1028,12 @@ typedef enum { DTLS_ST_SW_HELLO_VERIFY_REQUEST, TLS_ST_SW_SRVR_HELLO, TLS_ST_SW_CERT, + TLS_ST_SW_COMP_CERT, TLS_ST_SW_KEY_EXCH, TLS_ST_SW_CERT_REQ, TLS_ST_SW_SRVR_DONE, TLS_ST_SR_CERT, + TLS_ST_SR_COMP_CERT, TLS_ST_SR_KEY_EXCH, TLS_ST_SR_CERT_VRFY, TLS_ST_SR_NEXT_PROTO, @@ -2530,6 +2543,22 @@ void SSL_set_allow_early_data_cb(SSL *s, const char *OSSL_default_cipher_list(void); const char *OSSL_default_ciphersuites(void); +/* RFC8879 Certificate compression APIs */ + +int SSL_CTX_compress_certs(SSL_CTX *ctx, int alg); +int SSL_compress_certs(SSL *ssl, int alg); + +int SSL_CTX_set1_cert_comp_preference(SSL_CTX *ctx, int *algs, size_t len); +int SSL_set1_cert_comp_preference(SSL *ssl, int *algs, size_t len); + +int SSL_CTX_set1_compressed_cert(SSL_CTX *ctx, int algorithm, unsigned char *comp_data, + size_t comp_length, size_t orig_length); +int SSL_set1_compressed_cert(SSL *ssl, int algorithm, unsigned char *comp_data, + size_t comp_length, size_t orig_length); +size_t SSL_CTX_get1_compressed_cert(SSL_CTX *ctx, int alg, unsigned char **data, size_t *orig_len); +size_t SSL_get1_compressed_cert(SSL *ssl, int alg, unsigned char **data, size_t *orig_len); + + # ifdef __cplusplus } # endif diff --git a/include/openssl/ssl3.h b/include/openssl/ssl3.h index 49bd51f24e..6c172f45f9 100644 --- a/include/openssl/ssl3.h +++ b/include/openssl/ssl3.h @@ -317,6 +317,7 @@ extern "C" { # define SSL3_MT_CERTIFICATE_STATUS 22 # define SSL3_MT_SUPPLEMENTAL_DATA 23 # define SSL3_MT_KEY_UPDATE 24 +# define SSL3_MT_COMPRESSED_CERTIFICATE 25 # ifndef OPENSSL_NO_NEXTPROTONEG # define SSL3_MT_NEXT_PROTO 67 # endif diff --git a/include/openssl/sslerr.h b/include/openssl/sslerr.h index 8248b641d7..98bddbb857 100644 --- a/include/openssl/sslerr.h +++ b/include/openssl/sslerr.h @@ -27,6 +27,7 @@ # define SSL_R_AT_LEAST_TLS_1_2_NEEDED_IN_SUITEB_MODE 158 # define SSL_R_BAD_CHANGE_CIPHER_SPEC 103 # define SSL_R_BAD_CIPHER 186 +# define SSL_R_BAD_COMPRESSION_ALGORITHM 326 # define SSL_R_BAD_DATA 390 # define SSL_R_BAD_DATA_RETURNED_BY_CALLBACK 106 # define SSL_R_BAD_DECOMPRESSION 107 diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h index c516291695..0f1b854d42 100644 --- a/include/openssl/tls1.h +++ b/include/openssl/tls1.h @@ -134,6 +134,9 @@ extern "C" { /* ExtensionType value from RFC7627 */ # define TLSEXT_TYPE_extended_master_secret 23 +/* ExtensionType value from RFC8879 */ +# define TLSEXT_TYPE_compress_certificate 27 + /* ExtensionType value from RFC4507 */ # define TLSEXT_TYPE_session_ticket 35 @@ -195,6 +198,15 @@ extern "C" { # define TLSEXT_hash_num 10 +/* Possible compression values from RFC8879 */ +/* Not defined in RFC8879, but used internally for no-compression */ +# define TLSEXT_comp_cert_none 0 +# define TLSEXT_comp_cert_zlib 1 +# define TLSEXT_comp_cert_brotli 2 +# define TLSEXT_comp_cert_zstd 3 +/* one more than the number of defined values - used as size of 0-terminated array */ +# define TLSEXT_comp_cert_limit 4 + /* Flag set for unrecognised algorithms */ # define TLSEXT_nid_unknown 0x1000000 diff --git a/ssl/build.info b/ssl/build.info index 81631c7497..00e9e8fd7d 100644 --- a/ssl/build.info +++ b/ssl/build.info @@ -19,6 +19,7 @@ SOURCE[../libssl]=\ ssl_asn1.c ssl_txt.c ssl_init.c ssl_conf.c ssl_mcnf.c \ bio_ssl.c ssl_err.c ssl_err_legacy.c tls_srp.c t1_trce.c ssl_utst.c \ statem/statem.c \ + ssl_cert_comp.c \ tls_depr.c # For shared builds we need to include the libcrypto packet.c and quic_vlint.c diff --git a/ssl/ssl_cert.c b/ssl/ssl_cert.c index 8ec36b9c19..d2bc61476d 100644 --- a/ssl/ssl_cert.c +++ b/ssl/ssl_cert.c @@ -74,6 +74,9 @@ CERT *ssl_cert_dup(CERT *cert) { CERT *ret = OPENSSL_zalloc(sizeof(*ret)); int i; +#ifndef OPENSSL_NO_COMP_ALG + int j; +#endif if (ret == NULL) return NULL; @@ -98,6 +101,7 @@ CERT *ssl_cert_dup(CERT *cert) for (i = 0; i < SSL_PKEY_NUM; i++) { CERT_PKEY *cpk = cert->pkeys + i; CERT_PKEY *rpk = ret->pkeys + i; + if (cpk->x509 != NULL) { rpk->x509 = cpk->x509; X509_up_ref(rpk->x509); @@ -115,16 +119,22 @@ CERT *ssl_cert_dup(CERT *cert) goto err; } } - if (cert->pkeys[i].serverinfo != NULL) { + if (cpk->serverinfo != NULL) { /* Just copy everything. */ - ret->pkeys[i].serverinfo = - OPENSSL_malloc(cert->pkeys[i].serverinfo_length); - if (ret->pkeys[i].serverinfo == NULL) + rpk->serverinfo = OPENSSL_memdup(cpk->serverinfo, cpk->serverinfo_length); + if (rpk->serverinfo == NULL) goto err; - ret->pkeys[i].serverinfo_length = cert->pkeys[i].serverinfo_length; - memcpy(ret->pkeys[i].serverinfo, - cert->pkeys[i].serverinfo, cert->pkeys[i].serverinfo_length); + rpk->serverinfo_length = cpk->serverinfo_length; } +#ifndef OPENSSL_NO_COMP_ALG + for (j = TLSEXT_comp_cert_none; j < TLSEXT_comp_cert_limit; j++) { + if (cpk->comp_cert[j] != NULL) { + if (!OSSL_COMP_CERT_up_ref(cpk->comp_cert[j])) + goto err; + rpk->comp_cert[j] = cpk->comp_cert[j]; + } + } +#endif } /* Configured sigalgs copied across */ @@ -198,6 +208,10 @@ CERT *ssl_cert_dup(CERT *cert) void ssl_cert_clear_certs(CERT *c) { int i; +#ifndef OPENSSL_NO_COMP_ALG + int j; +#endif + if (c == NULL) return; for (i = 0; i < SSL_PKEY_NUM; i++) { @@ -211,6 +225,13 @@ void ssl_cert_clear_certs(CERT *c) OPENSSL_free(cpk->serverinfo); cpk->serverinfo = NULL; cpk->serverinfo_length = 0; +#ifndef OPENSSL_NO_COMP_ALG + for (j = 0; j < TLSEXT_comp_cert_limit; j++) { + OSSL_COMP_CERT_free(cpk->comp_cert[j]); + cpk->comp_cert[j] = NULL; + cpk->cert_comp_used = 0; + } +#endif } } diff --git a/ssl/ssl_cert_comp.c b/ssl/ssl_cert_comp.c new file mode 100644 index 0000000000..a86282279c --- /dev/null +++ b/ssl/ssl_cert_comp.c @@ -0,0 +1,479 @@ +/* + * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include +#include "ssl_local.h" +#include "internal/e_os.h" +#include "internal/refcount.h" + +size_t ossl_calculate_comp_expansion(int alg, size_t length) +{ + size_t ret; + /* + * Uncompressibility expansion: + * ZLIB: N + 11 + 5 * (N >> 14) + * Brotli: per RFC7932: N + 5 + 3 * (N >> 16) + * ZSTD: N + 4 + 14 + 3 * (N >> 17) + 4 + */ + + switch (alg) { + case TLSEXT_comp_cert_zlib: + ret = length + 11 + 5 * (length >> 14); + break; + case TLSEXT_comp_cert_brotli: + ret = length + 5 + 3 * (length >> 16); + break; + case TLSEXT_comp_cert_zstd: + ret = length + 22 + 3 * (length >> 17); + break; + default: + return 0; + } + /* Check for overflow */ + if (ret < length) + return 0; + return ret; +} + +int ossl_comp_has_alg(int a) +{ +#ifndef OPENSSL_NO_COMP_ALG + /* 0 means "any" algorithm */ + if ((a == 0 || a == TLSEXT_comp_cert_brotli) && BIO_f_brotli() != NULL) + return 1; + if ((a == 0 || a == TLSEXT_comp_cert_zstd) && BIO_f_zstd() != NULL) + return 1; + if ((a == 0 || a == TLSEXT_comp_cert_zlib) && BIO_f_zlib() != NULL) + return 1; +#endif + return 0; +} + +/* New operation Helper routine */ +#ifndef OPENSSL_NO_COMP_ALG +static OSSL_COMP_CERT *OSSL_COMP_CERT_new(unsigned char *data, size_t len, size_t orig_len, int alg) +{ + OSSL_COMP_CERT *ret = NULL; + + if (!ossl_comp_has_alg(alg) + || data == NULL + || (ret = OPENSSL_zalloc(sizeof(*ret))) == NULL + || (ret->lock = CRYPTO_THREAD_lock_new()) == NULL) + goto err; + + ret->references = 1; + ret->data = data; + ret->len = len; + ret->orig_len = orig_len; + ret->alg = alg; + return ret; + err: + ERR_raise(ERR_LIB_SSL, ERR_R_MALLOC_FAILURE); + OPENSSL_free(data); + OPENSSL_free(ret); + return NULL; +} + +__owur static OSSL_COMP_CERT *OSSL_COMP_CERT_from_compressed_data(unsigned char *data, size_t len, + size_t orig_len, int alg) +{ + return OSSL_COMP_CERT_new(OPENSSL_memdup(data, len), len, orig_len, alg); +} + +__owur static OSSL_COMP_CERT *OSSL_COMP_CERT_from_uncompressed_data(unsigned char *data, size_t len, + int alg) +{ + OSSL_COMP_CERT *ret = NULL; + size_t max_length; + int comp_length; + COMP_METHOD *method; + unsigned char *comp_data = NULL; + COMP_CTX *comp_ctx = NULL; + + switch (alg) { + case TLSEXT_comp_cert_brotli: + method = COMP_brotli_oneshot(); + break; + case TLSEXT_comp_cert_zlib: + method = COMP_zlib(); + break; + case TLSEXT_comp_cert_zstd: + method = COMP_zstd_oneshot(); + break; + default: + goto err; + } + + if ((max_length = ossl_calculate_comp_expansion(alg, len)) == 0 + || method == NULL + || (comp_ctx = COMP_CTX_new(method)) == NULL + || (comp_data = OPENSSL_zalloc(max_length)) == NULL) + goto err; + + comp_length = COMP_compress_block(comp_ctx, comp_data, max_length, data, len); + if (comp_length <= 0) + goto err; + + ret = OSSL_COMP_CERT_new(comp_data, comp_length, len, alg); + comp_data = NULL; + + err: + OPENSSL_free(comp_data); + COMP_CTX_free(comp_ctx); + return ret; +} + +void OSSL_COMP_CERT_free(OSSL_COMP_CERT *cc) +{ + int i; + + if (cc == NULL) + return; + + CRYPTO_DOWN_REF(&cc->references, &i, cc->lock); + REF_PRINT_COUNT("OSSL_COMP_CERT", cc); + if (i > 0) + return; + REF_ASSERT_ISNT(i < 0); + + OPENSSL_free(cc->data); + CRYPTO_THREAD_lock_free(cc->lock); + OPENSSL_free(cc); +} +int OSSL_COMP_CERT_up_ref(OSSL_COMP_CERT *cc) +{ + int i; + + if (CRYPTO_UP_REF(&cc->references, &i, cc->lock) <= 0) + return 0; + + REF_PRINT_COUNT("OSSL_COMP_CERT", cc); + REF_ASSERT_ISNT(i < 2); + return ((i > 1) ? 1 : 0); +} + +static int ssl_set_cert_comp_pref(int *prefs, int *algs, size_t len) +{ + size_t j = 0; + size_t i; + int found = 0; + int already_set[TLSEXT_comp_cert_limit]; + int tmp_prefs[TLSEXT_comp_cert_limit]; + + /* Note that |len| is the number of |algs| elements */ + /* clear all algorithms */ + if (len == 0 || algs == NULL) { + memset(prefs, 0, sizeof(tmp_prefs)); + return 1; + } + + /* This will 0-terminate the array */ + memset(tmp_prefs, 0, sizeof(tmp_prefs)); + memset(already_set, 0, sizeof(already_set)); + /* Include only those algorithms we support, ignoring duplicates and unknowns */ + for (i = 0; i < len; i++) { + if (algs[i] != 0 && ossl_comp_has_alg(algs[i])) { + /* Check for duplicate */ + if (already_set[algs[i]]) + return 0; + tmp_prefs[j++] = algs[i]; + already_set[algs[i]] = 1; + found = 1; + } + } + if (found) + memcpy(prefs, tmp_prefs, sizeof(tmp_prefs)); + return found; +} + +static size_t ssl_get_cert_to_compress(SSL *ssl, CERT_PKEY *cpk, unsigned char **data) +{ + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + WPACKET tmppkt; + BUF_MEM buf = { 0 }; + size_t ret = 0; + const SSL_METHOD *method = NULL; + + if (sc == NULL + || cpk == NULL + || !sc->server + || !SSL_in_before(ssl)) + return 0; + + /* Use the |tmppkt| for the to-be-compressed data */ + if (!WPACKET_init(&tmppkt, &buf)) + goto out; + + /* no context present, add 0-length context */ + if (!WPACKET_put_bytes_u8(&tmppkt, 0)) + goto out; + + /* + * ssl3_output_cert_chain() may generate an SSLfata() error, + * for this case, we want to ignore it + */ + sc->statem.ignore_fatal = 1; + ERR_set_mark(); + /* Must get the certificate as TLSv1.3, restore before returning */ + method = SSL_get_ssl_method(ssl); + if (!SSL_set_ssl_method(ssl, tlsv1_3_server_method())) + goto out; + + if (!ssl3_output_cert_chain(sc, &tmppkt, cpk)) + goto out; + WPACKET_get_total_written(&tmppkt, &ret); + + out: + /* Don't leave errors in the queue */ + ERR_pop_to_mark(); + sc->statem.ignore_fatal = 0; + if (method != NULL && !SSL_set_ssl_method(ssl, method)) + ret = 0; + WPACKET_cleanup(&tmppkt); + if (ret != 0 && data != NULL) + *data = (unsigned char *)buf.data; + else + OPENSSL_free(buf.data); + return ret; +} + +static int ssl_compress_one_cert(SSL *ssl, CERT_PKEY *cpk, int alg) +{ + unsigned char *cert_data = NULL; + OSSL_COMP_CERT *comp_cert = NULL; + size_t length; + + if (cpk == NULL + || alg == TLSEXT_comp_cert_none + || !ossl_comp_has_alg(alg)) + return 0; + + if ((length = ssl_get_cert_to_compress(ssl, cpk, &cert_data)) == 0) + return 0; + comp_cert = OSSL_COMP_CERT_from_uncompressed_data(cert_data, length, alg); + OPENSSL_free(cert_data); + if (comp_cert == NULL) + return 0; + + OSSL_COMP_CERT_free(cpk->comp_cert[alg]); + cpk->comp_cert[alg] = comp_cert; + return 1; +} + +/* alg_in can be 0, meaning any/all algorithms */ +static int ssl_compress_certs(SSL *ssl, CERT_PKEY *cpks, int alg_in) +{ + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + int i; + int j; + int alg; + int count = 0; + + if (sc == NULL + || cpks == NULL + || !ossl_comp_has_alg(alg_in)) + return 0; + + /* Look through the preferences to see what we have */ + for (i = 0; i < TLSEXT_comp_cert_limit; i++) { + /* + * alg = 0 means compress for everything, but only for algorithms enabled + * alg != 0 means compress for that algorithm if enabled + */ + alg = sc->cert_comp_prefs[i]; + if ((alg_in == 0 && alg != TLSEXT_comp_cert_none) + || (alg_in != 0 && alg == alg_in)) { + + for (j = 0; j < SSL_PKEY_NUM; j++) { + /* No cert, move on */ + if (cpks[j].x509 == NULL) + continue; + + if (!ssl_compress_one_cert(ssl, &cpks[j], alg)) + return 0; + + /* if the cert expanded, set the value in the CERT_PKEY to NULL */ + if (cpks[j].comp_cert[alg]->len >= cpks[j].comp_cert[alg]->orig_len) { + OSSL_COMP_CERT_free(cpks[j].comp_cert[alg]); + cpks[j].comp_cert[alg] = NULL; + } else { + count++; + } + } + } + } + return (count > 0); +} + +static size_t ssl_get_compressed_cert(SSL *ssl, CERT_PKEY *cpk, int alg, unsigned char **data, + size_t *orig_len) +{ + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + size_t cert_len = 0; + size_t comp_len = 0; + unsigned char *cert_data = NULL; + OSSL_COMP_CERT *comp_cert = NULL; + + if (sc == NULL + || cpk == NULL + || data == NULL + || orig_len == NULL + || !sc->server + || !SSL_in_before(ssl) + || !ossl_comp_has_alg(alg)) + return 0; + + if ((cert_len = ssl_get_cert_to_compress(ssl, cpk, &cert_data)) == 0) + goto err; + + comp_cert = OSSL_COMP_CERT_from_uncompressed_data(cert_data, cert_len, alg); + OPENSSL_free(cert_data); + if (comp_cert == NULL) + goto err; + + comp_len = comp_cert->len; + *orig_len = comp_cert->orig_len; + *data = comp_cert->data; + comp_cert->data = NULL; + err: + OSSL_COMP_CERT_free(comp_cert); + return comp_len; +} + +static int ossl_set1_compressed_cert(CERT *cert, int algorithm, + unsigned char *comp_data, size_t comp_length, + size_t orig_length) +{ + OSSL_COMP_CERT *comp_cert; + + /* No explicit cert set */ + if (cert == NULL || cert->key == NULL) + return 0; + + comp_cert = OSSL_COMP_CERT_from_compressed_data(comp_data, comp_length, + orig_length, algorithm); + if (comp_cert == NULL) + return 0; + + OSSL_COMP_CERT_free(cert->key->comp_cert[algorithm]); + cert->key->comp_cert[algorithm] = comp_cert; + + return 1; +} +#endif + +/*- + * Public API + */ +int SSL_CTX_set1_cert_comp_preference(SSL_CTX *ctx, int *algs, size_t len) +{ +#ifndef OPENSSL_NO_COMP_ALG + return ssl_set_cert_comp_pref(ctx->cert_comp_prefs, algs, len); +#else + return 0; +#endif +} + +int SSL_set1_cert_comp_preference(SSL *ssl, int *algs, size_t len) +{ +#ifndef OPENSSL_NO_COMP_ALG + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + + if (sc == NULL) + return 0; + return ssl_set_cert_comp_pref(sc->cert_comp_prefs, algs, len); +#else + return 0; +#endif +} + +int SSL_compress_certs(SSL *ssl, int alg) +{ +#ifndef OPENSSL_NO_COMP_ALG + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + + if (sc == NULL || sc->cert == NULL) + return 0; + + return ssl_compress_certs(ssl, sc->cert->pkeys, alg); +#endif + return 0; +} + +int SSL_CTX_compress_certs(SSL_CTX *ctx, int alg) +{ + int ret = 0; +#ifndef OPENSSL_NO_COMP_ALG + SSL *new = SSL_new(ctx); + + if (new == NULL) + return 0; + + ret = ssl_compress_certs(new, ctx->cert->pkeys, alg); + SSL_free(new); +#endif + return ret; +} + +size_t SSL_get1_compressed_cert(SSL *ssl, int alg, unsigned char **data, size_t *orig_len) +{ +#ifndef OPENSSL_NO_COMP_ALG + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + CERT_PKEY *cpk = NULL; + + if (sc->cert != NULL) + cpk = sc->cert->key; + else + cpk = ssl->ctx->cert->key; + + return ssl_get_compressed_cert(ssl, cpk, alg, data, orig_len); +#else + return 0; +#endif +} + +size_t SSL_CTX_get1_compressed_cert(SSL_CTX *ctx, int alg, unsigned char **data, size_t *orig_len) +{ +#ifndef OPENSSL_NO_COMP_ALG + size_t ret; + SSL *new = SSL_new(ctx); + + ret = ssl_get_compressed_cert(new, ctx->cert->key, alg, data, orig_len); + SSL_free(new); + return ret; +#else + return 0; +#endif +} + +int SSL_CTX_set1_compressed_cert(SSL_CTX *ctx, int algorithm, unsigned char *comp_data, + size_t comp_length, size_t orig_length) +{ +#ifndef OPENSSL_NO_COMP_ALG + return ossl_set1_compressed_cert(ctx->cert, algorithm, comp_data, comp_length, orig_length); +#else + return 0; +#endif +} + +int SSL_set1_compressed_cert(SSL *ssl, int algorithm, unsigned char *comp_data, + size_t comp_length, size_t orig_length) +{ +#ifndef OPENSSL_NO_COMP_ALG + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl); + + /* Cannot set a pre-compressed certificate on a client */ + if (sc == NULL || !sc->server) + return 0; + + return ossl_set1_compressed_cert(sc->cert, algorithm, comp_data, comp_length, orig_length); +#else + return 0; +#endif +} diff --git a/ssl/ssl_conf.c b/ssl/ssl_conf.c index e1a4bda9ed..bebfc501a9 100644 --- a/ssl/ssl_conf.c +++ b/ssl/ssl_conf.c @@ -397,7 +397,9 @@ static int cmd_Options(SSL_CONF_CTX *cctx, const char *value) SSL_FLAG_TBL_INV("ExtendedMasterSecret", SSL_OP_NO_EXTENDED_MASTER_SECRET), SSL_FLAG_TBL_INV("CANames", SSL_OP_DISABLE_TLSEXT_CA_NAMES), SSL_FLAG_TBL("KTLS", SSL_OP_ENABLE_KTLS), - SSL_FLAG_TBL_CERT("StrictCertCheck", SSL_CERT_FLAG_TLS_STRICT) + SSL_FLAG_TBL_CERT("StrictCertCheck", SSL_CERT_FLAG_TLS_STRICT), + SSL_FLAG_TBL_INV("TxCertificateCompression", SSL_OP_NO_TX_CERTIFICATE_COMPRESSION), + SSL_FLAG_TBL_INV("RxCertificateCompression", SSL_OP_NO_RX_CERTIFICATE_COMPRESSION), }; if (value == NULL) return -3; @@ -707,6 +709,10 @@ static const ssl_conf_cmd_tbl ssl_conf_cmds[] = { SSL_CONF_CMD_SWITCH("bugs", 0), SSL_CONF_CMD_SWITCH("no_comp", 0), SSL_CONF_CMD_SWITCH("comp", 0), + SSL_CONF_CMD_SWITCH("no_tx_cert_comp", 0), + SSL_CONF_CMD_SWITCH("tx_cert_comp", 0), + SSL_CONF_CMD_SWITCH("no_rx_cert_comp", 0), + SSL_CONF_CMD_SWITCH("rx_cert_comp", 0), SSL_CONF_CMD_SWITCH("ecdh_single", SSL_CONF_FLAG_SERVER), SSL_CONF_CMD_SWITCH("no_ticket", 0), SSL_CONF_CMD_SWITCH("serverpref", SSL_CONF_FLAG_SERVER), @@ -787,6 +793,10 @@ static const ssl_switch_tbl ssl_cmd_switches[] = { {SSL_OP_ALL, 0}, /* bugs */ {SSL_OP_NO_COMPRESSION, 0}, /* no_comp */ {SSL_OP_NO_COMPRESSION, SSL_TFLAG_INV}, /* comp */ + {SSL_OP_NO_TX_CERTIFICATE_COMPRESSION, 0}, /* no_tx_cert_comp */ + {SSL_OP_NO_TX_CERTIFICATE_COMPRESSION, SSL_TFLAG_INV}, /* tx_cert_comp */ + {SSL_OP_NO_RX_CERTIFICATE_COMPRESSION, 0}, /* no_rx_cert_comp */ + {SSL_OP_NO_RX_CERTIFICATE_COMPRESSION, SSL_TFLAG_INV}, /* rx_cert_comp */ {SSL_OP_SINGLE_ECDH_USE, 0}, /* ecdh_single */ {SSL_OP_NO_TICKET, 0}, /* no_ticket */ {SSL_OP_CIPHER_SERVER_PREFERENCE, 0}, /* serverpref */ diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c index fb825eb371..1a4f441a9f 100644 --- a/ssl/ssl_err.c +++ b/ssl/ssl_err.c @@ -26,6 +26,8 @@ static const ERR_STRING_DATA SSL_str_reasons[] = { {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_CHANGE_CIPHER_SPEC), "bad change cipher spec"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_CIPHER), "bad cipher"}, + {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_COMPRESSION_ALGORITHM), + "bad compression algorithm"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_DATA), "bad data"}, {ERR_PACK(ERR_LIB_SSL, 0, SSL_R_BAD_DATA_RETURNED_BY_CALLBACK), "bad data returned by callback"}, diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index fb43b9b369..186e60f34c 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -615,6 +615,9 @@ int ossl_ssl_connection_reset(SSL *s) sc->first_packet = 0; sc->key_update = SSL_KEY_UPDATE_NONE; + memset(sc->ext.compress_certificate_from_peer, 0, + sizeof(sc->ext.compress_certificate_from_peer)); + sc->ext.compress_certificate_sent = 0; EVP_MD_CTX_free(sc->pha_dgst); sc->pha_dgst = NULL; @@ -890,6 +893,10 @@ SSL *ossl_ssl_connection_new(SSL_CTX *ctx) s->job = NULL; +#ifndef OPENSSL_NO_COMP_ALG + memcpy(s->cert_comp_prefs, ctx->cert_comp_prefs, sizeof(s->cert_comp_prefs)); +#endif + #ifndef OPENSSL_NO_CT if (!SSL_set_ct_validation_callback(ssl, ctx->ct_validation_callback, ctx->ct_validation_callback_arg)) @@ -3658,6 +3665,9 @@ SSL_CTX *SSL_CTX_new_ex(OSSL_LIB_CTX *libctx, const char *propq, const SSL_METHOD *meth) { SSL_CTX *ret = NULL; +#ifndef OPENSSL_NO_COMP_ALG + int i; +#endif if (meth == NULL) { ERR_raise(ERR_LIB_SSL, SSL_R_NULL_SSL_METHOD_PASSED); @@ -3831,6 +3841,21 @@ SSL_CTX *SSL_CTX_new_ex(OSSL_LIB_CTX *libctx, const char *propq, ERR_clear_error(); } # endif +#endif + +#ifndef OPENSSL_NO_COMP_ALG + /* + * Set the default order: brotli, zlib, zstd + * Including only those enabled algorithms + */ + memset(ret->cert_comp_prefs, 0, sizeof(ret->cert_comp_prefs)); + i = 0; + if (ossl_comp_has_alg(TLSEXT_comp_cert_brotli)) + ret->cert_comp_prefs[i++] = TLSEXT_comp_cert_brotli; + if (ossl_comp_has_alg(TLSEXT_comp_cert_zlib)) + ret->cert_comp_prefs[i++] = TLSEXT_comp_cert_zlib; + if (ossl_comp_has_alg(TLSEXT_comp_cert_zstd)) + ret->cert_comp_prefs[i++] = TLSEXT_comp_cert_zstd; #endif /* * Disable compression by default to prevent CRIME. Applications can diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h index 75614c9fc9..2580064ebd 100644 --- a/ssl/ssl_local.h +++ b/ssl/ssl_local.h @@ -19,8 +19,8 @@ # include "internal/common.h" /* for HAS_PREFIX */ # include -# include # include +# include # include # include # include @@ -771,6 +771,7 @@ typedef enum tlsext_index_en { TLSEXT_IDX_key_share, TLSEXT_IDX_cookie, TLSEXT_IDX_cryptopro_bug, + TLSEXT_IDX_compress_certificate, TLSEXT_IDX_early_data, TLSEXT_IDX_certificate_authorities, TLSEXT_IDX_padding, @@ -1212,6 +1213,11 @@ struct ssl_ctx_st { uint32_t disabled_mac_mask; uint32_t disabled_mkey_mask; uint32_t disabled_auth_mask; + +#ifndef OPENSSL_NO_COMP_ALG + /* certificate compression preferences */ + int cert_comp_prefs[TLSEXT_comp_cert_limit]; +#endif }; typedef struct cert_pkey_st CERT_PKEY; @@ -1699,6 +1705,11 @@ struct ssl_connection_st { * selected. */ int tick_identity; + + /* This is the list of algorithms the peer supports that we also support */ + int compress_certificate_from_peer[TLSEXT_comp_cert_limit]; + /* indicate that we sent the extension, so we'll accept it */ + int compress_certificate_sent; } ext; /* @@ -1814,6 +1825,11 @@ struct ssl_connection_st { */ const struct sigalg_lookup_st **shared_sigalgs; size_t shared_sigalgslen; + +#ifndef OPENSSL_NO_COMP_ALG + /* certificate compression preferences */ + int cert_comp_prefs[TLSEXT_comp_cert_limit]; +#endif }; # define SSL_CONNECTION_FROM_SSL_ONLY_int(ssl, c) \ @@ -1986,6 +2002,21 @@ typedef struct dtls1_state_st { # define EXPLICIT_CHAR2_CURVE_TYPE 2 # define NAMED_CURVE_TYPE 3 +# ifndef OPENSSL_NO_COMP_ALG +struct ossl_comp_cert_st { + unsigned char *data; + size_t len; + size_t orig_len; + CRYPTO_REF_COUNT references; + CRYPTO_RWLOCK *lock; + int alg; +}; +typedef struct ossl_comp_cert_st OSSL_COMP_CERT; + +void OSSL_COMP_CERT_free(OSSL_COMP_CERT *c); +int OSSL_COMP_CERT_up_ref(OSSL_COMP_CERT *c); +# endif + struct cert_pkey_st { X509 *x509; EVP_PKEY *privatekey; @@ -2000,6 +2031,11 @@ struct cert_pkey_st { */ unsigned char *serverinfo; size_t serverinfo_length; +# ifndef OPENSSL_NO_COMP_ALG + /* Compressed certificate data - index 0 is unused */ + OSSL_COMP_CERT *comp_cert[TLSEXT_comp_cert_limit]; + int cert_comp_used; +# endif }; /* Retrieve Suite B flags */ # define tls1_suiteb(s) (s->cert->cert_flags & SSL_CERT_FLAG_SUITEB_128_LOS) @@ -2956,4 +2992,7 @@ static ossl_unused ossl_inline void ssl_tsan_counter(const SSL_CTX *ctx, } } +int ossl_comp_has_alg(int a); +size_t ossl_calculate_comp_expansion(int alg, size_t length); + #endif diff --git a/ssl/ssl_stat.c b/ssl/ssl_stat.c index 8854abcbd1..8b93ccd4ac 100644 --- a/ssl/ssl_stat.c +++ b/ssl/ssl_stat.c @@ -37,6 +37,8 @@ const char *SSL_state_string_long(const SSL *s) return "SSLv3/TLS read server hello"; case TLS_ST_CR_CERT: return "SSLv3/TLS read server certificate"; + case TLS_ST_CR_COMP_CERT: + return "TLSv1.3 read server compressed certificate"; case TLS_ST_CR_KEY_EXCH: return "SSLv3/TLS read server key exchange"; case TLS_ST_CR_CERT_REQ: @@ -47,6 +49,8 @@ const char *SSL_state_string_long(const SSL *s) return "SSLv3/TLS read server done"; case TLS_ST_CW_CERT: return "SSLv3/TLS write client certificate"; + case TLS_ST_CW_COMP_CERT: + return "TLSv1.3 write client compressed certificate"; case TLS_ST_CW_KEY_EXCH: return "SSLv3/TLS write client key exchange"; case TLS_ST_CW_CERT_VRFY: @@ -71,6 +75,8 @@ const char *SSL_state_string_long(const SSL *s) return "SSLv3/TLS write server hello"; case TLS_ST_SW_CERT: return "SSLv3/TLS write certificate"; + case TLS_ST_SW_COMP_CERT: + return "TLSv1.3 write server compressed certificate"; case TLS_ST_SW_KEY_EXCH: return "SSLv3/TLS write key exchange"; case TLS_ST_SW_CERT_REQ: @@ -81,6 +87,8 @@ const char *SSL_state_string_long(const SSL *s) return "SSLv3/TLS write server done"; case TLS_ST_SR_CERT: return "SSLv3/TLS read client certificate"; + case TLS_ST_SR_COMP_CERT: + return "TLSv1.3 read client compressed certificate"; case TLS_ST_SR_KEY_EXCH: return "SSLv3/TLS read client key exchange"; case TLS_ST_SR_CERT_VRFY: @@ -150,6 +158,8 @@ const char *SSL_state_string(const SSL *s) return "TRSH"; case TLS_ST_CR_CERT: return "TRSC"; + case TLS_ST_CR_COMP_CERT: + return "TRSCC"; case TLS_ST_CR_KEY_EXCH: return "TRSKE"; case TLS_ST_CR_CERT_REQ: @@ -158,6 +168,8 @@ const char *SSL_state_string(const SSL *s) return "TRSD"; case TLS_ST_CW_CERT: return "TWCC"; + case TLS_ST_CW_COMP_CERT: + return "TWCCC"; case TLS_ST_CW_KEY_EXCH: return "TWCKE"; case TLS_ST_CW_CERT_VRFY: @@ -182,6 +194,8 @@ const char *SSL_state_string(const SSL *s) return "TWSH"; case TLS_ST_SW_CERT: return "TWSC"; + case TLS_ST_SW_COMP_CERT: + return "TWSCC"; case TLS_ST_SW_KEY_EXCH: return "TWSKE"; case TLS_ST_SW_CERT_REQ: @@ -190,6 +204,8 @@ const char *SSL_state_string(const SSL *s) return "TWSD"; case TLS_ST_SR_CERT: return "TRCC"; + case TLS_ST_SR_COMP_CERT: + return "TRCCC"; case TLS_ST_SR_KEY_EXCH: return "TRCKE"; case TLS_ST_SR_CERT_VRFY: diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c index 880189d998..2cfc9f2b7d 100644 --- a/ssl/statem/extensions.c +++ b/ssl/statem/extensions.c @@ -62,6 +62,13 @@ static int final_maxfragmentlen(SSL_CONNECTION *s, unsigned int context, int sent); static int init_post_handshake_auth(SSL_CONNECTION *s, unsigned int context); static int final_psk(SSL_CONNECTION *s, unsigned int context, int sent); +static int tls_init_compress_certificate(SSL_CONNECTION *sc, unsigned int context); +static EXT_RETURN tls_construct_compress_certificate(SSL_CONNECTION *sc, WPACKET *pkt, + unsigned int context, + X509 *x, size_t chainidx); +static int tls_parse_compress_certificate(SSL_CONNECTION *sc, PACKET *pkt, + unsigned int context, + X509 *x, size_t chainidx); /* Structure to define a built-in extension */ typedef struct extensions_definition_st { @@ -358,6 +365,15 @@ static const EXTENSION_DEFINITION ext_defs[] = { | SSL_EXT_TLS1_2_AND_BELOW_ONLY, NULL, NULL, NULL, tls_construct_stoc_cryptopro_bug, NULL, NULL }, + { + TLSEXT_TYPE_compress_certificate, + SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_CERTIFICATE_REQUEST + | SSL_EXT_TLS_IMPLEMENTATION_ONLY | SSL_EXT_TLS1_3_ONLY, + tls_init_compress_certificate, + tls_parse_compress_certificate, tls_parse_compress_certificate, + tls_construct_compress_certificate, tls_construct_compress_certificate, + NULL + }, { TLSEXT_TYPE_early_data, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS @@ -1746,3 +1762,112 @@ static int final_psk(SSL_CONNECTION *s, unsigned int context, int sent) return 1; } + +static int tls_init_compress_certificate(SSL_CONNECTION *sc, unsigned int context) +{ + memset(sc->ext.compress_certificate_from_peer, 0, + sizeof(sc->ext.compress_certificate_from_peer)); + return 1; +} + +/* The order these are put into the packet imply a preference order: [brotli, zlib, zstd] */ +static EXT_RETURN tls_construct_compress_certificate(SSL_CONNECTION *sc, WPACKET *pkt, + unsigned int context, + X509 *x, size_t chainidx) +{ +#ifndef OPENSSL_NO_COMP_ALG + int i; + + if (!ossl_comp_has_alg(0)) + return EXT_RETURN_NOT_SENT; + + /* Do not indicate we support receiving compressed certificates */ + if ((sc->options & SSL_OP_NO_RX_CERTIFICATE_COMPRESSION) != 0) + return EXT_RETURN_NOT_SENT; + + if (sc->cert_comp_prefs[0] == TLSEXT_comp_cert_none) + return EXT_RETURN_NOT_SENT; + + if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_compress_certificate) + || !WPACKET_start_sub_packet_u16(pkt) + || !WPACKET_start_sub_packet_u8(pkt)) + goto err; + + for (i = 0; sc->cert_comp_prefs[i] != TLSEXT_comp_cert_none; i++) { + if (!WPACKET_put_bytes_u16(pkt, sc->cert_comp_prefs[i])) + goto err; + } + if (!WPACKET_close(pkt) || !WPACKET_close(pkt)) + goto err; + + sc->ext.compress_certificate_sent = 1; + return EXT_RETURN_SENT; + err: + SSLfatal(sc, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; +#else + return EXT_RETURN_NOT_SENT; +#endif +} + +#ifndef OPENSSL_NO_COMP_ALG +static int tls_comp_in_pref(SSL_CONNECTION *sc, int alg) +{ + int i; + + /* ossl_comp_has_alg() considers 0 as "any" */ + if (alg == 0) + return 0; + /* Make sure algorithm is enabled */ + if (!ossl_comp_has_alg(alg)) + return 0; + /* If no preferences are set, it's ok */ + if (sc->cert_comp_prefs[0] == TLSEXT_comp_cert_none) + return 1; + /* Find the algorithm */ + for (i = 0; i < TLSEXT_comp_cert_limit; i++) + if (sc->cert_comp_prefs[i] == alg) + return 1; + return 0; +} +#endif + +int tls_parse_compress_certificate(SSL_CONNECTION *sc, PACKET *pkt, unsigned int context, + X509 *x, size_t chainidx) +{ +#ifndef OPENSSL_NO_COMP_ALG + PACKET supported_comp_algs; + unsigned int comp; + int already_set[TLSEXT_comp_cert_limit]; + int j = 0; + + /* If no algorithms are available, ignore the extension */ + if (!ossl_comp_has_alg(0)) + return 1; + + /* Ignore the extension and don't send compressed certificates */ + if ((sc->options & SSL_OP_NO_TX_CERTIFICATE_COMPRESSION) != 0) + return 1; + + if (!PACKET_as_length_prefixed_1(pkt, &supported_comp_algs) + || PACKET_remaining(&supported_comp_algs) == 0) { + SSLfatal(sc, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION); + return 0; + } + + memset(already_set, 0, sizeof(already_set)); + /* + * The preference array has real values, so take a look at each + * value coming in, and make sure it's in our preference list + * The array is 0 (i.e. "none") terminated + * The preference list only contains supported algorithms + */ + while (PACKET_get_1(&supported_comp_algs, &comp)) { + if (tls_comp_in_pref(sc, comp) && !already_set[comp]) { + sc->ext.compress_certificate_from_peer[j++] = comp; + already_set[comp] = 1; + } + } +#endif + return 1; +} diff --git a/ssl/statem/extensions_cust.c b/ssl/statem/extensions_cust.c index a2c563b86b..57713702f7 100644 --- a/ssl/statem/extensions_cust.c +++ b/ssl/statem/extensions_cust.c @@ -525,6 +525,7 @@ int SSL_extension_supported(unsigned int ext_type) case TLSEXT_TYPE_certificate_authorities: case TLSEXT_TYPE_psk: case TLSEXT_TYPE_post_handshake_auth: + case TLSEXT_TYPE_compress_certificate: return 1; default: return 0; diff --git a/ssl/statem/statem.c b/ssl/statem/statem.c index 448d655a17..ba3681285a 100644 --- a/ssl/statem/statem.c +++ b/ssl/statem/statem.c @@ -130,6 +130,7 @@ void ossl_statem_clear(SSL_CONNECTION *s) s->statem.hand_state = TLS_ST_BEFORE; ossl_statem_set_in_init(s, 1); s->statem.no_cert_verify = 0; + s->statem.ignore_fatal = 0; } /* @@ -143,6 +144,15 @@ void ossl_statem_set_renegotiate(SSL_CONNECTION *s) void ossl_statem_send_fatal(SSL_CONNECTION *s, int al) { + /* + * Some public APIs may call internal functions that fatal error, + * which doesn't make sense outside the state machine. Those APIs + * that can handle a failure set this flag to avoid errors sending + * alerts. Example: getting a wire-formatted certificate for + * compression. + */ + if (s->statem.ignore_fatal) + return; /* We shouldn't call SSLfatal() twice. Once is enough */ if (s->statem.in_init && s->statem.state == MSG_FLOW_ERROR) return; diff --git a/ssl/statem/statem.h b/ssl/statem/statem.h index 2b73eba6f6..b60103e6e5 100644 --- a/ssl/statem/statem.h +++ b/ssl/statem/statem.h @@ -96,6 +96,7 @@ struct ossl_statem_st { OSSL_HANDSHAKE_STATE request_state; int in_init; int read_state_first_init; + int ignore_fatal; /* true when we are actually in SSL_accept() or SSL_connect() */ int in_handshake; /* diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c index 516aaf3984..31f0ebe8e5 100644 --- a/ssl/statem/statem_clnt.c +++ b/ssl/statem/statem_clnt.c @@ -135,6 +135,13 @@ static int ossl_statem_client13_read_transition(SSL_CONNECTION *s, int mt) st->hand_state = TLS_ST_CR_CERT; return 1; } +#ifndef OPENSSL_NO_COMP_ALG + if (mt == SSL3_MT_COMPRESSED_CERTIFICATE + && s->ext.compress_certificate_sent) { + st->hand_state = TLS_ST_CR_COMP_CERT; + return 1; + } +#endif } break; @@ -143,9 +150,17 @@ static int ossl_statem_client13_read_transition(SSL_CONNECTION *s, int mt) st->hand_state = TLS_ST_CR_CERT; return 1; } +#ifndef OPENSSL_NO_COMP_ALG + if (mt == SSL3_MT_COMPRESSED_CERTIFICATE + && s->ext.compress_certificate_sent) { + st->hand_state = TLS_ST_CR_COMP_CERT; + return 1; + } +#endif break; case TLS_ST_CR_CERT: + case TLS_ST_CR_COMP_CERT: if (mt == SSL3_MT_CERTIFICATE_VERIFY) { st->hand_state = TLS_ST_CR_CERT_VRFY; return 1; @@ -309,6 +324,7 @@ int ossl_statem_client_read_transition(SSL_CONNECTION *s, int mt) break; case TLS_ST_CR_CERT: + case TLS_ST_CR_COMP_CERT: /* * The CertificateStatus message is optional even if * |ext.status_expected| is set @@ -425,7 +441,10 @@ static WRITE_TRAN ossl_statem_client13_write_transition(SSL_CONNECTION *s) case TLS_ST_CR_CERT_REQ: if (s->post_handshake_auth == SSL_PHA_REQUESTED) { - st->hand_state = TLS_ST_CW_CERT; + if (s->ext.compress_certificate_from_peer[0] != TLSEXT_comp_cert_none) + st->hand_state = TLS_ST_CW_COMP_CERT; + else + st->hand_state = TLS_ST_CW_CERT; return WRITE_TRAN_CONTINUE; } /* @@ -447,9 +466,12 @@ static WRITE_TRAN ossl_statem_client13_write_transition(SSL_CONNECTION *s) else if ((s->options & SSL_OP_ENABLE_MIDDLEBOX_COMPAT) != 0 && s->hello_retry_request == SSL_HRR_NONE) st->hand_state = TLS_ST_CW_CHANGE; + else if (s->s3.tmp.cert_req == 0) + st->hand_state = TLS_ST_CW_FINISHED; + else if (s->ext.compress_certificate_from_peer[0] != TLSEXT_comp_cert_none) + st->hand_state = TLS_ST_CW_COMP_CERT; else - st->hand_state = (s->s3.tmp.cert_req != 0) ? TLS_ST_CW_CERT - : TLS_ST_CW_FINISHED; + st->hand_state = TLS_ST_CW_CERT; return WRITE_TRAN_CONTINUE; case TLS_ST_PENDING_EARLY_DATA_END: @@ -461,10 +483,15 @@ static WRITE_TRAN ossl_statem_client13_write_transition(SSL_CONNECTION *s) case TLS_ST_CW_END_OF_EARLY_DATA: case TLS_ST_CW_CHANGE: - st->hand_state = (s->s3.tmp.cert_req != 0) ? TLS_ST_CW_CERT - : TLS_ST_CW_FINISHED; + if (s->s3.tmp.cert_req == 0) + st->hand_state = TLS_ST_CW_FINISHED; + else if (s->ext.compress_certificate_from_peer[0] != TLSEXT_comp_cert_none) + st->hand_state = TLS_ST_CW_COMP_CERT; + else + st->hand_state = TLS_ST_CW_CERT; return WRITE_TRAN_CONTINUE; + case TLS_ST_CW_COMP_CERT: case TLS_ST_CW_CERT: /* If a non-empty Certificate we also send CertificateVerify */ st->hand_state = (s->s3.tmp.cert_req == 1) ? TLS_ST_CW_CERT_VRFY @@ -931,6 +958,13 @@ int ossl_statem_client_construct_message(SSL_CONNECTION *s, *mt = SSL3_MT_CERTIFICATE; break; +#ifndef OPENSSL_NO_COMP_ALG + case TLS_ST_CW_COMP_CERT: + *confunc = tls_construct_client_compressed_certificate; + *mt = SSL3_MT_COMPRESSED_CERTIFICATE; + break; +#endif + case TLS_ST_CW_KEY_EXCH: *confunc = tls_construct_client_key_exchange; *mt = SSL3_MT_CLIENT_KEY_EXCHANGE; @@ -980,6 +1014,7 @@ size_t ossl_statem_client_max_message_size(SSL_CONNECTION *s) case DTLS_ST_CR_HELLO_VERIFY_REQUEST: return HELLO_VERIFY_REQUEST_MAX_LENGTH; + case TLS_ST_CR_COMP_CERT: case TLS_ST_CR_CERT: return s->max_cert_list; @@ -1046,6 +1081,11 @@ MSG_PROCESS_RETURN ossl_statem_client_process_message(SSL_CONNECTION *s, case TLS_ST_CR_CERT: return tls_process_server_certificate(s, pkt); +#ifndef OPENSSL_NO_COMP_ALG + case TLS_ST_CR_COMP_CERT: + return tls_process_server_compressed_certificate(s, pkt); +#endif + case TLS_ST_CR_CERT_VRFY: return tls_process_cert_verify(s, pkt); @@ -1097,6 +1137,7 @@ WORK_STATE ossl_statem_client_post_process_message(SSL_CONNECTION *s, return WORK_ERROR; case TLS_ST_CR_CERT: + case TLS_ST_CR_COMP_CERT: return tls_post_process_server_certificate(s, wst); case TLS_ST_CR_CERT_VRFY: @@ -1968,6 +2009,21 @@ WORK_STATE tls_post_process_server_certificate(SSL_CONNECTION *s, return WORK_FINISHED_CONTINUE; } +#ifndef OPENSSL_NO_COMP_ALG +MSG_PROCESS_RETURN tls_process_server_compressed_certificate(SSL_CONNECTION *sc, PACKET *pkt) +{ + MSG_PROCESS_RETURN ret = MSG_PROCESS_ERROR; + PACKET tmppkt; + BUF_MEM *buf = BUF_MEM_new(); + + if (tls13_process_compressed_certificate(sc, pkt, &tmppkt, buf) != MSG_PROCESS_ERROR) + ret = tls_process_server_certificate(sc, &tmppkt); + + BUF_MEM_free(buf); + return ret; +} +#endif + static int tls_process_ske_psk_preamble(SSL_CONNECTION *s, PACKET *pkt) { #ifndef OPENSSL_NO_PSK @@ -3531,6 +3587,7 @@ WORK_STATE tls_prepare_client_certificate(SSL_CONNECTION *s, WORK_STATE wst) return WORK_FINISHED_CONTINUE; } else { s->s3.tmp.cert_req = 2; + s->ext.compress_certificate_from_peer[0] = TLSEXT_comp_cert_none; if (!ssl3_digest_cached_records(s, 0)) { /* SSLfatal() already called */ return WORK_ERROR; @@ -3538,6 +3595,10 @@ WORK_STATE tls_prepare_client_certificate(SSL_CONNECTION *s, WORK_STATE wst) } } + if (!SSL_CONNECTION_IS_TLS13(s) + || (s->options & SSL_OP_NO_TX_CERTIFICATE_COMPRESSION) != 0) + s->ext.compress_certificate_from_peer[0] = TLSEXT_comp_cert_none; + if (s->post_handshake_auth == SSL_PHA_REQUESTED) return WORK_FINISHED_STOP; return WORK_FINISHED_CONTINUE; @@ -3587,6 +3648,97 @@ CON_FUNC_RETURN tls_construct_client_certificate(SSL_CONNECTION *s, return CON_FUNC_SUCCESS; } +#ifndef OPENSSL_NO_COMP_ALG +CON_FUNC_RETURN tls_construct_client_compressed_certificate(SSL_CONNECTION *sc, + WPACKET *pkt) +{ + SSL *ssl = SSL_CONNECTION_GET_SSL(sc); + WPACKET tmppkt; + BUF_MEM *buf = NULL; + size_t length; + size_t max_length; + COMP_METHOD *method; + COMP_CTX *comp = NULL; + int comp_len; + int ret = 0; + int alg = sc->ext.compress_certificate_from_peer[0]; + + /* Note that sc->s3.tmp.cert_req == 2 is checked in write transition */ + + if ((buf = BUF_MEM_new()) == NULL || !WPACKET_init(&tmppkt, buf)) + goto err; + + /* Use the |tmppkt| for the to-be-compressed data */ + if (sc->pha_context == NULL) { + /* no context available, add 0-length context */ + if (!WPACKET_put_bytes_u8(&tmppkt, 0)) + goto err; + } else if (!WPACKET_sub_memcpy_u8(&tmppkt, sc->pha_context, sc->pha_context_len)) + goto err; + + if (!ssl3_output_cert_chain(sc, &tmppkt, sc->cert->key)) { + /* SSLfatal() already called */ + goto out; + } + + /* continue with the real |pkt| */ + if (!WPACKET_put_bytes_u16(pkt, alg) + || !WPACKET_get_total_written(&tmppkt, &length) + || !WPACKET_put_bytes_u24(pkt, length)) + goto err; + + switch (alg) { + case TLSEXT_comp_cert_zlib: + method = COMP_zlib(); + break; + case TLSEXT_comp_cert_brotli: + method = COMP_brotli_oneshot(); + break; + case TLSEXT_comp_cert_zstd: + method = COMP_zstd_oneshot(); + break; + default: + goto err; + } + max_length = ossl_calculate_comp_expansion(alg, length); + + if (!WPACKET_start_sub_packet_u24(pkt) + || !WPACKET_reserve_bytes(pkt, max_length, NULL) + || (comp = COMP_CTX_new(method)) == NULL) + goto err; + + comp_len = COMP_compress_block(comp, WPACKET_get_curr(pkt), max_length, + (unsigned char *)buf->data, length); + if (comp_len <= 0) + goto err; + + if (!WPACKET_allocate_bytes(pkt, comp_len, NULL) + || !WPACKET_close(pkt)) + goto err; + + if (SSL_IS_FIRST_HANDSHAKE(sc) + && (!ssl->method->ssl3_enc->change_cipher_state(sc, + SSL3_CC_HANDSHAKE | SSL3_CHANGE_CIPHER_CLIENT_WRITE))) { + /* + * This is a fatal error, which leaves sc->enc_write_ctx in an + * inconsistent state and thus ssl3_send_alert may crash. + */ + SSLfatal(sc, SSL_AD_NO_ALERT, SSL_R_CANNOT_CHANGE_CIPHER); + goto out; + } + ret = 1; + goto out; + + err: + SSLfatal(sc, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + out: + WPACKET_cleanup(&tmppkt); + BUF_MEM_free(buf); + COMP_CTX_free(comp); + return ret; +} +#endif + int ssl3_check_cert_and_algorithm(SSL_CONNECTION *s) { const SSL_CERT_LOOKUP *clu; diff --git a/ssl/statem/statem_lib.c b/ssl/statem/statem_lib.c index 8b34c11048..69e900f90e 100644 --- a/ssl/statem/statem_lib.c +++ b/ssl/statem/statem_lib.c @@ -2466,3 +2466,76 @@ int tls13_restore_handshake_digest_for_pha(SSL_CONNECTION *s) } return 1; } + +#ifndef OPENSSL_NO_COMP_ALG +MSG_PROCESS_RETURN tls13_process_compressed_certificate(SSL_CONNECTION *sc, + PACKET *pkt, + PACKET *tmppkt, + BUF_MEM *buf) +{ + MSG_PROCESS_RETURN ret = MSG_PROCESS_ERROR; + int comp_alg; + COMP_METHOD *method = NULL; + COMP_CTX *comp = NULL; + size_t expected_length; + size_t comp_length; + int i; + int found = 0; + + if (buf == NULL) { + SSLfatal(sc, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + if (!PACKET_get_net_2(pkt, (unsigned int*)&comp_alg)) { + SSLfatal(sc, SSL_AD_BAD_CERTIFICATE, ERR_R_INTERNAL_ERROR); + goto err; + } + /* If we have a prefs list, make sure the algorithm is in it */ + if (sc->cert_comp_prefs[0] != TLSEXT_comp_cert_none) { + for (i = 0; sc->cert_comp_prefs[i] != TLSEXT_comp_cert_none; i++) { + if (sc->cert_comp_prefs[i] == comp_alg) { + found = 1; + break; + } + } + if (!found) { + SSLfatal(sc, SSL_AD_BAD_CERTIFICATE, SSL_R_BAD_COMPRESSION_ALGORITHM); + goto err; + } + } + if (!ossl_comp_has_alg(comp_alg)) { + SSLfatal(sc, SSL_AD_BAD_CERTIFICATE, SSL_R_BAD_COMPRESSION_ALGORITHM); + goto err; + } + switch (comp_alg) { + case TLSEXT_comp_cert_zlib: + method = COMP_zlib(); + break; + case TLSEXT_comp_cert_brotli: + method = COMP_brotli_oneshot(); + break; + case TLSEXT_comp_cert_zstd: + method = COMP_zstd_oneshot(); + break; + default: + SSLfatal(sc, SSL_AD_BAD_CERTIFICATE, SSL_R_BAD_COMPRESSION_ALGORITHM); + goto err; + } + + if (!PACKET_get_net_3_len(pkt, &expected_length) + || !PACKET_get_net_3_len(pkt, &comp_length) + || PACKET_remaining(pkt) != comp_length + || !BUF_MEM_grow(buf, expected_length) + || !PACKET_buf_init(tmppkt, (unsigned char *)buf->data, expected_length) + || (comp = COMP_CTX_new(method)) == NULL + || COMP_expand_block(comp, (unsigned char *)buf->data, expected_length, + (unsigned char*)PACKET_data(pkt), comp_length) != (int)expected_length) { + SSLfatal(sc, SSL_AD_BAD_CERTIFICATE, SSL_R_BAD_DECOMPRESSION); + goto err; + } + ret = MSG_PROCESS_CONTINUE_PROCESSING; + err: + COMP_CTX_free(comp); + return ret; +} +#endif diff --git a/ssl/statem/statem_local.h b/ssl/statem/statem_local.h index e5c6cfe535..75b7274c2e 100644 --- a/ssl/statem/statem_local.h +++ b/ssl/statem/statem_local.h @@ -129,6 +129,13 @@ __owur WORK_STATE tls_finish_handshake(SSL_CONNECTION *s, WORK_STATE wst, int clearbufs, int stop); __owur WORK_STATE dtls_wait_for_dry(SSL_CONNECTION *s); +#ifndef OPENSSL_NO_COMP_ALG +__owur MSG_PROCESS_RETURN tls13_process_compressed_certificate(SSL_CONNECTION *sc, + PACKET *pkt, + PACKET *tmppkt, + BUF_MEM *buf); +#endif + /* some client-only functions */ __owur CON_FUNC_RETURN tls_construct_client_hello(SSL_CONNECTION *s, WPACKET *pkt); @@ -149,6 +156,10 @@ __owur WORK_STATE tls_prepare_client_certificate(SSL_CONNECTION *s, WORK_STATE wst); __owur CON_FUNC_RETURN tls_construct_client_certificate(SSL_CONNECTION *s, WPACKET *pkt); +#ifndef OPENSSL_NO_COMP_ALG +__owur CON_FUNC_RETURN tls_construct_client_compressed_certificate(SSL_CONNECTION *sc, + WPACKET *pkt); +#endif __owur int ssl_do_client_cert_cb(SSL_CONNECTION *s, X509 **px509, EVP_PKEY **ppkey); __owur CON_FUNC_RETURN tls_construct_client_key_exchange(SSL_CONNECTION *s, @@ -163,6 +174,10 @@ __owur MSG_PROCESS_RETURN tls_process_server_certificate(SSL_CONNECTION *s, PACKET *pkt); __owur WORK_STATE tls_post_process_server_certificate(SSL_CONNECTION *s, WORK_STATE wst); +#ifndef OPENSSL_NO_COMP_ALG +__owur MSG_PROCESS_RETURN tls_process_server_compressed_certificate(SSL_CONNECTION *sc, + PACKET *pkt); +#endif __owur int ssl3_check_cert_and_algorithm(SSL_CONNECTION *s); #ifndef OPENSSL_NO_NEXTPROTONEG __owur CON_FUNC_RETURN tls_construct_next_proto(SSL_CONNECTION *s, WPACKET *pkt); @@ -183,6 +198,10 @@ __owur CON_FUNC_RETURN dtls_construct_hello_verify_request(SSL_CONNECTION *s, WPACKET *pkt); __owur CON_FUNC_RETURN tls_construct_server_certificate(SSL_CONNECTION *s, WPACKET *pkt); +#ifndef OPENSSL_NO_COMP_ALG +__owur CON_FUNC_RETURN tls_construct_server_compressed_certificate(SSL_CONNECTION *sc, + WPACKET *pkt); +#endif __owur CON_FUNC_RETURN tls_construct_server_key_exchange(SSL_CONNECTION *s, WPACKET *pkt); __owur CON_FUNC_RETURN tls_construct_certificate_request(SSL_CONNECTION *s, @@ -191,6 +210,10 @@ __owur CON_FUNC_RETURN tls_construct_server_done(SSL_CONNECTION *s, WPACKET *pkt); __owur MSG_PROCESS_RETURN tls_process_client_certificate(SSL_CONNECTION *s, PACKET *pkt); +#ifndef OPENSSL_NO_COMP_ALG +__owur MSG_PROCESS_RETURN tls_process_client_compressed_certificate(SSL_CONNECTION *sc, + PACKET *pkt); +#endif __owur MSG_PROCESS_RETURN tls_process_client_key_exchange(SSL_CONNECTION *s, PACKET *pkt); __owur WORK_STATE tls_post_process_client_key_exchange(SSL_CONNECTION *s, diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c index ab1574419e..0d68e4b1cf 100644 --- a/ssl/statem/statem_srvr.c +++ b/ssl/statem/statem_srvr.c @@ -26,6 +26,7 @@ #include #include #include +#include #define TICKET_NONCE_SIZE 8 @@ -91,6 +92,13 @@ static int ossl_statem_server13_read_transition(SSL_CONNECTION *s, int mt) st->hand_state = TLS_ST_SR_CERT; return 1; } +#ifndef OPENSSL_NO_COMP_ALG + if (mt == SSL3_MT_COMPRESSED_CERTIFICATE + && s->ext.compress_certificate_sent) { + st->hand_state = TLS_ST_SR_COMP_CERT; + return 1; + } +#endif } else { if (mt == SSL3_MT_FINISHED) { st->hand_state = TLS_ST_SR_FINISHED; @@ -99,6 +107,7 @@ static int ossl_statem_server13_read_transition(SSL_CONNECTION *s, int mt) } break; + case TLS_ST_SR_COMP_CERT: case TLS_ST_SR_CERT: if (s->session->peer == NULL) { if (mt == SSL3_MT_FINISHED) { @@ -128,10 +137,18 @@ static int ossl_statem_server13_read_transition(SSL_CONNECTION *s, int mt) if (s->early_data_state == SSL_EARLY_DATA_READING) break; - if (mt == SSL3_MT_CERTIFICATE - && s->post_handshake_auth == SSL_PHA_REQUESTED) { - st->hand_state = TLS_ST_SR_CERT; - return 1; + if (s->post_handshake_auth == SSL_PHA_REQUESTED) { + if (mt == SSL3_MT_CERTIFICATE) { + st->hand_state = TLS_ST_SR_CERT; + return 1; + } +#ifndef OPENSSL_NO_COMP_ALG + if (mt == SSL3_MT_COMPRESSED_CERTIFICATE + && s->ext.compress_certificate_sent) { + st->hand_state = TLS_ST_SR_COMP_CERT; + return 1; + } +#endif } if (mt == SSL3_MT_KEY_UPDATE) { @@ -356,6 +373,27 @@ static int send_server_key_exchange(SSL_CONNECTION *s) return 0; } +/* + * Used to determine if we shoud send a CompressedCertificate message + * + * Returns the algorithm to use, TLSEXT_comp_cert_none means no compression + */ +static int get_compressed_certificate_alg(SSL_CONNECTION *sc) +{ +#ifndef OPENSSL_NO_COMP_ALG + int *alg = sc->ext.compress_certificate_from_peer; + + if (sc->s3.tmp.cert == NULL) + return TLSEXT_comp_cert_none; + + for (; *alg != TLSEXT_comp_cert_none; alg++) { + if (sc->s3.tmp.cert->comp_cert[*alg] != NULL) + return *alg; + } +#endif + return TLSEXT_comp_cert_none; +} + /* * Should we send a CertificateRequest message? * @@ -468,6 +506,8 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL_CONNECTION *s) st->hand_state = TLS_ST_SW_FINISHED; else if (send_certificate_request(s)) st->hand_state = TLS_ST_SW_CERT_REQ; + else if (get_compressed_certificate_alg(s) != TLSEXT_comp_cert_none) + st->hand_state = TLS_ST_SW_COMP_CERT; else st->hand_state = TLS_ST_SW_CERT; @@ -477,11 +517,14 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL_CONNECTION *s) if (s->post_handshake_auth == SSL_PHA_REQUEST_PENDING) { s->post_handshake_auth = SSL_PHA_REQUESTED; st->hand_state = TLS_ST_OK; + } else if (get_compressed_certificate_alg(s) != TLSEXT_comp_cert_none) { + st->hand_state = TLS_ST_SW_COMP_CERT; } else { st->hand_state = TLS_ST_SW_CERT; } return WRITE_TRAN_CONTINUE; + case TLS_ST_SW_COMP_CERT: case TLS_ST_SW_CERT: st->hand_state = TLS_ST_SW_CERT_VRFY; return WRITE_TRAN_CONTINUE; @@ -975,6 +1018,18 @@ WORK_STATE ossl_statem_server_post_work(SSL_CONNECTION *s, WORK_STATE wst) if (s->post_handshake_auth == SSL_PHA_REQUEST_PENDING) { if (statem_flush(s) != 1) return WORK_MORE_A; + } else { + if (!SSL_CONNECTION_IS_TLS13(s) + || (s->options & SSL_OP_NO_TX_CERTIFICATE_COMPRESSION) != 0) + s->ext.compress_certificate_from_peer[0] = TLSEXT_comp_cert_none; + } + break; + + case TLS_ST_SW_ENCRYPTED_EXTENSIONS: + if (!s->hit && !send_certificate_request(s)) { + if (!SSL_CONNECTION_IS_TLS13(s) + || (s->options & SSL_OP_NO_TX_CERTIFICATE_COMPRESSION) != 0) + s->ext.compress_certificate_from_peer[0] = TLSEXT_comp_cert_none; } break; @@ -1059,6 +1114,13 @@ int ossl_statem_server_construct_message(SSL_CONNECTION *s, *mt = SSL3_MT_CERTIFICATE; break; +#ifndef OPENSSL_NO_COMP_ALG + case TLS_ST_SW_COMP_CERT: + *confunc = tls_construct_server_compressed_certificate; + *mt = SSL3_MT_COMPRESSED_CERTIFICATE; + break; +#endif + case TLS_ST_SW_CERT_VRFY: *confunc = tls_construct_cert_verify; *mt = SSL3_MT_CERTIFICATE_VERIFY; @@ -1153,6 +1215,7 @@ size_t ossl_statem_server_max_message_size(SSL_CONNECTION *s) case TLS_ST_SR_END_OF_EARLY_DATA: return END_OF_EARLY_DATA_MAX_LENGTH; + case TLS_ST_SR_COMP_CERT: case TLS_ST_SR_CERT: return s->max_cert_list; @@ -1201,6 +1264,11 @@ MSG_PROCESS_RETURN ossl_statem_server_process_message(SSL_CONNECTION *s, case TLS_ST_SR_CERT: return tls_process_client_certificate(s, pkt); +#ifndef OPENSSL_NO_COMP_ALG + case TLS_ST_SR_COMP_CERT: + return tls_process_client_compressed_certificate(s, pkt); +#endif + case TLS_ST_SR_KEY_EXCH: return tls_process_client_key_exchange(s, pkt); @@ -3633,6 +3701,21 @@ MSG_PROCESS_RETURN tls_process_client_certificate(SSL_CONNECTION *s, return ret; } +#ifndef OPENSSL_NO_COMP_ALG +MSG_PROCESS_RETURN tls_process_client_compressed_certificate(SSL_CONNECTION *sc, PACKET *pkt) +{ + MSG_PROCESS_RETURN ret = MSG_PROCESS_ERROR; + PACKET tmppkt; + BUF_MEM *buf = BUF_MEM_new(); + + if (tls13_process_compressed_certificate(sc, pkt, &tmppkt, buf) != MSG_PROCESS_ERROR) + ret = tls_process_client_certificate(sc, &tmppkt); + + BUF_MEM_free(buf); + return ret; +} +#endif + CON_FUNC_RETURN tls_construct_server_certificate(SSL_CONNECTION *s, WPACKET *pkt) { CERT_PKEY *cpk = s->s3.tmp.cert; @@ -3658,6 +3741,32 @@ CON_FUNC_RETURN tls_construct_server_certificate(SSL_CONNECTION *s, WPACKET *pkt return CON_FUNC_SUCCESS; } +#ifndef OPENSSL_NO_COMP_ALG +CON_FUNC_RETURN tls_construct_server_compressed_certificate(SSL_CONNECTION *sc, WPACKET *pkt) +{ + int alg = get_compressed_certificate_alg(sc); + OSSL_COMP_CERT *cc = sc->s3.tmp.cert->comp_cert[alg]; + + if (!ossl_assert(cc != NULL)) { + SSLfatal(sc, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + /* + * Server can't compress on-demand + * Use pre-compressed certificate + */ + if (!WPACKET_put_bytes_u16(pkt, alg) + || !WPACKET_put_bytes_u24(pkt, cc->orig_len) + || !WPACKET_start_sub_packet_u24(pkt) + || !WPACKET_memcpy(pkt, cc->data, cc->len) + || !WPACKET_close(pkt)) + return 0; + + sc->s3.tmp.cert->cert_comp_used++; + return 1; +} +#endif + static int create_ticket_prequel(SSL_CONNECTION *s, WPACKET *pkt, uint32_t age_add, unsigned char *tick_nonce) { diff --git a/ssl/t1_trce.c b/ssl/t1_trce.c index 802958c383..1a032b3137 100644 --- a/ssl/t1_trce.c +++ b/ssl/t1_trce.c @@ -97,6 +97,7 @@ static const ssl_trace_tbl ssl_handshake_tbl[] = { {SSL3_MT_CERTIFICATE_STATUS, "CertificateStatus"}, {SSL3_MT_SUPPLEMENTAL_DATA, "SupplementalData"}, {SSL3_MT_KEY_UPDATE, "KeyUpdate"}, + {SSL3_MT_COMPRESSED_CERTIFICATE, "CompressedCertificate"}, # ifndef OPENSSL_NO_NEXTPROTONEG {SSL3_MT_NEXT_PROTO, "NextProto"}, # endif @@ -478,6 +479,7 @@ static const ssl_trace_tbl ssl_exts_tbl[] = { {TLSEXT_TYPE_padding, "padding"}, {TLSEXT_TYPE_encrypt_then_mac, "encrypt_then_mac"}, {TLSEXT_TYPE_extended_master_secret, "extended_master_secret"}, + {TLSEXT_TYPE_compress_certificate, "compress_certificate"}, {TLSEXT_TYPE_session_ticket, "session_ticket"}, {TLSEXT_TYPE_psk, "psk"}, {TLSEXT_TYPE_early_data, "early_data"}, @@ -617,6 +619,13 @@ static const ssl_trace_tbl ssl_key_update_tbl[] = { {SSL_KEY_UPDATE_REQUESTED, "update_requested"} }; +static const ssl_trace_tbl ssl_comp_cert_tbl[] = { + {TLSEXT_comp_cert_none, "none"}, + {TLSEXT_comp_cert_zlib, "zlib"}, + {TLSEXT_comp_cert_brotli, "brotli"}, + {TLSEXT_comp_cert_zstd, "zstd"} +}; + static void ssl_print_hex(BIO *bio, int indent, const char *name, const unsigned char *msg, size_t msglen) { @@ -721,6 +730,14 @@ static int ssl_print_extension(BIO *bio, int indent, int server, BIO_printf(bio, "extension_type=%s(%d), length=%d\n", ssl_trace_str(extype, ssl_exts_tbl), extype, (int)extlen); switch (extype) { + case TLSEXT_TYPE_compress_certificate: + if (extlen < 1) + return 0; + xlen = ext[0]; + if (extlen != xlen + 1) + return 0; + return ssl_trace_list(bio, indent + 2, ext + 1, xlen, 2, ssl_comp_cert_tbl); + case TLSEXT_TYPE_max_fragment_length: if (extlen < 1) return 0; @@ -1287,6 +1304,73 @@ static int ssl_print_certificates(BIO *bio, const SSL_CONNECTION *sc, int server return 1; } +static int ssl_print_compressed_certificates(BIO *bio, const SSL_CONNECTION *sc, + int server, int indent, + const unsigned char *msg, + size_t msglen) +{ + size_t uclen; + size_t clen; + unsigned int alg; + int ret = 1; +#ifndef OPENSSL_NO_COMP_ALG + COMP_METHOD *method; + COMP_CTX *comp = NULL; + unsigned char* ucdata = NULL; +#endif + + if (msglen < 8) + return 0; + + alg = (msg[0] << 8) | msg[1]; + uclen = (msg[2] << 16) | (msg[3] << 8) | msg[4]; + clen = (msg[5] << 16) | (msg[6] << 8) | msg[7]; + if (msglen != clen + 8) + return 0; + + msg += 8; + BIO_indent(bio, indent, 80); + BIO_printf(bio, "Compression type=%s (0x%04x)\n", ssl_trace_str(alg, ssl_comp_cert_tbl), alg); + BIO_indent(bio, indent, 80); + BIO_printf(bio, "Uncompressed length=%d\n", (int)uclen); + BIO_indent(bio, indent, 80); + BIO_printf(bio, "Compressed length=%d, Ratio=%f:1\n", (int)clen, (float)uclen / (float)clen); + + BIO_dump_indent(bio, (const char *)msg, clen, indent); + +#ifndef OPENSSL_NO_COMP_ALG + if (!ossl_comp_has_alg(alg)) + return 0; + + if ((ucdata = OPENSSL_malloc(uclen)) == NULL) + return 0; + + switch (alg) { + case TLSEXT_comp_cert_zlib: + method = COMP_zlib(); + break; + case TLSEXT_comp_cert_brotli: + method = COMP_brotli_oneshot(); + break; + case TLSEXT_comp_cert_zstd: + method = COMP_zstd_oneshot(); + break; + default: + goto err; + } + + if ((comp = COMP_CTX_new(method)) == NULL + || COMP_expand_block(comp, ucdata, uclen, (unsigned char*)msg, clen) != (int)uclen) + goto err; + + ret = ssl_print_certificates(bio, sc, server, indent, ucdata, uclen); + err: + COMP_CTX_free(comp); + OPENSSL_free(ucdata); +#endif + return ret; +} + static int ssl_print_cert_request(BIO *bio, int indent, const SSL_CONNECTION *sc, const unsigned char *msg, size_t msglen) { @@ -1486,6 +1570,11 @@ static int ssl_print_handshake(BIO *bio, const SSL_CONNECTION *sc, int server, return 0; break; + case SSL3_MT_COMPRESSED_CERTIFICATE: + if (!ssl_print_compressed_certificates(bio, sc, server, indent + 2, msg, msglen)) + return 0; + break; + case SSL3_MT_CERTIFICATE_VERIFY: if (!ssl_print_signature(bio, indent + 2, sc, &msg, &msglen)) return 0; diff --git a/test/bio_comp_test.c b/test/bio_comp_test.c index ab56e981f5..5dd13abd36 100644 --- a/test/bio_comp_test.c +++ b/test/bio_comp_test.c @@ -44,6 +44,8 @@ static int do_bio_comp_test(const BIO_METHOD *meth, size_t size) int ret = 0; /* Compress */ + if (!TEST_ptr(meth)) + goto err; if (!TEST_ptr(bcomp = BIO_new(meth))) goto err; if (!TEST_ptr(bmem = BIO_new(BIO_s_mem()))) diff --git a/test/build.info b/test/build.info index 6994131275..4a6fc835e1 100644 --- a/test/build.info +++ b/test/build.info @@ -73,6 +73,10 @@ IF[{- !$disabled{tests} -}] PROGRAMS{noinst}=priority_queue_test event_queue_test ENDIF + IF[{- !$disabled{comp} && (!$disabled{brotli} || !$disabled{zstd} || !$disabled{zlib}) -}] + PROGRAMS{noinst}=cert_comp_test + ENDIF + SOURCE[confdump]=confdump.c INCLUDE[confdump]=../include ../apps/include DEPEND[confdump]=../libcrypto @@ -1022,6 +1026,10 @@ ENDIF INCLUDE[quic_ackm_test]=../include ../apps/include DEPEND[quic_ackm_test]=../libcrypto.a ../libssl.a libtestutil.a + SOURCE[cert_comp_test]=cert_comp_test.c helpers/ssltestlib.c + INCLUDE[cert_comp_test]=../include ../apps/include .. + DEPEND[cert_comp_test]=../libcrypto ../libssl libtestutil.a + {- use File::Spec::Functions; use File::Basename; diff --git a/test/cert_comp_test.c b/test/cert_comp_test.c new file mode 100644 index 0000000000..66320bddd4 --- /dev/null +++ b/test/cert_comp_test.c @@ -0,0 +1,281 @@ +/* + * Copyright 2016-2022 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * We need access to the deprecated low level HMAC APIs for legacy purposes + * when the deprecated calls are not hidden + */ +#ifndef OPENSSL_NO_DEPRECATED_3_0 +# define OPENSSL_SUPPRESS_DEPRECATED +#endif + +#include +#include "internal/nelem.h" +#include "helpers/ssltestlib.h" +#include "testutil.h" +#include "../ssl/ssl_local.h" + +#undef OSSL_NO_USABLE_TLS1_3 +#if defined(OPENSSL_NO_TLS1_3) \ + || (defined(OPENSSL_NO_EC) && defined(OPENSSL_NO_DH)) +/* + * If we don't have ec or dh then there are no built-in groups that are usable + * with TLSv1.3 + */ +# define OSSL_NO_USABLE_TLS1_3 +#endif + +#if !defined(OSSL_NO_USEABLE_TLS1_3) + +static char *certsdir = NULL; +static char *cert = NULL; +static char *privkey = NULL; + +static int client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey) +{ + X509 *xcert; + EVP_PKEY *privpkey; + BIO *in = NULL; + BIO *priv_in = NULL; + + /* Check that SSL_get0_peer_certificate() returns something sensible */ + if (!TEST_ptr(SSL_get0_peer_certificate(ssl))) + return 0; + + in = BIO_new_file(cert, "r"); + if (!TEST_ptr(in)) + return 0; + + if (!TEST_ptr(xcert = X509_new_ex(NULL, NULL)) + || !TEST_ptr(PEM_read_bio_X509(in, &xcert, NULL, NULL)) + || !TEST_ptr(priv_in = BIO_new_file(privkey, "r")) + || !TEST_ptr(privpkey = PEM_read_bio_PrivateKey_ex(priv_in, NULL, + NULL, NULL, + NULL, NULL))) + goto err; + + *x509 = xcert; + *pkey = privpkey; + + BIO_free(in); + BIO_free(priv_in); + return 1; +err: + X509_free(xcert); + BIO_free(in); + BIO_free(priv_in); + return 0; +} + +static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + return 1; +} + +/* + * Test 0 = app pre-compresses certificate in SSL + * Test 1 = app pre-compresses certificate in SSL_CTX + * Test 2 = app pre-compresses certificate in SSL_CTX, client authentication + * Test 3 = app pre-compresses certificate in SSL_CTX, but it's unused due to prefs + */ +/* Compression helper */ +static int ssl_comp_cert(SSL *ssl, int alg) +{ + unsigned char *comp_data = NULL; + size_t comp_len = 0; + size_t orig_len = 0; + int retval = 0; + + if (!TEST_size_t_gt(comp_len = SSL_get1_compressed_cert(ssl, alg, &comp_data, &orig_len), 0)) + goto err; + + if (!TEST_true(SSL_set1_compressed_cert(ssl, alg, comp_data, comp_len, orig_len))) + goto err; + retval = alg; + + err: + OPENSSL_free(comp_data); + return retval; +} + +static void cert_comp_info_cb(const SSL *s, int where, int ret) +{ + int *seen = (int*)SSL_get_app_data(s); + + if (SSL_is_server(s)) { + /* TLS_ST_SR_COMP_CERT */ + if (!strcmp(SSL_state_string(s), "TRCCC") && seen != NULL) + *seen = 1; + } else { + /* TLS_ST_CR_COMP_CERT */ + if (!strcmp(SSL_state_string(s), "TRSCC") && seen != NULL) + *seen = 1; + } +} + +static int test_ssl_cert_comp(int test) +{ + SSL_CTX *cctx = NULL, *sctx = NULL; + SSL *clientssl = NULL, *serverssl = NULL; + int testresult = 0; + int expected_client = TLSEXT_comp_cert_none; + int expected_server = TLSEXT_comp_cert_none; + int client_seen = 0; + int server_seen = 0; + /* reverse default order */ + int server_pref[] = { TLSEXT_comp_cert_zstd, TLSEXT_comp_cert_zlib, TLSEXT_comp_cert_brotli }; + /* default order */ + int client_pref[] = { TLSEXT_comp_cert_brotli, TLSEXT_comp_cert_zlib, TLSEXT_comp_cert_zstd }; + + /* one of these *must* be defined! */ +#ifndef OPENSSL_NO_BROTLI + expected_server = TLSEXT_comp_cert_brotli; + expected_client = TLSEXT_comp_cert_brotli; +#endif +#ifndef OPENSSL_NO_ZLIB + expected_server = TLSEXT_comp_cert_zlib; + if (expected_client == TLSEXT_comp_cert_none) + expected_client = TLSEXT_comp_cert_zlib; +#endif +#ifndef OPENSSL_NO_ZSTD + expected_server = TLSEXT_comp_cert_zstd; + if (expected_client == TLSEXT_comp_cert_none) + expected_client = TLSEXT_comp_cert_zstd; +#endif + /* if there's only one comp algorithm, pref won't do much */ + if (test == 3 && expected_client == expected_server) { + TEST_info("Only one compression algorithm configured"); + return 1; + } + + if (!TEST_true(create_ssl_ctx_pair(NULL, TLS_server_method(), + TLS_client_method(), + TLS1_3_VERSION, 0, + &sctx, &cctx, cert, privkey))) + goto end; + if (test == 3) { + server_pref[0] = expected_server; + server_pref[1] = expected_client; + if (!TEST_true(SSL_CTX_set1_cert_comp_preference(sctx, server_pref, 2))) + goto end; + client_pref[0] = expected_client; + if (!TEST_true(SSL_CTX_set1_cert_comp_preference(cctx, client_pref, 1))) + goto end; + } else { + if (!TEST_true(SSL_CTX_set1_cert_comp_preference(sctx, server_pref, OSSL_NELEM(server_pref)))) + goto end; + if (!TEST_true(SSL_CTX_set1_cert_comp_preference(cctx, client_pref, OSSL_NELEM(client_pref)))) + goto end; + } + if (test == 2) { + /* Use callbacks from test_client_cert_cb() */ + SSL_CTX_set_client_cert_cb(cctx, client_cert_cb); + SSL_CTX_set_verify(sctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb); + } + + if (test == 1 || test== 2 || test == 3) { + if (!TEST_true(SSL_CTX_compress_certs(sctx, expected_server))) + goto end; + } + + if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl, + NULL, NULL))) + goto end; + + if (!TEST_true(SSL_set_app_data(clientssl, &client_seen))) + goto end; + if (!TEST_true(SSL_set_app_data(serverssl, &server_seen))) + goto end; + SSL_set_info_callback(clientssl, cert_comp_info_cb); + SSL_set_info_callback(serverssl, cert_comp_info_cb); + + if (test == 0) { + if (!TEST_int_eq(ssl_comp_cert(serverssl, expected_server), expected_server)) + goto end; + } + + if (!TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE))) + goto end; + if (test == 3) { + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(serverssl); + + /* expect that the pre-compressed cert won't be used */ + if (!TEST_int_eq(sc->cert->key->cert_comp_used, 0)) + goto end; + + if (!TEST_false(*(int*)SSL_get_app_data(clientssl))) + goto end; + } else { + SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(serverssl); + + if (!TEST_int_gt(sc->cert->key->cert_comp_used, 0)) + goto end; + + if (!TEST_true(*(int*)SSL_get_app_data(clientssl))) + goto end; + } + + if (test == 2) { + /* Only for client auth */ + if (!TEST_true(*(int*)SSL_get_app_data(serverssl))) + goto end; + } + + testresult = 1; + + end: + SSL_free(serverssl); + SSL_free(clientssl); + SSL_CTX_free(sctx); + SSL_CTX_free(cctx); + + return testresult; +} +#endif + +OPT_TEST_DECLARE_USAGE("certdir\n") + +int setup_tests(void) +{ +#if !defined(OSSL_NO_USEABLE_TLS1_3) + if (!test_skip_common_options()) { + TEST_error("Error parsing test options\n"); + return 0; + } + + if (!TEST_ptr(certsdir = test_get_argument(0))) + return 0; + + cert = test_mk_file_path(certsdir, "servercert.pem"); + if (cert == NULL) + goto err; + + privkey = test_mk_file_path(certsdir, "serverkey.pem"); + if (privkey == NULL) + goto err; + + ADD_ALL_TESTS(test_ssl_cert_comp, 4); + return 1; + + err: + OPENSSL_free(cert); + OPENSSL_free(privkey); + return 0; +#else + return 1; +#endif +} + +void cleanup_tests(void) +{ +#if !defined(OSSL_NO_USEABLE_TLS1_3) + OPENSSL_free(cert); + OPENSSL_free(privkey); +#endif +} diff --git a/test/ext_internal_test.c b/test/ext_internal_test.c index dc1420aba8..97a538abf9 100644 --- a/test/ext_internal_test.c +++ b/test/ext_internal_test.c @@ -67,6 +67,7 @@ static EXT_LIST ext_list[] = { EXT_ENTRY(key_share), EXT_ENTRY(cookie), EXT_ENTRY(cryptopro_bug), + EXT_ENTRY(compress_certificate), EXT_ENTRY(early_data), EXT_ENTRY(certificate_authorities), EXT_ENTRY(padding), diff --git a/test/helpers/handshake.c b/test/helpers/handshake.c index fc7f026300..e0422469e4 100644 --- a/test/helpers/handshake.c +++ b/test/helpers/handshake.c @@ -695,6 +695,14 @@ static int configure_handshake_ctx(SSL_CTX *server_ctx, SSL_CTX *server2_ctx, server2_ctx_data, client_ctx_data)) goto err; #endif /* !OPENSSL_NO_SRP */ +#ifndef OPENSSL_NO_COMP_ALG + if (test->compress_certificates) { + if (!TEST_true(SSL_CTX_compress_certs(server_ctx, 0))) + goto err; + if (server2_ctx != NULL && !TEST_true(SSL_CTX_compress_certs(server2_ctx, 0))) + goto err; + } +#endif return 1; err: return 0; diff --git a/test/helpers/ssl_test_ctx.c b/test/helpers/ssl_test_ctx.c index 69baa0b49f..1919481b02 100644 --- a/test/helpers/ssl_test_ctx.c +++ b/test/helpers/ssl_test_ctx.c @@ -446,6 +446,7 @@ const char *ssl_ct_validation_name(ssl_ct_validation_t mode) IMPLEMENT_SSL_TEST_BOOL_OPTION(SSL_TEST_CTX, test, resumption_expected) IMPLEMENT_SSL_TEST_BOOL_OPTION(SSL_TEST_SERVER_CONF, server, broken_session_ticket) IMPLEMENT_SSL_TEST_BOOL_OPTION(SSL_TEST_CTX, test, use_sctp) +IMPLEMENT_SSL_TEST_BOOL_OPTION(SSL_TEST_CTX, test, compress_certificates) IMPLEMENT_SSL_TEST_BOOL_OPTION(SSL_TEST_CTX, test, enable_client_sctp_label_bug) IMPLEMENT_SSL_TEST_BOOL_OPTION(SSL_TEST_CTX, test, enable_server_sctp_label_bug) @@ -686,6 +687,7 @@ static const ssl_test_ctx_option ssl_test_ctx_options[] = { { "ExpectedClientSignType", &parse_expected_client_sign_type }, { "ExpectedClientCANames", &parse_expected_client_ca_names }, { "UseSCTP", &parse_test_use_sctp }, + { "CompressCertificates", &parse_test_compress_certificates }, { "EnableClientSCTPLabelBug", &parse_test_enable_client_sctp_label_bug }, { "EnableServerSCTPLabelBug", &parse_test_enable_server_sctp_label_bug }, { "ExpectedCipher", &parse_test_expected_cipher }, diff --git a/test/helpers/ssl_test_ctx.h b/test/helpers/ssl_test_ctx.h index c7820d9764..4baf657f6d 100644 --- a/test/helpers/ssl_test_ctx.h +++ b/test/helpers/ssl_test_ctx.h @@ -218,6 +218,8 @@ typedef struct { STACK_OF(X509_NAME) *expected_client_ca_names; /* Whether to use SCTP for the transport */ int use_sctp; + /* Whether to pre-compress server certificates */ + int compress_certificates; /* Enable SSL_MODE_DTLS_SCTP_LABEL_LENGTH_BUG on client side */ int enable_client_sctp_label_bug; /* Enable SSL_MODE_DTLS_SCTP_LABEL_LENGTH_BUG on server side */ diff --git a/test/recipes/70-test_tls13certcomp.t b/test/recipes/70-test_tls13certcomp.t new file mode 100644 index 0000000000..bc960c8b37 --- /dev/null +++ b/test/recipes/70-test_tls13certcomp.t @@ -0,0 +1,295 @@ +#! /usr/bin/env perl +# Copyright 2015-2020 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + +use strict; +use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file srctop_dir bldtop_dir/; +use OpenSSL::Test::Utils; +use File::Temp qw(tempfile); +use TLSProxy::Proxy; +use checkhandshake qw(checkhandshake @handmessages @extensions); + +my $test_name = "test_tls13certcomp"; +setup($test_name); + +plan skip_all => "TLSProxy isn't usable on $^O" + if $^O =~ /^(VMS)$/; + +plan skip_all => "$test_name needs the dynamic engine feature enabled" + if disabled("engine") || disabled("dynamic-engine"); + +plan skip_all => "$test_name needs the sock feature enabled" + if disabled("sock"); + +plan skip_all => "$test_name needs TLSv1.3 enabled" + if disabled("tls1_3"); + +plan skip_all => "$test_name needs EC enabled" + if disabled("ec"); + +plan skip_all => "$test_name needs compression and algorithms enabled" + if disabled("comp") || (disabled("brotli") && disabled("zlib") && disabled("zstd")); + +@handmessages = ( + [TLSProxy::Message::MT_CLIENT_HELLO, + checkhandshake::ALL_HANDSHAKES], + [TLSProxy::Message::MT_SERVER_HELLO, + checkhandshake::ALL_HANDSHAKES], + [TLSProxy::Message::MT_ENCRYPTED_EXTENSIONS, + checkhandshake::ALL_HANDSHAKES], + [TLSProxy::Message::MT_CERTIFICATE_REQUEST, + checkhandshake::CERT_COMP_CLI_HANDSHAKE | checkhandshake::CERT_COMP_BOTH_HANDSHAKE], + [TLSProxy::Message::MT_CERTIFICATE, + checkhandshake::ALL_HANDSHAKES & ~(checkhandshake::CERT_COMP_SRV_HANDSHAKE | checkhandshake::CERT_COMP_BOTH_HANDSHAKE)], + [TLSProxy::Message::MT_COMPRESSED_CERTIFICATE, + checkhandshake::CERT_COMP_SRV_HANDSHAKE | checkhandshake::CERT_COMP_BOTH_HANDSHAKE], + [TLSProxy::Message::MT_CERTIFICATE_VERIFY, + checkhandshake::ALL_HANDSHAKES], + [TLSProxy::Message::MT_FINISHED, + checkhandshake::ALL_HANDSHAKES], + [TLSProxy::Message::MT_COMPRESSED_CERTIFICATE, + checkhandshake::CERT_COMP_CLI_HANDSHAKE | checkhandshake::CERT_COMP_BOTH_HANDSHAKE], + [TLSProxy::Message::MT_CERTIFICATE_VERIFY, + checkhandshake::CERT_COMP_CLI_HANDSHAKE | checkhandshake::CERT_COMP_BOTH_HANDSHAKE], + [TLSProxy::Message::MT_FINISHED, + checkhandshake::ALL_HANDSHAKES], + [0, 0] +); + +@extensions = ( + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SERVER_NAME, + TLSProxy::Message::CLIENT, + checkhandshake::SERVER_NAME_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_STATUS_REQUEST, + TLSProxy::Message::CLIENT, + checkhandshake::STATUS_REQUEST_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SUPPORTED_GROUPS, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_EC_POINT_FORMATS, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SIG_ALGS, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_ALPN, + TLSProxy::Message::CLIENT, + checkhandshake::ALPN_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SCT, + TLSProxy::Message::CLIENT, + checkhandshake::SCT_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_ENCRYPT_THEN_MAC, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_EXTENDED_MASTER_SECRET, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SESSION_TICKET, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_KEY_SHARE, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SUPPORTED_VERSIONS, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_PSK_KEX_MODES, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_PSK, + TLSProxy::Message::CLIENT, + checkhandshake::PSK_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_POST_HANDSHAKE_AUTH, + TLSProxy::Message::CLIENT, + checkhandshake::POST_HANDSHAKE_AUTH_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_COMPRESS_CERTIFICATE, + TLSProxy::Message::CLIENT, + checkhandshake::CERT_COMP_CLI_EXTENSION], + + [TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_SUPPORTED_VERSIONS, + TLSProxy::Message::SERVER, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_KEY_SHARE, + TLSProxy::Message::SERVER, + checkhandshake::KEY_SHARE_HRR_EXTENSION], + + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SERVER_NAME, + TLSProxy::Message::CLIENT, + checkhandshake::SERVER_NAME_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_STATUS_REQUEST, + TLSProxy::Message::CLIENT, + checkhandshake::STATUS_REQUEST_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SUPPORTED_GROUPS, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_EC_POINT_FORMATS, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SIG_ALGS, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_ALPN, + TLSProxy::Message::CLIENT, + checkhandshake::ALPN_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SCT, + TLSProxy::Message::CLIENT, + checkhandshake::SCT_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_ENCRYPT_THEN_MAC, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_EXTENDED_MASTER_SECRET, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SESSION_TICKET, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_KEY_SHARE, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SUPPORTED_VERSIONS, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_PSK_KEX_MODES, + TLSProxy::Message::CLIENT, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_PSK, + TLSProxy::Message::CLIENT, + checkhandshake::PSK_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_POST_HANDSHAKE_AUTH, + TLSProxy::Message::CLIENT, + checkhandshake::POST_HANDSHAKE_AUTH_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_COMPRESS_CERTIFICATE, + TLSProxy::Message::CLIENT, + checkhandshake::CERT_COMP_CLI_EXTENSION], + + [TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_SUPPORTED_VERSIONS, + TLSProxy::Message::SERVER, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_KEY_SHARE, + TLSProxy::Message::SERVER, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_PSK, + TLSProxy::Message::SERVER, + checkhandshake::PSK_SRV_EXTENSION], + + [TLSProxy::Message::MT_ENCRYPTED_EXTENSIONS, TLSProxy::Message::EXT_SERVER_NAME, + TLSProxy::Message::SERVER, + checkhandshake::SERVER_NAME_SRV_EXTENSION], + [TLSProxy::Message::MT_ENCRYPTED_EXTENSIONS, TLSProxy::Message::EXT_ALPN, + TLSProxy::Message::SERVER, + checkhandshake::ALPN_SRV_EXTENSION], + [TLSProxy::Message::MT_ENCRYPTED_EXTENSIONS, TLSProxy::Message::EXT_SUPPORTED_GROUPS, + TLSProxy::Message::SERVER, + checkhandshake::SUPPORTED_GROUPS_SRV_EXTENSION], + + [TLSProxy::Message::MT_CERTIFICATE_REQUEST, TLSProxy::Message::EXT_SIG_ALGS, + TLSProxy::Message::SERVER, + checkhandshake::DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CERTIFICATE_REQUEST, TLSProxy::Message::EXT_COMPRESS_CERTIFICATE, + TLSProxy::Message::SERVER, + checkhandshake::CERT_COMP_SRV_EXTENSION], + + [TLSProxy::Message::MT_CERTIFICATE, TLSProxy::Message::EXT_STATUS_REQUEST, + TLSProxy::Message::SERVER, + checkhandshake::STATUS_REQUEST_SRV_EXTENSION], + [TLSProxy::Message::MT_CERTIFICATE, TLSProxy::Message::EXT_SCT, + TLSProxy::Message::SERVER, + checkhandshake::SCT_SRV_EXTENSION], + + [0,0,0,0] +); + +my $proxy = TLSProxy::Proxy->new( + undef, + cmdstr(app(["openssl"]), display => 1), + srctop_file("apps", "server.pem"), + (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE}) +); + + +#Test 1: Client sends cert comp, but no client auth +$proxy->serverconnects(2); +$proxy->clear(); +$proxy->serverflags("-no_tx_cert_comp -no_rx_cert_comp"); +# One final skip check +$proxy->start() or plan skip_all => "Unable to start up Proxy for tests"; +plan tests => 8; +checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, + checkhandshake::DEFAULT_EXTENSIONS + | checkhandshake::CERT_COMP_CLI_EXTENSION, + "Client supports certificate compression"); + +#Test 2: Server sends cert comp, no client auth +$proxy->clear(); +$proxy->clientflags("-no_tx_cert_comp -no_rx_cert_comp"); +$proxy->serverflags("-cert_comp"); +$proxy->start(); +checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, + checkhandshake::DEFAULT_EXTENSIONS + | checkhandshake::CERT_COMP_SRV_EXTENSION, + "Server supports certificate compression, but no client auth"); + +#Test 3: Both send cert comp, no client auth +$proxy->clear(); +$proxy->serverflags("-cert_comp"); +$proxy->start(); +checkhandshake($proxy, checkhandshake::CERT_COMP_SRV_HANDSHAKE, + checkhandshake::DEFAULT_EXTENSIONS + | checkhandshake::CERT_COMP_CLI_EXTENSION + | checkhandshake::CERT_COMP_SRV_EXTENSION, + "Both support certificate compression, but no client auth"); + +#Test 4: Both send cert comp, with client auth +$proxy->clear(); +$proxy->clientflags("-cert ".srctop_file("apps", "server.pem")); +$proxy->serverflags("-Verify 5 -cert_comp"); +$proxy->start(); +checkhandshake($proxy, checkhandshake::CERT_COMP_BOTH_HANDSHAKE, + checkhandshake::DEFAULT_EXTENSIONS + | checkhandshake::CERT_COMP_CLI_EXTENSION + | checkhandshake::CERT_COMP_SRV_EXTENSION, + "Both support certificate compression, with client auth"); + +#Test 5: Client-to-server-only certificate compression, with client auth +$proxy->clear(); +$proxy->clientflags("-no_rx_cert_comp -cert ".srctop_file("apps", "server.pem")); +$proxy->serverflags("-no_tx_cert_comp -Verify 5 -cert_comp"); +$proxy->start(); +checkhandshake($proxy, checkhandshake::CERT_COMP_CLI_HANDSHAKE, + checkhandshake::DEFAULT_EXTENSIONS + | checkhandshake::CERT_COMP_SRV_EXTENSION, + "Client-to-server-only certificate compression, with client auth"); + +#Test 6: Server-to-client-only certificate compression +$proxy->clear(); +$proxy->clientflags("-no_tx_cert_comp"); +$proxy->serverflags("-no_rx_cert_comp -cert_comp"); +$proxy->start(); +checkhandshake($proxy, checkhandshake::CERT_COMP_SRV_HANDSHAKE, + checkhandshake::DEFAULT_EXTENSIONS + | checkhandshake::CERT_COMP_CLI_EXTENSION, + "Server-to-client-only certificate compression"); + +#Test 7: Neither side wants to send a compressed cert, but will accept one +$proxy->clear(); +$proxy->clientflags("-no_tx_cert_comp"); +$proxy->serverflags("-no_tx_cert_comp -cert_comp"); +$proxy->start(); +checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, + checkhandshake::DEFAULT_EXTENSIONS + | checkhandshake::CERT_COMP_CLI_EXTENSION + | checkhandshake::CERT_COMP_SRV_EXTENSION, + "Accept but not send compressed certificates"); + +#Test 8: Neither side wants to receive a compressed cert, but will send one +$proxy->clear(); +$proxy->clientflags("-no_rx_cert_comp"); +$proxy->serverflags("-no_rx_cert_comp -cert_comp"); +$proxy->start(); +checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, + checkhandshake::DEFAULT_EXTENSIONS, + "Send but not accept compressed certificates"); diff --git a/test/recipes/70-test_tls13kexmodes.t b/test/recipes/70-test_tls13kexmodes.t index 0d0681a838..1c2c18cc11 100644 --- a/test/recipes/70-test_tls13kexmodes.t +++ b/test/recipes/70-test_tls13kexmodes.t @@ -187,8 +187,8 @@ my $proxy = TLSProxy::Proxy->new( #Test 1: First get a session (undef, my $session) = tempfile(); -$proxy->clientflags("-sess_out ".$session); -$proxy->serverflags("-servername localhost"); +$proxy->clientflags("-no_rx_cert_comp -sess_out ".$session); +$proxy->serverflags("-no_rx_cert_comp -servername localhost"); $proxy->sessionfile($session); $proxy->start() or plan skip_all => "Unable to start up Proxy for tests"; plan tests => 11; @@ -197,7 +197,7 @@ ok(TLSProxy::Message->success(), "Initial connection"); #Test 2: Attempt a resume with no kex modes extension. Should fail (server # MUST abort handshake with pre_shared key and no psk_kex_modes) $proxy->clear(); -$proxy->clientflags("-sess_in ".$session); +$proxy->clientflags("-no_rx_cert_comp -sess_in ".$session); my $testtype = DELETE_EXTENSION; $proxy->filter(\&modify_kex_modes_filter); $proxy->start(); @@ -206,7 +206,7 @@ ok(TLSProxy::Message->fail(), "Resume with no kex modes"); #Test 3: Attempt a resume with empty kex modes extension. Should fail (empty # extension is invalid) $proxy->clear(); -$proxy->clientflags("-sess_in ".$session); +$proxy->clientflags("-no_rx_cert_comp -sess_in ".$session); $testtype = EMPTY_EXTENSION; $proxy->start(); ok(TLSProxy::Message->fail(), "Resume with empty kex modes"); @@ -214,8 +214,8 @@ ok(TLSProxy::Message->fail(), "Resume with empty kex modes"); #Test 4: Attempt a resume with non-dhe kex mode only. Should resume without a # key_share $proxy->clear(); -$proxy->clientflags("-allow_no_dhe_kex -sess_in ".$session); -$proxy->serverflags("-allow_no_dhe_kex"); +$proxy->clientflags("-no_rx_cert_comp -allow_no_dhe_kex -sess_in ".$session); +$proxy->serverflags("-no_rx_cert_comp -allow_no_dhe_kex"); $testtype = NON_DHE_KEX_MODE_ONLY; $proxy->start(); checkhandshake($proxy, checkhandshake::RESUME_HANDSHAKE, @@ -227,7 +227,7 @@ checkhandshake($proxy, checkhandshake::RESUME_HANDSHAKE, #Test 5: Attempt a resume with dhe kex mode only. Should resume with a key_share $proxy->clear(); -$proxy->clientflags("-sess_in ".$session); +$proxy->clientflags("-no_rx_cert_comp -sess_in ".$session); $testtype = DHE_KEX_MODE_ONLY; $proxy->start(); checkhandshake($proxy, checkhandshake::RESUME_HANDSHAKE, @@ -241,7 +241,7 @@ checkhandshake($proxy, checkhandshake::RESUME_HANDSHAKE, #Test 6: Attempt a resume with only unrecognised kex modes. Should not resume # but rather fall back to full handshake $proxy->clear(); -$proxy->clientflags("-sess_in ".$session); +$proxy->clientflags("-no_rx_cert_comp -sess_in ".$session); $testtype = UNKNOWN_KEX_MODES; $proxy->start(); checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, @@ -254,7 +254,7 @@ checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, #Test 7: Attempt a resume with both non-dhe and dhe kex mode. Should resume with # a key_share $proxy->clear(); -$proxy->clientflags("-sess_in ".$session); +$proxy->clientflags("-no_rx_cert_comp -sess_in ".$session); $testtype = BOTH_KEX_MODES; $proxy->start(); checkhandshake($proxy, checkhandshake::RESUME_HANDSHAKE, @@ -268,8 +268,8 @@ checkhandshake($proxy, checkhandshake::RESUME_HANDSHAKE, #Test 8: Attempt a resume with both non-dhe and dhe kex mode, but unacceptable # initial key_share. Should resume with a key_share following an HRR $proxy->clear(); -$proxy->clientflags("-sess_in ".$session); -$proxy->serverflags("-curves P-256"); +$proxy->clientflags("-no_rx_cert_comp -sess_in ".$session); +$proxy->serverflags("-no_rx_cert_comp -curves P-256"); $testtype = BOTH_KEX_MODES; $proxy->start(); checkhandshake($proxy, checkhandshake::HRR_RESUME_HANDSHAKE, @@ -284,8 +284,8 @@ checkhandshake($proxy, checkhandshake::HRR_RESUME_HANDSHAKE, #Test 9: Attempt a resume with dhe kex mode only and an unacceptable initial # key_share. Should resume with a key_share following an HRR $proxy->clear(); -$proxy->clientflags("-sess_in ".$session); -$proxy->serverflags("-curves P-256"); +$proxy->clientflags("-no_rx_cert_comp -sess_in ".$session); +$proxy->serverflags("-no_rx_cert_comp -curves P-256"); $testtype = DHE_KEX_MODE_ONLY; $proxy->start(); checkhandshake($proxy, checkhandshake::HRR_RESUME_HANDSHAKE, @@ -301,8 +301,8 @@ checkhandshake($proxy, checkhandshake::HRR_RESUME_HANDSHAKE, # initial key_share and no overlapping groups. Should resume without a # key_share $proxy->clear(); -$proxy->clientflags("-allow_no_dhe_kex -curves P-384 -sess_in ".$session); -$proxy->serverflags("-allow_no_dhe_kex -curves P-256"); +$proxy->clientflags("-no_rx_cert_comp -allow_no_dhe_kex -curves P-384 -sess_in ".$session); +$proxy->serverflags("-no_rx_cert_comp -allow_no_dhe_kex -curves P-256"); $testtype = BOTH_KEX_MODES; $proxy->start(); checkhandshake($proxy, checkhandshake::RESUME_HANDSHAKE, @@ -315,8 +315,8 @@ checkhandshake($proxy, checkhandshake::RESUME_HANDSHAKE, #Test 11: Attempt a resume with dhe kex mode only, unacceptable # initial key_share and no overlapping groups. Should fail $proxy->clear(); -$proxy->clientflags("-curves P-384 -sess_in ".$session); -$proxy->serverflags("-curves P-256"); +$proxy->clientflags("-no_rx_cert_comp -curves P-384 -sess_in ".$session); +$proxy->serverflags("-no_rx_cert_comp -curves P-256"); $testtype = DHE_KEX_MODE_ONLY; $proxy->start(); ok(TLSProxy::Message->fail(), "Resume with dhe kex mode, no overlapping groups"); diff --git a/test/recipes/70-test_tls13messages.t b/test/recipes/70-test_tls13messages.t index 58b88a3ca8..b2f9819f02 100644 --- a/test/recipes/70-test_tls13messages.t +++ b/test/recipes/70-test_tls13messages.t @@ -203,7 +203,7 @@ my $proxy = TLSProxy::Proxy->new( #Test 1: Check we get all the right messages for a default handshake (undef, my $session) = tempfile(); $proxy->serverconnects(2); -$proxy->clientflags("-sess_out ".$session); +$proxy->clientflags("-no_rx_cert_comp -sess_out ".$session); $proxy->sessionfile($session); $proxy->start() or plan skip_all => "Unable to start up Proxy for tests"; plan tests => 17; @@ -213,7 +213,7 @@ checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, #Test 2: Resumption handshake $proxy->clearClient(); -$proxy->clientflags("-sess_in ".$session); +$proxy->clientflags("-no_rx_cert_comp -sess_in ".$session); $proxy->clientstart(); checkhandshake($proxy, checkhandshake::RESUME_HANDSHAKE, (checkhandshake::DEFAULT_EXTENSIONS @@ -226,7 +226,7 @@ SKIP: { if disabled("ct") || disabled("ec") || disabled("ocsp"); #Test 3: A status_request handshake (client request only) $proxy->clear(); - $proxy->clientflags("-status"); + $proxy->clientflags("-no_rx_cert_comp -status"); $proxy->start(); checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, checkhandshake::DEFAULT_EXTENSIONS @@ -235,7 +235,8 @@ SKIP: { #Test 4: A status_request handshake (server support only) $proxy->clear(); - $proxy->serverflags("-status_file " + $proxy->clientflags("-no_rx_cert_comp"); + $proxy->serverflags("-no_rx_cert_comp -status_file " .srctop_file("test", "recipes", "ocsp-response.der")); $proxy->start(); checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, @@ -244,8 +245,8 @@ SKIP: { #Test 5: A status_request handshake (client and server) $proxy->clear(); - $proxy->clientflags("-status"); - $proxy->serverflags("-status_file " + $proxy->clientflags("-no_rx_cert_comp -status"); + $proxy->serverflags("-no_rx_cert_comp -status_file " .srctop_file("test", "recipes", "ocsp-response.der")); $proxy->start(); checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, @@ -256,9 +257,9 @@ SKIP: { #Test 6: A status_request handshake (client and server) with client auth $proxy->clear(); - $proxy->clientflags("-status -enable_pha -cert " + $proxy->clientflags("-no_rx_cert_comp -status -enable_pha -cert " .srctop_file("apps", "server.pem")); - $proxy->serverflags("-Verify 5 -status_file " + $proxy->serverflags("-no_rx_cert_comp -Verify 5 -status_file " .srctop_file("test", "recipes", "ocsp-response.der")); $proxy->start(); checkhandshake($proxy, checkhandshake::CLIENT_AUTH_HANDSHAKE, @@ -271,8 +272,8 @@ SKIP: { #Test 7: A client auth handshake $proxy->clear(); -$proxy->clientflags("-enable_pha -cert ".srctop_file("apps", "server.pem")); -$proxy->serverflags("-Verify 5"); +$proxy->clientflags("-no_rx_cert_comp -enable_pha -cert ".srctop_file("apps", "server.pem")); +$proxy->serverflags("-no_rx_cert_comp -Verify 5"); $proxy->start(); checkhandshake($proxy, checkhandshake::CLIENT_AUTH_HANDSHAKE, checkhandshake::DEFAULT_EXTENSIONS | @@ -281,7 +282,7 @@ checkhandshake($proxy, checkhandshake::CLIENT_AUTH_HANDSHAKE, #Test 8: Server name handshake (no client request) $proxy->clear(); -$proxy->clientflags("-noservername"); +$proxy->clientflags("-no_rx_cert_comp -noservername"); $proxy->start(); checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, checkhandshake::DEFAULT_EXTENSIONS @@ -290,8 +291,8 @@ checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, #Test 9: Server name handshake (server support only) $proxy->clear(); -$proxy->clientflags("-noservername"); -$proxy->serverflags("-servername testhost"); +$proxy->clientflags("-no_rx_cert_comp -noservername"); +$proxy->serverflags("-no_rx_cert_comp -servername testhost"); $proxy->start(); checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, checkhandshake::DEFAULT_EXTENSIONS @@ -300,8 +301,8 @@ checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, #Test 10: Server name handshake (client and server) $proxy->clear(); -$proxy->clientflags("-servername testhost"); -$proxy->serverflags("-servername testhost"); +$proxy->clientflags("-no_rx_cert_comp -servername testhost"); +$proxy->serverflags("-no_rx_cert_comp -servername testhost"); $proxy->start(); checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, checkhandshake::DEFAULT_EXTENSIONS @@ -310,7 +311,7 @@ checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, #Test 11: ALPN handshake (client request only) $proxy->clear(); -$proxy->clientflags("-alpn test"); +$proxy->clientflags("-no_rx_cert_comp -alpn test"); $proxy->start(); checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, checkhandshake::DEFAULT_EXTENSIONS @@ -319,7 +320,8 @@ checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, #Test 12: ALPN handshake (server support only) $proxy->clear(); -$proxy->serverflags("-alpn test"); +$proxy->clientflags("-no_rx_cert_comp"); +$proxy->serverflags("-no_rx_cert_comp -alpn test"); $proxy->start(); checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, checkhandshake::DEFAULT_EXTENSIONS, @@ -327,8 +329,8 @@ checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, #Test 13: ALPN handshake (client and server) $proxy->clear(); -$proxy->clientflags("-alpn test"); -$proxy->serverflags("-alpn test"); +$proxy->clientflags("-no_rx_cert_comp -alpn test"); +$proxy->serverflags("-no_rx_cert_comp -alpn test"); $proxy->start(); checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, checkhandshake::DEFAULT_EXTENSIONS @@ -343,8 +345,8 @@ SKIP: { #Test 14: SCT handshake (client request only) $proxy->clear(); #Note: -ct also sends status_request - $proxy->clientflags("-ct"); - $proxy->serverflags("-status_file " + $proxy->clientflags("-no_rx_cert_comp -ct"); + $proxy->serverflags("-no_rx_cert_comp -status_file " .srctop_file("test", "recipes", "ocsp-response.der") ." -serverinfo ".srctop_file("test", "serverinfo2.pem")); $proxy->start(); @@ -359,7 +361,8 @@ SKIP: { #Test 15: HRR Handshake $proxy->clear(); -$proxy->serverflags("-curves P-256"); +$proxy->clientflags("-no_rx_cert_comp"); +$proxy->serverflags("-no_rx_cert_comp -curves P-256"); $proxy->start(); checkhandshake($proxy, checkhandshake::HRR_HANDSHAKE, checkhandshake::DEFAULT_EXTENSIONS @@ -368,8 +371,8 @@ checkhandshake($proxy, checkhandshake::HRR_HANDSHAKE, #Test 16: Resumption handshake with HRR $proxy->clear(); -$proxy->clientflags("-sess_in ".$session); -$proxy->serverflags("-curves P-256"); +$proxy->clientflags("-no_rx_cert_comp -sess_in ".$session); +$proxy->serverflags("-no_rx_cert_comp -curves P-256"); $proxy->start(); checkhandshake($proxy, checkhandshake::HRR_RESUME_HANDSHAKE, (checkhandshake::DEFAULT_EXTENSIONS @@ -380,7 +383,7 @@ checkhandshake($proxy, checkhandshake::HRR_RESUME_HANDSHAKE, #Test 17: Acceptable but non preferred key_share $proxy->clear(); -$proxy->clientflags("-curves P-256"); +$proxy->clientflags("-no_rx_cert_comp -curves P-256"); $proxy->start(); checkhandshake($proxy, checkhandshake::DEFAULT_HANDSHAKE, checkhandshake::DEFAULT_EXTENSIONS diff --git a/test/recipes/80-test_ssl_new.t b/test/recipes/80-test_ssl_new.t index 1783916b9b..39690e55ef 100644 --- a/test/recipes/80-test_ssl_new.t +++ b/test/recipes/80-test_ssl_new.t @@ -42,7 +42,7 @@ if (defined $ENV{SSL_TESTS}) { @conf_srcs = glob(srctop_file("test", "ssl-tests", "*.cnf.in")); # We hard-code the number of tests to double-check that the globbing above # finds all files as expected. - plan tests => 31; + plan tests => 32; } map { s/;.*// } @conf_srcs if $^O eq "VMS"; my @conf_files = map { basename($_, ".in") } @conf_srcs; @@ -93,6 +93,7 @@ my %conf_dependent_tests = ( "27-ticket-appdata.cnf" => !$is_default_tls, "28-seclevel.cnf" => disabled("tls1_2") || $no_ec, "30-extended-master-secret.cnf" => disabled("tls1_2"), + "32-compressed-certificate.cnf" => disabled("comp") || disabled("tls1_3"), ); # Add your test here if it should be skipped for some compile-time @@ -127,7 +128,8 @@ my %skip = ( "25-cipher.cnf" => disabled("ec") || disabled("tls1_2"), "26-tls13_client_auth.cnf" => disabled("tls1_3") || ($no_ec && $no_dh), "29-dtls-sctp-label-bug.cnf" => disabled("sctp") || disabled("sock"), - "31-quic.cnf" => $no_quic || $no_ec + "31-quic.cnf" => $no_quic || $no_ec, + "32-compressed-certificate.cnf" => disabled("comp") || disabled("tls1_3"), ); foreach my $conf (@conf_files) { diff --git a/test/recipes/90-test_cert_comp.t b/test/recipes/90-test_cert_comp.t new file mode 100644 index 0000000000..a1421e60d0 --- /dev/null +++ b/test/recipes/90-test_cert_comp.t @@ -0,0 +1,24 @@ +#! /usr/bin/env perl +# Copyright 2016-2022 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + +use OpenSSL::Test::Utils; +use OpenSSL::Test qw/:DEFAULT srctop_dir bldtop_dir/; + +BEGIN { + setup("test_cert_comp"); +} + +use lib srctop_dir('Configurations'); +use lib bldtop_dir('.'); + +plan skip_all => "Certificate compression is disabled in this OpenSSL build" + if disabled("comp") || (disabled("brotli") && disabled("zstd") && disabled("zlib")); + +plan tests => 1; + +ok(run(test(["cert_comp_test", srctop_dir("test", "certs")])), "running cert_comp_test"); diff --git a/test/ssl-tests/32-compressed-certificate.cnf b/test/ssl-tests/32-compressed-certificate.cnf new file mode 100644 index 0000000000..d1898934d5 --- /dev/null +++ b/test/ssl-tests/32-compressed-certificate.cnf @@ -0,0 +1,244 @@ +# Generated with generate_ssl_tests.pl + +num_tests = 8 + +test-0 = 0-no-compressed-certificates +test-1 = 1-server-compressed-certificates +test-2 = 2-client-compressed-certificates +test-3 = 3-both-compressed-certificates +test-4 = 4-no-compressed-certificates-mtls +test-5 = 5-server-compressed-certificates-mtls +test-6 = 6-client-compressed-certificates-mtls +test-7 = 7-both-compressed-certificates-mtls +# =========================================================== + +[0-no-compressed-certificates] +ssl_conf = 0-no-compressed-certificates-ssl + +[0-no-compressed-certificates-ssl] +server = 0-no-compressed-certificates-server +client = 0-no-compressed-certificates-client + +[0-no-compressed-certificates-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +Options = -TxCertificateCompression,-RxCertificateCompression +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[0-no-compressed-certificates-client] +CipherString = DEFAULT +MinProtocol = TLSv1.3 +Options = -TxCertificateCompression,-RxCertificateCompression +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-0] +CompressCertificates = Yes +ExpectedResult = Success + + +# =========================================================== + +[1-server-compressed-certificates] +ssl_conf = 1-server-compressed-certificates-ssl + +[1-server-compressed-certificates-ssl] +server = 1-server-compressed-certificates-server +client = 1-server-compressed-certificates-client + +[1-server-compressed-certificates-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +Options = TxCertificateCompression,RxCertificateCompression +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[1-server-compressed-certificates-client] +CipherString = DEFAULT +MinProtocol = TLSv1.3 +Options = -TxCertificateCompression,-RxCertificateCompression +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-1] +CompressCertificates = Yes +ExpectedResult = Success + + +# =========================================================== + +[2-client-compressed-certificates] +ssl_conf = 2-client-compressed-certificates-ssl + +[2-client-compressed-certificates-ssl] +server = 2-client-compressed-certificates-server +client = 2-client-compressed-certificates-client + +[2-client-compressed-certificates-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +Options = -TxCertificateCompression,-RxCertificateCompression +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[2-client-compressed-certificates-client] +CipherString = DEFAULT +MinProtocol = TLSv1.3 +Options = TxCertificateCompression,RxCertificateCompression +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-2] +CompressCertificates = Yes +ExpectedResult = Success + + +# =========================================================== + +[3-both-compressed-certificates] +ssl_conf = 3-both-compressed-certificates-ssl + +[3-both-compressed-certificates-ssl] +server = 3-both-compressed-certificates-server +client = 3-both-compressed-certificates-client + +[3-both-compressed-certificates-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +Options = TxCertificateCompression,RxCertificateCompression +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem + +[3-both-compressed-certificates-client] +CipherString = DEFAULT +MinProtocol = TLSv1.3 +Options = TxCertificateCompression,RxCertificateCompression +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-3] +CompressCertificates = Yes +ExpectedResult = Success + + +# =========================================================== + +[4-no-compressed-certificates-mtls] +ssl_conf = 4-no-compressed-certificates-mtls-ssl + +[4-no-compressed-certificates-mtls-ssl] +server = 4-no-compressed-certificates-mtls-server +client = 4-no-compressed-certificates-mtls-client + +[4-no-compressed-certificates-mtls-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +Options = -TxCertificateCompression,-RxCertificateCompression +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/root-cert.pem +VerifyMode = Request + +[4-no-compressed-certificates-mtls-client] +Certificate = ${ENV::TEST_CERTS_DIR}/ee-client-chain.pem +CipherString = DEFAULT +MinProtocol = TLSv1.3 +Options = -TxCertificateCompression,-RxCertificateCompression +PrivateKey = ${ENV::TEST_CERTS_DIR}/ee-key.pem +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-4] +CompressCertificates = Yes +ExpectedResult = Success + + +# =========================================================== + +[5-server-compressed-certificates-mtls] +ssl_conf = 5-server-compressed-certificates-mtls-ssl + +[5-server-compressed-certificates-mtls-ssl] +server = 5-server-compressed-certificates-mtls-server +client = 5-server-compressed-certificates-mtls-client + +[5-server-compressed-certificates-mtls-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +Options = TxCertificateCompression,RxCertificateCompression +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/root-cert.pem +VerifyMode = Request + +[5-server-compressed-certificates-mtls-client] +Certificate = ${ENV::TEST_CERTS_DIR}/ee-client-chain.pem +CipherString = DEFAULT +MinProtocol = TLSv1.3 +Options = -TxCertificateCompression,-RxCertificateCompression +PrivateKey = ${ENV::TEST_CERTS_DIR}/ee-key.pem +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-5] +CompressCertificates = Yes +ExpectedResult = Success + + +# =========================================================== + +[6-client-compressed-certificates-mtls] +ssl_conf = 6-client-compressed-certificates-mtls-ssl + +[6-client-compressed-certificates-mtls-ssl] +server = 6-client-compressed-certificates-mtls-server +client = 6-client-compressed-certificates-mtls-client + +[6-client-compressed-certificates-mtls-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +Options = -TxCertificateCompression,-RxCertificateCompression +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/root-cert.pem +VerifyMode = Request + +[6-client-compressed-certificates-mtls-client] +Certificate = ${ENV::TEST_CERTS_DIR}/ee-client-chain.pem +CipherString = DEFAULT +MinProtocol = TLSv1.3 +Options = TxCertificateCompression,RxCertificateCompression +PrivateKey = ${ENV::TEST_CERTS_DIR}/ee-key.pem +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-6] +CompressCertificates = Yes +ExpectedResult = Success + + +# =========================================================== + +[7-both-compressed-certificates-mtls] +ssl_conf = 7-both-compressed-certificates-mtls-ssl + +[7-both-compressed-certificates-mtls-ssl] +server = 7-both-compressed-certificates-mtls-server +client = 7-both-compressed-certificates-mtls-client + +[7-both-compressed-certificates-mtls-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +Options = TxCertificateCompression,RxCertificateCompression +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/root-cert.pem +VerifyMode = Request + +[7-both-compressed-certificates-mtls-client] +Certificate = ${ENV::TEST_CERTS_DIR}/ee-client-chain.pem +CipherString = DEFAULT +MinProtocol = TLSv1.3 +Options = TxCertificateCompression,RxCertificateCompression +PrivateKey = ${ENV::TEST_CERTS_DIR}/ee-key.pem +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-7] +CompressCertificates = Yes +ExpectedResult = Success + + diff --git a/test/ssl-tests/32-compressed-certificate.cnf.in b/test/ssl-tests/32-compressed-certificate.cnf.in new file mode 100644 index 0000000000..780c982d3c --- /dev/null +++ b/test/ssl-tests/32-compressed-certificate.cnf.in @@ -0,0 +1,147 @@ +# -*- mode: perl; -*- +# Copyright 2016-2016 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the OpenSSL license (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + + +## SSL test configurations + +package ssltests; + +use OpenSSL::Test::Utils; + +our @tests = (); + +our @tests = ( + { + name => "no-compressed-certificates", + server => { + "Options" => "-TxCertificateCompression,-RxCertificateCompression", + }, + client => { + "Options" => "-TxCertificateCompression,-RxCertificateCompression", + "MinProtocol" => "TLSv1.3", + }, + test => { + "ExpectedResult" => "Success", + "CompressCertificates" => "Yes", + }, + }, + { + name => "server-compressed-certificates", + server => { + "Options" => "TxCertificateCompression,RxCertificateCompression", + }, + client => { + "Options" => "-TxCertificateCompression,-RxCertificateCompression", + "MinProtocol" => "TLSv1.3", + }, + test => { + "ExpectedResult" => "Success", + "CompressCertificates" => "Yes", + }, + }, + { + name => "client-compressed-certificates", + server => { + "Options" => "-TxCertificateCompression,-RxCertificateCompression", + }, + client => { + "Options" => "TxCertificateCompression,RxCertificateCompression", + "MinProtocol" => "TLSv1.3", + }, + test => { + "ExpectedResult" => "Success", + "CompressCertificates" => "Yes", + }, + }, + { + name => "both-compressed-certificates", + server => { + "Options" => "TxCertificateCompression,RxCertificateCompression", + }, + client => { + "Options" => "TxCertificateCompression,RxCertificateCompression", + "MinProtocol" => "TLSv1.3", + }, + test => { + "ExpectedResult" => "Success", + "CompressCertificates" => "Yes", + }, + }, + { + name => "no-compressed-certificates-mtls", + server => { + "Options" => "-TxCertificateCompression,-RxCertificateCompression", + "VerifyCAFile" => test_pem("root-cert.pem"), + "VerifyMode" => "Request", + }, + client => { + "Options" => "-TxCertificateCompression,-RxCertificateCompression", + "MinProtocol" => "TLSv1.3", + "Certificate" => test_pem("ee-client-chain.pem"), + "PrivateKey" => test_pem("ee-key.pem"), + }, + test => { + "ExpectedResult" => "Success", + "CompressCertificates" => "Yes", + }, + }, + { + name => "server-compressed-certificates-mtls", + server => { + "Options" => "TxCertificateCompression,RxCertificateCompression", + "VerifyCAFile" => test_pem("root-cert.pem"), + "VerifyMode" => "Request", + }, + client => { + "Options" => "-TxCertificateCompression,-RxCertificateCompression", + "MinProtocol" => "TLSv1.3", + "Certificate" => test_pem("ee-client-chain.pem"), + "PrivateKey" => test_pem("ee-key.pem"), + }, + test => { + "ExpectedResult" => "Success", + "CompressCertificates" => "Yes", + }, + }, + { + name => "client-compressed-certificates-mtls", + server => { + "Options" => "-TxCertificateCompression,-RxCertificateCompression", + "VerifyCAFile" => test_pem("root-cert.pem"), + "VerifyMode" => "Request", + }, + client => { + "Options" => "TxCertificateCompression,RxCertificateCompression", + "MinProtocol" => "TLSv1.3", + "Certificate" => test_pem("ee-client-chain.pem"), + "PrivateKey" => test_pem("ee-key.pem"), + }, + test => { + "ExpectedResult" => "Success", + "CompressCertificates" => "Yes", + }, + }, + { + name => "both-compressed-certificates-mtls", + server => { + "Options" => "TxCertificateCompression,RxCertificateCompression", + "VerifyCAFile" => test_pem("root-cert.pem"), + "VerifyMode" => "Request", + }, + client => { + "Options" => "TxCertificateCompression,RxCertificateCompression", + "MinProtocol" => "TLSv1.3", + "Certificate" => test_pem("ee-client-chain.pem"), + "PrivateKey" => test_pem("ee-key.pem"), + }, + test => { + "ExpectedResult" => "Success", + "CompressCertificates" => "Yes", + }, + }, +); diff --git a/test/sslapitest.c b/test/sslapitest.c index 1be4f7e14e..7d3432de28 100644 --- a/test/sslapitest.c +++ b/test/sslapitest.c @@ -7164,6 +7164,38 @@ static struct info_cb_states_st { {SSL_CB_LOOP, "TWFIN"}, {SSL_CB_HANDSHAKE_DONE, NULL}, {SSL_CB_EXIT, NULL}, {SSL_CB_LOOP, "SSLOK"}, {SSL_CB_LOOP, "SSLOK"}, {SSL_CB_LOOP, "TRST"}, {SSL_CB_EXIT, NULL}, {0, NULL}, + }, { + /* TLSv1.3 server, certificate compression, followed by resumption */ + {SSL_CB_HANDSHAKE_START, NULL}, {SSL_CB_LOOP, "PINIT"}, + {SSL_CB_LOOP, "PINIT"}, {SSL_CB_LOOP, "TRCH"}, {SSL_CB_LOOP, "TWSH"}, + {SSL_CB_LOOP, "TWCCS"}, {SSL_CB_LOOP, "TWEE"}, {SSL_CB_LOOP, "TWSCC"}, + {SSL_CB_LOOP, "TWSCV"}, {SSL_CB_LOOP, "TWFIN"}, {SSL_CB_LOOP, "TED"}, + {SSL_CB_EXIT, NULL}, {SSL_CB_LOOP, "TED"}, {SSL_CB_LOOP, "TRFIN"}, + {SSL_CB_HANDSHAKE_DONE, NULL}, {SSL_CB_LOOP, "TWST"}, + {SSL_CB_LOOP, "TWST"}, {SSL_CB_EXIT, NULL}, {SSL_CB_ALERT, NULL}, + {SSL_CB_HANDSHAKE_START, NULL}, {SSL_CB_LOOP, "PINIT"}, + {SSL_CB_LOOP, "PINIT"}, {SSL_CB_LOOP, "TRCH"}, {SSL_CB_LOOP, "TWSH"}, + {SSL_CB_LOOP, "TWCCS"}, {SSL_CB_LOOP, "TWEE"}, {SSL_CB_LOOP, "TWFIN"}, + {SSL_CB_LOOP, "TED"}, {SSL_CB_EXIT, NULL}, {SSL_CB_LOOP, "TED"}, + {SSL_CB_LOOP, "TRFIN"}, {SSL_CB_HANDSHAKE_DONE, NULL}, + {SSL_CB_LOOP, "TWST"}, {SSL_CB_EXIT, NULL}, {0, NULL}, + }, { + /* TLSv1.3 client, certificate compression, followed by resumption */ + {SSL_CB_HANDSHAKE_START, NULL}, {SSL_CB_LOOP, "PINIT"}, + {SSL_CB_LOOP, "TWCH"}, {SSL_CB_EXIT, NULL}, {SSL_CB_LOOP, "TWCH"}, + {SSL_CB_LOOP, "TRSH"}, {SSL_CB_LOOP, "TREE"}, {SSL_CB_LOOP, "TRSCC"}, + {SSL_CB_LOOP, "TRSCV"}, {SSL_CB_LOOP, "TRFIN"}, {SSL_CB_LOOP, "TWCCS"}, + {SSL_CB_LOOP, "TWFIN"}, {SSL_CB_HANDSHAKE_DONE, NULL}, + {SSL_CB_EXIT, NULL}, {SSL_CB_LOOP, "SSLOK"}, {SSL_CB_LOOP, "SSLOK"}, + {SSL_CB_LOOP, "TRST"}, {SSL_CB_EXIT, NULL}, {SSL_CB_LOOP, "SSLOK"}, + {SSL_CB_LOOP, "SSLOK"}, {SSL_CB_LOOP, "TRST"}, {SSL_CB_EXIT, NULL}, + {SSL_CB_ALERT, NULL}, {SSL_CB_HANDSHAKE_START, NULL}, + {SSL_CB_LOOP, "PINIT"}, {SSL_CB_LOOP, "TWCH"}, {SSL_CB_EXIT, NULL}, + {SSL_CB_LOOP, "TWCH"}, {SSL_CB_LOOP, "TRSH"}, {SSL_CB_LOOP, "TREE"}, + {SSL_CB_LOOP, "TRFIN"}, {SSL_CB_LOOP, "TWCCS"}, {SSL_CB_LOOP, "TWFIN"}, + {SSL_CB_HANDSHAKE_DONE, NULL}, {SSL_CB_EXIT, NULL}, + {SSL_CB_LOOP, "SSLOK"}, {SSL_CB_LOOP, "SSLOK"}, {SSL_CB_LOOP, "TRST"}, + {SSL_CB_EXIT, NULL}, {0, NULL}, }, { {0, NULL}, } @@ -7221,6 +7253,8 @@ static void sslapi_info_callback(const SSL *s, int where, int ret) * Test 3: TLSv1.3, client * Test 4: TLSv1.3, server, early_data * Test 5: TLSv1.3, client, early_data + * Test 6: TLSv1.3, server, compressed certificate + * Test 7: TLSv1.3, client, compressed certificate */ static int test_info_callback(int tst) { @@ -7252,7 +7286,7 @@ static int test_info_callback(int tst) info_cb_offset = tst; #ifndef OSSL_NO_USABLE_TLS1_3 - if (tst >= 4) { + if (tst >= 4 && tst < 6) { SSL_SESSION *sess = NULL; size_t written, readbytes; unsigned char buf[80]; @@ -7303,6 +7337,10 @@ static int test_info_callback(int tst) */ SSL_CTX_set_info_callback((tst % 2) == 0 ? sctx : cctx, sslapi_info_callback); + if (tst >= 6) { + if (!SSL_CTX_compress_certs(sctx, 0)) + goto end; + } if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl, NULL, NULL)) @@ -10339,7 +10377,12 @@ int setup_tests(void) #if !defined(OPENSSL_NO_SRP) && !defined(OPENSSL_NO_TLS1_2) ADD_ALL_TESTS(test_srp, 6); #endif +#if !defined(OPENSSL_NO_COMP_ALG) + /* Add compression case */ + ADD_ALL_TESTS(test_info_callback, 8); +#else ADD_ALL_TESTS(test_info_callback, 6); +#endif ADD_ALL_TESTS(test_ssl_pending, 2); ADD_ALL_TESTS(test_ssl_get_shared_ciphers, OSSL_NELEM(shared_ciphers_data)); ADD_ALL_TESTS(test_ticket_callbacks, 20); diff --git a/util/libssl.num b/util/libssl.num index adb095bd53..c275cffcd0 100644 --- a/util/libssl.num +++ b/util/libssl.num @@ -524,3 +524,11 @@ SSL_client_hello_get_extension_order ? 3_2_0 EXIST::FUNCTION: OSSL_QUIC_client_method ? 3_2_0 EXIST::FUNCTION:QUIC OSSL_QUIC_client_thread_method ? 3_2_0 EXIST::FUNCTION:QUIC OSSL_QUIC_server_method ? 3_2_0 EXIST::FUNCTION:QUIC +SSL_CTX_set1_cert_comp_preference ? 3_2_0 EXIST::FUNCTION: +SSL_set1_cert_comp_preference ? 3_2_0 EXIST::FUNCTION: +SSL_CTX_compress_certs ? 3_2_0 EXIST::FUNCTION: +SSL_compress_certs ? 3_2_0 EXIST::FUNCTION: +SSL_CTX_set1_compressed_cert ? 3_2_0 EXIST::FUNCTION: +SSL_set1_compressed_cert ? 3_2_0 EXIST::FUNCTION: +SSL_CTX_get1_compressed_cert ? 3_2_0 EXIST::FUNCTION: +SSL_get1_compressed_cert ? 3_2_0 EXIST::FUNCTION: diff --git a/util/perl/TLSProxy/Message.pm b/util/perl/TLSProxy/Message.pm index 2c1bdb3837..648d986342 100644 --- a/util/perl/TLSProxy/Message.pm +++ b/util/perl/TLSProxy/Message.pm @@ -28,6 +28,7 @@ use constant { MT_CLIENT_KEY_EXCHANGE => 16, MT_FINISHED => 20, MT_CERTIFICATE_STATUS => 22, + MT_COMPRESSED_CERTIFICATE => 25, MT_NEXT_PROTO => 67 }; @@ -59,6 +60,7 @@ my %message_type = ( MT_CLIENT_KEY_EXCHANGE, "ClientKeyExchange", MT_FINISHED, "Finished", MT_CERTIFICATE_STATUS, "CertificateStatus", + MT_COMPRESSED_CERTIFICATE, "CompressedCertificate", MT_NEXT_PROTO, "NextProto" ); @@ -76,6 +78,7 @@ use constant { EXT_PADDING => 21, EXT_ENCRYPT_THEN_MAC => 22, EXT_EXTENDED_MASTER_SECRET => 23, + EXT_COMPRESS_CERTIFICATE => 27, EXT_SESSION_TICKET => 35, EXT_KEY_SHARE => 51, EXT_PSK => 41, diff --git a/util/perl/checkhandshake.pm b/util/perl/checkhandshake.pm index a2ae24ee2a..618ee99577 100644 --- a/util/perl/checkhandshake.pm +++ b/util/perl/checkhandshake.pm @@ -26,8 +26,11 @@ use constant { EC_HANDSHAKE => 64, HRR_HANDSHAKE => 128, HRR_RESUME_HANDSHAKE => 256, + CERT_COMP_SRV_HANDSHAKE => 512, + CERT_COMP_CLI_HANDSHAKE => 1024, + CERT_COMP_BOTH_HANDSHAKE => 2048, - ALL_HANDSHAKES => 511 + ALL_HANDSHAKES => 4095 }; use constant { @@ -54,7 +57,9 @@ use constant { PSK_KEX_MODES_EXTENSION => 0x00040000, KEY_SHARE_HRR_EXTENSION => 0x00080000, SUPPORTED_GROUPS_SRV_EXTENSION => 0x00100000, - POST_HANDSHAKE_AUTH_CLI_EXTENSION => 0x00200000 + POST_HANDSHAKE_AUTH_CLI_EXTENSION => 0x00200000, + CERT_COMP_CLI_EXTENSION => 0x00400000, + CERT_COMP_SRV_EXTENSION => 0x00800000 }; our @handmessages = ();