Initial import.
This commit is contained in:
commit
3210f38e04
292 changed files with 58526 additions and 0 deletions
992
src/ssl/ssl_hs_client.t0
Normal file
992
src/ssl/ssl_hs_client.t0
Normal file
|
|
@ -0,0 +1,992 @@
|
|||
\ Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
\
|
||||
\ 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 "adresses" 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);
|
||||
|
||||
/*
|
||||
* 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_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
|
||||
};
|
||||
|
||||
/*
|
||||
* 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);
|
||||
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->irsavrfy(ctx->eng.pad, sig_len,
|
||||
hash_oid, hv_len, &pk->key.rsa, tmp)
|
||||
|| memcmp(tmp, hv, hv_len) != 0)
|
||||
{
|
||||
return BR_ERR_BAD_SIGNATURE;
|
||||
}
|
||||
} else {
|
||||
if (!ctx->iecdsa(ctx->eng.iec, hv, hv_len, &pk->key.ec,
|
||||
ctx->eng.pad, sig_len))
|
||||
{
|
||||
return BR_ERR_BAD_SIGNATURE;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform client-size 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 *generator, *order, *point_src;
|
||||
size_t glen, olen, point_len;
|
||||
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);
|
||||
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.
|
||||
*/
|
||||
generator = 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.
|
||||
*/
|
||||
br_ssl_engine_compute_master(&ctx->eng, prf_id, point + 1, glen >> 1);
|
||||
|
||||
memcpy(point, generator, glen);
|
||||
if (!ctx->eng.iec->mul(point, glen, key, olen, curve)) {
|
||||
return -BR_ERR_INVALID_ALGORITHM;
|
||||
}
|
||||
memcpy(ctx->eng.pad, point, glen);
|
||||
return (int)glen;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
\ =======================================================================
|
||||
|
||||
: addr-ctx:
|
||||
next-word { field }
|
||||
"addr-" field + 0 1 define-word
|
||||
0 8191 "offsetof(br_ssl_client_context, " field + ")" + make-CX
|
||||
postpone literal postpone ; ;
|
||||
|
||||
\ 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 ;
|
||||
|
||||
\ Test support for RSA signatures.
|
||||
cc: supports-rsa-sign? ( -- bool ) {
|
||||
T0_PUSHi(-(CTX->irsavrfy != 0));
|
||||
}
|
||||
|
||||
\ Test support for ECDSA signatures.
|
||||
cc: supports-ecdsa? ( -- bool ) {
|
||||
T0_PUSHi(-(CTX->iecdsa != 0));
|
||||
}
|
||||
|
||||
\ Length of Signatures extension.
|
||||
: ext-signatures-length ( -- len )
|
||||
supported-hash-functions { x } drop
|
||||
0
|
||||
supports-rsa-sign? if x + then
|
||||
supports-ecdsa? if x + then
|
||||
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 ;
|
||||
|
||||
\ Write handshake message: ClientHello
|
||||
: write-ClientHello ( -- )
|
||||
{ ; total-ext-length }
|
||||
|
||||
\ Compute length for extensions (without the general two-byte header)
|
||||
ext-reneg-length ext-sni-length + ext-frag-length +
|
||||
ext-signatures-length +
|
||||
ext-supported-curves-length + ext-point-format-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
|
||||
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 use increasing id order, which makes P-256
|
||||
\ the preferred curve.
|
||||
ext-supported-curves-length dup if
|
||||
0x000A write16 \ extension type (10)
|
||||
4 - dup write16 \ extension length
|
||||
2- write16 \ list length
|
||||
supported-curves 0
|
||||
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
|
||||
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 ;
|
||||
|
||||
\ 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 { suite ; found }
|
||||
\ 0 >found
|
||||
\ addr-suites_buf dup addr-suites_num get8 1 << +
|
||||
\ begin dup2 < while
|
||||
\ 2 - dup get16
|
||||
\ suite = found or >found
|
||||
\ repeat
|
||||
\ 2drop found ifnot ERR_BAD_CIPHER_SUITE fail then
|
||||
read16
|
||||
dup scan-suite 0< if ERR_BAD_CIPHER_SUITE fail then
|
||||
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 }
|
||||
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
|
||||
|
||||
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
|
||||
then
|
||||
close-elt
|
||||
resume
|
||||
;
|
||||
|
||||
cc: x509-start-chain ( expected-key-type -- ) {
|
||||
const br_x509_class *xc;
|
||||
|
||||
xc = *(ENG->x509ctx);
|
||||
xc->start_chain(ENG->x509ctx, T0_POP(), ENG->server_name);
|
||||
}
|
||||
|
||||
cc: x509-start-cert ( length -- ) {
|
||||
const br_x509_class *xc;
|
||||
|
||||
xc = *(ENG->x509ctx);
|
||||
xc->start_cert(ENG->x509ctx, T0_POP());
|
||||
}
|
||||
|
||||
cc: x509-append ( length -- ) {
|
||||
const br_x509_class *xc;
|
||||
size_t len;
|
||||
|
||||
xc = *(ENG->x509ctx);
|
||||
len = T0_POP();
|
||||
xc->append(ENG->x509ctx, ENG->pad, len);
|
||||
}
|
||||
|
||||
cc: x509-end-cert ( -- ) {
|
||||
const br_x509_class *xc;
|
||||
|
||||
xc = *(ENG->x509ctx);
|
||||
xc->end_cert(ENG->x509ctx);
|
||||
}
|
||||
|
||||
cc: x509-end-chain ( -- err ) {
|
||||
const br_x509_class *xc;
|
||||
|
||||
xc = *(ENG->x509ctx);
|
||||
T0_PUSH(xc->end_chain(ENG->x509ctx));
|
||||
}
|
||||
|
||||
\ Parse Certificate
|
||||
: read-Certificate ( -- )
|
||||
\ Get header, and check message type.
|
||||
read-handshake-header 11 = ifnot ERR_UNEXPECTED fail then
|
||||
|
||||
\ Start processing the chain through the X.509 engine.
|
||||
addr-cipher_suite get16 expected-key-type
|
||||
x509-start-chain
|
||||
|
||||
\ Total chain length is a 24-bit integer.
|
||||
read24 open-elt
|
||||
begin
|
||||
dup while
|
||||
read24 open-elt
|
||||
dup x509-start-cert
|
||||
|
||||
\ We read the certificate by chunks through the pad, so
|
||||
\ as to use the existing reading function (read-blob)
|
||||
\ that also ensures proper hashing.
|
||||
begin
|
||||
dup while
|
||||
dup 256 > if 256 else dup then { len }
|
||||
addr-pad len read-blob
|
||||
len x509-append
|
||||
repeat
|
||||
close-elt
|
||||
x509-end-cert
|
||||
repeat
|
||||
|
||||
\ We must close the chain AND the handshake message.
|
||||
close-elt
|
||||
close-elt
|
||||
|
||||
\ Chain processing is finished; get the error code.
|
||||
x509-end-chain
|
||||
dup if fail then drop
|
||||
;
|
||||
|
||||
\ 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 ;
|
||||
|
||||
\ Parse CertificateRequest. Header has already been read.
|
||||
: read-contents-CertificateRequest ( lim -- )
|
||||
\ TODO: implement client certificates. Right now, we simply
|
||||
\ drop the complete message.
|
||||
begin dup while read8 drop repeat drop ;
|
||||
|
||||
\ 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);
|
||||
}
|
||||
}
|
||||
|
||||
\ 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 ;
|
||||
|
||||
\ =======================================================================
|
||||
|
||||
\ Perform a handshake.
|
||||
: do-handshake ( -- )
|
||||
0 addr-application_data set8
|
||||
22 addr-record_type_out set8
|
||||
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
|
||||
|
||||
\ 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
|
||||
\ TODO: client certificate support.
|
||||
write-empty-Certificate
|
||||
then
|
||||
write-ClientKeyExchange
|
||||
|
||||
\ TODO: CertificateVerify
|
||||
|
||||
-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. Theoretically
|
||||
\ we could just ignore that, however if the
|
||||
\ server sent an HelloRequest then it is
|
||||
\ expecting a handshake and will wait for our
|
||||
\ ClientHello.
|
||||
addr-reneg get8 1 = if
|
||||
flush-record
|
||||
begin can-output? not while
|
||||
wait-co drop
|
||||
repeat
|
||||
100 send-warning
|
||||
else
|
||||
do-handshake
|
||||
then
|
||||
endof
|
||||
ERR_UNEXPECTED fail
|
||||
endcase
|
||||
again
|
||||
;
|
||||
Loading…
Add table
Add a link
Reference in a new issue