Elliptic curve + post-quantum key exchange

CECPQ1 is a new key exchange that concatenates the results of an X25519
key agreement and a NEWHOPE key agreement.

Change-Id: Ib919bdc2e1f30f28bf80c4c18f6558017ea386bb
Reviewed-on: https://boringssl-review.googlesource.com/7962
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/crypto/obj/obj_mac.num b/crypto/obj/obj_mac.num
index 074657a..3a7844c 100644
--- a/crypto/obj/obj_mac.num
+++ b/crypto/obj/obj_mac.num
@@ -946,3 +946,4 @@
 dh_std_kdf		946
 dh_cofactor_kdf		947
 X25519		948
+cecpq1		949
diff --git a/crypto/obj/objects.txt b/crypto/obj/objects.txt
index f3990d0..75f2cbf 100644
--- a/crypto/obj/objects.txt
+++ b/crypto/obj/objects.txt
@@ -1333,3 +1333,6 @@
 
 # NID for X25519 (no corresponding OID).
  : X25519
+
+# NID for CECPQ1 (no corresponding OID)
+ : cecpq1
diff --git a/include/openssl/nid.h b/include/openssl/nid.h
index d51a67c..5ed44ee 100644
--- a/include/openssl/nid.h
+++ b/include/openssl/nid.h
@@ -4159,5 +4159,8 @@
 #define SN_X25519 "X25519"
 #define NID_X25519 948
 
+#define SN_cecpq1 "cecpq1"
+#define NID_cecpq1 949
+
 
 #endif  /* OPENSSL_HEADER_NID_H */
diff --git a/ssl/internal.h b/ssl/internal.h
index 962d835..acd9739 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -533,6 +533,7 @@
 #define SSL_CURVE_SECP384R1 24
 #define SSL_CURVE_SECP521R1 25
 #define SSL_CURVE_X25519 29
+#define SSL_CURVE_CECPQ1 65165
 
 /* An SSL_ECDH_METHOD is an implementation of ECDH-like key exchanges for
  * TLS. */
@@ -567,6 +568,15 @@
   int (*finish)(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);
+
+  /* get_key initializes |out| with a length-prefixed key from |cbs|. It returns
+   * one on success and zero on error. */
+  int (*get_key)(CBS *cbs, CBS *out);
+
+  /* add_key initializes |out_contents| to receive a key. Typically it will then
+   * be passed to |offer| or |accept|. It returns one on success and zero on
+   * error. */
+  int (*add_key)(CBB *cbb, CBB *out_contents);
 } /* SSL_ECDH_METHOD */;
 
 /* ssl_nid_to_curve_id looks up the curve corresponding to |nid|. On success, it
@@ -586,6 +596,12 @@
  * call it in the zero state. */
 void SSL_ECDH_CTX_cleanup(SSL_ECDH_CTX *ctx);
 
+/* SSL_ECDH_CTX_get_key calls the |get_key| method of |SSL_ECDH_METHOD|. */
+int SSL_ECDH_CTX_get_key(SSL_ECDH_CTX *ctx, CBS *cbs, CBS *out);
+
+/* SSL_ECDH_CTX_add_key calls the |add_key| method of |SSL_ECDH_METHOD|. */
+int SSL_ECDH_CTX_add_key(SSL_ECDH_CTX *ctx, CBB *cbb, CBB *out_contents);
+
 /* SSL_ECDH_CTX_offer calls the |offer| method of |SSL_ECDH_METHOD|. */
 int SSL_ECDH_CTX_offer(SSL_ECDH_CTX *ctx, CBB *out_public_key);
 
diff --git a/ssl/s3_clnt.c b/ssl/s3_clnt.c
index 88a30e3..39aea3c 100644
--- a/ssl/s3_clnt.c
+++ b/ssl/s3_clnt.c
@@ -1142,8 +1142,7 @@
     CBS 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)) {
+        !CBS_get_u16(&server_key_exchange, &curve_id)) {
       al = SSL_AD_DECODE_ERROR;
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       goto f_err;
@@ -1157,13 +1156,22 @@
       goto f_err;
     }
 
-    /* Initialize ECDH and save the peer public key for later. */
-    size_t peer_key_len;
-    if (!SSL_ECDH_CTX_init(&ssl->s3->tmp.ecdh_ctx, curve_id) ||
-        !CBS_stow(&point, &ssl->s3->tmp.peer_key, &peer_key_len)) {
+    if (!SSL_ECDH_CTX_init(&ssl->s3->tmp.ecdh_ctx, curve_id)) {
       goto err;
     }
-    /* |point| has a u8 length prefix, so this fits in a |uint16_t|. */
+    if (!SSL_ECDH_CTX_get_key(&ssl->s3->tmp.ecdh_ctx, &server_key_exchange,
+                              &point)) {
+      al = SSL_AD_DECODE_ERROR;
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+      goto f_err;
+    }
+
+    /* Initialize ECDH and save the peer public key for later. */
+    size_t peer_key_len;
+    if (!CBS_stow(&point, &ssl->s3->tmp.peer_key, &peer_key_len)) {
+      goto err;
+    }
+    /* |point| has a u8 or u16 length prefix, so this fits in a |uint16_t|. */
     assert(sizeof(ssl->s3->tmp.peer_key_len) == 2 && peer_key_len <= 0xffff);
     ssl->s3->tmp.peer_key_len = (uint16_t)peer_key_len;
   } else if (!(alg_k & SSL_kPSK)) {
@@ -1616,17 +1624,9 @@
       goto err;
     }
   } else if (alg_k & (SSL_kECDHE|SSL_kDHE)) {
-    /* Generate a keypair and serialize the public half. ECDHE uses a u8 length
-     * prefix while DHE uses u16. */
+    /* Generate a keypair and serialize the public half. */
     CBB child;
-    int child_ok;
-    if (alg_k & SSL_kECDHE) {
-      child_ok = CBB_add_u8_length_prefixed(&cbb, &child);
-    } else {
-      child_ok = CBB_add_u16_length_prefixed(&cbb, &child);
-    }
-
-    if (!child_ok) {
+    if (!SSL_ECDH_CTX_add_key(&ssl->s3->tmp.ecdh_ctx, &cbb, &child)) {
       goto err;
     }
 
diff --git a/ssl/s3_srvr.c b/ssl/s3_srvr.c
index 01d0be3..17dc15b 100644
--- a/ssl/s3_srvr.c
+++ b/ssl/s3_srvr.c
@@ -1209,7 +1209,7 @@
           !BN_bn2cbb_padded(&child, BN_num_bytes(params->p), params->p) ||
           !CBB_add_u16_length_prefixed(&cbb, &child) ||
           !BN_bn2cbb_padded(&child, BN_num_bytes(params->g), params->g) ||
-          !CBB_add_u16_length_prefixed(&cbb, &child) ||
+          !SSL_ECDH_CTX_add_key(&ssl->s3->tmp.ecdh_ctx, &cbb, &child) ||
           !SSL_ECDH_CTX_offer(&ssl->s3->tmp.ecdh_ctx, &child)) {
         goto err;
       }
@@ -1227,7 +1227,7 @@
       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) ||
+          !SSL_ECDH_CTX_add_key(&ssl->s3->tmp.ecdh_ctx, &cbb, &child) ||
           !SSL_ECDH_CTX_offer(&ssl->s3->tmp.ecdh_ctx, &child)) {
         goto err;
       }
@@ -1591,18 +1591,11 @@
     OPENSSL_free(decrypt_buf);
     decrypt_buf = NULL;
   } else if (alg_k & (SSL_kECDHE|SSL_kDHE)) {
-    /* Parse the ClientKeyExchange. ECDHE uses a u8 length prefix while DHE uses
-     * u16. */
+    /* Parse the ClientKeyExchange. */
     CBS peer_key;
-    int peer_key_ok;
-    if (alg_k & SSL_kECDHE) {
-      peer_key_ok = CBS_get_u8_length_prefixed(&client_key_exchange, &peer_key);
-    } else {
-      peer_key_ok =
-          CBS_get_u16_length_prefixed(&client_key_exchange, &peer_key);
-    }
-
-    if (!peer_key_ok || CBS_len(&client_key_exchange) != 0) {
+    if (!SSL_ECDH_CTX_get_key(&ssl->s3->tmp.ecdh_ctx, &client_key_exchange,
+                              &peer_key) ||
+        CBS_len(&client_key_exchange) != 0) {
       al = SSL_AD_DECODE_ERROR;
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       goto f_err;
diff --git a/ssl/ssl_ecdh.c b/ssl/ssl_ecdh.c
index 305a5af..04ecd39 100644
--- a/ssl/ssl_ecdh.c
+++ b/ssl/ssl_ecdh.c
@@ -23,6 +23,7 @@
 #include <openssl/ec.h>
 #include <openssl/err.h>
 #include <openssl/mem.h>
+#include <openssl/newhope.h>
 #include <openssl/nid.h>
 
 #include "internal.h"
@@ -220,6 +221,154 @@
   return 1;
 }
 
+
+/* Combined X25119 + New Hope (post-quantum) implementation. */
+
+typedef struct {
+  uint8_t x25519_key[32];
+  NEWHOPE_POLY *newhope_sk;
+} cecpq1_data;
+
+#define CECPQ1_OFFERMSG_LENGTH (32 + NEWHOPE_OFFERMSG_LENGTH)
+#define CECPQ1_ACCEPTMSG_LENGTH (32 + NEWHOPE_ACCEPTMSG_LENGTH)
+#define CECPQ1_SECRET_LENGTH (32 + SHA256_DIGEST_LENGTH)
+
+static void ssl_cecpq1_cleanup(SSL_ECDH_CTX *ctx) {
+  if (ctx->data == NULL) {
+    return;
+  }
+  cecpq1_data *data = ctx->data;
+  NEWHOPE_POLY_free(data->newhope_sk);
+  OPENSSL_cleanse(data, sizeof(cecpq1_data));
+  OPENSSL_free(data);
+}
+
+static int ssl_cecpq1_offer(SSL_ECDH_CTX *ctx, CBB *out) {
+  assert(ctx->data == NULL);
+  cecpq1_data *data = OPENSSL_malloc(sizeof(cecpq1_data));
+  if (data == NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+  ctx->data = data;
+  data->newhope_sk = NEWHOPE_POLY_new();
+  if (data->newhope_sk == NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  uint8_t x25519_public_key[32];
+  X25519_keypair(x25519_public_key, data->x25519_key);
+
+  uint8_t newhope_offermsg[NEWHOPE_OFFERMSG_LENGTH];
+  NEWHOPE_offer(newhope_offermsg, data->newhope_sk);
+
+  if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) ||
+      !CBB_add_bytes(out, newhope_offermsg, sizeof(newhope_offermsg))) {
+    return 0;
+  }
+  return 1;
+}
+
+static int ssl_cecpq1_accept(SSL_ECDH_CTX *ctx, CBB *cbb, uint8_t **out_secret,
+                             size_t *out_secret_len, uint8_t *out_alert,
+                             const uint8_t *peer_key, size_t peer_key_len) {
+  if (peer_key_len != CECPQ1_OFFERMSG_LENGTH) {
+    *out_alert = SSL_AD_DECODE_ERROR;
+    return 0;
+  }
+
+  *out_alert = SSL_AD_INTERNAL_ERROR;
+
+  assert(ctx->data == NULL);
+  cecpq1_data *data = OPENSSL_malloc(sizeof(cecpq1_data));
+  if (data == NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+  data->newhope_sk = NULL;
+  ctx->data = data;
+
+  uint8_t *secret = OPENSSL_malloc(CECPQ1_SECRET_LENGTH);
+  if (secret == NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  /* Generate message to server, and secret key, at once. */
+
+  uint8_t x25519_public_key[32];
+  X25519_keypair(x25519_public_key, data->x25519_key);
+  if (!X25519(secret, data->x25519_key, peer_key)) {
+    *out_alert = SSL_AD_DECODE_ERROR;
+    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT);
+    goto err;
+  }
+
+  uint8_t newhope_acceptmsg[NEWHOPE_ACCEPTMSG_LENGTH];
+  if (!NEWHOPE_accept(secret + 32, newhope_acceptmsg, peer_key + 32,
+                      NEWHOPE_OFFERMSG_LENGTH)) {
+    *out_alert = SSL_AD_DECODE_ERROR;
+    goto err;
+  }
+
+  if (!CBB_add_bytes(cbb, x25519_public_key, sizeof(x25519_public_key)) ||
+      !CBB_add_bytes(cbb, newhope_acceptmsg, sizeof(newhope_acceptmsg))) {
+    goto err;
+  }
+
+  *out_secret = secret;
+  *out_secret_len = CECPQ1_SECRET_LENGTH;
+  return 1;
+
+ err:
+  OPENSSL_cleanse(secret, CECPQ1_SECRET_LENGTH);
+  OPENSSL_free(secret);
+  return 0;
+}
+
+static int ssl_cecpq1_finish(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) {
+  if (peer_key_len != CECPQ1_ACCEPTMSG_LENGTH) {
+    *out_alert = SSL_AD_DECODE_ERROR;
+    return 0;
+  }
+
+  *out_alert = SSL_AD_INTERNAL_ERROR;
+
+  assert(ctx->data != NULL);
+  cecpq1_data *data = ctx->data;
+
+  uint8_t *secret = OPENSSL_malloc(CECPQ1_SECRET_LENGTH);
+  if (secret == NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  if (!X25519(secret, data->x25519_key, peer_key)) {
+    *out_alert = SSL_AD_DECODE_ERROR;
+    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT);
+    goto err;
+  }
+
+  if (!NEWHOPE_finish(secret + 32, data->newhope_sk, peer_key + 32,
+                      NEWHOPE_ACCEPTMSG_LENGTH)) {
+    *out_alert = SSL_AD_DECODE_ERROR;
+    goto err;
+  }
+
+  *out_secret = secret;
+  *out_secret_len = CECPQ1_SECRET_LENGTH;
+  return 1;
+
+ err:
+  OPENSSL_cleanse(secret, CECPQ1_SECRET_LENGTH);
+  OPENSSL_free(secret);
+  return 0;
+}
+
+
 /* Legacy DHE-based implementation. */
 
 static void ssl_dhe_cleanup(SSL_ECDH_CTX *ctx) {
@@ -295,6 +444,8 @@
     ssl_dhe_offer,
     ssl_dhe_accept,
     ssl_dhe_finish,
+    CBS_get_u16_length_prefixed,
+    CBB_add_u16_length_prefixed,
 };
 
 static const SSL_ECDH_METHOD kMethods[] = {
@@ -306,6 +457,8 @@
         ssl_ec_point_offer,
         ssl_ec_point_accept,
         ssl_ec_point_finish,
+        CBS_get_u8_length_prefixed,
+        CBB_add_u8_length_prefixed,
     },
     {
         NID_secp384r1,
@@ -315,6 +468,8 @@
         ssl_ec_point_offer,
         ssl_ec_point_accept,
         ssl_ec_point_finish,
+        CBS_get_u8_length_prefixed,
+        CBB_add_u8_length_prefixed,
     },
     {
         NID_secp521r1,
@@ -324,6 +479,8 @@
         ssl_ec_point_offer,
         ssl_ec_point_accept,
         ssl_ec_point_finish,
+        CBS_get_u8_length_prefixed,
+        CBB_add_u8_length_prefixed,
     },
     {
         NID_X25519,
@@ -333,6 +490,19 @@
         ssl_x25519_offer,
         ssl_x25519_accept,
         ssl_x25519_finish,
+        CBS_get_u8_length_prefixed,
+        CBB_add_u8_length_prefixed,
+    },
+    {
+        NID_cecpq1,
+        SSL_CURVE_CECPQ1,
+        "CECPQ1",
+        ssl_cecpq1_cleanup,
+        ssl_cecpq1_offer,
+        ssl_cecpq1_accept,
+        ssl_cecpq1_finish,
+        CBS_get_u16_length_prefixed,
+        CBB_add_u16_length_prefixed,
     },
 };
 
@@ -392,6 +562,20 @@
   ctx->data = params;
 }
 
+int SSL_ECDH_CTX_get_key(SSL_ECDH_CTX *ctx, CBS *cbs, CBS *out) {
+  if (ctx->method == NULL) {
+    return 0;
+  }
+  return ctx->method->get_key(cbs, out);
+}
+
+int SSL_ECDH_CTX_add_key(SSL_ECDH_CTX *ctx, CBB *cbb, CBB *out_contents) {
+  if (ctx->method == NULL) {
+    return 0;
+  }
+  return ctx->method->add_key(cbb, out_contents);
+}
+
 void SSL_ECDH_CTX_cleanup(SSL_ECDH_CTX *ctx) {
   if (ctx->method == NULL) {
     return;
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index c3ff33b..b676707 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1310,7 +1310,7 @@
   }
   if (config->enable_all_curves) {
     static const int kAllCurves[] = {
-        NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1, NID_X25519,
+      NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1, NID_X25519, NID_cecpq1
     };
     if (!SSL_set1_curves(ssl.get(), kAllCurves,
                          sizeof(kAllCurves) / sizeof(kAllCurves[0]))) {
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 57b7b29..3b085d3 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -103,6 +103,7 @@
 	CurveP384   CurveID = 24
 	CurveP521   CurveID = 25
 	CurveX25519 CurveID = 29
+	CurveCECPQ1 CurveID = 65165
 )
 
 // TLS Elliptic Curve Point Formats
diff --git a/ssl/test/runner/key_agreement.go b/ssl/test/runner/key_agreement.go
index f1d44f2..75998b4 100644
--- a/ssl/test/runner/key_agreement.go
+++ b/ssl/test/runner/key_agreement.go
@@ -21,6 +21,7 @@
 	"math/big"
 
 	"./curve25519"
+	"./newhope"
 )
 
 var errClientKeyExchange = errors.New("tls: invalid ClientKeyExchange message")
@@ -348,6 +349,66 @@
 	return out[:], nil
 }
 
+// cecpq1Curve is combined elliptic curve (X25519) and post-quantum (new hope) key
+// agreement.
+type cecpq1Curve struct {
+	x25519  *x25519ECDHCurve
+	newhope *newhope.Poly
+}
+
+func (e *cecpq1Curve) offer(rand io.Reader) (publicKey []byte, err error) {
+	var x25519OfferMsg, newhopeOfferMsg []byte
+
+	e.x25519 = new(x25519ECDHCurve)
+	if x25519OfferMsg, err = e.x25519.offer(rand); err != nil {
+		return nil, err
+	}
+
+	newhopeOfferMsg, e.newhope = newhope.Offer(rand)
+
+	return append(x25519OfferMsg, newhopeOfferMsg[:]...), nil
+}
+
+func (e *cecpq1Curve) accept(rand io.Reader, peerKey []byte) (publicKey []byte, preMasterSecret []byte, err error) {
+	if len(peerKey) != 32+newhope.OfferMsgLen {
+		return nil, nil, errors.New("cecpq1: invalid offer message")
+	}
+
+	var x25519AcceptMsg, newhopeAcceptMsg []byte
+	var x25519Secret []byte
+	var newhopeSecret newhope.Key
+
+	x25519 := new(x25519ECDHCurve)
+	if x25519AcceptMsg, x25519Secret, err = x25519.accept(rand, peerKey[:32]); err != nil {
+		return nil, nil, err
+	}
+
+	if newhopeSecret, newhopeAcceptMsg, err = newhope.Accept(rand, peerKey[32:]); err != nil {
+		return nil, nil, err
+	}
+
+	return append(x25519AcceptMsg, newhopeAcceptMsg[:]...), append(x25519Secret, newhopeSecret[:]...), nil
+}
+
+func (e *cecpq1Curve) finish(peerKey []byte) (preMasterSecret []byte, err error) {
+	if len(peerKey) != 32+newhope.AcceptMsgLen {
+		return nil, errors.New("cecpq1: invalid accept message")
+	}
+
+	var x25519Secret []byte
+	var newhopeSecret newhope.Key
+
+	if x25519Secret, err = e.x25519.finish(peerKey[:32]); err != nil {
+		return nil, err
+	}
+
+	if newhopeSecret, err = e.newhope.Finish(peerKey[32:]); err != nil {
+		return nil, err
+	}
+
+	return append(x25519Secret, newhopeSecret[:]...), nil
+}
+
 func curveForCurveID(id CurveID) (ecdhCurve, bool) {
 	switch id {
 	case CurveP224:
@@ -360,6 +421,8 @@
 		return &ellipticECDHCurve{curve: elliptic.P521()}, true
 	case CurveX25519:
 		return &x25519ECDHCurve{}, true
+	case CurveCECPQ1:
+		return &cecpq1Curve{}, true
 	default:
 		return nil, false
 	}
@@ -584,15 +647,19 @@
 	}
 
 	// http://tools.ietf.org/html/rfc4492#section-5.4
-	serverECDHParams := make([]byte, 1+2+1+len(publicKey))
-	serverECDHParams[0] = 3 // named curve
-	serverECDHParams[1] = byte(curveid >> 8)
-	serverECDHParams[2] = byte(curveid)
+	var serverECDHParams []byte
+	serverECDHParams = append(serverECDHParams, byte(3)) // named curve
+	serverECDHParams = append(serverECDHParams, byte(curveid>>8))
+	serverECDHParams = append(serverECDHParams, byte(curveid))
 	if config.Bugs.InvalidSKXCurve {
 		serverECDHParams[2] ^= 0xff
 	}
-	serverECDHParams[3] = byte(len(publicKey))
-	copy(serverECDHParams[4:], publicKey)
+	if curveid == CurveCECPQ1 {
+		// The larger key size requires an extra length byte.
+		serverECDHParams = append(serverECDHParams, byte(len(publicKey)>>8))
+	}
+	serverECDHParams = append(serverECDHParams, byte(len(publicKey)&0xff))
+	serverECDHParams = append(serverECDHParams, publicKey[:]...)
 	if config.Bugs.InvalidECDHPoint {
 		serverECDHParams[4] ^= 0xff
 	}
@@ -601,10 +668,21 @@
 }
 
 func (ka *ecdheKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) {
-	if len(ckx.ciphertext) == 0 || int(ckx.ciphertext[0]) != len(ckx.ciphertext)-1 {
+	if len(ckx.ciphertext) == 0 {
 		return nil, errClientKeyExchange
 	}
-	return ka.curve.finish(ckx.ciphertext[1:])
+	peerKeyLen := int(ckx.ciphertext[0])
+	offset := 1
+	if _, postQuantum := ka.curve.(*cecpq1Curve); postQuantum {
+		// The larger key size requires an extra length byte.
+		peerKeyLen = int(ckx.ciphertext[0])<<8 + int(ckx.ciphertext[1])
+		offset = 2
+	}
+	peerKey := ckx.ciphertext[offset:]
+	if peerKeyLen != len(peerKey) {
+		return nil, errClientKeyExchange
+	}
+	return ka.curve.finish(peerKey)
 }
 
 func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
@@ -622,15 +700,22 @@
 	}
 
 	publicLen := int(skx.key[3])
-	if publicLen+4 > len(skx.key) {
+	publicOffset := 4
+	if curveid == CurveCECPQ1 {
+		// The larger key size requires an extra length byte.
+		publicLen = int(skx.key[3])<<8 + int(skx.key[4])
+		publicOffset += 1
+	}
+
+	if publicLen+publicOffset > len(skx.key) {
 		return errServerKeyExchange
 	}
 	// Save the peer key for later.
-	ka.peerKey = skx.key[4 : 4+publicLen]
+	ka.peerKey = skx.key[publicOffset : publicOffset+publicLen]
 
 	// Check the signature.
-	serverECDHParams := skx.key[:4+publicLen]
-	sig := skx.key[4+publicLen:]
+	serverECDHParams := skx.key[:publicOffset+publicLen]
+	sig := skx.key[publicOffset+publicLen:]
 	return ka.auth.verifyParameters(config, clientHello, serverHello, cert, serverECDHParams, sig)
 }
 
@@ -645,12 +730,15 @@
 	}
 
 	ckx := new(clientKeyExchangeMsg)
-	ckx.ciphertext = make([]byte, 1+len(publicKey))
-	ckx.ciphertext[0] = byte(len(publicKey))
-	copy(ckx.ciphertext[1:], publicKey)
-	if config.Bugs.InvalidECDHPoint {
-		ckx.ciphertext[1] ^= 0xff
+	if _, postQuantum := ka.curve.(*cecpq1Curve); postQuantum {
+		// The larger key size requires an extra length byte.
+		ckx.ciphertext = append(ckx.ciphertext, byte(len(publicKey)>>8))
 	}
+	ckx.ciphertext = append(ckx.ciphertext, byte(len(publicKey)&0xff))
+	if config.Bugs.InvalidECDHPoint {
+		publicKey[0] ^= 0xff
+	}
+	ckx.ciphertext = append(ckx.ciphertext, publicKey[:]...)
 
 	return preMasterSecret, ckx, nil
 }
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index eb1efdf..004709b 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -5006,6 +5006,7 @@
 	{"P-384", CurveP384},
 	{"P-521", CurveP521},
 	{"X25519", CurveX25519},
+	{"CECPQ1", CurveCECPQ1},
 }
 
 func addCurveTests() {