(**************************************************************************)
(*                                                                        *)
(*  Transport Layer Security                                              *)
(*  Copyright (C) 2025   Peter Moylan                                     *)
(*                                                                        *)
(*  This program is free software: you can redistribute it and/or modify  *)
(*  it under the terms of the GNU General Public License as published by  *)
(*  the Free Software Foundation, either version 3 of the License, or     *)
(*  (at your option) any later version.                                   *)
(*                                                                        *)
(*  This program is distributed in the hope that it will be useful,       *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
(*  GNU General Public License for more details.                          *)
(*                                                                        *)
(*  You should have received a copy of the GNU General Public License     *)
(*  along with this program.  If not, see <http://www.gnu.org/licenses/>. *)
(*                                                                        *)
(*  To contact author:   http://www.pmoylan.org   peter@pmoylan.org       *)
(*                                                                        *)
(**************************************************************************)

IMPLEMENTATION MODULE TLSHandshake;

        (********************************************************)
        (*                                                      *)
        (*                TLS Handshake Protocol                *)
        (*                                                      *)
        (*  Programmer:         P. Moylan                       *)
        (*  Started:            3 July 2023                     *)
        (*  Last edited:        17 December 2025                *)
        (*  Status:             Mostly OK                       *)
        (*                                                      *)
        (*  Shortcomings:                                       *)
        (*      Some handshaking record types are not           *)
        (*        implemented, because I have not needed them.  *)
        (*                                                      *)
        (********************************************************)

(************************************************************************)
(*  NOTE ABOUT VERSIONS                                                 *)
(*  Version numbering continues on from the SSL version numbers.        *)
(*  Thus we have:                                                       *)
(*          3.0 or lower    SSL versions pre-TLS                        *)
(*          3.1             TSL version 1.0                             *)
(*          3.2             TSL version 1.1                             *)
(*          3.3             TSL version 1.2                             *)
(*  The present code supports only 3.3.                                 *)
(************************************************************************)


FROM STextIO IMPORT                 (* for debugging *)
    (* proc *)  WriteChar, WriteString, WriteLn;

IMPORT Strings, RSAKeys, BigNum, RSA, TLSCertificates, TLSRecord, TLSsessID, OS2;

FROM SYSTEM IMPORT CAST, CARD8, CARD16, ADDRESS, ADR, LOC;

FROM BigNum IMPORT
    (* type *)  BN;

FROM VarStrings IMPORT
    (* type *)  ByteStringPtr, ByteStr,
    (* proc *)  MakeBS, DiscardBS;

FROM Names IMPORT
    (* type *)  HostName, DomainName;

FROM TLSAlerts IMPORT
    (* proc *)  LogAlert, CreateAlert;

FROM TLSBase IMPORT
    (* type *)  TextType,
    (* proc *)  NYI, WriteHex, AppendHex, AppendHex4, WriteHexString, AppendHexString,
                Get3Bytes, Put3Bytes, Checksize, Resize, GenerateRandom,
                GenerateRandom32, DiscardFragment;

FROM TLSsessID IMPORT
    (* type *)  sessIDtype,
    (* proc *)  GenerateSessionID, DeleteSessID, SaveSessionParams,
                RestoreSession, AppendSessID, LastSavedID;

FROM TLSRecord IMPORT
    (* type *)  RLState,
    (* proc *)  TLSRecordInit, TLSRecordClose, FirstNBytes, GetFragment, PutFragment,
                RecordTraceOn, RecordTraceOff;

FROM TLSPRF IMPORT
    (* type *)  PRFAlgorithm,
    (* proc *)  PRF;

FROM TLSCipherSuites IMPORT
    (* const*)  NullSuite,
    (* type *)  CipherSuite,
    (* proc *)  NameOfSuite, AcceptCipherSuite, CodeToCipherSuite, CipherSuiteToCode,
                SuiteEnabled, KeyBlockSize, Commit;

FROM TLSCompression IMPORT
    (* type *)  CompressionMethod,
    (* proc *)  CompressionSupported;

FROM TLSCertificates IMPORT
    (* proc *)  GetPrivateKey, AssembleCertificateChain, AcceptCertificates;

FROM RSAKeys IMPORT
    (* type *)  RSAKeyType,
    (* proc *)  InitKey, DiscardKey;

FROM TLSRSA IMPORT
    (* proc *)  PKCS1Encrypt;

FROM SHA2 IMPORT
    (* type *)  SHA2_CTX,
    (* proc *)  SHA256Init, SHA2Update, SHA2Final;

FROM Domains IMPORT
    (* type *)  Domain,
    (* proc *)  NameToDomain, NameOfDomain;

FROM INIData IMPORT
    (* type *)  HINI,
    (* proc *)  OpenINIFile, INIValid, CloseINIFile, INIGetString;

FROM TransLog IMPORT
    (* type *)  TransactionLogID,
    (* proc *)  LogTransaction, LogTransactionL;

FROM FileOps IMPORT
    (* type *)  ChanId, FilenameString,
    (* proc *)  OpenOldFile, CloseFile, ReadRaw, ReadLine;

FROM MiscFuncs IMPORT
    (* proc *)  AppendCard;

FROM Inet2Misc IMPORT
    (* proc *)  Swap2;

FROM Sockets IMPORT
    (* type *)  Socket,
    (* proc *)  recv, soclose;

FROM TaskControl IMPORT
    (* type *)  Lock,
    (* proc *)  CreateLock, Obtain, Release;

FROM LowLevel IMPORT
    (* proc *)  Copy, EVAL, AddOffset;

FROM Storage IMPORT
    (* proc *)  ALLOCATE, DEALLOCATE;

(************************************************************************)

TYPE
    Byte32 = ARRAY [0..31] OF CARD8;
    Byte48 = ARRAY [0..47] OF CARD8;

    HSstate = POINTER TO HandshakeState;

CONST
    Nul = CHR(0);
    CtrlZ = CHR(26);
    NILDomain = CAST (Domain, NIL);
    SSLversionMajor = 3;
    SSLversionMinor = 3;

    Zero32 = Byte32 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

    Zero48 = Byte48 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

    LogCipherSuites = FALSE;

TYPE
    SecurityParameters =
            RECORD
                prf_algorithm:          PRFAlgorithm;      (* USED to generate keyblock *)
                compression_algorithm:  CompressionMethod; (* set during handshake *)
                master_secret:          Byte48;            (* calculated and used *)
                client_random:          Byte32;            (* supplied by client. used *)
                server_random:          Byte32;            (* generated, sent to client *)
                client_premaster:       Byte48;            (* supplied by client *)

                (* Extra fields not mentioned in the standard. *)

                pending:                CipherSuite;
                keyblock:               ARRAY [0..255] OF CARD8;   (* USED in this module *)

            END (*RECORD*);

    HandshakeState =  RECORD
                          IsServer:   BOOLEAN;
                          bornagain:  BOOLEAN;
                          truncateMAC: BOOLEAN;
                          sock:       Socket;
                          logID:      TransactionLogID;
                          sessID:     sessIDtype;
                          sessIDlength: CARDINAL;
                          security:   SecurityParameters;
                          rlstate:    RLState;
                          hashctx1,
                           hashctx2:  SHA2_CTX;
                          serverkey:  RSAKeyType;
                          domain:     Domain;
                          serveraddr: CARDINAL;
                      END (*RECORD*);

    (* bornagain is TRUE iff an old session is being resumed.           *)

    (* If we are running as a client, sessID is first the ID we are     *)
    (* asking for.  Then, in both server and client cases, a definite   *)
    (* sessID is established by the ServerHello.                        *)

    (* If we are acting as a server, serveraddr is zero, and domain is  *)
    (* the domain the client is asking for. If we are acting as a       *)
    (* client, state^.domain is NIL, and serveraddr is the IP address   *)
    (* of the server we are connecting to.                              *)

VAR ININame: ARRAY [0..31] OF CHAR;
    BlankDomainLegal: BOOLEAN;
    encryptlock: Lock;

(************************************************************************)

PROCEDURE SetBlankDomainLegal;

    (* Makes it legal for a ClientHello handshake message to not        *)
    (* specify a server domain name.  This can be done if the server    *)
    (* uses a single certificate to cover all domains.                  *)

    BEGIN
        BlankDomainLegal := TRUE;
    END SetBlankDomainLegal;

(************************************************************************)
(*                            DEBUGGING STUFF                           *)
(************************************************************************)

(*
PROCEDURE WriteAscString (VAR (*IN*) buf: ARRAY OF CARD8;  pos, N: CARDINAL);

    (* Like WriteHexString, but we interpret the bytes as characters.  *)

    VAR j: CARDINAL;

    BEGIN
        FOR j := 0 TO N-1 DO
            WriteChar (CHR(buf[pos]));  INC(pos);
        END (*FOR*);
    END WriteAscString;
*)

(************************************************************************)

(*
PROCEDURE AppendAscString (VAR (*IN*) buf: ARRAY OF CARD8;  pos, N: CARDINAL;
                            VAR (*INOUT*) message: ARRAY OF CHAR);

    (* Appends N  characters from buf[pos] to message.  *)

    VAR substr: ARRAY [0..255] OF CHAR;

    BEGIN
        Copy (ADR(buf[pos]), ADR(substr), N);
        substr[N] := Nul;
        Strings.Append (substr, message);
    END AppendAscString;
*)

(************************************************************************)

(*
PROCEDURE WriteCard (N: CARDINAL);

    (* Write N in decimal.  *)

    BEGIN
        IF N > 9 THEN
            WriteCard (N DIV 10);
            N := N MOD 10;
        END (*IF*);
        WriteChar (CHR(ORD('0') + N));
    END WriteCard;
*)

(************************************************************************)

PROCEDURE BEval  (VAR (*IN*) buf: ARRAY OF CARD8;  pos, N: CARDINAL): CARDINAL;

    (* Treats the next N bytes, starting at buf[pos], as bigendian.  *)

    VAR result, k: CARDINAL;

    BEGIN
        result := 0;
        FOR k := 0 TO N-1 DO
            IF pos <= HIGH(buf) THEN
                result := 256*result + buf[pos];
                INC (pos);
            END (*IF*);
        END (*FOR*);
        RETURN result;
    END BEval;

(************************************************************************)
(*                          GENERATE KEY BLOCK                          *)
(************************************************************************)

PROCEDURE GenerateKeyBlock (state: HSstate);

    (* Calculates the block of key information required for a pending   *)
    (* change of cipher suite.  This can be done once ClientKeyExchange *)
    (* (or a session restore) has supplied the master secret.  In the   *)
    (* case of a session restore, we can call this function once we     *)
    (* have the client_random and server_random values, plus of course  *)
    (* the master_secret.                                               *)

    VAR seed: ARRAY [0..63] OF CARD8;
        message: ARRAY [0..128] OF CHAR;
        blocksize: CARDINAL;

    BEGIN
        (* Note that the seed for this calculation is the same as for   *)
        (* the master secret except that the order is swapped.  This is *)
        (* not an error, even though it looks like one.                 *)

        blocksize := KeyBlockSize (state^.security.pending);
        Copy (ADR(state^.security.server_random), ADR(seed), 32);
        Copy (ADR(state^.security.client_random), ADR(seed[32]), 32);
        PRF (state^.security.prf_algorithm,
                48, state^.security.master_secret,
                "key expansion", 64, seed,
                 blocksize, state^.security.keyblock);

        (* Save the master secret for possible future use. *)

        IF NOT state^.bornagain THEN
            SaveSessionParams (state^.serveraddr, state^.sessID,
                                state^.sessIDlength, state^.security.pending,
                                  state^.security.master_secret);
            message := "saved session with ID ";
            AppendSessID (state^.sessID, state^.sessIDlength, message);
            LogTransaction (state^.logID, message);
        END (*IF*);

    END GenerateKeyBlock;

(************************************************************************)
(*                         CHANGE CIPHER SPEC                           *)
(************************************************************************)

PROCEDURE ChangeCipherSpec (state: HSstate;  forinput: BOOLEAN);

    (* Commits the new encryption details that have been negotiated *)
    (* by handshaking.                                              *)

    VAR server_write: BOOLEAN;

    BEGIN
        (* Commit the keys. *)

        server_write := state^.IsServer <> forinput;
        Commit (state^.rlstate, state^.security.pending,
                        server_write, state^.truncateMAC,
                         state^.security.keyblock);
        (*
        message := "Have changed ";
        IF forinput THEN
            Strings.Append ("input", message);
        ELSE
            Strings.Append ("output", message);
        END (*IF*);
        Strings.Append (" cipher spec", message);
        LogTransaction (state^.logID, message);
        *)

    END ChangeCipherSpec;

(************************************************************************)

PROCEDURE SendChangeCipherSpec (state: HSstate): BOOLEAN;

    (* Sends a ChangeCipherSpec to the other partner, and also does *)
    (* the change at our end.                                       *)

    VAR buffer: TextType;  success: BOOLEAN;

    BEGIN
        (*LogTransactionL (state^.logID, "Sending ChangeCipherSpec");*)
        buffer.contenttype := 20;   (* type 20 means "change cipher spec" *)
        buffer.majversion := SSLversionMajor;
        buffer.minversion := SSLversionMinor;
        buffer.length := Swap2 (1);        (* convert to BigEndian *)
        ALLOCATE (buffer.fragment, 1);
        buffer.fragment^[0] := 1;
        success := PutFragment (state^.rlstate, state^.logID, buffer);
        DiscardFragment (buffer);    (* NEEDED? *)
        (*LogTransactionL (state^.logID, "Have sent ChangeCipherSpec");*)
        ChangeCipherSpec (state, FALSE);
        RETURN success;
    END SendChangeCipherSpec;

(************************************************************************)
(*                            HANDSHAKES                                *)
(************************************************************************)

PROCEDURE SendHandshake (state: HSstate;  type: CARD8;
                                VAR (*INOUT*) content: ByteStringPtr;
                                  length: CARDINAL): BOOLEAN;

    (* A handshake record starts with code 22 to mean "handshake", then *)
    (* we have a two-byte version code and a two-byte overall length    *)
    (* Then we have the "fragment" field with                           *)
    (*      HandshakeType (1 byte)                                      *)
    (*      length        (3 bytes)                                     *)
    (*      further data  as supplied by the caller                     *)
    (* For efficiency the caller must provide the space for the four    *)
    (* initial bytes, which we will fill in, in the content^ record.    *)
    (* The length parameter supplied by the caller includes counting    *)
    (* these initial four bytes.                                        *)
    (* We deallocate the caller data before return.                     *)

    VAR buffer: TextType;
        (*logmess: ARRAY [0..255] OF CHAR;*)
        j, L0: CARDINAL;  success: BOOLEAN;

    BEGIN

        buffer.contenttype := 22;   (* type 22 means "handshake" *)
        buffer.majversion := SSLversionMajor;
        buffer.minversion := SSLversionMinor;

        (* To embed the handshake record into something suitable to     *)
        (* pass to the record layer, we have to prepend a one-byte      *)
        (* handshake type and a 3-byte length field.  At present I'm    *)
        (* doing this by requiring the caller to leave the first four   *)
        (* bytes in content^ free for our use.  There must be a better  *)
        (* way to handle a layered protocol, but for now I'll use the   *)
        (* crude obvious approach.                                      *)

        buffer.length := Swap2 (length);        (* convert to BigEndian *)
        L0 := length - 4;
        content^[0] := type;
        FOR j := 3 TO 1 BY -1 DO
            content^[j] := L0 MOD 256;
            L0 := L0 DIV 256;
        END (*FOR*);

        IF type = 11 THEN
            (*
            LogTransactionL (state^.logID, "Sending certificate chain. First few bytes of output are");
            logmess := "";
            j := 32;
            IF j > length THEN
                j := length;
            END (*IF*);
            AppendHexString (content^, 0, j, TRUE, logmess);
            LogTransaction (state^.logID, logmess);
            *)
        END (*IF*);

        buffer.fragment := content;
        content := NIL;
        IF length > 0 THEN
            IF type = 20 THEN
                (* Special case.  We have to include it it the hash if  *)
                (* this is the first handshake we have encountered,     *)
                (* either in or out, but not otherwise.  Note that we   *)
                (* have already consumed at least one of the hashes by  *)
                (* the time we reach this point in the code.            *)
                IF CAST (ADDRESS, state^.hashctx2) <> NIL THEN
                    SHA2Update (state^.hashctx2, buffer.fragment^, length);
                END (*IF*);
            ELSIF type <> 0 THEN
                SHA2Update (state^.hashctx1, buffer.fragment^, length);
                SHA2Update (state^.hashctx2, buffer.fragment^, length);
            END (*IF*);
        END (*IF*);
        success := PutFragment (state^.rlstate, state^.logID, buffer);
        LogTransactionL (state^.logID, "SendHandshake: returned from PutFragment");

        (* If I delete the following line, the problem of heap      *)
        (* corruption seems to go away; but of course that creates  *)
        (* a memory leak.                                           *)

        DiscardFragment (buffer);
        RETURN success;

    END SendHandshake;

(************************************************************************)
(*             HANDSHAKE PROTOCOL -- ClientHello EXTENSIONS             *)
(*                                                                      *)
(* Browsers that I have checked use the following extensions (hex):     *)
(*      Firefox 38.8.0    0000 0005 000A 000B 000D 0010 0023 3374 FF01  *)
(*      Firefox 59.0.2    0005 000A 000B 0010 0017 0023 0033 FF01       *)
(*                          (list possibly incomplete, there's a bug)   *)
(*      M$Edge            0005 000A 000B 000D 0010 0017 0023            *)
(*      Chrome            0005 000A 000B 000D 0010 0012 0017            *)
(*                          0023 1A1A 3A3A 7550 FF01                    *)
(*                                                                      *)
(************************************************************************)

PROCEDURE Extension0 (state: HSstate;  VAR (*IN*) record: ByteStringPtr;
                         offset, size: CARDINAL;  VAR (*OUT*) abort: BOOLEAN);

    (* The server_name extension - identifies the domain that is being addressed. *)

    VAR namelength: CARDINAL;
        nametype: CARD8;
        name: DomainName;
        message: ARRAY [0..511] OF CHAR;

    BEGIN
        abort := FALSE;
        message := "server_name";
        WHILE size > 0 DO

            (* In each subrecord the length is redundantly given    *)
            (* twice, so we can just skip the outer length spec.    *)

            INC (offset, 2);  DEC (size, 2);
            nametype := record^[offset];
            INC (offset);  DEC (size);
            namelength := BEval(record^, offset, 2);
            INC (offset, 2);  DEC (size, 2);
            Strings.Append ("    ", message);
            AppendCard (nametype, message);  Strings.Append (' ', message);
            Copy (ADR(record^[offset]), ADR(name), namelength);
            name[namelength] := Nul;
            INC (offset, namelength);  DEC (size, namelength);
            IF namelength > 0 THEN
                Strings.Append (name, message);
                state^.domain := NameToDomain (name);
            ELSE
                state^.domain := NILDomain;
                IF NOT BlankDomainLegal THEN
                    message := "Error: empty server_name";
                    abort := TRUE;
                END (*IF*);
            END (*IF*);
        END (*WHILE*);
        LogTransaction (state^.logID, message);
    END Extension0;

(************************************************************************)

PROCEDURE Extension13 (logID: TransactionLogID;  VAR (*IN*) record: ByteStringPtr;
                                                  offset, size: CARDINAL);

    (* The signature_algorithms extension, definitely important. *)

    CONST LogThem = FALSE;

    VAR code, count: CARD8;
        message: ARRAY [0..511] OF CHAR;

    BEGIN
        IF LogThem THEN
            LogTransactionL (logID, "signature_algorithms");
        END (*IF*);
        (*
        WriteString ("The next few bytes in the record are: ");  WriteLn;
        WriteHexString (record^, offset, 32); WriteLn;
        *)

        size := BEval(record^, offset, 2);  INC (offset, 2);

        IF LogThem THEN
            message := "";  count := 0;
        END (*IF*);
        WHILE size > 0 DO

            IF LogThem THEN
                Strings.Append ("  ", message);
            END (*IF*);

            (* Read a pair of values, one for hash algorithm and    *)
            (* one for signature algorithm.                         *)

            code := record^[offset];
            INC (offset);  DEC (size);
            IF LogThem THEN
                CASE code OF
                        0:  Strings.Append ("none", message);
                    |   1:  Strings.Append ("md5", message);
                    |   2:  Strings.Append ("sha1", message);
                    |   3:  Strings.Append ("sha224", message);
                    |   4:  Strings.Append ("sha256", message);
                    |   5:  Strings.Append ("sha384", message);
                    |   6:  Strings.Append ("sha512", message);
                    ELSE    Strings.Append ("???", message);
                END (*CASE*);
                Strings.Append ('_', message);
            END (*IF*);
            code := record^[offset];
            INC (offset);  DEC (size);
            IF LogThem THEN
                CASE code OF
                        0:  Strings.Append ("anon", message);
                    |   1:  Strings.Append ("rsa", message);
                    |   2:  Strings.Append ("dsa", message);
                    |   3:  Strings.Append ("ecdsa", message);
                    ELSE    Strings.Append ("???", message);
                END (*CASE*);
                INC (count);
                IF count = 3 THEN
                    LogTransaction (logID, message);
                    message := "";  count := 0;
                END (*IF*);
            END (*IF*);
        END (*WHILE*);
        IF LogThem THEN
            IF count > 0 THEN
                LogTransaction (logID, message);
            END (*IF*);
        END (*IF*);
    END Extension13;

(************************************************************************)

PROCEDURE AppendExtensionName (extnum: CARDINAL;  VAR (*INOUT*) message: ARRAY OF CHAR);

    VAR name: ARRAY [0..31] OF CHAR;

    BEGIN
        name[0] := Nul;
        IF extnum = 5 THEN
            name := "status_request";
        ELSIF extnum = 10 THEN
            name := "supported_groups";
        ELSIF extnum = 11 THEN
            name := "ec_point_formats";
        ELSIF extnum = 16 THEN
            name := "app_layer_prot_neg";
        ELSIF extnum = 28 THEN
            name := "record_size_limit";
        ELSIF extnum = 35 THEN
            name := "session_ticket";
        ELSE
            (* Append nothing *)
        END (*IF*);
        IF name[0] <> Nul THEN
            Strings.Append (' ', message);
            Strings.Append (name, message);
        END (*IF*);
    END AppendExtensionName;

(************************************************************************)
(*                             CLIENT HELLO                             *)
(************************************************************************)

PROCEDURE ParseHandshakeExtensions (state: HSstate;
                                        VAR (*IN*) record: ByteStringPtr;
                                            offset, length: CARDINAL;
                                              VAR (*OUT*) abort: BOOLEAN);

    (* Parses the extension fields in a ClientHello, but for now does   *)
    (* nothing with most of them.                                       *)

    CONST LogMiscExtensions = FALSE;
    VAR size, type, L: CARDINAL;
        message: ARRAY [0..127] OF CHAR;

    BEGIN
        abort := FALSE;

        (* On entry, length is the number of bytes left. The next two   *)
        (* bytes have a redundant length field. Check for discrepancies.*)

        IF length < 2 THEN
            LogTransactionL (state^.logID, "Error: extension less than 2 bytes long");
        ELSE
            L := BEval (record^, offset, 2);
            INC (offset, 2);  DEC (length, 2);
            IF L <> length THEN
                message := "Error: extension length mismatch, L = ";
                AppendCard (L, message);
                Strings.Append (", bytes left = ", message);
                AppendCard (length, message);
                LogTransaction (state^.logID, message);
                IF L < length THEN
                    length := L;
                END (*IF*);
            END (*IF*);
        END (*IF*);
        WHILE length >= 4 DO

            (* Each extension begins with four bytes: an extension  *)
            (* type and a length.                                   *)

            type := BEval (record^, offset, 2);
            INC (offset, 2);
            size := BEval (record^, offset, 2);
            INC (offset, 2);
            DEC (length, 4);
            IF type = 0 THEN
                Extension0 (state, record, offset, size, abort);
                IF abort THEN
                    length := 0;
                END (*IF*);
            ELSIF state^.bornagain THEN
                (* Other extensions are not worth looking at *)
            ELSIF type = 13 THEN
                Extension13 (state^.logID, record, offset, size);
            ELSIF LogMiscExtensions THEN
                message := "Extension ";
                AppendHex4 (type, message);
                Strings.Append (" length ", message);
                AppendCard (size, message);
                AppendExtensionName (type, message);
                LogTransaction (state^.logID, message);
            END (*IF*);
            INC (offset, size);
            IF length >= size THEN
                DEC (length, size);
            ELSE
                length := 0;
                LogTransactionL (state^.logID, "length error in ClientHello extension");
            END (*IF*)
        END (*WHILE*);

        IF length > 0 THEN
            message := "Extra bytes after ClientHello ";
            AppendHexString (record^, offset, length, TRUE, message);
            LogTransaction (state^.logID, message);
        END (*IF*);

    END ParseHandshakeExtensions;

(************************************************************************)

PROCEDURE HandleClientHello (state: HSstate;  VAR (*IN*) record: ByteStringPtr;
                                      offset, length: CARDINAL): BOOLEAN;

    (* The data for this level start at record[offset].    *)

    TYPE CharSet = SET OF CHAR;

    CONST Starters = CharSet {'A'..'Z', 'a'..'z', '0'..'9'};

    VAR sublength, j, k, count, sessIDlength: CARDINAL;
        sessID: sessIDtype;
        name: ARRAY [0..31] OF CHAR;
        code1, code2: CARD8;
        suite, newsuite: CipherSuite;
        method, newmethod: CompressionMethod;
        buffer: TextType;
        message: ARRAY [0..127] OF CHAR;
        ListCipherSuites, abort: BOOLEAN;

    BEGIN
        abort := FALSE;

        (* VERSION - which we are currently not checking for compatibility *)

        (* The first two bytes should be the version.  Note that I  *)
        (* am updating length to show how many bytes are left in    *)
        (* this Hello message.                                      *)

        (*
        WriteString ("Protocol version ");  WriteCard (record^[offset]);
        WriteChar ('.');  WriteCard (record^[offset+1]);  WriteLn;
        *)
        DEC (length, 2);

        (* CLIENT RANDOM SEED *)

        (* Copy client random seed. *)

        Copy (ADR(record^[offset+2]), ADR(state^.security.client_random), 32);
        (*
        Strings.Assign ("client_random = ", message);
        AppendHexString (state^.security.client_random, 0, 32, FALSE, message);
        LogTransaction (state^.logID, message);
        *)
        DEC (length, 32);

        (* First four bytes of seed should be Unix time. *)

        (*
        unixtime := 0;
        FOR j := 0 TO 3 DO
            unixtime := 256*unixtime + state^.security.client_random[j];
        END (*FOR*);
        WriteString ("Unix time = ");  WriteCard (unixtime);  WriteLn;
        *)

        (* SESSION ID - we will check whether this matches a stored session. *)

        (* Requested session ID.  The first byte is the length.  *)

        j := offset+34;
        sublength := record^[j];  INC (j);
        sessIDlength := sublength;
        DEC (length, sessIDlength+1);
        k := 0;
        WHILE sublength > 0 DO
            sessID[k] := record^[j];
            INC(j);  INC(k);
            DEC (sublength);
        END (*WHILE*);
        sessID[k] := 0;
        IF sessIDlength <= 1 THEN
            message := "No sessID requested";
            state^.bornagain := FALSE;
        ELSE
            Strings.Assign ("Want sessID ", message);
            IF CHR(sessID[0]) IN Starters THEN
                AppendSessID (sessID, sessIDlength, message);
                Strings.Append (", length ", message);
                AppendCard (sessIDlength, message);
            ELSE
                Strings.Append ("(garbled)", message);
            END (*IF*);
            state^.bornagain := RestoreSession (state^.serveraddr, sessID, sessIDlength,
                                                state^.security.pending,
                                                state^.security.master_secret);
            IF state^.bornagain THEN
                state^.sessID := sessID;
                state^.sessIDlength := sessIDlength;
                Strings.Append (" - accepted", message);
            ELSE
                Strings.Append (" - rejected", message);
            END (*IF*);
        END (*IF*);
        LogTransaction (state^.logID, message);

        (* CIPHER SUITES *)

        (* Now we have to get a list of the cipher suites acceptable to the client. *)
        (* Each cipher suite code is two bytes long. *)

        (* If we have just accepted a session ID from the client, we don't  *)
        (* want to select a cipher suite, but we still have to go through   *)
        (* most of this code to skip past the right number of bytes.        *)

        ListCipherSuites := LogCipherSuites AND NOT state^.bornagain;
        IF state^.bornagain THEN
            suite := state^.security.pending;
        ELSE
            suite := NullSuite;
        END (*IF*);
        sublength := 256*record^[j] + record^[j+1];
        INC (j, 2);
        DEC (length, sublength + 2);
        sublength := sublength DIV 2;
        IF ListCipherSuites THEN
            LogTransactionL (state^.logID, "Client will accept cipher suites:");
        END (*IF*);
        message := "";  count := 0;
        FOR k := 0 TO sublength-1 DO
            code1 := record^[j];  INC (j);
            code2 := record^[j];  INC (j);
            IF ListCipherSuites THEN
                Strings.Append ("    ", message);
                AppendHex (code1, message);
                Strings.Append (' ', message);
                AppendHex (code2, message);
            END (*IF*);
            IF ListCipherSuites OR (suite = NullSuite) THEN
                newsuite := AcceptCipherSuite(code1, code2);
                IF (suite = NullSuite) AND (newsuite <> NullSuite) THEN
                    suite := newsuite;
                END (*IF*);
                IF ListCipherSuites THEN
                    IF newsuite <> NullSuite THEN
                        IF suite = newsuite THEN
                            Strings.Append ("   accepted", message);
                        ELSE
                            Strings.Append ("   acceptable", message);
                        END (*IF*);
                    ELSE
                        Strings.Append ("   rejected", message);
                    END (*IF*);
                    INC (count);
                    IF count = 2 THEN
                        LogTransaction (state^.logID, message);
                        message := "";  count := 0;
                    END (*IF*);
                END (*IF*);
            END (*IF*);
        END (*FOR*);
        IF ListCipherSuites AND (count > 0) THEN
            LogTransaction (state^.logID, message);
        END (*IF*);
        IF NOT state^.bornagain THEN
            state^.security.pending := suite;
        END (*IF*);
        Strings.Assign ("ciphersuite = ", message);
        NameOfSuite (suite, name);
        Strings.Append (name, message);
        LogTransaction (state^.logID, message);

        (* COMPRESSION ALGORITHMS *)

        (* And then the compression methods. *)

        sublength := record^[j];
        INC (j);
        DEC (length, sublength+1);

        method := nocompression;
        FOR k := 1 TO sublength DO
            code1 := record^[j];  INC (j);
            newmethod := CompressionSupported(code1);
            IF (method = nocompression) AND (newmethod <> nocompression) THEN
                method := newmethod;
            END (*IF*);
        END (*FOR*);
        state^.security.compression_algorithm := method;

        (* EXTENSIONS *)

        IF length > 0 THEN

            ParseHandshakeExtensions (state, record, j, length, abort);

        END (*IF*);

        IF abort THEN
            CreateAlert (50, TRUE, buffer);
            EVAL (PutFragment (state^.rlstate, state^.logID, buffer));
            DiscardFragment (buffer);
            RETURN FALSE;

        ELSIF (state^.domain = NILDomain) AND NOT BlankDomainLegal THEN

            (* If we don't have a valid domain name by now, abort the handshaking. *)

            CreateAlert (112, TRUE, buffer);
            EVAL (PutFragment (state^.rlstate, state^.logID, buffer));
            DiscardFragment (buffer);
            LogTransactionL (state^.logID, "Client hello failure: no valid domain name");
            RETURN FALSE;
        ELSE
            LogTransactionL (state^.logID, "Finished handling client hello");
            RETURN TRUE;
        END (*IF*);

    END HandleClientHello;

(************************************************************************)

PROCEDURE SendClientHello (state: HSstate;
                            VAR (*IN*) servername: ARRAY OF CHAR): BOOLEAN;

    (* Send the client hello to the server.         *)
    (* The data for this level start at record[0].  *)

    CONST
        InitialSize = 128;
        client_hello_code = 1;

    VAR recsize, length, length0, L, sublength: CARDINAL;
        name: ARRAY [0..31] OF CHAR;
        record: ByteStringPtr;
        suite: CipherSuite;
        sessID: sessIDtype;
        message: ARRAY [0..127] OF CHAR;
        code1, code2: CARD8;

    BEGIN
        recsize := InitialSize;
        ALLOCATE (record, recsize);
        (*length := 4;*)            (* Initial space reserved for SendHandshake *)

        (* VERSION *)

        (* The first two bytes should be the version.  *)

        Checksize (record, recsize, 4, 2);
        record^[4] := SSLversionMajor;
        record^[5] := SSLversionMinor;

        (* CLIENT RANDOM SEED *)

        (* Create and send client random seed. *)

        GenerateRandom32 (state^.security.client_random);
        Checksize (record, recsize, 6, 32);
        Copy (ADR(state^.security.client_random), ADR(record^[6]), 32);

        (* SESSION ID - ask for a known one if possible.  We won't know *)
        (* whether we can get that ID until we see the response.        *)

        L := LastSavedID (state^.serveraddr, sessID);

        (* The first byte is the length.  *)

        Checksize (record, recsize, 38, L+1);
        record^[38] := L;
        length := 39;
        IF L > 0 THEN
            Copy (ADR(sessID), ADR(record^[39]), L);
            length := 39 + L;
        END (*IF*);

        (* The following assignment is tentative.  The reply from the   *)
        (* server will establish the definitive session ID.             *)

        state^.sessID := sessID;

        (* CIPHER SUITES *)

        (* Now we have to send a list of the cipher suites acceptable   *)
        (* to the client.  Each cipher suite code is two bytes long.    *)

        (* TEMPORARY CHANGE TO FORCE A PARTICULAR SUITE. *)
        (*suite := TLS_RSA_WITH_AES_128_CBC_SHA;*)

        suite := NullSuite;
        Checksize (record, recsize, length, 2);
        length0 := length;  INC (length, 2);  sublength := 0;
        LogTransactionL (state^.logID, "Client offering ciphersuites");
        WHILE suite < MAX(CipherSuite) DO
            INC (suite);
            IF SuiteEnabled (suite) THEN
                CipherSuiteToCode (suite, code1, code2);
                Checksize (record, recsize, length, 2);
                record^[length] := code1;  INC (length);
                record^[length] := code2;  INC (length);
                INC (sublength, 2);
                message := "    ";
                AppendHex (code1, message);
                Strings.Append (' ', message);
                AppendHex (code2, message);
                Strings.Append ("   ", message);
                NameOfSuite (suite, name);
                Strings.Append (name, message);
                LogTransaction (state^.logID, message);
            END (*IF*);
        END (*WHILE*);
        record^[length0] := sublength DIV 256;
        record^[length0+1] := sublength MOD 256;

        (* COMPRESSION ALGORITHMS *)

        (* And then the compression methods. *)
        (* For now we support only "no compression". *)

        state^.security.compression_algorithm := nocompression;
        Checksize (record, recsize, length, 2);
        record^[length] := 1;  INC (length);        (* length of list (1 byte) *)
        record^[length] := ORD(state^.security.compression_algorithm);  INC (length);

        (* EXTENSIONS *)

        length0 := length;
        Checksize (record, recsize, length, 2);
        record^[length] := 0;  INC (length);    (* length of all extemsions *)
        record^[length] := 0;  INC (length);

        (* Extension 0: server_name *)

        L := Strings.Length (servername);
        IF L > 0 THEN
            Checksize (record, recsize, length, L+7);
            record^[length] := 0;  INC (length);                (* extemsion type (2 bytes) *)
            record^[length] := 0;  INC (length);
            record^[length] := (L+5) DIV 256;  INC (length);    (* length of nextemsion data (2 bytes) *)
            record^[length] := (L+5) MOD 256;  INC (length);
            record^[length] := (L+3) DIV 256;  INC (length);    (* length of list (2 bytes) *)
            record^[length] := (L+3) MOD 256;  INC (length);
            record^[length] := 0;  INC (length);                (* name type (1 byte) *)
            record^[length] := L DIV 256;  INC (length);        (* length of server name (2 bytes) *)
            record^[length] := L MOD 256;  INC (length);
            Copy (ADR(servername), ADR(record^[length]), L);
            INC (length, L);
        END (*IF*);

        L := length - length0 - 2;
        IF L > 2 THEN
            record^[length0] := L DIV 256;      (* length of all extemsions *)
            record^[length0 + 1] := L MOD 256;
        ELSE
            (* No extensions, so cancel the above. *)

            length := length0;

        END (*IF*);

        Resize (record, recsize, length);

        message := "Sending client hello, ";
        AppendCard (length, message);
        Strings.Append (" bytes.", message);
        LogTransaction (state^.logID, message);
        (*
        WriteString ("First few bytes of output are ");
        WriteHexString (record^, 0, 51, TRUE);
        WriteLn;
        *)

        RETURN SendHandshake (state, client_hello_code, record, length);

    END SendClientHello;

(************************************************************************)
(*                            KEY EXCHANGE                              *)
(************************************************************************)

PROCEDURE ComputeMasterSecret (state: HSstate);

    (* Calculates the master secret.  This can be done once             *)
    (* ClientKeyExchange has supplied a non-encrypted client_premaster. *)

    VAR seed: ARRAY [0..63] OF CARD8;

    BEGIN

        Copy (ADR(state^.security.client_random), ADR(seed), 32);
        Copy (ADR(state^.security.server_random), ADR(seed[32]), 32);
        PRF (state^.security.prf_algorithm,
                48, state^.security.client_premaster,
                "master secret", 64, seed, 48, state^.security.master_secret);

    END ComputeMasterSecret;

(************************************************************************)

PROCEDURE HandleClientKeyExchange (state: HSstate;  VAR (*IN*) record: ByteStringPtr;
                                      offset, length: CARDINAL);

    (* The data for this level start at record[offset].                 *)
    (* In the present version the only supported key exchange algorithm *)
    (* is RSA, so what the client sends is the encrypted premaster      *)
    (* secret.  This is a two-byte version, followed by a 46-byte       *)
    (* random value.  The version is part of the premaster secret.      *)

    VAR message: ARRAY [0..2047] OF CHAR;
        decrypted: ARRAY [0..2047] OF CARD8;
        ptib: OS2.PTIB;  ppib: OS2.PPIB;
        oldpriority: CARDINAL;
        ourdomainname: DomainName;
        key: RSAKeys.RSAKeyType;
        encrypted: ByteStr;
        S, T: BN;

    BEGIN
        (* CLIENT PREMASTER SECRET *)

        (* Copy (encrypted) client premaster seed. *)
        (* The record starts with two length bytes.  *)

        length := 256*record^[offset] + record^[offset+1];
        MakeBS (encrypted, length);
        Copy (ADR(record^[offset+2]), encrypted.data, length);

        (*
        message := "encrypted client_premaster ";
        AppendHexString (encrypted.data^, 0, length, TRUE, message);
        LogTransaction (state^.logID, message);
        *)

        (* Decrypt the seed.  I'm lowering our priority here so as  *)
        (* not to hog the processor.  The reason for the Lock is    *)
        (* to serialise this step.                                  *)

        Obtain (encryptlock);
        OS2.DosGetInfoBlocks (ptib, ppib);
        oldpriority := ptib^.tib_ptib2^.tib2_ulpri MOD 65536;
        OS2.DosSetPriority (OS2.PRTYS_THREAD, OS2.PRTYC_IDLETIME, 15, 0);
        message := "Getting private key for domain ";
        NameOfDomain (state^.domain, ourdomainname);
        Strings.Append (ourdomainname, message);
        LogTransaction (state^.logID, message);
        key := GetPrivateKey (state^.domain);
        S := BigNum.BinToBN (encrypted.data^, 0, length);
        (*LogTransactionL (state^.logID, "About to call PrivKeyEncode");*)
        T := RSA.PrivKeyEncode (S, key);
        (*LogTransactionL (state^.logID, "Returned from PrivKeyEncode");*)
        length := BigNum.BNtoBytes (T, decrypted);
        OS2.DosSetPriority (OS2.PRTYS_THREAD, oldpriority DIV 256, oldpriority MOD 256, 0);
        Release (encryptlock);

        (*
        message := "decrypted block ";
        AppendHexString (decrypted, 0, length, TRUE, message);
        LogTransaction (state^.logID, message);
        *)

        (* Extract the last 48 bytes from this block.  The rest is  *)
        (* padding, which we can ignore.                            *)

        Copy (ADR(decrypted[length-48]), ADR(state^.security.client_premaster), 48);

        (*
        message := "decrypted client_premaster ";
        AppendHexString (state^.security.client_premaster, 0, 48, TRUE, message);
        LogTransaction (state^.logID, message);
        *)

        (* VERSION *)

        (* The first two bytes should be the version.  At present I'm   *)
        (* not bothering to check this, excepting inspecting the log.   *)

        (*
        WriteString ("Premaster protocol version ");  WriteCard (record^[offset]);
        WriteChar ('.');  WriteCard (record^[offset+1]);  WriteLn;
        *)

        (* We now have enough information to calculate the master   *)
        (* secret and the block of key material.                    *)

        ComputeMasterSecret (state);
        GenerateKeyBlock (state);
        LogTransactionL (state^.logID, "Have finished handling client key exchange");

    END HandleClientKeyExchange;

(************************************************************************)

PROCEDURE SendClientKey (state: HSstate): BOOLEAN;

    (* Send the client premaster information to the server.             *)
    (* In the present version the only supported key exchange algorithm *)
    (* is RSA, so what the client sends is the encrypted premaster      *)
    (* secret.  This is a two-byte version, followed by a 46-byte       *)
    (* random value which is the premaster secret.                      *)

    CONST
        InitialSize = 128;
        client_keyexchange_code = 16;

    VAR encrypted: ByteStr;
        record: ByteStr;

    BEGIN
        LogTransactionL (state^.logID, "Sending client premaster");

        (* CLIENT PREMASTER SECRET *)

        (* For RSA, this is a two-byte version followed by 46 random bytes. *)

        GenerateRandom (48, state^.security.client_premaster);
        state^.security.client_premaster[0] := SSLversionMajor;
        state^.security.client_premaster[1] := SSLversionMinor;

        (* We now have enough information to calculate the master   *)
        (* secret and the block of key material.                    *)

        ComputeMasterSecret (state);
        GenerateKeyBlock (state);

        (* We must encrypt the client premaster before sending it.  *)

        encrypted := PKCS1Encrypt (state^.serverkey, 48,
                                        state^.security.client_premaster);
        MakeBS (record, 6 + encrypted.size);
        record.data^[4] := encrypted.size DIV 256;
        record.data^[5] := encrypted.size MOD 256;
        Copy (encrypted.data, ADR(record.data^[6]), encrypted.size);
        DiscardBS (encrypted);
        RETURN SendHandshake (state, client_keyexchange_code, record.data, record.size);

    END SendClientKey;

(************************************************************************)
(*                             SERVER HELLO                             *)
(************************************************************************)

PROCEDURE CopyBytes (VAR (*IN*) src: ARRAY OF LOC;  N: CARDINAL;
                      record: ByteStringPtr;  VAR (*INOUT*) pos: CARDINAL);

    (* Copies N bytes from src to record^[pos], updates pos. *)

    VAR j: CARDINAL;

    BEGIN
        FOR j := 0 TO N-1 DO
            record^[pos] := CAST (CARD8,src[j]);
            INC (pos);
        END (*FOR*);
    END CopyBytes;

(************************************************************************)

PROCEDURE AssembleServerHello (state: HSstate;
                        VAR (*OUT*) record: ByteStringPtr): CARDINAL;

    (* Puts together the content of a server hello, returns its length. *)
    (* This content consists of                                         *)
    (*      overhead        (4 bytes)                                   *)
    (*      version         (2 bytes)                                   *)
    (*      random seed     (32 bytes)                                  *)
    (*      session ID      (1-byte length, then up to 32 bytes of ID)  *)
    (*      ciphersuite     (2 bytes)                                   *)
    (*      compression method  (1 byte)                                *)
    (* That adds up to 42 plus the session ID length.                   *)
    (* Extensions can follow, but for now I'll leave those out.         *)

    VAR pos, amount, L: CARDINAL;
        code1, code2: CARD8;

    BEGIN
        (* My convention for session ID is that the trailing Nuls is counted. *)

        IF NOT state^.bornagain THEN
            GenerateSessionID (state^.sessID, state^.sessIDlength);
        END (*IF*);
        L := state^.sessIDlength;
        amount := 42 + L;
        ALLOCATE (record, amount);
        record^[4] := SSLversionMajor;  record^[5] := SSLversionMinor;
        pos := 6;
        CopyBytes (state^.security.server_random, 32, record, pos);
        record^[pos] := L;  INC(pos);
        CopyBytes (state^.sessID, L, record, pos);
        CipherSuiteToCode (state^.security.pending, code1, code2);
        record^[pos] := code1;  INC(pos);
        record^[pos] := code2;  INC(pos);
        record^[pos] := ORD(nocompression);  INC(pos);
        IF pos <> amount THEN
            LogTransactionL (state^.logID, "Allocation error in AssembleServerHello.");
        END (*IF*);

        (* EXTENSIONS *)

        (* Set aside a field for total extension length. *)

        Resize (record, pos, pos+2);
        record^[pos] := 0;  INC (pos);
        record^[pos] := 0;  INC (pos);

        (*    SERVER NAME EXTENSION                                     *)
        (* There was a server_name extension in the ClientHello iff     *)
        (* state^.domain is not NIL.  However, we should not include    *)
        (* this extension for a resumed session.                        *)

        (* NOTE: according to everything I've read, we should send      *)
        (* either two or three bytes at this point.  (The standard      *)
        (* seems to imply two, but on-line examples use three.)  But    *)
        (* my tests for Thunderbird compatibility show that only four   *)
        (* bytes works.                                                 *)

        IF (NOT state^.bornagain) AND (state^.domain <> NILDomain) THEN
            Resize (record, pos, pos+3);
            record^[pos] := 0;  INC (pos);      (* type=0 *)
            record^[pos] := 0;  INC (pos);
            record^[pos] := 0;  INC (pos);
            record^[pos] := 0;  INC (pos);
        END (*IF*);

        (* Only signature algorithm I can support at present is RSA     *)
        (* with SHA256, because that's all the certificate has.  But in *)
        (* any case the standard says that a server MUST NOT send the   *)
        (* signature_algorithms extension, although it must be able to  *)
        (* receive it.                                                  *)

        (* Recall that amount was the size before extensions added.     *)

        L := pos - amount;
        IF L > 2 THEN
            DEC (L, 2);
            record^[amount] := L DIV 256;
            record^[amount+1] := L MOD 256;
        ELSE
            Resize (record, pos, amount);
            pos := amount;
        END (*IF*);

        (* If we have a resumed session, we now have enough information *)
        (* to generate the key block.  If not, the key bloack will be   *)
        (* generated once we have the ClientKeyExchange message.        *)

        IF state^.bornagain THEN
            GenerateKeyBlock (state);
        END (*IF*);

        RETURN pos;

    END AssembleServerHello;

(************************************************************************)

PROCEDURE SendServerHello (state: HSstate): BOOLEAN;

    (* A handshake record starts with code 22 to mean "handshake", then *)
    (* we have a two-byte version code and a two-byte overall length    *)
    (* Then we have the "fragment" field with                           *)
    (*      HandshakeType (1 byte, value 2 for server hello)            *)
    (*      length        (3 bytes)                                     *)
    (*      content       (depends on handshake type)                   *)

    CONST server_hello_code = 2;

    VAR p: ByteStringPtr;
        length: CARDINAL;  success: BOOLEAN;
        message: ARRAY [0..127] OF CHAR;

    BEGIN
        GenerateRandom32 (state^.security.server_random);
        length := AssembleServerHello (state, p);
        success := SendHandshake (state, server_hello_code, p, length);
        message :="Have sent server hello, ";
        AppendCard (length, message);
        Strings.Append (" bytes", message);
        LogTransaction (state^.logID, message);
        RETURN success;
    END SendServerHello;

(************************************************************************)

PROCEDURE ReceiveServerHello (state: HSstate;  record: ByteStringPtr;
                                    offset, length: CARDINAL): BOOLEAN;

    (* Receive the server_hello handshake.                              *)

    (*      overhead        (4 bytes, handled by caller)                *)
    (*      version         (2 bytes)                                   *)
    (*      random seed     (32 bytes)                                  *)
    (*      session ID      (1-byte length, then up to 32 bytes of ID)  *)
    (*      ciphersuite     (2 bytes)                                   *)
    (*      compression method  (1 byte)                                *)
    (*      extensions      (0 or more bytes)                           *)

    VAR message: ARRAY [0..127] OF CHAR;
        name: ARRAY [0..63] OF CHAR;
        j, k, sublength, type: CARDINAL;
        code1, code2: CARD8;
        sessionID: sessIDtype;

    BEGIN
        LogTransactionL (state^.logID, "Received server hello");

        (* VERSION *)

        (* The first two bytes should be the version.  Note that I  *)
        (* am updating length to show how many bytes are left in    *)
        (* this Hello message.                                      *)

        (*
        WriteString ("Protocol version ");  WriteCard (record^[offset]);
        WriteChar ('.');  WriteCard (record^[offset+1]);  WriteLn;
        *)
        DEC (length, 2);

        (* SERVER RANDOM SEED *)

        (* Accept server random seed. *)

        Copy (ADR(record^[offset+2]), ADR(state^.security.server_random), 32);
        (*
        Strings.Assign ("server_random = ", message);
        AppendHexString (state^.security.server_random, 0, 32, FALSE, message);
        LogTransaction (state^.logID, message);
        *)
        DEC (length, 32);

        (* Server's session ID.  The first byte is the length.  If it   *)
        (* matches what we requested, the server has agreed to resume   *)
        (* an old session.                                              *)

        j := offset+34;
        sublength := record^[j];  INC (j);
        state^.sessIDlength := sublength;
        DEC (length, sublength+1);
        k := 0;
        WHILE sublength > 0 DO
            sessionID[k] := record^[j];  INC(j);  INC(k);
            DEC (sublength);
        END (*WHILE*);
        IF k < 32 THEN
            sessionID[k] := 0;
            INC (state^.sessIDlength);
        END (*IF*);
        state^.bornagain := RestoreSession (state^.serveraddr, sessionID,
                                            state^.sessIDlength,
                                            state^.security.pending,
                                            state^.security.master_secret);
        IF state^.bornagain THEN
            Strings.Assign ("RESUMING SESSION ID ", message);
        ELSE
            Strings.Assign ("New session ID ", message);
        END (*IF*);
        IF k = 0 THEN
            Strings.Append ("''", message);
        ELSE
            AppendSessID (sessionID, state^.sessIDlength, message);
        END (*IF*);
        state^.sessID := sessionID;
        offset := j;

        (* CIPHER SUITE *)

        code1 := record^[offset];  INC (offset);  DEC(length);
        code2 := record^[offset];  INC (offset);  DEC(length);
        Strings.Assign ("ciphersuite = ", message);
        state^.security.pending := CodeToCipherSuite (code1, code2);
        NameOfSuite (state^.security.pending, name);
        Strings.Append (name, message);
        LogTransaction (state^.logID, message);

        (* COMPRESSION *)

        (* The compression method code is a single byte. *)

        code1 := record^[offset];  INC (offset);  DEC (length);
        state^.security.compression_algorithm := CompressionSupported (code1);

        IF state^.bornagain THEN
            GenerateKeyBlock (state);
        END (*IF*);

        (* EXTENSIONS. *)

        (* While testing, log what remains. *)

        IF length > 0 THEN
            message := "Extension data ";
            AppendHexString (record^, offset, length, TRUE, message);
            LogTransaction (state^.logID, message);

            (* The 2rosenthals server seems to be asking for a      *)
            (* truncated HMAC, which is illegal since the client    *)
            (* disn't ask for it. For now it seems best just to     *)
            (* ignore the extension.                                *)

        END (*IF*);

        (* The next two bytes should be a "total length of          *)
        (* extensions" word, but it's redundant so I'll just skip it. *)

        IF length >= 2 THEN
            DEC (length, 2);
            INC (offset, 2);
        END (*IF*);

        (* The extensions I allow for here are server_name (0) and  *)
        (* truncated_hmac (4).  The first takes three or four bytes, *)
        (* the second takes two bytes.                              *)

        WHILE length >= 2 DO
            type := BEval (record^, offset, 2);
            INC (offset, 2);
            DEC (length, 2);
            IF type = 0 THEN
                (* server_name, one or two zero bytes to pick up.   *)
                (* (I can't find agreement on how many.)            *)
                IF length > 0 THEN
                    INC (offset);  DEC (length);
                END (*IF*);
                IF length > 0 THEN
                    INC (offset);  DEC (length);
                END (*IF*);
            ELSIF type = 4 THEN
                (* truncated_hmac, no more bytes to fetch.          *)
                (* This option should appear in the ServerHello     *)
                (* only if it was requested in the ClientHello, but *)
                (* I know of one server that sends it (illegally)   *)
                (* without a request.  As a temporary measure, I'll *)
                (* accept it anyway.                                *)

                state^.truncateMAC := TRUE;
            ELSE
                message := "unexpected extension ";
                AppendCard (type, message);
                LogTransaction (state^.logID, message);
            END (*IF*);
        END (*WHILE*);

        IF length > 0 THEN
            message := "unexpected extension data ";
            AppendHexString (record^, offset, length, TRUE, message);
            LogTransaction (state^.logID, message);

            (* The 2rosenthals server seems to be asking for a      *)
            (* truncated HMAC, which is illegal since the client    *)
            (* disn't ask for it. For now it seems best just to     *)
            (* ignore the extension.                                *)

        END (*IF*);

        LogTransactionL (state^.logID, "Finished processing server hello");
        RETURN TRUE;

    END ReceiveServerHello;

(************************************************************************)
(*                        SERVER HELLO DONE                             *)
(************************************************************************)

PROCEDURE ReceiveServerHelloDone (state: HSstate;
                                  VAR (*INOUT*) buffer: TextType): BOOLEAN;

    (* Receive the server_hello_done handshake. *)

    BEGIN
        (* There is nothing to do here apart from checking that the     *)
        (* server_hello_done message has arrived, and the caller has    *)
        (* seen that; otherwise we wouldn't have been called.           *)

        LogTransactionL (state^.logID, "Have received Server HelloDone");
        DiscardFragment (buffer);
        RETURN TRUE;

    END ReceiveServerHelloDone;

(************************************************************************)

PROCEDURE SendServerHelloDone (state: HSstate): BOOLEAN;

    (* A handshake record starts with code 22 to mean "handshake", then *)
    (* we have a two-byte version code and a two-byte overall length    *)
    (* Then we have the "fragment" field with                           *)
    (*      HandshakeType (1 byte, value 14 for server hello done)      *)
    (*      length        (3 bytes)                                     *)
    (*      content       (empty, in the hello_done case)               *)

    CONST server_hello_done_code = 14;

    VAR p: ByteStringPtr;  success: BOOLEAN;

    BEGIN

        ALLOCATE (p, 4);
        RecordTraceOn;
        TLSRecord.DumpOn;
        success := SendHandshake (state, server_hello_done_code, p, 4);
        TLSRecord.DumpOff;
        RecordTraceOff;
        RETURN success;

    END SendServerHelloDone;

(************************************************************************)
(*                         CERTIFICATE EXCHANGE                         *)
(************************************************************************)

PROCEDURE SendServerCertificate (state: HSstate): BOOLEAN;

    (* Sends our certificate to the client.  The required format is a   *)
    (* 3-byte total length; then, for each certificate, a 3-byte length *)
    (* followed by the certificate itself.  That suggests that I need   *)
    (* to define a new data type that allows linking of nested          *)
    (* structures, but for now I can't think of an elegant way to do this.*)

    VAR pdata: ByteStringPtr;
        amount: CARDINAL;  success: BOOLEAN;

    CONST certificate_code = 11;

    BEGIN
        AssembleCertificateChain (state^.domain, amount, pdata, state^.logID);
        RecordTraceOn;
        success := SendHandshake (state, certificate_code, pdata, amount);
        RecordTraceOff;
        RETURN success;
    END SendServerCertificate;

(************************************************************************)

PROCEDURE ReceiveServerCertificate (state: HSstate;
                                 VAR (*INOUT*) buffer: TextType): BOOLEAN;

    (* Accept and handle a certificate chain from the server. *)

    VAR success: BOOLEAN;  N, length: CARDINAL;
        logmess: ARRAY [0..511] OF CHAR;

    BEGIN
        length := Swap2(buffer.length);
        logmess :="Receiving certificate chain, ";
        AppendCard (length, logmess);
        Strings.Append (" bytes", logmess);
        LogTransaction (state^.logID, logmess);
        logmess :=" certificate 1: ";
        N := 7;
        IF N < length THEN
            N := Get3Bytes (buffer.fragment^, N);
        ELSE
            N := 0;
        END (*IF*);
        AppendCard (N, logmess);
        Strings.Append (" bytes; certificate 2: ", logmess);
        INC (N, 7+3);
        IF N < length THEN
            N := Get3Bytes (buffer.fragment^, N);
        ELSE
            N := 0;
        END (*IF*);
        AppendCard (N, logmess);
        Strings.Append (" bytes", logmess);
        LogTransaction (state^.logID, logmess);
        logmess := "";
        N := 32;
        IF N > length THEN
            N := length;
        END (*IF*);
        AppendHexString (buffer.fragment^, 0, N, TRUE, logmess);
        LogTransaction (state^.logID, logmess);

        success := AcceptCertificates (state^.logID,
                        buffer.fragment, 4, state^.serverkey);
        LogTransactionL (state^.logID, "Have extracted server public key");
        RETURN success;
    END ReceiveServerCertificate;

(************************************************************************)
(*                              FINISHED                                *)
(* This is sent just before exiting from handshake mode and moving on   *)
(* to sending and receiving application data. Usually the server sends  *)
(* it first, just after issuing a ChangeCipherSpec. In that case the    *)
(* client should respond by sending its own ChangeCipherSpec and then   *)
(* its own Finished message.  There are some situations where the       *)
(* client is the one to send the first Finished.                        *)
(************************************************************************)

PROCEDURE CalculateFinishedData (state: HSstate;  fromserver: BOOLEAN;
                                  resultlength: CARDINAL;
                                  VAR (*OUT*) result: ARRAY OF CARD8);

    (* result should match the value in the Finished message. *)

    VAR label: ARRAY [0..15] OF CHAR;
        hash: ARRAY [0..311] OF CARD8;

    BEGIN
        (* Make sure to use the two hash results in *)
        (* the correct order.                       *)

        IF CAST(ADDRESS, state^.hashctx1) <> NIL THEN
            SHA2Final (state^.hashctx1, hash);
        ELSE
            SHA2Final (state^.hashctx2, hash);
        END (*IF*);
        IF fromserver THEN
            label := "server finished";
        ELSE
            label := "client finished";
        END (*IF*);
        (* The accumulated hash is the seed *)
        PRF (state^.security.prf_algorithm,
                48, state^.security.master_secret,
                label, 32, hash, resultlength, result);
    END CalculateFinishedData;

(************************************************************************)

PROCEDURE MatchBlock (VAR (*IN*) buffer: TextType;  offset, length: CARDINAL;
                        VAR (*IN*) data: ARRAY OF CARD8): BOOLEAN;

    (* Returns TRUE iff a sequence of length bytes, in the buffer at    *)
    (* the give offset, matches the given data.                         *)

    VAR j: CARDINAL;  match: BOOLEAN;

    BEGIN
        (* I justify this form of loop on the grounds that there will   *)
        (* almost always be a complete match.                           *)
        match := TRUE;
        FOR j := 0 TO length-1 DO
            match := match AND (buffer.fragment^[offset+j] = data[j]);
        END (*FOR*);
        RETURN match;
    END MatchBlock;

(************************************************************************)

PROCEDURE ReceiveFinished (state: HSstate;
                            VAR (*INOUT*) buffer: TextType): BOOLEAN;

    (* See page 63. The data transferred with the message, 12 bytes     *)
    (* long for all cipher suites I handle so far, was created          *)
    (* with a PRF call, which uses a hash of all handshake messages     *)
    (* (in both directions, I presume) up until now, but not including  *)
    (* HelloRequest messages.  That means that I have to add some hash  *)
    (* calculations at the appropriate point.  As I understand it, the  *)
    (* calculation is the same for the sender and receiver of the       *)
    (* Finished message.                                                *)

    VAR length, pos: CARDINAL;
        message: ARRAY [0..79] OF CHAR;
        digest: ARRAY [0..31] OF CARD8;

    BEGIN
        message := "Finished message received, ";
        pos := 1;
        length := Get3Bytes (buffer.fragment^, pos);
        AppendCard (length, message);
        Strings.Append (" bytes", message);
        LogTransaction (state^.logID, message);

        (* Work out what those bytes should have been. *)

        CalculateFinishedData (state, NOT state^.IsServer, length, digest);
        IF NOT MatchBlock (buffer, 4, length, digest) THEN
            message := "received ";
            AppendHexString (buffer.fragment^, 4, length, TRUE, message);
            LogTransaction (state^.logID, message);
            message := "expected ";
            AppendHexString (digest, 0, length, TRUE, message);
            LogTransaction (state^.logID, message);
        END (*IF*);

        RETURN TRUE;
    END ReceiveFinished;

(************************************************************************)

PROCEDURE SendFinished (state: HSstate): BOOLEAN;

    (* Sends out a "finished" message. *)

    CONST length = 12;      (* True for all the suites we support *)
        finishedcode = 20;

    VAR p, q: ByteStringPtr;  success: BOOLEAN;
        message: ARRAY [0..127] OF CHAR;

    BEGIN
        (*LogTransactionL (state^.logID, "Sending Finished handshake message");*)
        ALLOCATE (p, length + 4);
        q := ADR(p^[4]);
        CalculateFinishedData (state, state^.IsServer, length, q^);
        success := SendHandshake (state, finishedcode, p, length + 4);
        message := "Have sent ";
        IF state^.IsServer THEN
            Strings.Append ("server", message);
        ELSE
            Strings.Append ("client", message);
        END (*IF*);
        Strings.Append (" Finished message", message);
        LogTransaction (state^.logID, message);
        RETURN success;
    END SendFinished;

(************************************************************************)
(*                 RECEIVE HANDSHAKE MESSAGE FROM PEER                  *)
(************************************************************************)

PROCEDURE ReceivePartnerHandshake (state: HSstate;
                               VAR (*OUT*) done, ConnectionLost: BOOLEAN)
                                                                : BOOLEAN;

    VAR type: CARD8;  success: BOOLEAN;
        j, length, L: CARDINAL;
        buffer: TextType;
        message: ARRAY [0..127] OF CHAR;

    (* A handshake record starts with code 22 to mean "handshake", then *)
    (* we have a two-byte version code and a two-byte length.  Those    *)
    (* initial five bytes are loaded into the appropriate fields of     *)
    (* buffer by procedure GetFragment.  After this we have the         *)
    (* "fragment" field with                                            *)
    (*      HandshakeType (1 byte)                                      *)
    (*      length        (3 bytes)                                     *)
    (*      content       (depends on handshake type)                   *)

    (* The expected sequence, as seen by the client end, is:            *)
    (*      send client Hello                                           *)
    (*      receive server Hello                                        *)
    (*      (maybe) receive server certificate                          *)
    (*      (maybe) receive server key exchange                         *)
    (*      (maybe) receive certificate request from server             *)
    (*      receive server HelloDone                                    *)
    (*      (maybe) send client certificate                             *)
    (*      send client KeyExchange                                     *)
    (*      (maybe) send CertificateVerify                              *)
    (*      send ChangeCipherSpec (not a handshake message)             *)
    (*      send Finished                                               *)
    (*      receive ChangeCipherSpec                                    *)
    (*      receive Finished                                            *)
    (* After this, application data can flow.                           *)

    BEGIN
        done := FALSE;
        ConnectionLost := FALSE;
        (*RecordTraceOn (state^.logID);*)
        LogTransactionL (state^.logID, "ReceivePartnerHandshake: calling GetFragment");
        success := GetFragment (state^.rlstate, state^.logID,
                                            buffer, ConnectionLost);
        IF success THEN
            LogTransactionL (state^.logID, "ReceivePartnerHandshake: GetFragment succeeded");
        ELSE
            LogTransactionL (state^.logID, "ReceivePartnerHandshake: GetFragment failed");
        END (*IF*);
        RecordTraceOff;
        (*length := Swap2 (buffer.length);*)

        IF ConnectionLost OR NOT success THEN
            DiscardFragment (buffer);
            RETURN FALSE;

        ELSIF buffer.contenttype = 20 THEN

            (* Change cipher spec. *)

            ChangeCipherSpec (state, TRUE);
            success := TRUE;

        ELSIF buffer.contenttype = 21 THEN

            (* Alert *)

            success := LogAlert (state^.logID, buffer, done, ConnectionLost);

        ELSIF buffer.contenttype = 22 THEN

            (* Handshake *)

            IF buffer.fragment = NIL THEN
                type := 0;
                length := 0;
                L := 0;
            ELSE
                type := buffer.fragment^[0];

                (* Interpret the three-byte length. *)

                j := 1;
                length := Get3Bytes (buffer.fragment^, j);
                L := length + 4;
            END (*IF*);

            (* Update handshake hash.  We update both instances except in   *)
            (* the special case of a Finished message.                      *)

            IF L > 0 THEN
                IF type = 20 THEN
                    (* Include this in second hash iff it is the first  *)
                    (* Finished record.                                 *)
                    IF CAST (ADDRESS, state^.hashctx1) <> NIL THEN
                        SHA2Update (state^.hashctx2, buffer.fragment^, L);
                    END (*IF*);
                ELSE
                    IF type <> 0 THEN
                        SHA2Update (state^.hashctx1, buffer.fragment^, L);
                        SHA2Update (state^.hashctx2, buffer.fragment^, L);
                    END (*IF*);
                END (*IF*);
            END (*IF*);

            CASE type OF

                | 0:    (* Hello request, only ever sent from server to client. *)

                        NYI (state^.logID, "Hello request");

                | 1:    (* Client Hello received, so this is server code. *)

                        (*
                        message := "Just before calling HandleClientHello, length = ";
                        AppendCard (length, message);
                        LogTransaction (state^.logID, message);
                        *)
                        success := HandleClientHello (state, buffer.fragment, 4, length);
                        IF success THEN
                            LogTransactionL (state^.logID, "Sending server hello");
                            success := SendServerHello (state);
                            IF success THEN
                                IF state^.bornagain THEN
                                    success := SendChangeCipherSpec (state)
                                                AND SendFinished (state);
                                ELSE
                                    LogTransactionL (state^.logID, "Sending server certificate");
                                    success := SendServerCertificate (state);
                                    IF success THEN
                                        LogTransactionL (state^.logID, "Sending hello done");
                                        success := SendServerHelloDone (state);
                                        IF success THEN
                                            LogTransactionL (state^.logID, "Returned from SendServerHelloDone");
                                        ELSE
                                            LogTransactionL (state^.logID, "Couldn't send SendServerHelloDone");
                                        END (*IF*);
                                    END (*IF*);
                                END (*IF*);
                            ELSE
                                LogTransactionL (state^.logID, "Failed to send server hello");
                            END (*IF*);

                            (* At this stage we expect the client   *)
                            (* to send more handshake records:      *)
                            (*  certificate (optional)              *)
                            (*  client key exchange                 *)
                            (*  certificate verify (optional)       *)
                            (*  change cipher spec                  *)
                            (* (That last one is not a handshake    *)
                            (* message, but a different content     *)
                            (* type.)                               *)
                            (* Then we expect a "finished"          *)
                            (* handshake message.                   *)

                            IF success THEN
                                REPEAT
                                    success := ReceivePartnerHandshake (state, done, ConnectionLost);
                                UNTIL done OR NOT success;
                            END (*IF*);
                        END (*IF*);
                        IF NOT success THEN
                            done := TRUE;
                        END (*IF*);

                |  2:   (* Server hello.  This is client code. *)

                        success := ReceiveServerHello (state, buffer.fragment, 4, length);

                (*  3..10 do not appear in RFC 5246 *)

                | 11:   (* Certificate *)

                        success := ReceiveServerCertificate (state, buffer);

                (* 12 = server key exchange, not implemented here. *)
                (* 13 = certificate request, not implemented here. *)

                | 14:   (* Server Hello done.  This is client code. *)

                        success := ReceiveServerHelloDone (state, buffer);

                        (* WORKAROUND FOR HEAP CORRUPTION BUG *)

                        (*DiscardFragment (buffer);*)
                        buffer.length := 0;

                        success := success AND SendClientKey (state)
                                    AND SendChangeCipherSpec (state)
                                    AND SendFinished (state);

                        (* Are we there yet?  That depends on who sent the first Finished. *)

                        IF CAST(ADDRESS, state^.hashctx2) = NIL THEN
                            done := TRUE;
                        END (*IF*);

                (* 15 = certificate verify, not implemented here. *)

                | 16:   (* Client KeyExchange.  This is server code. *)

                        HandleClientKeyExchange(state, buffer.fragment, 4, length);

                | 20:   (* Finished.  Common code for server and client. *)

                        success := ReceiveFinished (state, buffer);

                        (* Are we there yet?  If hashctx2 is NIL we     *)
                        (* have just processed the second Finished      *)
                        (* message, i.e. we have reached the end of the *)
                        (* handshaking.  Otherwise, it's our turn       *)
                        (* to do the last bit of handshaking.           *)

                        IF CAST(ADDRESS, state^.hashctx2) <> NIL THEN
                            success := SendChangeCipherSpec (state)
                                            AND SendFinished (state);
                        END (*IF*);
                        done := TRUE;

                |
                ELSE
                        message := "Unhandled handshake type ";
                        AppendCard (type, message);
                        LogTransaction (state^.logID, message);
                        success := FALSE;
            END (*CASE*);
        ELSE
            Strings.Assign ("Received record, type ", message);
            AppendCard (buffer.contenttype, message);
            Strings.Append (", is not a handshake record", message);
            LogTransaction (state^.logID, message);
            success := FALSE;
        END (*IF*);

        DiscardFragment (buffer);
        RETURN success;

    END ReceivePartnerHandshake;

(************************************************************************)

PROCEDURE CreateStateRecord (S: Socket;  as_server: BOOLEAN;
                                    rlstate: RLState;
                                    logID: TransactionLogID): HSstate;

    (* Sets up the initial handshake protocol state.    *)

    VAR result: HSstate;

    BEGIN
        NEW (result);
        result^.IsServer := as_server;
        result^.bornagain := FALSE;
        result^.truncateMAC := FALSE;
        result^.sessID[0] := 0;
        result^.sessIDlength := 1;
        result^.sock := S;
        result^.rlstate := rlstate;
        result^.logID := logID;
        result^.serverkey := InitKey();
        result^.domain := NIL;
        result^.hashctx1 := NIL;
        result^.hashctx2 := NIL;
        result^.serveraddr := 0;
        WITH result^.security DO
            prf_algorithm           := tls_prf_sha256;
            compression_algorithm   := nocompression;
            master_secret           := Zero48;
            client_random           := Zero32;
            server_random           := Zero32;
            pending                 := NullSuite;
        END (*WITH*);
        RETURN result;
    END CreateStateRecord;

(************************************************************************)

PROCEDURE DoHandshake (S: Socket;  as_server: BOOLEAN;
                            serveraddr: CARDINAL;
                            VAR (*IN*) serverdomain: ARRAY OF CHAR;
                            rlstate: RLState;
                            logID: TransactionLogID;
                            VAR (*OUT*) ConnectionLost: BOOLEAN): BOOLEAN;

    (* Returns TRUE if the handshaking has proceeded to completion      *)
    (* successfully.                                                    *)

    (* A handshake record starts with code 22 (16H) to mean "handshake",*)
    (* then we have a two-byte version code and a two-byte length.      *)
    (* Those initial five bytes are loaded into the appropriate fields  *)
    (* of buffer by procedure GetFragment.  After this we have the      *)
    (* "fragment" field with                                            *)
    (*      HandshakeType (1 byte)                                      *)
    (*      length        (3 bytes)                                     *)
    (*      content       (depends on handshake type)                   *)

    (* The expected sequence, as seen by the server end, is:            *)
    (*      receive client Hello                                        *)
    (*      send server Hello                                           *)
    (*      (maybe) send server certificate                             *)
    (*      (maybe) send server key exchange                            *)
    (*      (maybe) send certificate request from server                *)
    (*      send server HelloDone                                       *)
    (*      (maybe) receive client certificate                          *)
    (*      receive client KeyExchange                                  *)
    (*      (maybe) receive CertificateVerify                           *)
    (*      receive ChangeCipherSpec (not a handshake message)          *)
    (*      receive Finished                                            *)
    (*      send ChangeCipherSpec                                       *)
    (*      send Finished                                               *)
    (* After this, application data can flow.                           *)

    (* From the viewpoint of the client, this is:                       *)
    (*      send client Hello                                           *)
    (*      receive server Hello                                        *)
    (*      (maybe) receive server certificate                          *)
    (*      (maybe) receive server key exchange                         *)
    (*      (maybe) receive certificate request from server             *)
    (*      receive server HelloDone                                    *)
    (*      (maybe) send client certificate                             *)
    (*      send client KeyExchange                                     *)
    (*      (maybe) send CertificateVerify                              *)
    (*      send ChangeCipherSpec (not a handshake message)             *)
    (*      send Finished                                               *)
    (*      receive ChangeCipherSpec                                    *)
    (*      receive Finished                                            *)
    (* After this, application data can flow.                           *)

    VAR done, success: BOOLEAN;
        state: HSstate;

    BEGIN
        ConnectionLost := FALSE;
        state := CreateStateRecord (S, as_server, rlstate, logID);
        state^.domain := NIL;
        state^.hashctx1 := SHA256Init();
        state^.hashctx2 := SHA256Init();

        done := FALSE;
        IF as_server THEN
            state^.serveraddr := 0;
            success := TRUE;
        ELSE
            state^.serveraddr := serveraddr;
            success := SendClientHello (state, serverdomain);
        END (*IF*);
        WHILE success AND NOT done DO
            success := ReceivePartnerHandshake (state, done, ConnectionLost);
        END (*WHILE*);
        DiscardKey (state^.serverkey);
        IF success THEN
            LogTransactionL (state^.logID, "Successful end of handshaking");
        ELSE
            LogTransactionL (state^.logID, "Handshaking failed");
            IF state^.bornagain THEN
                (* The reused session ID did not work.  This might  *)
                (* mean it's no longer valid, e.g. certificate      *)
                (* might have been renewed.                         *)
                DeleteSessID (state^.serveraddr, state^.sessID, state^.sessIDlength);
            END (*IF*);
        END (*IF*);
        DISPOSE (state);
        RETURN success AND NOT ConnectionLost;
    END DoHandshake;

(************************************************************************)
(*                            INITIALISATION                            *)
(************************************************************************)

PROCEDURE SetINIname (name: ARRAY OF CHAR);

    (* Takes note of our INI name. *)

    BEGIN
        TLSsessID.SetINIname (name);
        TLSCertificates.SetINIname (name);
        Strings.Assign (name, ININame);
    END SetINIname;

(************************************************************************)

BEGIN
    BlankDomainLegal := FALSE;
    CreateLock (encryptlock);
END TLSHandshake.

