Added ChaCha20 implementation with SSE2 opcodes.
[BearSSL] / src / ssl / ssl_hs_server.t0
index c155e79..58d5c94 100644 (file)
@@ -49,7 +49,7 @@ do_rsa_decrypt(br_ssl_server_context *ctx, int prf_id,
        /*
         * Decrypt the PMS.
         */
-       x = (*ctx->policy_vtable)->do_keyx(ctx->policy_vtable, epms, len);
+       x = (*ctx->policy_vtable)->do_keyx(ctx->policy_vtable, epms, &len);
 
        /*
         * Set the first two bytes to the maximum supported client
@@ -85,21 +85,12 @@ do_rsa_decrypt(br_ssl_server_context *ctx, int prf_id,
  */
 static void
 ecdh_common(br_ssl_server_context *ctx, int prf_id,
-       unsigned char *cpoint, size_t cpoint_len, uint32_t ctl)
+       unsigned char *xcoor, size_t xcoor_len, uint32_t ctl)
 {
        unsigned char rpms[80];
-       size_t pms_len;
 
-       /*
-        * The point length is supposed to be 1+2*Xlen, where Xlen is
-        * the length (in bytes) of the X coordinate, i.e. the pre-master
-        * secret. If the provided point is too large, then it is
-        * obviously incorrect (i.e. everybody can see that it is
-        * incorrect), so leaking that fact is not a problem.
-        */
-       pms_len = cpoint_len >> 1;
-       if (pms_len > sizeof rpms) {
-               pms_len = sizeof rpms;
+       if (xcoor_len > sizeof rpms) {
+               xcoor_len = sizeof rpms;
                ctl = 0;
        }
 
@@ -108,19 +99,19 @@ ecdh_common(br_ssl_server_context *ctx, int prf_id,
         * decryption failed. Note that we use a constant-time conditional
         * copy.
         */
-       br_hmac_drbg_generate(&ctx->eng.rng, rpms, pms_len);
-       br_ccopy(ctl ^ 1, cpoint + 1, rpms, pms_len);
+       br_hmac_drbg_generate(&ctx->eng.rng, rpms, xcoor_len);
+       br_ccopy(ctl ^ 1, xcoor, rpms, xcoor_len);
 
        /*
         * Compute master secret.
         */
-       br_ssl_engine_compute_master(&ctx->eng, prf_id, cpoint + 1, pms_len);
+       br_ssl_engine_compute_master(&ctx->eng, prf_id, xcoor, xcoor_len);
 
        /*
         * Clear the pre-master secret from RAM: it is normally a buffer
         * in the context, hence potentially long-lived.
         */
-       memset(cpoint, 0, cpoint_len);
+       memset(xcoor, 0, xcoor_len);
 }
 
 /*
@@ -136,10 +127,77 @@ do_ecdh(br_ssl_server_context *ctx, int prf_id,
         * Finalise the key exchange.
         */
        x = (*ctx->policy_vtable)->do_keyx(ctx->policy_vtable,
-               cpoint, cpoint_len);
+               cpoint, &cpoint_len);
        ecdh_common(ctx, prf_id, cpoint, cpoint_len, x);
 }
 
+/*
+ * Do the full static ECDH key exchange. When this function is called,
+ * it has already been verified that the cipher suite uses ECDH (not ECDHE),
+ * and the client's public key (from its certificate) has type EC and is
+ * apt for key exchange.
+ */
+static void
+do_static_ecdh(br_ssl_server_context *ctx, int prf_id)
+{
+       unsigned char cpoint[133];
+       size_t cpoint_len;
+       const br_x509_class **xc;
+       const br_x509_pkey *pk;
+
+       xc = ctx->eng.x509ctx;
+       pk = (*xc)->get_pkey(xc, NULL);
+       cpoint_len = pk->key.ec.qlen;
+       if (cpoint_len > sizeof cpoint) {
+               /*
+                * If the point is larger than our buffer then we need to
+                * restrict it. Length 2 is not a valid point length, so
+                * the ECDH will fail.
+                */
+               cpoint_len = 2;
+       }
+       memcpy(cpoint, pk->key.ec.q, cpoint_len);
+       do_ecdh(ctx, prf_id, cpoint, cpoint_len);
+}
+
+static size_t
+hash_data(br_ssl_server_context *ctx,
+       void *dst, int hash_id, const void *src, size_t len)
+{
+       const br_hash_class *hf;
+       br_hash_compat_context hc;
+
+       if (hash_id == 0) {
+               unsigned char tmp[36];
+
+               hf = br_multihash_getimpl(&ctx->eng.mhash, br_md5_ID);
+               if (hf == NULL) {
+                       return 0;
+               }
+               hf->init(&hc.vtable);
+               hf->update(&hc.vtable, src, len);
+               hf->out(&hc.vtable, tmp);
+               hf = br_multihash_getimpl(&ctx->eng.mhash, br_sha1_ID);
+               if (hf == NULL) {
+                       return 0;
+               }
+               hf->init(&hc.vtable);
+               hf->update(&hc.vtable, src, len);
+               hf->out(&hc.vtable, tmp + 16);
+               memcpy(dst, tmp, 36);
+               return 36;
+       } else {
+               hf = br_multihash_getimpl(&ctx->eng.mhash, hash_id);
+               if (hf == NULL) {
+                       return 0;
+               }
+               hf->init(&hc.vtable);
+               hf->update(&hc.vtable, src, len);
+               hf->out(&hc.vtable, dst);
+               return (hf->desc >> BR_HASHDESC_OUT_OFF) & BR_HASHDESC_OUT_MASK;
+       }
+}
+
 /*
  * Do the ECDHE key exchange (part 1: generation of transient key, and
  * computing of the point to send to the client). Returned value is the
@@ -150,12 +208,10 @@ do_ecdh(br_ssl_server_context *ctx, int prf_id,
 static int
 do_ecdhe_part1(br_ssl_server_context *ctx, int curve)
 {
-       int hash;
+       unsigned algo_id;
        unsigned mask;
-       const unsigned char *order, *generator;
+       const unsigned char *order;
        size_t olen, glen;
-       br_multihash_context mhc;
-       unsigned char head[4];
        size_t hv_len, sig_len;
 
        if (!((ctx->eng.iec->supported_curves >> curve) & 1)) {
@@ -184,49 +240,33 @@ do_ecdhe_part1(br_ssl_server_context *ctx, int curve)
        /*
         * Compute our ECDH point.
         */
-       generator = ctx->eng.iec->generator(curve, &glen);
-       memcpy(ctx->eng.ecdhe_point, generator, glen);
+       glen = ctx->eng.iec->mulgen(ctx->eng.ecdhe_point,
+               ctx->ecdhe_key, olen, curve);
        ctx->eng.ecdhe_point_len = glen;
-       if (!ctx->eng.iec->mul(ctx->eng.ecdhe_point, glen,
-               ctx->ecdhe_key, olen, curve))
-       {
-               return -BR_ERR_INVALID_ALGORITHM;
-       }
 
        /*
-        * Compute the signature.
+        * Assemble the message to be signed, and possibly hash it.
         */
-       br_multihash_zero(&mhc);
-       br_multihash_copyimpl(&mhc, &ctx->eng.mhash);
-       br_multihash_init(&mhc);
-       br_multihash_update(&mhc,
-               ctx->eng.client_random, sizeof ctx->eng.client_random);
-       br_multihash_update(&mhc,
-               ctx->eng.server_random, sizeof ctx->eng.server_random);
-       head[0] = 3;
-       head[1] = 0;
-       head[2] = curve;
-       head[3] = ctx->eng.ecdhe_point_len;
-       br_multihash_update(&mhc, head, sizeof head);
-       br_multihash_update(&mhc,
+       memcpy(ctx->eng.pad, ctx->eng.client_random, 32);
+       memcpy(ctx->eng.pad + 32, ctx->eng.server_random, 32);
+       ctx->eng.pad[64 + 0] = 0x03;
+       ctx->eng.pad[64 + 1] = 0x00;
+       ctx->eng.pad[64 + 2] = curve;
+       ctx->eng.pad[64 + 3] = ctx->eng.ecdhe_point_len;
+       memcpy(ctx->eng.pad + 64 + 4,
                ctx->eng.ecdhe_point, ctx->eng.ecdhe_point_len);
-       hash = ctx->sign_hash_id;
-       if (hash) {
-               hv_len = br_multihash_out(&mhc, hash, ctx->eng.pad);
+       hv_len = 64 + 4 + ctx->eng.ecdhe_point_len;
+       algo_id = ctx->sign_hash_id;
+       if (algo_id >= (unsigned)0xFF00) {
+               hv_len = hash_data(ctx, ctx->eng.pad, algo_id & 0xFF,
+                       ctx->eng.pad, hv_len);
                if (hv_len == 0) {
                        return -BR_ERR_INVALID_ALGORITHM;
                }
-       } else {
-               if (!br_multihash_out(&mhc, br_md5_ID, ctx->eng.pad)
-                       || !br_multihash_out(&mhc,
-                       br_sha1_ID, ctx->eng.pad + 16))
-               {
-                       return -BR_ERR_INVALID_ALGORITHM;
-               }
-               hv_len = 36;
        }
+
        sig_len = (*ctx->policy_vtable)->do_sign(ctx->policy_vtable,
-               hash, hv_len, ctx->eng.pad, sizeof ctx->eng.pad);
+               algo_id, ctx->eng.pad, hv_len, sizeof ctx->eng.pad);
        return sig_len ? (int)sig_len : -BR_ERR_INVALID_ALGORITHM;
 }
 
@@ -239,16 +279,18 @@ do_ecdhe_part2(br_ssl_server_context *ctx, int prf_id,
        unsigned char *cpoint, size_t cpoint_len)
 {
        int curve;
-       uint32_t x;
+       uint32_t ctl;
+       size_t xoff, xlen;
 
        curve = ctx->eng.ecdhe_curve;
 
        /*
         * Finalise the key exchange.
         */
-       x = ctx->eng.iec->mul(cpoint, cpoint_len,
+       ctl = ctx->eng.iec->mul(cpoint, cpoint_len,
                ctx->ecdhe_key, ctx->ecdhe_key_len, curve);
-       ecdh_common(ctx, prf_id, cpoint, cpoint_len, x);
+       xoff = ctx->eng.iec->xoff(curve, &xlen);
+       ecdh_common(ctx, prf_id, cpoint + xoff, xlen, ctl);
 
        /*
         * Clear the ECDHE private key. Forward Secrecy is achieved insofar
@@ -258,6 +300,94 @@ do_ecdhe_part2(br_ssl_server_context *ctx, int prf_id,
        memset(ctx->ecdhe_key, 0, ctx->ecdhe_key_len);
 }
 
+/*
+ * Offset for hash value within the pad (when obtaining all hash values,
+ * in preparation for verification of the CertificateVerify message).
+ * Order is MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512; last value
+ * is used to get the total length.
+ */
+static const unsigned char HASH_PAD_OFF[] = { 0, 16, 36, 64, 96, 144, 208 };
+
+/*
+ * OID for hash functions in RSA signatures.
+ */
+static const unsigned char HASH_OID_SHA1[] = {
+       0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A
+};
+
+static const unsigned char HASH_OID_SHA224[] = {
+       0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04
+};
+
+static const unsigned char HASH_OID_SHA256[] = {
+       0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01
+};
+
+static const unsigned char HASH_OID_SHA384[] = {
+       0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02
+};
+
+static const unsigned char HASH_OID_SHA512[] = {
+       0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03
+};
+
+static const unsigned char *HASH_OID[] = {
+       HASH_OID_SHA1,
+       HASH_OID_SHA224,
+       HASH_OID_SHA256,
+       HASH_OID_SHA384,
+       HASH_OID_SHA512
+};
+
+/*
+ * Verify the signature in CertificateVerify. Returned value is 0 on
+ * success, or a non-zero error code. Lack of implementation of the
+ * designated signature algorithm is reported as a "bad signature"
+ * error (because it means that the peer did not honour our advertised
+ * set of supported signature algorithms).
+ */
+static int
+verify_CV_sig(br_ssl_server_context *ctx, size_t sig_len)
+{
+       const br_x509_class **xc;
+       const br_x509_pkey *pk;
+       int id;
+
+       id = ctx->hash_CV_id;
+       xc = ctx->eng.x509ctx;
+       pk = (*xc)->get_pkey(xc, NULL);
+       if (pk->key_type == BR_KEYTYPE_RSA) {
+               unsigned char tmp[64];
+               const unsigned char *hash_oid;
+
+               if (id == 0) {
+                       hash_oid = NULL;
+               } else {
+                       hash_oid = HASH_OID[id - 2];
+               }
+               if (ctx->eng.irsavrfy == 0) {
+                       return BR_ERR_BAD_SIGNATURE;
+               }
+               if (!ctx->eng.irsavrfy(ctx->eng.pad, sig_len,
+                       hash_oid, ctx->hash_CV_len, &pk->key.rsa, tmp)
+                       || memcmp(tmp, ctx->hash_CV, ctx->hash_CV_len) != 0)
+               {
+                       return BR_ERR_BAD_SIGNATURE;
+               }
+       } else {
+               if (ctx->eng.iecdsa == 0) {
+                       return BR_ERR_BAD_SIGNATURE;
+               }
+               if (!ctx->eng.iecdsa(ctx->eng.iec,
+                       ctx->hash_CV, ctx->hash_CV_len,
+                       &pk->key.ec, ctx->eng.pad, sig_len))
+               {
+                       return BR_ERR_BAD_SIGNATURE;
+               }
+       }
+       return 0;
+}
+
 }
 
 \ =======================================================================
@@ -268,7 +398,6 @@ do_ecdhe_part2(br_ssl_server_context *ctx, int prf_id,
        0 8191 "offsetof(br_ssl_server_context, " field + ")" + make-CX
        postpone literal postpone ; ;
 
-addr-ctx: flags
 addr-ctx: client_max_version
 addr-ctx: client_suites
 addr-ctx: client_suites_num
@@ -282,10 +411,6 @@ addr-ctx: sign_hash_id
        addr-client_suites
        CX 0 1023 { BR_MAX_CIPHER_SUITES * sizeof(br_suite_translated) } ;
 
-\ Check a server flag by index.
-: flag? ( index -- bool )
-       addr-flags get32 swap >> 1 and neg ;
-
 \ Read the client SNI extension.
 : read-client-sni ( lim -- lim )
        \ Open extension value.
@@ -391,26 +516,7 @@ cc: set-max-frag-len ( len -- ) {
        \ Open extension value.
        read16 open-elt
 
-       \ Clear list of supported signature algorithms.
-       0 addr-hashes set16
-
-       \ Get list of algorithms length.
-       read16 open-elt
-       begin dup while
-               read8 { hash } read8 { sign }
-               \ We keep the value if the signature is either 1 (RSA) or
-               \ 3 (ECDSA), and the hash is one of the SHA-* functions
-               \ (2 to 6, from SHA-1 to SHA-512). Note that we reject
-               \ any use of MD5. Also, we do not keep track of the client
-               \ preferences.
-               hash 2 >= hash 6 <= and
-               sign 1 = sign 3 = or
-               and if
-                       addr-hashes get16
-                       1 sign 1- 2 << hash + << or addr-hashes set16
-               then
-       repeat
-       close-elt
+       read-list-sign-algos addr-hashes set32
 
        \ Close extension value.
        close-elt ;
@@ -435,6 +541,41 @@ cc: set-max-frag-len ( len -- ) {
        close-elt
        close-elt ;
 
+\ Read the ALPN extension from client.
+: read-ALPN-from-client ( lim -- lim )
+       \ If we do not have configured names, then we just ignore the
+       \ extension.
+       addr-protocol_names_num get16 ifnot read-ignore-16 ret then
+
+       \ Open extension value.
+       read16 open-elt
+
+       \ Open list of protocol names.
+       read16 open-elt
+
+       \ Get all names and test for their support. We keep the one with
+       \ the lowest index (because we apply server's preferences, as
+       \ recommended by RFC 7301, section 3.2. We set the 'found' variable
+       \ to -2 and use an unsigned comparison, making -2 a huge value.
+       -2 { found }
+       begin dup while
+               read8 dup { len } addr-pad swap read-blob
+               len test-protocol-name dup found u< if
+                       >found
+               else
+                       drop
+               then
+       repeat
+
+       \ End of extension.
+       close-elt
+       close-elt
+
+       \ Write back found name index (or not). If no match was found,
+       \ then we write -1 (0xFFFF) in the index value, not 0, so that
+       \ the caller knows that we tried to match, and failed.
+       found 1+ addr-selected_protocol set16 ;
+
 \ Call policy handler to get cipher suite, hash function identifier and
 \ certificate chain. Returned value is 0 (false) on failure.
 cc: call-policy-handler ( -- bool ) {
@@ -444,9 +585,9 @@ cc: call-policy-handler ( -- bool ) {
        x = (*CTX->policy_vtable)->choose(
                CTX->policy_vtable, CTX, &choices);
        ENG->session.cipher_suite = choices.cipher_suite;
-       CTX->sign_hash_id = choices.hash_id;
-       CTX->chain = choices.chain;
-       CTX->chain_len = choices.chain_len;
+       CTX->sign_hash_id = choices.algo_id;
+       ENG->chain = choices.chain;
+       ENG->chain_len = choices.chain_len;
        T0_PUSHi(-(x != 0));
 }
 
@@ -470,6 +611,13 @@ cc: save-session ( -- ) {
        }
 }
 
+\ Read and drop ClientHello. This is used when a client-triggered
+\ renegotiation attempt is rejected.
+: skip-ClientHello ( -- )
+       read-handshake-header-core
+       1 = ifnot ERR_UNEXPECTED fail then
+       dup skip-blob drop ;
+
 \ Read ClientHello. If the session is resumed, then -1 is returned.
 : read-ClientHello ( -- resume )
        \ Get header, and check message type.
@@ -492,7 +640,7 @@ cc: save-session ( -- ) {
        check-resume { resume }
 
        \ Cipher suites. We read all cipher suites from client, each time
-       \ matching against our own list. We accumulare suites in the
+       \ matching against our own list. We accumulate suites in the
        \ client_suites[] context buffer: we keep suites that are
        \ supported by both the client and the server (so the list size
        \ cannot exceed that of the server list), and we keep them in
@@ -580,7 +728,7 @@ cc: save-session ( -- ) {
        \ -- client is reputed to know RSA and ECDSA, both with SHA-1
        \ -- the default elliptic curve is P-256 (secp256r1, id = 23)
        0 addr-server_name set8
-       0x404 addr-hashes set16
+       0x0404 addr-hashes set32
        0x800000 addr-curves set32
 
        \ Process extensions, if any.
@@ -616,6 +764,11 @@ cc: save-session ( -- ) {
                                \       read-ignore-16
                                \ endof
 
+                               \ ALPN
+                               0x0010 of
+                                       read-ALPN-from-client
+                               endof
+
                                \ Other extensions are ignored.
                                drop read-ignore-16 0
                        endcase
@@ -668,15 +821,23 @@ cc: save-session ( -- ) {
        \ we should mark the client as "supporting secure renegotiation".
        reneg-scsv if 2 addr-reneg set8 then
 
+       \ If, at that point, the 'reneg' value is still 0, then the client
+       \ did not send the extension or the SCSV, so we have to assume
+       \ that secure renegotiation is not supported by that client.
+       addr-reneg get8 ifnot 1 addr-reneg set8 then
+
        \ Check compression.
        ok-compression ifnot 40 fail-alert then
 
        \ Filter hash function support by what the server also supports.
-       \ If no common hash function remains, then ECDHE suites are not
-       \ possible.
-       supported-hash-functions drop 257 *
-       addr-hashes get16 and dup addr-hashes set16
-       0<> { can-ecdhe }
+       \ If no common hash function remains with RSA and/or ECDSA, then
+       \ the corresponding ECDHE suites are not possible.
+       supported-hash-functions drop 257 * 0xFFFF0000 or
+       addr-hashes get32 and dup addr-hashes set32
+       \ In 'can-ecdhe', bit 12 is set if ECDHE_RSA is possible, bit 13 is
+       \ set if ECDHE_ECDSA is possible.
+       dup 0xFF and 0<> neg
+       swap 8 >> 0<> 2 and or 12 << { can-ecdhe }
 
        \ Filter supported curves. If there is no common curve between
        \ client and us, then ECDHE suites cannot be used. Note that we
@@ -689,21 +850,27 @@ cc: save-session ( -- ) {
        resume if -1 ret then
 
        \ We are not resuming, so a new session ID should be generated.
+       \ We don't check that the new ID is distinct from the one sent
+       \ by the client because probability of such an event is 2^(-256),
+       \ i.e. much (much) lower than that of an undetected transmission
+       \ error or hardware miscomputation, and with similar consequences
+       \ (handshake simply fails).
        addr-session_id 32 mkrand
+       32 addr-session_id_len set8
 
        \ Translate common cipher suites, then squeeze out holes: there
        \ may be holes because of the way we fill the list when the
        \ server preference order is enforced, and also in case some
        \ suites are filtered out. In particular:
        \ -- ECDHE suites are removed if there is no common hash function
-       \    (for signatures) or no common curve.
+       \    (for the relevant signature algorithm) or no common curve.
        \ -- TLS-1.2-only suites are removed if the negociated version is
        \    TLS-1.1 or lower.
        addr-client_suites dup >css-off
        begin dup css-max < while
                dup get16 dup cipher-suite-to-elements
-               can-ecdhe ifnot
-                       dup 12 >> dup 1 = swap 2 = or if
+               dup 12 >> dup 1 = swap 2 = or if
+                       dup can-ecdhe and ifnot
                                2drop 0 dup
                        then
                then
@@ -730,6 +897,12 @@ cc: save-session ( -- ) {
        then
        addr-client_suites_num set8
 
+       \ Check ALPN.
+       addr-selected_protocol get16 0xFFFF = if
+               3 flag? if 120 fail-alert then
+               0 addr-selected_protocol set16
+       then
+
        \ Call policy handler to obtain the cipher suite and other
        \ parameters.
        call-policy-handler ifnot 40 fail-alert then
@@ -740,20 +913,28 @@ cc: save-session ( -- ) {
 \ Write ServerHello.
 : write-ServerHello ( initial -- )
        { initial }
-       \ Compute ServerHello length. Right now we only send the
-       \ "secure renegotiation" extension.
+       \ Compute ServerHello length.
        2 write8 70
 
+       \ Compute length of Secure Renegotiation extension.
        addr-reneg get8 2 = if
                initial if 5 else 29 then
        else
                0
        then
        { ext-reneg-len }
+
+       \ Compute length of Max Fragment Length extension.
        addr-peer_log_max_frag_len get8 if 5 else 0 then
        { ext-max-frag-len }
 
-       ext-reneg-len ext-max-frag-len + dup if 2 + then +
+       \ Compute length of ALPN extension. This also copy the
+       \ selected protocol name into the pad.
+       addr-selected_protocol get16 dup if 1- copy-protocol-name 7 + then
+       { ext-ALPN-len }
+
+       \ Adjust ServerHello length to account for the extensions.
+       ext-reneg-len ext-max-frag-len + ext-ALPN-len + dup if 2 + then +
        write24
 
        \ Protocol version
@@ -778,7 +959,7 @@ cc: save-session ( -- ) {
        0 write8
 
        \ Extensions
-       ext-reneg-len ext-max-frag-len + dup if
+       ext-reneg-len ext-max-frag-len + ext-ALPN-len + dup if
                write16
                ext-reneg-len dup if
                        0xFF01 write16
@@ -791,67 +972,20 @@ cc: save-session ( -- ) {
                        0x0001 write16
                        1 write16 addr-peer_log_max_frag_len get8 8 - write8
                then
+               ext-ALPN-len dup if
+                       \ Note: the selected protocol name was previously
+                       \ copied into the pad.
+                       0x0010 write16
+                       4 - dup write16
+                       2- dup write16
+                       1- addr-pad swap write-blob-head8
+               else
+                       drop
+               then
        else
                drop
        then ;
 
-\ Compute total chain length. This includes the individual certificate
-\ headers, but not the total chain header. This also sets the cert_cur,
-\ cert_len and chain_len context fields.
-cc: total-chain-length ( -- len ) {
-       size_t u;
-       uint32_t total;
-
-       total = 0;
-       for (u = 0; u < CTX->chain_len; u ++) {
-               total += 3 + (uint32_t)CTX->chain[u].data_len;
-       }
-       T0_PUSH(total);
-}
-
-\ Get length for current certificate in the chain; if the chain end was
-\ reached, then this returns -1.
-cc: begin-cert ( -- len ) {
-       if (CTX->chain_len == 0) {
-               T0_PUSHi(-1);
-       } else {
-               CTX->cert_cur = CTX->chain->data;
-               CTX->cert_len = CTX->chain->data_len;
-               CTX->chain ++;
-               CTX->chain_len --;
-               T0_PUSH(CTX->cert_len);
-       }
-}
-
-\ Copy a chunk of certificate data into the pad. Returned value is the
-\ chunk length, or 0 if the certificate end is reached.
-cc: copy-cert-chunk ( -- len ) {
-       size_t clen;
-
-       clen = CTX->cert_len;
-       if (clen > sizeof ENG->pad) {
-               clen = sizeof ENG->pad;
-       }
-       memcpy(ENG->pad, CTX->cert_cur, clen);
-       CTX->cert_cur += clen;
-       CTX->cert_len -= clen;
-       T0_PUSH(clen);
-}
-
-\ Write the server Certificate.
-: write-Certificate ( -- )
-       11 write8
-       total-chain-length
-       dup 3 + write24 write24
-       begin
-               begin-cert
-               dup 0< if drop ret then write24
-               begin copy-cert-chunk dup while
-                       addr-pad swap write-blob
-               repeat
-               drop
-       again ;
-
 \ Do the first part of ECDHE. Returned value is the computed signature
 \ length, or a negative error code on error.
 cc: do-ecdhe-part1 ( curve -- len ) {
@@ -859,21 +993,33 @@ cc: do-ecdhe-part1 ( curve -- len ) {
        T0_PUSHi(do_ecdhe_part1(CTX, curve));
 }
 
+\ Get index of first bit set to 1 (in low to high order).
+: lowest-1 ( bits -- n )
+       dup ifnot drop -1 ret then
+       0 begin dup2 >> 1 and 0= while 1+ repeat
+       swap drop ;
+
 \ Write the Server Key Exchange message (if applicable).
 : write-ServerKeyExchange ( -- )
        addr-cipher_suite get16 use-ecdhe? ifnot ret then
 
        \ We must select an appropriate curve among the curves that
-       \ are supported both by us and the peer. Right now we use
-       \ the one with the smallest ID, which in practice means P-256.
+       \ are supported both by us and the peer. Right now, we apply
+       \ a fixed preference order: Curve25519, P-256, P-384, P-521,
+       \ then the common curve with the lowest ID.
        \ (TODO: add some option to make that behaviour configurable.)
        \
        \ This loop always terminates because previous processing made
        \ sure that ECDHE suites are not selectable if there is no common
        \ curve.
-       addr-curves get32 0
-       begin dup2 >> 1 and 0= while 1+ repeat
-       { curve-id } drop
+       addr-curves get32
+       dup 0x20000000 and if
+               drop 29
+       else
+               dup 0x38000000 and dup if swap then
+               drop lowest-1
+       then
+       { curve-id }
 
        \ Compute the signed curve point to send.
        curve-id do-ecdhe-part1 dup 0< if neg fail then { sig-len }
@@ -893,17 +1039,184 @@ cc: do-ecdhe-part1 ( curve -- len ) {
 
        \ If TLS-1.2+, write hash and signature identifiers.
        tls1.2+ if
-               \ Hash identifier is in the sign_hash_id field.
-               addr-sign_hash_id get8 write8
-               \ 'use-rsa-ecdhe?' returns -1 for RSA, 0 for ECDSA.
-               \ The byte on the wire shall be 1 for RSA, 3 for ECDSA.
-               addr-cipher_suite get16 use-rsa-ecdhe? 1 << 3 + write8
+               \ sign_hash_id contains either a hash identifier,
+               \ or the complete 16-bit value to write.
+               addr-sign_hash_id get16
+               dup 0xFF00 < if
+                       write16
+               else
+                       0xFF and write8
+                       \ 'use-rsa-ecdhe?' returns -1 for RSA, 0 for
+                       \ ECDSA. The byte on the wire shall be 1 for RSA,
+                       \ 3 for ECDSA.
+                       addr-cipher_suite get16 use-rsa-ecdhe? 1 << 3 + write8
+               then
        then
 
        \ Signature.
        sig-len write16
        addr-pad sig-len write-blob ;
 
+\ Get length of the list of anchor names to send to the client. The length
+\ includes the per-name 2-byte header, but _not_ the 2-byte header for
+\ the list itself. If no client certificate is requested, then this
+\ returns 0.
+cc: ta-names-total-length ( -- len ) {
+       size_t u, len;
+
+       len = 0;
+       if (CTX->ta_names != NULL) {
+               for (u = 0; u < CTX->num_tas; u ++) {
+                       len += CTX->ta_names[u].len + 2;
+               }
+       } else if (CTX->tas != NULL) {
+               for (u = 0; u < CTX->num_tas; u ++) {
+                       len += CTX->tas[u].dn.len + 2;
+               }
+       }
+       T0_PUSH(len);
+}
+
+\ Compute length and optionally write the contents of the list of
+\ supported client authentication methods.
+: write-list-auth ( do_write -- len )
+       0
+       addr-cipher_suite get16 use-ecdh? if
+               2+ over if 65 write8 66 write8 then
+       then
+       supports-rsa-sign? if 1+ over if 1 write8 then then
+       supports-ecdsa? if 1+ over if 64 write8 then then
+       swap drop ;
+
+: write-signhash-inner2 ( dow algo hashes len id -- dow algo hashes len )
+       { id }
+       over 1 id << and ifnot ret then
+       2+
+       3 pick if id write8 2 pick write8 then ;
+
+: write-signhash-inner1 ( dow algo hashes -- dow len )
+       0
+       4 write-signhash-inner2
+       5 write-signhash-inner2
+       6 write-signhash-inner2
+       3 write-signhash-inner2
+       2 write-signhash-inner2
+       -rot 2drop ;
+
+\ Compute length and optionally write the contents of the list of
+\ supported sign+hash algorithms.
+: write-list-signhash ( do_write -- len )
+       0 { len }
+       \ If supporting neither RSA nor ECDSA in the engine, then we
+       \ will do only static ECDH, and thus we claim support for
+       \ everything (for the X.509 validator).
+       supports-rsa-sign? supports-ecdsa? or ifnot
+               1 0x7C write-signhash-inner1 >len
+               3 0x7C write-signhash-inner1 len +
+               swap drop ret
+       then
+       supports-rsa-sign? if
+               1 supported-hash-functions drop
+               write-signhash-inner1 >len
+       then
+       supports-ecdsa? if
+               3 supported-hash-functions drop
+               write-signhash-inner1 len + >len
+       then
+       drop len ;
+
+\ Initialise index for sending the list of anchor DN.
+cc: begin-ta-name-list ( -- ) {
+       CTX->cur_dn_index = 0;
+}
+
+\ Switch to next DN in the list. Returned value is the DN length, or -1
+\ if the end of the list was reached.
+cc: begin-ta-name ( -- len ) {
+       const br_x500_name *dn;
+       if (CTX->cur_dn_index >= CTX->num_tas) {
+               T0_PUSHi(-1);
+       } else {
+               if (CTX->ta_names == NULL) {
+                       dn = &CTX->tas[CTX->cur_dn_index].dn;
+               } else {
+                       dn = &CTX->ta_names[CTX->cur_dn_index];
+               }
+               CTX->cur_dn_index ++;
+               CTX->cur_dn = dn->data;
+               CTX->cur_dn_len = dn->len;
+               T0_PUSH(CTX->cur_dn_len);
+       }
+}
+
+\ Copy a chunk of the current DN into the pad. Returned value is the
+\ chunk length; this is 0 when the end of the current DN is reached.
+cc: copy-dn-chunk ( -- len ) {
+       size_t clen;
+
+       clen = CTX->cur_dn_len;
+       if (clen > sizeof ENG->pad) {
+               clen = sizeof ENG->pad;
+       }
+       memcpy(ENG->pad, CTX->cur_dn, clen);
+       CTX->cur_dn += clen;
+       CTX->cur_dn_len -= clen;
+       T0_PUSH(clen);
+}
+
+\ Write a CertificateRequest message.
+: write-CertificateRequest ( -- )
+       \ The list of client authentication types includes:
+       \    rsa_sign (1)
+       \    ecdsa_sign (64)
+       \    rsa_fixed_ecdh (65)
+       \    ecdsa_fixed_ecdh (66)
+       \ rsa_sign and ecdsa_sign require, respectively, RSA and ECDSA
+       \ support. Static ECDH requires that the cipher suite is ECDH.
+       \ When we ask for static ECDH, we always send both rsa_fixed_ecdh
+       \ and ecdsa_fixed_ecdh because what matters there is what the
+       \ X.509 engine may support, and we do not control that.
+       \
+       \ With TLS 1.2, we must also send a list of supported signature
+       \ and hash algorithms. That list is supposed to qualify both
+       \ the engine itself, and the X.509 validator, which are separate
+       \ in BearSSL. There again, we use the engine capabilities in that
+       \ list, and resort to a generic all-support list if only
+       \ static ECDH is accepted.
+       \
+       \ (In practice, client implementations tend to have at most one
+       \ or two certificates, and send the chain regardless of what
+       \ algorithms are used in it.)
+
+       0 write-list-auth
+       addr-version get16 0x0303 >= if
+               2+ 0 write-list-signhash +
+       then
+       ta-names-total-length + 3 +
+
+       \ Message header
+       13 write8 write24
+
+       \ List of authentication methods
+       0 write-list-auth write8 1 write-list-auth drop
+
+       \ For TLS 1.2+, list of sign+hash
+       addr-version get16 0x0303 >= if
+               0 write-list-signhash write16 1 write-list-signhash drop
+       then
+
+       \ Trust anchor names
+       ta-names-total-length write16
+       begin-ta-name-list
+       begin
+               begin-ta-name
+               dup 0< if drop ret then write16
+               begin copy-dn-chunk dup while
+                       addr-pad swap write-blob
+               repeat
+               drop
+       again ;
+
 \ Write the Server Hello Done message.
 : write-ServerHelloDone ( -- )
        14 write8 0 write24 ;
@@ -931,11 +1244,18 @@ cc: do-ecdhe-part2 ( len prf_id -- ) {
        do_ecdhe_part2(CTX, prf_id, ENG->pad, len);
 }
 
-\ Read the Client Key Exchange.
-: read-ClientKeyExchange ( -- )
-       \ Get header, and check message type.
-       read-handshake-header 16 = ifnot ERR_UNEXPECTED fail then
+\ Perform static ECDH. The point from the client is the public key
+\ extracted from its certificate.
+cc: do-static-ecdh ( prf_id -- ) {
+       do_static_ecdh(CTX, T0_POP());
+}
 
+\ Read a ClientKeyExchange header.
+: read-ClientKeyExchange-header ( -- len )
+       read-handshake-header 16 = ifnot ERR_UNEXPECTED fail then ;
+
+\ Read the Client Key Exchange contents (non-empty case).
+: read-ClientKeyExchange-contents ( lim -- )
        \ What we should get depends on the cipher suite.
        addr-cipher_suite get16 use-rsa-keyx? if
                \ RSA key exchange: we expect a RSA-encrypted value.
@@ -955,6 +1275,116 @@ cc: do-ecdhe-part2 ( len prf_id -- ) {
        then
        close-elt ;
 
+\ Read the Client Key Exchange (normal case).
+: read-ClientKeyExchange ( -- )
+       read-ClientKeyExchange-header
+       read-ClientKeyExchange-contents ;
+
+\ Obtain all possible hash values for handshake messages so far. This
+\ is done because we need the hash value for the CertificateVerify
+\ _before_ knowing which hash function will actually be used, as this
+\ information is obtained from decoding the message header itself.
+\ All hash values are stored in the pad (208 bytes in total).
+cc: compute-hash-CV ( -- ) {
+       int i;
+
+       for (i = 1; i <= 6; i ++) {
+               br_multihash_out(&ENG->mhash, i,
+                       ENG->pad + HASH_PAD_OFF[i - 1]);
+       }
+}
+
+\ Copy the proper hash value from the pad into the dedicated buffer.
+\ Returned value is true (-1) on success, false (0) on error (error
+\ being an unimplemented hash function). The id has already been verified
+\ to be either 0 (for MD5+SHA-1) or one of the SHA-* functions.
+cc: copy-hash-CV ( hash_id -- bool ) {
+       int id = T0_POP();
+       size_t off, len;
+
+       if (id == 0) {
+               off = 0;
+               len = 36;
+       } else {
+               if (br_multihash_getimpl(&ENG->mhash, id) == 0) {
+                       T0_PUSH(0);
+                       T0_RET();
+               }
+               off = HASH_PAD_OFF[id - 1];
+               len = HASH_PAD_OFF[id] - off;
+       }
+       memcpy(CTX->hash_CV, ENG->pad + off, len);
+       CTX->hash_CV_len = len;
+       CTX->hash_CV_id = id;
+       T0_PUSHi(-1);
+}
+
+\ Verify signature in CertificateVerify. Output is 0 on success, or a
+\ non-zero error code.
+cc: verify-CV-sig ( sig-len -- err ) {
+       int err;
+
+       err = verify_CV_sig(CTX, T0_POP());
+       T0_PUSHi(err);
+}
+
+\ Process static ECDH.
+: process-static-ECDH ( ktu -- )
+       \ Static ECDH is allowed only if the cipher suite uses ECDH, and
+       \ the client's public key has type EC and allows key exchange.
+       \ BR_KEYTYPE_KEYX is 0x10, and BR_KEYTYPE_EC is 2.
+       0x1F and 0x12 = ifnot ERR_WRONG_KEY_USAGE fail then
+       addr-cipher_suite get16
+       dup use-ecdh? ifnot ERR_UNEXPECTED fail then
+       prf-id
+       do-static-ecdh ;
+
+\ Read CertificateVerify header.
+: read-CertificateVerify-header ( -- lim )
+       compute-hash-CV
+       read-handshake-header 15 = ifnot ERR_UNEXPECTED fail then ;
+
+\ Read CertificateVerify. The client key type + usage is expected on the
+\ stack.
+: read-CertificateVerify ( ktu -- )
+       \ Check that the key allows for signatures.
+       dup 0x20 and ifnot ERR_WRONG_KEY_USAGE fail then
+       0x0F and { key-type }
+
+       \ Get header.
+       read-CertificateVerify-header
+
+       \ With TLS 1.2+, there is an explicit hash + signature indication,
+       \ which must be compatible with the key type.
+       addr-version get16 0x0303 >= if
+               \ Get hash function, then signature algorithm. The
+               \ signature algorithm is 1 (RSA) or 3 (ECDSA) while our
+               \ symbolic constants for key types are 1 (RSA) or 2 (EC).
+               read16
+               dup 0xFF and 1+ 1 >> key-type = ifnot
+                       ERR_BAD_SIGNATURE fail
+               then
+               8 >>
+
+               \ We support only SHA-1, SHA-224, SHA-256, SHA-384
+               \ and SHA-512. We explicitly reject MD5.
+               dup 2 < over 6 > or if ERR_INVALID_ALGORITHM fail then
+       else
+               \ With TLS 1.0 and 1.1, hash is MD5+SHA-1 (0) for RSA,
+               \ SHA-1 (2) for ECDSA.
+               key-type 0x01 = if 0 else 2 then
+       then
+       copy-hash-CV ifnot ERR_INVALID_ALGORITHM fail then
+
+       \ Read signature.
+       read16 dup { sig-len }
+       dup 512 > if ERR_LIMIT_EXCEEDED fail then
+       addr-pad swap read-blob
+       sig-len verify-CV-sig
+       dup if fail then drop
+
+       close-elt ;
+
 \ Send a HelloRequest.
 : send-HelloRequest ( -- )
        flush-record
@@ -967,6 +1397,7 @@ cc: do-ecdhe-part2 ( len prf_id -- ) {
 : do-handshake ( initial -- )
        0 addr-application_data set8
        22 addr-record_type_out set8
+       0 addr-selected_protocol set16
        multihash-init
        read-ClientHello
        more-incoming-bytes? if ERR_UNEXPECTED fail then
@@ -978,11 +1409,54 @@ cc: do-ecdhe-part2 ( len prf_id -- ) {
        else
                \ Not a session resumption
                write-ServerHello
-               write-Certificate
+               write-Certificate drop
                write-ServerKeyExchange
+               ta-names-total-length if
+                       write-CertificateRequest
+               then
                write-ServerHelloDone
                flush-record
-               read-ClientKeyExchange
+
+               \ If we sent a CertificateRequest then we expect a
+               \ Certificate message.
+               ta-names-total-length if
+                       \ Read client certificate.
+                       0 read-Certificate
+
+                       choice
+                               dup 0< uf
+                                       \ Client certificate validation failed.
+                                       2 flag? ifnot neg fail then
+                                       drop
+                                       read-ClientKeyExchange
+                                       read-CertificateVerify-header
+                                       dup skip-blob drop
+                               enduf
+                               dup 0= uf
+                                       \ Client sent no certificate at all.
+                                       drop
+                                       2 flag? ifnot
+                                               ERR_NO_CLIENT_AUTH fail
+                                       then
+                                       read-ClientKeyExchange
+                               enduf
+
+                               \ Client certificate was validated.
+                               read-ClientKeyExchange-header
+                               dup ifnot
+                                       \ Empty ClientKeyExchange.
+                                       drop
+                                       process-static-ECDH
+                               else
+                                       read-ClientKeyExchange-contents
+                                       read-CertificateVerify
+                               then
+                       endchoice
+               else
+                       \ No client certificate request, we just expect
+                       \ a non-empty ClientKeyExchange.
+                       read-ClientKeyExchange
+               then
                0 read-CCS-Finished
                0 write-CCS-Finished
                save-session
@@ -1006,23 +1480,28 @@ cc: do-ecdhe-part2 ( len prf_id -- ) {
                                        \ The best we can do is ask for a
                                        \ renegotiation, then wait for it
                                        \ to happen.
+                                       0 addr-application_data set8
                                        send-HelloRequest
                                then
                        endof
                        0x01 of
                                \ Reject renegotiations if the peer does not
-                               \ support secure renegotiation. As allowed
-                               \ by RFC 5246, we do not send a
-                               \ no_renegotiation alert and just ignore the
-                               \ HelloRequest.
+                               \ support secure renegotiation, or if the
+                               \ "no renegotiation" flag is set.
                                drop
-                               addr-reneg get8 1 <> if
-                                       0 do-handshake
-                               else
+                               addr-reneg get8 1 = 1 flag? or if
+                                       skip-ClientHello
                                        flush-record
                                        begin can-output? not while
                                                wait-co drop
                                        repeat
+                                       100 send-warning
+                                       \ Put back connection in "application
+                                       \ data" state: it's not dead yet.
+                                       1 addr-application_data set8
+                                       23 addr-record_type_out set8
+                               else
+                                       0 do-handshake
                                then
                        endof
                        ERR_UNEXPECTED fail