X-Git-Url: https://www.bearssl.org/gitweb//home/git/?p=BearSSL;a=blobdiff_plain;f=src%2Fssl%2Fssl_hs_server.t0;h=7f5fe85155bbbdcaa6f4032cf2aafc37650ba167;hp=c6bb939810aeef2fc5b94521cba74ac72978fe31;hb=b42bd5972f935ffc32019acac6f8a07ae08ae9c2;hpb=60126cafc85572a53d38752b8830e91c7ab18f88 diff --git a/src/ssl/ssl_hs_server.t0 b/src/ssl/ssl_hs_server.t0 index c6bb939..7f5fe85 100644 --- a/src/ssl/ssl_hs_server.t0 +++ b/src/ssl/ssl_hs_server.t0 @@ -140,6 +140,35 @@ do_ecdh(br_ssl_server_context *ctx, int prf_id, ecdh_common(ctx, prf_id, cpoint, cpoint_len, x); } +/* + * Do the full static ECDH key exchange. When this function is called, + * it has already been verified that the cipher suite uses ECDH (not ECDHE), + * and the client's public key (from its certificate) has type EC and is + * apt for key exchange. + */ +static void +do_static_ecdh(br_ssl_server_context *ctx, int prf_id) +{ + unsigned char cpoint[133]; + size_t cpoint_len; + const br_x509_class **xc; + const br_x509_pkey *pk; + + xc = ctx->eng.x509ctx; + pk = (*xc)->get_pkey(xc, NULL); + cpoint_len = pk->key.ec.qlen; + if (cpoint_len > sizeof cpoint) { + /* + * If the point is larger than our buffer then we need to + * restrict it. Length 2 is not a valid point length, so + * the ECDH will fail. + */ + cpoint_len = 2; + } + memcpy(cpoint, pk->key.ec.q, cpoint_len); + do_ecdh(ctx, prf_id, cpoint, cpoint_len); +} + /* * Do the ECDHE key exchange (part 1: generation of transient key, and * computing of the point to send to the client). Returned value is the @@ -258,6 +287,94 @@ do_ecdhe_part2(br_ssl_server_context *ctx, int prf_id, memset(ctx->ecdhe_key, 0, ctx->ecdhe_key_len); } +/* + * Offset for hash value within the pad (when obtaining all hash values, + * in preparation for verification of the CertificateVerify message). + * Order is MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512; last value + * is used to get the total length. + */ +static const unsigned char HASH_PAD_OFF[] = { 0, 16, 36, 64, 96, 144, 208 }; + +/* + * OID for hash functions in RSA signatures. + */ +static const unsigned char HASH_OID_SHA1[] = { + 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A +}; + +static const unsigned char HASH_OID_SHA224[] = { + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04 +}; + +static const unsigned char HASH_OID_SHA256[] = { + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 +}; + +static const unsigned char HASH_OID_SHA384[] = { + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 +}; + +static const unsigned char HASH_OID_SHA512[] = { + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 +}; + +static const unsigned char *HASH_OID[] = { + HASH_OID_SHA1, + HASH_OID_SHA224, + HASH_OID_SHA256, + HASH_OID_SHA384, + HASH_OID_SHA512 +}; + +/* + * Verify the signature in CertificateVerify. Returned value is 0 on + * success, or a non-zero error code. Lack of implementation of the + * designated signature algorithm is reported as a "bad signature" + * error (because it means that the peer did not honour our advertised + * set of supported signature algorithms). + */ +static int +verify_CV_sig(br_ssl_server_context *ctx, size_t sig_len) +{ + const br_x509_class **xc; + const br_x509_pkey *pk; + int id; + + id = ctx->hash_CV_id; + xc = ctx->eng.x509ctx; + pk = (*xc)->get_pkey(xc, NULL); + if (pk->key_type == BR_KEYTYPE_RSA) { + unsigned char tmp[64]; + const unsigned char *hash_oid; + + if (id == 0) { + hash_oid = NULL; + } else { + hash_oid = HASH_OID[id - 2]; + } + if (ctx->eng.irsavrfy == 0) { + return BR_ERR_BAD_SIGNATURE; + } + if (!ctx->eng.irsavrfy(ctx->eng.pad, sig_len, + hash_oid, ctx->hash_CV_len, &pk->key.rsa, tmp) + || memcmp(tmp, ctx->hash_CV, ctx->hash_CV_len) != 0) + { + return BR_ERR_BAD_SIGNATURE; + } + } else { + if (ctx->eng.iecdsa == 0) { + return BR_ERR_BAD_SIGNATURE; + } + if (!ctx->eng.iecdsa(ctx->eng.iec, + ctx->hash_CV, ctx->hash_CV_len, + &pk->key.ec, ctx->eng.pad, sig_len)) + { + return BR_ERR_BAD_SIGNATURE; + } + } + return 0; +} + } \ ======================================================================= @@ -268,7 +385,6 @@ do_ecdhe_part2(br_ssl_server_context *ctx, int prf_id, 0 8191 "offsetof(br_ssl_server_context, " field + ")" + make-CX postpone literal postpone ; ; -addr-ctx: flags addr-ctx: client_max_version addr-ctx: client_suites addr-ctx: client_suites_num @@ -282,10 +398,6 @@ addr-ctx: sign_hash_id addr-client_suites CX 0 1023 { BR_MAX_CIPHER_SUITES * sizeof(br_suite_translated) } ; -\ Check a server flag by index. -: flag? ( index -- bool ) - addr-flags get32 swap >> 1 and neg ; - \ Read the client SNI extension. : read-client-sni ( lim -- lim ) \ Open extension value. @@ -391,26 +503,7 @@ cc: set-max-frag-len ( len -- ) { \ Open extension value. read16 open-elt - \ Clear list of supported signature algorithms. - 0 addr-hashes set16 - - \ Get list of algorithms length. - read16 open-elt - begin dup while - read8 { hash } read8 { sign } - \ We keep the value if the signature is either 1 (RSA) or - \ 3 (ECDSA), and the hash is one of the SHA-* functions - \ (2 to 6, from SHA-1 to SHA-512). Note that we reject - \ any use of MD5. Also, we do not keep track of the client - \ preferences. - hash 2 >= hash 6 <= and - sign 1 = sign 3 = or - and if - addr-hashes get16 - 1 sign 1- 2 << hash + << or addr-hashes set16 - then - repeat - close-elt + read-list-sign-algos addr-hashes set16 \ Close extension value. close-elt ; @@ -445,8 +538,8 @@ cc: call-policy-handler ( -- bool ) { CTX->policy_vtable, CTX, &choices); ENG->session.cipher_suite = choices.cipher_suite; CTX->sign_hash_id = choices.hash_id; - CTX->chain = choices.chain; - CTX->chain_len = choices.chain_len; + ENG->chain = choices.chain; + ENG->chain_len = choices.chain_len; T0_PUSHi(-(x != 0)); } @@ -492,7 +585,7 @@ cc: save-session ( -- ) { check-resume { resume } \ Cipher suites. We read all cipher suites from client, each time - \ matching against our own list. We accumulare suites in the + \ matching against our own list. We accumulate suites in the \ client_suites[] context buffer: we keep suites that are \ supported by both the client and the server (so the list size \ cannot exceed that of the server list), and we keep them in @@ -526,6 +619,17 @@ cc: save-session ( -- ) { -1 >reneg-scsv then + \ Special handling for TLS_FALLBACK_SCSV. If the client + \ maximum version is less than our own maximum version, + \ then this is an undue downgrade. We mark it by setting + \ the client max version to 0x10000. + dup 0x5600 = if + client-version-max addr-version_min get16 >= + client-version-max addr-version_max get16 < and if + -1 >client-version-max + then + then + \ Test whether the suite is supported by the server. scan-suite dup 0< if \ We do not support this cipher suite. Note @@ -626,6 +730,13 @@ cc: save-session ( -- ) { \ 0x0300 (SSL-3.0), then fail. Otherwise, we may at least send an \ alert with that version. We still reject versions lower than our \ configured minimum. + \ As a special case, in case of undue downgrade, we send a specific + \ alert (see RFC 7507). Note that this case may happen only if + \ we would otherwise accept the client's version. + client-version-max 0< if + addr-client_max_version get16 addr-version_out set16 + 86 fail-alert + then addr-version_max get16 dup client-version-max > if drop client-version-max then dup 0x0300 < if ERR_BAD_VERSION fail then @@ -654,11 +765,14 @@ cc: save-session ( -- ) { ok-compression ifnot 40 fail-alert then \ Filter hash function support by what the server also supports. - \ If no common hash function remains, then ECDHE suites are not - \ possible. + \ If no common hash function remains with RSA and/or ECDSA, then + \ the corresponding ECDHE suites are not possible. supported-hash-functions drop 257 * addr-hashes get16 and dup addr-hashes set16 - 0<> { can-ecdhe } + \ In 'can-ecdhe', bit 12 is set if ECDHE_RSA is possible, bit 13 is + \ set if ECDHE_ECDSA is possible. + dup 0xFF and 0<> neg + swap 8 >> 0<> 2 and or 12 << { can-ecdhe } \ Filter supported curves. If there is no common curve between \ client and us, then ECDHE suites cannot be used. Note that we @@ -671,21 +785,27 @@ cc: save-session ( -- ) { resume if -1 ret then \ We are not resuming, so a new session ID should be generated. + \ We don't check that the new ID is distinct from the one sent + \ by the client because probability of such an event is 2^(-256), + \ i.e. much (much) lower than that of an undetected transmission + \ error or hardware miscomputation, and with similar consequences + \ (handshake simply fails). addr-session_id 32 mkrand + 32 addr-session_id_len set8 \ Translate common cipher suites, then squeeze out holes: there \ may be holes because of the way we fill the list when the \ server preference order is enforced, and also in case some \ suites are filtered out. In particular: \ -- ECDHE suites are removed if there is no common hash function - \ (for signatures) or no common curve. + \ (for the relevant signature algorithm) or no common curve. \ -- TLS-1.2-only suites are removed if the negociated version is \ TLS-1.1 or lower. addr-client_suites dup >css-off begin dup css-max < while dup get16 dup cipher-suite-to-elements - can-ecdhe ifnot - dup 12 >> dup 1 = swap 2 = or if + dup 12 >> dup 1 = swap 2 = or if + dup can-ecdhe and ifnot 2drop 0 dup then then @@ -777,63 +897,6 @@ cc: save-session ( -- ) { drop then ; -\ Compute total chain length. This includes the individual certificate -\ headers, but not the total chain header. This also sets the cert_cur, -\ cert_len and chain_len context fields. -cc: total-chain-length ( -- len ) { - size_t u; - uint32_t total; - - total = 0; - for (u = 0; u < CTX->chain_len; u ++) { - total += 3 + (uint32_t)CTX->chain[u].data_len; - } - T0_PUSH(total); -} - -\ Get length for current certificate in the chain; if the chain end was -\ reached, then this returns -1. -cc: begin-cert ( -- len ) { - if (CTX->chain_len == 0) { - T0_PUSHi(-1); - } else { - CTX->cert_cur = CTX->chain->data; - CTX->cert_len = CTX->chain->data_len; - CTX->chain ++; - CTX->chain_len --; - T0_PUSH(CTX->cert_len); - } -} - -\ Copy a chunk of certificate data into the pad. Returned value is the -\ chunk length, or 0 if the certificate end is reached. -cc: copy-cert-chunk ( -- len ) { - size_t clen; - - clen = CTX->cert_len; - if (clen > sizeof ENG->pad) { - clen = sizeof ENG->pad; - } - memcpy(ENG->pad, CTX->cert_cur, clen); - CTX->cert_cur += clen; - CTX->cert_len -= clen; - T0_PUSH(clen); -} - -\ Write the server Certificate. -: write-Certificate ( -- ) - 11 write8 - total-chain-length - dup 3 + write24 write24 - begin - begin-cert - dup 0< if drop ret then write24 - begin copy-cert-chunk dup while - addr-pad swap write-blob - repeat - drop - again ; - \ Do the first part of ECDHE. Returned value is the computed signature \ length, or a negative error code on error. cc: do-ecdhe-part1 ( curve -- len ) { @@ -886,6 +949,166 @@ cc: do-ecdhe-part1 ( curve -- len ) { sig-len write16 addr-pad sig-len write-blob ; +\ Get length of the list of anchor names to send to the client. The length +\ includes the per-name 2-byte header, but _not_ the 2-byte header for +\ the list itself. If no client certificate is requested, then this +\ returns 0. +cc: ta-names-total-length ( -- len ) { + size_t u, len; + + len = 0; + if (CTX->ta_names != NULL) { + for (u = 0; u < CTX->num_tas; u ++) { + len += CTX->ta_names[u].len + 2; + } + } else if (CTX->tas != NULL) { + for (u = 0; u < CTX->num_tas; u ++) { + len += CTX->tas[u].dn.len + 2; + } + } + T0_PUSH(len); +} + +\ Compute length and optionally write the contents of the list of +\ supported client authentication methods. +: write-list-auth ( do_write -- len ) + 0 + addr-cipher_suite get16 use-ecdh? if + 2+ over if 65 write8 66 write8 then + then + supports-rsa-sign? if 1+ over if 1 write8 then then + supports-ecdsa? if 1+ over if 64 write8 then then + swap drop ; + +: write-signhash-inner2 ( dow algo hashes len id -- dow algo hashes len ) + { id } + over 1 id << and ifnot ret then + 2+ + 3 pick if id write8 2 pick write8 then ; + +: write-signhash-inner1 ( dow algo hashes -- dow len ) + 0 + 4 write-signhash-inner2 + 5 write-signhash-inner2 + 6 write-signhash-inner2 + 3 write-signhash-inner2 + 2 write-signhash-inner2 + -rot 2drop ; + +\ Compute length and optionally write the contents of the list of +\ supported sign+hash algorithms. +: write-list-signhash ( do_write -- len ) + 0 { len } + \ If supporting neither RSA nor ECDSA in the engine, then we + \ will do only static ECDH, and thus we claim support for + \ everything (for the X.509 validator). + supports-rsa-sign? supports-ecdsa? or ifnot + 1 0x7C write-signhash-inner1 >len + 3 0x7C write-signhash-inner1 len + + swap drop ret + then + supports-rsa-sign? if + 1 supported-hash-functions drop + write-signhash-inner1 >len + then + supports-ecdsa? if + 3 supported-hash-functions drop + write-signhash-inner1 len + >len + then + drop len ; + +\ Initialise index for sending the list of anchor DN. +cc: begin-ta-name-list ( -- ) { + CTX->cur_dn_index = 0; +} + +\ Switch to next DN in the list. Returned value is the DN length, or -1 +\ if the end of the list was reached. +cc: begin-ta-name ( -- len ) { + const br_x500_name *dn; + if (CTX->cur_dn_index >= CTX->num_tas) { + T0_PUSHi(-1); + } else { + if (CTX->ta_names == NULL) { + dn = &CTX->tas[CTX->cur_dn_index].dn; + } else { + dn = &CTX->ta_names[CTX->cur_dn_index]; + } + CTX->cur_dn_index ++; + CTX->cur_dn = dn->data; + CTX->cur_dn_len = dn->len; + T0_PUSH(CTX->cur_dn_len); + } +} + +\ Copy a chunk of the current DN into the pad. Returned value is the +\ chunk length; this is 0 when the end of the current DN is reached. +cc: copy-dn-chunk ( -- len ) { + size_t clen; + + clen = CTX->cur_dn_len; + if (clen > sizeof ENG->pad) { + clen = sizeof ENG->pad; + } + memcpy(ENG->pad, CTX->cur_dn, clen); + CTX->cur_dn += clen; + CTX->cur_dn_len -= clen; + T0_PUSH(clen); +} + +\ Write a CertificateRequest message. +: write-CertificateRequest ( -- ) + \ The list of client authentication types includes: + \ rsa_sign (1) + \ ecdsa_sign (64) + \ rsa_fixed_ecdh (65) + \ ecdsa_fixed_ecdh (66) + \ rsa_sign and ecdsa_sign require, respectively, RSA and ECDSA + \ support. Static ECDH requires that the cipher suite is ECDH. + \ When we ask for static ECDH, we always send both rsa_fixed_ecdh + \ and ecdsa_fixed_ecdh because what matters there is what the + \ X.509 engine may support, and we do not control that. + \ + \ With TLS 1.2, we must also send a list of supported signature + \ and hash algorithms. That list is supposed to qualify both + \ the engine itself, and the X.509 validator, which are separate + \ in BearSSL. There again, we use the engine capabilities in that + \ list, and resort to a generic all-support list if only + \ static ECDH is accepted. + \ + \ (In practice, client implementations tend to have at most one + \ or two certificates, and send the chain regardless of what + \ algorithms are used in it.) + + 0 write-list-auth + addr-version get16 0x0303 >= if + 2+ 0 write-list-signhash + + then + ta-names-total-length + 3 + + + \ Message header + 13 write8 write24 + + \ List of authentication methods + 0 write-list-auth write8 1 write-list-auth drop + + \ For TLS 1.2+, list of sign+hash + addr-version get16 0x0303 >= if + 0 write-list-signhash write16 1 write-list-signhash drop + then + + \ Trust anchor names + ta-names-total-length write16 + begin-ta-name-list + begin + begin-ta-name + dup 0< if drop ret then write16 + begin copy-dn-chunk dup while + addr-pad swap write-blob + repeat + drop + again ; + \ Write the Server Hello Done message. : write-ServerHelloDone ( -- ) 14 write8 0 write24 ; @@ -913,11 +1136,18 @@ cc: do-ecdhe-part2 ( len prf_id -- ) { do_ecdhe_part2(CTX, prf_id, ENG->pad, len); } -\ Read the Client Key Exchange. -: read-ClientKeyExchange ( -- ) - \ Get header, and check message type. - read-handshake-header 16 = ifnot ERR_UNEXPECTED fail then +\ Perform static ECDH. The point from the client is the public key +\ extracted from its certificate. +cc: do-static-ecdh ( prf_id -- ) { + do_static_ecdh(CTX, T0_POP()); +} + +\ Read a ClientKeyExchange header. +: read-ClientKeyExchange-header ( -- len ) + read-handshake-header 16 = ifnot ERR_UNEXPECTED fail then ; +\ Read the Client Key Exchange contents (non-empty case). +: read-ClientKeyExchange-contents ( lim -- ) \ What we should get depends on the cipher suite. addr-cipher_suite get16 use-rsa-keyx? if \ RSA key exchange: we expect a RSA-encrypted value. @@ -937,6 +1167,116 @@ cc: do-ecdhe-part2 ( len prf_id -- ) { then close-elt ; +\ Read the Client Key Exchange (normal case). +: read-ClientKeyExchange ( -- ) + read-ClientKeyExchange-header + read-ClientKeyExchange-contents ; + +\ Obtain all possible hash values for handshake messages so far. This +\ is done because we need the hash value for the CertificateVerify +\ _before_ knowing which hash function will actually be used, as this +\ information is obtained from decoding the message header itself. +\ All hash values are stored in the pad (208 bytes in total). +cc: compute-hash-CV ( -- ) { + int i; + + for (i = 1; i <= 6; i ++) { + br_multihash_out(&ENG->mhash, i, + ENG->pad + HASH_PAD_OFF[i - 1]); + } +} + +\ Copy the proper hash value from the pad into the dedicated buffer. +\ Returned value is true (-1) on success, false (0) on error (error +\ being an unimplemented hash function). The id has already been verified +\ to be either 0 (for MD5+SHA-1) or one of the SHA-* functions. +cc: copy-hash-CV ( hash_id -- bool ) { + int id = T0_POP(); + size_t off, len; + + if (id == 0) { + off = 0; + len = 36; + } else { + if (br_multihash_getimpl(&ENG->mhash, id) == 0) { + T0_PUSH(0); + T0_RET(); + } + off = HASH_PAD_OFF[id - 1]; + len = HASH_PAD_OFF[id] - off; + } + memcpy(CTX->hash_CV, ENG->pad + off, len); + CTX->hash_CV_len = len; + CTX->hash_CV_id = id; + T0_PUSHi(-1); +} + +\ Verify signature in CertificateVerify. Output is 0 on success, or a +\ non-zero error code. +cc: verify-CV-sig ( sig-len -- err ) { + int err; + + err = verify_CV_sig(CTX, T0_POP()); + T0_PUSHi(err); +} + +\ Process static ECDH. +: process-static-ECDH ( ktu -- ) + \ Static ECDH is allowed only if the cipher suite uses ECDH, and + \ the client's public key has type EC and allows key exchange. + \ BR_KEYTYPE_KEYX is 0x10, and BR_KEYTYPE_EC is 2. + 0x1F and 0x12 = ifnot ERR_WRONG_KEY_USAGE fail then + addr-cipher_suite get16 + dup use-ecdh? ifnot ERR_UNEXPECTED fail then + prf-id + do-static-ecdh ; + +\ Read CertificateVerify header. +: read-CertificateVerify-header ( -- lim ) + compute-hash-CV + read-handshake-header 15 = ifnot ERR_UNEXPECTED fail then ; + +\ Read CertificateVerify. The client key type + usage is expected on the +\ stack. +: read-CertificateVerify ( ktu -- ) + \ Check that the key allows for signatures. + dup 0x20 and ifnot ERR_WRONG_KEY_USAGE fail then + 0x0F and { key-type } + + \ Get header. + read-CertificateVerify-header + + \ With TLS 1.2+, there is an explicit hash + signature indication, + \ which must be compatible with the key type. + addr-version get16 0x0303 >= if + \ Get hash function, then signature algorithm. The + \ signature algorithm is 1 (RSA) or 3 (ECDSA) while our + \ symbolic constants for key types are 1 (RSA) or 2 (EC). + read16 + dup 0xFF and 1+ 1 >> key-type = ifnot + ERR_BAD_SIGNATURE fail + then + 8 >> + + \ We support only SHA-1, SHA-224, SHA-256, SHA-384 + \ and SHA-512. We explicitly reject MD5. + dup 2 < over 6 > or if ERR_INVALID_ALGORITHM fail then + else + \ With TLS 1.0 and 1.1, hash is MD5+SHA-1 (0) for RSA, + \ SHA-1 (2) for ECDSA. + key-type 0x01 = if 0 else 2 then + then + copy-hash-CV ifnot ERR_INVALID_ALGORITHM fail then + + \ Read signature. + read16 dup { sig-len } + dup 512 > if ERR_LIMIT_EXCEEDED fail then + addr-pad swap read-blob + sig-len verify-CV-sig + dup if fail then drop + + close-elt ; + \ Send a HelloRequest. : send-HelloRequest ( -- ) flush-record @@ -960,11 +1300,54 @@ cc: do-ecdhe-part2 ( len prf_id -- ) { else \ Not a session resumption write-ServerHello - write-Certificate + write-Certificate drop write-ServerKeyExchange + ta-names-total-length if + write-CertificateRequest + then write-ServerHelloDone flush-record - read-ClientKeyExchange + + \ If we sent a CertificateRequest then we expect a + \ Certificate message. + ta-names-total-length if + \ Read client certificate. + 0 read-Certificate + + choice + dup 0< uf + \ Client certificate validation failed. + 2 flag? ifnot neg fail then + drop + read-ClientKeyExchange + read-CertificateVerify-header + dup skip-blob drop + enduf + dup 0= uf + \ Client sent no certificate at all. + drop + 2 flag? ifnot + ERR_NO_CLIENT_AUTH fail + then + read-ClientKeyExchange + enduf + + \ Client certificate was validated. + read-ClientKeyExchange-header + dup ifnot + \ Empty ClientKeyExchange. + drop + process-static-ECDH + else + read-ClientKeyExchange-contents + read-CertificateVerify + then + endchoice + else + \ No client certificate request, we just expect + \ a non-empty ClientKeyExchange. + read-ClientKeyExchange + then 0 read-CCS-Finished 0 write-CCS-Finished save-session @@ -993,18 +1376,17 @@ cc: do-ecdhe-part2 ( len prf_id -- ) { endof 0x01 of \ Reject renegotiations if the peer does not - \ support secure renegotiation. As allowed - \ by RFC 5246, we do not send a - \ no_renegotiation alert and just ignore the - \ HelloRequest. + \ support secure renegotiation, or if the + \ "no renegotiation" flag is set. drop - addr-reneg get8 1 <> if - 0 do-handshake - else + addr-reneg get8 1 = 1 flag? or if flush-record begin can-output? not while wait-co drop repeat + 100 send-warning + else + 0 do-handshake then endof ERR_UNEXPECTED fail