Added SHAKE implementation.
[BearSSL] / src / ssl / ssl_hs_client.t0
index ea9f5b5..23b39e7 100644 (file)
@@ -31,7 +31,7 @@ preamble {
  * specific name. It must be noted that since the engine context is the
  * first field of the br_ssl_client_context structure ('eng'), then
  * pointers values of both types are interchangeable, modulo an
- * appropriate cast. This also means that "adresses" computed as offsets
+ * appropriate cast. This also means that "addresses" computed as offsets
  * within the structure work for both kinds of context.
  */
 #define CTX  ((br_ssl_client_context *)ENG)
@@ -55,7 +55,7 @@ make_pms_rsa(br_ssl_client_context *ctx, int prf_id)
        size_t nlen, u;
 
        xc = ctx->eng.x509ctx;
-       pk = (*xc)->get_pkey(xc);
+       pk = (*xc)->get_pkey(xc, NULL);
 
        /*
         * Compute actual RSA key length, in case there are leading zeros.
@@ -115,39 +115,21 @@ make_pms_rsa(br_ssl_client_context *ctx, int prf_id)
 /*
  * 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
+       BR_HASH_OID_SHA1,
+       BR_HASH_OID_SHA224,
+       BR_HASH_OID_SHA256,
+       BR_HASH_OID_SHA384,
+       BR_HASH_OID_SHA512
 };
 
 /*
  * Check the RSA signature on the ServerKeyExchange message.
+ *
  *   hash      hash function ID (2 to 6), or 0 for MD5+SHA-1 (with RSA only)
  *   use_rsa   non-zero for RSA signature, zero for ECDSA
  *   sig_len   signature length (in bytes); signature value is in the pad
+ *
  * Returned value is 0 on success, or an error code.
  */
 static int
@@ -161,7 +143,7 @@ verify_SKE_sig(br_ssl_client_context *ctx,
        size_t hv_len;
 
        xc = ctx->eng.x509ctx;
-       pk = (*xc)->get_pkey(xc);
+       pk = (*xc)->get_pkey(xc, NULL);
        br_multihash_zero(&mhc);
        br_multihash_copyimpl(&mhc, &ctx->eng.mhash);
        br_multihash_init(&mhc);
@@ -198,14 +180,14 @@ verify_SKE_sig(br_ssl_client_context *ctx,
                } else {
                        hash_oid = NULL;
                }
-               if (!ctx->irsavrfy(ctx->eng.pad, sig_len,
+               if (!ctx->eng.irsavrfy(ctx->eng.pad, sig_len,
                        hash_oid, hv_len, &pk->key.rsa, tmp)
                        || memcmp(tmp, hv, hv_len) != 0)
                {
                        return BR_ERR_BAD_SIGNATURE;
                }
        } else {
-               if (!ctx->iecdsa(ctx->eng.iec, hv, hv_len, &pk->key.ec,
+               if (!ctx->eng.iecdsa(ctx->eng.iec, hv, hv_len, &pk->key.ec,
                        ctx->eng.pad, sig_len))
                {
                        return BR_ERR_BAD_SIGNATURE;
@@ -215,7 +197,7 @@ verify_SKE_sig(br_ssl_client_context *ctx,
 }
 
 /*
- * Perform client-size ECDH (or ECDHE). The point that should be sent to
+ * Perform client-side ECDH (or ECDHE). The point that should be sent to
  * the server is written in the pad; returned value is either the point
  * length (in bytes), or -x on error, with 'x' being an error code.
  *
@@ -228,8 +210,8 @@ make_pms_ecdh(br_ssl_client_context *ctx, unsigned ecdhe, int prf_id)
 {
        int curve;
        unsigned char key[66], point[133];
-       const unsigned char *generator, *order, *point_src;
-       size_t glen, olen, point_len;
+       const unsigned char *order, *point_src;
+       size_t glen, olen, point_len, xoff, xlen;
        unsigned char mask;
 
        if (ecdhe) {
@@ -241,7 +223,7 @@ make_pms_ecdh(br_ssl_client_context *ctx, unsigned ecdhe, int prf_id)
                const br_x509_pkey *pk;
 
                xc = ctx->eng.x509ctx;
-               pk = (*xc)->get_pkey(xc);
+               pk = (*xc)->get_pkey(xc, NULL);
                curve = pk->key.ec.curve;
                point_src = pk->key.ec.q;
                point_len = pk->key.ec.qlen;
@@ -269,7 +251,7 @@ make_pms_ecdh(br_ssl_client_context *ctx, unsigned ecdhe, int prf_id)
         * Compute the common ECDH point, whose X coordinate is the
         * pre-master secret.
         */
-       generator = ctx->eng.iec->generator(curve, &glen);
+       ctx->eng.iec->generator(curve, &glen);
        if (glen != point_len) {
                return -BR_ERR_INVALID_ALGORITHM;
        }
@@ -282,16 +264,81 @@ make_pms_ecdh(br_ssl_client_context *ctx, unsigned ecdhe, int prf_id)
        /*
         * The pre-master secret is the X coordinate.
         */
-       br_ssl_engine_compute_master(&ctx->eng, prf_id, point + 1, glen >> 1);
+       xoff = ctx->eng.iec->xoff(curve, &xlen);
+       br_ssl_engine_compute_master(&ctx->eng, prf_id, point + xoff, xlen);
 
-       memcpy(point, generator, glen);
-       if (!ctx->eng.iec->mul(point, glen, key, olen, curve)) {
-               return -BR_ERR_INVALID_ALGORITHM;
-       }
+       ctx->eng.iec->mulgen(point, key, olen, curve);
        memcpy(ctx->eng.pad, point, glen);
        return (int)glen;
 }
 
+/*
+ * Perform full static ECDH. This occurs only in the context of client
+ * authentication with certificates: the server uses an EC public key,
+ * the cipher suite is of type ECDH (not ECDHE), the server requested a
+ * client certificate and accepts static ECDH, the client has a
+ * certificate with an EC public key in the same curve, and accepts
+ * static ECDH as well.
+ *
+ * Returned value is 0 on success, -1 on error.
+ */
+static int
+make_pms_static_ecdh(br_ssl_client_context *ctx, int prf_id)
+{
+       unsigned char point[133];
+       size_t point_len;
+       const br_x509_class **xc;
+       const br_x509_pkey *pk;
+
+       xc = ctx->eng.x509ctx;
+       pk = (*xc)->get_pkey(xc, NULL);
+       point_len = pk->key.ec.qlen;
+       if (point_len > sizeof point) {
+               return -1;
+       }
+       memcpy(point, pk->key.ec.q, point_len);
+       if (!(*ctx->client_auth_vtable)->do_keyx(
+               ctx->client_auth_vtable, point, &point_len))
+       {
+               return -1;
+       }
+       br_ssl_engine_compute_master(&ctx->eng,
+               prf_id, point, point_len);
+       return 0;
+}
+
+/*
+ * Compute the client-side signature. This is invoked only when a
+ * signature-based client authentication was selected. The computed
+ * signature is in the pad; its length (in bytes) is returned. On
+ * error, 0 is returned.
+ */
+static size_t
+make_client_sign(br_ssl_client_context *ctx)
+{
+       size_t hv_len;
+
+       /*
+        * Compute hash of handshake messages so far. This "cannot" fail
+        * because the list of supported hash functions provided to the
+        * client certificate handler was trimmed to include only the
+        * hash functions that the multi-hasher supports.
+        */
+       if (ctx->hash_id) {
+               hv_len = br_multihash_out(&ctx->eng.mhash,
+                       ctx->hash_id, ctx->eng.pad);
+       } else {
+               br_multihash_out(&ctx->eng.mhash,
+                       br_md5_ID, ctx->eng.pad);
+               br_multihash_out(&ctx->eng.mhash,
+                       br_sha1_ID, ctx->eng.pad + 16);
+               hv_len = 36;
+       }
+       return (*ctx->client_auth_vtable)->do_sign(
+               ctx->client_auth_vtable, ctx->hash_id, hv_len,
+               ctx->eng.pad, sizeof ctx->eng.pad);
+}
+
 }
 
 \ =======================================================================
@@ -303,6 +350,9 @@ make_pms_ecdh(br_ssl_client_context *ctx, unsigned ecdhe, int prf_id)
        postpone literal postpone ; ;
 
 addr-ctx: min_clienthello_len
+addr-ctx: hashes
+addr-ctx: auth_type
+addr-ctx: hash_id
 
 \ Length of the Secure Renegotiation extension. This is 5 for the
 \ first handshake, 17 for a renegotiation (if the server supports the
@@ -319,22 +369,11 @@ addr-ctx: min_clienthello_len
 : ext-frag-length ( -- len )
        addr-log_max_frag_len get8 14 = if 0 else 5 then ;
 
-\ Test support for RSA signatures.
-cc: supports-rsa-sign? ( -- bool ) {
-       T0_PUSHi(-(CTX->irsavrfy != 0));
-}
-
-\ Test support for ECDSA signatures.
-cc: supports-ecdsa? ( -- bool ) {
-       T0_PUSHi(-(CTX->iecdsa != 0));
-}
-
 \ Length of Signatures extension.
 : ext-signatures-length ( -- len )
-       supported-hash-functions { x } drop
-       0
-       supports-rsa-sign? if x + then
-       supports-ecdsa? if x + then
+       supported-hash-functions { num } drop 0
+       supports-rsa-sign? if num + then
+       supports-ecdsa? if num + then
        dup if 1 << 6 + then ;
 
 \ Write supported hash functions ( sign -- )
@@ -367,6 +406,21 @@ cc: supports-ecdsa? ( -- bool ) {
 : ext-point-format-length ( -- len )
        supported-curves if 6 else 0 then ;
 
+\ Length of ALPN extension.
+cc: ext-ALPN-length ( -- len ) {
+       size_t u, len;
+
+       if (ENG->protocol_names_num == 0) {
+               T0_PUSH(0);
+               T0_RET();
+       }
+       len = 6;
+       for (u = 0; u < ENG->protocol_names_num; u ++) {
+               len += 1 + strlen(ENG->protocol_names[u]);
+       }
+       T0_PUSH(len);
+}
+
 \ Write handshake message: ClientHello
 : write-ClientHello ( -- )
        { ; total-ext-length }
@@ -376,6 +430,7 @@ cc: supports-ecdsa? ( -- bool ) {
        ext-reneg-length ext-sni-length + ext-frag-length +
        ext-signatures-length +
        ext-supported-curves-length + ext-point-format-length +
+       ext-ALPN-length +
        >total-ext-length
 
        \ ClientHello type
@@ -460,13 +515,16 @@ cc: supports-ecdsa? ( -- bool ) {
                        supports-rsa-sign? if 1 write-hashes then
                then
                \ TODO: add an API to specify preference order for curves.
-               \ Right now we use increasing id order, which makes P-256
-               \ the preferred curve.
+               \ Right now we send Curve25519 first, then other curves in
+               \ increasing ID values (hence P-256 in second).
                ext-supported-curves-length dup if
                        0x000A write16          \ extension type (10)
                        4 - dup write16         \ extension length
                        2- write16              \ list length
                        supported-curves 0
+                       dup 0x20000000 and if
+                               0xDFFFFFFF and 29 write16
+                       then
                        begin dup 32 < while
                                dup2 >> 1 and if dup write16 then
                                1+
@@ -480,6 +538,21 @@ cc: supports-ecdsa? ( -- bool ) {
                        0x0002 write16          \ extension length
                        0x0100 write16          \ value: 1 format: uncompressed
                then
+               ext-ALPN-length dup if
+                       0x0010 write16          \ extension type (16)
+                       4 - dup write16         \ extension length
+                       2- write16              \ list length
+                       addr-protocol_names_num get16 0
+                       begin
+                               dup2 > while
+                               dup copy-protocol-name
+                               dup write8 addr-pad swap write-blob
+                               1+
+                       repeat
+                       2drop
+               else
+                       drop
+               then
                ext-padding-amount 0< ifnot
                        0x0015 write16          \ extension value (21)
                        ext-padding-amount
@@ -533,6 +606,24 @@ cc: supports-ecdsa? ( -- bool ) {
                then
        then ;
 
+\ Read the ALPN extension from the server. It must contain a single name,
+\ and that name must match one of our names.
+: read-ALPN-from-server ( lim -- lim )
+       \ Extension contents length.
+       read16 open-elt
+       \ Length of list of names.
+       read16 open-elt
+       \ There should be a single name.
+       read8 addr-pad swap dup { len } read-blob
+       close-elt
+       close-elt
+       len test-protocol-name dup 0< if
+               3 flag? if ERR_UNEXPECTED fail then
+               drop
+       else
+               1+ addr-selected_protocol set16
+       then ;
+
 \ Save a value in a 16-bit field, or check it in case of session resumption.
 : check-resume ( val addr resume -- )
        if get16 = ifnot ERR_RESUME_MISMATCH fail then else set16 then ;
@@ -595,16 +686,12 @@ cc: DEBUG-BLOB ( addr len -- ) {
 
        \ Cipher suite. We check that it is part of the list of cipher
        \ suites that we advertised.
-       \ read16 { suite ; found }
-       \ 0 >found
-       \ addr-suites_buf dup addr-suites_num get8 1 << +
-       \ begin dup2 < while
-       \       2 - dup get16
-       \       suite = found or >found
-       \ repeat
-       \ 2drop found ifnot ERR_BAD_CIPHER_SUITE fail then
        read16
        dup scan-suite 0< if ERR_BAD_CIPHER_SUITE fail then
+       \ Also check that the cipher suite is compatible with the
+       \ announced version: suites that don't use HMAC/SHA-1 are
+       \ for TLS-1.2 only, not older versions.
+       dup use-tls12? version 0x0303 < and if ERR_BAD_CIPHER_SUITE fail then
        addr-cipher_suite resume check-resume
 
        \ Compression method. Should be 0 (no compression).
@@ -626,6 +713,7 @@ cc: DEBUG-BLOB ( addr len -- ) {
                ext-signatures-length { ok-signatures }
                ext-supported-curves-length { ok-curves }
                ext-point-format-length { ok-points }
+               ext-ALPN-length { ok-ALPN }
                begin dup while
                        read16
                        case
@@ -691,6 +779,15 @@ cc: DEBUG-BLOB ( addr len -- ) {
                                        read-ignore-16
                                endof
 
+                               \ ALPN.
+                               0x0010 of
+                                       ok-ALPN ifnot
+                                               ERR_EXTRA_EXTENSION fail
+                                       then
+                                       0 >ok-ALPN
+                                       read-ALPN-from-server
+                               endof
+
                                ERR_EXTRA_EXTENSION fail
                        endcase
                repeat
@@ -704,85 +801,38 @@ cc: DEBUG-BLOB ( addr len -- ) {
                        1 addr-reneg set8
                then
                close-elt
+       else
+               \ No extension received at all, so the server does not
+               \ support secure renegotiation. This is a hard failure
+               \ if the server was previously known to support it (i.e.
+               \ this is a renegotiation).
+               ext-reneg-length 5 > if ERR_BAD_SECRENEG fail then
+               1 addr-reneg set8
        then
        close-elt
        resume
        ;
 
-cc: x509-start-chain ( expected-key-type -- ) {
-       const br_x509_class *xc;
-
-       xc = *(ENG->x509ctx);
-       xc->start_chain(ENG->x509ctx, T0_POP(), ENG->server_name);
-}
-
-cc: x509-start-cert ( length -- ) {
-       const br_x509_class *xc;
-
-       xc = *(ENG->x509ctx);
-       xc->start_cert(ENG->x509ctx, T0_POP());
-}
-
-cc: x509-append ( length -- ) {
-       const br_x509_class *xc;
-       size_t len;
-
-       xc = *(ENG->x509ctx);
-       len = T0_POP();
-       xc->append(ENG->x509ctx, ENG->pad, len);
-}
-
-cc: x509-end-cert ( -- ) {
-       const br_x509_class *xc;
-
-       xc = *(ENG->x509ctx);
-       xc->end_cert(ENG->x509ctx);
-}
-
-cc: x509-end-chain ( -- err ) {
+cc: set-server-curve ( -- ) {
        const br_x509_class *xc;
+       const br_x509_pkey *pk;
 
        xc = *(ENG->x509ctx);
-       T0_PUSH(xc->end_chain(ENG->x509ctx));
+       pk = xc->get_pkey(ENG->x509ctx, NULL);
+       CTX->server_curve =
+               (pk->key_type == BR_KEYTYPE_EC) ? pk->key.ec.curve : 0;
 }
 
-\ Parse Certificate
-: read-Certificate ( -- )
-       \ Get header, and check message type.
-       read-handshake-header 11 = ifnot ERR_UNEXPECTED fail then
-
-       \ Start processing the chain through the X.509 engine.
+\ Read Certificate message from server.
+: read-Certificate-from-server ( -- )
        addr-cipher_suite get16 expected-key-type
-       x509-start-chain
+       -1 read-Certificate
+       dup 0< if neg fail then
+       dup ifnot ERR_UNEXPECTED fail then
+       over and <> if ERR_WRONG_KEY_USAGE fail then
 
-       \ Total chain length is a 24-bit integer.
-       read24 open-elt
-       begin
-               dup while
-               read24 open-elt
-               dup x509-start-cert
-
-               \ We read the certificate by chunks through the pad, so
-               \ as to use the existing reading function (read-blob)
-               \ that also ensures proper hashing.
-               begin
-                       dup while
-                       dup 256 > if 256 else dup then { len }
-                       addr-pad len read-blob
-                       len x509-append
-               repeat
-               close-elt
-               x509-end-cert
-       repeat
-
-       \ We must close the chain AND the handshake message.
-       close-elt
-       close-elt
-
-       \ Chain processing is finished; get the error code.
-       x509-end-chain
-       dup if fail then drop
-       ;
+       \ Set server curve (used for static ECDH).
+       set-server-curve ;
 
 \ Verify signature on ECDHE point sent by the server.
 \   'hash' is the hash function to use (1 to 6, or 0 for RSA with MD5+SHA-1)
@@ -851,15 +901,166 @@ cc: verify-SKE-sig ( hash use-rsa sig-len -- err ) {
 
        close-elt ;
 
+\ Client certificate: start processing of anchor names.
+cc: anchor-dn-start-name-list ( -- ) {
+       if (CTX->client_auth_vtable != NULL) {
+               (*CTX->client_auth_vtable)->start_name_list(
+                       CTX->client_auth_vtable);
+       }
+}
+
+\ Client certificate: start a new anchor DN (length is 16-bit).
+cc: anchor-dn-start-name ( length -- ) {
+       size_t len;
+
+       len = T0_POP();
+       if (CTX->client_auth_vtable != NULL) {
+               (*CTX->client_auth_vtable)->start_name(
+                       CTX->client_auth_vtable, len);
+       }
+}
+
+\ Client certificate: push some data for current anchor DN.
+cc: anchor-dn-append-name ( length -- ) {
+       size_t len;
+
+       len = T0_POP();
+       if (CTX->client_auth_vtable != NULL) {
+               (*CTX->client_auth_vtable)->append_name(
+                       CTX->client_auth_vtable, ENG->pad, len);
+       }
+}
+
+\ Client certificate: end current anchor DN.
+cc: anchor-dn-end-name ( -- ) {
+       if (CTX->client_auth_vtable != NULL) {
+               (*CTX->client_auth_vtable)->end_name(
+                       CTX->client_auth_vtable);
+       }
+}
+
+\ Client certificate: end list of anchor DN.
+cc: anchor-dn-end-name-list ( -- ) {
+       if (CTX->client_auth_vtable != NULL) {
+               (*CTX->client_auth_vtable)->end_name_list(
+                       CTX->client_auth_vtable);
+       }
+}
+
+\ Client certificate: obtain the client certificate chain.
+cc: get-client-chain ( auth_types -- ) {
+       uint32_t auth_types;
+
+       auth_types = T0_POP();
+       if (CTX->client_auth_vtable != NULL) {
+               br_ssl_client_certificate ux;
+
+               (*CTX->client_auth_vtable)->choose(CTX->client_auth_vtable,
+                       CTX, auth_types, &ux);
+               CTX->auth_type = (unsigned char)ux.auth_type;
+               CTX->hash_id = (unsigned char)ux.hash_id;
+               ENG->chain = ux.chain;
+               ENG->chain_len = ux.chain_len;
+       } else {
+               CTX->hash_id = 0;
+               ENG->chain_len = 0;
+       }
+}
+
 \ Parse CertificateRequest. Header has already been read.
 : read-contents-CertificateRequest ( lim -- )
-       \ TODO: implement client certificates. Right now, we simply
-       \ drop the complete message.
-       begin dup while read8 drop repeat drop ;
+       \ Read supported client authentication types. We keep only
+       \ RSA, ECDSA, and ECDH.
+       0 { auth_types }
+       read8 open-elt
+       begin dup while
+               read8 case
+                       1  of 0x0000FF endof
+                       64 of 0x00FF00 endof
+                       65 of 0x010000 endof
+                       66 of 0x020000 endof
+                       0 swap
+               endcase
+               auth_types or >auth_types
+       repeat
+       close-elt
+
+       \ Full static ECDH is allowed only if the cipher suite is ECDH
+       \ (not ECDHE). It would be theoretically feasible to use static
+       \ ECDH on the client side with an ephemeral key pair from the
+       \ server, but RFC 4492 (section 3) forbids it because ECDHE suites
+       \ are supposed to provide forward secrecy, and static ECDH would
+       \ negate that property.
+       addr-cipher_suite get16 use-ecdh? ifnot
+               auth_types 0xFFFF and >auth_types
+       then
+
+       \ Note: if the cipher suite is ECDH, then the X.509 validation
+       \ engine was invoked with the BR_KEYTYPE_EC | BR_KEYTYPE_KEYX
+       \ combination, so the server's public key has already been
+       \ checked to be fit for a key exchange.
+
+       \ With TLS 1.2:
+       \  - rsa_fixed_ecdh and ecdsa_fixed_ecdh are synoymous.
+       \  - There is an explicit list of supported sign+hash.
+       \ With TLS 1.0,
+       addr-version get16 0x0303 >= if
+               \ With TLS 1.2:
+               \  - There is an explicit list of supported sign+hash.
+               \  - The ECDH flags must be adjusted for RSA/ECDSA
+               \    support.
+               read-list-sign-algos dup addr-hashes set32
+
+               \ Trim down the list depending on what hash functions
+               \ we support (since the hashing itself is done by the SSL
+               \ engine, not by the certificate handler).
+               supported-hash-functions drop dup 8 << or 0x030000 or and
+
+               auth_types and
+               auth_types 0x030000 and if
+                       dup 0x0000FF and if 0x010000 or then
+                       dup 0x00FF00 and if 0x020000 or then
+               then
+               >auth_types
+       else
+               \ TLS 1.0 or 1.1. The hash function is fixed for signatures
+               \ (MD5+SHA-1 for RSA, SHA-1 for ECDSA).
+               auth_types 0x030401 and >auth_types
+       then
 
+       \ Parse list of anchor DN.
+       anchor-dn-start-name-list
+       read16 open-elt
+       begin dup while
+               read16 open-elt
+               dup anchor-dn-start-name
+
+               \ We read the DN by chunks through the pad, so
+               \ as to use the existing reading function (read-blob)
+               \ that also ensures proper hashing.
+               begin
+                       dup while
+                       dup 256 > if 256 else dup then { len }
+                       addr-pad len read-blob
+                       len anchor-dn-append-name
+               repeat
+               close-elt
+               anchor-dn-end-name
+       repeat
+       close-elt
+       anchor-dn-end-name-list
+
+       \ We should have reached the message end.
+       close-elt
+
+       \ Obtain the client chain.
+       auth_types get-client-chain
+       ;
+
+\ (obsolete)
 \ Write an empty Certificate message.
-: write-empty-Certificate ( -- )
-       11 write8 3 write24 0 write24 ;
+: write-empty-Certificate ( -- )
+\      11 write8 3 write24 0 write24 ;
 
 cc: do-rsa-encrypt ( prf_id -- nlen ) {
        int x;
@@ -887,7 +1088,27 @@ cc: do-ecdh ( echde prf_id -- ulen ) {
        }
 }
 
-\ Write ClientKeyExchange
+cc: do-static-ecdh ( prf-id -- ) {
+       unsigned prf_id = T0_POP();
+
+       if (make_pms_static_ecdh(CTX, prf_id) < 0) {
+               br_ssl_engine_fail(ENG, BR_ERR_INVALID_ALGORITHM);
+               T0_CO();
+       }
+}
+
+cc: do-client-sign ( -- sig_len ) {
+       size_t sig_len;
+
+       sig_len = make_client_sign(CTX);
+       if (sig_len == 0) {
+               br_ssl_engine_fail(ENG, BR_ERR_INVALID_ALGORITHM);
+               T0_CO();
+       }
+       T0_PUSH(sig_len);
+}
+
+\ Write ClientKeyExchange.
 : write-ClientKeyExchange ( -- )
        16 write8
        addr-cipher_suite get16
@@ -903,12 +1124,27 @@ cc: do-ecdh ( echde prf_id -- ulen ) {
                addr-pad swap write-blob
        then ;
 
+\ Write CertificateVerify. This is invoked only if a client certificate
+\ was requested and sent, and the authentication is not full static ECDH.
+: write-CertificateVerify ( -- )
+       do-client-sign
+       15 write8 dup
+       addr-version get16 0x0303 >= if
+               4 + write24
+               addr-hash_id get8 write8
+               addr-auth_type get8 write8
+       else
+               2+ write24
+       then
+       dup write16 addr-pad swap write-blob ;
+
 \ =======================================================================
 
 \ Perform a handshake.
 : do-handshake ( -- )
        0 addr-application_data set8
        22 addr-record_type_out set8
+       0 addr-selected_protocol set16
        multihash-init
 
        write-ClientHello
@@ -924,7 +1160,9 @@ cc: do-ecdh ( echde prf_id -- ulen ) {
 
                \ Not a session resumption.
 
-               read-Certificate
+               \ Read certificate; then check key type and usages against
+               \ cipher suite.
+               read-Certificate-from-server
 
                \ Depending on cipher suite, we may now expect a
                \ ServerKeyExchange.
@@ -956,12 +1194,27 @@ cc: do-ecdh ( echde prf_id -- ulen ) {
                more-incoming-bytes? if ERR_UNEXPECTED fail then
 
                seen-CR if
-                       \ TODO: client certificate support.
-                       write-empty-Certificate
+                       \ If the server requested a client certificate, then
+                       \ we must write a Certificate message (it may be
+                       \ empty).
+                       write-Certificate
+
+                       \ If using static ECDH, then the ClientKeyExchange
+                       \ is empty, and there is no CertificateVerify.
+                       \ Otherwise, there is a ClientKeyExchange; there
+                       \ will then be a CertificateVerify if a client chain
+                       \ was indeed sent.
+                       addr-hash_id get8 0xFF = if
+                               drop
+                               16 write8 0 write24
+                               addr-cipher_suite get16 prf-id do-static-ecdh
+                       else
+                               write-ClientKeyExchange
+                               if write-CertificateVerify then
+                       then
+               else
+                       write-ClientKeyExchange
                then
-               write-ClientKeyExchange
-
-               \ TODO: CertificateVerify
 
                -1 write-CCS-Finished
                -1 read-CCS-Finished
@@ -1007,6 +1260,12 @@ cc: do-ecdh ( echde prf_id -- ulen ) {
                                                wait-co drop
                                        repeat
                                        100 send-warning
+                                       \ We rejected the renegotiation,
+                                       \ but the connection is not dead.
+                                       \ We must set back things into
+                                       \ working "application data" state.
+                                       1 addr-application_data set8
+                                       23 addr-record_type_out set8
                                else
                                        do-handshake
                                then