Added minimal support of Certificate Policies extension (ability to ignore its conten...
[BearSSL] / src / ssl / ssl_hs_server.t0
index 7f5fe85..cb0579c 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,7 +127,7 @@ 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);
 }
 
@@ -169,6 +160,44 @@ do_static_ecdh(br_ssl_server_context *ctx, int prf_id)
        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
@@ -179,12 +208,10 @@ do_static_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)) {
@@ -213,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;
 }
 
@@ -268,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
@@ -503,7 +516,7 @@ cc: set-max-frag-len ( len -- ) {
        \ Open extension value.
        read16 open-elt
 
-       read-list-sign-algos addr-hashes set16
+       read-list-sign-algos addr-hashes set32
 
        \ Close extension value.
        close-elt ;
@@ -528,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 ) {
@@ -537,7 +585,7 @@ 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->sign_hash_id = choices.algo_id;
        ENG->chain = choices.chain;
        ENG->chain_len = choices.chain_len;
        T0_PUSHi(-(x != 0));
@@ -673,7 +721,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.
@@ -709,6 +757,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
@@ -767,8 +820,8 @@ cc: save-session ( -- ) {
        \ Filter hash function support by what the server also supports.
        \ If no common hash function remains with RSA and/or ECDSA, then
        \ the corresponding ECDHE suites are not possible.
-       supported-hash-functions drop 257 *
-       addr-hashes get16 and dup addr-hashes set16
+       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
@@ -832,6 +885,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
@@ -842,20 +901,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
@@ -880,7 +947,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
@@ -893,6 +960,16 @@ 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 ;
@@ -904,21 +981,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 }
@@ -938,11 +1027,18 @@ 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.
@@ -1289,6 +1385,7 @@ cc: verify-CV-sig ( sig-len -- err ) {
 : 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