diff --git a/src/wp_ecc_kmgmt.c b/src/wp_ecc_kmgmt.c index f691060d..dc2e2fb0 100644 --- a/src/wp_ecc_kmgmt.c +++ b/src/wp_ecc_kmgmt.c @@ -537,8 +537,8 @@ static const OSSL_PARAM* wp_ecc_settable_params(WOLFPROV_CTX* provCtx) static const OSSL_PARAM wp_ecc_supported_settable_params[] = { OSSL_PARAM_int(OSSL_PKEY_PARAM_USE_COFACTOR_ECDH, NULL), OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, NULL, 0), - OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_EC_PUB_X, NULL, 0), - OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_EC_PUB_Y, NULL, 0), + OSSL_PARAM_BN(OSSL_PKEY_PARAM_EC_PUB_X, NULL, 0), + OSSL_PARAM_BN(OSSL_PKEY_PARAM_EC_PUB_Y, NULL, 0), OSSL_PARAM_int(OSSL_PKEY_PARAM_EC_INCLUDE_PUBLIC, NULL), OSSL_PARAM_END }; @@ -546,6 +546,94 @@ static const OSSL_PARAM* wp_ecc_settable_params(WOLFPROV_CTX* provCtx) return wp_ecc_supported_settable_params; } +/** + * Import an X9.63 encoded public key point into the ECC key object. + * + * The imported point is always checked against the curve as + * wc_ecc_import_x963_ex() only does so when wolfSSL is built with + * WOLFSSL_VALIDATE_ECC_IMPORT. + * + * @param [in, out] ecc ECC key object. + * @param [in] data X9.63 encoded public key point. + * @param [in] len Length of encoded point in bytes. + * @return 1 on success. + * @return 0 on failure. + */ +static int wp_ecc_import_pub_x963(wp_Ecc* ecc, const unsigned char* data, + word32 len) +{ + int ok = 1; + int rc; + int init = 0; + ecc_key pub; + + WOLFPROV_ENTER(WP_LOG_COMP_ECC, "wp_ecc_import_pub_x963"); + + rc = wc_ecc_init_ex(&pub, NULL, INVALID_DEVID); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ecc_init_ex", rc); + ok = 0; + } + else { + init = 1; + } + if (ok) { + rc = wc_ecc_import_x963_ex(data, len, &pub, ecc->curveId); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, + "wc_ecc_import_x963_ex", rc); + ok = 0; + } + } + if (ok) { + rc = wc_ecc_check_key(&pub); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ecc_check_key", + rc); + ok = 0; + } + } + /* Curve may have been resolved from the point size when no curve was set + * on the key object yet. */ + if (ok && (pub.dp == NULL)) { + ok = 0; + } + if (ok) { + rc = wc_ecc_set_curve(&ecc->key, 0, pub.dp->id); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ecc_set_curve", + rc); + ok = 0; + } + } + if (ok) { + rc = wc_ecc_copy_point((ecc_point*)&pub.pubkey, &ecc->key.pubkey); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ecc_copy_point", + rc); + ok = 0; + } + } + if (ok) { + /* Only the public part was set - keep any private key in place. */ + if ((ecc->key.type == ECC_PRIVATEKEY) || + (ecc->key.type == ECC_PRIVATEKEY_ONLY)) { + ecc->key.type = ECC_PRIVATEKEY; + } + else { + ecc->key.type = ECC_PUBLICKEY; + } + ecc->hasPub = 1; + } + + if (init) { + wc_ecc_free(&pub); + } + + WOLFPROV_LEAVE(WP_LOG_COMP_ECC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); + return ok; +} + /** * Set the encoded public key parameter into ECC key object. * @@ -568,15 +656,9 @@ static int wp_ecc_set_params_enc_pub_key(wp_Ecc *ecc, const OSSL_PARAM params[], ok = 0; } if (ok && (data != NULL)) { - int rc = wc_ecc_import_x963_ex(data, (word32)len, &ecc->key, - ecc->curveId); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_ecc_import_x963_ex", rc); + if (!wp_ecc_import_pub_x963(ecc, data, (word32)len)) { ok = 0; } - if (ok) { - ecc->hasPub = 1; - } } WOLFPROV_LEAVE(WP_LOG_COMP_ECC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); @@ -584,35 +666,102 @@ static int wp_ecc_set_params_enc_pub_key(wp_Ecc *ecc, const OSSL_PARAM params[], } /** - * Set the public key values into ECC key object. + * Set the public key ordinates into the ECC key object. + * + * X and Y are imported together as an X9.63 point, and the resulting point is + * checked against the curve, instead of being stored unvalidated. * * @param [in, out] ecc ECC key object. * @param [in] params Array of parameters and values. * @return 1 on success. * @return 0 on failure. */ -static int wp_ecc_set_params_pub(wp_Ecc *ecc, const OSSL_PARAM params[]) +static int wp_ecc_set_params_pub_xy(wp_Ecc* ecc, const OSSL_PARAM params[]) { int ok = 1; - int set = 0; + int setX = 0; + int setY = 0; + int init = 0; + int size = 0; + unsigned char* point = NULL; + mp_int x; + mp_int y; - WOLFPROV_ENTER(WP_LOG_COMP_ECC, "wp_ecc_set_params_pub"); + WOLFPROV_ENTER(WP_LOG_COMP_ECC, "wp_ecc_set_params_pub_xy"); - if (!wp_params_get_mp(params, OSSL_PKEY_PARAM_EC_PUB_X, - ecc->key.pubkey.x, &set)) { + if (mp_init_multi(&x, &y, NULL, NULL, NULL, NULL) != MP_OKAY) { + ok = 0; + } + else { + init = 1; + } + if (ok && (!wp_params_get_mp(params, OSSL_PKEY_PARAM_EC_PUB_X, &x, + &setX))) { ok = 0; } - if (ok && (set == 1)) { - if (mp_iszero(ecc->key.pubkey.x)) { + if (ok && (!wp_params_get_mp(params, OSSL_PKEY_PARAM_EC_PUB_Y, &y, + &setY))) { + ok = 0; + } + /* One ordinate on its own is not a public key. */ + if (ok && (setX != setY)) { + ok = 0; + } + if (ok && setX) { + size = wc_ecc_get_curve_size_from_id(ecc->curveId); + if (size <= 0) { ok = 0; } - if (ok) { - ecc->key.type = ECC_PUBLICKEY; - ecc->hasPub = 1; + } + if (ok && setX) { + point = OPENSSL_malloc(1 + (2 * (size_t)size)); + if (point == NULL) { + ok = 0; + } + } + if (ok && setX) { + /* Uncompressed X9.63 point: 0x04 || X || Y. */ + point[0] = 0x04; + if (mp_to_unsigned_bin_len(&x, point + 1, size) != MP_OKAY) { + ok = 0; } } - if (!wp_params_get_mp(params, OSSL_PKEY_PARAM_EC_PUB_Y, - ecc->key.pubkey.y, NULL)) { + if (ok && setX) { + if (mp_to_unsigned_bin_len(&y, point + 1 + size, size) != MP_OKAY) { + ok = 0; + } + } + if (ok && setX) { + if (!wp_ecc_import_pub_x963(ecc, point, 1 + (2 * (word32)size))) { + ok = 0; + } + } + + OPENSSL_free(point); + if (init) { + mp_clear(&x); + mp_clear(&y); + } + + WOLFPROV_LEAVE(WP_LOG_COMP_ECC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); + return ok; +} + +/** + * Set the public key values into ECC key object. + * + * @param [in, out] ecc ECC key object. + * @param [in] params Array of parameters and values. + * @return 1 on success. + * @return 0 on failure. + */ +static int wp_ecc_set_params_pub(wp_Ecc *ecc, const OSSL_PARAM params[]) +{ + int ok = 1; + + WOLFPROV_ENTER(WP_LOG_COMP_ECC, "wp_ecc_set_params_pub"); + + if (!wp_ecc_set_params_pub_xy(ecc, params)) { ok = 0; } if (wp_ecc_set_params_enc_pub_key(ecc, params, @@ -686,10 +835,10 @@ static const OSSL_PARAM *wp_ecc_gettable_params(WOLFPROV_CTX* provCtx) OSSL_PARAM_utf8_string(OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT, NULL, 0), OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, NULL, 0), - OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_EC_PUB_X, NULL, 0), - OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_EC_PUB_Y, NULL, 0), + OSSL_PARAM_BN(OSSL_PKEY_PARAM_EC_PUB_X, NULL, 0), + OSSL_PARAM_BN(OSSL_PKEY_PARAM_EC_PUB_Y, NULL, 0), OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, NULL, 0), - OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0), + OSSL_PARAM_BN(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0), OSSL_PARAM_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, NULL, 0), OSSL_PARAM_int(OSSL_PKEY_PARAM_USE_COFACTOR_ECDH, NULL), OSSL_PARAM_END diff --git a/test/test_ecc.c b/test/test_ecc.c index 06337585..170916fc 100644 --- a/test/test_ecc.c +++ b/test/test_ecc.c @@ -2873,6 +2873,369 @@ static int test_ec_import_group_no_nul(void) return err; } +/* + * Import an off-curve P-256 EC_PUB_X/EC_PUB_Y point, returning 1 when accepted + * (and setting *pkey), 0 when refused and -1 when the setup failed. + */ +static int test_ec_import_off_curve_xy(EVP_PKEY **pkey) +{ + int ret = -1; + int err = 0; + EVP_PKEY_CTX *ctx = NULL; + OSSL_PARAM *params = NULL; + OSSL_PARAM_BLD *bld = NULL; + BIGNUM *x = NULL; + BIGNUM *y = NULL; + unsigned char offCurveY[32]; + + memcpy(offCurveY, ecc_p256_pub + 1 + sizeof(offCurveY), sizeof(offCurveY)); + offCurveY[sizeof(offCurveY) - 1] ^= 0x01; + + err = (bld = OSSL_PARAM_BLD_new()) == NULL; + if (err == 0) { + err = OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, + ecc_p256_group_str, 0) != 1; + } + if (err == 0) { + err = (x = BN_bin2bn(ecc_p256_pub + 1, 32, NULL)) == NULL; + } + if (err == 0) { + err = (y = BN_bin2bn(offCurveY, sizeof(offCurveY), NULL)) == NULL; + } + if (err == 0) { + err = OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_PUB_X, x) != 1; + } + if (err == 0) { + err = OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_PUB_Y, y) != 1; + } + if (err == 0) { + err = (params = OSSL_PARAM_BLD_to_param(bld)) == NULL; + } + if (err == 0) { + err = (ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "EC", NULL)) == NULL; + } + if (err == 0) { + err = EVP_PKEY_fromdata_init(ctx) != 1; + } + if (err == 0) { + ret = EVP_PKEY_fromdata(ctx, pkey, EVP_PKEY_PUBLIC_KEY, params) == 1; + } + + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(bld); + BN_free(x); + BN_free(y); + + return ret; +} + +/* + * An EC_PUB_X/EC_PUB_Y point that is not on the curve must be rejected on + * import. + */ +static int test_ec_import_pub_xy_off_curve(void) +{ + int err = 0; + int rc; + EVP_PKEY *pkey = NULL; + + rc = test_ec_import_off_curve_xy(&pkey); + if (rc < 0) { + err = 1; + } + else if (rc == 1) { + PRINT_ERR_MSG("EC import accepted an off-curve EC_PUB_X/EC_PUB_Y point"); + err = 1; + } + + EVP_PKEY_free(pkey); + + return err; +} + +/* + * EC_PUB_X with no EC_PUB_Y is not a public key and must be rejected on + * import. + */ +static int test_ec_import_pub_x_only(void) +{ + int err = 0; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + OSSL_PARAM *params = NULL; + OSSL_PARAM_BLD *bld = NULL; + BIGNUM *x = NULL; + + err = (bld = OSSL_PARAM_BLD_new()) == NULL; + if (err == 0) { + err = OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, + ecc_p256_group_str, 0) != 1; + } + if (err == 0) { + err = (x = BN_bin2bn(ecc_p256_pub + 1, 32, NULL)) == NULL; + } + if (err == 0) { + err = OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_PUB_X, x) != 1; + } + if (err == 0) { + err = (params = OSSL_PARAM_BLD_to_param(bld)) == NULL; + } + if (err == 0) { + err = (ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "EC", NULL)) == NULL; + } + if (err == 0) { + err = EVP_PKEY_fromdata_init(ctx) != 1; + } + if (err == 0 && + EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) == 1) { + PRINT_ERR_MSG("EC import accepted EC_PUB_X with no EC_PUB_Y"); + err = 1; + } + + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(bld); + BN_free(x); + + return err; +} + +/* + * Build a P-256 key holding the private scalar only, as EVP_PKEY_set_params() + * needs a provider side key that already has a private key in it. + */ +static int test_ec_priv_only_key(EVP_PKEY **pkey) +{ + int err = 0; + EVP_PKEY_CTX *ctx = NULL; + OSSL_PARAM *params = NULL; + OSSL_PARAM_BLD *bld = NULL; + BIGNUM *d = NULL; + + err = (bld = OSSL_PARAM_BLD_new()) == NULL; + if (err == 0) { + err = OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, + ecc_p256_group_str, 0) != 1; + } + if (err == 0) { + err = (d = BN_bin2bn(ecc_p256_priv, sizeof(ecc_p256_priv), + NULL)) == NULL; + } + if (err == 0) { + err = OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, d) != 1; + } + if (err == 0) { + err = (params = OSSL_PARAM_BLD_to_param(bld)) == NULL; + } + if (err == 0) { + err = (ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "EC", NULL)) == NULL; + } + if (err == 0) { + err = EVP_PKEY_fromdata_init(ctx) != 1; + } + if (err == 0) { + err = EVP_PKEY_fromdata(ctx, pkey, EVP_PKEY_KEYPAIR, params) != 1; + } + + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(bld); + BN_clear_free(d); + + return err; +} + +/* Check the key still holds the expected private scalar. */ +static int test_ec_priv_intact(EVP_PKEY *pkey) +{ + int err = 0; + BIGNUM *d = NULL; + BIGNUM *expected = NULL; + + err = (expected = BN_bin2bn(ecc_p256_priv, sizeof(ecc_p256_priv), + NULL)) == NULL; + if (err == 0 && + EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &d) != 1) { + PRINT_ERR_MSG("Private key is gone"); + err = 1; + } + if (err == 0 && BN_cmp(d, expected) != 0) { + PRINT_ERR_MSG("Private key changed"); + err = 1; + } + + BN_clear_free(d); + BN_free(expected); + + return err; +} + +/* + * Setting a public key through EVP_PKEY_set_params() must leave an existing + * private key alone. wc_ecc_import_x963_ex() re-initializes the private scalar + * and forces the key type to public, so the point has to be imported into a + * scratch key first. + */ +static int test_ec_set_params_pub_keeps_priv(void) +{ + int err = 0; + EVP_PKEY *pkey = NULL; + OSSL_PARAM *params = NULL; + OSSL_PARAM_BLD *bld = NULL; + BIGNUM *x = NULL; + BIGNUM *y = NULL; + + err = test_ec_priv_only_key(&pkey); + if (err == 0) { + err = test_ec_priv_intact(pkey); + } + + /* EC_PUB_X and EC_PUB_Y, which have to be set together. */ + if (err == 0) { + err = (bld = OSSL_PARAM_BLD_new()) == NULL; + } + if (err == 0) { + err = (x = BN_bin2bn(ecc_p256_pub + 1, 32, NULL)) == NULL; + } + if (err == 0) { + err = (y = BN_bin2bn(ecc_p256_pub + 33, 32, NULL)) == NULL; + } + if (err == 0) { + err = OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_PUB_X, x) != 1; + } + if (err == 0) { + err = OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_PUB_Y, y) != 1; + } + if (err == 0) { + err = (params = OSSL_PARAM_BLD_to_param(bld)) == NULL; + } + if (err == 0 && EVP_PKEY_set_params(pkey, params) != 1) { + PRINT_ERR_MSG("Setting EC_PUB_X/EC_PUB_Y failed"); + err = 1; + } + if (err == 0) { + PRINT_MSG("Private key survives EC_PUB_X/EC_PUB_Y"); + err = test_ec_priv_intact(pkey); + } + + /* Same again through the encoded point. */ + if (err == 0 && EVP_PKEY_set_octet_string_param(pkey, + OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, ecc_p256_pub, + sizeof(ecc_p256_pub)) != 1) { + PRINT_ERR_MSG("Setting ENCODED_PUBLIC_KEY failed"); + err = 1; + } + if (err == 0) { + PRINT_MSG("Private key survives ENCODED_PUBLIC_KEY"); + err = test_ec_priv_intact(pkey); + } + + EVP_PKEY_free(pkey); + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(bld); + BN_free(x); + BN_free(y); + + return err; +} + +#ifdef WP_HAVE_ECDH +/* + * An off-curve peer key must never reach wc_ecc_shared_secret(), checked with + * validate_peer = 0 so that the provider and not OpenSSL is under test. + */ +static int test_ec_derive_off_curve_peer(void) +{ + int err = 0; + int rc; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY_CTX *dctx = NULL; + EVP_PKEY *priv = NULL; + EVP_PKEY *peer = NULL; + OSSL_PARAM *params = NULL; + OSSL_PARAM_BLD *bld = NULL; + BIGNUM *d = NULL; + unsigned char secret[32]; + size_t secretLen = sizeof(secret); + + rc = test_ec_import_off_curve_xy(&peer); + if (rc < 0) { + err = 1; + } + else if (rc == 0) { + /* Import already rejects the point - nothing left to derive with. */ + PRINT_MSG("Off-curve point rejected on import, skipping derive"); + return 0; + } + + /* Our own private key: group plus private scalar only. */ + if (err == 0) { + err = (bld = OSSL_PARAM_BLD_new()) == NULL; + } + if (err == 0) { + err = OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, + ecc_p256_group_str, 0) != 1; + } + if (err == 0) { + err = (d = BN_bin2bn(ecc_p256_priv, sizeof(ecc_p256_priv), + NULL)) == NULL; + } + if (err == 0) { + err = OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, d) != 1; + } + if (err == 0) { + err = (params = OSSL_PARAM_BLD_to_param(bld)) == NULL; + } + if (err == 0) { + err = (ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "EC", NULL)) == NULL; + } + if (err == 0) { + err = EVP_PKEY_fromdata_init(ctx) != 1; + } + if (err == 0) { + err = EVP_PKEY_fromdata(ctx, &priv, EVP_PKEY_KEYPAIR, params) != 1; + } + + if (err == 0) { + err = (dctx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, priv, + NULL)) == NULL; + } + if (err == 0) { + err = EVP_PKEY_derive_init(dctx) != 1; + } + if (err == 0 && EVP_PKEY_derive_set_peer_ex(dctx, peer, 0) > 0 && + EVP_PKEY_derive(dctx, secret, &secretLen) > 0) { + size_t i; + int allZero = 1; + + for (i = 0; i < secretLen; i++) { + if (secret[i] != 0) { + allZero = 0; + break; + } + } + PRINT_ERR_MSG("ECDH derived a secret from an off-curve peer key"); + if (allZero) { + /* pubkey.z left at 0 makes the point behave as infinity. */ + PRINT_ERR_MSG("Derived secret is all zeros"); + } + err = 1; + } + + EVP_PKEY_CTX_free(dctx); + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(priv); + EVP_PKEY_free(peer); + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(bld); + BN_clear_free(d); + + return err; +} +#endif /* WP_HAVE_ECDH */ + /* * A truncated PEM (header only) must be rejected without an out-of-bounds read * in wp_pem2der_convert (base64Data past the buffer / base64Len underflow). @@ -2922,6 +3285,20 @@ int test_ec_import(void* data) if (err == 0) { err = test_ec_import_group_no_nul(); } + if (err == 0) { + err = test_ec_import_pub_xy_off_curve(); + } + if (err == 0) { + err = test_ec_import_pub_x_only(); + } + if (err == 0) { + err = test_ec_set_params_pub_keeps_priv(); + } +#ifdef WP_HAVE_ECDH + if (err == 0) { + err = test_ec_derive_off_curve_peer(); + } +#endif return err; }