Commit e6fe06a719 for openssl.org
commit e6fe06a71930c93b93ebed2ccd6a881fa90f51d3
Author: Igor Ustinov <igus@openssl.foundation>
Date: Tue Apr 14 16:46:51 2026 +0200
Added the EVP_EC_affine2oct() helper function
This function converts affine coordinates of an EC point
to an octet string conforming to Sec. 2.3.4
of the SECG SEC 1 ("Elliptic Curve Cryptography") standard.
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Simo Sorce <simo@redhat.com>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
MergeDate: Wed May 6 16:47:57 2026
(Merged from https://github.com/openssl/openssl/pull/30597)
diff --git a/CHANGES.md b/CHANGES.md
index a1ecdccfa4..94953f18bd 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -100,6 +100,12 @@ OpenSSL Releases
*Tomáš Mráz*
+ * Added `EVP_EC_affine2oct()` that converts the affine coordinates of an
+ EC point to an octet string conforming to Sec. 2.3.4 of the SECG SEC 1
+ ("Elliptic Curve Cryptography") standard.
+
+ *Igor Ustinov*
+
* Made more QUIC transport parameters configurable via the
`SSL_get_value_uint`/`SSL_set_value_uint` functions. Now also configurable:
`max_udp_payload_size`, `initial_max_data`,
diff --git a/crypto/evp/ec_support.c b/crypto/evp/ec_support.c
index 20883c48f1..4763507ec1 100644
--- a/crypto/evp/ec_support.c
+++ b/crypto/evp/ec_support.c
@@ -9,6 +9,7 @@
#include <string.h>
#include <openssl/ec.h>
+#include <openssl/err.h>
#include "crypto/ec.h"
#include "internal/nelem.h"
@@ -186,3 +187,51 @@ int ossl_ec_curve_nist2nid_int(const char *name)
}
return NID_undef;
}
+
+int EVP_EC_affine2oct(const BIGNUM *x, const BIGNUM *y, size_t field_len,
+ unsigned char **pbuf, size_t *pbsize)
+{
+ unsigned char *buf = NULL;
+ size_t buflen = 0;
+
+ if (x == NULL || y == NULL || pbuf == NULL || pbsize == NULL) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
+ return 0;
+ }
+
+ if (field_len > 2048) {
+ ERR_raise_data(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT,
+ "The value of field_len is unreasonably large");
+ return 0;
+ }
+
+ /* Checking if affine coordinates are not too long */
+ if (BN_num_bytes(x) > (int)field_len || BN_num_bytes(y) > (int)field_len) {
+ ERR_raise_data(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT,
+ "EC affine coordinate exceeds field length");
+ return 0;
+ }
+
+ /* Converting (X,Y) to the SEC1 uncompressed point encoding blob */
+ buflen = 1 + 2 * field_len;
+ buf = OPENSSL_malloc(buflen);
+ if (buf == NULL)
+ return 0;
+ buf[0] = POINT_CONVERSION_UNCOMPRESSED;
+ if (BN_bn2binpad(x, buf + 1, (int)field_len) < 0) {
+ ERR_raise_data(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT,
+ "failed to encode X coordinate");
+ OPENSSL_free(buf);
+ return 0;
+ }
+ if (BN_bn2binpad(y, buf + 1 + field_len, (int)field_len) < 0) {
+ ERR_raise_data(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT,
+ "failed to encode Y coordinate");
+ OPENSSL_free(buf);
+ return 0;
+ }
+
+ *pbuf = buf;
+ *pbsize = buflen;
+ return 1;
+}
diff --git a/doc/build.info b/doc/build.info
index d0e5088b9c..6e8dfedb5c 100644
--- a/doc/build.info
+++ b/doc/build.info
@@ -1155,6 +1155,10 @@ DEPEND[html/man3/EVP_DigestVerifyInit.html]=man3/EVP_DigestVerifyInit.pod
GENERATE[html/man3/EVP_DigestVerifyInit.html]=man3/EVP_DigestVerifyInit.pod
DEPEND[man/man3/EVP_DigestVerifyInit.3]=man3/EVP_DigestVerifyInit.pod
GENERATE[man/man3/EVP_DigestVerifyInit.3]=man3/EVP_DigestVerifyInit.pod
+DEPEND[html/man3/EVP_EC_gen.html]=man3/EVP_EC_gen.pod
+GENERATE[html/man3/EVP_EC_gen.html]=man3/EVP_EC_gen.pod
+DEPEND[man/man3/EVP_EC_gen.3]=man3/EVP_EC_gen.pod
+GENERATE[man/man3/EVP_EC_gen.3]=man3/EVP_EC_gen.pod
DEPEND[html/man3/EVP_EncodeInit.html]=man3/EVP_EncodeInit.pod
GENERATE[html/man3/EVP_EncodeInit.html]=man3/EVP_EncodeInit.pod
DEPEND[man/man3/EVP_EncodeInit.3]=man3/EVP_EncodeInit.pod
@@ -3336,6 +3340,7 @@ html/man3/EVP_CIPHER_CTX_get_original_iv.html \
html/man3/EVP_DigestInit.html \
html/man3/EVP_DigestSignInit.html \
html/man3/EVP_DigestVerifyInit.html \
+html/man3/EVP_EC_gen.html \
html/man3/EVP_EncodeInit.html \
html/man3/EVP_EncryptInit.html \
html/man3/EVP_KDF.html \
@@ -4010,6 +4015,7 @@ man/man3/EVP_CIPHER_CTX_get_original_iv.3 \
man/man3/EVP_DigestInit.3 \
man/man3/EVP_DigestSignInit.3 \
man/man3/EVP_DigestVerifyInit.3 \
+man/man3/EVP_EC_gen.3 \
man/man3/EVP_EncodeInit.3 \
man/man3/EVP_EncryptInit.3 \
man/man3/EVP_KDF.3 \
diff --git a/doc/man3/EC_KEY_new.pod b/doc/man3/EC_KEY_new.pod
index 2bdfd6d96a..9495174366 100644
--- a/doc/man3/EC_KEY_new.pod
+++ b/doc/man3/EC_KEY_new.pod
@@ -2,7 +2,6 @@
=head1 NAME
-EVP_EC_gen,
EC_KEY_get_method, EC_KEY_set_method, EC_KEY_new_ex,
EC_KEY_new, EC_KEY_get_flags, EC_KEY_set_flags, EC_KEY_clear_flags,
EC_KEY_new_by_curve_name_ex, EC_KEY_new_by_curve_name, EC_KEY_free,
@@ -21,8 +20,6 @@ EC_KEY objects
#include <openssl/ec.h>
- EVP_PKEY *EVP_EC_gen(const char *curve);
-
The following functions have been deprecated since OpenSSL 3.0, and can be
hidden entirely by defining B<OPENSSL_API_COMPAT> with a suitable version value,
see L<openssl_user_macros(7)>:
@@ -67,8 +64,6 @@ see L<openssl_user_macros(7)>:
=head1 DESCRIPTION
-EVP_EC_gen() generates a new EC key pair on the given I<curve>.
-
All of the functions described below are deprecated.
Applications should instead use EVP_EC_gen(), L<EVP_PKEY_Q_keygen(3)>, or
L<EVP_PKEY_keygen_init(3)> and L<EVP_PKEY_keygen(3)>.
@@ -223,15 +218,14 @@ L<OSSL_LIB_CTX(3)>
=head1 HISTORY
-EVP_EC_gen() was added in OpenSSL 3.0.
-All other functions described here were deprecated in OpenSSL 3.0.
+All the functions described here were deprecated in OpenSSL 3.0.
For replacement see L<EVP_PKEY-EC(7)>.
The EC_KEY_get0_engine() was removed in OpenSSL 4.0.
=head1 COPYRIGHT
-Copyright 2013-2023 The OpenSSL Project Authors. All Rights Reserved.
+Copyright 2013-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
diff --git a/doc/man3/EVP_EC_gen.pod b/doc/man3/EVP_EC_gen.pod
new file mode 100644
index 0000000000..6279a73445
--- /dev/null
+++ b/doc/man3/EVP_EC_gen.pod
@@ -0,0 +1,49 @@
+=pod
+
+=head1 NAME
+
+EVP_EC_gen, EVP_EC_affine2oct
+- EVP routines for EC keys
+
+=head1 SYNOPSIS
+
+ #include <openssl/evp.h>
+
+ EVP_PKEY *EVP_EC_gen(const char *curve);
+ int EVP_EC_affine2oct(const BIGNUM *x, const BIGNUM *y, size_t field_len,
+ unsigned char **pbuf, size_t *pbsize);
+
+=head1 DESCRIPTION
+
+EVP_EC_gen() generates a new EC key pair on the given I<curve>.
+
+EVP_EC_affine2oct() converts affine coordinates I<x> and I<y> of an EC point
+to an octet string conforming to Sec. 2.3.4 of the SECG SEC 1
+("Elliptic Curve Cryptography") standard. This octet string can further
+be passed to OSSL_PARAM_BLD_push_octet_string(). The length of the field degree
+representation (in bytes) must be passed to I<field_len>. The function allocates
+a buffer for octet string and places a pointer to I<*pbuf>. It's the caller's
+responsibility to free this buffer with OPENSSL_free().
+
+=head1 RETURN VALUES
+
+EVP_EC_gen() returns the EC key pair generated or NULL on error.
+
+EVP_EC_affine2oct() returns 1 on success or 0 on error.
+
+=head1 HISTORY
+
+EVP_EC_gen() was added in OpenSSL 3.0.
+
+EVP_EC_affine2oct() was added in OpenSSL 4.1.
+
+=head1 COPYRIGHT
+
+Copyright 2013-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
+L<https://www.openssl.org/source/license.html>.
+
+=cut
diff --git a/doc/man7/EVP_PKEY-EC.pod b/doc/man7/EVP_PKEY-EC.pod
index 547c3bbd0d..78ac13f332 100644
--- a/doc/man7/EVP_PKEY-EC.pod
+++ b/doc/man7/EVP_PKEY-EC.pod
@@ -239,6 +239,13 @@ For EC Keys, L<EVP_PKEY_private_check(3)> and L<EVP_PKEY_pairwise_check(3)>
conform to SP800-56Ar3 I<Private key validity> and
I<Owner Assurance of Pair-wise Consistency> respectively.
+=head1 NOTES
+
+"qx" (B<OSSL_PKEY_PARAM_EC_PUB_X>) and "qy" (B<OSSL_PKEY_PARAM_EC_PUB_Y>)
+can be used for getting the EC public key affine coordinates. To set
+them call EVP_EC_affine2oct() to convert affine coordinates to
+an octet string and then use "pub" (B<OSSL_PKEY_PARAM_PUB_KEY>).
+
=head1 EXAMPLES
An B<EVP_PKEY> context can be obtained by calling:
diff --git a/include/openssl/evp.h b/include/openssl/evp.h
index 2fefb1bd93..91cec28fe8 100644
--- a/include/openssl/evp.h
+++ b/include/openssl/evp.h
@@ -1945,6 +1945,9 @@ const char *EVP_SKEY_get0_provider_name(const EVP_SKEY *skey);
EVP_SKEY *EVP_SKEY_to_provider(EVP_SKEY *skey, OSSL_LIB_CTX *libctx,
OSSL_PROVIDER *prov, const char *propquery);
+int EVP_EC_affine2oct(const BIGNUM *x, const BIGNUM *y, size_t field_len,
+ unsigned char **pbuf, size_t *pbsize);
+
#ifdef __cplusplus
}
#endif
diff --git a/test/evp_pkey_provided_test.c b/test/evp_pkey_provided_test.c
index fbb418ab6b..3bead64de0 100644
--- a/test/evp_pkey_provided_test.c
+++ b/test/evp_pkey_provided_test.c
@@ -1601,7 +1601,13 @@ err:
}
#endif /* OPENSSL_NO_ECX */
-static int test_fromdata_ec(void)
+/*
+ * tst uses indexes 0..3
+ * 0 = uncompressed format
+ * 1 = compressed format
+ * 2 = affine coordinates via EVP_EC_affine2oct()
+ */
+static int test_fromdata_ec(int tst)
{
int ret = 0;
EVP_PKEY_CTX *ctx = NULL;
@@ -1637,6 +1643,19 @@ static int test_fromdata_ec(void)
0x02, 0xa5, 0x77, 0x57, 0xc8, 0xa3, 0x47, 0x73,
0x3a, 0x6a, 0x08, 0x28, 0x39, 0xbd, 0xc9, 0xd2
};
+ /* SAME IN AFFINE COORDINATES */
+ static const unsigned char x_buf[] = {
+ 0x1b, 0x93, 0x67, 0x55, 0x1c, 0x55, 0x9f, 0x63,
+ 0xd1, 0x22, 0xa4, 0xd8, 0xd1, 0x0a, 0x60, 0x6d,
+ 0x02, 0xa5, 0x77, 0x57, 0xc8, 0xa3, 0x47, 0x73,
+ 0x3a, 0x6a, 0x08, 0x28, 0x39, 0xbd, 0xc9, 0xd2
+ };
+ static const unsigned char y_buf[] = {
+ 0x80, 0xec, 0xe9, 0xa7, 0x08, 0x29, 0x71, 0x2f,
+ 0xc9, 0x56, 0x82, 0xee, 0x9a, 0x85, 0x0f, 0x6d,
+ 0x7f, 0x59, 0x5f, 0x8c, 0xd1, 0x96, 0x0b, 0xdf,
+ 0x29, 0x3e, 0x49, 0x07, 0x88, 0x3f, 0x9a, 0x29
+ };
static const unsigned char ec_priv_keydata[] = {
0x33, 0xd0, 0x43, 0x83, 0xa9, 0x89, 0x56, 0x03,
0xd2, 0xd7, 0xfe, 0x6b, 0x01, 0x6f, 0xe4, 0x59,
@@ -1659,6 +1678,10 @@ static int test_fromdata_ec(void)
NULL, 0),
OSSL_PARAM_END
};
+ BIGNUM *x = NULL;
+ BIGNUM *y = NULL;
+ unsigned char *buf = NULL;
+ size_t buflen = 0;
if (!TEST_ptr(bld = OSSL_PARAM_BLD_new()))
goto err;
@@ -1678,11 +1701,35 @@ static int test_fromdata_ec(void)
* `OSSL_PKEY_PARAM_PUB_KEY` and expect to default to uncompressed
* format.
*/
- if (OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY,
- ec_pub_keydata_compressed,
- sizeof(ec_pub_keydata_compressed))
- <= 0)
+ switch (tst) {
+ case 0:
+ if (!TEST_true(OSSL_PARAM_BLD_push_octet_string(bld,
+ OSSL_PKEY_PARAM_PUB_KEY,
+ ec_pub_keydata_compressed,
+ sizeof(ec_pub_keydata_compressed))))
+ goto err;
+ break;
+ case 1:
+ if (!TEST_true(OSSL_PARAM_BLD_push_octet_string(bld,
+ OSSL_PKEY_PARAM_PUB_KEY,
+ ec_pub_keydata, sizeof(ec_pub_keydata))))
+ goto err;
+ break;
+ case 2:
+ if (!TEST_ptr(x = BN_bin2bn(x_buf, sizeof(x_buf), NULL))
+ || !TEST_ptr(y = BN_bin2bn(y_buf, sizeof(y_buf), NULL)))
+ goto err;
+ if (!TEST_true(EVP_EC_affine2oct(x, y, 32, &buf, &buflen))
+ || !TEST_ptr(buf)
+ || !TEST_size_t_eq(buflen, 65))
+ goto err;
+ if (!TEST_true(OSSL_PARAM_BLD_push_octet_string(bld,
+ OSSL_PKEY_PARAM_PUB_KEY, buf, buflen)))
+ goto err;
+ break;
+ default:
goto err;
+ }
if (OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, ec_priv_bn) <= 0)
goto err;
if (!TEST_ptr(fromdata_params = OSSL_PARAM_BLD_to_param(bld)))
@@ -1818,11 +1865,15 @@ err:
BN_free(p);
BN_free(bn_priv);
BN_free(ec_priv_bn);
+ BN_free(x);
+ BN_free(y);
OSSL_PARAM_free(fromdata_params);
OSSL_PARAM_BLD_free(bld);
EVP_PKEY_free(pk);
EVP_PKEY_free(copy_pk);
EVP_PKEY_CTX_free(ctx);
+ if (buf != NULL)
+ OPENSSL_free(buf);
return ret;
}
@@ -2307,7 +2358,7 @@ int setup_tests(void)
#ifndef OPENSSL_NO_ECX
ADD_ALL_TESTS(test_fromdata_ecx, 4 * 3);
#endif
- ADD_TEST(test_fromdata_ec);
+ ADD_ALL_TESTS(test_fromdata_ec, 3);
ADD_TEST(test_ec_dup_no_operation);
ADD_TEST(test_ec_dup_keygen_operation);
#endif
diff --git a/util/libcrypto.num b/util/libcrypto.num
index 5061b5e5a0..4e6ac0563c 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -5719,3 +5719,4 @@ CTLOG_STORE_add0_log ? 4_1_0 EXIST::FUNCTION:CT
CRYPTO_atomic_load_ptr ? 4_1_0 EXIST::FUNCTION:
CRYPTO_atomic_store_ptr ? 4_1_0 EXIST::FUNCTION:
CRYPTO_atomic_cmp_exch_ptr ? 4_1_0 EXIST::FUNCTION:
+EVP_EC_affine2oct ? 4_1_0 EXIST::FUNCTION: