x25519.c revision 1.1 1 1.1 christos /*
2 1.1 christos * Copyright 2022 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
15 1.1 christos /*
16 1.1 christos * This is a demonstration of key exchange using X25519.
17 1.1 christos *
18 1.1 christos * The variables beginning `peer1_` / `peer2_` are data which would normally be
19 1.1 christos * accessible to that peer.
20 1.1 christos *
21 1.1 christos * Ordinarily you would use random keys, which are demonstrated
22 1.1 christos * below when use_kat=0. A known answer test is demonstrated
23 1.1 christos * when use_kat=1.
24 1.1 christos */
25 1.1 christos
26 1.1 christos /* A property query used for selecting the X25519 implementation. */
27 1.1 christos static const char *propq = NULL;
28 1.1 christos
29 1.1 christos static const unsigned char peer1_privk_data[32] = {
30 1.1 christos 0x80, 0x5b, 0x30, 0x20, 0x25, 0x4a, 0x70, 0x2c,
31 1.1 christos 0xad, 0xa9, 0x8d, 0x7d, 0x47, 0xf8, 0x1b, 0x20,
32 1.1 christos 0x89, 0xd2, 0xf9, 0x14, 0xac, 0x92, 0x27, 0xf2,
33 1.1 christos 0x10, 0x7e, 0xdb, 0x21, 0xbd, 0x73, 0x73, 0x5d
34 1.1 christos };
35 1.1 christos
36 1.1 christos static const unsigned char peer2_privk_data[32] = {
37 1.1 christos 0xf8, 0x84, 0x19, 0x69, 0x79, 0x13, 0x0d, 0xbd,
38 1.1 christos 0xb1, 0x76, 0xd7, 0x0e, 0x7e, 0x0f, 0xb6, 0xf4,
39 1.1 christos 0x8c, 0x4a, 0x8c, 0x5f, 0xd8, 0x15, 0x09, 0x0a,
40 1.1 christos 0x71, 0x78, 0x74, 0x92, 0x0f, 0x85, 0xc8, 0x43
41 1.1 christos };
42 1.1 christos
43 1.1 christos static const unsigned char expected_result[32] = {
44 1.1 christos 0x19, 0x71, 0x26, 0x12, 0x74, 0xb5, 0xb1, 0xce,
45 1.1 christos 0x77, 0xd0, 0x79, 0x24, 0xb6, 0x0a, 0x5c, 0x72,
46 1.1 christos 0x0c, 0xa6, 0x56, 0xc0, 0x11, 0xeb, 0x43, 0x11,
47 1.1 christos 0x94, 0x3b, 0x01, 0x45, 0xca, 0x19, 0xfe, 0x09
48 1.1 christos };
49 1.1 christos
50 1.1 christos typedef struct peer_data_st {
51 1.1 christos const char *name; /* name of peer */
52 1.1 christos EVP_PKEY *privk; /* privk generated for peer */
53 1.1 christos unsigned char pubk_data[32]; /* generated pubk to send to other peer */
54 1.1 christos
55 1.1 christos unsigned char *secret; /* allocated shared secret buffer */
56 1.1 christos size_t secret_len;
57 1.1 christos } PEER_DATA;
58 1.1 christos
59 1.1 christos /*
60 1.1 christos * Prepare for X25519 key exchange. The public key to be sent to the remote peer
61 1.1 christos * is put in pubk_data, which should be a 32-byte buffer. Returns 1 on success.
62 1.1 christos */
63 1.1 christos static int keyexch_x25519_before(
64 1.1 christos OSSL_LIB_CTX *libctx,
65 1.1 christos const unsigned char *kat_privk_data,
66 1.1 christos PEER_DATA *local_peer)
67 1.1 christos {
68 1.1 christos int rv = 0;
69 1.1 christos size_t pubk_data_len = 0;
70 1.1 christos
71 1.1 christos /* Generate or load X25519 key for the peer */
72 1.1 christos if (kat_privk_data != NULL)
73 1.1 christos local_peer->privk =
74 1.1 christos EVP_PKEY_new_raw_private_key_ex(libctx, "X25519", propq,
75 1.1 christos kat_privk_data,
76 1.1 christos sizeof(peer1_privk_data));
77 1.1 christos else
78 1.1 christos local_peer->privk = EVP_PKEY_Q_keygen(libctx, propq, "X25519");
79 1.1 christos
80 1.1 christos if (local_peer->privk == NULL) {
81 1.1 christos fprintf(stderr, "Could not load or generate private key\n");
82 1.1 christos goto end;
83 1.1 christos }
84 1.1 christos
85 1.1 christos /* Get public key corresponding to the private key */
86 1.1 christos if (EVP_PKEY_get_octet_string_param(local_peer->privk,
87 1.1 christos OSSL_PKEY_PARAM_PUB_KEY,
88 1.1 christos local_peer->pubk_data,
89 1.1 christos sizeof(local_peer->pubk_data),
90 1.1 christos &pubk_data_len) == 0) {
91 1.1 christos fprintf(stderr, "EVP_PKEY_get_octet_string_param() failed\n");
92 1.1 christos goto end;
93 1.1 christos }
94 1.1 christos
95 1.1 christos /* X25519 public keys are always 32 bytes */
96 1.1 christos if (pubk_data_len != 32) {
97 1.1 christos fprintf(stderr, "EVP_PKEY_get_octet_string_param() "
98 1.1 christos "yielded wrong length\n");
99 1.1 christos goto end;
100 1.1 christos }
101 1.1 christos
102 1.1 christos rv = 1;
103 1.1 christos end:
104 1.1 christos if (rv == 0) {
105 1.1 christos EVP_PKEY_free(local_peer->privk);
106 1.1 christos local_peer->privk = NULL;
107 1.1 christos }
108 1.1 christos
109 1.1 christos return rv;
110 1.1 christos }
111 1.1 christos
112 1.1 christos /*
113 1.1 christos * Complete X25519 key exchange. remote_peer_pubk_data should be the 32 byte
114 1.1 christos * public key value received from the remote peer. On success, returns 1 and the
115 1.1 christos * secret is pointed to by *secret. The caller must free it.
116 1.1 christos */
117 1.1 christos static int keyexch_x25519_after(
118 1.1 christos OSSL_LIB_CTX *libctx,
119 1.1 christos int use_kat,
120 1.1 christos PEER_DATA *local_peer,
121 1.1 christos const unsigned char *remote_peer_pubk_data)
122 1.1 christos {
123 1.1 christos int rv = 0;
124 1.1 christos EVP_PKEY *remote_peer_pubk = NULL;
125 1.1 christos EVP_PKEY_CTX *ctx = NULL;
126 1.1 christos
127 1.1 christos local_peer->secret = NULL;
128 1.1 christos
129 1.1 christos /* Load public key for remote peer. */
130 1.1 christos remote_peer_pubk =
131 1.1 christos EVP_PKEY_new_raw_public_key_ex(libctx, "X25519", propq,
132 1.1 christos remote_peer_pubk_data, 32);
133 1.1 christos if (remote_peer_pubk == NULL) {
134 1.1 christos fprintf(stderr, "EVP_PKEY_new_raw_public_key_ex() failed\n");
135 1.1 christos goto end;
136 1.1 christos }
137 1.1 christos
138 1.1 christos /* Create key exchange context. */
139 1.1 christos ctx = EVP_PKEY_CTX_new_from_pkey(libctx, local_peer->privk, propq);
140 1.1 christos if (ctx == NULL) {
141 1.1 christos fprintf(stderr, "EVP_PKEY_CTX_new_from_pkey() failed\n");
142 1.1 christos goto end;
143 1.1 christos }
144 1.1 christos
145 1.1 christos /* Initialize derivation process. */
146 1.1 christos if (EVP_PKEY_derive_init(ctx) == 0) {
147 1.1 christos fprintf(stderr, "EVP_PKEY_derive_init() failed\n");
148 1.1 christos goto end;
149 1.1 christos }
150 1.1 christos
151 1.1 christos /* Configure each peer with the other peer's public key. */
152 1.1 christos if (EVP_PKEY_derive_set_peer(ctx, remote_peer_pubk) == 0) {
153 1.1 christos fprintf(stderr, "EVP_PKEY_derive_set_peer() failed\n");
154 1.1 christos goto end;
155 1.1 christos }
156 1.1 christos
157 1.1 christos /* Determine the secret length. */
158 1.1 christos if (EVP_PKEY_derive(ctx, NULL, &local_peer->secret_len) == 0) {
159 1.1 christos fprintf(stderr, "EVP_PKEY_derive() failed\n");
160 1.1 christos goto end;
161 1.1 christos }
162 1.1 christos
163 1.1 christos /*
164 1.1 christos * We are using X25519, so the secret generated will always be 32 bytes.
165 1.1 christos * However for exposition, the code below demonstrates a generic
166 1.1 christos * implementation for arbitrary lengths.
167 1.1 christos */
168 1.1 christos if (local_peer->secret_len != 32) { /* unreachable */
169 1.1 christos fprintf(stderr, "Secret is always 32 bytes for X25519\n");
170 1.1 christos goto end;
171 1.1 christos }
172 1.1 christos
173 1.1 christos /* Allocate memory for shared secrets. */
174 1.1 christos local_peer->secret = OPENSSL_malloc(local_peer->secret_len);
175 1.1 christos if (local_peer->secret == NULL) {
176 1.1 christos fprintf(stderr, "Could not allocate memory for secret\n");
177 1.1 christos goto end;
178 1.1 christos }
179 1.1 christos
180 1.1 christos /* Derive the shared secret. */
181 1.1 christos if (EVP_PKEY_derive(ctx, local_peer->secret,
182 1.1 christos &local_peer->secret_len) == 0) {
183 1.1 christos fprintf(stderr, "EVP_PKEY_derive() failed\n");
184 1.1 christos goto end;
185 1.1 christos }
186 1.1 christos
187 1.1 christos printf("Shared secret (%s):\n", local_peer->name);
188 1.1 christos BIO_dump_indent_fp(stdout, local_peer->secret, local_peer->secret_len, 2);
189 1.1 christos putchar('\n');
190 1.1 christos
191 1.1 christos rv = 1;
192 1.1 christos end:
193 1.1 christos EVP_PKEY_CTX_free(ctx);
194 1.1 christos EVP_PKEY_free(remote_peer_pubk);
195 1.1 christos if (rv == 0) {
196 1.1 christos OPENSSL_clear_free(local_peer->secret, local_peer->secret_len);
197 1.1 christos local_peer->secret = NULL;
198 1.1 christos }
199 1.1 christos
200 1.1 christos return rv;
201 1.1 christos }
202 1.1 christos
203 1.1 christos static int keyexch_x25519(int use_kat)
204 1.1 christos {
205 1.1 christos int rv = 0;
206 1.1 christos OSSL_LIB_CTX *libctx = NULL;
207 1.1 christos PEER_DATA peer1 = {"peer 1"}, peer2 = {"peer 2"};
208 1.1 christos
209 1.1 christos /*
210 1.1 christos * Each peer generates its private key and sends its public key
211 1.1 christos * to the other peer. The private key is stored locally for
212 1.1 christos * later use.
213 1.1 christos */
214 1.1 christos if (keyexch_x25519_before(libctx, use_kat ? peer1_privk_data : NULL,
215 1.1 christos &peer1) == 0)
216 1.1 christos return 0;
217 1.1 christos
218 1.1 christos if (keyexch_x25519_before(libctx, use_kat ? peer2_privk_data : NULL,
219 1.1 christos &peer2) == 0)
220 1.1 christos return 0;
221 1.1 christos
222 1.1 christos /*
223 1.1 christos * Each peer uses the other peer's public key to perform key exchange.
224 1.1 christos * After this succeeds, each peer has the same secret in its
225 1.1 christos * PEER_DATA.
226 1.1 christos */
227 1.1 christos if (keyexch_x25519_after(libctx, use_kat, &peer1, peer2.pubk_data) == 0)
228 1.1 christos return 0;
229 1.1 christos
230 1.1 christos if (keyexch_x25519_after(libctx, use_kat, &peer2, peer1.pubk_data) == 0)
231 1.1 christos return 0;
232 1.1 christos
233 1.1 christos /*
234 1.1 christos * Here we demonstrate the secrets are equal for exposition purposes.
235 1.1 christos *
236 1.1 christos * Although in practice you will generally not need to compare secrets
237 1.1 christos * produced through key exchange, if you do compare cryptographic secrets,
238 1.1 christos * always do so using a constant-time function such as CRYPTO_memcmp, never
239 1.1 christos * using memcmp(3).
240 1.1 christos */
241 1.1 christos if (CRYPTO_memcmp(peer1.secret, peer2.secret, peer1.secret_len) != 0) {
242 1.1 christos fprintf(stderr, "Negotiated secrets do not match\n");
243 1.1 christos goto end;
244 1.1 christos }
245 1.1 christos
246 1.1 christos /* If we are doing the KAT, the secret should equal our reference result. */
247 1.1 christos if (use_kat && CRYPTO_memcmp(peer1.secret, expected_result,
248 1.1 christos peer1.secret_len) != 0) {
249 1.1 christos fprintf(stderr, "Did not get expected result\n");
250 1.1 christos goto end;
251 1.1 christos }
252 1.1 christos
253 1.1 christos rv = 1;
254 1.1 christos end:
255 1.1 christos /* The secrets are sensitive, so ensure they are erased before freeing. */
256 1.1 christos OPENSSL_clear_free(peer1.secret, peer1.secret_len);
257 1.1 christos OPENSSL_clear_free(peer2.secret, peer2.secret_len);
258 1.1 christos
259 1.1 christos EVP_PKEY_free(peer1.privk);
260 1.1 christos EVP_PKEY_free(peer2.privk);
261 1.1 christos OSSL_LIB_CTX_free(libctx);
262 1.1 christos return rv;
263 1.1 christos }
264 1.1 christos
265 1.1 christos int main(int argc, char **argv)
266 1.1 christos {
267 1.1 christos /* Test X25519 key exchange with known result. */
268 1.1 christos printf("Key exchange using known answer (deterministic):\n");
269 1.1 christos if (keyexch_x25519(1) == 0)
270 1.1 christos return 1;
271 1.1 christos
272 1.1 christos /* Test X25519 key exchange with random keys. */
273 1.1 christos printf("Key exchange using random keys:\n");
274 1.1 christos if (keyexch_x25519(0) == 0)
275 1.1 christos return 1;
276 1.1 christos
277 1.1 christos return 0;
278 1.1 christos }
279