Commit 9d59af025a for openssl.org
commit 9d59af025a025b0aaa18148868ba0c773ce0ce97
Author: Viktor Dukhovni <viktor@openssl.org>
Date: Sat Apr 25 22:12:59 2026 +1000
Improve TLS handling of EC point formats
Decouple the ec_point_formats extension from TLS 1.2 X.509
selection and acceptance. Remove tls1_check_pkey_comp() and its
callers in tls1_check_cert_param() and tls1_check_chain(): TLS 1.3
disregards the extension already, and we can decode any point form
a peer might send, so refusing a compressed peer cert in TLS 1.2
because we didn't advertise compressed buys nothing.
The RFC 4492/8422 section 5.1.2 requirement that the peer's list
contain "uncompressed" used to be enforced in a final hook on the
client side only. Move it to the two points where the negotiated
ciphersuite is known: the client's ServerHello parse hook, and the
server's ServerHello construct hook. Both sites fire the alert
only when an ECC TLS 1.2 ciphersuite has been negotiated, so a
missing "uncompressed" is ignored under TLS 1.3 or a non-ECC
cipher. The client- and server-side parse hooks now share one
function.
Drop the always-NULL ext.ecpointformats fields on SSL_CTX and SSL;
our own list is built directly inside the constructors. The
peer's list continues to be stored verbatim, and is also exposed
through the SSL_get0_ec_point_formats() accessor (now documented).
New tests verify the four corners (ECC vs non-ECC ciphersuite, TLS
1.2 vs 1.3) plus that a compressed point form EC cert is usable on
both sides without any opt-in.
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
MergeDate: Fri Jul 10 09:21:30 2026
(Merged from https://github.com/openssl/openssl/pull/30940)
diff --git a/CHANGES.md b/CHANGES.md
index 0c9366b01e..70ed350c79 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -31,6 +31,34 @@ OpenSSL Releases
### Changes between 4.0 and 4.1 [xx XXX xxxx]
+ * EC key point format simplification.
+
+ The point conversion form (compressed, uncompressed, or hybrid)
+ is now a single value on the `EC_GROUP` and round-trips
+ unchanged through import and export of `EC_KEY` objects.
+
+ Freshly generated keys have their public point encoded in
+ uncompressed form. A `point-format` supplied at key generation
+ time via `OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT` is
+ validated (an invalid value is rejected) but otherwise ignored
+ on the generated key. EC parameter generation continues to
+ honour the requested form on the group's generator; imported
+ keys keep their form.
+
+ The `ec_point_formats` extension no longer affects TLS 1.2
+ X.509 certificate selection or acceptance. OpenSSL now
+ accepts an EC certificate in any point form it can decode,
+ and sends any EC certificate it has regardless of point form.
+ TLS 1.3 disregards the extension entirely.
+
+ The RFC 4492/8422 section 5.1.2 requirement that the peer's
+ point-format list contain "uncompressed" is now enforced on
+ both sides (previously client-only), and only when an ECC
+ TLS 1.2 ciphersuite is negotiated -- a missing "uncompressed"
+ is ignored under TLS 1.3 or with a non-ECC cipher.
+
+ *Viktor Dukhovni*
+
* Added unit tests setup activated via `enable-unit-tests` option. This works
only on platforms with ld `--wrap` support (Linux, BSD).
@@ -51,7 +79,7 @@ OpenSSL Releases
*Adriano Sela Aviles*
* SubjectPublicKeyInfo blobs whose AlgorithmIdentifier uses id-RSAES-OAEP
- (NID_rsaesOaep, 1.2.840.113549.1.1.7) with a plain RSAPublicKey body
+ (`NID_rsaesOaep`, 1.2.840.113549.1.1.7) with a plain RSAPublicKey body
are now decoded as RSA keys. This is required for interoperability
with TPM 1.2 Endorsement Key certificates per TCG Credential Profiles
V1.2 section 3.2.7. The OAEP AlgorithmIdentifier parameters are not
diff --git a/doc/man3/SSL_CTX_set1_curves.pod b/doc/man3/SSL_CTX_set1_curves.pod
index 64321df5f4..f8f9a149f7 100755
--- a/doc/man3/SSL_CTX_set1_curves.pod
+++ b/doc/man3/SSL_CTX_set1_curves.pod
@@ -4,7 +4,8 @@
SSL_CTX_set1_groups, SSL_CTX_set1_groups_list, SSL_set1_groups,
SSL_set1_groups_list, SSL_get1_groups, SSL_get0_iana_groups,
-SSL_get_shared_group, SSL_get_negotiated_group, SSL_CTX_set1_curves,
+SSL_get0_ec_point_formats, SSL_get_shared_group, SSL_get_negotiated_group,
+SSL_CTX_set1_curves,
SSL_CTX_set1_curves_list, SSL_set1_curves, SSL_set1_curves_list,
SSL_get1_curves, SSL_get_shared_curve, SSL_CTX_get0_implemented_groups
- EC supported curve functions
@@ -21,6 +22,7 @@ SSL_get1_curves, SSL_get_shared_curve, SSL_CTX_get0_implemented_groups
int SSL_get1_groups(SSL *ssl, int *groups);
int SSL_get0_iana_groups(SSL *ssl, uint16_t **out);
+ int SSL_get0_ec_point_formats(SSL *ssl, const unsigned char **plst);
int SSL_get_shared_group(SSL *s, int n);
int SSL_get_negotiated_group(SSL *s);
@@ -246,6 +248,19 @@ identifiers, as assigned by IANA. The group list is returned in the same order
that was received in the ClientHello. The return value is the number of groups,
not the number of bytes written.
+SSL_get0_ec_point_formats() retrieves the peer's B<ec_point_formats>
+extension as a byte array of B<TLSEXT_ECPOINTFORMAT_*> values in the
+order received. If the peer sent the extension, B<*plst> is set to an
+internal buffer holding the list and its length is returned; otherwise
+the return value is 0 and B<*plst> is left unchanged. The returned
+pointer is owned by the B<SSL> connection and must not be freed. The
+point format no longer affects certificate selection or acceptance.
+
+The B<ec_point_formats> extension applies only to TLS 1.2 and below, but
+the peer's list is recorded even when TLS 1.3 is ultimately negotiated,
+since a TLS 1.3 B<ClientHello> may still carry the extension for
+backward compatibility.
+
SSL_get_shared_group() returns the NID of the shared group B<n> for a
server-side SSL B<ssl>. If B<n> is -1 then the total number of shared groups is
returned, which may be zero. Other than for diagnostic purposes,
@@ -304,6 +319,10 @@ SSL_get1_groups() returns the number of groups, which may be zero.
SSL_get0_iana_groups() returns the number of (uint16_t) groups, which may be zero.
+SSL_get0_ec_point_formats() returns the number of point formats sent by
+the peer in its B<ec_point_formats> extension, or 0 if no such extension
+was received.
+
SSL_get_shared_group() returns the NID of shared group B<n> or NID_undef if there
is no shared group B<n>; or the total number of shared groups if B<n>
is -1.
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 2902a5f5aa..a900ad557a 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -819,15 +819,6 @@ SSL *ossl_ssl_connection_new_int(SSL_CTX *ctx, SSL *user_ssl,
goto err;
s->session_ctx = ctx;
- if (ctx->ext.ecpointformats != NULL) {
- s->ext.ecpointformats = OPENSSL_memdup(ctx->ext.ecpointformats,
- ctx->ext.ecpointformats_len);
- if (s->ext.ecpointformats == NULL) {
- s->ext.ecpointformats_len = 0;
- goto err;
- }
- s->ext.ecpointformats_len = ctx->ext.ecpointformats_len;
- }
if (ctx->ext.supportedgroups != NULL) {
size_t add = 0;
@@ -1527,7 +1518,6 @@ void ossl_ssl_connection_free(SSL *ssl)
OPENSSL_free(s->ext.hostname);
SSL_CTX_free(s->session_ctx);
- OPENSSL_free(s->ext.ecpointformats);
OPENSSL_free(s->ext.peer_ecpointformats);
OPENSSL_free(s->ext.supportedgroups);
OPENSSL_free(s->ext.keyshares);
@@ -4616,7 +4606,6 @@ void SSL_CTX_free(SSL_CTX *a)
ssl_ctx_srp_ctx_free_intern(a);
#endif
- OPENSSL_free(a->ext.ecpointformats);
OPENSSL_free(a->ext.supportedgroups);
OPENSSL_free(a->ext.keyshares);
OPENSSL_free(a->ext.tuples);
diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h
index 5c0fa8f925..978f627290 100644
--- a/ssl/ssl_local.h
+++ b/ssl/ssl_local.h
@@ -12,6 +12,7 @@
#ifndef OSSL_SSL_LOCAL_H
#define OSSL_SSL_LOCAL_H
+#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
@@ -1039,10 +1040,6 @@ struct ssl_ctx_st {
/* RFC 4366 Maximum Fragment Length Negotiation */
uint8_t max_fragment_len_mode;
- /* EC extension values inherited by SSL structure */
- size_t ecpointformats_len;
- unsigned char *ecpointformats;
-
size_t supportedgroups_len;
uint16_t *supportedgroups;
@@ -1653,8 +1650,6 @@ struct ssl_connection_st {
unsigned char *scts;
/* Length of raw extension data, if seen */
uint16_t scts_len;
- /* Expect OCSP CertificateStatus message */
- int status_expected;
struct {
/* OCSP status request only */
@@ -1666,15 +1661,16 @@ struct ssl_connection_st {
STACK_OF(OCSP_RESPONSE) *resp_ex;
} ocsp;
- /* RFC4507 session ticket expected to be received or sent */
- int ticket_expected;
/* TLS 1.3 tickets requested by the application. */
int extra_tickets_expected;
- /* our list */
- size_t ecpointformats_len;
- unsigned char *ecpointformats;
- /* peer's list */
+ /*
+ * Peer's advertised ec_point_formats list (TLS 1.2 and below),
+ * retained as received so SSL_get0_ec_point_formats() can return
+ * it verbatim. Point format no longer influences cert selection
+ * or acceptance; the parse hook validates RFC 4492/8422 section
+ * 5.1.2 ("uncompressed" must be present) inline.
+ */
size_t peer_ecpointformats_len;
unsigned char *peer_ecpointformats;
@@ -1719,19 +1715,12 @@ struct ssl_connection_st {
/* The available PSK key exchange modes */
int psk_kex_mode;
- /* Set to one if we have negotiated ETM */
- int use_etm;
-
/* Are we expecting to receive early data? */
int early_data;
- /* Is the session suitable for early data? */
- int early_data_ok;
/* May be sent by a server in HRR. Must be echoed back in ClientHello */
unsigned char *tls13_cookie;
size_t tls13_cookie_len;
- /* Have we received a cookie from the client? */
- int cookieok;
/*
* Maximum Fragment Length as per RFC 4366.
@@ -1753,8 +1742,6 @@ struct ssl_connection_st {
/* 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;
uint8_t client_cert_type;
uint8_t client_cert_type_ctos;
@@ -1767,7 +1754,27 @@ struct ssl_connection_st {
/* RFC 8701 GREASE */
uint8_t grease_seed[OSSL_GREASE_LAST_INDEX + 1];
- int grease_seeded;
+
+ /* "bool" fields go last, for slightly better packing */
+ bool grease_seeded;
+
+ /* Expect OCSP CertificateStatus message */
+ bool status_expected;
+
+ /* RFC4507 session ticket expected to be received or sent */
+ bool ticket_expected;
+
+ /* Set to one if we have negotiated ETM */
+ bool use_etm;
+
+ /* Is the session suitable for early data? */
+ bool early_data_ok;
+
+ /* Have we received a cookie from the client? */
+ bool cookieok;
+
+ /* indicate that we sent the extension, so we'll accept it */
+ bool compress_certificate_sent;
} ext;
/*
diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c
index 2f97850972..017d1a5fb5 100644
--- a/ssl/statem/extensions.c
+++ b/ssl/statem/extensions.c
@@ -59,8 +59,6 @@ static int final_ech(SSL_CONNECTION *s, unsigned int context, int sent);
static int final_renegotiate(SSL_CONNECTION *s, unsigned int context, int sent);
static int init_server_name(SSL_CONNECTION *s, unsigned int context);
static int final_server_name(SSL_CONNECTION *s, unsigned int context, int sent);
-static int final_ec_pt_formats(SSL_CONNECTION *s, unsigned int context,
- int sent);
static int init_session_ticket(SSL_CONNECTION *s, unsigned int context);
#ifndef OPENSSL_NO_OCSP
static int init_status_request(SSL_CONNECTION *s, unsigned int context);
@@ -111,6 +109,9 @@ static EXT_RETURN tls_construct_compress_certificate(SSL_CONNECTION *sc, WPACKET
static int tls_parse_compress_certificate(SSL_CONNECTION *sc, PACKET *pkt,
unsigned int context,
X509 *x, size_t chainidx);
+static int tls_parse_ec_pt_formats(SSL_CONNECTION *s, PACKET *pkt,
+ unsigned int context,
+ X509 *x, size_t chainidx);
/* Structure to define a built-in extension */
typedef struct extensions_definition_st {
@@ -217,9 +218,9 @@ static const EXTENSION_DEFINITION ext_defs[] = {
SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO
| SSL_EXT_TLS1_2_AND_BELOW_ONLY,
OSSL_ECH_HANDLING_COMPRESS,
- init_ec_point_formats, tls_parse_ctos_ec_pt_formats, tls_parse_stoc_ec_pt_formats,
+ init_ec_point_formats, tls_parse_ec_pt_formats, tls_parse_ec_pt_formats,
tls_construct_stoc_ec_pt_formats, tls_construct_ctos_ec_pt_formats,
- final_ec_pt_formats },
+ NULL },
{ /*
* "supported_groups" is spread across several specifications.
* It was originally specified as "elliptic_curves" in RFC 4492,
@@ -748,6 +749,55 @@ static int verify_extension(SSL_CONNECTION *s, unsigned int context,
return 1;
}
+/*
+ * Parse an ec_point_formats extension off the wire (one function for
+ * both sides). The peer's list is retained verbatim for
+ * SSL_get0_ec_point_formats() and for the RFC 4492/8422 section 5.1.2
+ * 'uncompressed must be present' check.
+ *
+ * The check is gated on the negotiated ciphersuite -- a TLS 1.3
+ * handshake or a non-ECC TLS 1.2 ciphersuite makes the extension moot
+ * and any missing 'uncompressed' codepoint is ignored. On the client
+ * the chosen ciphersuite is already locked in by the time we parse
+ * ServerHello, so the check happens inline here. On the server it's
+ * deferred to tls_construct_stoc_ec_pt_formats(), the first point at
+ * which s->s3.tmp.new_cipher is set for TLS 1.2.
+ */
+static int tls_parse_ec_pt_formats(SSL_CONNECTION *s, PACKET *pkt,
+ unsigned int context, X509 *x, size_t chainidx)
+{
+ PACKET list;
+
+ if (!PACKET_as_length_prefixed_1(pkt, &list)
+ || PACKET_remaining(&list) == 0) {
+ SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+ return 0;
+ }
+ if (!s->hit
+ && !PACKET_memdup(&list, &s->ext.peer_ecpointformats,
+ &s->ext.peer_ecpointformats_len)) {
+ SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+
+ if (!s->server) {
+ unsigned long alg_k = s->s3.tmp.new_cipher->algorithm_mkey;
+ unsigned long alg_a = s->s3.tmp.new_cipher->algorithm_auth;
+
+ if (((alg_k & SSL_kECDHE) || (alg_a & SSL_aECDSA))
+ && memchr(PACKET_data(&list),
+ TLSEXT_ECPOINTFORMAT_uncompressed,
+ PACKET_remaining(&list))
+ == NULL) {
+ SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER,
+ SSL_R_TLS_INVALID_ECPOINTFORMAT_LIST);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
/*
* Check whether the context defined for an extension |extctx| means whether
* the extension is relevant for the current context |thisctx| or not. Returns
@@ -1360,45 +1410,6 @@ static int final_server_name(SSL_CONNECTION *s, unsigned int context, int sent)
}
}
-static int final_ec_pt_formats(SSL_CONNECTION *s, unsigned int context,
- int sent)
-{
- unsigned long alg_k, alg_a;
-
- if (s->server)
- return 1;
-
- alg_k = s->s3.tmp.new_cipher->algorithm_mkey;
- alg_a = s->s3.tmp.new_cipher->algorithm_auth;
-
- /*
- * If we are client and using an elliptic curve cryptography cipher
- * suite, then if server returns an EC point formats lists extension it
- * must contain uncompressed.
- */
- if (s->ext.ecpointformats != NULL
- && s->ext.ecpointformats_len > 0
- && s->ext.peer_ecpointformats != NULL
- && s->ext.peer_ecpointformats_len > 0
- && ((alg_k & SSL_kECDHE) || (alg_a & SSL_aECDSA))) {
- /* we are using an ECC cipher */
- size_t i;
- unsigned char *list = s->ext.peer_ecpointformats;
-
- for (i = 0; i < s->ext.peer_ecpointformats_len; i++) {
- if (*list++ == TLSEXT_ECPOINTFORMAT_uncompressed)
- break;
- }
- if (i == s->ext.peer_ecpointformats_len) {
- SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER,
- SSL_R_TLS_INVALID_ECPOINTFORMAT_LIST);
- return 0;
- }
- }
-
- return 1;
-}
-
static int init_session_ticket(SSL_CONNECTION *s, unsigned int context)
{
if (!s->server)
diff --git a/ssl/statem/extensions_clnt.c b/ssl/statem/extensions_clnt.c
index 0b220965c1..e3e713f5f6 100644
--- a/ssl/statem/extensions_clnt.c
+++ b/ssl/statem/extensions_clnt.c
@@ -1763,46 +1763,6 @@ int tls_parse_stoc_server_name(SSL_CONNECTION *s, PACKET *pkt,
return 1;
}
-int tls_parse_stoc_ec_pt_formats(SSL_CONNECTION *s, PACKET *pkt,
- unsigned int context,
- X509 *x, size_t chainidx)
-{
- size_t ecpointformats_len;
- PACKET ecptformatlist;
-
- if (!PACKET_as_length_prefixed_1(pkt, &ecptformatlist)) {
- SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
- return 0;
- }
- if (!s->hit) {
- ecpointformats_len = PACKET_remaining(&ecptformatlist);
- if (ecpointformats_len == 0) {
- SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_LENGTH);
- return 0;
- }
-
- s->ext.peer_ecpointformats_len = 0;
- OPENSSL_free(s->ext.peer_ecpointformats);
- s->ext.peer_ecpointformats = OPENSSL_malloc(ecpointformats_len);
- if (s->ext.peer_ecpointformats == NULL) {
- s->ext.peer_ecpointformats_len = 0;
- SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
- return 0;
- }
-
- s->ext.peer_ecpointformats_len = ecpointformats_len;
-
- if (!PACKET_copy_bytes(&ecptformatlist,
- s->ext.peer_ecpointformats,
- ecpointformats_len)) {
- SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
- return 0;
- }
- }
-
- return 1;
-}
-
int tls_parse_stoc_session_ticket(SSL_CONNECTION *s, PACKET *pkt,
unsigned int context,
X509 *x, size_t chainidx)
diff --git a/ssl/statem/extensions_srvr.c b/ssl/statem/extensions_srvr.c
index 0460cd79e7..12c06eef9b 100644
--- a/ssl/statem/extensions_srvr.c
+++ b/ssl/statem/extensions_srvr.c
@@ -245,30 +245,6 @@ int tls_parse_ctos_srp(SSL_CONNECTION *s, PACKET *pkt, unsigned int context,
}
#endif
-int tls_parse_ctos_ec_pt_formats(SSL_CONNECTION *s, PACKET *pkt,
- unsigned int context,
- X509 *x, size_t chainidx)
-{
- PACKET ec_point_format_list;
-
- if (!PACKET_as_length_prefixed_1(pkt, &ec_point_format_list)
- || PACKET_remaining(&ec_point_format_list) == 0) {
- SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
- return 0;
- }
-
- if (!s->hit) {
- if (!PACKET_memdup(&ec_point_format_list,
- &s->ext.peer_ecpointformats,
- &s->ext.peer_ecpointformats_len)) {
- SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
- return 0;
- }
- }
-
- return 1;
-}
-
int tls_parse_ctos_session_ticket(SSL_CONNECTION *s, PACKET *pkt,
unsigned int context,
X509 *x, size_t chainidx)
@@ -1685,14 +1661,29 @@ EXT_RETURN tls_construct_stoc_ec_pt_formats(SSL_CONNECTION *s, WPACKET *pkt,
{
unsigned long alg_k = s->s3.tmp.new_cipher->algorithm_mkey;
unsigned long alg_a = s->s3.tmp.new_cipher->algorithm_auth;
- int using_ecc = ((alg_k & SSL_kECDHE) || (alg_a & SSL_aECDSA))
- && (s->ext.peer_ecpointformats != NULL);
+ int using_ecc = (alg_k & SSL_kECDHE) || (alg_a & SSL_aECDSA);
const unsigned char *plist;
size_t plistlen;
- if (!using_ecc)
+ /*
+ * The extension is irrelevant unless we're negotiating an ECC
+ * ciphersuite at TLS 1.2 or below, and the peer sent a list. This
+ * is the first point at which the chosen ciphersuite is known, so
+ * the RFC 4492/8422 section 5.1.2 check for the required
+ * 'uncompressed' codepoint also happens here.
+ */
+ if (!using_ecc || s->ext.peer_ecpointformats == NULL)
return EXT_RETURN_NOT_SENT;
+ if (memchr(s->ext.peer_ecpointformats,
+ TLSEXT_ECPOINTFORMAT_uncompressed,
+ s->ext.peer_ecpointformats_len)
+ == NULL) {
+ SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER,
+ SSL_R_TLS_INVALID_ECPOINTFORMAT_LIST);
+ return EXT_RETURN_FAIL;
+ }
+
tls1_get_formatlist(s, &plist, &plistlen);
if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_ec_point_formats)
|| !WPACKET_start_sub_packet_u16(pkt)
diff --git a/ssl/statem/statem_local.h b/ssl/statem/statem_local.h
index 68ac803fa9..b73c1e100f 100644
--- a/ssl/statem/statem_local.h
+++ b/ssl/statem/statem_local.h
@@ -306,9 +306,6 @@ int tls_parse_ctos_srp(SSL_CONNECTION *s, PACKET *pkt, unsigned int context,
int tls_parse_ctos_early_data(SSL_CONNECTION *s, PACKET *pkt,
unsigned int context,
X509 *x, size_t chainidx);
-int tls_parse_ctos_ec_pt_formats(SSL_CONNECTION *s, PACKET *pkt,
- unsigned int context,
- X509 *x, size_t chainidx);
int tls_parse_ctos_supported_groups(SSL_CONNECTION *s, PACKET *pkt,
unsigned int context,
X509 *x, size_t chainidxl);
@@ -518,9 +515,6 @@ int tls_parse_stoc_early_data(SSL_CONNECTION *s, PACKET *pkt,
int tls_parse_stoc_maxfragmentlen(SSL_CONNECTION *s, PACKET *pkt,
unsigned int context,
X509 *x, size_t chainidx);
-int tls_parse_stoc_ec_pt_formats(SSL_CONNECTION *s, PACKET *pkt,
- unsigned int context,
- X509 *x, size_t chainidx);
int tls_parse_stoc_session_ticket(SSL_CONNECTION *s, PACKET *pkt,
unsigned int context,
X509 *x, size_t chainidx);
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index b55b101324..5d0276a5a1 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -1886,13 +1886,7 @@ int tls1_check_group_id(SSL_CONNECTION *s, uint16_t group_id,
void tls1_get_formatlist(SSL_CONNECTION *s, const unsigned char **pformats,
size_t *num_formats)
{
- /*
- * If we have a custom point format list use it otherwise use default
- */
- if (s->ext.ecpointformats) {
- *pformats = s->ext.ecpointformats;
- *num_formats = s->ext.ecpointformats_len;
- } else if ((s->options & SSL_OP_LEGACY_EC_POINT_FORMATS) != 0) {
+ if ((s->options & SSL_OP_LEGACY_EC_POINT_FORMATS) != 0) {
*pformats = ecformats_all;
/* For Suite B we don't support char2 fields */
if (tls1_suiteb(s))
@@ -1905,53 +1899,6 @@ void tls1_get_formatlist(SSL_CONNECTION *s, const unsigned char **pformats,
}
}
-/* Check a key is compatible with compression extension */
-static int tls1_check_pkey_comp(SSL_CONNECTION *s, EVP_PKEY *pkey)
-{
- unsigned char comp_id;
- size_t i;
- int point_conv;
-
- /* If not an EC key nothing to check */
- if (!EVP_PKEY_is_a(pkey, "EC"))
- return 1;
-
- /* Get required compression id */
- point_conv = EVP_PKEY_get_ec_point_conv_form(pkey);
- if (point_conv == 0)
- return 0;
- if (point_conv == POINT_CONVERSION_UNCOMPRESSED) {
- comp_id = TLSEXT_ECPOINTFORMAT_uncompressed;
- } else if (SSL_CONNECTION_IS_TLS13(s)) {
- /*
- * ec_point_formats extension is not used in TLSv1.3 so we ignore
- * this check.
- */
- return 1;
- } else {
- int field_type = EVP_PKEY_get_field_type(pkey);
-
- if (field_type == NID_X9_62_prime_field)
- comp_id = TLSEXT_ECPOINTFORMAT_ansiX962_compressed_prime;
- else if (field_type == NID_X9_62_characteristic_two_field)
- comp_id = TLSEXT_ECPOINTFORMAT_ansiX962_compressed_char2;
- else
- return 0;
- }
- /*
- * If point formats extension present check it, otherwise everything is
- * supported (see RFC4492).
- */
- if (s->ext.peer_ecpointformats == NULL)
- return 1;
-
- for (i = 0; i < s->ext.peer_ecpointformats_len; i++) {
- if (s->ext.peer_ecpointformats[i] == comp_id)
- return 1;
- }
- return 0;
-}
-
/* Return group id of a key */
static uint16_t tls1_get_group_id(EVP_PKEY *pkey)
{
@@ -1964,7 +1911,7 @@ static uint16_t tls1_get_group_id(EVP_PKEY *pkey)
/*
* Check cert parameters compatible with extensions: currently just checks EC
- * certificates have compatible curves and compression.
+ * certificates have compatible curves.
*/
static int tls1_check_cert_param(SSL_CONNECTION *s, X509 *x, int check_ee_md)
{
@@ -1976,9 +1923,6 @@ static int tls1_check_cert_param(SSL_CONNECTION *s, X509 *x, int check_ee_md)
/* If not EC nothing to do */
if (!EVP_PKEY_is_a(pkey, "EC"))
return 1;
- /* Check compression */
- if (!tls1_check_pkey_comp(s, pkey))
- return 0;
group_id = tls1_get_group_id(pkey);
/*
* For a server we allow the certificate to not be in our list of supported
@@ -2917,13 +2861,11 @@ int tls12_check_peer_sigalg(SSL_CONNECTION *s, uint16_t sig, EVP_PKEY *pkey)
}
if (pkeyid == EVP_PKEY_EC) {
-
- /* Check point compression is permitted */
- if (!tls1_check_pkey_comp(s, pkey)) {
- SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER,
- SSL_R_ILLEGAL_POINT_COMPRESSION);
- return 0;
- }
+ /*
+ * No point-format check on either the peer's or own cert.
+ * We accept any form we can decode, and send the cert we
+ * have.
+ */
/* For TLS 1.3 or Suite B check curve matches signature algorithm */
if (SSL_CONNECTION_IS_TLS13(s) || tls1_suiteb(s)) {
@@ -4231,8 +4173,6 @@ int tls1_check_chain(SSL_CONNECTION *s, X509 *x, EVP_PKEY *pk,
chain = cpk->chain;
strict_mode = c->cert_flags & SSL_CERT_FLAGS_CHECK_TLS_STRICT;
if (tls12_rpk_and_privkey(s, idx)) {
- if (EVP_PKEY_is_a(pk, "EC") && !tls1_check_pkey_comp(s, pk))
- return 0;
*pvalid = rv = CERT_PKEY_RPK;
return rv;
}
diff --git a/test/certs/server-ec-compressed-cert.pem b/test/certs/server-ec-compressed-cert.pem
new file mode 100644
index 0000000000..4e97f49af2
--- /dev/null
+++ b/test/certs/server-ec-compressed-cert.pem
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIIBrzCCATSgAwIBAgIBAjAKBggqhkjOPQQDAjAbMRkwFwYDVQQDDBBFQ0RTQSBQ
+LTM4NCByb290MCAXDTI2MDYxOTE4MDA1OFoYDzIxMjYwNTI2MTgwMDU4WjAZMRcw
+FQYDVQQDDA5zZXJ2ZXIuZXhhbXBsZTA5MBMGByqGSM49AgEGCCqGSM49AwEHAyIA
+A4Mt9T6fKt3APp8/Frw65PDi2eMYdZK98nhBW9pA1Ccho4GIMIGFMB0GA1UdDgQW
+BBTozN8kakexZEnc26PUo5K2W9ptbTAfBgNVHSMEGDAWgBQm0I8de1/cHn9BgH1j
+yhx1gdaFaTAJBgNVHRMEAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
+AjAZBgNVHREEEjAQgg5zZXJ2ZXIuZXhhbXBsZTAKBggqhkjOPQQDAgNpADBmAjEA
+hvcNLTyL7vamQEJet5uvOXH7NKPHlG8sbfPvGS/AQ0yk6ARdc1Y1gV6FAnmnQUhX
+AjEA2HMTkh+h2ZDm68uRnhsNXIxI4dYF/nr5bdxw8XKN4P84Mg4gs37/IPoVN9jG
+Y4yh
+-----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/recipes/70-test_ec_point_formats.t b/test/recipes/70-test_ec_point_formats.t
new file mode 100644
index 0000000000..4c86b66c9a
--- /dev/null
+++ b/test/recipes/70-test_ec_point_formats.t
@@ -0,0 +1,183 @@
+#! /usr/bin/env 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
+
+use strict;
+
+use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/;
+use OpenSSL::Test::Utils;
+use TLSProxy::Proxy;
+use Cwd qw(abs_path);
+
+my $test_name = "test_ec_point_formats";
+setup($test_name);
+
+$ENV{OPENSSL_MODULES} = abs_path(bldtop_dir("test"));
+
+plan skip_all => "TLSProxy isn't usable on $^O"
+ if $^O =~ /^(VMS)$/;
+
+plan skip_all => "$test_name needs the module feature enabled"
+ if disabled("module");
+
+plan skip_all => "$test_name needs the sock feature enabled"
+ if disabled("sock");
+
+plan skip_all => "$test_name needs TLS 1.2 enabled"
+ if disabled("tls1_2");
+
+plan skip_all => "$test_name needs EC enabled"
+ if disabled("ec");
+
+# RFC 4492 section 5.1.2 requires the peer's ec_point_formats list to
+# contain "uncompressed", but only when an EC cipher suite is actually
+# negotiated at TLS 1.2 or below. The peer's list is irrelevant at TLS
+# 1.3 (the extension itself is) and at TLS 1.2 with a non-ECC cipher
+# suite. We mangle the list to contain only "compressed" (0x01) and
+# verify the four corner cases: both sides reject in an ECC TLS 1.2
+# handshake; both sides tolerate the missing "uncompressed" when TLS 1.3
+# or a non-ECC cipher suite is in play.
+
+# Wire format for ec_point_formats: 1-byte length prefix, then the
+# ECPointFormat bytes. "\x01\x01" advertises a list of one format,
+# the value 1 = ansiX962_compressed_prime; 0 (uncompressed) is absent.
+my $only_compressed = "\x01\x01";
+
+my $fatal_alert = 0;
+
+my $proxy = TLSProxy::Proxy->new(
+ \&mangle_ec_point_formats_clienthello,
+ cmdstr(app(["openssl"]), display => 1),
+ srctop_file("apps", "server.pem"),
+ (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE}),
+);
+
+# Test 1: a non-conforming list in the ClientHello -- the server
+# must abort with illegal_parameter. ECDHE-RSA suffices to
+# trigger the check (client supports at least one ECDHE group and
+# cipher, so sends the extension), no EC cert required from either
+# side.
+$proxy->clientflags("-tls1_2");
+$proxy->serverflags("-tls1_2");
+$proxy->cipherc("ECDHE-RSA-AES128-SHA256");
+$proxy->ciphers("ECDHE-RSA-AES128-SHA256");
+$proxy->start() or plan skip_all => "Unable to start up proxy for tests";
+plan tests => 4;
+ok($fatal_alert,
+ "Server rejects ClientHello whose ec_point_formats omits uncompressed");
+
+# Test 2: a non-conforming list in the ServerHello -- the client must
+# abort with the same error.
+$fatal_alert = 0;
+$proxy->clear();
+$proxy->filter(\&mangle_ec_point_formats_serverhello);
+$proxy->clientflags("-tls1_2");
+$proxy->serverflags("-tls1_2");
+$proxy->cipherc("ECDHE-RSA-AES128-SHA256");
+$proxy->ciphers("ECDHE-RSA-AES128-SHA256");
+$proxy->start();
+ok($fatal_alert,
+ "Client rejects ServerHello whose ec_point_formats omits uncompressed");
+
+# Mutating a ClientHello on the wire breaks the handshake transcript
+# (the client and server compute different transcript hashes), so a
+# tolerance test can't wait for the handshake to complete -- the
+# Finished MAC will fail later regardless of whether the construct
+# hook rejected. Instead, the proxy is allowed to run to whatever
+# end it finds and we look at $proxy->message_list afterwards: if the
+# server sent a ServerHello in response to the mangled ClientHello,
+# it accepted the extension; if it rejected, the only server-side
+# message in the list will be the fatal alert.
+sub server_sent_hello {
+ my $proxy = shift;
+ foreach my $msg (@{$proxy->message_list}) {
+ return 1 if $msg->mt == TLSProxy::Message::MT_SERVER_HELLO;
+ }
+ return 0;
+}
+
+# Test 3: TLS 1.3 ignores the ec_point_formats extension entirely. The
+# server doesn't emit one in its TLS 1.3 ServerHello, and the
+# construct-hook 5.1.2 check requires an ECC TLS <= 1.2 ciphersuite
+# to fire. A mangled ClientHello list must therefore leave the server
+# willing to send its ServerHello.
+SKIP: {
+ skip "TLS 1.3 disabled", 1 if disabled("tls1_3");
+
+ $fatal_alert = 0;
+ $proxy->clear();
+ $proxy->filter(\&mangle_ec_point_formats_clienthello);
+ $proxy->clientflags("-tls1_3");
+ $proxy->serverflags("-tls1_3");
+ $proxy->start();
+ ok(server_sent_hello($proxy),
+ "TLS 1.3 server tolerates ec_point_formats missing uncompressed");
+}
+
+# Test 4: At TLS 1.2 with a non-ECC cipher suite, the construct hook
+# returns NOT_SENT without inspecting the peer's list. Offer ECDHE-RSA
+# alongside DHE-RSA on the client -- the ECDHE entry is enough for the
+# client to advertise ec_point_formats -- pin the server to DHE-RSA,
+# and confirm a mangled list is tolerated.
+SKIP: {
+ skip "DH disabled", 1 if disabled("dh");
+
+ $fatal_alert = 0;
+ $proxy->clear();
+ $proxy->filter(\&mangle_ec_point_formats_clienthello);
+ $proxy->clientflags("-tls1_2");
+ $proxy->serverflags("-tls1_2");
+ $proxy->cipherc("ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-GCM-SHA256");
+ $proxy->ciphers("DHE-RSA-AES128-GCM-SHA256");
+ $proxy->start();
+ ok(server_sent_hello($proxy),
+ "TLS 1.2 non-ECC server tolerates ec_point_formats missing uncompressed");
+}
+
+sub mangle_ec_point_formats_clienthello
+{
+ my $proxy = shift;
+
+ if ($proxy->flight == 0) {
+ foreach my $message (@{$proxy->message_list}) {
+ if ($message->mt == TLSProxy::Message::MT_CLIENT_HELLO) {
+ $message->set_extension(
+ TLSProxy::Message::EXT_EC_POINT_FORMATS,
+ $only_compressed);
+ $message->repack();
+ }
+ }
+ return;
+ }
+
+ my $last_record = @{$proxy->{record_list}}[-1];
+ $fatal_alert = 1
+ if defined $last_record && $last_record->is_fatal_alert(1);
+}
+
+sub mangle_ec_point_formats_serverhello
+{
+ my $proxy = shift;
+
+ if ($proxy->flight == 0) {
+ return;
+ } elsif ($proxy->flight == 1) {
+ foreach my $message (@{$proxy->message_list}) {
+ if ($message->mt == TLSProxy::Message::MT_SERVER_HELLO) {
+ $message->set_extension(
+ TLSProxy::Message::EXT_EC_POINT_FORMATS,
+ $only_compressed);
+ $message->repack();
+ }
+ }
+ return;
+ }
+
+ my $last_record = @{$proxy->{record_list}}[-1];
+ $fatal_alert = 1
+ if defined $last_record && $last_record->is_fatal_alert(0);
+}
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..fd9816a27e
--- /dev/null
+++ b/test/ssl-tests/33-compressed-spki.cnf
@@ -0,0 +1,76 @@
+# Generated with generate_ssl_tests.pl
+
+num_tests = 2
+
+test-0 = 0-tls12-compressed-spki
+test-1 = 1-tls13-compressed-spki
+# ===========================================================
+
+[0-tls12-compressed-spki]
+ssl_conf = 0-tls12-compressed-spki-ssl
+
+[0-tls12-compressed-spki-ssl]
+server = 0-tls12-compressed-spki-server
+client = 0-tls12-compressed-spki-client
+
+[0-tls12-compressed-spki-server]
+Certificate = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-cert.pem
+CipherString = DEFAULT
+ClientCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem
+MaxProtocol = TLSv1.2
+MinProtocol = TLSv1.2
+PrivateKey = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-key.pem
+VerifyCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem
+VerifyMode = Require
+
+[0-tls12-compressed-spki-client]
+Certificate = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-cert.pem
+CipherString = ECDHE-ECDSA-AES128-GCM-SHA256
+MaxProtocol = TLSv1.2
+MinProtocol = TLSv1.2
+PrivateKey = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-key.pem
+VerifyCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem
+VerifyMode = Peer
+
+[test-0]
+ExpectedClientCertType = P-256
+ExpectedProtocol = TLSv1.2
+ExpectedResult = Success
+ExpectedServerCertType = P-256
+
+
+# ===========================================================
+
+[1-tls13-compressed-spki]
+ssl_conf = 1-tls13-compressed-spki-ssl
+
+[1-tls13-compressed-spki-ssl]
+server = 1-tls13-compressed-spki-server
+client = 1-tls13-compressed-spki-client
+
+[1-tls13-compressed-spki-server]
+Certificate = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-cert.pem
+CipherString = DEFAULT
+ClientCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem
+MaxProtocol = TLSv1.3
+MinProtocol = TLSv1.3
+PrivateKey = ${ENV::TEST_CERTS_DIR}/server-ec-compressed-key.pem
+VerifyCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem
+VerifyMode = Require
+
+[1-tls13-compressed-spki-client]
+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
+VerifyCAFile = ${ENV::TEST_CERTS_DIR}/p384-root.pem
+VerifyMode = Peer
+
+[test-1]
+ExpectedClientCertType = P-256
+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..06302a46a9
--- /dev/null
+++ b/test/ssl-tests/33-compressed-spki.cnf.in
@@ -0,0 +1,81 @@
+# -*- 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
+
+
+## End-to-end check that compressed-form EC leaf certificates
+## (server-ec-compressed-cert.pem, P-256 named curve, SPKI bit-string
+## leading byte 0x02 or 0x03, anchored to the P-384 EC root) work at
+## both TLS 1.2 and TLS 1.3 in both directions: the client presents
+## the same compressed leaf to a server that requires client
+## authentication, exercising X.509 acceptance of compressed point
+## form on both sides simultaneously. The ec_point_formats extension
+## no longer affects X.509 cert selection or acceptance, so neither
+## peer opts into LegacyECPointFormats; the default ec_point_formats
+## lists ('uncompressed' only) and the compressed leaves coexist.
+
+package ssltests;
+use OpenSSL::Test::Utils;
+
+our @tests = ();
+
+unless (disabled("ec") || disabled("tls1_2") || disabled("tls1_3")) {
+@tests = (
+ {
+ name => "tls12-compressed-spki",
+ server => {
+ "Certificate" => test_pem("server-ec-compressed-cert.pem"),
+ "PrivateKey" => test_pem("server-ec-compressed-key.pem"),
+ "VerifyCAFile" => test_pem("p384-root.pem"),
+ "ClientCAFile" => test_pem("p384-root.pem"),
+ "VerifyMode" => "Require",
+ "MinProtocol" => "TLSv1.2",
+ "MaxProtocol" => "TLSv1.2",
+ },
+ client => {
+ "Certificate" => test_pem("server-ec-compressed-cert.pem"),
+ "PrivateKey" => test_pem("server-ec-compressed-key.pem"),
+ "VerifyCAFile" => test_pem("p384-root.pem"),
+ "MinProtocol" => "TLSv1.2",
+ "MaxProtocol" => "TLSv1.2",
+ "CipherString" => "ECDHE-ECDSA-AES128-GCM-SHA256",
+ },
+ test => {
+ "ExpectedResult" => "Success",
+ "ExpectedProtocol" => "TLSv1.2",
+ "ExpectedServerCertType" => "P-256",
+ "ExpectedClientCertType" => "P-256",
+ },
+ },
+
+ {
+ name => "tls13-compressed-spki",
+ server => {
+ "Certificate" => test_pem("server-ec-compressed-cert.pem"),
+ "PrivateKey" => test_pem("server-ec-compressed-key.pem"),
+ "VerifyCAFile" => test_pem("p384-root.pem"),
+ "ClientCAFile" => test_pem("p384-root.pem"),
+ "VerifyMode" => "Require",
+ "MinProtocol" => "TLSv1.3",
+ "MaxProtocol" => "TLSv1.3",
+ },
+ client => {
+ "Certificate" => test_pem("server-ec-compressed-cert.pem"),
+ "PrivateKey" => test_pem("server-ec-compressed-key.pem"),
+ "VerifyCAFile" => test_pem("p384-root.pem"),
+ "MinProtocol" => "TLSv1.3",
+ "MaxProtocol" => "TLSv1.3",
+ },
+ test => {
+ "ExpectedResult" => "Success",
+ "ExpectedProtocol" => "TLSv1.3",
+ "ExpectedServerCertType" => "P-256",
+ "ExpectedClientCertType" => "P-256",
+ },
+ },
+);
+}
diff --git a/test/sslapitest.c b/test/sslapitest.c
index 1c106355c6..8baf38b93d 100644
--- a/test/sslapitest.c
+++ b/test/sslapitest.c
@@ -12439,7 +12439,7 @@ static int test_legacy_ec_point_formats(void)
{
SSL_CTX *cctx = NULL, *sctx = NULL;
SSL *clientssl = NULL, *serverssl = NULL;
- const char *pformats = NULL;
+ const unsigned char *pformats = NULL;
int nformats;
int testresult = 0;
diff --git a/util/missingmacro.txt b/util/missingmacro.txt
index 50b4b17b15..20e6077c6b 100644
--- a/util/missingmacro.txt
+++ b/util/missingmacro.txt
@@ -137,7 +137,6 @@ SSL_get0_certificate_types(3)
SSL_CTX_set1_client_certificate_types(3)
SSL_set1_client_certificate_types(3)
SSL_get0_raw_cipherlist(3)
-SSL_get0_ec_point_formats(3)
SSL_CTX_need_tmp_RSA(3)
SSL_CTX_set_tmp_rsa(3)
SSL_need_tmp_RSA(3)
diff --git a/util/other.syms b/util/other.syms
index 564704dd87..114e25a348 100644
--- a/util/other.syms
+++ b/util/other.syms
@@ -647,6 +647,7 @@ SSL_clear_mode define
SSL_disable_ct define
SSL_get0_chain_certs define
SSL_get0_iana_groups define
+SSL_get0_ec_point_formats define
SSL_get0_session define
SSL_get0_chain_cert_store define
SSL_get0_verify_cert_store define