Implement draft-ietf-tls-curve25519-01 in C.

The new curve is not enabled by default.

As EC_GROUP/EC_POINT is a bit too complex for X25519, this introduces an
SSL_ECDH_METHOD abstraction which wraps just the raw ECDH operation. It
also tidies up some of the curve code which kept converting back and
force between NIDs and curve IDs. Now everything transits as curve IDs
except for API entry points (SSL_set1_curves) which take NIDs. Those
convert immediately and act on curve IDs from then on.

Note that, like the Go implementation, this slightly tweaks the order of
operations. The client sees the server public key before sending its
own. To keep the abstraction simple, SSL_ECDH_METHOD expects to
generate a keypair before consuming the peer's public key. Instead, the
client handshake stashes the serialized peer public value and defers
parsing it until it comes time to send ClientKeyExchange. (This is
analogous to what it was doing before where it stashed the parsed peer
public value instead.)

It still uses TLS 1.2 terminology everywhere, but this abstraction should also
be compatible with TLS 1.3 which unifies (EC)DH-style key exchanges.
(Accordingly, this abstraction intentionally does not handle parsing the
ClientKeyExchange/ServerKeyExchange framing or attempt to handle asynchronous
plain RSA or the authentication bits.)

BUG=571231

Change-Id: Iba09dddee5bcdfeb2b70185308e8ab0632717932
Reviewed-on: https://boringssl-review.googlesource.com/6780
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/CMakeLists.txt b/ssl/CMakeLists.txt
index a5ad126..c82cb9b 100644
--- a/ssl/CMakeLists.txt
+++ b/ssl/CMakeLists.txt
@@ -26,6 +26,7 @@
   ssl_buffer.c
   ssl_cert.c
   ssl_cipher.c
+  ssl_ecdh.c
   ssl_file.c
   ssl_lib.c
   ssl_rsa.c
diff --git a/ssl/internal.h b/ssl/internal.h
index 5e3dd09..7741527 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -514,6 +514,58 @@
 int ssl3_update_handshake_hash(SSL *ssl, const uint8_t *in, size_t in_len);
 
 
+/* ECDH curves. */
+
+#define SSL_CURVE_SECP256R1 23
+#define SSL_CURVE_SECP384R1 24
+#define SSL_CURVE_SECP521R1 25
+#define SSL_CURVE_ECDH_X25519 29
+
+/* An SSL_ECDH_METHOD is an implementation of ECDH-like key exchanges for
+ * TLS. */
+struct ssl_ecdh_method_st {
+  int nid;
+  uint16_t curve_id;
+  const char name[8];
+
+  /* cleanup releases state in |ctx|. */
+  void (*cleanup)(SSL_ECDH_CTX *ctx);
+
+  /* generate_keypair generates a keypair and writes the public value to
+   * |out_public_key|. It returns one on success and zero on error. */
+  int (*generate_keypair)(SSL_ECDH_CTX *ctx, CBB *out_public_key);
+
+  /* compute_secret performs a key exchange against |peer_key| and, on
+   * success, returns one and sets |*out_secret| and |*out_secret_len| to
+   * a newly-allocated buffer containing the shared secret. The caller must
+   * release this buffer with |OPENSSL_free|. Otherwise, it returns zero and
+   * sets |*out_alert| to an alert to send to the peer. */
+  int (*compute_secret)(SSL_ECDH_CTX *ctx, uint8_t **out_secret,
+                        size_t *out_secret_len, uint8_t *out_alert,
+                        const uint8_t *peer_key, size_t peer_key_len);
+} /* SSL_ECDH_METHOD */;
+
+/* ssl_nid_to_curve_id looks up the curve corresponding to |nid|. On success, it
+ * sets |*out_curve_id| to the curve ID and returns one. Otherwise, it returns
+ * zero. */
+int ssl_nid_to_curve_id(uint16_t *out_curve_id, int nid);
+
+/* SSL_ECDH_CTX_init sets up |ctx| for use with curve |curve_id|. It returns one
+ * on success and zero on error. */
+int SSL_ECDH_CTX_init(SSL_ECDH_CTX *ctx, uint16_t curve_id);
+
+/* SSL_ECDH_CTX_cleanup releases memory associated with |ctx|. It is legal to
+ * call it in the zero state. */
+void SSL_ECDH_CTX_cleanup(SSL_ECDH_CTX *ctx);
+
+/* The following functions call the corresponding method of
+ * |SSL_ECDH_METHOD|. */
+int SSL_ECDH_CTX_generate_keypair(SSL_ECDH_CTX *ctx, CBB *out_public_key);
+int SSL_ECDH_CTX_compute_secret(SSL_ECDH_CTX *ctx, uint8_t **out_secret,
+                                size_t *out_secret_len, uint8_t *out_alert,
+                                const uint8_t *peer_key, size_t peer_key_len);
+
+
 /* Transport buffers. */
 
 /* ssl_read_buffer returns a pointer to contents of the read buffer. */
@@ -1128,24 +1180,16 @@
 int ssl3_alert_code(int code);
 
 char ssl_early_callback_init(struct ssl_early_callback_ctx *ctx);
-int tls1_ec_curve_id2nid(uint16_t curve_id);
-int tls1_ec_nid2curve_id(uint16_t *out_curve_id, int nid);
 
-/* tls1_ec_curve_id2name returns a human-readable name for the
- * curve specified by the TLS curve id in |curve_id|. If the
- * curve is unknown, it returns NULL. */
-const char* tls1_ec_curve_id2name(uint16_t curve_id);
+/* tls1_check_curve_id returns one if |curve_id| is consistent with both our
+ * and the peer's curve preferences. Note: if called as the client, only our
+ * preferences are checked; the peer (the server) does not send preferences. */
+int tls1_check_curve_id(SSL *ssl, uint16_t curve_id);
 
-/* tls1_check_curve parses ECParameters out of |cbs|, modifying it. It
- * checks the curve is one of our preferences and writes the
- * NamedCurve value to |*out_curve_id|. It returns one on success and
- * zero on error. */
-int tls1_check_curve(SSL *s, CBS *cbs, uint16_t *out_curve_id);
-
-/* tls1_get_shared_curve returns the NID of the first preferred shared curve
- * between client and server preferences. If none can be found, it returns
- * NID_undef. */
-int tls1_get_shared_curve(SSL *s);
+/* tls1_get_shared_curve sets |*out_curve_id| to the first preferred shared
+ * curve between client and server preferences and returns one. If none may be
+ * found, it returns zero. */
+int tls1_get_shared_curve(SSL *ssl, uint16_t *out_curve_id);
 
 /* tls1_set_curves converts the array of |ncurves| NIDs pointed to by |curves|
  * into a newly allocated array of TLS curve IDs. On success, the function
diff --git a/ssl/s3_clnt.c b/ssl/s3_clnt.c
index 85c0408..6d5e3b1 100644
--- a/ssl/s3_clnt.c
+++ b/ssl/s3_clnt.c
@@ -1179,59 +1179,36 @@
     s->s3->tmp.peer_dh_tmp = dh;
     dh = NULL;
   } else if (alg_k & SSL_kECDHE) {
-    /* Extract elliptic curve parameters and the server's ephemeral ECDH public
-     * key.  Check curve is one of our preferences, if not server has sent an
-     * invalid curve. */
+    /* Parse the server parameters. */
+    uint8_t curve_type;
     uint16_t curve_id;
-    if (!tls1_check_curve(s, &server_key_exchange, &curve_id)) {
-      al = SSL_AD_DECODE_ERROR;
-      OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CURVE);
-      goto f_err;
-    }
-    s->session->key_exchange_info = curve_id;
-
-    int curve_nid = tls1_ec_curve_id2nid(curve_id);
-    if (curve_nid == NID_undef) {
-      al = SSL_AD_INTERNAL_ERROR;
-      OPENSSL_PUT_ERROR(SSL, SSL_R_UNABLE_TO_FIND_ECDH_PARAMETERS);
-      goto f_err;
-    }
-
-    ecdh = EC_KEY_new_by_curve_name(curve_nid);
-    if (ecdh == NULL) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_EC_LIB);
-      goto err;
-    }
-
-    const EC_GROUP *group = EC_KEY_get0_group(ecdh);
-
-    /* Next, get the encoded ECPoint */
     CBS point;
-    if (!CBS_get_u8_length_prefixed(&server_key_exchange, &point)) {
+    if (!CBS_get_u8(&server_key_exchange, &curve_type) ||
+        curve_type != NAMED_CURVE_TYPE ||
+        !CBS_get_u16(&server_key_exchange, &curve_id) ||
+        !CBS_get_u8_length_prefixed(&server_key_exchange, &point)) {
       al = SSL_AD_DECODE_ERROR;
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       goto f_err;
     }
+    s->session->key_exchange_info = curve_id;
 
-    srvr_ecpoint = EC_POINT_new(group);
-    if (srvr_ecpoint == NULL) {
-      goto err;
-    }
-
-    if (!EC_POINT_oct2point(group, srvr_ecpoint, CBS_data(&point),
-                            CBS_len(&point), NULL)) {
-      al = SSL_AD_DECODE_ERROR;
-      OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT);
+    /* Ensure the curve is consistent with preferences. */
+    if (!tls1_check_curve_id(s, curve_id)) {
+      al = SSL_AD_ILLEGAL_PARAMETER;
+      OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CURVE);
       goto f_err;
     }
-    if (!EC_KEY_set_public_key(ecdh, srvr_ecpoint)) {
+
+    /* Initialize ECDH and save the peer public key for later. */
+    size_t peer_key_len;
+    if (!SSL_ECDH_CTX_init(&s->s3->tmp.ecdh_ctx, curve_id) ||
+        !CBS_stow(&point, &s->s3->tmp.peer_key, &peer_key_len)) {
       goto err;
     }
-    EC_KEY_free(s->s3->tmp.peer_ecdh_tmp);
-    s->s3->tmp.peer_ecdh_tmp = ecdh;
-    ecdh = NULL;
-    EC_POINT_free(srvr_ecpoint);
-    srvr_ecpoint = NULL;
+    /* |point| has a u8 length prefix, so this fits in a |uint8_t|. */
+    assert(peer_key_len <= 0xff);
+    s->s3->tmp.peer_key_len = (uint8_t)peer_key_len;
   } else if (!(alg_k & SSL_kPSK)) {
     al = SSL_AD_UNEXPECTED_MESSAGE;
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
@@ -1596,7 +1573,6 @@
 
   uint8_t *pms = NULL;
   size_t pms_len = 0;
-  EC_KEY *eckey = NULL;
   CBB cbb;
   if (!CBB_init_fixed(&cbb, ssl_handshake_start(ssl),
                       ssl->init_buf->max - SSL_HM_HEADER_LENGTH(ssl))) {
@@ -1736,52 +1712,27 @@
 
     DH_free(dh);
   } else if (alg_k & SSL_kECDHE) {
-    if (ssl->s3->tmp.peer_ecdh_tmp == NULL) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      goto err;
-    }
-    EC_KEY *peer_eckey = ssl->s3->tmp.peer_ecdh_tmp;
-
-    const EC_GROUP *group = EC_KEY_get0_group(peer_eckey);
-    eckey = EC_KEY_new();
-    if (eckey == NULL ||
-        !EC_KEY_set_group(eckey, group) ||
-        !EC_KEY_generate_key(eckey)) {
-      goto err;
-    }
-
-    pms_len = (EC_GROUP_get_degree(group) + 7) / 8;
-    pms = OPENSSL_malloc(pms_len);
-    if (pms == NULL) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
-    }
-
-    int ecdh_len = ECDH_compute_key(
-        pms, pms_len, EC_KEY_get0_public_key(peer_eckey), eckey, NULL);
-    if (ecdh_len <= 0) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_ECDH_LIB);
-      goto err;
-    }
-    pms_len = ecdh_len;
-
-    size_t encoded_len =
-        EC_POINT_point2oct(group, EC_KEY_get0_public_key(eckey),
-                           POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL);
-    uint8_t *ptr;
+    /* Generate a keypair and serialize the public half. */
     CBB child;
-    if (encoded_len == 0 ||
-        !CBB_add_u8_length_prefixed(&cbb, &child) ||
-        !CBB_add_space(&child, &ptr, encoded_len) ||
-        EC_POINT_point2oct(group, EC_KEY_get0_public_key(eckey),
-                           POINT_CONVERSION_UNCOMPRESSED, ptr, encoded_len,
-                           NULL) != encoded_len ||
+    if (!CBB_add_u8_length_prefixed(&cbb, &child) ||
+        !SSL_ECDH_CTX_generate_keypair(&ssl->s3->tmp.ecdh_ctx, &child) ||
         !CBB_flush(&cbb)) {
       goto err;
     }
 
-    EC_KEY_free(eckey);
-    eckey = NULL;
+    /* Compute the premaster. */
+    uint8_t alert;
+    if (!SSL_ECDH_CTX_compute_secret(&ssl->s3->tmp.ecdh_ctx, &pms, &pms_len,
+                                     &alert, ssl->s3->tmp.peer_key,
+                                     ssl->s3->tmp.peer_key_len)) {
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
+      goto err;
+    }
+
+    /* The key exchange state may now be discarded. */
+    SSL_ECDH_CTX_cleanup(&ssl->s3->tmp.ecdh_ctx);
+    OPENSSL_free(ssl->s3->tmp.peer_key);
+    ssl->s3->tmp.peer_key = NULL;
   } else if (alg_k & SSL_kPSK) {
     /* For plain PSK, other_secret is a block of 0s with the same length as
      * the pre-shared key. */
@@ -1844,7 +1795,6 @@
   return ssl_do_write(ssl);
 
 err:
-  EC_KEY_free(eckey);
   if (pms != NULL) {
     OPENSSL_cleanse(pms, pms_len);
     OPENSSL_free(pms);
diff --git a/ssl/s3_lib.c b/ssl/s3_lib.c
index 3f8cea0..e8f7cc2 100644
--- a/ssl/s3_lib.c
+++ b/ssl/s3_lib.c
@@ -229,14 +229,14 @@
   ssl_read_buffer_clear(s);
   ssl_write_buffer_clear(s);
   DH_free(s->s3->tmp.dh);
-  EC_KEY_free(s->s3->tmp.ecdh);
+  SSL_ECDH_CTX_cleanup(&s->s3->tmp.ecdh_ctx);
+  OPENSSL_free(s->s3->tmp.peer_key);
 
   sk_X509_NAME_pop_free(s->s3->tmp.ca_names, X509_NAME_free);
   OPENSSL_free(s->s3->tmp.certificate_types);
   OPENSSL_free(s->s3->tmp.peer_ellipticcurvelist);
   OPENSSL_free(s->s3->tmp.peer_psk_identity_hint);
   DH_free(s->s3->tmp.peer_dh_tmp);
-  EC_KEY_free(s->s3->tmp.peer_ecdh_tmp);
   ssl3_free_handshake_buffer(s);
   ssl3_free_handshake_hash(s);
   OPENSSL_free(s->s3->alpn_selected);
diff --git a/ssl/s3_srvr.c b/ssl/s3_srvr.c
index bcd662a..ef2c396 100644
--- a/ssl/s3_srvr.c
+++ b/ssl/s3_srvr.c
@@ -1237,6 +1237,7 @@
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
         goto err;
       }
+      ssl->session->key_exchange_info = DH_num_bits(params);
 
       /* Generate and save a keypair. */
       DH *dh = DHparams_dup(params);
@@ -1260,40 +1261,20 @@
       }
     } else if (alg_k & SSL_kECDHE) {
       /* Determine the curve to use. */
-      int nid = tls1_get_shared_curve(ssl);
-      if (nid == NID_undef) {
+      uint16_t curve_id;
+      if (!tls1_get_shared_curve(ssl, &curve_id)) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_TMP_ECDH_KEY);
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
         goto err;
       }
-      /* We only support ephemeral ECDH keys over named (not generic) curves. */
-      uint16_t curve_id;
-      if (!tls1_ec_nid2curve_id(&curve_id, nid)) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_ELLIPTIC_CURVE);
-        goto err;
-      }
+      ssl->session->key_exchange_info = curve_id;
 
-      /* Generate and save a keypair. */
-      EC_KEY *ecdh = EC_KEY_new_by_curve_name(nid);
-      if (ecdh == NULL || !EC_KEY_generate_key(ecdh)) {
-        EC_KEY_free(ecdh);
-        goto err;
-      }
-      EC_KEY_free(ssl->s3->tmp.ecdh);
-      ssl->s3->tmp.ecdh = ecdh;
-
-      const EC_GROUP *group = EC_KEY_get0_group(ecdh);
-      const EC_POINT *public_key = EC_KEY_get0_public_key(ecdh);
-      size_t point_len = EC_POINT_point2oct(
-          group, public_key, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL);
-      uint8_t *ptr;
-      if (point_len == 0 ||
+      /* Set up ECDH, generate a key, and emit the public half. */
+      if (!SSL_ECDH_CTX_init(&ssl->s3->tmp.ecdh_ctx, curve_id) ||
           !CBB_add_u8(&cbb, NAMED_CURVE_TYPE) ||
           !CBB_add_u16(&cbb, curve_id) ||
           !CBB_add_u8_length_prefixed(&cbb, &child) ||
-          !CBB_add_space(&child, &ptr, point_len) ||
-          EC_POINT_point2oct(group, public_key, POINT_CONVERSION_UNCOMPRESSED,
-                             ptr, point_len, NULL) != point_len) {
+          !SSL_ECDH_CTX_generate_keypair(&ssl->s3->tmp.ecdh_ctx, &child)) {
         goto err;
       }
     } else {
@@ -1486,10 +1467,7 @@
   BIGNUM *pub = NULL;
   DH *dh_srvr;
 
-  EC_KEY *srvr_ecdh = NULL;
-  EVP_PKEY *clnt_pub_pkey = NULL;
-  EC_POINT *clnt_ecpoint = NULL;
-  unsigned int psk_len = 0;
+  unsigned psk_len = 0;
   uint8_t psk[PSK_MAX_PSK_LEN];
 
   if (s->state == SSL3_ST_SR_KEY_EXCH_A ||
@@ -1707,41 +1685,8 @@
 
     premaster_secret_len = dh_len;
   } else if (alg_k & SSL_kECDHE) {
-    int ecdh_len;
-    const EC_KEY *tkey;
-    const EC_GROUP *group;
-    const BIGNUM *priv_key;
+    /* Parse the ClientKeyExchange. */
     CBS ecdh_Yc;
-
-    /* initialize structures for server's ECDH key pair */
-    srvr_ecdh = EC_KEY_new();
-    if (srvr_ecdh == NULL) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
-    }
-
-    /* Use the ephermeral values we saved when generating the ServerKeyExchange
-     * msg. */
-    tkey = s->s3->tmp.ecdh;
-
-    group = EC_KEY_get0_group(tkey);
-    priv_key = EC_KEY_get0_private_key(tkey);
-
-    if (!EC_KEY_set_group(srvr_ecdh, group) ||
-        !EC_KEY_set_private_key(srvr_ecdh, priv_key)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_EC_LIB);
-      goto err;
-    }
-
-    /* Let's get client's public key */
-    clnt_ecpoint = EC_POINT_new(group);
-    if (clnt_ecpoint == NULL) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
-    }
-
-    /* Get client's public key from encoded point in the ClientKeyExchange
-     * message. */
     if (!CBS_get_u8_length_prefixed(&client_key_exchange, &ecdh_Yc) ||
         CBS_len(&client_key_exchange) != 0) {
       al = SSL_AD_DECODE_ERROR;
@@ -1749,44 +1694,17 @@
       goto f_err;
     }
 
-    if (!EC_POINT_oct2point(group, clnt_ecpoint, CBS_data(&ecdh_Yc),
-                            CBS_len(&ecdh_Yc), NULL)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_EC_LIB);
-      goto err;
+    /* Compute the premaster. */
+    uint8_t alert;
+    if (!SSL_ECDH_CTX_compute_secret(&s->s3->tmp.ecdh_ctx, &premaster_secret,
+                                     &premaster_secret_len, &alert,
+                                     CBS_data(&ecdh_Yc), CBS_len(&ecdh_Yc))) {
+      al = alert;
+      goto f_err;
     }
 
-    /* Allocate a buffer for both the secret and the PSK. */
-    unsigned field_size = EC_GROUP_get_degree(group);
-    if (field_size == 0) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_ECDH_LIB);
-      goto err;
-    }
-
-    ecdh_len = (field_size + 7) / 8;
-    premaster_secret = OPENSSL_malloc(ecdh_len);
-    if (premaster_secret == NULL) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      goto err;
-    }
-
-    /* Compute the shared pre-master secret */
-    ecdh_len = ECDH_compute_key(premaster_secret, ecdh_len, clnt_ecpoint,
-                                srvr_ecdh, NULL);
-    if (ecdh_len <= 0) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_ECDH_LIB);
-      goto err;
-    }
-
-    EVP_PKEY_free(clnt_pub_pkey);
-    clnt_pub_pkey = NULL;
-    EC_POINT_free(clnt_ecpoint);
-    clnt_ecpoint = NULL;
-    EC_KEY_free(srvr_ecdh);
-    srvr_ecdh = NULL;
-    EC_KEY_free(s->s3->tmp.ecdh);
-    s->s3->tmp.ecdh = NULL;
-
-    premaster_secret_len = ecdh_len;
+    /* The key exchange state may now be discarded. */
+    SSL_ECDH_CTX_cleanup(&s->s3->tmp.ecdh_ctx);
   } else if (alg_k & SSL_kPSK) {
     /* For plain PSK, other_secret is a block of 0s with the same length as the
      * pre-shared key. */
@@ -1843,16 +1761,11 @@
 f_err:
   ssl3_send_alert(s, SSL3_AL_FATAL, al);
 err:
-  if (premaster_secret) {
-    if (premaster_secret_len) {
-      OPENSSL_cleanse(premaster_secret, premaster_secret_len);
-    }
+  if (premaster_secret != NULL) {
+    OPENSSL_cleanse(premaster_secret, premaster_secret_len);
     OPENSSL_free(premaster_secret);
   }
   OPENSSL_free(decrypt_buf);
-  EVP_PKEY_free(clnt_pub_pkey);
-  EC_POINT_free(clnt_ecpoint);
-  EC_KEY_free(srvr_ecdh);
 
   return -1;
 }
diff --git a/ssl/ssl_ecdh.c b/ssl/ssl_ecdh.c
new file mode 100644
index 0000000..3328f5a
--- /dev/null
+++ b/ssl/ssl_ecdh.c
@@ -0,0 +1,311 @@
+/* Copyright (c) 2015, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <openssl/ssl.h>
+
+#include <assert.h>
+#include <string.h>
+
+#include <openssl/bn.h>
+#include <openssl/bytestring.h>
+#include <openssl/curve25519.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/mem.h>
+#include <openssl/obj.h>
+
+#include "internal.h"
+
+
+/* |EC_POINT| implementation. */
+
+static void ssl_ec_point_cleanup(SSL_ECDH_CTX *ctx) {
+  BIGNUM *private_key = (BIGNUM *)ctx->data;
+  BN_clear_free(private_key);
+}
+
+static int ssl_ec_point_generate_keypair(SSL_ECDH_CTX *ctx, CBB *out) {
+  assert(ctx->data == NULL);
+  BIGNUM *private_key = BN_new();
+  if (private_key == NULL) {
+    return 0;
+  }
+  ctx->data = private_key;
+
+  /* Set up a shared |BN_CTX| for all operations. */
+  BN_CTX *bn_ctx = BN_CTX_new();
+  if (bn_ctx == NULL) {
+    return 0;
+  }
+  BN_CTX_start(bn_ctx);
+
+  int ret = 0;
+  EC_POINT *public_key = NULL;
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(ctx->method->nid);
+  if (group == NULL) {
+    goto err;
+  }
+
+  /* Generate a private key. */
+  const BIGNUM *order = EC_GROUP_get0_order(group);
+  do {
+    if (!BN_rand_range(private_key, order)) {
+      goto err;
+    }
+  } while (BN_is_zero(private_key));
+
+  /* Compute the corresponding public key. */
+  public_key = EC_POINT_new(group);
+  if (public_key == NULL ||
+      !EC_POINT_mul(group, public_key, private_key, NULL, NULL, bn_ctx)) {
+    goto err;
+  }
+
+  /* Serialize the public key. */
+  size_t len = EC_POINT_point2oct(
+      group, public_key, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bn_ctx);
+  uint8_t *ptr;
+  if (len == 0 ||
+      !CBB_add_space(out, &ptr, len) ||
+      EC_POINT_point2oct(group, public_key, POINT_CONVERSION_UNCOMPRESSED, ptr,
+                         len, bn_ctx) != len) {
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  EC_GROUP_free(group);
+  EC_POINT_free(public_key);
+  BN_CTX_end(bn_ctx);
+  BN_CTX_free(bn_ctx);
+  return ret;
+}
+
+int ssl_ec_point_compute_secret(SSL_ECDH_CTX *ctx, uint8_t **out_secret,
+                                size_t *out_secret_len, uint8_t *out_alert,
+                                const uint8_t *peer_key, size_t peer_key_len) {
+  BIGNUM *private_key = (BIGNUM *)ctx->data;
+  assert(private_key != NULL);
+  *out_alert = SSL_AD_INTERNAL_ERROR;
+
+  /* Set up a shared |BN_CTX| for all operations. */
+  BN_CTX *bn_ctx = BN_CTX_new();
+  if (bn_ctx == NULL) {
+    return 0;
+  }
+  BN_CTX_start(bn_ctx);
+
+  int ret = 0;
+  EC_GROUP *group = EC_GROUP_new_by_curve_name(ctx->method->nid);
+  EC_POINT *peer_point = NULL, *result = NULL;
+  uint8_t *secret = NULL;
+  if (group == NULL) {
+    goto err;
+  }
+
+  /* Compute the x-coordinate of |peer_key| * |private_key|. */
+  peer_point = EC_POINT_new(group);
+  result = EC_POINT_new(group);
+  if (peer_point == NULL || result == NULL) {
+    goto err;
+  }
+  BIGNUM *x = BN_CTX_get(bn_ctx);
+  if (x == NULL) {
+    goto err;
+  }
+  if (!EC_POINT_oct2point(group, peer_point, peer_key, peer_key_len, bn_ctx)) {
+    *out_alert = SSL_AD_DECODE_ERROR;
+    goto err;
+  }
+  if (!EC_POINT_mul(group, result, NULL, peer_point, private_key, bn_ctx) ||
+      !EC_POINT_get_affine_coordinates_GFp(group, result, x, NULL, bn_ctx)) {
+    goto err;
+  }
+
+  /* Encode the x-coordinate left-padded with zeros. */
+  size_t secret_len = (EC_GROUP_get_degree(group) + 7) / 8;
+  secret = OPENSSL_malloc(secret_len);
+  if (secret == NULL || !BN_bn2bin_padded(secret, secret_len, x)) {
+    goto err;
+  }
+
+  *out_secret = secret;
+  *out_secret_len = secret_len;
+  secret = NULL;
+  ret = 1;
+
+err:
+  EC_GROUP_free(group);
+  EC_POINT_free(peer_point);
+  EC_POINT_free(result);
+  BN_CTX_end(bn_ctx);
+  BN_CTX_free(bn_ctx);
+  OPENSSL_free(secret);
+  return ret;
+}
+
+
+/* X25119 implementation. */
+
+static void ssl_x25519_cleanup(SSL_ECDH_CTX *ctx) {
+  if (ctx->data == NULL) {
+    return;
+  }
+  OPENSSL_cleanse(ctx->data, 32);
+  OPENSSL_free(ctx->data);
+}
+
+static int ssl_x25519_generate_keypair(SSL_ECDH_CTX *ctx, CBB *out) {
+  assert(ctx->data == NULL);
+
+  ctx->data = OPENSSL_malloc(32);
+  if (ctx->data == NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+  uint8_t public_key[32];
+  X25519_keypair(public_key, (uint8_t *)ctx->data);
+  return CBB_add_bytes(out, public_key, sizeof(public_key));
+}
+
+static int ssl_x25519_compute_secret(SSL_ECDH_CTX *ctx, uint8_t **out_secret,
+                                     size_t *out_secret_len, uint8_t *out_alert,
+                                     const uint8_t *peer_key,
+                                     size_t peer_key_len) {
+  assert(ctx->data != NULL);
+  *out_alert = SSL_AD_INTERNAL_ERROR;
+
+  uint8_t *secret = OPENSSL_malloc(32);
+  if (secret == NULL) {
+    return 0;
+  }
+
+  if (peer_key_len != 32 ||
+      !X25519(secret, (uint8_t *)ctx->data, peer_key)) {
+    OPENSSL_free(secret);
+    *out_alert = SSL_AD_DECODE_ERROR;
+    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT);
+    return 0;
+  }
+
+  *out_secret = secret;
+  *out_secret_len = 32;
+  return 1;
+}
+
+
+static const SSL_ECDH_METHOD kMethods[] = {
+    {
+        NID_X9_62_prime256v1,
+        SSL_CURVE_SECP256R1,
+        "P-256",
+        ssl_ec_point_cleanup,
+        ssl_ec_point_generate_keypair,
+        ssl_ec_point_compute_secret,
+    },
+    {
+        NID_secp384r1,
+        SSL_CURVE_SECP384R1,
+        "P-384",
+        ssl_ec_point_cleanup,
+        ssl_ec_point_generate_keypair,
+        ssl_ec_point_compute_secret,
+    },
+    {
+        NID_secp521r1,
+        SSL_CURVE_SECP521R1,
+        "P-521",
+        ssl_ec_point_cleanup,
+        ssl_ec_point_generate_keypair,
+        ssl_ec_point_compute_secret,
+    },
+    {
+        NID_x25519,
+        SSL_CURVE_ECDH_X25519,
+        "X25519",
+        ssl_x25519_cleanup,
+        ssl_x25519_generate_keypair,
+        ssl_x25519_compute_secret,
+    },
+};
+
+static const SSL_ECDH_METHOD *method_from_curve_id(uint16_t curve_id) {
+  size_t i;
+  for (i = 0; i < sizeof(kMethods) / sizeof(kMethods[0]); i++) {
+    if (kMethods[i].curve_id == curve_id) {
+      return &kMethods[i];
+    }
+  }
+  return NULL;
+}
+
+static const SSL_ECDH_METHOD *method_from_nid(int nid) {
+  size_t i;
+  for (i = 0; i < sizeof(kMethods) / sizeof(kMethods[0]); i++) {
+    if (kMethods[i].nid == nid) {
+      return &kMethods[i];
+    }
+  }
+  return NULL;
+}
+
+const char* SSL_get_curve_name(uint16_t curve_id) {
+  const SSL_ECDH_METHOD *method = method_from_curve_id(curve_id);
+  if (method == NULL) {
+    return NULL;
+  }
+  return method->name;
+}
+
+int ssl_nid_to_curve_id(uint16_t *out_curve_id, int nid) {
+  const SSL_ECDH_METHOD *method = method_from_nid(nid);
+  if (method == NULL) {
+    return 0;
+  }
+  *out_curve_id = method->curve_id;
+  return 1;
+}
+
+int SSL_ECDH_CTX_init(SSL_ECDH_CTX *ctx, uint16_t curve_id) {
+  SSL_ECDH_CTX_cleanup(ctx);
+
+  const SSL_ECDH_METHOD *method = method_from_curve_id(curve_id);
+  if (method == NULL) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_ELLIPTIC_CURVE);
+    return 0;
+  }
+  ctx->method = method;
+  return 1;
+}
+
+void SSL_ECDH_CTX_cleanup(SSL_ECDH_CTX *ctx) {
+  if (ctx->method == NULL) {
+    return;
+  }
+  ctx->method->cleanup(ctx);
+  ctx->method = NULL;
+  ctx->data = NULL;
+}
+
+int SSL_ECDH_CTX_generate_keypair(SSL_ECDH_CTX *ctx, CBB *out_public_key) {
+  return ctx->method->generate_keypair(ctx, out_public_key);
+}
+
+int SSL_ECDH_CTX_compute_secret(SSL_ECDH_CTX *ctx, uint8_t **out_secret,
+                                size_t *out_secret_len, uint8_t *out_alert,
+                                const uint8_t *peer_key, size_t peer_key_len) {
+  return ctx->method->compute_secret(ctx, out_secret, out_secret_len, out_alert,
+                                     peer_key, peer_key_len);
+}
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index beb57ad..a4fcad5 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -1731,7 +1731,8 @@
 
   /* If we are considering an ECC cipher suite that uses an ephemeral EC
    * key, check for a shared curve. */
-  if (tls1_get_shared_curve(s) != NID_undef) {
+  uint16_t unused;
+  if (tls1_get_shared_curve(s, &unused)) {
     mask_k |= SSL_kECDHE;
   }
 
@@ -1822,10 +1823,6 @@
   return ssl_get_version(session->ssl_version);
 }
 
-const char* SSL_get_curve_name(uint16_t curve_id) {
-  return tls1_ec_curve_id2name(curve_id);
-}
-
 void ssl_clear_cipher_ctx(SSL *s) {
   SSL_AEAD_CTX_free(s->aead_read_ctx);
   s->aead_read_ctx = NULL;
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index 3e5d9d9..eb1e569 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -335,58 +335,14 @@
   return 0;
 }
 
-struct tls_curve {
-  uint16_t curve_id;
-  int nid;
-  const char curve_name[8];
-};
-
-/* ECC curves from RFC4492. */
-static const struct tls_curve tls_curves[] = {
-    {23, NID_X9_62_prime256v1, "P-256"},
-    {24, NID_secp384r1, "P-384"},
-    {25, NID_secp521r1, "P-521"},
-};
-
 static const uint16_t eccurves_default[] = {
-    23, /* X9_62_prime256v1 */
-    24, /* secp384r1 */
+    SSL_CURVE_SECP256R1,
+    SSL_CURVE_SECP384R1,
 #if defined(BORINGSSL_ANDROID_SYSTEM)
-    25, /* secp521r1 */
+    SSL_CURVE_SECP521R1,
 #endif
 };
 
-int tls1_ec_curve_id2nid(uint16_t curve_id) {
-  size_t i;
-  for (i = 0; i < sizeof(tls_curves) / sizeof(tls_curves[0]); i++) {
-    if (curve_id == tls_curves[i].curve_id) {
-      return tls_curves[i].nid;
-    }
-  }
-  return NID_undef;
-}
-
-int tls1_ec_nid2curve_id(uint16_t *out_curve_id, int nid) {
-  size_t i;
-  for (i = 0; i < sizeof(tls_curves) / sizeof(tls_curves[0]); i++) {
-    if (nid == tls_curves[i].nid) {
-      *out_curve_id = tls_curves[i].curve_id;
-      return 1;
-    }
-  }
-  return 0;
-}
-
-const char* tls1_ec_curve_id2name(uint16_t curve_id) {
-  size_t i;
-  for (i = 0; i < sizeof(tls_curves) / sizeof(tls_curves[0]); i++) {
-    if (curve_id == tls_curves[i].curve_id) {
-      return tls_curves[i].curve_name;
-    }
-  }
-  return NULL;
-}
-
 /* tls1_get_curvelist sets |*out_curve_ids| and |*out_curve_ids_len| to the
  * list of allowed curve IDs. If |get_peer_curves| is non-zero, return the
  * peer's curve list. Otherwise, return the preferred list. */
@@ -410,50 +366,30 @@
   }
 }
 
-int tls1_check_curve(SSL *s, CBS *cbs, uint16_t *out_curve_id) {
-  uint8_t curve_type;
-  uint16_t curve_id;
-  const uint16_t *curves;
-  size_t curves_len, i;
-
-  /* Only support named curves. */
-  if (!CBS_get_u8(cbs, &curve_type) ||
-      curve_type != NAMED_CURVE_TYPE ||
-      !CBS_get_u16(cbs, &curve_id)) {
-    return 0;
-  }
-
-  tls1_get_curvelist(s, 0, &curves, &curves_len);
-  for (i = 0; i < curves_len; i++) {
-    if (curve_id == curves[i]) {
-      *out_curve_id = curve_id;
-      return 1;
-    }
-  }
-
-  return 0;
-}
-
-int tls1_get_shared_curve(SSL *s) {
+int tls1_get_shared_curve(SSL *ssl, uint16_t *out_curve_id) {
   const uint16_t *curves, *peer_curves, *pref, *supp;
   size_t curves_len, peer_curves_len, pref_len, supp_len, i, j;
 
   /* Can't do anything on client side */
-  if (s->server == 0) {
-    return NID_undef;
+  if (ssl->server == 0) {
+    return 0;
   }
 
-  tls1_get_curvelist(s, 0 /* local curves */, &curves, &curves_len);
-  tls1_get_curvelist(s, 1 /* peer curves */, &peer_curves, &peer_curves_len);
+  tls1_get_curvelist(ssl, 0 /* local curves */, &curves, &curves_len);
+  tls1_get_curvelist(ssl, 1 /* peer curves */, &peer_curves, &peer_curves_len);
 
   if (peer_curves_len == 0) {
     /* Clients are not required to send a supported_curves extension. In this
      * case, the server is free to pick any curve it likes. See RFC 4492,
-     * section 4, paragraph 3. */
-    return (curves_len == 0) ? NID_undef : tls1_ec_curve_id2nid(curves[0]);
+     * section 4, paragraph 3.
+     *
+     * However, in the interests of compatibility, we will skip ECDH if the
+     * client didn't send an extension because we can't be sure that they'll
+     * support our favoured curve. */
+    return 0;
   }
 
-  if (s->options & SSL_OP_CIPHER_SERVER_PREFERENCE) {
+  if (ssl->options & SSL_OP_CIPHER_SERVER_PREFERENCE) {
     pref = curves;
     pref_len = curves_len;
     supp = peer_curves;
@@ -468,12 +404,13 @@
   for (i = 0; i < pref_len; i++) {
     for (j = 0; j < supp_len; j++) {
       if (pref[i] == supp[j]) {
-        return tls1_ec_curve_id2nid(pref[i]);
+        *out_curve_id = pref[i];
+        return 1;
       }
     }
   }
 
-  return NID_undef;
+  return 0;
 }
 
 int tls1_set_curves(uint16_t **out_curve_ids, size_t *out_curve_ids_len,
@@ -487,7 +424,7 @@
   }
 
   for (i = 0; i < ncurves; i++) {
-    if (!tls1_ec_nid2curve_id(&curve_ids[i], curves[i])) {
+    if (!ssl_nid_to_curve_id(&curve_ids[i], curves[i])) {
       OPENSSL_free(curve_ids);
       return 0;
     }
@@ -520,7 +457,7 @@
 
   /* Determine curve ID */
   nid = EC_GROUP_get_curve_name(grp);
-  if (!tls1_ec_nid2curve_id(&id, nid)) {
+  if (!ssl_nid_to_curve_id(&id, nid)) {
     return 0;
   }
 
@@ -544,19 +481,19 @@
 /* tls1_check_curve_id returns one if |curve_id| is consistent with both our
  * and the peer's curve preferences. Note: if called as the client, only our
  * preferences are checked; the peer (the server) does not send preferences. */
-static int tls1_check_curve_id(SSL *s, uint16_t curve_id) {
+int tls1_check_curve_id(SSL *ssl, uint16_t curve_id) {
   const uint16_t *curves;
   size_t curves_len, i, get_peer_curves;
 
   /* Check against our list, then the peer's list. */
   for (get_peer_curves = 0; get_peer_curves <= 1; get_peer_curves++) {
-    if (get_peer_curves && !s->server) {
+    if (get_peer_curves && !ssl->server) {
       /* Servers do not present a preference list so, if we are a client, only
        * check our list. */
       continue;
     }
 
-    tls1_get_curvelist(s, get_peer_curves, &curves, &curves_len);
+    tls1_get_curvelist(ssl, get_peer_curves, &curves, &curves_len);
     if (get_peer_curves && curves_len == 0) {
       /* Clients are not required to send a supported_curves extension. In this
        * case, the server is free to pick any curve it likes. See RFC 4492,
@@ -671,8 +608,8 @@
  * supported or doesn't appear in supported signature algorithms. Unlike
  * ssl_cipher_get_disabled this applies to a specific session and not global
  * settings. */
-void ssl_set_client_disabled(SSL *s) {
-  CERT *c = s->cert;
+void ssl_set_client_disabled(SSL *ssl) {
+  CERT *c = ssl->cert;
   const uint8_t *sigalgs;
   size_t i, sigalgslen;
   int have_rsa = 0, have_ecdsa = 0;
@@ -681,7 +618,7 @@
 
   /* Now go through all signature algorithms seeing if we support any for RSA,
    * DSA, ECDSA. Do this for all versions not just TLS 1.2. */
-  sigalgslen = tls12_get_psigalgs(s, &sigalgs);
+  sigalgslen = tls12_get_psigalgs(ssl, &sigalgs);
   for (i = 0; i < sigalgslen; i += 2, sigalgs += 2) {
     switch (sigalgs[1]) {
       case TLSEXT_signature_rsa:
@@ -703,7 +640,7 @@
   }
 
   /* with PSK there must be client callback set */
-  if (!s->psk_client_callback) {
+  if (!ssl->psk_client_callback) {
     c->mask_a |= SSL_aPSK;
     c->mask_k |= SSL_kPSK;
   }
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 73ca87d..562e6c5 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1239,7 +1239,7 @@
   }
   if (config->enable_all_curves) {
     static const int kAllCurves[] = {
-        NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1,
+        NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1, NID_x25519,
     };
     if (!SSL_set1_curves(ssl.get(), kAllCurves,
                          sizeof(kAllCurves) / sizeof(kAllCurves[0]))) {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 2a32cd3..5600ec6 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -838,6 +838,12 @@
 	return strings.Contains("-"+suiteName+"-", "-"+component+"-")
 }
 
+func isTLSOnly(suiteName string) bool {
+	// BoringSSL doesn't support ECDHE without a curves extension, and
+	// SSLv3 doesn't contain extensions.
+	return hasComponent(suiteName, "ECDHE") || isTLS12Only(suiteName)
+}
+
 func isTLS12Only(suiteName string) bool {
 	return hasComponent(suiteName, "GCM") ||
 		hasComponent(suiteName, "SHA256") ||
@@ -1813,6 +1819,8 @@
 					NoSupportedCurves: true,
 				},
 			},
+			shouldFail:    true,
+			expectedError: ":NO_SHARED_CIPHER:",
 		},
 		{
 			testType: serverTest,
@@ -2118,20 +2126,12 @@
 				continue
 			}
 
-			testCases = append(testCases, testCase{
-				testType: clientTest,
-				name:     ver.name + "-" + suite.name + "-client",
-				config: Config{
-					MinVersion:           ver.version,
-					MaxVersion:           ver.version,
-					CipherSuites:         []uint16{suite.id},
-					Certificates:         []Certificate{cert},
-					PreSharedKey:         []byte(psk),
-					PreSharedKeyIdentity: pskIdentity,
-				},
-				flags:         flags,
-				resumeSession: true,
-			})
+			shouldFail := isTLSOnly(suite.name) && ver.version == VersionSSL30
+
+			expectedError := ""
+			if shouldFail {
+				expectedError = ":NO_SHARED_CIPHER:"
+			}
 
 			testCases = append(testCases, testCase{
 				testType: serverTest,
@@ -2148,6 +2148,27 @@
 				keyFile:       keyFile,
 				flags:         flags,
 				resumeSession: true,
+				shouldFail:    shouldFail,
+				expectedError: expectedError,
+			})
+
+			if shouldFail {
+				continue
+			}
+
+			testCases = append(testCases, testCase{
+				testType: clientTest,
+				name:     ver.name + "-" + suite.name + "-client",
+				config: Config{
+					MinVersion:           ver.version,
+					MaxVersion:           ver.version,
+					CipherSuites:         []uint16{suite.id},
+					Certificates:         []Certificate{cert},
+					PreSharedKey:         []byte(psk),
+					PreSharedKeyIdentity: pskIdentity,
+				},
+				flags:         flags,
+				resumeSession: true,
 			})
 
 			if ver.hasDTLS && isDTLSCipher(suite.name) {
@@ -4624,6 +4645,7 @@
 	{"P-256", CurveP256},
 	{"P-384", CurveP384},
 	{"P-521", CurveP521},
+	{"X25519", CurveX25519},
 }
 
 func addCurveTests() {