IMPLEMENTATION MODULE TLSHMAC;

        (********************************************************)
        (*                                                      *)
        (*              Message authentication code             *)
        (*                                                      *)
        (*  Programmer:         P. Moylan                       *)
        (*  Started:            16 January 2021                 *)
        (*  Last edited:        13 June 2025                    *)
        (*  Status:             Looks complete                  *)
        (*                                                      *)
        (********************************************************)


FROM SYSTEM IMPORT CARD8, ADR;

IMPORT Strings;

FROM HMAC IMPORT
    (* proc *)  HMAC_SHA1, HMAC_SHA256, HMAC_SHA384, HMAC_SHA512, HMAC_MD5;

FROM VarStrings IMPORT ByteStringPtr;

FROM MiscFuncs IMPORT
    (* proc *)  Swap2;

FROM LowLevel IMPORT
    (* proc *)  Copy;

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

TYPE
    CardArray = ARRAY HMACType OF CARDINAL;

VAR
    Size: CardArray;

(************************************************************************)
(*                      THE LENGTH OF A MAC DIGEST                      *)
(************************************************************************)

PROCEDURE HMAClength (mt: HMACType): CARDINAL;

    (* Returns the number of bytes in the HMAC. *)

    BEGIN
        RETURN Size[mt];
    END HMAClength;

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

PROCEDURE HMACkeylength (mt: HMACType): CARDINAL;

    (* Returns the number of bytes in the HMAC key.  For all HMAC       *)
    (* algorithms listed in RFC 5246, the key length is equal to the    *)
    (* HMAC length.                                                     *)

    BEGIN
        RETURN Size[mt];
    END HMACkeylength;

(************************************************************************)
(*                      NAME OF THE MAC ALGORITHM                       *)
(************************************************************************)

PROCEDURE AppendHMACName (mt: HMACType;  VAR (*INOUT*) message: ARRAY OF CHAR);

    (* Appends the name of the HMAC to the message. *)

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

    BEGIN
        CASE mt OF
            | nullhmac:         name := "nullhmac";
            | hmacmd5:          name := "hmacmd5";
            | hmacsha1:         name := "hmacsha1";
            | hmacsha1_96:      name := "hmacsha1_96";
            | hmacsha256:       name := "hmacsha256";
            | hmacsha256_96:    name := "hmacsha256_96";
            | hmacsha384:       name := "hmacsha384";
            | hmacsha384_96:    name := "hmacsha384_96";
            | hmacsha512:       name := "hmacsha512";
            | hmacsha512_96:    name := "hmacsha512_96";
            | hmacmd5_96:       name := "hmacmd5_96";
            | none:             name := "none";
        ELSE
            name := "unknown";
        END (*IF*);
        Strings.Append (name, message);
    END AppendHMACName;

(************************************************************************)
(*                           GENERATING AN HMAC                         *)
(************************************************************************)

PROCEDURE ComputeHMac (mt: HMACType;  VAR (*IN*) key: ARRAY OF CARD8;
                       keylength: CARDINAL;
                        dataptr: ByteStringPtr;  datalength: CARDINAL;
                         VAR (*OUT*) outdata: ARRAY OF CARD8;
                          resultlength: CARDINAL);

    (* The HMAC is outdata, of size resultlength bytes.  The "normal"   *)
    (* can be found by calling HMAClength, but a smaller length can be  *)
    (* specified if a truncated HMAC is desired.                        *)
    (* The input is an unencrypted byte string.                         *)

    VAR p: ByteStringPtr;  length: CARDINAL;
        digest: ARRAY [0..127] OF CARD8;

    BEGIN
        IF mt = none THEN
            RETURN;
        END (*IF*);

        length := datalength;
        p := dataptr;
        CASE mt OF
            | nullhmac:
                (* do nothing *)
            | hmacsha1, hmacsha1_96:
                HMAC_SHA1 (key, keylength, p^, length, digest);
            | hmacsha256, hmacsha256_96:
                HMAC_SHA256 (key, keylength, p^, length, digest);
            | hmacsha384, hmacsha384_96:
                HMAC_SHA384 (key, keylength, p^, length, digest);
            | hmacsha512, hmacsha512_96:
                HMAC_SHA512 (key, keylength, p^, length, digest);
            | hmacmd5, hmacmd5_96:
                HMAC_MD5 (key, keylength, p^, length, digest);
            | none:
                (* Do nothing; should never happen *)
        END (*CASE*);
        IF resultlength > 0 THEN
            Copy (ADR(digest), ADR(outdata), resultlength);
        END (*IF*);

    END ComputeHMac;

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

PROCEDURE SetSize;

    (* I used to have the "Size" array declared as a constant, but      *)
    (* doing it this way is safer given the risk that a revision might  *)
    (* alter the order of MAC algorithms.                               *)

    VAR k: HMACType;

    BEGIN
        FOR k := MIN (HMACType) TO MAX (HMACType) DO
            CASE k OF
                | nullhmac:
                    Size[k] := 0;
                | hmacsha1:
                    Size[k] := 20;
                | hmacsha1_96:
                    Size[k] := 12;
                | hmacsha256:
                    Size[k] := 32;
                | hmacsha256_96:
                    Size[k] := 12;
                | hmacsha384:
                    Size[k] := 48;
                | hmacsha384_96:
                    Size[k] := 12;
                | hmacsha512:
                    Size[k] := 64;
                | hmacsha512_96:
                    Size[k] := 12;
                | hmacmd5:
                    Size[k] := 16;
                | hmacmd5_96:
                    Size[k] := 12;
                | none:
                    Size[k] := 0;
            END (*CASE*);
        END (*FOR*);
    END SetSize;

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

BEGIN
    SetSize;
END TLSHMAC.

