
(********************************************************************)
(*                                                                  *)
(*  x509cert.s7i  Support for X.509 public key certificates.        *)
(*  Copyright (C) 2013 - 2016, 2018, 2019  Thomas Mertes            *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library 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 Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "asn1.s7i";
include "pkcs1.s7i";
include "elliptic.s7i";
include "msgdigest.s7i";
include "time.s7i";
include "duration.s7i";


const type: algorithmIdentifierType is new struct
    var string: algorithm is "";     # OBJECT IDENTIFIER
    var string: parameters is "";    # ANY DEFINED BY algorithm OPTIONAL
  end struct;

const type: nameType is hash [string] string;

const type: validityType is new struct
    var time: notBefore is time.value;
    var time: notAfter is time.value;
  end struct;

const type: subjectPublicKeyInfoType is new struct
    var algorithmIdentifierType: algorithm is algorithmIdentifierType.value;
    var string: subjectPublicKey is "";  # BIT STRING
    var rsaKey: publicRsaKey is rsaKey.value;
    var ellipticCurve: eCurve is ellipticCurve.value;
    var ecPoint: publicEccKey is ecPoint.value;
  end struct;

const type: tbsCertificateType is new struct
    var integer: version is 0;  # v1
    var string: serialNumber is "";  # INTEGER (up to 20 octets in length)
    var algorithmIdentifierType: signature is algorithmIdentifierType.value;
    var nameType: issuer is nameType.value;
    var validityType: validity is validityType.value;
    var nameType: subject is nameType.value;
    var subjectPublicKeyInfoType: subjectPublicKeyInfo is subjectPublicKeyInfoType.value;
(*
        issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
                             -- If present, version shall be v2 or v3
        subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
                             -- If present, version shall be v2 or v3
        extensions      [3]  EXPLICIT Extensions OPTIONAL
                             -- If present, version shall be v3
*)
  end struct;

const type: x509cert is new struct
    var tbsCertificateType:      tbsCertificate     is tbsCertificateType.value;
    var algorithmIdentifierType: signatureAlgorithm is algorithmIdentifierType.value;
    var string:                  signatureValue     is "";
    var string:                  messageDigest      is "";
  end struct;

const type: certAndKey is new struct
    var array string: certList is 0 times "";
    var rsaKey: privateRsaKey is rsaKey.value;
    var bigInteger: privateEccKey is 0_;
  end struct;

const string: ALIASED_ENTRY_NAME_OID        is encodeObjectIdentifier([] (2, 5, 4, 1));   # "U\4;\1;"
const string: KNOWLEDGE_INFORMATION_OID     is encodeObjectIdentifier([] (2, 5, 4, 2));   # "U\4;\2;"
const string: COMMON_NAME_OID               is encodeObjectIdentifier([] (2, 5, 4, 3));   # "U\4;\3;"
const string: SURNAME_OID                   is encodeObjectIdentifier([] (2, 5, 4, 4));   # "U\4;\4;"
const string: SERIAL_NUMBER_OID             is encodeObjectIdentifier([] (2, 5, 4, 5));   # "U\4;\5;"
const string: COUNTRY_NAME_OID              is encodeObjectIdentifier([] (2, 5, 4, 6));   # "U\4;\6;"
const string: LOCALITY_NAME_OID             is encodeObjectIdentifier([] (2, 5, 4, 7));   # "U\4;\a"
const string: STATE_OR_OR_PROVINCE_NAME_OID is encodeObjectIdentifier([] (2, 5, 4, 8));   # "U\4;\b"
const string: STREET_ADDRESS_OID            is encodeObjectIdentifier([] (2, 5, 4, 9));   # "U\4;\t"
const string: ORGANIZATION_NAME_OID         is encodeObjectIdentifier([] (2, 5, 4, 10));  # "U\4;\n"
const string: ORGANIZATION_UNIT_NAME_OID    is encodeObjectIdentifier([] (2, 5, 4, 11));  # "U\4;\v"
const string: TITLE_OID                     is encodeObjectIdentifier([] (2, 5, 4, 12));  # "U\4;\f"

const string: SUBJECT_KEY_IDENTIFIER_OID    is encodeObjectIdentifier([] (2, 5, 29, 14));  # "U\29;\14;"
const string: KEY_USAGE_OID                 is encodeObjectIdentifier([] (2, 5, 29, 15));  # "U\29;\15;"
const string: SUBJECT_ALT_NAME              is encodeObjectIdentifier([] (2, 5, 29, 17));  # "U\29;\17;"
const string: BASIC_CONSTRAINTS_OID         is encodeObjectIdentifier([] (2, 5, 29, 19));  # "U\29;\19;"
const string: CRLD_DISTRIBUTION_POINTS_OID  is encodeObjectIdentifier([] (2, 5, 29, 31));  # "U\29;\31;"
const string: CERTIFICATE_POLICIES_OID      is encodeObjectIdentifier([] (2, 5, 29, 32));  # "U\29; "
const string: AUTHORITY_KEY_IDENTIFIER_OID  is encodeObjectIdentifier([] (2, 5, 29, 35));  # "U\29;#"
const string: EXT_KEY_USAGE_OID             is encodeObjectIdentifier([] (2, 5, 29, 37));  # "U\29;%"

const string: CERT_TYPE_OID                 is encodeObjectIdentifier([] (2, 16, 840, 1, 113730, 1, 1));  # "`\134;H\1;\134;�B\1;\1;"

# iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1
const string: PKCS_1 is  encodeObjectIdentifier([] (1, 2, 840, 113549, 1, 1));  # "*\134;H\134;\247;\r\1;\1;"
const string: RSA_ENCRYPTION_OID         is PKCS_1 & "\1;";
const string: MD2_WITH_RSA_ENCRYPTION    is PKCS_1 & "\2;";
const string: MD5_WITH_RSA_ENCRYPTION    is PKCS_1 & "\4;";
const string: SHA1_WITH_RSA_ENCRYPTION   is PKCS_1 & "\5;";
const string: RSAES_OAEP_OID             is PKCS_1 & "\7;";
const string: MGF1_OID                   is PKCS_1 & "\8;";
const string: P_SPECIFIED_OID            is PKCS_1 & "\9;";
const string: RSASSA_PSS_OID             is PKCS_1 & "\10;";
const string: SHA256_WITH_RSA_ENCRYPTION is PKCS_1 & "\11;";
const string: SHA384_WITH_RSA_ENCRYPTION is PKCS_1 & "\12;";
const string: SHA512_WITH_RSA_ENCRYPTION is PKCS_1 & "\13;";

const string: EC_PUBLIC_KEY is encodeObjectIdentifier([] (1, 2, 840, 10045, 2, 1));     # "*\134;H\206;=\2;\1;"

const string: SECP192K1_OID is encodeObjectIdentifier([] (1, 3, 132, 0, 31));
const string: SECP192R1_OID is encodeObjectIdentifier([] (1, 2, 840, 10045, 3, 1, 1));
const string: SECP224K1_OID is encodeObjectIdentifier([] (1, 3, 132, 0, 32));
const string: SECP224R1_OID is encodeObjectIdentifier([] (1, 3, 132, 0, 33));
const string: SECP256K1_OID is encodeObjectIdentifier([] (1, 3, 132, 0, 10));
const string: SECP256R1_OID is encodeObjectIdentifier([] (1, 2, 840, 10045, 3, 1, 7));
const string: SECP384R1_OID is encodeObjectIdentifier([] (1, 3, 132, 0, 34));
const string: SECP521R1_OID is encodeObjectIdentifier([] (1, 3, 132, 0, 34));

const string: SHA1_OID is encodeObjectIdentifier([] (1, 3, 14, 3, 2, 26));
const string: SHA256_OID is encodeObjectIdentifier([] (2, 16, 840, 1, 101, 3, 4, 2, 1));
const string: SHA384_OID is encodeObjectIdentifier([] (2, 16, 840, 1, 101, 3, 4, 2, 2));
const string: SHA512_OID is encodeObjectIdentifier([] (2, 16, 840, 1, 101, 3, 4, 2, 3));

const string: RSA_WITH_SHA256 is encodeObjectIdentifier([] (1, 2, 840, 113549, 1, 1, 11));
const string: ECDSA_WITH_SHA256 is encodeObjectIdentifier([] (1, 2, 840, 10045, 4, 3, 2));


const func algorithmIdentifierType: getAlgorithmIdentifier (in string: stri, inout integer: pos) is func
  result
    var algorithmIdentifierType: algId is algorithmIdentifierType.value;
  local
    var asn1DataElement: dataElem is asn1DataElement.value;
    var integer: beyond is 0;
  begin
    dataElem := getAsn1DataElement(stri, pos);
    # writeln("in getAlgorithmIdentifier " <& classTagName[ord(dataElem.tagCategory)]);
    if dataElem.tagCategory = tagSequence then
      beyond := pos + dataElem.length;
      dataElem := getAsn1DataElement(stri, pos);
      # writeln("in getAlgorithmIdentifier " <& classTagName[ord(dataElem.tagCategory)]);
      if dataElem.tagCategory = tagObjectIdentifier then
        algId.algorithm := getData(stri, pos, dataElem);
      end if;
      if pos < beyond then
        dataElem := getAsn1DataElement(stri, pos);
        # writeln("in getAlgorithmIdentifier " <& classTagName[ord(dataElem.tagCategory)]);
        if dataElem.tagCategory = tagNull then
          algId.parameters := "";
        elsif dataElem.tagCategory = tagObjectIdentifier then
          algId.parameters := getData(stri, pos, dataElem);
          # writeln("algId.parameters: " <& literal(algId.parameters));
        else
          writeln("*** Unexpected data element ***");
        end if;
      end if;
    end if;
  end func;


const func string: genAlgorithmIdentifier (in algorithmIdentifierType: algId) is func
  result
    var string: stri is "";
  begin
    stri := genAsn1Element(tagObjectIdentifier, algId.algorithm);
    if algId.parameters = "" then
      stri &:= genAsn1Element(tagNull, "");
    else
      stri &:= genAsn1Element(tagObjectIdentifier, algId.parameters);
    end if;
    stri := genAsn1Sequence(stri);
  end func;


const func rsaKey: getRsaKey (in string: stri) is func
  result
    var rsaKey: anRsaKey is rsaKey.value;
  local
    var integer: pos is 1;
    var asn1DataElement: dataElem is asn1DataElement.value;
    var bigInteger: modulus is 0_;
    var bigInteger: exponent is 0_;
  begin
    dataElem := getAsn1DataElement(stri, pos);
    if dataElem.tagCategory = tagSequence then
      dataElem := getAsn1DataElement(stri, pos);
      if dataElem.tagCategory = tagInteger then
        modulus := bytes2BigInt(getData(stri, pos, dataElem), UNSIGNED, BE);
        dataElem := getAsn1DataElement(stri, pos);
        if dataElem.tagCategory = tagInteger then
          exponent := bytes2BigInt(getData(stri, pos, dataElem), UNSIGNED, BE);
          anRsaKey := rsaKey(modulus, exponent);
        end if;
      end if;
    end if;
  end func;


const func string: genX509RsaKey (in rsaKey: anRsaKey) is func
  result
    var string: stri is "";
  begin
    stri := genAsn1Integer(bytes(anRsaKey.modulus, SIGNED, BE));
    stri &:= genAsn1Integer(bytes(anRsaKey.exponent, SIGNED, BE));
    stri := genAsn1Sequence(stri);
  end func;


const func ellipticCurve: getEllipticCurveFromOid (in string: oid) is func
  result
    var ellipticCurve: curve is ellipticCurve.value;
  begin
    if oid = SECP192K1_OID then
      curve := secp192k1;
    elsif oid = SECP192R1_OID then
      curve := secp192r1;
    elsif oid = SECP224K1_OID then
      curve := secp224k1;
    elsif oid = SECP224R1_OID then
      curve := secp224r1;
    elsif oid = SECP256K1_OID then
      curve := secp256k1;
    elsif oid = SECP256R1_OID then
      curve := secp256r1;
    elsif oid = SECP384R1_OID then
      curve := secp384r1;
    elsif oid = SECP521R1_OID then
      curve := secp521r1;
    end if;
  end func;


const func string: getEllipticCurveOid (in ellipticCurve: curve) is func
  result
    var string: oid is "";
  begin
    if curve.name = "secp192k1" then
      oid := SECP192K1_OID;
    elsif curve.name = "secp192r1" then
      oid := SECP192R1_OID;
    elsif curve.name = "secp224k1" then
      oid := SECP224K1_OID;
    elsif curve.name = "secp224r1" then
      oid := SECP224R1_OID;
    elsif curve.name = "secp256k1" then
      oid := SECP256K1_OID;
    elsif curve.name = "secp256r1" then
      oid := SECP256R1_OID;
    elsif curve.name = "secp384r1" then
      oid := SECP384R1_OID;
    elsif curve.name = "secp512r1" then
      oid := SECP521R1_OID;
    end if;
  end func;


const func subjectPublicKeyInfoType: getPublicKeyInfo (in string: stri, inout integer: pos) is func
  result
    var subjectPublicKeyInfoType: keyInfo is subjectPublicKeyInfoType.value;
  local
    var asn1DataElement: dataElem is asn1DataElement.value;
  begin
    # writeln("in getPublicKeyInfo " <& pos);
    dataElem := getAsn1DataElement(stri, pos);
    # writeln("tag: " <& classTagName[ord(dataElem.tagCategory)]);
    if dataElem.tagCategory = tagSequence then
      keyInfo.algorithm := getAlgorithmIdentifier(stri, pos);
      dataElem := getAsn1DataElement(stri, pos);
      # writeln("tag: " <& classTagName[ord(dataElem.tagCategory)]);
      if dataElem.tagCategory = tagBitString then
        # The initial octet of a bit-string encodes
        # the number of unused bits in the final octet.
        keyInfo.subjectPublicKey := getData(stri, pos, dataElem);
        if keyInfo.subjectPublicKey[1] <> '\0;' then
          writeln("Initial octet of bit-string: " <& ord(keyInfo.subjectPublicKey[1]));
        end if;
        keyInfo.subjectPublicKey := keyInfo.subjectPublicKey[2 ..];
        # keyInfo.subjectPublicKey := getData(stri, pos, dataElem)[2 ..];
        # writeln("subjectPublicKey: " <& literal(keyInfo.subjectPublicKey));
        # writeln("length(subjectPublicKey): " <& length(keyInfo.subjectPublicKey));
        # writeln("algorithm: " <& literal(keyInfo.algorithm.algorithm));
        # writeln("algorithm parameters: " <& literal(keyInfo.algorithm.parameters));
        if keyInfo.algorithm.algorithm = RSA_ENCRYPTION_OID then
          # writeln("RSA_ENCRYPTION_OID");
          keyInfo.publicRsaKey := getRsaKey(keyInfo.subjectPublicKey);
        elsif keyInfo.algorithm.algorithm = EC_PUBLIC_KEY then
          # writeln("EC_PUBLIC_KEY");
          keyInfo.eCurve := getEllipticCurveFromOid(keyInfo.algorithm.parameters);
          if keyInfo.eCurve.bits <> 0 then
            keyInfo.publicEccKey := ecPointDecode(keyInfo.eCurve, keyInfo.subjectPublicKey);
          end if;
        else
          writeln("*** Unknown algorithm ***");
        end if;
      end if;
    end if;
  end func;


const func string: genX509PublicKeyInfo (in subjectPublicKeyInfoType: keyInfo) is func
  result
    var string: stri is "";
  local
    var string: subjectPublicKey is "";
  begin
    stri := genAlgorithmIdentifier(keyInfo.algorithm);
    if keyInfo.subjectPublicKey <> "" then
      subjectPublicKey := keyInfo.subjectPublicKey;
    else
      subjectPublicKey := genX509RsaKey(keyInfo.publicRsaKey);
    end if;
    # The initial octet of a bit-string encodes
    # the number of unused bits in the final octet.
    stri &:= genAsn1Element(tagBitString, "\0;" & subjectPublicKey);
    stri := genAsn1Sequence(stri);
  end func;


const func nameType: getName (in string: stri, inout integer: pos) is func
  result
    var nameType: name is nameType.value;
  local
    var asn1DataElement: dataElem is asn1DataElement.value;
    var integer: posAfterwards is 0;
    var string: attrKey is "";
    var string: attrValue is "";
  begin
    # writeln("in getName");
    dataElem := getAsn1DataElement(stri, pos);
    # writeln("tag: " <& classTagName[ord(dataElem.tagCategory)]);
    if dataElem.tagCategory = tagSequence then
      posAfterwards := pos + dataElem.length;
      while pos < posAfterwards do
        dataElem := getAsn1DataElement(stri, pos);
        if dataElem.tagCategory = tagSet then
          dataElem := getAsn1DataElement(stri, pos);
          if dataElem.tagCategory = tagSequence then
            dataElem := getAsn1DataElement(stri, pos);
            if dataElem.tagCategory = tagObjectIdentifier then
              attrKey := getData(stri, pos, dataElem);
            end if;
            dataElem := getAsn1DataElement(stri, pos);
            attrValue := getData(stri, pos, dataElem);
            name @:= [attrKey] attrValue;
            # writeln("getName: " <& objectIdentifier(attrKey) <& ": " <& literal(attrValue));
          end if;
        end if;
      end while;
    end if;
  end func;


const func string: genX509Name (in nameType: name) is func
  result
    var string: stri is "";
  local
    var string: attrKey is "";
    var string: attrValue is "";
    var string: setElement is "";
  begin
    for attrValue key attrKey range name do
      setElement := genAsn1Element(tagObjectIdentifier, attrKey);
      setElement &:= genAsn1Element(tagPrintableString, attrValue);
      setElement := genAsn1Sequence(setElement);
      stri &:= genAsn1Set(setElement);
    end for;
    stri := genAsn1Sequence(stri);
  end func;


const func time: getTime_yymmddhhmmssZ (in string: stri) is func
  result
    var time: aTime is time.value;
  local
    var integer: yearInCentury is 0;
    var integer: referenceYear is 0;
    var integer: referenceCentury is 0;
    var integer: possibleYear1 is 0;
    var integer: possibleYear2 is 0;
    var integer: possibleYear3 is 0;
    var integer: diffToYear1 is 0;
    var integer: diffToYear2 is 0;
    var integer: diffToYear3 is 0;
    var integer: year is 0;
  begin
    yearInCentury := integer(stri[.. 2]);
    referenceYear := time(NOW).year;
    referenceCentury := referenceYear mdiv 100;
    possibleYear1 := pred(referenceCentury) * 100 + yearInCentury;
    possibleYear2 :=      referenceCentury  * 100 + yearInCentury;
    possibleYear3 := succ(referenceCentury) * 100 + yearInCentury;
    diffToYear1 := abs(referenceYear - possibleYear1);
    diffToYear2 := abs(referenceYear - possibleYear2);
    diffToYear3 := abs(referenceYear - possibleYear3);
    # writeln("possibleYear1: " <& possibleYear1 <& " diff: " <& diffToYear1);
    # writeln("possibleYear2: " <& possibleYear2 <& " diff: " <& diffToYear2);
    # writeln("possibleYear3: " <& possibleYear3 <& " diff: " <& diffToYear3);
    if  diffToYear1 < diffToYear2 and diffToYear1 < diffToYear3 then
      year := possibleYear1;
    elsif diffToYear3 < diffToYear1 and diffToYear3 < diffToYear2 then
      year := possibleYear3;
    else
      year := possibleYear2;
    end if;
    aTime := time(year,
                  integer(stri[ 3 len 2]),  # month
                  integer(stri[ 5 len 2]),  # day
                  integer(stri[ 7 len 2]),  # hour
                  integer(stri[ 9 len 2]),  # minute
                  integer(stri[11 len 2])); # second
    # writeln("getTime_yymmddhhmmssZ(" <& literal(stri) <& ") --> " <& aTime);
  end func;


const func time: getTime_yyyymmddhhmmssfffZ (in string: stri) is func
  result
    var time: aTime is time.value;
  begin
    aTime := time(integer(stri[.. 4]),      # year
                  integer(stri[ 3 len 2]),  # month
                  integer(stri[ 5 len 2]),  # day
                  integer(stri[ 7 len 2]),  # hour
                  integer(stri[ 9 len 2]),  # minute
                  integer(stri[11 len 2])); # second
    # writeln("getTime_yyyymmddhhmmssfffZ(" <& literal(stri) <& ") --> " <& aTime);
  end func;


const func validityType: validityType (in time: notBefore, in time: notAfter) is func
  result
    var validityType: validity is validityType.value;
  begin
    validity.notBefore := notBefore;
    validity.notAfter := notAfter;
  end func;


const func validityType: getValidity (in string: stri, inout integer: pos) is func
  result
    var validityType: validity is validityType.value;
  local
    var asn1DataElement: dataElem is asn1DataElement.value;
  begin
    # writeln("in getValidity");
    dataElem := getAsn1DataElement(stri, pos);
    # writeln("tag: " <& classTagName[ord(dataElem.tagCategory)]);
    if dataElem.tagCategory = tagSequence then
      dataElem := getAsn1DataElement(stri, pos);
      # writeln("tag: " <& classTagName[ord(dataElem.tagCategory)]);
      if dataElem.tagCategory = tagUTCTime then
        validity.notBefore := getTime_yymmddhhmmssZ(getData(stri, pos, dataElem));
      elsif dataElem.tagCategory = tagGeneralizedTime then
        validity.notBefore := getTime_yyyymmddhhmmssfffZ(getData(stri, pos, dataElem));
      end if;
      dataElem := getAsn1DataElement(stri, pos);
      # writeln("tag: " <& classTagName[ord(dataElem.tagCategory)]);
      if dataElem.tagCategory = tagUTCTime then
        validity.notAfter := getTime_yymmddhhmmssZ(getData(stri, pos, dataElem));
      elsif dataElem.tagCategory = tagGeneralizedTime then
        validity.notAfter := getTime_yyyymmddhhmmssfffZ(getData(stri, pos, dataElem));
      end if;
    end if;
  end func;


const func string: genX509Validity (in validityType: validity) is func
  result
    var string: stri is "";
  begin
    stri := genAsn1Element(tagUTCTime, str_yy_mm_dd(validity.notBefore, "") &
                                       str_hh_mm_ss(validity.notBefore, "") & "Z");
    stri &:= genAsn1Element(tagUTCTime, str_yy_mm_dd(validity.notAfter, "") &
                                        str_hh_mm_ss(validity.notAfter, "") & "Z");
    stri := genAsn1Sequence(stri);
  end func;


const func tbsCertificateType: getTbsCertificate (in string: stri, inout integer: pos) is func
  result
    var tbsCertificateType: tbsCertificate is tbsCertificateType.value;
  local
    var asn1DataElement: dataElem is asn1DataElement.value;
    var integer: beyond is 0;
  begin
    dataElem := getAsn1DataElement(stri, pos);
    # writeln("getTbsCertificate: tagCategory=" <& classTagName[ord(dataElem.tagCategory)]);
    if dataElem.tagCategory = tagSequence then
      beyond := pos + dataElem.length;
      dataElem := getAsn1DataElement(stri, pos);
      if dataElem.tagClass = contextSpecificTagClass and dataElem.constructed then
        dataElem := getAsn1DataElement(stri, pos);
        if dataElem.tagCategory = tagInteger then
          tbsCertificate.version := bytes2Int(getData(stri, pos, dataElem), UNSIGNED, BE);
        end if;
        dataElem := getAsn1DataElement(stri, pos);
        if dataElem.tagCategory = tagInteger then
          tbsCertificate.serialNumber := getData(stri, pos, dataElem);
        end if;
      elsif dataElem.tagCategory = tagInteger then
        # Some certificates don't have an explicit tag and a version number.
        tbsCertificate.serialNumber := getData(stri, pos, dataElem);
      end if;
      tbsCertificate.signature := getAlgorithmIdentifier(stri, pos);
      tbsCertificate.issuer := getName(stri, pos);
      tbsCertificate.validity := getValidity(stri, pos);
      tbsCertificate.subject := getName(stri, pos);
      tbsCertificate.subjectPublicKeyInfo := getPublicKeyInfo(stri, pos);
      # writeln("pos=" <& pos);
      # writeln("beyond=" <& beyond);
      if pos < beyond then
        dataElem := getAsn1DataElement(stri, pos);
        if dataElem.constructed and ord(dataElem.tagCategory) = 3 then
          # EXPLICIT TAG 3
          skipData(pos, dataElem);
        end if;
      end if;
    end if;
  end func;


const func string: genX509TbsCertificate (in tbsCertificateType: tbsCertificate) is func
  result
    var string: stri is "";
  local
    var string: version is "";
  begin
    version := genAsn1Integer(str(chr(tbsCertificate.version)));
    stri := genAsn1Tag(0, version);
    stri &:= genAsn1Integer(tbsCertificate.serialNumber);
    stri &:= genAlgorithmIdentifier(tbsCertificate.signature);
    stri &:= genX509Name(tbsCertificate.issuer);
    stri &:= genX509Validity(tbsCertificate.validity);
    stri &:= genX509Name(tbsCertificate.subject);
    stri &:= genX509PublicKeyInfo(tbsCertificate.subjectPublicKeyInfo);
    stri := genAsn1Sequence(stri);
  end func;


(**
 *  Read a X.509 public key certificate from ''stri''.
 *  @return the X.509 public key certificate.
 *)
const func x509cert: getX509Cert (in string: stri) is func
  result
    var x509cert: cert is x509cert.value;
  local
    var integer: pos is 1;
    var asn1DataElement: dataElem is asn1DataElement.value;
    var integer: tbsCertificatePos is 1;
    var integer: posAfterTbsCertificate is 1;
  begin
    dataElem := getAsn1DataElement(stri, pos);
    # writeln("getX509Cert: tagCategory=" <& classTagName[ord(dataElem.tagCategory)]);
    if dataElem.tagCategory = tagSequence then
      tbsCertificatePos := pos;
      cert.tbsCertificate := getTbsCertificate(stri, pos);
      posAfterTbsCertificate := pos;
    end if;
    cert.signatureAlgorithm := getAlgorithmIdentifier(stri, pos);
    dataElem := getAsn1DataElement(stri, pos);
    if dataElem.tagCategory = tagBitString then
      # The initial octet of a bit-string encodes
      # the number of unused bits in the final octet.
      cert.signatureValue := getData(stri, pos, dataElem)[2 ..];
      # writeln("messageDigest start: " <& tbsCertificatePos);
      # writeln("messageDigest end:   " <& pred(posAfterTbsCertificate));
      if cert.signatureAlgorithm.algorithm = MD5_WITH_RSA_ENCRYPTION then
        cert.messageDigest := md5(stri[tbsCertificatePos .. pred(posAfterTbsCertificate)]);
      elsif cert.signatureAlgorithm.algorithm = SHA1_WITH_RSA_ENCRYPTION then
        cert.messageDigest := sha1(stri[tbsCertificatePos .. pred(posAfterTbsCertificate)]);
      end if;
    end if;
    # writeln("end getX509Cert");
  end func;


const func string: genX509Cert (inout x509cert: cert, in rsaKey: encryptionKey) is func
  result
    var string: stri is "";
  local
    var string: signature is "";
    var algorithmIdentifierType: signatureAlgorithmIdentifier is algorithmIdentifierType.value;
  begin
    stri := genX509TbsCertificate(cert.tbsCertificate);
    if cert.signatureAlgorithm.algorithm = MD5_WITH_RSA_ENCRYPTION then
      cert.messageDigest := md5(stri);
    elsif cert.signatureAlgorithm.algorithm = SHA1_WITH_RSA_ENCRYPTION then
      cert.messageDigest := sha1(stri);
    end if;
    signature := genAlgorithmIdentifier(cert.signatureAlgorithm);
    signature &:= genAsn1Element(tagOctetString, cert.messageDigest);
    signature := genAsn1Sequence(signature);
    cert.signatureValue := rsaEncryptBytes(encryptionKey, signature);
    stri &:= genAlgorithmIdentifier(cert.signatureAlgorithm);
    stri &:= genAsn1Element(tagBitString, "\0;" & cert.signatureValue);
    stri := genAsn1Sequence(stri);
  end func;


(**
 *  Validate the signature of a X.509 certificate ''cert'' with ''publicKey''.
 *  @return TRUE if the certificate can be validated,
 *          FALSE otherwise.
 *)
const func boolean: validateSignature (in x509cert: cert, in rsaKey: publicKey) is func
  result
    var boolean: okay is FALSE;
  local
    var string: decrypted is "";
    var integer: pos is 1;
    var asn1DataElement: dataElem is asn1DataElement.value;
    var algorithmIdentifierType: digestAlgorithmIdentifier is algorithmIdentifierType.value;
    var string: digest is "";
  begin
    # writeln("in validateSignature: algorithm=" <& literal(cert.signatureAlgorithm.algorithm));
    if cert.signatureAlgorithm.algorithm = MD5_WITH_RSA_ENCRYPTION or
        cert.signatureAlgorithm.algorithm = SHA1_WITH_RSA_ENCRYPTION then
      # writeln("-------------");
      # writeln("length(messageDigest): " <& length(cert.messageDigest));
      # writeln("messageDigest: " <& literal(cert.messageDigest));
      decrypted := rsaDecryptBytes(publicKey, cert.signatureValue);
      # writeln("length(decrypted):     " <& length(decrypted));
      # writeln("decrypted:     " <& literal(decrypted));
      printAsn1(decrypted);
      dataElem := getAsn1DataElement(decrypted, pos);
      if dataElem.tagCategory = tagSequence then
        digestAlgorithmIdentifier := getAlgorithmIdentifier(decrypted, pos);
        dataElem := getAsn1DataElement(decrypted, pos);
        if dataElem.tagCategory = tagOctetString then
          digest := getData(decrypted, pos, dataElem);
          # writeln("digest: " <& literal(digest));
          okay := cert.messageDigest = digest;
        end if;
      end if;
    end if;
  end func;


(**
 *  Create a X509 certificate that can be used for self signing.
 *  @param publicRsaKey Public RSA key to be entered to the certificate.
 *  @param serialNumber Serial number of certificate.
 *  @param commonName Common name of issuer and subject.
 *  @param country Country of issuer and subject (e.g. "AT" for Austria).
 *  @param locality Locality of issuer and subject (e.g. "Vienna").
 *  @param organization Organization of issuer and subject.
 *  @param organizationUnit Organization unit of issuer and subject.
 *)
const func x509cert: createX509Cert (in rsaKey: publicRsaKey,
    in bigInteger: serialNumber, in string: commonName, in string: country,
    in string: locality, in string: organization,
    in string: organizationUnit, in validityType: validity) is func
  result
    var x509cert: cert is x509cert.value;
  begin
    cert.tbsCertificate.version := 0;  # v1
    cert.tbsCertificate.serialNumber := bytes(serialNumber, UNSIGNED, BE);
    cert.tbsCertificate.signature.algorithm := SHA1_WITH_RSA_ENCRYPTION;
    cert.tbsCertificate.issuer @:= [COMMON_NAME_OID] commonName;
    cert.tbsCertificate.issuer @:= [COUNTRY_NAME_OID] country;
    cert.tbsCertificate.issuer @:= [LOCALITY_NAME_OID] locality;
    cert.tbsCertificate.issuer @:= [ORGANIZATION_NAME_OID] organization;
    cert.tbsCertificate.issuer @:= [ORGANIZATION_UNIT_NAME_OID] organizationUnit;
    cert.tbsCertificate.validity := validity;
    cert.tbsCertificate.subject @:= [COMMON_NAME_OID] commonName;
    cert.tbsCertificate.subject @:= [COUNTRY_NAME_OID] country;
    cert.tbsCertificate.subject @:= [LOCALITY_NAME_OID] locality;
    cert.tbsCertificate.subject @:= [ORGANIZATION_NAME_OID] organization;
    cert.tbsCertificate.subject @:= [ORGANIZATION_UNIT_NAME_OID] organizationUnit;
    cert.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm := RSA_ENCRYPTION_OID;
    cert.tbsCertificate.subjectPublicKeyInfo.publicRsaKey := publicRsaKey;
    cert.signatureAlgorithm.algorithm := SHA1_WITH_RSA_ENCRYPTION;
  end func;


(**
 *  Create a X509 certificate that can be used for self signing.
 *  @param curve Elliptic curve used for the cryptographie.
 *  @param publicEccKey Public ECC key to be entered to the certificate.
 *  @param serialNumber Serial number of certificate.
 *  @param commonName Common name of issuer and subject.
 *  @param country Country of issuer and subject (e.g. "AT" for Austria).
 *  @param locality Locality of issuer and subject (e.g. "Vienna").
 *  @param organization Organization of issuer and subject.
 *  @param organizationUnit Organization unit of issuer and subject.
 *)
const func x509cert: createX509Cert (in ellipticCurve: curve, in ecPoint: publicEccKey,
    in bigInteger: serialNumber, in string: commonName, in string: country,
    in string: locality, in string: organization,
    in string: organizationUnit, in validityType: validity) is func
  result
    var x509cert: cert is x509cert.value;
  begin
    cert.tbsCertificate.version := 0;  # v1
    cert.tbsCertificate.serialNumber := bytes(serialNumber, UNSIGNED, BE);
    cert.tbsCertificate.signature.algorithm := SHA1_WITH_RSA_ENCRYPTION;
    cert.tbsCertificate.issuer @:= [COMMON_NAME_OID] commonName;
    cert.tbsCertificate.issuer @:= [COUNTRY_NAME_OID] country;
    cert.tbsCertificate.issuer @:= [LOCALITY_NAME_OID] locality;
    cert.tbsCertificate.issuer @:= [ORGANIZATION_NAME_OID] organization;
    cert.tbsCertificate.issuer @:= [ORGANIZATION_UNIT_NAME_OID] organizationUnit;
    cert.tbsCertificate.validity := validity;
    cert.tbsCertificate.subject @:= [COMMON_NAME_OID] commonName;
    cert.tbsCertificate.subject @:= [COUNTRY_NAME_OID] country;
    cert.tbsCertificate.subject @:= [LOCALITY_NAME_OID] locality;
    cert.tbsCertificate.subject @:= [ORGANIZATION_NAME_OID] organization;
    cert.tbsCertificate.subject @:= [ORGANIZATION_UNIT_NAME_OID] organizationUnit;
    cert.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm := EC_PUBLIC_KEY;
    cert.tbsCertificate.subjectPublicKeyInfo.algorithm.parameters := getEllipticCurveOid(curve);
    cert.tbsCertificate.subjectPublicKeyInfo.eCurve := curve;
    cert.tbsCertificate.subjectPublicKeyInfo.publicEccKey := publicEccKey;
    cert.signatureAlgorithm.algorithm := SHA1_WITH_RSA_ENCRYPTION;
  end func;


const func certAndKey: certAndKey (in array string: certList, in rsaKey: privateRsaKey) is func
  result
    var certAndKey: certificate is certAndKey.value;
  begin
    certificate.certList := certList;
    certificate.privateRsaKey := privateRsaKey;
  end func;


const func certAndKey: certAndKey (in array string: certList, in bigInteger: privateEccKey) is func
  result
    var certAndKey: certificate is certAndKey.value;
  begin
    certificate.certList := certList;
    certificate.privateEccKey := privateEccKey;
  end func;


(**
 *  Create a self signed X509 certificate from a RSA key pair.
 *  @param keyPair Public and private RSA keys.
 *  @param serialNumber Serial number of certificate.
 *  @param commonName Common name of issuer and subject.
 *  @param country Country of issuer and subject (e.g. "AT" for Austria).
 *  @param locality Locality of issuer and subject (e.g. "Vienna").
 *  @param organization Organization of issuer and subject.
 *  @param organizationUnit Organization unit of issuer and subject.
 *)
const func certAndKey: selfSignedX509Cert (in rsaKeyPair: keyPair,
    in bigInteger: serialNumber, in string: commonName, in string: country,
    in string: locality, in string: organization,
    in string: organizationUnit, in validityType: validity) is func
  result
    var certAndKey: certificate is certAndKey.value;
  local
    var x509cert: cert is x509cert.value;
  begin
    cert := createX509Cert(keyPair.publicKey, serialNumber, commonName,
                           country, locality, organization, organizationUnit,
                           validity);
    certificate.certList := [] (genX509Cert(cert, keyPair.privateKey));
    certificate.privateRsaKey := keyPair.privateKey;
    # printAsn1(certificate.certList[1]);
  end func;


(**
 *  Create a self signed X509 certificate.
 *  @param commonName Common name of issuer and subject.
 *  @param country Country of issuer and subject (e.g. "AT" for Austria).
 *  @param locality Locality of issuer and subject (e.g. "Vienna").
 *  @param organization Organization of issuer and subject.
 *  @param organizationUnit Organization unit of issuer and subject.
 *)
const func certAndKey: selfSignedX509Cert (in string: commonName,
    in string: country, in string: locality, in string: organization,
    in string: organizationUnit) is
  return selfSignedX509Cert(genRsaKeyPair(2048, 16#10001_),
                            rand(0_, 2_ ** (20 * 8) - 1_), commonName,
                            country, locality, organization, organizationUnit,
                            validityType(time(NOW) - 1 . YEARS,
                                         time(NOW) + 1 . YEARS));


const certAndKey: stdCertificate is selfSignedX509Cert(
    stdRsaKeyPair, 3_, "localhost",
    "AT", "Vienna", "Black Hole", "Super Massive",
    validityType(date(2018, 1, 1), date(2022, 1, 1)));
