ecdh.c revision 1.1 1 1.1 christos /*
2 1.1 christos * Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved.
3 1.1 christos *
4 1.1 christos * Licensed under the Apache License 2.0 (the "License"). You may not use
5 1.1 christos * this file except in compliance with the License. You can obtain a copy
6 1.1 christos * in the file LICENSE in the source distribution or at
7 1.1 christos * https://www.openssl.org/source/license.html
8 1.1 christos */
9 1.1 christos
10 1.1 christos #include <stdio.h>
11 1.1 christos #include <string.h>
12 1.1 christos #include <openssl/core_names.h>
13 1.1 christos #include <openssl/evp.h>
14 1.1 christos #include <openssl/err.h>
15 1.1 christos
16 1.1 christos /*
17 1.1 christos * This is a demonstration of key exchange using ECDH.
18 1.1 christos *
19 1.1 christos * EC key exchange requires 2 parties (peers) to first agree on shared group
20 1.1 christos * parameters (the EC curve name). Each peer then generates a public/private
21 1.1 christos * key pair using the shared curve name. Each peer then gives their public key
22 1.1 christos * to the other peer. A peer can then derive the same shared secret using their
23 1.1 christos * private key and the other peers public key.
24 1.1 christos */
25 1.1 christos
26 1.1 christos /* Object used to store information for a single Peer */
27 1.1 christos typedef struct peer_data_st {
28 1.1 christos const char *name; /* name of peer */
29 1.1 christos const char *curvename; /* The shared curve name */
30 1.1 christos EVP_PKEY *priv; /* private keypair */
31 1.1 christos EVP_PKEY *pub; /* public key to send to other peer */
32 1.1 christos unsigned char *secret; /* allocated shared secret buffer */
33 1.1 christos size_t secretlen;
34 1.1 christos } PEER_DATA;
35 1.1 christos
36 1.1 christos /*
37 1.1 christos * The public key needs to be given to the other peer
38 1.1 christos * The following code extracts the public key data from the private key
39 1.1 christos * and then builds an EVP_KEY public key.
40 1.1 christos */
41 1.1 christos static int get_peer_public_key(PEER_DATA *peer, OSSL_LIB_CTX *libctx)
42 1.1 christos {
43 1.1 christos int ret = 0;
44 1.1 christos EVP_PKEY_CTX *ctx;
45 1.1 christos OSSL_PARAM params[3];
46 1.1 christos unsigned char pubkeydata[256];
47 1.1 christos size_t pubkeylen;
48 1.1 christos
49 1.1 christos /* Get the EC encoded public key data from the peers private key */
50 1.1 christos if (!EVP_PKEY_get_octet_string_param(peer->priv, OSSL_PKEY_PARAM_PUB_KEY,
51 1.1 christos pubkeydata, sizeof(pubkeydata),
52 1.1 christos &pubkeylen))
53 1.1 christos return 0;
54 1.1 christos
55 1.1 christos /* Create a EC public key from the public key data */
56 1.1 christos ctx = EVP_PKEY_CTX_new_from_name(libctx, "EC", NULL);
57 1.1 christos if (ctx == NULL)
58 1.1 christos return 0;
59 1.1 christos params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,
60 1.1 christos (char *)peer->curvename, 0);
61 1.1 christos params[1] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY,
62 1.1 christos pubkeydata, pubkeylen);
63 1.1 christos params[2] = OSSL_PARAM_construct_end();
64 1.1 christos ret = EVP_PKEY_fromdata_init(ctx) > 0
65 1.1 christos && (EVP_PKEY_fromdata(ctx, &peer->pub, EVP_PKEY_PUBLIC_KEY,
66 1.1 christos params) > 0);
67 1.1 christos EVP_PKEY_CTX_free(ctx);
68 1.1 christos return ret;
69 1.1 christos }
70 1.1 christos
71 1.1 christos static int create_peer(PEER_DATA *peer, OSSL_LIB_CTX *libctx)
72 1.1 christos {
73 1.1 christos int ret = 0;
74 1.1 christos EVP_PKEY_CTX *ctx = NULL;
75 1.1 christos OSSL_PARAM params[2];
76 1.1 christos
77 1.1 christos params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,
78 1.1 christos (char *)peer->curvename, 0);
79 1.1 christos params[1] = OSSL_PARAM_construct_end();
80 1.1 christos
81 1.1 christos ctx = EVP_PKEY_CTX_new_from_name(libctx, "EC", NULL);
82 1.1 christos if (ctx == NULL)
83 1.1 christos return 0;
84 1.1 christos
85 1.1 christos if (EVP_PKEY_keygen_init(ctx) <= 0
86 1.1 christos || !EVP_PKEY_CTX_set_params(ctx, params)
87 1.1 christos || EVP_PKEY_generate(ctx, &peer->priv) <= 0
88 1.1 christos || !get_peer_public_key(peer, libctx)) {
89 1.1 christos EVP_PKEY_free(peer->priv);
90 1.1 christos peer->priv = NULL;
91 1.1 christos goto err;
92 1.1 christos }
93 1.1 christos ret = 1;
94 1.1 christos err:
95 1.1 christos EVP_PKEY_CTX_free(ctx);
96 1.1 christos return ret;
97 1.1 christos }
98 1.1 christos
99 1.1 christos static void destroy_peer(PEER_DATA *peer)
100 1.1 christos {
101 1.1 christos EVP_PKEY_free(peer->priv);
102 1.1 christos EVP_PKEY_free(peer->pub);
103 1.1 christos }
104 1.1 christos
105 1.1 christos static int generate_secret(PEER_DATA *peerA, EVP_PKEY *peerBpub,
106 1.1 christos OSSL_LIB_CTX *libctx)
107 1.1 christos {
108 1.1 christos unsigned char *secret = NULL;
109 1.1 christos size_t secretlen = 0;
110 1.1 christos EVP_PKEY_CTX *derivectx;
111 1.1 christos
112 1.1 christos /* Create an EVP_PKEY_CTX that contains peerA's private key */
113 1.1 christos derivectx = EVP_PKEY_CTX_new_from_pkey(libctx, peerA->priv, NULL);
114 1.1 christos if (derivectx == NULL)
115 1.1 christos return 0;
116 1.1 christos
117 1.1 christos if (EVP_PKEY_derive_init(derivectx) <= 0)
118 1.1 christos goto cleanup;
119 1.1 christos /* Set up peerB's public key */
120 1.1 christos if (EVP_PKEY_derive_set_peer(derivectx, peerBpub) <= 0)
121 1.1 christos goto cleanup;
122 1.1 christos
123 1.1 christos /*
124 1.1 christos * For backwards compatibility purposes the OpenSSL ECDH provider supports
125 1.1 christos * optionally using a X963KDF to expand the secret data. This can be done
126 1.1 christos * with code similar to the following.
127 1.1 christos *
128 1.1 christos * OSSL_PARAM params[5];
129 1.1 christos * size_t outlen = 128;
130 1.1 christos * unsigned char ukm[] = { 1, 2, 3, 4 };
131 1.1 christos * params[0] = OSSL_PARAM_construct_utf8_string(OSSL_EXCHANGE_PARAM_KDF_TYPE,
132 1.1 christos * "X963KDF", 0);
133 1.1 christos * params[1] = OSSL_PARAM_construct_utf8_string(OSSL_EXCHANGE_PARAM_KDF_DIGEST,
134 1.1 christos * "SHA256", 0);
135 1.1 christos * params[2] = OSSL_PARAM_construct_size_t(OSSL_EXCHANGE_PARAM_KDF_OUTLEN,
136 1.1 christos * &outlen);
137 1.1 christos * params[3] = OSSL_PARAM_construct_octet_string(OSSL_EXCHANGE_PARAM_KDF_UKM,
138 1.1 christos * ukm, sizeof(ukm));
139 1.1 christos * params[4] = OSSL_PARAM_construct_end();
140 1.1 christos * if (!EVP_PKEY_CTX_set_params(derivectx, params))
141 1.1 christos * goto cleanup;
142 1.1 christos *
143 1.1 christos * Note: After the secret is generated below, the peer could alternatively
144 1.1 christos * pass the secret to a KDF to derive additional key data from the secret.
145 1.1 christos * See demos/kdf/hkdf.c for an example (where ikm is the secret key)
146 1.1 christos */
147 1.1 christos
148 1.1 christos /* Calculate the size of the secret and allocate space */
149 1.1 christos if (EVP_PKEY_derive(derivectx, NULL, &secretlen) <= 0)
150 1.1 christos goto cleanup;
151 1.1 christos secret = (unsigned char *)OPENSSL_malloc(secretlen);
152 1.1 christos if (secret == NULL)
153 1.1 christos goto cleanup;
154 1.1 christos
155 1.1 christos /*
156 1.1 christos * Derive the shared secret. In this example 32 bytes are generated.
157 1.1 christos * For EC curves the secret size is related to the degree of the curve
158 1.1 christos * which is 256 bits for P-256.
159 1.1 christos */
160 1.1 christos if (EVP_PKEY_derive(derivectx, secret, &secretlen) <= 0)
161 1.1 christos goto cleanup;
162 1.1 christos peerA->secret = secret;
163 1.1 christos peerA->secretlen = secretlen;
164 1.1 christos
165 1.1 christos printf("Shared secret (%s):\n", peerA->name);
166 1.1 christos BIO_dump_indent_fp(stdout, peerA->secret, peerA->secretlen, 2);
167 1.1 christos putchar('\n');
168 1.1 christos
169 1.1 christos return 1;
170 1.1 christos cleanup:
171 1.1 christos OPENSSL_free(secret);
172 1.1 christos EVP_PKEY_CTX_free(derivectx);
173 1.1 christos return 0;
174 1.1 christos }
175 1.1 christos
176 1.1 christos int main(void)
177 1.1 christos {
178 1.1 christos int ret = EXIT_FAILURE;
179 1.1 christos /* Initialise the 2 peers that will share a secret */
180 1.1 christos PEER_DATA peer1 = {"peer 1", "P-256"};
181 1.1 christos PEER_DATA peer2 = {"peer 2", "P-256"};
182 1.1 christos /*
183 1.1 christos * Setting libctx to NULL uses the default library context
184 1.1 christos * Use OSSL_LIB_CTX_new() to create a non default library context
185 1.1 christos */
186 1.1 christos OSSL_LIB_CTX *libctx = NULL;
187 1.1 christos
188 1.1 christos /* Each peer creates a (Ephemeral) keypair */
189 1.1 christos if (!create_peer(&peer1, libctx)
190 1.1 christos || !create_peer(&peer2, libctx)) {
191 1.1 christos fprintf(stderr, "Create peer failed\n");
192 1.1 christos goto cleanup;
193 1.1 christos }
194 1.1 christos
195 1.1 christos /*
196 1.1 christos * Each peer uses its private key and the other peers public key to
197 1.1 christos * derive a shared secret
198 1.1 christos */
199 1.1 christos if (!generate_secret(&peer1, peer2.pub, libctx)
200 1.1 christos || !generate_secret(&peer2, peer1.pub, libctx)) {
201 1.1 christos fprintf(stderr, "Generate secrets failed\n");
202 1.1 christos goto cleanup;
203 1.1 christos }
204 1.1 christos
205 1.1 christos /* For illustrative purposes demonstrate that the derived secrets are equal */
206 1.1 christos if (peer1.secretlen != peer2.secretlen
207 1.1 christos || CRYPTO_memcmp(peer1.secret, peer2.secret, peer1.secretlen) != 0) {
208 1.1 christos fprintf(stderr, "Derived secrets do not match\n");
209 1.1 christos goto cleanup;
210 1.1 christos } else {
211 1.1 christos fprintf(stdout, "Derived secrets match\n");
212 1.1 christos }
213 1.1 christos
214 1.1 christos ret = EXIT_SUCCESS;
215 1.1 christos cleanup:
216 1.1 christos if (ret != EXIT_SUCCESS)
217 1.1 christos ERR_print_errors_fp(stderr);
218 1.1 christos destroy_peer(&peer2);
219 1.1 christos destroy_peer(&peer1);
220 1.1 christos return ret;
221 1.1 christos }
222