Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option to export single component from ecp structure #8746

Closed
MaJerle opened this issue Jan 24, 2024 · 3 comments
Closed

Option to export single component from ecp structure #8746

MaJerle opened this issue Jan 24, 2024 · 3 comments

Comments

@MaJerle
Copy link

MaJerle commented Jan 24, 2024

Suggested enhancement

Proposal is to improve ecp export option adding capability to separately export one of the 3 components. Today there is only a function mbedtls_ecp_export, which has a limitation when private and public keys are parsed from PEM format and come from different storage sources.

Proposal is to add these functions, with simple implementation:

int mbedtls_ecp_export_group(const mbedtls_ecp_keypair *key, mbedtls_ecp_group *grp)
{
    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
    if( ( ret = mbedtls_ecp_group_copy( grp, &key->grp ) ) != 0 )
        return ret;
    return 0;
}

int mbedtls_ecp_export_key(const mbedtls_ecp_keypair *key, mbedtls_mpi *d)
{
    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
    if( ( ret = mbedtls_mpi_copy( d, &key->d ) ) != 0 )
        return ret;
    return 0;
}
int mbedtls_ecp_export_public_key(const mbedtls_ecp_keypair *key, mbedtls_ecp_point *Q)
{
    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
    if( ( ret = mbedtls_ecp_copy( Q, &key->Q ) ) != 0 )
        return ret;
    return 0;
}

Which will allow user to get private and public key components from different sources, without accessing the private mbedTLS structure members.

Justification

For ECDH, if you use static private key in PEM format, parsing of the private key is done with mbedtls_pk_parse_key function, which accepts mbedtls_pk_context as destination parameter.

Corresponding public key for shared key calculation comes from the another party, usually with certificate, that is parsed with mbedtls_x509_crt_parse. This is needed to calculate shared secret.

We can use mbedtls_ecp_export to export all 3 components from the mbedtls_x509_context.pk, however there is no private key in the certificate and therefore function doesn't fulfil the requirement. We still need to provide the parameter for private key, or function will fail.

Parsing of the 2 pairs of certificate and private keys (2 devices for key exchange)

    /* Parse first device data */
    ret = mbedtls_x509_crt_parse(&x509_crt_oem, ec_oem_ca_str, sizeof(ec_oem_ca_str));
    printf("RET: %d, line: %d, flags: %u\r\n", (int)ret, (int)__LINE__, (uint32_t)flags);
    ret = mbedtls_pk_parse_key(&pk_key_oem, ec_oem_key_str, sizeof(ec_oem_key_str), NULL, 0, NULL, NULL);
    printf("RET: %d, line: %d, flags: %u\r\n", (int)ret, (int)__LINE__, (uint32_t)flags);

    /* Parse second device data */
    ret = mbedtls_x509_crt_parse(&x509_crt_device, ec_device_ca_str, sizeof(ec_device_ca_str));
    printf("RET: %d, line: %d, flags: %u\r\n", (int)ret, (int)__LINE__, (uint32_t)flags);
    ret = mbedtls_pk_parse_key(&pk_key_device, ec_device_key_str, sizeof(ec_device_key_str), NULL, 0, NULL, NULL);
    printf("RET: %d, line: %d, flags: %u\r\n", (int)ret, (int)__LINE__, (uint32_t)flags);

Exporting key pair ECP components, but private key component doesn't exist in certificate.

/* priv_key_A and priv_key_B are useless and are there just to make sure function doesn't fail */
/* We try to parse public keys of both parties */
ret = mbedtls_ecp_export(mbedtls_pk_ec(x509_crt_oem.pk), &grp_A, &priv_key_A, &pub_key_A);
printf("RET: %d, line: %d\r\n", (int)ret, (int)__LINE__);
ret = mbedtls_ecp_export(mbedtls_pk_ec(x509_crt_device.pk), &grp_B, &priv_key_B, &pub_key_B);
printf("RET: %d, line: %d\r\n", (int)ret, (int)__LINE__);

Private key comes from another mbedtls_ecp_context, where we only need to read private key part of the context. Today, this is only possible by doing code below, but this will access private component of the ECP keypair:

/* pk_key_oem and pk_key_device are of mbedtls_pk_context, both parse the private key in PEM format with mbedtls_pk_parse_key function */
const mbedtls_ecp_keypair* k_A = mbedtls_pk_ec(pk_key_oem);
const mbedtls_ecp_keypair* k_B = mbedtls_pk_ec(pk_key_device);
mbedtls_mpi_copy(&priv_key_A, &k_A->MBEDTLS_PRIVATE(d)); //Problem here
mbedtls_mpi_copy(&priv_key_B, &k_B->MBEDTLS_PRIVATE(d)); //Problem here

We can finally do the computation on both sides, which generates same key twice:

/* Alice side */
ret = mbedtls_ecdh_compute_shared(&grp_A, &shared_secret_A, &pub_key_B, &priv_key_A, fn_rng, NULL);
printf("RET: %d, line: %d\r\n", (int)ret, (int)__LINE__);
/* Bob side */
ret = mbedtls_ecdh_compute_shared(&grp_B, &shared_secret_B, &pub_key_A, &priv_key_B, fn_rng, NULL);
printf("RET: %d, line: %d\r\n", (int)ret, (int)__LINE__);

ret = mbedtls_mpi_cmp_mpi(&shared_secret_A, &shared_secret_B);
printf("RET: %d, line: %d\r\n", (int)ret, (int)__LINE__);

Unless if assumption with mbedTLS is that we should never use static keys, rather at least one being empeheral, which could be the one on the server side, not client (embedded app).

@gilles-peskine-arm
Copy link
Contributor

I believe this is fulfilled by #7815, which was merged into the development branch last week.

@MaJerle MaJerle closed this as completed Jan 25, 2024
@MaJerle MaJerle reopened this Jan 25, 2024
@MaJerle
Copy link
Author

MaJerle commented Jan 25, 2024

Sorry, I think I closed it too early.

Private key is the mbedtls_mpi type, that is expected for computing the shared.
What is the correct expected procedure to add public key to private key, which is of type mbedtls_pk_context after it was parsed in PEM format?

    mbedtls_x509_crt x509_crt_device;
    mbedtls_pk_context pk_key_device;
    mbedtls_x509_crt x509_crt_oem;
    mbedtls_pk_context pk_key_oem;
    mbedtls_ecdh_context ecdh_oem;
    mbedtls_ecdh_context ecdh_device;
    mbedtls_ecp_group grp_A;
    mbedtls_ecp_group grp_B;
    mbedtls_mpi priv_key_A;
    mbedtls_mpi priv_key_B;
    mbedtls_mpi shared_secret_A;
    mbedtls_mpi shared_secret_B;
    mbedtls_ecp_point pub_key_A;
    mbedtls_ecp_point pub_key_B;
    /* Init all structures... */
    mbedtls_x509_crt_init(&x509_crt_device);
    mbedtls_pk_init(&pk_key_device);
    mbedtls_x509_crt_init(&x509_crt_oem);
    mbedtls_pk_init(&pk_key_oem);
    mbedtls_ecdh_init(&ecdh_oem);
    mbedtls_ecdh_init(&ecdh_device);
    mbedtls_ecp_group_init(&grp_A);
    mbedtls_ecp_group_init(&grp_B);
    mbedtls_mpi_init(&priv_key_A);
    mbedtls_mpi_init(&priv_key_B);
    mbedtls_ecp_point_init(&pub_key_A);
    mbedtls_ecp_point_init(&pub_key_B);
    mbedtls_mpi_init(&shared_secret_A);
    mbedtls_mpi_init(&shared_secret_B);

Parse certificate and private keys of both devices

    /* Parse first device data */
    ret = mbedtls_x509_crt_parse(&x509_crt_oem, ec_oem_ca_str, sizeof(ec_oem_ca_str));
    ret = mbedtls_pk_parse_key(&pk_key_oem, ec_oem_key_str, sizeof(ec_oem_key_str), NULL, 0, NULL, NULL);

    /* Parse second device data */
    ret = mbedtls_x509_crt_parse(&x509_crt_device, ec_device_ca_str, sizeof(ec_device_ca_str));
    ret = mbedtls_pk_parse_key(&pk_key_device, ec_device_key_str, sizeof(ec_device_key_str), NULL, 0, NULL, NULL);

    /* ECDH setup for both devices */
    ret = mbedtls_ecdh_setup(&ecdh_device, MBEDTLS_ECP_DP_SECP256R1);
    ret = mbedtls_ecdh_setup(&ecdh_device, MBEDTLS_ECP_DP_SECP256R1);

Here we need to merge public key into private key.

  • public key type: mbedtls_x509_crt, with mbedtls_x509_crt.pk public component, that is of mbedtls_pk_context type
  • private key type: mbedtls_pk_context.
  • To export 3 components for shared secret computation, I can use mbedtls_ecp_export. I can use mbedtls_pk_ec(x509_crt_oem.pk) to get mbedtls_pk_keypair* type, which is needed for export.

What is the proposed solution? Like below?

I only assume code below is intended to work like this, since function to export has been updated and now checks if parameters are not NULL which could mean these are optional.

    /* Export group and pub key from certificate pk_context */
    ret = mbedtls_ecp_export(mbedtls_pk_ec(x509_crt_oem.pk), &grp_A, NULL, &pub_key_A);
    ret = mbedtls_ecp_export(mbedtls_pk_ec(x509_crt_device.pk), &grp_B, NULL, &pub_key_B);
    /* Export private key from the private key pk_context */
    ret = mbedtls_ecp_export(mbedtls_pk_ec(pk_key_oem), NULL, &priv_key_A, NULL);
    ret = mbedtls_ecp_export(mbedtls_pk_ec(pk_key_device), NULL, &priv_key_B, NULL);

Ultimate final computation:

    /* Now compute twice... */
    /* Alice side */
    ret = mbedtls_ecdh_compute_shared(&grp_A, &shared_secret_A, &pub_key_B, &priv_key_A, fn_rng, NULL);
    /* Bob side */
    ret = mbedtls_ecdh_compute_shared(&grp_B, &shared_secret_B, &pub_key_A, &priv_key_B, fn_rng, NULL);

    ret = mbedtls_mpi_cmp_mpi(&shared_secret_A, &shared_secret_B);
    printf("RET: %d, line: %d\r\n", (int)ret, (int)__LINE__);

Result of the comparison seems to be valid, which is indeed good. Question is, if that's THE WAY to do it? In reality, I expect to use certificate + private key only for authentication, and use another set of private/public keys for short-lived shared secret key generation.

@paul-elliott-arm
Copy link
Member

Hi!

This part of your question feels much more like support, which is much more likely to get a response on the mailing list (where you already posted) than here. I'm closing this as a result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants