From 8563ce7e09347f99ba013fc66c0bc24f82ba3d92 Mon Sep 17 00:00:00 2001 From: Viktor Dukhovni Date: Sun, 26 Apr 2026 04:05:02 +1000 Subject: [PATCH 1/3] EC: make the group the source of the point conversion form The EC point conversion form was stored in two places that could disagree: the EC_KEY's conv_form and the EC_GROUP's asn1_form. Encoders read the key, but provider keygen wrote only the group, so genpkey -pkeyopt point-format:compressed produced keys whose encoded public point was uncompressed. Make the group canonical. Encoders, OSSL_PARAM output, and the EVP_PKEY_get_ec_point_conv_form() legacy fallback all read EC_GROUP_get_point_conversion_form(). EC_KEY_oct2key mirrors the decoded form onto the group, and provider keygen mirrors the group's form onto the key, so the two fields stay aligned. The deprecated EC_KEY_set_conv_form() and EC_KEY_get_conv_form() remain functional for legacy callers. openssl genpkey -pkeyopt point-format: now outputs the specified form. --- crypto/ec/ec_ameth.c | 5 +- crypto/ec/ec_asn1.c | 18 ++++-- crypto/ec/ec_key.c | 9 ++- crypto/evp/p_lib.c | 7 ++- .../encode_decode/encode_key2text.c | 3 +- providers/implementations/keymgmt/ec_kmgmt.c | 55 +++++++++++++------ 6 files changed, 70 insertions(+), 27 deletions(-) diff --git a/crypto/ec/ec_ameth.c b/crypto/ec/ec_ameth.c index a274f720f3..263d084bbb 100644 --- a/crypto/ec/ec_ameth.c +++ b/crypto/ec/ec_ameth.c @@ -282,7 +282,8 @@ static int do_EC_KEY_print(BIO *bp, const EC_KEY *x, int off, ec_print_t ktype) } if (ktype != EC_KEY_PRINT_PARAM && EC_KEY_get0_public_key(x) != NULL) { - publen = EC_KEY_key2buf(x, EC_KEY_get_conv_form(x), &pub, NULL); + publen = EC_KEY_key2buf(x, + EC_GROUP_get_point_conversion_form(group), &pub, NULL); if (publen == 0) goto err; } @@ -500,7 +501,7 @@ static int ec_pkey_export_to(const EVP_PKEY *from, void *to_keydata, if (pub_point != NULL) { /* convert pub_point to a octet string according to the SECG standard */ - point_conversion_form_t format = EC_KEY_get_conv_form(eckey); + point_conversion_form_t format = EC_GROUP_get_point_conversion_form(ecg); if ((pub_key_buflen = EC_POINT_point2buf(ecg, pub_point, format, diff --git a/crypto/ec/ec_asn1.c b/crypto/ec/ec_asn1.c index 8c25b8ea53..66dbf4c0c1 100644 --- a/crypto/ec/ec_asn1.c +++ b/crypto/ec/ec_asn1.c @@ -1064,7 +1064,8 @@ int i2d_ECPrivateKey(const EC_KEY *a, unsigned char **out) goto err; } - publen = EC_KEY_key2buf(a, a->conv_form, &pub, NULL); + publen = EC_KEY_key2buf(a, + EC_GROUP_get_point_conversion_form(a->group), &pub, NULL); if (publen == 0 || publen > INT_MAX) { ERR_raise(ERR_LIB_EC, ERR_R_EC_LIB); @@ -1156,6 +1157,7 @@ EC_KEY *o2i_ECPublicKey(EC_KEY **a, const unsigned char **in, long len) int i2o_ECPublicKey(const EC_KEY *a, unsigned char **out) { + point_conversion_form_t form; size_t buf_len = 0; int new_buffer = 0; @@ -1164,8 +1166,16 @@ int i2o_ECPublicKey(const EC_KEY *a, unsigned char **out) return 0; } - buf_len = EC_POINT_point2oct(a->group, a->pub_key, - a->conv_form, NULL, 0, NULL); + /* + * The encoded form follows the group's asn1_form, which the deprecated + * EC_KEY_set_conv_form() and the EC_KEY_oct2key() decode path both keep + * in sync with the key's conv_form. Reading the group avoids a stale + * key-side value when the group was updated through one of the EC_GROUP + * setters directly. + */ + form = EC_GROUP_get_point_conversion_form(a->group); + + buf_len = EC_POINT_point2oct(a->group, a->pub_key, form, NULL, 0, NULL); if (buf_len > INT_MAX) { ERR_raise(ERR_LIB_EC, ERR_R_PASSED_INVALID_ARGUMENT); @@ -1180,7 +1190,7 @@ int i2o_ECPublicKey(const EC_KEY *a, unsigned char **out) return 0; new_buffer = 1; } - if (!EC_POINT_point2oct(a->group, a->pub_key, a->conv_form, + if (!EC_POINT_point2oct(a->group, a->pub_key, form, *out, buf_len, NULL)) { ERR_raise(ERR_LIB_EC, ERR_R_EC_LIB); if (new_buffer) { diff --git a/crypto/ec/ec_key.c b/crypto/ec/ec_key.c index 48e08e7de4..5ac22a482e 100644 --- a/crypto/ec/ec_key.c +++ b/crypto/ec/ec_key.c @@ -959,9 +959,16 @@ int EC_KEY_oct2key(EC_KEY *key, const unsigned char *buf, size_t len, * the last significant bit) contains the point conversion form. * EC_POINT_oct2point() has already performed sanity checking of * the buffer so we know it is valid. + * + * Mirror the form to the group as well, so that subsequent reads + * via EC_GROUP_get_point_conversion_form() see the loaded form + * rather than the EC_GROUP_new() default. The encoder and the + * provider OSSL_PARAM emitter both consult the group. */ - if ((key->group->meth->flags & EC_FLAGS_CUSTOM_CURVE) == 0) + if ((key->group->meth->flags & EC_FLAGS_CUSTOM_CURVE) == 0) { key->conv_form = (point_conversion_form_t)(buf[0] & ~0x01); + EC_GROUP_set_point_conversion_form(key->group, key->conv_form); + } return 1; } diff --git a/crypto/evp/p_lib.c b/crypto/evp/p_lib.c index fcf64ed004..5aeb93925e 100644 --- a/crypto/evp/p_lib.c +++ b/crypto/evp/p_lib.c @@ -2408,7 +2408,12 @@ int EVP_PKEY_get_ec_point_conv_form(const EVP_PKEY *pkey) if (ec == NULL) return 0; - return EC_KEY_get_conv_form(ec); + /* + * Read the form from the group rather than the deprecated + * EC_KEY_get_conv_form(). EC_KEY_set_conv_form() and + * EC_KEY_oct2key() both keep the group's asn1_form in sync. + */ + return EC_GROUP_get_point_conversion_form(EC_KEY_get0_group(ec)); #else return 0; #endif diff --git a/providers/implementations/encode_decode/encode_key2text.c b/providers/implementations/encode_decode/encode_key2text.c index 965113426b..33bf6ab567 100644 --- a/providers/implementations/encode_decode/encode_key2text.c +++ b/providers/implementations/encode_decode/encode_key2text.c @@ -357,7 +357,8 @@ static int ec_to_text(BIO *out, const void *key, int selection) goto err; } - pub_len = EC_KEY_key2buf(ec, EC_KEY_get_conv_form(ec), &pub, NULL); + pub_len = EC_KEY_key2buf(ec, + EC_GROUP_get_point_conversion_form(group), &pub, NULL); if (pub_len == 0) goto err; } diff --git a/providers/implementations/keymgmt/ec_kmgmt.c b/providers/implementations/keymgmt/ec_kmgmt.c index d203a0c171..736f2dcc0e 100644 --- a/providers/implementations/keymgmt/ec_kmgmt.c +++ b/providers/implementations/keymgmt/ec_kmgmt.c @@ -156,7 +156,7 @@ static ossl_inline int key_to_params(const EC_KEY *eckey, OSSL_PARAM_BLD *tmpl, if (p != NULL || tmpl != NULL) { /* convert pub_point to a octet string according to the SECG standard */ - point_conversion_form_t format = EC_KEY_get_conv_form(eckey); + point_conversion_form_t format = EC_GROUP_get_point_conversion_form(ecg); if ((pub_key_len = EC_POINT_point2buf(ecg, pub_point, format, @@ -250,19 +250,10 @@ static ossl_inline int otherparams_to_params(const EC_KEY *ec, OSSL_PARAM_BLD *t { int ecdh_cofactor_mode = 0, group_check = 0; const char *name = NULL; - point_conversion_form_t format; if (ec == NULL) return 0; - format = EC_KEY_get_conv_form(ec); - name = ossl_ec_pt_format_id2name((int)format); - if (name != NULL - && !ossl_param_build_set_utf8_string(tmpl, params, - OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT, - name)) - return 0; - group_check = EC_KEY_get_flags(ec) & EC_FLAG_CHECK_NAMED_GROUP_MASK; name = ossl_ec_check_group_type_id2name(group_check); if (name != NULL @@ -516,15 +507,21 @@ static int ec_export(void *keydata, int selection, OSSL_CALLBACK *param_cb, if (tmpl == NULL) return 0; - if ((selection & OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS) != 0) { - bnctx = BN_CTX_new_ex(ossl_ec_key_get_libctx(ec)); - if (bnctx == NULL) { - ok = 0; - goto end; - } - BN_CTX_start(bnctx); - ok = ok && ossl_ec_group_todata(EC_KEY_get0_group(ec), tmpl, NULL, ossl_ec_key_get_libctx(ec), ossl_ec_key_get0_propq(ec), bnctx, &genbuf); + bnctx = BN_CTX_new_ex(ossl_ec_key_get_libctx(ec)); + if (bnctx == NULL) { + ok = 0; + goto end; } + BN_CTX_start(bnctx); + /* + * OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT is added based on the group's + * asn1_form by the call below; otherparams_to_params() no longer adds the + * key's deprecated conv_form, so this is the only source on the export + * side. + */ + ok = ossl_ec_group_todata(EC_KEY_get0_group(ec), tmpl, NULL, + ossl_ec_key_get_libctx(ec), ossl_ec_key_get0_propq(ec), + bnctx, &genbuf); if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) != 0) { int include_private = selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY ? 1 : 0; @@ -748,6 +745,12 @@ static int common_get_params(void *key, OSSL_PARAM params[], int sm2) goto err; } + /* + * OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT is added base on the group's + * asn1_form by ossl_ec_group_todata() below; otherparams_to_params() no + * longer adds the key's deprecated conv_form, so this is the only source + * on the get-params side. + */ ret = ec_get_ecm_params(ecg, params) && ossl_ec_group_todata(ecg, NULL, params, libctx, propq, bnctx, &genbuf) @@ -1258,6 +1261,18 @@ static int ec_gen_assign_group(EC_KEY *ec, EC_GROUP *group) return EC_KEY_set_group(ec, group) > 0; } +/* + * Mirror the group's point-conversion form to the key's conv_form + * field so the two stay aligned after keygen. EC_KEY_new() leaves + * conv_form at the UNCOMPRESSED default; this sync ensures any caller + * that asks the key directly sees the same value the encoders do. + */ +static void ec_gen_sync_conv_form(EC_KEY *ec) +{ + EC_KEY_set_conv_form(ec, + EC_GROUP_get_point_conversion_form(EC_KEY_get0_group(ec))); +} + /* * The callback arguments (osslcb & cbarg) are not used by EC_KEY generation */ @@ -1300,6 +1315,8 @@ static void *ec_gen(void *genctx, OSSL_CALLBACK *osslcb, void *cbarg) /* We must always assign a group, no matter what */ ret = ec_gen_assign_group(ec, gctx->gen_group); + if (ret) + ec_gen_sync_conv_form(ec); /* Whether you want it or not, you get a keypair, not just one half */ if ((gctx->selection & OSSL_KEYMGMT_SELECT_KEYPAIR) != 0) { @@ -1376,6 +1393,8 @@ static void *sm2_gen(void *genctx, OSSL_CALLBACK *osslcb, void *cbarg) /* We must always assign a group, no matter what */ ret = ec_gen_assign_group(ec, gctx->gen_group); + if (ret) + ec_gen_sync_conv_form(ec); /* Whether you want it or not, you get a keypair, not just one half */ if ((gctx->selection & OSSL_KEYMGMT_SELECT_KEYPAIR) != 0) From e01449f798c6a2d05a222aec9cfb535a53b878bc Mon Sep 17 00:00:00 2001 From: Viktor Dukhovni Date: Sat, 25 Apr 2026 22:12:59 +1000 Subject: [PATCH 2/3] Filter EC certs against our own advertised point formats tls1_check_pkey_comp() only compared the cert's point form to the peer's advertised list, never to our own. If a server held a compressed EC leaf, the peer advertised compressed, and we didn't, the cert-selector picked the leaf anyway and the peer responded with an illegal_parameter alert -- the mismatch only got caught after the cert had already gone out on the wire. Now we also require the cert's form to appear in our own advertised list. The new 33-compressed-spki ssl_new test runs through ten stanzas: both sides advertise compressed (cert selected), either side leaves the option off (server aborts), TLS 1.3 (the option is unused), mixed RSA+EC fallback, and an uncompressed control to confirm we're filtering by point form rather than cert type. This is also our first end-to-end TLS test of compressed EC public keys in certificates, made possible now that genpkey has working support for: -algorithm ec -pkeyopt point-format:compressed --- ssl/t1_lib.c | 17 ++ test/certs/mkcert.sh | 12 +- test/certs/server-ec-compressed-cert.pem | 11 + test/certs/server-ec-compressed-key.pem | 5 + test/certs/setup.sh | 10 + test/recipes/80-test_ssl_new.t | 4 +- test/ssl-tests/33-compressed-spki.cnf | 338 ++++++++++++++++++++++ test/ssl-tests/33-compressed-spki.cnf.in | 345 +++++++++++++++++++++++ 8 files changed, 740 insertions(+), 2 deletions(-) create mode 100644 test/certs/server-ec-compressed-cert.pem create mode 100644 test/certs/server-ec-compressed-key.pem create mode 100644 test/ssl-tests/33-compressed-spki.cnf create mode 100644 test/ssl-tests/33-compressed-spki.cnf.in diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c index 2a15378570..84dc90cfc2 100644 --- a/ssl/t1_lib.c +++ b/ssl/t1_lib.c @@ -1911,6 +1911,8 @@ static int tls1_check_pkey_comp(SSL_CONNECTION *s, EVP_PKEY *pkey) unsigned char comp_id; size_t i; int point_conv; + const unsigned char *own_formats; + size_t own_formats_len; /* If not an EC key nothing to check */ if (!EVP_PKEY_is_a(pkey, "EC")) @@ -1944,6 +1946,21 @@ static int tls1_check_pkey_comp(SSL_CONNECTION *s, EVP_PKEY *pkey) */ if (s->ext.peer_ecpointformats == NULL) return 1; + /* + * The cert's point form must also be in our OWN advertised list -- we + * mustn't commit to a cert in a form we told the peer we don't speak. + * Without this the server's cert-selection happily picks (and the + * client happily decodes-and-then-rejects) a leaf whose form is in the + * peer's list but not ours, leaving the peer to alert illegal_parameter + * on receipt instead of catching the mismatch before it's wire-visible. + */ + tls1_get_formatlist(s, &own_formats, &own_formats_len); + for (i = 0; i < own_formats_len; i++) { + if (own_formats[i] == comp_id) + break; + } + if (i == own_formats_len) + return 0; for (i = 0; i < s->ext.peer_ecpointformats_len; i++) { if (s->ext.peer_ecpointformats[i] == comp_id) diff --git a/test/certs/mkcert.sh b/test/certs/mkcert.sh index 087dc343d7..9a417e77ae 100755 --- a/test/certs/mkcert.sh +++ b/test/certs/mkcert.sh @@ -52,7 +52,17 @@ key() { case $alg in rsa) args=("${args[@]}" -pkeyopt rsa_keygen_bits:$bits );; ec) args=("${args[@]}" -pkeyopt "ec_paramgen_curve:$bits") - args=("${args[@]}" -pkeyopt ec_param_enc:named_curve);; + args=("${args[@]}" -pkeyopt ec_param_enc:named_curve) + # OPENSSL_EC_POINT_FORMAT (uncompressed|compressed|hybrid) drives + # the per-key point conversion form recorded on the generated + # EC_KEY, surfaced in SubjectPublicKeyInfo and negotiated via the + # TLS 1.2 ec_point_formats extension. Empty/unset preserves the + # provider default (uncompressed). + if [ -n "$OPENSSL_EC_POINT_FORMAT" ]; then + args=("${args[@]}" \ + -pkeyopt "point-format:$OPENSSL_EC_POINT_FORMAT") + fi + ;; dsa) args=(-paramfile "$bits");; ed25519) ;; ed448) ;; diff --git a/test/certs/server-ec-compressed-cert.pem b/test/certs/server-ec-compressed-cert.pem new file mode 100644 index 0000000000..475ccd457b --- /dev/null +++ b/test/certs/server-ec-compressed-cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBozCCASigAwIBAgIBAjAKBggqhkjOPQQDAjAbMRkwFwYDVQQDDBBFQ0RTQSBQ +LTM4NCByb290MCAXDTI2MDQyNTA1MDgzNFoYDzIxMjYwNDI2MDUwODM0WjAZMRcw +FQYDVQQDDA5zZXJ2ZXIuZXhhbXBsZTA5MBMGByqGSM49AgEGCCqGSM49AwEHAyIA +A4Mt9T6fKt3APp8/Frw65PDi2eMYdZK98nhBW9pA1Ccho30wezAdBgNVHQ4EFgQU +6MzfJGpHsWRJ3Nuj1KOStlvabW0wHwYDVR0jBBgwFoAUJtCPHXtf3B5/QYB9Y8oc +dYHWhWkwCQYDVR0TBAIwADATBgNVHSUEDDAKBggrBgEFBQcDATAZBgNVHREEEjAQ +gg5zZXJ2ZXIuZXhhbXBsZTAKBggqhkjOPQQDAgNpADBmAjEApXyoA/nmW3woPvjq +H/ea3zDcN7mcaUYMwaCsyvsNNlKjFv1xdSFjxRFKUTqepJ/UAjEA7hQZq6hSm85b ++2TawUUn6Yj04cYq/XTirif3VtVauLABTRJ40S57jEpkDh6sovSC +-----END CERTIFICATE----- diff --git a/test/certs/server-ec-compressed-key.pem b/test/certs/server-ec-compressed-key.pem new file mode 100644 index 0000000000..98218fcb3c --- /dev/null +++ b/test/certs/server-ec-compressed-key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MGcCAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcETTBLAgEBBCAb1VgxSUhJyh43soLb +FMsebjWSp/Hma3kSyw6lT4txDaEkAyIAA4Mt9T6fKt3APp8/Frw65PDi2eMYdZK9 +8nhBW9pA1Cch +-----END PRIVATE KEY----- diff --git a/test/certs/setup.sh b/test/certs/setup.sh index 3bee78ec32..3bba1b769b 100755 --- a/test/certs/setup.sh +++ b/test/certs/setup.sh @@ -211,6 +211,16 @@ OPENSSL_KEYBITS=768 \ # EC cert with named curve signed by named curve ca ./mkcert.sh genee server.example ee-key-ec-named-named \ ee-cert-ec-named-named ca-key-ec-named ca-cert-ec-named +# EC EE cert directly issued by the P-384 EC root, with a P-256 named-curve +# leaf key whose per-key conv_form is COMPRESSED. The leading SPKI bit-string +# byte is 0x02 or 0x03, exercising the OSSL_PKEY_PARAM_EC_POINT_CONVERSION_- +# FORMAT keygen path end-to-end. Anchors TLS 1.2 ec_point_formats negotiation +# tests in 80-test_ssl_new (sslnew tests rely solely on PKIX, so the CN/SAN +# value is functionally arbitrary; we keep "server.example" for consistency). +OPENSSL_KEYALG=ec OPENSSL_KEYBITS=prime256v1 \ +OPENSSL_EC_POINT_FORMAT=compressed \ +./mkcert.sh genee server.example server-ec-compressed-key \ + server-ec-compressed-cert p384-root-key p384-root # 1024-bit leaf key OPENSSL_KEYBITS=1024 \ ./mkcert.sh genee server.example ee-key-1024 ee-cert-1024 ca-key ca-cert diff --git a/test/recipes/80-test_ssl_new.t b/test/recipes/80-test_ssl_new.t index a2d69e3c47..8516652f97 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; @@ -100,6 +100,7 @@ my %conf_dependent_tests = ( "28-seclevel.cnf" => disabled("tls1_2") || $no_ecx, "30-extended-master-secret.cnf" => disabled("tls1_2"), "32-compressed-certificate.cnf" => disabled("comp") || disabled("tls1_3"), + "33-compressed-spki.cnf" => disabled("tls1_2") || disabled("tls1_3") || $no_ec, ); # Add your test here if it should be skipped for some compile-time @@ -135,6 +136,7 @@ my %skip = ( "26-tls13_client_auth.cnf" => disabled("tls1_3") || ($no_ec && $no_dh), "29-dtls-sctp-label-bug.cnf" => disabled("sctp") || disabled("sock"), "32-compressed-certificate.cnf" => disabled("comp") || disabled("tls1_3"), + "33-compressed-spki.cnf" => disabled("tls1_2") || disabled("tls1_3") || $no_ec, ); foreach my $conf (@conf_files) { diff --git a/test/ssl-tests/33-compressed-spki.cnf b/test/ssl-tests/33-compressed-spki.cnf new file mode 100644 index 0000000000..bc428de0e4 --- /dev/null +++ b/test/ssl-tests/33-compressed-spki.cnf @@ -0,0 +1,338 @@ +# Generated with generate_ssl_tests.pl + +num_tests = 10 + +test-0 = 0-tls12-compressed-spki-both-sides-ok +test-1 = 1-tls12-compressed-spki-client-no-option +test-2 = 2-tls12-compressed-spki-server-no-option +test-3 = 3-tls13-compressed-spki +test-4 = 4-tls12-mixed-both-sides-ok +test-5 = 5-tls12-mixed-client-no-option +test-6 = 6-tls12-mixed-server-no-option +test-7 = 7-tls13-mixed +test-8 = 8-tls12-mixed-uncompressed +test-9 = 9-tls13-mixed-uncompressed +# =========================================================== + +[0-tls12-compressed-spki-both-sides-ok] +ssl_conf = 0-tls12-compressed-spki-both-sides-ok-ssl + +[0-tls12-compressed-spki-both-sides-ok-ssl] +server = 0-tls12-compressed-spki-both-sides-ok-server +client = 0-tls12-compressed-spki-both-sides-ok-client + +[0-tls12-compressed-spki-both-sides-ok-server] +Certificate = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-cert.pem +CipherString = DEFAULT +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +Options = LegacyECPointFormats +PrivateKey = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-key.pem + +[0-tls12-compressed-spki-both-sides-ok-client] +CipherString = ECDHE-ECDSA-AES128-GCM-SHA256 +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +Options = LegacyECPointFormats +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem +VerifyMode = Peer + +[test-0] +ExpectedProtocol = TLSv1.2 +ExpectedResult = Success +ExpectedServerCertType = P-256 + + +# =========================================================== + +[1-tls12-compressed-spki-client-no-option] +ssl_conf = 1-tls12-compressed-spki-client-no-option-ssl + +[1-tls12-compressed-spki-client-no-option-ssl] +server = 1-tls12-compressed-spki-client-no-option-server +client = 1-tls12-compressed-spki-client-no-option-client + +[1-tls12-compressed-spki-client-no-option-server] +Certificate = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-cert.pem +CipherString = DEFAULT +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +Options = LegacyECPointFormats +PrivateKey = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-key.pem + +[1-tls12-compressed-spki-client-no-option-client] +CipherString = ECDHE-ECDSA-AES128-GCM-SHA256 +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem +VerifyMode = Peer + +[test-1] +ExpectedResult = ServerFail +ExpectedServerAlert = HandshakeFailure + + +# =========================================================== + +[2-tls12-compressed-spki-server-no-option] +ssl_conf = 2-tls12-compressed-spki-server-no-option-ssl + +[2-tls12-compressed-spki-server-no-option-ssl] +server = 2-tls12-compressed-spki-server-no-option-server +client = 2-tls12-compressed-spki-server-no-option-client + +[2-tls12-compressed-spki-server-no-option-server] +Certificate = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-cert.pem +CipherString = DEFAULT +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +PrivateKey = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-key.pem + +[2-tls12-compressed-spki-server-no-option-client] +CipherString = ECDHE-ECDSA-AES128-GCM-SHA256 +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +Options = LegacyECPointFormats +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem +VerifyMode = Peer + +[test-2] +ExpectedResult = ServerFail +ExpectedServerAlert = HandshakeFailure + + +# =========================================================== + +[3-tls13-compressed-spki] +ssl_conf = 3-tls13-compressed-spki-ssl + +[3-tls13-compressed-spki-ssl] +server = 3-tls13-compressed-spki-server +client = 3-tls13-compressed-spki-client + +[3-tls13-compressed-spki-server] +Certificate = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-cert.pem +CipherString = DEFAULT +MaxProtocol = TLSv1.3 +MinProtocol = TLSv1.3 +PrivateKey = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-key.pem + +[3-tls13-compressed-spki-client] +CipherString = DEFAULT +MaxProtocol = TLSv1.3 +MinProtocol = TLSv1.3 +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem +VerifyMode = Peer + +[test-3] +ExpectedProtocol = TLSv1.3 +ExpectedResult = Success +ExpectedServerCertType = P-256 + + +# =========================================================== + +[4-tls12-mixed-both-sides-ok] +ssl_conf = 4-tls12-mixed-both-sides-ok-ssl + +[4-tls12-mixed-both-sides-ok-ssl] +server = 4-tls12-mixed-both-sides-ok-server +client = 4-tls12-mixed-both-sides-ok-client + +[4-tls12-mixed-both-sides-ok-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 +ECDSA.Certificate = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-cert.pem +ECDSA.PrivateKey = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-key.pem +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +Options = LegacyECPointFormats +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem +SignatureAlgorithms = ecdsa_secp256r1_sha256:rsa_pkcs1_sha256 + +[4-tls12-mixed-both-sides-ok-client] +CipherString = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 +EC.VerifyCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +Options = LegacyECPointFormats +SignatureAlgorithms = ecdsa_secp256r1_sha256:rsa_pkcs1_sha256 +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-4] +ExpectedProtocol = TLSv1.2 +ExpectedResult = Success +ExpectedServerCertType = P-256 + + +# =========================================================== + +[5-tls12-mixed-client-no-option] +ssl_conf = 5-tls12-mixed-client-no-option-ssl + +[5-tls12-mixed-client-no-option-ssl] +server = 5-tls12-mixed-client-no-option-server +client = 5-tls12-mixed-client-no-option-client + +[5-tls12-mixed-client-no-option-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 +ECDSA.Certificate = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-cert.pem +ECDSA.PrivateKey = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-key.pem +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +Options = LegacyECPointFormats +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem +SignatureAlgorithms = ecdsa_secp256r1_sha256:rsa_pkcs1_sha256 + +[5-tls12-mixed-client-no-option-client] +CipherString = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 +EC.VerifyCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +SignatureAlgorithms = ecdsa_secp256r1_sha256:rsa_pkcs1_sha256 +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-5] +ExpectedProtocol = TLSv1.2 +ExpectedResult = Success +ExpectedServerCertType = RSA + + +# =========================================================== + +[6-tls12-mixed-server-no-option] +ssl_conf = 6-tls12-mixed-server-no-option-ssl + +[6-tls12-mixed-server-no-option-ssl] +server = 6-tls12-mixed-server-no-option-server +client = 6-tls12-mixed-server-no-option-client + +[6-tls12-mixed-server-no-option-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 +ECDSA.Certificate = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-cert.pem +ECDSA.PrivateKey = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-key.pem +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem +SignatureAlgorithms = ecdsa_secp256r1_sha256:rsa_pkcs1_sha256 + +[6-tls12-mixed-server-no-option-client] +CipherString = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 +EC.VerifyCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +Options = LegacyECPointFormats +SignatureAlgorithms = ecdsa_secp256r1_sha256:rsa_pkcs1_sha256 +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-6] +ExpectedProtocol = TLSv1.2 +ExpectedResult = Success +ExpectedServerCertType = RSA + + +# =========================================================== + +[7-tls13-mixed] +ssl_conf = 7-tls13-mixed-ssl + +[7-tls13-mixed-ssl] +server = 7-tls13-mixed-server +client = 7-tls13-mixed-client + +[7-tls13-mixed-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +ECDSA.Certificate = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-cert.pem +ECDSA.PrivateKey = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-key.pem +MaxProtocol = TLSv1.3 +MinProtocol = TLSv1.3 +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem +SignatureAlgorithms = ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256 + +[7-tls13-mixed-client] +CipherString = DEFAULT +EC.VerifyCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem +MaxProtocol = TLSv1.3 +MinProtocol = TLSv1.3 +SignatureAlgorithms = ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256 +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-7] +ExpectedProtocol = TLSv1.3 +ExpectedResult = Success +ExpectedServerCertType = P-256 + + +# =========================================================== + +[8-tls12-mixed-uncompressed] +ssl_conf = 8-tls12-mixed-uncompressed-ssl + +[8-tls12-mixed-uncompressed-ssl] +server = 8-tls12-mixed-uncompressed-server +client = 8-tls12-mixed-uncompressed-client + +[8-tls12-mixed-uncompressed-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 +ECDSA.Certificate = ${ENV::TEST_CERTS_DIR}/server-ecdsa-cert.pem +ECDSA.PrivateKey = ${ENV::TEST_CERTS_DIR}/server-ecdsa-key.pem +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem +SignatureAlgorithms = ecdsa_secp256r1_sha256:rsa_pkcs1_sha256 + +[8-tls12-mixed-uncompressed-client] +CipherString = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 +MaxProtocol = TLSv1.2 +MinProtocol = TLSv1.2 +SignatureAlgorithms = ecdsa_secp256r1_sha256:rsa_pkcs1_sha256 +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-8] +ExpectedProtocol = TLSv1.2 +ExpectedResult = Success +ExpectedServerCertType = P-256 + + +# =========================================================== + +[9-tls13-mixed-uncompressed] +ssl_conf = 9-tls13-mixed-uncompressed-ssl + +[9-tls13-mixed-uncompressed-ssl] +server = 9-tls13-mixed-uncompressed-server +client = 9-tls13-mixed-uncompressed-client + +[9-tls13-mixed-uncompressed-server] +Certificate = ${ENV::TEST_CERTS_DIR}/servercert.pem +CipherString = DEFAULT +ECDSA.Certificate = ${ENV::TEST_CERTS_DIR}/server-ecdsa-cert.pem +ECDSA.PrivateKey = ${ENV::TEST_CERTS_DIR}/server-ecdsa-key.pem +MaxProtocol = TLSv1.3 +MinProtocol = TLSv1.3 +PrivateKey = ${ENV::TEST_CERTS_DIR}/serverkey.pem +SignatureAlgorithms = ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256 + +[9-tls13-mixed-uncompressed-client] +CipherString = DEFAULT +MaxProtocol = TLSv1.3 +MinProtocol = TLSv1.3 +SignatureAlgorithms = ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256 +VerifyCAFile = ${ENV::TEST_CERTS_DIR}/rootcert.pem +VerifyMode = Peer + +[test-9] +ExpectedProtocol = TLSv1.3 +ExpectedResult = Success +ExpectedServerCertType = P-256 + + diff --git a/test/ssl-tests/33-compressed-spki.cnf.in b/test/ssl-tests/33-compressed-spki.cnf.in new file mode 100644 index 0000000000..9b016dc67b --- /dev/null +++ b/test/ssl-tests/33-compressed-spki.cnf.in @@ -0,0 +1,345 @@ +# -*- mode: perl; -*- +# Copyright 2026 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 + + +## SSL test configurations +## +## The server presents a TLS leaf certificate (server-ec-compressed-cert.pem) +## whose SubjectPublicKeyInfo's bit-string starts with 0x02 or 0x03 -- i.e. a +## P-256 named-curve public key encoded in COMPRESSED form, anchored to the +## P-384 EC root. +## +## TLS 1.2 ec_point_formats (RFC 4492) requires both peers to opt into legacy +## compressed support, exposed via SSL_OP_LEGACY_EC_POINT_FORMATS / the +## "LegacyECPointFormats" SSL_CONF Options token. Without it on either side +## the per-side ec_point_formats list contains only 'uncompressed', and +## tls1_check_pkey_comp() refuses to use a compressed-form leaf -- on the +## server when selecting a cert against the client's advertised list, on the +## client when verifying the server's cert against the server's advertised +## list. +## +## TLS 1.3 dropped the ec_point_formats extension entirely; a compressed-form +## leaf is just bytes the receiver decodes, so the option is irrelevant. + +package ssltests; +use OpenSSL::Test::Utils; + +# Shared bits for the mixed-cert (RSA + EC-compressed) stanzas below. The +# server loads BOTH an RSA leaf (anchored to ssltests_base.pm's default +# rootcert.pem) and the compressed-form EC leaf (anchored to p384-root.pem); +# the per-key-type prefixes ("ECDSA.", "EC.") are arbitrary distinct strings +# stripped before SSL_CONF command matching -- they exist solely to keep +# duplicate directive names from colliding. Slot dispatch is by the loaded +# cert's key type, not the prefix. do_store() appends each VerifyCAFile to +# the verify_store, so the EC.VerifyCAFile below adds p384-root.pem on top +# of the base's rootcert.pem default rather than replacing it. +my $mixed_server = { + "Certificate" => test_pem("servercert.pem"), + "PrivateKey" => test_pem("serverkey.pem"), + "ECDSA.Certificate" => test_pem("server-ec-compressed-cert.pem"), + "ECDSA.PrivateKey" => test_pem("server-ec-compressed-key.pem"), +}; + +my $mixed_client = { + "EC.VerifyCAFile" => test_pem("p384-root.pem"), +}; + +# Variant of the above where the EC leaf is the stock P-256 uncompressed-form +# cert (server-ecdsa-cert.pem). Both server-ecdsa-cert.pem and servercert.pem +# chain to ssltests_base.pm's default VerifyCAFile (rootcert.pem), so the +# client side needs no overrides. +my $uncompressed_server = { + "Certificate" => test_pem("servercert.pem"), + "PrivateKey" => test_pem("serverkey.pem"), + "ECDSA.Certificate" => test_pem("server-ecdsa-cert.pem"), + "ECDSA.PrivateKey" => test_pem("server-ecdsa-key.pem"), +}; + +# Every stanza below is EC-specific and requires both TLS 1.2 and TLS 1.3. +# In any build that lacks one of those, emit no tests at all -- there's +# nothing meaningful to exercise here. The recipe's %conf_dependent_tests +# entry skips the reference-comparison check under the same conditions. +our @tests = (); + +unless (disabled("ec") || disabled("tls1_2") || disabled("tls1_3")) { +@tests = ( + { + name => "tls12-compressed-spki-both-sides-ok", + server => { + "Certificate" => test_pem("server-ec-compressed-cert.pem"), + "PrivateKey" => test_pem("server-ec-compressed-key.pem"), + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + "Options" => "LegacyECPointFormats", + }, + client => { + "VerifyCAFile" => test_pem("p384-root.pem"), + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + "CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256", + "Options" => "LegacyECPointFormats", + }, + test => { + "ExpectedResult" => "Success", + "ExpectedProtocol" => "TLSv1.2", + "ExpectedServerCertType" => "P-256", + }, + }, + + { + # Client did not opt in: its ec_point_formats lists 'uncompressed' + # only. tls1_check_pkey_comp() on the server compares its leaf cert's + # point form against the *peer's* advertised formats; the cert is + # compressed, the peer advertises only uncompressed, so cert selection + # finds nothing and the server aborts with handshake_failure. + name => "tls12-compressed-spki-client-no-option", + server => { + "Certificate" => test_pem("server-ec-compressed-cert.pem"), + "PrivateKey" => test_pem("server-ec-compressed-key.pem"), + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + "Options" => "LegacyECPointFormats", + }, + client => { + "VerifyCAFile" => test_pem("p384-root.pem"), + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + "CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256", + }, + test => { + "ExpectedResult" => "ServerFail", + "ExpectedServerAlert" => "HandshakeFailure", + }, + }, + + { + # Server did not opt in: its own ec_point_formats lists 'uncompressed' + # only. tls1_check_pkey_comp() now (correctly) checks the cert's + # form against BOTH the server's own and the peer's advertised lists; + # the cert is compressed, the server's own list is uncompressed-only, + # the cert is dropped from the candidate set, no other usable cert + # exists, and the server aborts with handshake_failure -- before + # anything cert-shaped is wire-visible. + name => "tls12-compressed-spki-server-no-option", + server => { + "Certificate" => test_pem("server-ec-compressed-cert.pem"), + "PrivateKey" => test_pem("server-ec-compressed-key.pem"), + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + }, + client => { + "VerifyCAFile" => test_pem("p384-root.pem"), + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + "CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256", + "Options" => "LegacyECPointFormats", + }, + test => { + "ExpectedResult" => "ServerFail", + "ExpectedServerAlert" => "HandshakeFailure", + }, + }, + + { + # TLS 1.3 omits ec_point_formats; the option is irrelevant and the + # compressed SPKI just decodes. + name => "tls13-compressed-spki", + server => { + "Certificate" => test_pem("server-ec-compressed-cert.pem"), + "PrivateKey" => test_pem("server-ec-compressed-key.pem"), + "MinProtocol" => "TLSv1.3", + "MaxProtocol" => "TLSv1.3", + }, + client => { + "VerifyCAFile" => test_pem("p384-root.pem"), + "MinProtocol" => "TLSv1.3", + "MaxProtocol" => "TLSv1.3", + }, + test => { + "ExpectedResult" => "Success", + "ExpectedProtocol" => "TLSv1.3", + "ExpectedServerCertType" => "P-256", + }, + }, + + # ---------------------------------------------------------------------- + # Mixed-cert RSA-fallback stanzas. The server presents BOTH an RSA leaf + # (servercert.pem under root-cert.pem) and the P-256 compressed-SPKI EC + # leaf (server-ec-compressed-cert.pem under p384-root.pem). Both peers + # advertise sigalgs preferring ECDSA over RSA, so the EC leaf is used + # whenever it's eligible; when point-format negotiation rules it out (one + # side or the other didn't opt into compressed), tls1_check_pkey_comp() + # filters the EC cert and the server falls back to RSA. TLS 1.3 dropped + # ec_point_formats, so the EC leaf is always eligible there. + # ---------------------------------------------------------------------- + + { + # Both sides opted in: ECDHE-ECDSA picked, EC leaf used. + name => "tls12-mixed-both-sides-ok", + server => { + %$mixed_server, + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + "Options" => "LegacyECPointFormats", + "SignatureAlgorithms" => "ecdsa_secp256r1_sha256:rsa_pkcs1_sha256", + "CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256", + }, + client => { + %$mixed_client, + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + "Options" => "LegacyECPointFormats", + "SignatureAlgorithms" => "ecdsa_secp256r1_sha256:rsa_pkcs1_sha256", + "CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256", + }, + test => { + "ExpectedResult" => "Success", + "ExpectedProtocol" => "TLSv1.2", + "ExpectedServerCertType" => "P-256", + }, + }, + + { + # Client did not opt in. Server's selection sees the peer + # advertises only 'uncompressed' point format, so the compressed EC + # leaf is filtered; cert selection falls back to the RSA leaf and + # the handshake completes with ECDHE-RSA. + name => "tls12-mixed-client-no-option", + server => { + %$mixed_server, + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + "Options" => "LegacyECPointFormats", + "SignatureAlgorithms" => "ecdsa_secp256r1_sha256:rsa_pkcs1_sha256", + "CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256", + }, + client => { + %$mixed_client, + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + "SignatureAlgorithms" => "ecdsa_secp256r1_sha256:rsa_pkcs1_sha256", + "CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256", + }, + test => { + "ExpectedResult" => "Success", + "ExpectedProtocol" => "TLSv1.2", + "ExpectedServerCertType" => "RSA", + }, + }, + + { + # Server did not opt in. After the tls1_check_pkey_comp() fix the + # server compares its cert's point form against its OWN advertised + # list too; the compressed EC leaf is filtered and selection falls + # back to RSA. Without the fix the server would have committed to + # the EC leaf and the client would have alerted illegal_parameter + # on receipt. + name => "tls12-mixed-server-no-option", + server => { + %$mixed_server, + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + "SignatureAlgorithms" => "ecdsa_secp256r1_sha256:rsa_pkcs1_sha256", + "CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256", + }, + client => { + %$mixed_client, + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + "Options" => "LegacyECPointFormats", + "SignatureAlgorithms" => "ecdsa_secp256r1_sha256:rsa_pkcs1_sha256", + "CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256", + }, + test => { + "ExpectedResult" => "Success", + "ExpectedProtocol" => "TLSv1.2", + "ExpectedServerCertType" => "RSA", + }, + }, + + { + # TLS 1.3 omits ec_point_formats and rsa_pkcs1_* is forbidden for + # handshake signatures, so we use rsa_pss_rsae_sha256 (the cert's + # key OID is rsaEncryption -- _rsae_, not _pss_). EC is always + # eligible here; ECDSA wins by sigalg preference. + name => "tls13-mixed", + server => { + %$mixed_server, + "MinProtocol" => "TLSv1.3", + "MaxProtocol" => "TLSv1.3", + "SignatureAlgorithms" => "ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256", + }, + client => { + %$mixed_client, + "MinProtocol" => "TLSv1.3", + "MaxProtocol" => "TLSv1.3", + "SignatureAlgorithms" => "ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256", + }, + test => { + "ExpectedResult" => "Success", + "ExpectedProtocol" => "TLSv1.3", + "ExpectedServerCertType" => "P-256", + }, + }, + + # ---------------------------------------------------------------------- + # Control: same RSA-vs-EC sigalg-preference setup, but the EC leaf is in + # the standard uncompressed SPKI form. Neither side opts into legacy + # compressed support; each side's ec_point_formats list is the default + # ('uncompressed' only). An uncompressed leaf is admitted by every + # branch of tls1_check_pkey_comp() unconditionally, so the EC cert is + # selected and the RSA cert is unused -- demonstrating that the + # point-format machinery filters only compressed leaves. + # ---------------------------------------------------------------------- + + { + name => "tls12-mixed-uncompressed", + server => { + %$uncompressed_server, + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + "SignatureAlgorithms" => "ecdsa_secp256r1_sha256:rsa_pkcs1_sha256", + "CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256", + }, + client => { + "MinProtocol" => "TLSv1.2", + "MaxProtocol" => "TLSv1.2", + "SignatureAlgorithms" => "ecdsa_secp256r1_sha256:rsa_pkcs1_sha256", + "CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256", + }, + test => { + "ExpectedResult" => "Success", + "ExpectedProtocol" => "TLSv1.2", + "ExpectedServerCertType" => "P-256", + }, + }, + + { + # TLS 1.3 omits ec_point_formats entirely; this is the same setup + # as tls12-mixed-uncompressed but at TLS 1.3, kept for parity with + # the rest of the matrix. + name => "tls13-mixed-uncompressed", + server => { + %$uncompressed_server, + "MinProtocol" => "TLSv1.3", + "MaxProtocol" => "TLSv1.3", + "SignatureAlgorithms" => "ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256", + }, + client => { + "MinProtocol" => "TLSv1.3", + "MaxProtocol" => "TLSv1.3", + "SignatureAlgorithms" => "ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256", + }, + test => { + "ExpectedResult" => "Success", + "ExpectedProtocol" => "TLSv1.3", + "ExpectedServerCertType" => "P-256", + }, + }, +); +} From 27c8682e83ee08fee15579361499201e6fe64fa3 Mon Sep 17 00:00:00 2001 From: Viktor Dukhovni Date: Sun, 26 Apr 2026 12:45:46 +1000 Subject: [PATCH 3/3] fixup! EC: make the group the source of the point conversion form --- providers/implementations/keymgmt/ec_kmgmt.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/providers/implementations/keymgmt/ec_kmgmt.c b/providers/implementations/keymgmt/ec_kmgmt.c index 736f2dcc0e..8af8e13aa3 100644 --- a/providers/implementations/keymgmt/ec_kmgmt.c +++ b/providers/implementations/keymgmt/ec_kmgmt.c @@ -503,16 +503,14 @@ static int ec_export(void *keydata, int selection, OSSL_CALLBACK *param_cb, && (selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) == 0) return 0; - tmpl = OSSL_PARAM_BLD_new(); - if (tmpl == NULL) + if ((bnctx = BN_CTX_new_ex(ossl_ec_key_get_libctx(ec))) == NULL) return 0; + BN_CTX_start(bnctx); - bnctx = BN_CTX_new_ex(ossl_ec_key_get_libctx(ec)); - if (bnctx == NULL) { + if ((tmpl = OSSL_PARAM_BLD_new()) == NULL) { ok = 0; goto end; } - BN_CTX_start(bnctx); /* * OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT is added based on the group's * asn1_form by the call below; otherparams_to_params() no longer adds the