VYPR
Medium severity4.7NVD Advisory· Published Jun 3, 2026· Updated Jun 3, 2026

CVE-2026-45614

CVE-2026-45614

Description

OP-TEE versions prior to 4.11.0 allow private key reconstruction by verifying public keys against the wrong elliptic curve.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

OP-TEE versions prior to 4.11.0 allow private key reconstruction by verifying public keys against the wrong elliptic curve.

Vulnerability

OP-TEE versions prior to 4.11.0 do not verify that provided public keys are points on the correct elliptic curve when using ECDH shared secret paths. This affects the TEE_DeriveKey function, where the (X, Y) point of a public key may not satisfy the curve equation Y^2 == X^3 + aX + b mod P. This vulnerability has been observed in the optee_test crypto TA on NXP iMX8 systems using the CAAM driver and on Nvidia platforms using libtomcrypt [1].

Exploitation

A normal world attacker can reconstruct the ECDH private key by passing approximately 30-40 crafted public keys to OP-TEE. The attacker selects public keys such that each TEE_DeriveKey call leaks d % r, where d is the private key and r is derived from the relationship between the correct curve and the attacker's chosen curve. The Chinese remainder theorem can then be used to recover the full private key with enough leaked data [1].

Impact

Successful exploitation allows a normal world attacker to recover the ECDH private key. This can lead to the disclosure of sensitive information or unauthorized access to resources protected by the compromised key, depending on its usage within the Trusted Execution Environment [1].

Mitigation

Version 4.11.0 of OP-TEE fixes this issue by verifying that public keys are points on the correct curve [1]. No workarounds are described in the available references. It is not specified if this vulnerability is listed on the Known Exploited Vulnerabilities (KEV) catalog.

AI Insight generated on Jun 3, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

1
c2d64e139058

core: validate ECC public keys are on the curve

https://github.com/OP-TEE/optee_osMartin NyhusApr 26, 2026via github-commit-search
9 files changed · +139 23
  • core/crypto/crypto.c+10 0 modified
    @@ -796,6 +796,16 @@ TEE_Result crypto_acipher_ecc_shared_secret(struct ecc_keypair *private_key,
     					       secret_len);
     }
     
    +TEE_Result crypto_acipher_verify_ecc_public_key(struct ecc_public_key *key)
    +{
    +	assert(key && key->ops);
    +
    +	if (!key->ops->validate)
    +		return TEE_ERROR_NOT_IMPLEMENTED;
    +
    +	return key->ops->validate(key);
    +}
    +
     TEE_Result crypto_acipher_sm2_pke_decrypt(struct ecc_keypair *key,
     					  const uint8_t *src, size_t src_len,
     					  uint8_t *dst, size_t *dst_len)
    
  • core/drivers/crypto/crypto_api/acipher/ecc.c+31 0 modified
    @@ -117,6 +117,36 @@ static void ecc_free_public_key(struct ecc_public_key *key)
     	}
     }
     
    +/*
    + * Check that an ECC public key is valid for use in ECDH
    + *
    + * @key   Public Key
    + */
    +static TEE_Result ecc_validate_public_key(struct ecc_public_key *key)
    +{
    +	const struct crypto_ecc_public_ops *fallback = NULL;
    +	const struct drvcrypt_ecc *driver_impl = NULL;
    +
    +	if (!key) {
    +		CRYPTO_TRACE("Parameters error key is NULL");
    +		return TEE_ERROR_BAD_PARAMETERS;
    +	}
    +
    +	driver_impl = drvcrypt_get_ops(CRYPTO_ECC);
    +	if (driver_impl && driver_impl->validate_publickey) {
    +		CRYPTO_TRACE("ECC Public Key validate");
    +		return driver_impl->validate_publickey(key);
    +	}
    +
    +	fallback = crypto_asym_get_ecc_public_ops(TEE_TYPE_ECDH_PUBLIC_KEY);
    +	if (fallback && fallback->validate) {
    +		CRYPTO_TRACE("ECC Public Key validate (fallback)");
    +		return fallback->validate(key);
    +	}
    +
    +	return TEE_ERROR_NOT_IMPLEMENTED;
    +}
    +
     /*
      * Generates an ECC keypair
      *
    @@ -556,6 +586,7 @@ TEE_Result drvcrypt_asym_alloc_ecc_keypair(struct ecc_keypair *key,
     
     static const struct crypto_ecc_public_ops ecc_public_key_ops = {
     	.free = ecc_free_public_key,
    +	.validate = ecc_validate_public_key,
     	.verify = ecc_verify,
     	.encrypt = ecc_encrypt,
     };
    
  • core/drivers/crypto/crypto_api/include/drvcrypt_acipher.h+2 0 modified
    @@ -158,6 +158,8 @@ struct drvcrypt_ecc {
     				      size_t size_bits);
     	/* Free ECC public key */
     	void (*free_publickey)(struct ecc_public_key *key);
    +	/* Check that an ECC public key is valid */
    +	TEE_Result (*validate_publickey)(struct ecc_public_key *key);
     	/* Generates the ECC keypair */
     	TEE_Result (*gen_keypair)(struct ecc_keypair *key, size_t size_bits);
     	/* ECC Sign a message and returns the signature */
    
  • core/include/crypto/crypto.h+1 0 modified
    @@ -292,6 +292,7 @@ TEE_Result crypto_acipher_ecc_sign(uint32_t algo, struct ecc_keypair *key,
     TEE_Result crypto_acipher_ecc_verify(uint32_t algo, struct ecc_public_key *key,
     				     const uint8_t *msg, size_t msg_len,
     				     const uint8_t *sig, size_t sig_len);
    +TEE_Result crypto_acipher_verify_ecc_public_key(struct ecc_public_key *key);
     TEE_Result crypto_acipher_ecc_shared_secret(struct ecc_keypair *private_key,
     					    struct ecc_public_key *public_key,
     					    void *secret,
    
  • core/include/crypto/crypto_impl.h+5 3 modified
    @@ -405,12 +405,14 @@ drvcrypt_authenc_alloc_ctx(struct crypto_authenc_ctx **ctx __unused,
      * crypto_acipher_free_ecc_*() functions.
      * Reference set in ecc_public_key when key allocated.
      *
    - * @free    is mandatory
    - * @verify  is optional
    - * @encrypt is optional
    + * @free     is mandatory
    + * @validate is optional
    + * @verify   is optional
    + * @encrypt  is optional
      */
     struct crypto_ecc_public_ops {
     	void (*free)(struct ecc_public_key *key);
    +	TEE_Result (*validate)(struct ecc_public_key *key);
     	TEE_Result (*verify)(uint32_t algo, struct ecc_public_key *key,
     			     const uint8_t *msg, size_t msg_len,
     			     const uint8_t *sig, size_t sig_len);
    
  • core/lib/libtomcrypt/ecc.c+20 0 modified
    @@ -22,6 +22,25 @@ static void _ltc_ecc_free_public_key(struct ecc_public_key *s)
     	crypto_bignum_free(&s->y);
     }
     
    +static TEE_Result _ltc_ecc_validate_public_key(struct ecc_public_key *key)
    +{
    +	TEE_Result res = TEE_ERROR_GENERIC;
    +	ecc_key ltc_public_key = { };
    +
    +	res = ecc_populate_ltc_public_key(&ltc_public_key, key, 0, NULL);
    +	if (res != TEE_SUCCESS)
    +		return res;
    +
    +	if (ltc_ecc_verify_key(&ltc_public_key) != CRYPT_OK) {
    +		res = TEE_ERROR_BAD_PARAMETERS;
    +		goto out;
    +	}
    +
    +out:
    +	ecc_free(&ltc_public_key);
    +	return res;
    +}
    +
     /*
      * For a given TEE @curve, return key size and LTC curve name. Also check that
      * @algo is compatible with this curve.
    @@ -357,6 +376,7 @@ static const struct crypto_ecc_keypair_ops ecc_keypair_ops = {
     
     static const struct crypto_ecc_public_ops ecc_public_key_ops = {
     	.free = _ltc_ecc_free_public_key,
    +	.validate = _ltc_ecc_validate_public_key,
     	.verify = _ltc_ecc_verify,
     };
     
    
  • core/lib/libtomcrypt/sub.mk+1 0 modified
    @@ -339,6 +339,7 @@ srcs-$(_CFG_CORE_LTC_ECC) += src/pk/ecc/ltc_ecc_mul2add.c
     srcs-$(_CFG_CORE_LTC_ECC) += src/pk/ecc/ltc_ecc_points.c
     srcs-$(_CFG_CORE_LTC_ECC) += src/pk/ecc/ltc_ecc_projective_add_point.c
     srcs-$(_CFG_CORE_LTC_ECC) += src/pk/ecc/ltc_ecc_projective_dbl_point.c
    +srcs-$(_CFG_CORE_LTC_ECC) += src/pk/ecc/ltc_ecc_verify_key.c
     
     ifneq (,$(filter y,$(_CFG_CORE_LTC_SM2_DSA) $(_CFG_CORE_LTC_SM2_PKE)))
        cppflags-lib-y += -DLTC_ECC_SM2
    
  • core/tee/tee_svc_cryp.c+5 0 modified
    @@ -3844,6 +3844,10 @@ TEE_Result syscall_cryp_derive_key(unsigned long state,
     		crypto_bignum_bin2bn(y_bbuf, params[1].content.ref.length,
     				     key_public.y);
     
    +		res = crypto_acipher_verify_ecc_public_key(&key_public);
    +		if (res != TEE_SUCCESS)
    +			goto ecdh_out;
    +
     		pt_secret = (uint8_t *)(sk + 1);
     		pt_secret_len = sk->alloc_size;
     		res = crypto_acipher_ecc_shared_secret(ko->attr, &key_public,
    @@ -3856,6 +3860,7 @@ TEE_Result syscall_cryp_derive_key(unsigned long state,
     			set_attribute(so, type_props, TEE_ATTR_SECRET_VALUE);
     		}
     
    +ecdh_out:
     		/* free the public key */
     		crypto_acipher_free_ecc_public_key(&key_public);
     	}
    
  • lib/libmbedtls/core/ecc.c+64 20 modified
    @@ -35,6 +35,26 @@ static TEE_Result get_tee_result(int lmd_res)
     	}
     }
     
    +static mbedtls_ecp_group_id curve_to_group_id(uint32_t curve)
    +{
    +	switch (curve) {
    +	case TEE_ECC_CURVE_NIST_P192:
    +		return MBEDTLS_ECP_DP_SECP192R1;
    +	case TEE_ECC_CURVE_NIST_P224:
    +		return MBEDTLS_ECP_DP_SECP224R1;
    +	case TEE_ECC_CURVE_NIST_P256:
    +		return MBEDTLS_ECP_DP_SECP256R1;
    +	case TEE_ECC_CURVE_NIST_P384:
    +		return MBEDTLS_ECP_DP_SECP384R1;
    +	case TEE_ECC_CURVE_NIST_P521:
    +		return MBEDTLS_ECP_DP_SECP521R1;
    +	case TEE_ECC_CURVE_SM2:
    +		return MBEDTLS_ECP_DP_SM2;
    +	default:
    +		return MBEDTLS_ECP_DP_NONE;
    +	}
    +}
    +
     static void ecc_free_public_key(struct ecc_public_key *s)
     {
     	if (!s)
    @@ -44,6 +64,49 @@ static void ecc_free_public_key(struct ecc_public_key *s)
     	crypto_bignum_free(&s->y);
     }
     
    +static TEE_Result ecc_verify_public_key(struct ecc_public_key *key)
    +{
    +	mbedtls_ecp_group_id gid = MBEDTLS_ECP_DP_NONE;
    +	TEE_Result res = TEE_ERROR_GENERIC;
    +	mbedtls_ecp_point point = { };
    +	mbedtls_ecp_group grp = { };
    +	uint8_t one[1] = { 1 };
    +	int lmd_res = 0;
    +
    +	mbedtls_ecp_group_init(&grp);
    +	mbedtls_ecp_point_init(&point);
    +
    +	gid = curve_to_group_id(key->curve);
    +	lmd_res = mbedtls_ecp_group_load(&grp, gid);
    +	if (lmd_res != 0) {
    +		res = TEE_ERROR_NOT_SUPPORTED;
    +		goto out;
    +	}
    +
    +	point.X = *(mbedtls_mpi *)key->x;
    +	point.Y = *(mbedtls_mpi *)key->y;
    +	if (mbedtls_mpi_read_binary(&point.Z, one, sizeof(one))) {
    +		res = TEE_ERROR_OUT_OF_MEMORY;
    +		goto out;
    +	}
    +
    +	lmd_res = mbedtls_ecp_check_pubkey(&grp, &point);
    +	if (lmd_res != 0) {
    +		res = TEE_ERROR_BAD_PARAMETERS;
    +		goto out;
    +	}
    +
    +	res = TEE_SUCCESS;
    +
    +out:
    +	/* Reset X and Y since they are shallow copies of the input key */
    +	mbedtls_mpi_init(&point.X);
    +	mbedtls_mpi_init(&point.Y);
    +	mbedtls_ecp_point_free(&point);
    +	mbedtls_ecp_group_free(&grp);
    +	return res;
    +}
    +
     static TEE_Result ecc_get_keysize(uint32_t curve, uint32_t algo,
     				  size_t *key_size_bytes, size_t *key_size_bits)
     {
    @@ -84,26 +147,6 @@ static TEE_Result ecc_get_keysize(uint32_t curve, uint32_t algo,
     	return TEE_SUCCESS;
     }
     
    -static mbedtls_ecp_group_id curve_to_group_id(uint32_t curve)
    -{
    -	switch (curve) {
    -	case TEE_ECC_CURVE_NIST_P192:
    -		return MBEDTLS_ECP_DP_SECP192R1;
    -	case TEE_ECC_CURVE_NIST_P224:
    -		return MBEDTLS_ECP_DP_SECP224R1;
    -	case TEE_ECC_CURVE_NIST_P256:
    -		return MBEDTLS_ECP_DP_SECP256R1;
    -	case TEE_ECC_CURVE_NIST_P384:
    -		return MBEDTLS_ECP_DP_SECP384R1;
    -	case TEE_ECC_CURVE_NIST_P521:
    -		return MBEDTLS_ECP_DP_SECP521R1;
    -	case TEE_ECC_CURVE_SM2:
    -		return MBEDTLS_ECP_DP_SM2;
    -	default:
    -		return MBEDTLS_ECP_DP_NONE;
    -	}
    -}
    -
     static TEE_Result ecc_generate_keypair(struct ecc_keypair *key, size_t key_size)
     {
     	TEE_Result res = TEE_SUCCESS;
    @@ -453,6 +496,7 @@ TEE_Result crypto_asym_alloc_ecc_keypair(struct ecc_keypair *s,
     
     static const struct crypto_ecc_public_ops ecc_public_key_ops = {
     	.free = ecc_free_public_key,
    +	.validate = ecc_verify_public_key,
     	.verify = ecc_verify,
     };
     
    

Vulnerability mechanics

Root cause

"The TEE did not validate that provided ECC public keys were points on the correct curve during ECDH operations."

Attack vector

A normal world attacker can call the TEE_DeriveKey function with approximately 30-40 crafted public keys. These public keys are not validated to be on the correct curve, allowing the attacker to leak information about the private key with each call. By collecting enough leaked data, the attacker can reconstruct the full private key using the Chinese remainder theorem [ref_id=1].

Affected code

The vulnerability lies within the TEE_DeriveKey syscall handler in `core/tee/tee_svc_cryp.c`. The `crypto_acipher_verify_ecc_public_key` function, which calls the `validate` method of the public key operations, is now invoked before the ECDH shared secret calculation. Implementations in `lib/libmbedtls/core/ecc.c`, `core/drivers/crypto/crypto_api/ecc.c`, and `core/lib/libtomcrypt/ecc.c` provide the `validate` functionality.

What the fix does

The patch introduces a validation step for ECC public keys before they are used in ECDH operations. This validation ensures that the provided public key is a valid point on the specified curve, preventing invalid curve attacks. The validation is integrated into the derive_key syscall and can be handled by hardware acceleration or a software fallback, such as mbed TLS or libtomcrypt [patch_id=4688796].

Preconditions

  • inputCrafted ECC public keys that are not points on the correct curve.

Reproduction

The PoC is implemented as a test case for xtest using the optee_test crypto TA. Due to unrelated bugs, some of the public keys that were chosen will fail depending on the value of the private key (i.e. at random), causing the test to be a little unstable (99.7% success rate for CAAM, 77% for libtomcrypt). Verification of the recovered key is done both by calculating the shared secret in software, and by getting the private key from OP-TEE (when possible) and checking the bytes directly. As an example I've attached the output when running the PoC against libtomcrypt. [ref_id=1]

Generated on Jun 3, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

1

News mentions

0

No linked articles in our index yet.