]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Added the EVP_EC_affine2oct() helper function
authorIgor Ustinov <igus@openssl.foundation>
Tue, 14 Apr 2026 14:46:51 +0000 (16:46 +0200)
committerTomas Mraz <tomas@openssl.foundation>
Wed, 6 May 2026 16:46:51 +0000 (18:46 +0200)
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)

CHANGES.md
crypto/evp/ec_support.c
doc/build.info
doc/man3/EC_KEY_new.pod
doc/man3/EVP_EC_gen.pod [new file with mode: 0644]
doc/man7/EVP_PKEY-EC.pod
include/openssl/evp.h
test/evp_pkey_provided_test.c
util/libcrypto.num

index a1ecdccfa45a9bc7b487f20ea5eb472633bed8d6..94953f18bd6500c120be6bcf96429a99c078f229 100644 (file)
@@ -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`,
index 20883c48f10d69d6fcd2615d15189213506ac161..4763507ec1c5939cad052289358a88de9ed70e6e 100644 (file)
@@ -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;
+}
index d0e5088b9cd73c803133aae4954efcabcb1a0e83..6e8dfedb5c2a467527221fa9146b4c4f8aef4726 100644 (file)
@@ -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 \
index 2bdfd6d96a6ce9f9aa0aa694bde246d34ee242b9..9495174366542026b1919b91936d86a504c67352 100644 (file)
@@ -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 (file)
index 0000000..6279a73
--- /dev/null
@@ -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
index 547c3bbd0d0df2d87de37b6169e2ebbb582484ae..78ac13f332c0c57e0be3b59c02aa97b74e694e17 100644 (file)
@@ -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:
index 2fefb1bd93ade2891a425028a286d3032a761b13..91cec28fe8fa1637b93ab08852e4eedb8ed58bec 100644 (file)
@@ -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
index fbb418ab6ba75a4fcc30cad133b7d0beda7d9b8d..3bead64de08509c5c429bab9b6773296263a4df5 100644 (file)
@@ -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
index 5061b5e5a002d4b06a67275fff50f4f773500eaa..4e6ac0563c13071cdaa05596b487fb673a25d76c 100644 (file)
@@ -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: