]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
Make raw pubkey optional for raw privkey import.
authord-Dudas <david.dudas03@e-uvt.ro>
Mon, 13 Apr 2026 07:15:50 +0000 (10:15 +0300)
committerd-Dudas <david.dudas03@e-uvt.ro>
Sat, 18 Apr 2026 06:26:59 +0000 (09:26 +0300)
If no raw pubkey is provided when importing raw
privkey, then derive the pubkey from privkey.

Signed-off-by: David Dudas <david.dudas03@e-uvt.ro>
lib/hpke/hpke-key-management.c
lib/nettle/pk.c
lib/x509/privkey.c
tests/key-import-export.c

index 3373496e0d02c7b70f7595d7c2ffef02ca9941fa..6a7ec23cad478b1257ae844d8106b120086f1328 100644 (file)
 
 #include "errors.h"
 
-#include <nettle/curve25519.h>
-#include <nettle/curve448.h>
-#include <nettle/ecc.h>
-#include <nettle/ecc-curve.h>
-
 #define GNUTLS_HPKE_MAX_RAW_KEY_COORDINATE_SIZE 66
 #define GNUTLS_HPKE_MAX_MONTGOMERY_KEY_SIZE 56
 
@@ -202,32 +197,6 @@ static void clamp_sk(const gnutls_hpke_kem_t kem, unsigned char *sk_buf)
        }
 }
 
-static int derive_montgomery_curve_public_key(const gnutls_hpke_kem_t kem,
-                                             const gnutls_datum_t *priv_raw,
-                                             unsigned char *pub_raw)
-{
-       uint8_t k[GNUTLS_HPKE_MAX_MONTGOMERY_KEY_SIZE];
-
-       memcpy(k, priv_raw->data, priv_raw->size);
-       clamp_sk(kem, k);
-
-       switch (kem) {
-       case GNUTLS_HPKE_KEM_DHKEM_X25519: {
-               static const uint8_t basepoint[32] = { 9 };
-               curve25519_mul(pub_raw, k, basepoint);
-       } break;
-       case GNUTLS_HPKE_KEM_DHKEM_X448: {
-               static const uint8_t basepoint[56] = { 5 };
-               curve448_mul(pub_raw, k, basepoint);
-       } break;
-       default:
-               break;
-       }
-
-       zeroize_key(k, sizeof(k));
-       return 0;
-}
-
 static int montgomery_curve_keypair_from_raw_privkey(
        const gnutls_mac_algorithm_t mac, const gnutls_hpke_kem_t kem,
        const gnutls_datum_t *dkp_prk, const gnutls_ecc_curve_t curve,
@@ -268,12 +237,7 @@ static int montgomery_curve_keypair_from_raw_privkey(
                goto cleanup;
        }
 
-       unsigned char pk_buf[32];
-
-       derive_montgomery_curve_public_key(kem, &sk, pk_buf);
-
-       gnutls_datum_t x = { pk_buf, 32 };
-       ret = gnutls_privkey_import_ecc_raw(*privkey, curve, &x, NULL, &sk);
+       ret = gnutls_privkey_import_ecc_raw(*privkey, curve, NULL, NULL, &sk);
        if (ret < 0) {
                gnutls_assert_val(ret);
                goto error;
@@ -285,7 +249,7 @@ static int montgomery_curve_keypair_from_raw_privkey(
                goto error;
        }
 
-       ret = gnutls_pubkey_import_ecc_raw(*pubkey, curve, &x, NULL);
+       ret = gnutls_pubkey_import_privkey(*pubkey, *privkey, 0, 0);
        if (ret < 0) {
                gnutls_assert_val(ret);
                goto error;
@@ -344,123 +308,6 @@ static int be_lt(const unsigned char *a, const unsigned char *b, size_t len)
        return 0;
 }
 
-static int get_ecc_and_curve_len_for_curve(const gnutls_ecc_curve_t curve,
-                                          const struct ecc_curve **ecc,
-                                          size_t *coord_size)
-{
-       switch (curve) {
-       case GNUTLS_ECC_CURVE_SECP256R1:
-               *ecc = nettle_get_secp_256r1();
-               *coord_size = 32;
-               break;
-       case GNUTLS_ECC_CURVE_SECP384R1:
-               *ecc = nettle_get_secp_384r1();
-               *coord_size = 48;
-               break;
-       case GNUTLS_ECC_CURVE_SECP521R1:
-               *ecc = nettle_get_secp_521r1();
-               *coord_size = 66;
-               break;
-       default:
-               return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
-       }
-
-       return 0;
-}
-
-static int export_pubkey_coordinate(const size_t coord_size, mpz_t p,
-                                   gnutls_datum_t *raw)
-{
-       unsigned char tmp[66];
-       size_t count = 0;
-
-       memset(tmp, 0, sizeof(tmp));
-       memset(raw->data, 0, coord_size);
-
-       mpz_export(tmp, &count, 1, 1, 1, 0, p);
-       if (count > coord_size) {
-               return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
-       }
-
-       memcpy(raw->data + (coord_size - count), tmp, count);
-       raw->size = coord_size;
-
-       zeroize_key(tmp, sizeof(tmp));
-
-       return 0;
-}
-
-static int raw_public_key_for_prime_curve(const gnutls_ecc_curve_t curve,
-                                         const gnutls_datum_t *priv_raw,
-                                         gnutls_datum_t *x, gnutls_datum_t *y)
-{
-       int ret = 0;
-       const struct ecc_curve *ecc = NULL;
-       struct ecc_scalar s;
-       struct ecc_point p;
-       mpz_t k, px, py;
-       size_t coord_len = 0;
-       int scalar_inited = 0;
-       int point_inited = 0;
-       int mpz_inited = 0;
-
-       ret = get_ecc_and_curve_len_for_curve(curve, &ecc, &coord_len);
-       if (ret < 0) {
-               return gnutls_assert_val(ret);
-       }
-
-       x->size = 0;
-       y->size = 0;
-
-       mpz_init(k);
-       mpz_init(px);
-       mpz_init(py);
-       mpz_inited = 1;
-
-       mpz_import(k, priv_raw->size, 1, 1, 1, 0, priv_raw->data);
-
-       ecc_scalar_init(&s, ecc);
-       scalar_inited = 1;
-
-       if (!ecc_scalar_set(&s, k)) {
-               ret = gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER);
-               goto cleanup;
-       }
-
-       ecc_point_init(&p, ecc);
-       point_inited = 1;
-
-       ecc_point_mul_g(&p, &s);
-       ecc_point_get(&p, px, py);
-
-       ret = export_pubkey_coordinate(coord_len, px, x);
-       if (ret < 0) {
-               ret = gnutls_assert_val(ret);
-               goto cleanup;
-       }
-
-       ret = export_pubkey_coordinate(coord_len, py, y);
-       if (ret < 0) {
-               ret = gnutls_assert_val(ret);
-               goto cleanup;
-       }
-
-       ret = 0;
-
-cleanup:
-       if (point_inited)
-               ecc_point_clear(&p);
-       if (scalar_inited)
-               ecc_scalar_clear(&s);
-       if (mpz_inited) {
-               mpz_clear(k);
-               mpz_clear(px);
-               mpz_clear(py);
-       }
-
-       return ret;
-}
-
 static int prime_curve_keypair_from_raw_privkey(
        const gnutls_mac_algorithm_t mac, const gnutls_hpke_kem_t kem,
        const gnutls_datum_t *dkp_prk, const gnutls_ecc_curve_t curve,
@@ -524,19 +371,7 @@ static int prime_curve_keypair_from_raw_privkey(
                        goto cleanup;
                }
 
-               unsigned char x_buf[GNUTLS_HPKE_MAX_RAW_KEY_COORDINATE_SIZE];
-               unsigned char y_buf[GNUTLS_HPKE_MAX_RAW_KEY_COORDINATE_SIZE];
-
-               gnutls_datum_t x = { x_buf, 0 };
-               gnutls_datum_t y = { y_buf, 0 };
-
-               ret = raw_public_key_for_prime_curve(curve, &sk, &x, &y);
-               if (ret < 0) {
-                       gnutls_assert_val(ret);
-                       goto error;
-               }
-
-               ret = gnutls_privkey_import_ecc_raw(*privkey, curve, &x, &y,
+               ret = gnutls_privkey_import_ecc_raw(*privkey, curve, NULL, NULL,
                                                    &sk);
                if (ret < 0) {
                        gnutls_assert_val(ret);
@@ -549,7 +384,7 @@ static int prime_curve_keypair_from_raw_privkey(
                        goto error;
                }
 
-               ret = gnutls_pubkey_import_ecc_raw(*pubkey, curve, &x, &y);
+               ret = gnutls_pubkey_import_privkey(*pubkey, *privkey, 0, 0);
                if (ret < 0) {
                        gnutls_assert_val(ret);
                        goto error;
index 6a00924fc8886dadd6eed489c2b78d716323c136..e9521028b2c20e71d94007fad0636267fd4dfbd5 100644 (file)
@@ -73,6 +73,8 @@
 #endif
 #include "attribute.h"
 
+#define MAX_PRIME_CURVE_COORD_SIZE 66
+
 static inline const struct ecc_curve *get_supported_nist_curve(int curve);
 static inline const struct ecc_curve *get_supported_gost_curve(int curve);
 
@@ -4908,7 +4910,7 @@ static int wrap_nettle_pk_fixup(gnutls_pk_algorithm_t algo,
                                gnutls_direction_t direction,
                                gnutls_pk_params_st *params)
 {
-       int ret;
+       int ret = 0;
 
        if (direction != GNUTLS_IMPORT)
                return 0;
@@ -4964,8 +4966,8 @@ static int wrap_nettle_pk_fixup(gnutls_pk_algorithm_t algo,
                if (ret == 0) {
                        return gnutls_assert_val(GNUTLS_E_PK_INVALID_PRIVKEY);
                }
+               ret = 0;
        } break;
-
        case GNUTLS_PK_EDDSA_ED25519:
        case GNUTLS_PK_EDDSA_ED448:
                if (unlikely(get_eddsa_curve(algo) != params->curve))
@@ -5098,11 +5100,93 @@ static int wrap_nettle_pk_fixup(gnutls_pk_algorithm_t algo,
                ecc_scalar_clear(&priv);
        } break;
 #endif
-       default:
+       case GNUTLS_PK_EC:
+               if (params->params_nr == ECC_PRIVATE_PARAMS) {
+                       return 0;
+               }
+               struct ecc_scalar s;
+               struct ecc_point p;
+               mpz_t pk, px, py;
+               gnutls_datum_t k = { NULL, 0 };
+               unsigned char exported_mpz_buf[MAX_PRIME_CURVE_COORD_SIZE] = {
+                       0
+               };
+               uint8_t x_buf[MAX_PRIME_CURVE_COORD_SIZE] = { 0 };
+               uint8_t y_buf[MAX_PRIME_CURVE_COORD_SIZE] = { 0 };
+               size_t count = 0;
+
+               const struct ecc_curve *ecc =
+                       get_supported_nist_curve(params->curve);
+
+               if (ecc == NULL) {
+                       return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+               }
+
+               size_t coord_size = gnutls_ecc_curve_get_size(params->curve);
+               if (coord_size == 0) {
+                       return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
+               }
+
+               mpz_init(pk);
+               mpz_init(px);
+               mpz_init(py);
+
+               ret = _gnutls_mpi_dprint(params->params[ECC_K], &k);
+               if (ret != 0) {
+                       ret = gnutls_assert_val(ret);
+                       goto cleanup;
+               }
+
+               mpz_import(pk, k.size, 1, 1, 1, 0, k.data);
+
+               ecc_scalar_init(&s, ecc);
+
+               if (!ecc_scalar_set(&s, pk)) {
+                       ret = gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER);
+                       goto cleanup;
+               }
+
+               ecc_point_init(&p, ecc);
+               ecc_point_mul_g(&p, &s);
+               ecc_point_get(&p, px, py);
+
+               mpz_export(exported_mpz_buf, &count, 1, 1, 1, 0, px);
+               memcpy(x_buf + (coord_size - count), exported_mpz_buf, count);
+
+               zeroize_key(exported_mpz_buf, MAX_PRIME_CURVE_COORD_SIZE);
+               mpz_export(exported_mpz_buf, &count, 1, 1, 1, 0, py);
+               memcpy(y_buf + (coord_size - count), exported_mpz_buf, count);
+
+               ret = _gnutls_mpi_init_scan(&params->params[ECC_X], x_buf,
+                                           coord_size);
+               if (ret != 0) {
+                       ret = gnutls_assert_val(GNUTLS_E_MPI_SCAN_FAILED);
+                       goto cleanup;
+               }
+               params->params_nr++;
+
+               ret = _gnutls_mpi_init_scan(&params->params[ECC_Y], y_buf,
+                                           coord_size);
+               if (ret != 0) {
+                       ret = gnutls_assert_val(GNUTLS_E_MPI_SCAN_FAILED);
+                       goto cleanup;
+               }
+               params->params_nr++;
+
+       cleanup:
+               ecc_point_clear(&p);
+               ecc_scalar_clear(&s);
+               mpz_clear(pk);
+               mpz_clear(px);
+               mpz_clear(py);
+
+               _gnutls_free_key_datum(&k);
                break;
+       default:
+               return 0;
        }
 
-       return 0;
+       return ret;
 }
 
 int crypto_pk_prio = INT_MAX;
index 77f1d36d38403fbbabd5816589f977045389d919..06c3eb80c84f14988cffdb6b9a305daa5dcdbbe4 100644 (file)
@@ -1342,17 +1342,11 @@ int gnutls_x509_privkey_import_ecc_raw(gnutls_x509_privkey_t key,
                }
 
                size = gnutls_ecc_curve_get_size(curve);
-               if (x->size != size || k->size != size) {
+               if ((x && x->size != size) || k->size != size) {
                        ret = gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
                        goto cleanup;
                }
 
-               ret = _gnutls_set_datum(&key->params.raw_pub, x->data, x->size);
-               if (ret < 0) {
-                       gnutls_assert();
-                       goto cleanup;
-               }
-
                ret = _gnutls_set_datum(&key->params.raw_priv, k->data,
                                        k->size);
                if (ret < 0) {
@@ -1360,24 +1354,24 @@ int gnutls_x509_privkey_import_ecc_raw(gnutls_x509_privkey_t key,
                        goto cleanup;
                }
 
-               return 0;
-       }
-
-       if (_gnutls_mpi_init_scan_nz(&key->params.params[ECC_X], x->data,
-                                    x->size)) {
-               gnutls_assert();
-               ret = GNUTLS_E_MPI_SCAN_FAILED;
-               goto cleanup;
-       }
-       key->params.params_nr++;
+               if (x) {
+                       ret = _gnutls_set_datum(&key->params.raw_pub, x->data,
+                                               x->size);
+                       if (ret < 0) {
+                               gnutls_assert();
+                               goto cleanup;
+                       }
+               } else {
+                       ret = _gnutls_pk_fixup(key->params.algo, GNUTLS_IMPORT,
+                                              &key->params);
+                       if (ret < 0) {
+                               gnutls_assert();
+                               goto cleanup;
+                       }
+               }
 
-       if (_gnutls_mpi_init_scan_nz(&key->params.params[ECC_Y], y->data,
-                                    y->size)) {
-               gnutls_assert();
-               ret = GNUTLS_E_MPI_SCAN_FAILED;
-               goto cleanup;
+               return 0;
        }
-       key->params.params_nr++;
 
        if (_gnutls_mpi_init_scan_nz(&key->params.params[ECC_K], k->data,
                                     k->size)) {
@@ -1389,10 +1383,29 @@ int gnutls_x509_privkey_import_ecc_raw(gnutls_x509_privkey_t key,
 
        key->params.algo = GNUTLS_PK_EC;
 
-       ret = _gnutls_pk_fixup(GNUTLS_PK_EC, GNUTLS_IMPORT, &key->params);
-       if (ret < 0) {
-               gnutls_assert();
-               goto cleanup;
+       if (x && y) {
+               ret = _gnutls_mpi_init_scan_nz(&key->params.params[ECC_X],
+                                              x->data, x->size);
+               if (ret < 0) {
+                       ret = gnutls_assert_val(GNUTLS_E_MPI_SCAN_FAILED);
+                       goto cleanup;
+               }
+               key->params.params_nr++;
+
+               ret = _gnutls_mpi_init_scan_nz(&key->params.params[ECC_Y],
+                                              y->data, y->size);
+               if (ret < 0) {
+                       ret = gnutls_assert_val(GNUTLS_E_MPI_SCAN_FAILED);
+                       goto cleanup;
+               }
+               key->params.params_nr++;
+       } else {
+               ret = _gnutls_pk_fixup(GNUTLS_PK_EC, GNUTLS_IMPORT,
+                                      &key->params);
+               if (ret < 0) {
+                       gnutls_assert();
+                       goto cleanup;
+               }
        }
 
        ret = _gnutls_asn1_encode_privkey(&key->key, &key->params);
index b778a7110ca30d412ee218a6c6f99d584b91ef01..9710cb10f82c8a83c595114432b0dfeb7dc7b7b5 100644 (file)
@@ -142,6 +142,8 @@ unsigned char ecc_params[] = "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07";
 unsigned char ecc_point[] =
        "\x04\x41\x04\x3c\x15\x6f\x1d\x48\x3e\x64\x59\x13\x2c\x6d\x04\x1a\x38\x0d\x30\x5c\xe4\x3f\x55\xcb\xd9\x17\x15\x46\x72\x71\x92\xc1\xf8\xc6\x33\x3d\x04\x2e\xc8\xc1\x0f\xc0\x50\x04\x7b\x9f\xc9\x48\xb5\x40\xfa\x6f\x93\x82\x59\x61\x5e\x72\x57\xcb\x83\x06\xbd\xcc\x82\x94\xc1";
 
+#define MAX_PRIME_CURVE_PUBKEY_SIZE 266
+
 static int _gnutls_privkey_export2_pkcs8(gnutls_privkey_t key,
                                         gnutls_x509_crt_fmt_t f,
                                         const char *password, unsigned flags,
@@ -163,6 +165,8 @@ static int _gnutls_privkey_export2_pkcs8(gnutls_privkey_t key,
 }
 
 #define CMP(name, dat, v) cmp(name, __LINE__, dat, v, sizeof(v) - 1)
+#define CMP_DATUM(name, dat1, dat2) \
+       cmp(name, __LINE__, dat1, dat2.data, dat2.size)
 static int cmp(const char *name, int line, gnutls_datum_t *v1,
               unsigned char *v2, unsigned size)
 {
@@ -1154,6 +1158,156 @@ static int check_gost(void)
        return 0;
 }
 
+typedef struct pubkey_generation_if_not_provided_test_vector_st {
+       gnutls_datum_t raw_sk;
+       gnutls_datum_t expected_raw_pk;
+       gnutls_ecc_curve_t curve;
+} pubkey_generation_if_not_provided_test_vector_t;
+
+#define HEX_ARRAY_TO_DATUM(...)             \
+       { (unsigned char[]){ __VA_ARGS__ }, \
+         sizeof((unsigned char[]){ __VA_ARGS__ }) }
+
+static int check_pubkey_derivation_if_not_provided(void)
+{
+       // Since this feature is implemented as a dependency for HPKE,
+       // the test vectors will be used from RFC9180 appendices.
+       pubkey_generation_if_not_provided_test_vector_t test_vectors[] = {
+               {
+                       // RFC9180 A.1.1.
+                       .raw_sk = HEX_ARRAY_TO_DATUM(
+                               0x52, 0xc4, 0xa7, 0x58, 0xa8, 0x02, 0xcd, 0x8b,
+                               0x93, 0x6e, 0xce, 0xea, 0x31, 0x44, 0x32, 0x79,
+                               0x8d, 0x5b, 0xaf, 0x2d, 0x7e, 0x92, 0x35, 0xdc,
+                               0x08, 0x4a, 0xb1, 0xb9, 0xcf, 0xa2, 0xf7, 0x36),
+                       .expected_raw_pk = HEX_ARRAY_TO_DATUM(
+                               0x37, 0xfd, 0xa3, 0x56, 0x7b, 0xdb, 0xd6, 0x28,
+                               0xe8, 0x86, 0x68, 0xc3, 0xc8, 0xd7, 0xe9, 0x7d,
+                               0x1d, 0x12, 0x53, 0xb6, 0xd4, 0xea, 0x6d, 0x44,
+                               0xc1, 0x50, 0xf7, 0x41, 0xf1, 0xbf, 0x44, 0x31),
+                       .curve = GNUTLS_ECC_CURVE_X25519,
+               },
+               { // RFC 9180 A.3.1.
+                 .raw_sk = HEX_ARRAY_TO_DATUM(
+                         0x49, 0x95, 0x78, 0x8e, 0xf4, 0xb9, 0xd6, 0x13, 0x2b,
+                         0x24, 0x9c, 0xe5, 0x9a, 0x77, 0x28, 0x14, 0x93, 0xeb,
+                         0x39, 0xaf, 0x37, 0x3d, 0x23, 0x6a, 0x1f, 0xe4, 0x15,
+                         0xcb, 0x0c, 0x2d, 0x7b, 0xeb
+
+                         ),
+                 .expected_raw_pk = HEX_ARRAY_TO_DATUM(
+                         0x04, 0xa9, 0x27, 0x19, 0xc6, 0x19, 0x5d, 0x50, 0x85,
+                         0x10, 0x4f, 0x46, 0x9a, 0x8b, 0x98, 0x14, 0xd5, 0x83,
+                         0x8f, 0xf7, 0x2b, 0x60, 0x50, 0x1e, 0x2c, 0x44, 0x66,
+                         0xe5, 0xe6, 0x7b, 0x32, 0x5a, 0xc9, 0x85, 0x36, 0xd7,
+                         0xb6, 0x1a, 0x1a, 0xf4, 0xb7, 0x8e, 0x5b, 0x7f, 0x95,
+                         0x1c, 0x09, 0x00, 0xbe, 0x86, 0x3c, 0x40, 0x3c, 0xe6,
+                         0x5c, 0x9b, 0xfc, 0xb9, 0x38, 0x26, 0x57, 0x22, 0x2d,
+                         0x18, 0xc4),
+                 .curve = GNUTLS_ECC_CURVE_SECP256R1 },
+               { // RFC 9180 A.6.1.
+                 .raw_sk = HEX_ARRAY_TO_DATUM(
+                         0x01, 0x47, 0x84, 0xc6, 0x92, 0xda, 0x35, 0xdf, 0x6e,
+                         0xcd, 0xe9, 0x8e, 0xe4, 0x3a, 0xc4, 0x25, 0xdb, 0xdd,
+                         0x09, 0x69, 0xc0, 0xc7, 0x2b, 0x42, 0xf2, 0xe7, 0x08,
+                         0xab, 0x9d, 0x53, 0x54, 0x15, 0xa8, 0x56, 0x9b, 0xda,
+                         0xcf, 0xcc, 0x0a, 0x11, 0x4c, 0x85, 0xb8, 0xe3, 0xf2,
+                         0x6a, 0xcf, 0x4d, 0x68, 0x11, 0x5f, 0x8c, 0x91, 0xa6,
+                         0x61, 0x78, 0xcd, 0xbd, 0x03, 0xb7, 0xbc, 0xc5, 0x29,
+                         0x1e, 0x37, 0x4b),
+                 .expected_raw_pk = HEX_ARRAY_TO_DATUM(
+                         0x04, 0x01, 0x38, 0xb3, 0x85, 0xca, 0x16, 0xbb, 0x0d,
+                         0x5f, 0xa0, 0xc0, 0x66, 0x5f, 0xbb, 0xd7, 0xe6, 0x9e,
+                         0x3e, 0xe2, 0x9f, 0x63, 0x99, 0x1d, 0x3e, 0x9b, 0x5f,
+                         0xa7, 0x40, 0xaa, 0xb8, 0x90, 0x0a, 0xae, 0xed, 0x46,
+                         0xed, 0x73, 0xa4, 0x90, 0x55, 0x75, 0x84, 0x25, 0xa0,
+                         0xce, 0x36, 0x50, 0x7c, 0x54, 0xb2, 0x9c, 0xc5, 0xb8,
+                         0x5a, 0x5c, 0xee, 0x6b, 0xae, 0x0c, 0xf1, 0xc2, 0x1f,
+                         0x27, 0x31, 0xec, 0xe2, 0x01, 0x3d, 0xc3, 0xfb, 0x7c,
+                         0x8d, 0x21, 0x65, 0x4b, 0xb1, 0x61, 0xb4, 0x63, 0x96,
+                         0x2c, 0xa1, 0x9e, 0x8c, 0x65, 0x4f, 0xf2, 0x4c, 0x94,
+                         0xdd, 0x28, 0x98, 0xde, 0x12, 0x05, 0x1f, 0x1e, 0xd0,
+                         0x69, 0x22, 0x37, 0xfb, 0x02, 0xb2, 0xf8, 0xd1, 0xdc,
+                         0x1c, 0x73, 0xe9, 0xb3, 0x66, 0xb5, 0x29, 0xeb, 0x43,
+                         0x6e, 0x98, 0xa9, 0x96, 0xee, 0x52, 0x2a, 0xef, 0x86,
+                         0x3d, 0xd5, 0x73, 0x9d, 0x2f, 0x29, 0xb0),
+                 .curve = GNUTLS_ECC_CURVE_SECP521R1 }
+       };
+
+       int ret = 0;
+       const int number_of_test_vectors =
+               sizeof(test_vectors) / sizeof(test_vectors[0]);
+
+       for (int i = 0; i < number_of_test_vectors; i++) {
+               gnutls_privkey_t sk;
+
+               gnutls_ecc_curve_t curve = test_vectors[i].curve;
+
+               ret = gnutls_privkey_init(&sk);
+               if (ret != 0) {
+                       fail("Failed to initialize private key for curve: %s\n",
+                            gnutls_ecc_curve_get_name(curve));
+               }
+
+               ret = gnutls_privkey_import_ecc_raw(sk, curve, NULL, NULL,
+                                                   &test_vectors[i].raw_sk);
+               if (ret != 0) {
+                       fail("Failed to import raw key for curve: %s\n",
+                            gnutls_ecc_curve_get_name(curve));
+               }
+
+               switch (curve) {
+               case GNUTLS_ECC_CURVE_X25519: {
+                       gnutls_datum_t x;
+
+                       ret = gnutls_privkey_export_ecc_raw(sk, NULL, &x, NULL,
+                                                           NULL);
+                       if (ret != 0) {
+                               fail("Failed to export private key for curve: %s\n",
+                                    gnutls_ecc_curve_get_name(curve));
+                       }
+
+                       CMP_DATUM(gnutls_ecc_curve_get_name(curve), &x,
+                                 test_vectors[i].expected_raw_pk);
+                       break;
+               }
+               case GNUTLS_ECC_CURVE_SECP256R1:
+               case GNUTLS_ECC_CURVE_SECP521R1: {
+                       gnutls_datum_t x;
+                       gnutls_datum_t y;
+
+                       ret = gnutls_privkey_export_ecc_raw2(
+                               sk, NULL, &x, &y, NULL,
+                               GNUTLS_EXPORT_FLAG_NO_LZ);
+                       if (ret != 0) {
+                               fail("Failed to export private key for curve: %s\n",
+                                    gnutls_ecc_curve_get_name(curve));
+                       }
+
+                       unsigned char pk_raw_buf[MAX_PRIME_CURVE_PUBKEY_SIZE] = {
+                               0
+                       };
+                       pk_raw_buf[0] = 0x04;
+                       memcpy(pk_raw_buf + 1, x.data, x.size);
+                       memcpy(pk_raw_buf + 1 + x.size, y.data, y.size);
+
+                       gnutls_datum_t pk_raw = { pk_raw_buf,
+                                                 1 + x.size + y.size };
+                       CMP_DATUM(gnutls_ecc_curve_get_name(curve), &pk_raw,
+                                 test_vectors[i].expected_raw_pk);
+                       break;
+               }
+               default:
+                       fail("Unexpected curve: %s\n",
+                            gnutls_ecc_curve_get_name(curve));
+               }
+
+               gnutls_privkey_deinit(sk);
+       }
+
+       return ret;
+}
+
 void doit(void)
 {
        if (check_x509_privkey() != 0) {
@@ -1189,4 +1343,8 @@ void doit(void)
        if (check_gost() != 0) {
                fail("error in gost check\n");
        }
+
+       if (check_pubkey_derivation_if_not_provided() != 0) {
+               fail("error in pubkey derivation\n");
+       }
 }