(**************************************************************************)
(*                                                                        *)
(*  Encryption library                                                    *)
(*  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       *)
(*                                                                        *)
(**************************************************************************)

<* WOFF316+ *>

IMPLEMENTATION MODULE TLSPRF;

        (********************************************************)
        (*                                                      *)
        (*      Pseudo-random function for key generation       *)
        (*         as specified in RFC 5246, section 5.         *)
        (*                                                      *)
        (*  Programmer:         P. Moylan                       *)
        (*  Started:            6 October 2017                  *)
        (*  Last edited:        13 June 2025                    *)
        (*  Status:             Almost complete                 *)
        (*                                                      *)
        (********************************************************)


FROM SYSTEM IMPORT
    (* type *)  CARD8, LOC, ADDRESS,
    (* proc *)  ADR;

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

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

FROM TLSHMAC IMPORT
    (* type *)  HMACType,
    (* proc *)  HMAClength, ComputeHMac;

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

(************************************************************************)
(*                                                                      *)
(*                          GENERIC FUNCTIONS                           *)
(*                                                                      *)
(*  This section contains mostly pseudo-code, as a template for future  *)
(*  expansion into real code.                                           *)
(*                                                                      *)
(************************************************************************)

TYPE
    (* The standard is vague about the types of the parameters, so I am *)
    (* defaulting to the SHA_256 case.                                  *)

    (*DigestType = SHA2_DigestType;
    DigestString = ARRAY [0..31] OF LOC;*)

(************************************************************************)
(*                       BYTE STRING CONCATENATION                      *)
(************************************************************************)

PROCEDURE Concat (A, B: ByteStr): ByteStr;

    (* Produces the result of A followed by B. A and B are not destroyed. *)

    VAR result: ByteStr;

    BEGIN
        result.size := A.size + B.size;
        ALLOCATE (result.data, result.size);
        result.allocated := result.size;
        Copy (A.data, result.data, A.size);
        Copy (B.data, AddOffset (result.data, A.size), B.size);
        RETURN result;
    END Concat;

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

PROCEDURE HMAC_hash (mt: HMACType;  secret: ARRAY OF CARD8;  secretlength: CARDINAL;
                         indata: ByteStr;
                         VAR (*OUT*) outdata: ARRAY OF CARD8);

    (* This is a redundant function, because we could call ComputeMac   *)
    (* directly, but for clarity I am trying to follow the notation of  *)
    (* RFC 5246 as closely as possible.                                 *)

    VAR outsize: CARDINAL;

    BEGIN
        outsize := HMAClength(mt);
        ComputeHMac (mt, secret, secretlength, indata.data, indata.size, outdata, outsize);
    END HMAC_hash;

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

PROCEDURE P_hash (mt: HMACType;  secretlength: CARDINAL;  secret: ARRAY OF CARD8;
                  seed: ByteStr;
                  needed: CARDINAL;  VAR (*OUT*) result: ARRAY OF CARD8);

    (* Produces a pseudorandom result of length needed. *)
    (* Parameters seed and secret must be BigEndian.    *)

    VAR p: ADDRESS;
        pos, partlength, L: CARDINAL;

    VAR A, indata: ByteStr;
        digest: ARRAY [0..63] OF CARD8;

    BEGIN
        L := HMAClength (mt);
        pos := 0;
        p := ADR(result);
        A := CopyOfBS (seed);

        (* Remark: A has size L _except_ in the first iteration. *)

        REPEAT
            (* Compute A(i) from A(i-1). *)

            HMAC_hash (mt, secret, secretlength, A, digest);
            DEALLOCATE (A.data, A.allocated);
            A.size := L;  A.allocated := L;
            ALLOCATE (A.data, L);
            Copy (ADR(digest), A.data, L);

            (* Now generate another block of data. *)

            indata := Concat (A, seed);
            HMAC_hash (mt, secret, secretlength, indata, digest);
            DiscardBS (indata);
            partlength := L;
            IF partlength > needed THEN
                partlength := needed;
            END (*IF*);
            Copy (ADR(digest), p, partlength);
            INC (pos, partlength);
            p := AddOffset (p, partlength);
            DEC (needed, partlength);
        UNTIL needed = 0;

        DiscardBS (A);

    END P_hash;

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

PROCEDURE PRF (prf_alg: PRFAlgorithm;  secretlength: CARDINAL;  secret: ARRAY OF CARD8;
                label: ARRAY OF CHAR;
                seedlength: CARDINAL;  seed: ARRAY OF CARD8;
                needed: CARDINAL;  VAR (*OUT*) result: ARRAY OF CARD8);

    (* The pseudo-random function of RFC 5246, section 5.  The 'needed' *)
    (* parameter says how long result must be.                          *)

    VAR indata: ByteStr;
        mt: HMACType;
        L: CARDINAL;

    BEGIN
        (* All cipher suites defined in RFC 5246 use the same prf_alg,  *)
        (* based on hmacsha256.                                         *)

        mt := hmacsha256;

        (* Let indata be label followed by seed *)

        L := LENGTH(label);
        MakeBS (indata, L + seedlength);
        Copy (ADR(label), indata.data, L);
        Copy (ADR(seed), AddOffset(indata.data, L), seedlength);

        P_hash (mt, secretlength, secret, indata, needed, result);
        DiscardBS (indata);

    END PRF;

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

(*
PROCEDURE GenerateKeys (needed: CARDINAL;  VAR (*OUT*) key_block: ARRAY OF CARD8);

    (* Section 6.3 of the TLS standard.  We use the PRF function to     *)
    (* generate a key string, then we partition it into the up to six   *)
    (* keys that we really want:                                        *)
    (*      client_write_MAC_key[SecurityParameters.mac_key_length]     *)
    (*      server_write_MAC_key[SecurityParameters.mac_key_length]     *)
    (*      client_write_key[SecurityParameters.enc_key_length]         *)
    (*      server_write_key[SecurityParameters.enc_key_length]         *)
    (*      client_write_IV[SecurityParameters.fixed_iv_length]         *)
    (*      server_write_IV[SecurityParameters.fixed_iv_length]         *)
    (* Unused values are empty.                                         *)

    (* Implementation note: The currently defined cipher suite which    *)
    (* requires the most material is AES_256_CBC_SHA256. It requires    *)
    (* 2 x 32 byte keys and 2 x 32 byte MAC keys, for a total 128 bytes *)
    (* of key material.                                                 *)

    (* This procedure probably belongs in a different module, but for   *)
    (* now I am keeping it here for the sake of documenting it.         *)

    BEGIN
        (*
        PRF(prf_alg, SecurityParameters.master_secret,
                           "key expansion",
                           SecurityParameters.server_random +
                           SecurityParameters.client_random,
                           needed, key_block);
        *)
    END GenerateKeys;
*)

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

END TLSPRF.

