\ Copyright (c) 2016 Thomas Pornin \ \ Permission is hereby granted, free of charge, to any person obtaining \ a copy of this software and associated documentation files (the \ "Software"), to deal in the Software without restriction, including \ without limitation the rights to use, copy, modify, merge, publish, \ distribute, sublicense, and/or sell copies of the Software, and to \ permit persons to whom the Software is furnished to do so, subject to \ the following conditions: \ \ The above copyright notice and this permission notice shall be \ included in all copies or substantial portions of the Software. \ \ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, \ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND \ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS \ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN \ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN \ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE \ SOFTWARE. \ ---------------------------------------------------------------------- \ Handshake processing code, for the client. \ The common T0 code (ssl_hs_common.t0) shall be read first. preamble { /* * This macro evaluates to a pointer to the client context, under that * 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 "addresses" computed as offsets * within the structure work for both kinds of context. */ #define CTX ((br_ssl_client_context *)ENG) /* * Generate the pre-master secret for RSA key exchange, and encrypt it * with the server's public key. Returned value is either the encrypted * data length (in bytes), or -x on error, with 'x' being an error code. * * This code assumes that the public key has been already verified (it * was properly obtained by the X.509 engine, and it has the right type, * i.e. it is of type RSA and suitable for encryption). */ static int make_pms_rsa(br_ssl_client_context *ctx, int prf_id) { const br_x509_class **xc; const br_x509_pkey *pk; const unsigned char *n; unsigned char *pms; size_t nlen, u; xc = ctx->eng.x509ctx; pk = (*xc)->get_pkey(xc, NULL); /* * Compute actual RSA key length, in case there are leading zeros. */ n = pk->key.rsa.n; nlen = pk->key.rsa.nlen; while (nlen > 0 && *n == 0) { n ++; nlen --; } /* * We need at least 59 bytes (48 bytes for pre-master secret, and * 11 bytes for the PKCS#1 type 2 padding). Note that the X.509 * minimal engine normally blocks RSA keys shorter than 128 bytes, * so this is mostly for public keys provided explicitly by the * caller. */ if (nlen < 59) { return -BR_ERR_X509_WEAK_PUBLIC_KEY; } if (nlen > sizeof ctx->eng.pad) { return -BR_ERR_LIMIT_EXCEEDED; } /* * Make PMS. */ pms = ctx->eng.pad + nlen - 48; br_enc16be(pms, ctx->eng.version_max); br_hmac_drbg_generate(&ctx->eng.rng, pms + 2, 46); br_ssl_engine_compute_master(&ctx->eng, prf_id, pms, 48); /* * Apply PKCS#1 type 2 padding. */ ctx->eng.pad[0] = 0x00; ctx->eng.pad[1] = 0x02; ctx->eng.pad[nlen - 49] = 0x00; br_hmac_drbg_generate(&ctx->eng.rng, ctx->eng.pad + 2, nlen - 51); for (u = 2; u < nlen - 49; u ++) { while (ctx->eng.pad[u] == 0) { br_hmac_drbg_generate(&ctx->eng.rng, &ctx->eng.pad[u], 1); } } /* * Compute RSA encryption. */ if (!ctx->irsapub(ctx->eng.pad, nlen, &pk->key.rsa)) { return -BR_ERR_LIMIT_EXCEEDED; } return (int)nlen; } /* * OID for hash functions in RSA signatures. */ static const unsigned char *HASH_OID[] = { 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 verify_SKE_sig(br_ssl_client_context *ctx, int hash, int use_rsa, size_t sig_len) { const br_x509_class **xc; const br_x509_pkey *pk; br_multihash_context mhc; unsigned char hv[64], head[4]; size_t hv_len; xc = ctx->eng.x509ctx; pk = (*xc)->get_pkey(xc, NULL); 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] = ctx->eng.ecdhe_curve; head[3] = ctx->eng.ecdhe_point_len; br_multihash_update(&mhc, head, sizeof head); br_multihash_update(&mhc, ctx->eng.ecdhe_point, ctx->eng.ecdhe_point_len); if (hash) { hv_len = br_multihash_out(&mhc, hash, hv); if (hv_len == 0) { return BR_ERR_INVALID_ALGORITHM; } } else { if (!br_multihash_out(&mhc, br_md5_ID, hv) || !br_multihash_out(&mhc, br_sha1_ID, hv + 16)) { return BR_ERR_INVALID_ALGORITHM; } hv_len = 36; } if (use_rsa) { unsigned char tmp[64]; const unsigned char *hash_oid; if (hash) { hash_oid = HASH_OID[hash - 2]; } else { hash_oid = NULL; } 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->eng.iecdsa(ctx->eng.iec, hv, hv_len, &pk->key.ec, ctx->eng.pad, sig_len)) { return BR_ERR_BAD_SIGNATURE; } } return 0; } /* * 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. * * The point _from_ the server is taken from ecdhe_point[] if 'ecdhe' * is non-zero, or from the X.509 engine context if 'ecdhe' is zero * (for static ECDH). */ static int make_pms_ecdh(br_ssl_client_context *ctx, unsigned ecdhe, int prf_id) { int curve; unsigned char key[66], point[133]; const unsigned char *order, *point_src; size_t glen, olen, point_len, xoff, xlen; unsigned char mask; if (ecdhe) { curve = ctx->eng.ecdhe_curve; point_src = ctx->eng.ecdhe_point; point_len = ctx->eng.ecdhe_point_len; } else { const br_x509_class **xc; const br_x509_pkey *pk; xc = ctx->eng.x509ctx; pk = (*xc)->get_pkey(xc, NULL); curve = pk->key.ec.curve; point_src = pk->key.ec.q; point_len = pk->key.ec.qlen; } if ((ctx->eng.iec->supported_curves & ((uint32_t)1 << curve)) == 0) { return -BR_ERR_INVALID_ALGORITHM; } /* * We need to generate our key, as a non-zero random value which * is lower than the curve order, in a "large enough" range. We * force top bit to 0 and bottom bit to 1, which guarantees that * the value is in the proper range. */ order = ctx->eng.iec->order(curve, &olen); mask = 0xFF; while (mask >= order[0]) { mask >>= 1; } br_hmac_drbg_generate(&ctx->eng.rng, key, olen); key[0] &= mask; key[olen - 1] |= 0x01; /* * Compute the common ECDH point, whose X coordinate is the * pre-master secret. */ ctx->eng.iec->generator(curve, &glen); if (glen != point_len) { return -BR_ERR_INVALID_ALGORITHM; } memcpy(point, point_src, glen); if (!ctx->eng.iec->mul(point, glen, key, olen, curve)) { return -BR_ERR_INVALID_ALGORITHM; } /* * The pre-master secret is the X coordinate. */ xoff = ctx->eng.iec->xoff(curve, &xlen); br_ssl_engine_compute_master(&ctx->eng, prf_id, point + xoff, xlen); 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); } } \ ======================================================================= : addr-ctx: next-word { field } "addr-" field + 0 1 define-word 0 8191 "offsetof(br_ssl_client_context, " field + ")" + make-CX 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 \ extension), or 0 if we know that the server does not support the \ extension. : ext-reneg-length ( -- n ) addr-reneg get8 dup if 1 - 17 * else drop 5 then ; \ Length of SNI extension. : ext-sni-length ( -- len ) addr-server_name strlen dup if 9 + then ; \ Length of Maximum Fragment Length extension. : ext-frag-length ( -- len ) addr-log_max_frag_len get8 14 = if 0 else 5 then ; \ Length of Signatures extension. : ext-signatures-length ( -- len ) 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 -- ) : write-hashes { sign } supported-hash-functions drop \ We advertise hash functions in the following preference order: \ SHA-256 SHA-224 SHA-384 SHA-512 SHA-1 \ Rationale: \ -- SHA-256 and SHA-224 are more efficient on 32-bit architectures \ -- SHA-1 is less than ideally collision-resistant dup 0x10 and if 4 write8 sign write8 then dup 0x08 and if 3 write8 sign write8 then dup 0x20 and if 5 write8 sign write8 then dup 0x40 and if 6 write8 sign write8 then 0x04 and if 2 write8 sign write8 then ; \ Length of Supported Curves extension. : ext-supported-curves-length ( -- len ) supported-curves dup if 0 { x } begin dup while dup 1 and x + >x 1 >> repeat drop x 1 << 6 + then ; \ Length of Supported Point Formats extension. : 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 } \ Compute length for extensions (without the general two-byte header). \ This does not take padding extension into account. 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 1 write8 \ Compute and write length 39 addr-session_id_len get8 + addr-suites_num get8 1 << + total-ext-length if 2+ total-ext-length + then \ Compute padding (if requested). addr-min_clienthello_len get16 over - dup 0> if \ We well add a Pad ClientHello extension, which has its \ own header (4 bytes) and might be the only extension \ (2 extra bytes for the extension list header). total-ext-length ifnot swap 2+ swap 2- then \ Account for the extension header. 4 - dup 0< if drop 0 then \ Adjust total extension length. dup 4 + total-ext-length + >total-ext-length \ Adjust ClientHello length. swap 4 + over + swap else drop -1 then { ext-padding-amount } write24 \ Protocol version addr-version_max get16 write16 \ Client random addr-client_random 4 bzero addr-client_random 4 + 28 mkrand addr-client_random 32 write-blob \ Session ID addr-session_id addr-session_id_len get8 write-blob-head8 \ Supported cipher suites. We also check here that we indeed \ support all these suites. addr-suites_num get8 dup 1 << write16 addr-suites_buf swap begin dup while 1- over get16 dup suite-supported? ifnot ERR_BAD_CIPHER_SUITE fail then write16 swap 2+ swap repeat 2drop \ Compression methods (only "null" compression) 1 write8 0 write8 \ Extensions total-ext-length if total-ext-length write16 ext-reneg-length if 0xFF01 write16 \ extension type (0xFF01) addr-saved_finished ext-reneg-length 4 - dup write16 \ extension length 1- write-blob-head8 \ verify data then ext-sni-length if 0x0000 write16 \ extension type (0) addr-server_name ext-sni-length 4 - dup write16 \ extension length 2 - dup write16 \ ServerNameList length 0 write8 \ name type: host_name 3 - write-blob-head16 \ the name itself then ext-frag-length if 0x0001 write16 \ extension type (1) 0x0001 write16 \ extension length addr-log_max_frag_len get8 8 - write8 then ext-signatures-length if 0x000D write16 \ extension type (13) ext-signatures-length 4 - dup write16 \ extension length 2 - write16 \ list length supports-ecdsa? if 3 write-hashes then supports-rsa-sign? if 1 write-hashes then then \ TODO: add an API to specify preference order for curves. \ 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+ repeat 2drop else drop then ext-point-format-length if 0x000B write16 \ extension type (11) 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 dup write16 \ extension length begin dup while 1- 0 write8 repeat \ value (only zeros) drop then then ; \ ======================================================================= \ Parse server SNI extension. If present, then it should be empty. : read-server-sni ( lim -- lim ) read16 if ERR_BAD_SNI fail then ; \ Parse server Max Fragment Length extension. If present, then it should \ advertise the same length as the client. Note that whether the server \ sends it or not changes nothing for us: we won't send any record larger \ than the advertised value anyway, and we will accept incoming records \ up to our input buffer length. : read-server-frag ( lim -- lim ) read16 1 = ifnot ERR_BAD_FRAGLEN fail then read8 8 + addr-log_max_frag_len get8 = ifnot ERR_BAD_FRAGLEN fail then ; \ Parse server Secure Renegotiation extension. This is called only if \ the client sent that extension, so we only have two cases to \ distinguish: first handshake, and renegotiation; in the latter case, \ we know that the server supports the extension, otherwise the client \ would not have sent it. : read-server-reneg ( lim -- lim ) read16 addr-reneg get8 ifnot \ "reneg" is 0, so this is a first handshake. The server's \ extension MUST be empty. We also learn that the server \ supports the extension. 1 = ifnot ERR_BAD_SECRENEG fail then read8 0 = ifnot ERR_BAD_SECRENEG fail then 2 addr-reneg set8 else \ "reneg" is non-zero, and we sent an extension, so it must \ be 2 and this is a renegotiation. We must verify that \ the extension contents have length exactly 24 bytes and \ match the saved client and server "Finished". 25 = ifnot ERR_BAD_SECRENEG fail then read8 24 = ifnot ERR_BAD_SECRENEG fail then addr-pad 24 read-blob addr-saved_finished addr-pad 24 memcmp ifnot ERR_BAD_SECRENEG fail 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 ; cc: DEBUG-BLOB ( addr len -- ) { extern int printf(const char *fmt, ...); size_t len = T0_POP(); unsigned char *buf = (unsigned char *)CTX + T0_POP(); size_t u; printf("BLOB:"); for (u = 0; u < len; u ++) { if (u % 16 == 0) { printf("\n "); } printf(" %02x", buf[u]); } printf("\n"); } \ Parse incoming ServerHello. Returned value is true (-1) on session \ resumption. : read-ServerHello ( -- bool ) \ Get header, and check message type. read-handshake-header 2 = ifnot ERR_UNEXPECTED fail then \ Get protocol version. read16 { version } version addr-version_min get16 < version addr-version_max get16 > or if ERR_UNSUPPORTED_VERSION fail then \ Enforce chosen version for subsequent records in both directions. version addr-version_in get16 <> if ERR_BAD_VERSION fail then version addr-version_out set16 \ Server random. addr-server_random 32 read-blob \ The "session resumption" flag. 0 { resume } \ Session ID. read8 { idlen } idlen 32 > if ERR_OVERSIZED_ID fail then addr-pad idlen read-blob idlen addr-session_id_len get8 = idlen 0 > and if addr-session_id addr-pad idlen memcmp if \ Server session ID is non-empty and matches what \ we sent, so this is a session resumption. -1 >resume then then addr-session_id addr-pad idlen memcpy idlen addr-session_id_len set8 \ Record version. version addr-version resume check-resume \ Cipher suite. We check that it is part of the list of cipher \ suites that we advertised. 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). read8 if ERR_BAD_COMPRESSION fail then \ Parse extensions (if any). If there is no extension, then the \ read limit (on the TOS) should be 0 at that point. dup if \ Length of extension list. \ message size. read16 open-elt \ Enumerate extensions. For each of them, check that we \ sent an extension of that type, and did not see it \ yet; and then process it. ext-sni-length { ok-sni } ext-reneg-length { ok-reneg } ext-frag-length { ok-frag } 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 \ Server Name Indication. The server may \ send such an extension if it uses the SNI \ from the client, but that "response \ extension" is supposed to be empty. 0x0000 of ok-sni ifnot ERR_EXTRA_EXTENSION fail then 0 >ok-sni read-server-sni endof \ Max Frag Length. The contents shall be \ a single byte whose value matches the one \ sent by the client. 0x0001 of ok-frag ifnot ERR_EXTRA_EXTENSION fail then 0 >ok-frag read-server-frag endof \ Secure Renegotiation. 0xFF01 of ok-reneg ifnot ERR_EXTRA_EXTENSION fail then 0 >ok-reneg read-server-reneg endof \ Signature Algorithms. \ Normally, the server should never send this \ extension (so says RFC 5246 #7.4.1.4.1), \ but some existing servers do. 0x000D of ok-signatures ifnot ERR_EXTRA_EXTENSION fail then 0 >ok-signatures read-ignore-16 endof \ Supported Curves. 0x000A of ok-curves ifnot ERR_EXTRA_EXTENSION fail then 0 >ok-curves read-ignore-16 endof \ Supported Point Formats. 0x000B of ok-points ifnot ERR_EXTRA_EXTENSION fail then 0 >ok-points 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 \ If we sent a secure renegotiation extension but did not \ receive a response, then the server does not support \ secure renegotiation. This is a hard failure if this \ is a renegotiation. ok-reneg if ok-reneg 5 > if ERR_BAD_SECRENEG fail then 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: set-server-curve ( -- ) { const br_x509_class *xc; const br_x509_pkey *pk; xc = *(ENG->x509ctx); pk = xc->get_pkey(ENG->x509ctx, NULL); CTX->server_curve = (pk->key_type == BR_KEYTYPE_EC) ? pk->key.ec.curve : 0; } \ Read Certificate message from server. : read-Certificate-from-server ( -- ) addr-cipher_suite get16 expected-key-type -1 read-Certificate dup 0< if neg fail then dup ifnot ERR_UNEXPECTED fail then over and <> if ERR_WRONG_KEY_USAGE fail then \ 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) \ 'use-rsa' is 0 for ECDSA, -1 for for RSA \ 'sig-len' is the signature length (in bytes) \ The signature itself is in the pad. cc: verify-SKE-sig ( hash use-rsa sig-len -- err ) { size_t sig_len = T0_POP(); int use_rsa = T0_POPi(); int hash = T0_POPi(); T0_PUSH(verify_SKE_sig(CTX, hash, use_rsa, sig_len)); } \ Parse ServerKeyExchange : read-ServerKeyExchange ( -- ) \ Get header, and check message type. read-handshake-header 12 = ifnot ERR_UNEXPECTED fail then \ We expect a named curve, and we must support it. read8 3 = ifnot ERR_INVALID_ALGORITHM fail then read16 dup addr-ecdhe_curve set8 dup 32 >= if ERR_INVALID_ALGORITHM fail then supported-curves swap >> 1 and ifnot ERR_INVALID_ALGORITHM fail then \ Read the server point. read8 dup 133 > if ERR_INVALID_ALGORITHM fail then dup addr-ecdhe_point_len set8 addr-ecdhe_point swap read-blob \ If using TLS-1.2+, then the hash function and signature algorithm \ are explicitly provided; the signature algorithm must match what \ the cipher suite specifies. With TLS-1.0 and 1.1, the signature \ algorithm is inferred from the cipher suite, and the hash is \ either MD5+SHA-1 (for RSA signatures) or SHA-1 (for ECDSA). addr-version get16 0x0303 >= { tls1.2+ } addr-cipher_suite get16 use-rsa-ecdhe? { use-rsa } 2 { hash } tls1.2+ if \ Read hash function; accept only the SHA-* identifiers \ (from SHA-1 to SHA-512, no MD5 here). read8 dup dup 2 < swap 6 > or if ERR_INVALID_ALGORITHM fail then >hash read8 \ Get expected signature algorithm and compare with what \ the server just sent. Expected value is 1 for RSA, 3 \ for ECDSA. Note that 'use-rsa' evaluates to -1 for RSA, \ 0 for ECDSA. use-rsa 1 << 3 + = ifnot ERR_INVALID_ALGORITHM fail then else \ For MD5+SHA-1, we set 'hash' to 0. use-rsa if 0 >hash then then \ Read signature into the pad. read16 dup { sig-len } dup 512 > if ERR_LIMIT_EXCEEDED fail then addr-pad swap read-blob \ Verify signature. hash use-rsa sig-len verify-SKE-sig dup if fail then drop 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 -- ) \ 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 ; cc: do-rsa-encrypt ( prf_id -- nlen ) { int x; x = make_pms_rsa(CTX, T0_POP()); if (x < 0) { br_ssl_engine_fail(ENG, -x); T0_CO(); } else { T0_PUSH(x); } } cc: do-ecdh ( echde prf_id -- ulen ) { unsigned prf_id = T0_POP(); unsigned ecdhe = T0_POP(); int x; x = make_pms_ecdh(CTX, ecdhe, prf_id); if (x < 0) { br_ssl_engine_fail(ENG, -x); T0_CO(); } else { T0_PUSH(x); } } 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 dup use-rsa-keyx? if prf-id do-rsa-encrypt dup 2+ write24 dup write16 addr-pad swap write-blob else dup use-ecdhe? swap prf-id do-ecdh dup 1+ write24 dup write8 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 flush-record read-ServerHello if \ Session resumption. -1 read-CCS-Finished -1 write-CCS-Finished else \ Not a session resumption. \ 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. addr-cipher_suite get16 expected-key-type CX 0 63 { BR_KEYTYPE_SIGN } and if read-ServerKeyExchange then \ Get next header. read-handshake-header \ If this is a CertificateRequest, parse it, then read \ next header. dup 13 = if drop read-contents-CertificateRequest read-handshake-header -1 else 0 then { seen-CR } \ At that point, we should have a ServerHelloDone, \ whose length must be 0. 14 = ifnot ERR_UNEXPECTED fail then if ERR_BAD_HELLO_DONE fail then \ There should not be more bytes in the record at that point. more-incoming-bytes? if ERR_UNEXPECTED fail then seen-CR if \ 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 -1 write-CCS-Finished -1 read-CCS-Finished then \ Now we should be invoked only in case of renegotiation. 1 addr-application_data set8 23 addr-record_type_out set8 ; \ Read a HelloRequest message. : read-HelloRequest ( -- ) \ A HelloRequest has length 0 and type 0. read-handshake-header-core if ERR_UNEXPECTED fail then if ERR_BAD_HANDSHAKE fail then ; \ Entry point. : main ( -- ! ) \ Perform initial handshake. do-handshake begin \ Wait for further invocation. At that point, we should \ get either an explicit call for renegotiation, or \ an incoming HelloRequest handshake message. wait-co dup 0x07 and case 0x00 of 0x10 and if do-handshake then endof 0x01 of drop 0 addr-application_data set8 read-HelloRequest \ Reject renegotiations if the peer does not \ support secure renegotiation, or if the \ "no renegotiation" flag is set. addr-reneg get8 1 = 1 flag? or if flush-record begin can-output? not while 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 endof ERR_UNEXPECTED fail endcase again ;