X-Git-Url: https://www.bearssl.org/gitweb//home/git/?p=BearSSL;a=blobdiff_plain;f=src%2Fssl%2Fssl_hs_server.t0;h=58d5c9414ddd30e4b800133162e67bde7fb47006;hp=817642901a957ba335c1f9099ba33b092f33d016;hb=f81a2828787c3ae7903bff66d64d71d6362ab4e1;hpb=ab68048011dee644262fd0190a83a13162c14140 diff --git a/src/ssl/ssl_hs_server.t0 b/src/ssl/ssl_hs_server.t0 index 8176429..58d5c94 100644 --- a/src/ssl/ssl_hs_server.t0 +++ b/src/ssl/ssl_hs_server.t0 @@ -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; +} + } \ ======================================================================= @@ -386,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 ; @@ -430,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 ) { @@ -439,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)); } @@ -465,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. @@ -487,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 @@ -575,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. @@ -611,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 @@ -663,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 @@ -684,6 +850,11 @@ 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 @@ -692,14 +863,14 @@ cc: save-session ( -- ) { \ 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 @@ -726,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 @@ -736,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 @@ -774,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 @@ -787,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 ) { @@ -855,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 } @@ -889,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 ; @@ -927,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. @@ -951,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 @@ -963,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 @@ -974,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 @@ -1002,6 +1480,7 @@ 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 @@ -1011,11 +1490,16 @@ cc: do-ecdhe-part2 ( len prf_id -- ) { \ "no renegotiation" flag is set. drop 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