Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/bearssl/src/ssl/ssl_hs_client.t0
39483 views
\ Copyright (c) 2016 Thomas Pornin <[email protected]>
\
\ 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
	;