IMPLEMENTATION MODULE TLSRSA;

        (********************************************************)
        (*                                                      *)
        (*                 TLS-RSA private key                  *)
        (*                                                      *)
        (*  Programmer:         P. Moylan                       *)
        (*  Started:            14 October 2024                 *)
        (*  Last edited:        17 August 2025                  *)
        (*  Status:             OK                              *)
        (*                                                      *)
        (********************************************************)


IMPORT RSAKeys, Strings, Base64, ASN1, BigNum;

FROM SYSTEM IMPORT LOC, ADR, CARD8;

FROM TLSBase IMPORT
    (* proc *)  GenerateRandom;

FROM RSA IMPORT
    (* proc *)  PubKeyEncode, PrivKeyEncode;

FROM FileOps IMPORT
    (* const*)  NoSuchChannel,
    (* type *)  ChanId,
    (* proc *)  OpenOldFile, CloseFile, ReadLine;

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

FROM MiscFuncs IMPORT
    (* proc *)  HeadMatch;

FROM BigNum IMPORT
    (* type *)  BN;

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

FROM Semaphores IMPORT
    (* type *)  Semaphore,
    (* proc *)  CreateSemaphore, DestroySemaphore, Wait;

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

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

(************************************************************************)
(*                          LOAD RSA PRIVATE KEY                        *)
(************************************************************************)

PROCEDURE LoadrawRSAPrivateKey (keydata: ByteStr): RSAKeys.RSAKeyType;

    (* Gets RSA private key from an ASN.1 sequence.  *)
    (* This version handles the sort of file that starts with   *)
    (*  -----BEGIN RSA PRIVATE KEY-----                         *)
    (* I'm not sure whether I need to handle other formats.     *)

    CONST
        MaxChars = 32768;
        MaxBytes = 32768;

    VAR numbers: ARRAY [0..9] OF BN;
        RSAKey: RSAKeys.RSAKeyType;

    BEGIN
        EVAL (ASN1.GetNumericArray (keydata.data^, numbers));
        DiscardBS (keydata);

        (* Assign the values to the fields of our key. *)

        BigNum.Discard (numbers[0]);
        RSAKey.n       := numbers[1];
        RSAKey.public  := numbers[2];
        RSAKey.private := numbers[3];
        RSAKey.p       := numbers[4];
        RSAKey.q       := numbers[5];
        RSAKey.dp      := numbers[6];
        RSAKey.dq      := numbers[7];
        RSAKey.qinv    := numbers[8];

        RETURN RSAKey;

    END LoadrawRSAPrivateKey;

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

PROCEDURE LoadRSAPrivateKey (filename: ARRAY OF CHAR): RSAKeys.RSAKeyType;

    (* Gets RSA private key from a file.  *)
    (* This version handles the sort of file that starts with   *)
    (*  -----BEGIN PRIVATE KEY-----                             *)
    (* or                                                       *)
    (*  -----BEGIN RSA PRIVATE KEY-----                         *)
    (* I'm not sure whether I need to handle other formats.     *)

    CONST
        MaxChars = 32768;
        MaxBytes = 32768;

    VAR cid: ChanId;
        line: ARRAY [0..255] OF CHAR;
        pstring: VarStringPtr;
        pbytes: ByteStringPtr;
        part: ARRAY [0..7] OF ByteStr;
        keydata: ByteStr;
        RSAKey: RSAKeys.RSAKeyType;
        simpleformat: BOOLEAN;

    BEGIN
        cid := OpenOldFile (filename, FALSE, FALSE);
        IF cid = NoSuchChannel THEN
            (* Fatal error *)
            WriteString ("FATAL ERROR: Cannot open ");
            WriteString (filename);  WriteLn;
            WriteString ("Program halted");  WriteLn;
            HALT;
        END (*IF*);

        simpleformat := FALSE;

        (* Skip past the header line(s). *)

        REPEAT
            ReadLine (cid, line);
            IF HeadMatch (line, "-----BEGIN ") THEN
                simpleformat := line[11] = 'R';
            END (*IF*);
        UNTIL line[0] <> '-';

        (* Read the Base64 string. *)

        ALLOCATE (pstring, MaxChars);
        pstring^[0] := CHR(0);
        WHILE line[0] <> '-' DO
            Strings.Append (line, pstring^);
            ReadLine (cid, line);
        END (*WHILE*);
        CloseFile (cid);

        ALLOCATE (pbytes, MaxBytes);
        EVAL (Base64.Decode (pstring^, pbytes^));
        DEALLOCATE (pstring, MaxChars);

        (* Now we have the actual binary data.  In the BEGIN PRIVATE    *)
        (* KEY format the top level is a sequence of three things: an   *)
        (* integer, an object ID, and a long octet string.  In the      *)
        (* BEGIN RSA PRIVATE KEY format we have only the octet string.  *)

        IF simpleformat THEN
            keydata.data := pbytes;
            keydata.size := MaxBytes;
            keydata.allocated := 0;
        ELSE
            EVAL (ASN1.ExtractParts (pbytes^, part));
            keydata := ASN1.CopyContent (part[2].data^);
        END (*IF*);

        RSAKey := LoadrawRSAPrivateKey (keydata);
        DEALLOCATE (pbytes, MaxBytes);
        RETURN RSAKey;

    END LoadRSAPrivateKey;

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

PROCEDURE RSAKeySize (key: RSAKeys.RSAKeyType): CARDINAL;

    (* Returns the length in bytes of the modulus of the key.   *)

    BEGIN
        RETURN BigNum.NbytesNLZ (key.n);
    END RSAKeySize;

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

PROCEDURE PKCS1Pad (key: RSAKeys.RSAKeyType;  L: CARDINAL;
                            VAR (*IN*) message: ARRAY OF LOC): ByteStr;

    (* Returns a copy of the message, of length L, with padding as      *)
    (* prescribed by RSAES-PKCS1-v1_5 in RFC 8017 (page 29).  The key   *)
    (* paramater is needed so that we can work out the size of the result.*)

    VAR j, k: CARDINAL;  p: ByteStringPtr;
        result: ByteStr;

    BEGIN
        k := RSAKeySize (key);
        MakeBS (result, k);
        result.data^[0] := 0; result.data^[1] := 2;
        p := ADR(result.data^[2]);
        GenerateRandom (k - L - 3, p^);
        j := k - L - 1;
        result.data^[j] := 0;
        p := ADR(result.data^[j+1]);
        Copy (ADR(message), p, L);
        RETURN result;
    END PKCS1Pad;

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

PROCEDURE PKCS1Encrypt (key: RSAKeys.RSAKeyType;  L: CARDINAL;
                            VAR (*IN*) message: ARRAY OF LOC): ByteStr;

    (* Returns an encrypted copy of the message, of length L, using     *)
    (* the algorithm RSAES-PKCS1-V1_5-ENCRYPT in RFC 8017 (page 29).    *)
    (* The result is longer than L because padding is added.            *)

    VAR j, k: CARDINAL;  p: ByteStringPtr;
        padded, result: ByteStr;
        S, T: BN;

    BEGIN
        k := RSAKeySize (key);
        MakeBS (padded, k);
        padded.data^[0] := 0; padded.data^[1] := 2;
        p := ADR(padded.data^[2]);
        GenerateRandom (k - L - 3, p^);
        j := k - L - 1;
        padded.data^[j] := 0;
        p := ADR(padded.data^[j+1]);
        Copy (ADR(message), p, L);
        S := BigNum.BinToBN (padded.data^, 0, k);
        T := PubKeyEncode (S, key);
        DiscardBS (padded);
        MakeBS (result, k);
        result.size := BigNum.BNtoBytes (T, result.data^);
        RETURN result;
    END PKCS1Encrypt;

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

END TLSRSA.

