Path: blob/main/crypto/openssl/demos/keyexch/ecdh.c
108307 views
/*1* Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved.2*3* Licensed under the Apache License 2.0 (the "License"). You may not use4* this file except in compliance with the License. You can obtain a copy5* in the file LICENSE in the source distribution or at6* https://www.openssl.org/source/license.html7*/89#include <stdio.h>10#include <string.h>11#include <openssl/core_names.h>12#include <openssl/evp.h>13#include <openssl/err.h>1415/*16* This is a demonstration of key exchange using ECDH.17*18* EC key exchange requires 2 parties (peers) to first agree on shared group19* parameters (the EC curve name). Each peer then generates a public/private20* key pair using the shared curve name. Each peer then gives their public key21* to the other peer. A peer can then derive the same shared secret using their22* private key and the other peers public key.23*/2425/* Object used to store information for a single Peer */26typedef struct peer_data_st {27const char *name; /* name of peer */28const char *curvename; /* The shared curve name */29EVP_PKEY *priv; /* private keypair */30EVP_PKEY *pub; /* public key to send to other peer */31unsigned char *secret; /* allocated shared secret buffer */32size_t secretlen;33} PEER_DATA;3435/*36* The public key needs to be given to the other peer37* The following code extracts the public key data from the private key38* and then builds an EVP_KEY public key.39*/40static int get_peer_public_key(PEER_DATA *peer, OSSL_LIB_CTX *libctx)41{42int ret = 0;43EVP_PKEY_CTX *ctx;44OSSL_PARAM params[3];45unsigned char pubkeydata[256];46size_t pubkeylen;4748/* Get the EC encoded public key data from the peers private key */49if (!EVP_PKEY_get_octet_string_param(peer->priv, OSSL_PKEY_PARAM_PUB_KEY,50pubkeydata, sizeof(pubkeydata),51&pubkeylen))52return 0;5354/* Create a EC public key from the public key data */55ctx = EVP_PKEY_CTX_new_from_name(libctx, "EC", NULL);56if (ctx == NULL)57return 0;58params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,59(char *)peer->curvename, 0);60params[1] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY,61pubkeydata, pubkeylen);62params[2] = OSSL_PARAM_construct_end();63ret = EVP_PKEY_fromdata_init(ctx) > 064&& (EVP_PKEY_fromdata(ctx, &peer->pub, EVP_PKEY_PUBLIC_KEY,65params)66> 0);67EVP_PKEY_CTX_free(ctx);68return ret;69}7071static int create_peer(PEER_DATA *peer, OSSL_LIB_CTX *libctx)72{73int ret = 0;74EVP_PKEY_CTX *ctx = NULL;75OSSL_PARAM params[2];7677params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,78(char *)peer->curvename, 0);79params[1] = OSSL_PARAM_construct_end();8081ctx = EVP_PKEY_CTX_new_from_name(libctx, "EC", NULL);82if (ctx == NULL)83return 0;8485if (EVP_PKEY_keygen_init(ctx) <= 086|| !EVP_PKEY_CTX_set_params(ctx, params)87|| EVP_PKEY_generate(ctx, &peer->priv) <= 088|| !get_peer_public_key(peer, libctx)) {89EVP_PKEY_free(peer->priv);90peer->priv = NULL;91goto err;92}93ret = 1;94err:95EVP_PKEY_CTX_free(ctx);96return ret;97}9899static void destroy_peer(PEER_DATA *peer)100{101EVP_PKEY_free(peer->priv);102EVP_PKEY_free(peer->pub);103}104105static int generate_secret(PEER_DATA *peerA, EVP_PKEY *peerBpub,106OSSL_LIB_CTX *libctx)107{108unsigned char *secret = NULL;109size_t secretlen = 0;110EVP_PKEY_CTX *derivectx;111112/* Create an EVP_PKEY_CTX that contains peerA's private key */113derivectx = EVP_PKEY_CTX_new_from_pkey(libctx, peerA->priv, NULL);114if (derivectx == NULL)115return 0;116117if (EVP_PKEY_derive_init(derivectx) <= 0)118goto cleanup;119/* Set up peerB's public key */120if (EVP_PKEY_derive_set_peer(derivectx, peerBpub) <= 0)121goto cleanup;122123/*124* For backwards compatibility purposes the OpenSSL ECDH provider supports125* optionally using a X963KDF to expand the secret data. This can be done126* with code similar to the following.127*128* OSSL_PARAM params[5];129* size_t outlen = 128;130* unsigned char ukm[] = { 1, 2, 3, 4 };131* params[0] = OSSL_PARAM_construct_utf8_string(OSSL_EXCHANGE_PARAM_KDF_TYPE,132* "X963KDF", 0);133* params[1] = OSSL_PARAM_construct_utf8_string(OSSL_EXCHANGE_PARAM_KDF_DIGEST,134* "SHA256", 0);135* params[2] = OSSL_PARAM_construct_size_t(OSSL_EXCHANGE_PARAM_KDF_OUTLEN,136* &outlen);137* params[3] = OSSL_PARAM_construct_octet_string(OSSL_EXCHANGE_PARAM_KDF_UKM,138* ukm, sizeof(ukm));139* params[4] = OSSL_PARAM_construct_end();140* if (!EVP_PKEY_CTX_set_params(derivectx, params))141* goto cleanup;142*143* Note: After the secret is generated below, the peer could alternatively144* pass the secret to a KDF to derive additional key data from the secret.145* See demos/kdf/hkdf.c for an example (where ikm is the secret key)146*/147148/* Calculate the size of the secret and allocate space */149if (EVP_PKEY_derive(derivectx, NULL, &secretlen) <= 0)150goto cleanup;151secret = (unsigned char *)OPENSSL_malloc(secretlen);152if (secret == NULL)153goto cleanup;154155/*156* Derive the shared secret. In this example 32 bytes are generated.157* For EC curves the secret size is related to the degree of the curve158* which is 256 bits for P-256.159*/160if (EVP_PKEY_derive(derivectx, secret, &secretlen) <= 0)161goto cleanup;162peerA->secret = secret;163peerA->secretlen = secretlen;164165printf("Shared secret (%s):\n", peerA->name);166BIO_dump_indent_fp(stdout, peerA->secret, peerA->secretlen, 2);167putchar('\n');168169return 1;170cleanup:171OPENSSL_free(secret);172EVP_PKEY_CTX_free(derivectx);173return 0;174}175176int main(void)177{178int ret = EXIT_FAILURE;179/* Initialise the 2 peers that will share a secret */180PEER_DATA peer1 = { "peer 1", "P-256" };181PEER_DATA peer2 = { "peer 2", "P-256" };182/*183* Setting libctx to NULL uses the default library context184* Use OSSL_LIB_CTX_new() to create a non default library context185*/186OSSL_LIB_CTX *libctx = NULL;187188/* Each peer creates a (Ephemeral) keypair */189if (!create_peer(&peer1, libctx)190|| !create_peer(&peer2, libctx)) {191fprintf(stderr, "Create peer failed\n");192goto cleanup;193}194195/*196* Each peer uses its private key and the other peers public key to197* derive a shared secret198*/199if (!generate_secret(&peer1, peer2.pub, libctx)200|| !generate_secret(&peer2, peer1.pub, libctx)) {201fprintf(stderr, "Generate secrets failed\n");202goto cleanup;203}204205/* For illustrative purposes demonstrate that the derived secrets are equal */206if (peer1.secretlen != peer2.secretlen207|| CRYPTO_memcmp(peer1.secret, peer2.secret, peer1.secretlen) != 0) {208fprintf(stderr, "Derived secrets do not match\n");209goto cleanup;210} else {211fprintf(stdout, "Derived secrets match\n");212}213214ret = EXIT_SUCCESS;215cleanup:216if (ret != EXIT_SUCCESS)217ERR_print_errors_fp(stderr);218destroy_peer(&peer2);219destroy_peer(&peer1);220return ret;221}222223224