Skip to content

Commit b67af78

Browse files
authored
Merge pull request #338 from aidangarske/fix-ecc-wp
Add EC public key auto derivation from private key
2 parents e582de1 + a0aa332 commit b67af78

File tree

4 files changed

+175
-2
lines changed

4 files changed

+175
-2
lines changed

src/wp_ecc_kmgmt.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,31 @@ static int wp_ecc_import_keypair(wp_Ecc* ecc, const OSSL_PARAM params[],
11491149
) {
11501150
ecc->key.type = ECC_PRIVATEKEY;
11511151
ecc->hasPriv = 1;
1152+
1153+
/* Auto-derive public key if not already present and curve is
1154+
* set with OpenSSL version newer than 3.6.0 */
1155+
#if OPENSSL_VERSION_NUMBER > 0x30600000L
1156+
if (!ecc->hasPub && ecc->curveId != 0) {
1157+
int rc;
1158+
1159+
rc = wc_ecc_set_curve(&ecc->key, 0, ecc->curveId);
1160+
if (rc == 0) {
1161+
#ifdef ECC_TIMING_RESISTANT
1162+
rc = wc_ecc_make_pub_ex(&ecc->key, NULL, &ecc->rng);
1163+
#else
1164+
rc = wc_ecc_make_pub_ex(&ecc->key, NULL, NULL);
1165+
#endif
1166+
/* Indicate public key is available if derivation succeeds */
1167+
if (rc == 0) {
1168+
ecc->key.type = ECC_PRIVATEKEY;
1169+
ecc->hasPub = 1;
1170+
}
1171+
/* If derivation fails, continue.
1172+
* The key is still valid for private key operations.
1173+
* We will fail later if public key is accessed. */
1174+
}
1175+
}
1176+
#endif /* OPENSSL_VERSION_NUMBER > 0x30600000L 3.6.0+ */
11521177
}
11531178

11541179
WOLFPROV_LEAVE(WP_LOG_COMP_ECC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);

test/test_ecc.c

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1877,7 +1877,51 @@ static int test_ec_import_priv(void)
18771877
err = EVP_PKEY_fromdata(ctx2, &pkey2, EVP_PKEY_KEYPAIR, params) != 1;
18781878
}
18791879

1880-
/* For imported private only keys, get bn params should fail */
1880+
/* For imported private only keys, public key params behavior depends on OpenSSL version */
1881+
#if OPENSSL_VERSION_NUMBER > 0x30600000L
1882+
/* OpenSSL 3.6.0+ auto-derives public keys from private keys */
1883+
if (err == 0) {
1884+
err = EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_EC_PUB_X, &x1) != 1;
1885+
}
1886+
if (err == 0) {
1887+
err = EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_EC_PUB_X, &x2) != 1;
1888+
}
1889+
if (err == 0) {
1890+
err = EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_EC_PUB_Y, &y1) != 1;
1891+
}
1892+
if (err == 0) {
1893+
err = EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_EC_PUB_Y, &y2) != 1;
1894+
}
1895+
1896+
/* Verify public key is available */
1897+
if (err == 0) {
1898+
if (EVP_PKEY_get_octet_string_param(pkey1,
1899+
OSSL_PKEY_PARAM_PUB_KEY, NULL, 0, (size_t *)&len) != 1) {
1900+
err = 1;
1901+
}
1902+
}
1903+
if (err == 0) {
1904+
if (EVP_PKEY_get_octet_string_param(pkey2,
1905+
OSSL_PKEY_PARAM_PUB_KEY, NULL, 0, (size_t *)&len) != 1) {
1906+
err = 1;
1907+
}
1908+
}
1909+
1910+
/* Verify encoded public key is available */
1911+
if (err == 0) {
1912+
if (EVP_PKEY_get_octet_string_param(pkey1,
1913+
OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, NULL, 0, (size_t *)&len) != 1) {
1914+
err = 1;
1915+
}
1916+
}
1917+
if (err == 0) {
1918+
if (EVP_PKEY_get_octet_string_param(pkey2,
1919+
OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, NULL, 0, (size_t *)&len) != 1) {
1920+
err = 1;
1921+
}
1922+
}
1923+
#else
1924+
/* OpenSSL < 3.6.0: private-only keys should not have public components */
18811925
if (err == 0) {
18821926
err = EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_EC_PUB_X, &x1) == 1;
18831927
}
@@ -1918,7 +1962,8 @@ static int test_ec_import_priv(void)
19181962
err = 1;
19191963
}
19201964
}
1921-
#endif
1965+
#endif /* OPENSSL_VERSION_NUMBER >= 0x30006000L 3.0.6+ */
1966+
#endif /* OPENSSL_VERSION_NUMBER > 0x30600000L 3.6.0+ */
19221967

19231968
EVP_PKEY_free(pkey1);
19241969
EVP_PKEY_free(pkey2);
@@ -2060,4 +2105,105 @@ int test_ec_null_init(void* data)
20602105
return err;
20612106
}
20622107

2108+
#if OPENSSL_VERSION_NUMBER > 0x30600000L
2109+
static int test_ec_auto_derive_pub(void)
2110+
{
2111+
int err = 0;
2112+
EVP_PKEY_CTX *ctx = NULL;
2113+
EVP_PKEY* pkey = NULL;
2114+
OSSL_PARAM *params = NULL;
2115+
OSSL_PARAM_BLD *bld = NULL;
2116+
BIGNUM* priv = NULL;
2117+
BIGNUM* pub_x = NULL;
2118+
BIGNUM* pub_y = NULL;
2119+
unsigned char pub_key[65] = {0};
2120+
size_t pub_key_len = sizeof(pub_key);
2121+
2122+
/* Build params with private key only (no public key) */
2123+
err = (bld = OSSL_PARAM_BLD_new()) == NULL;
2124+
if (err == 0) {
2125+
err = OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME,
2126+
ecc_p256_group_str, 0) != 1;
2127+
}
2128+
if (err == 0) {
2129+
err = (priv = BN_bin2bn(ecc_p256_priv, sizeof(ecc_p256_priv), NULL)) == NULL;
2130+
}
2131+
if (err == 0) {
2132+
err = OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, priv) != 1;
2133+
}
2134+
if (err == 0) {
2135+
err = (params = OSSL_PARAM_BLD_to_param(bld)) == NULL;
2136+
}
2137+
/* Import key using wolfProvider */
2138+
if (err == 0) {
2139+
err = (ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "EC", NULL)) == NULL;
2140+
}
2141+
if (err == 0) {
2142+
err = EVP_PKEY_fromdata_init(ctx) != 1;
2143+
}
2144+
if (err == 0) {
2145+
err = EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) != 1;
2146+
}
2147+
/* Verify public X coordinate is available (auto-derived) */
2148+
if (err == 0) {
2149+
err = EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &pub_x) != 1;
2150+
}
2151+
/* Verify public Y coordinate is available (auto-derived) */
2152+
if (err == 0) {
2153+
err = EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &pub_y) != 1;
2154+
}
2155+
/* Verify public key octet string is available */
2156+
if (err == 0) {
2157+
err = EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY,
2158+
pub_key, pub_key_len, &pub_key_len) != 1;
2159+
}
2160+
if (err == 0) {
2161+
if (pub_key_len == 0 || pub_key_len > sizeof(pub_key)) {
2162+
err = 1;
2163+
}
2164+
}
2165+
/* Verify encoded public key is available */
2166+
if (err == 0) {
2167+
pub_key_len = sizeof(pub_key);
2168+
err = EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY,
2169+
pub_key, pub_key_len, &pub_key_len) != 1;
2170+
}
2171+
if (err == 0) {
2172+
if (pub_key_len == 0 || pub_key_len > sizeof(pub_key)) {
2173+
err = 1;
2174+
}
2175+
}
2176+
/* Verify the derived public key is valid (non-zero coordinates) */
2177+
if (err == 0) {
2178+
if (BN_is_zero(pub_x) || BN_is_zero(pub_y)) {
2179+
err = 1;
2180+
}
2181+
}
2182+
2183+
EVP_PKEY_free(pkey);
2184+
EVP_PKEY_CTX_free(ctx);
2185+
OSSL_PARAM_free(params);
2186+
OSSL_PARAM_BLD_free(bld);
2187+
BN_clear_free(priv);
2188+
BN_free(pub_x);
2189+
BN_free(pub_y);
2190+
2191+
return err;
2192+
}
2193+
#endif /* OPENSSL_VERSION_NUMBER > 0x30600000L */
2194+
2195+
int test_ec_auto_derive_pubkey(void* data)
2196+
{
2197+
int err = 0;
2198+
(void)data;
2199+
2200+
#if OPENSSL_VERSION_NUMBER > 0x30600000L
2201+
err = test_ec_auto_derive_pub();
2202+
#else
2203+
err = 0;
2204+
#endif
2205+
2206+
return err;
2207+
}
2208+
20632209
#endif /* WP_HAVE_ECC */

test/unit.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ TEST_CASE test_case[] = {
355355
#endif
356356
TEST_DECL(test_ec_decode, NULL),
357357
TEST_DECL(test_ec_import, NULL),
358+
TEST_DECL(test_ec_auto_derive_pubkey, NULL),
358359
TEST_DECL(test_ec_null_init, NULL),
359360
#endif
360361
#ifdef WP_HAVE_EC_P384

test/unit.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ int test_ec_load_cert(void* data);
396396

397397
int test_ec_decode(void* data);
398398
int test_ec_import(void* data);
399+
int test_ec_auto_derive_pubkey(void* data);
399400
int test_ec_null_init(void* data);
400401

401402
#endif /* WP_HAVE_ECC */

0 commit comments

Comments
 (0)