Home | History | Annotate | Line # | Download | only in keyexch
      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