(********************************************************************)
(*                                                                  *)
(*  x509cert.s7i  Support for X.509 public key certificates.        *)
(*  Copyright (C) 2013 - 2016, 2018, 2019  Thomas Mertes            *)
(*  Copyright (C) 2021 - 2023  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 "unicode.s7i";
include "time.s7i";
include "duration.s7i";


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

const type: x509Name is hash [string] string;

const type: x509Validity 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: x509Extension is new struct
    var string: extensionOid is "";
    var boolean: isCritical is FALSE;
    var string: octetStringData is "";
  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 x509Name: issuer is x509Name.value;
    var x509Validity: validity is x509Validity.value;
    var x509Name: subject is x509Name.value;
    var subjectPublicKeyInfoType: subjectPublicKeyInfo is subjectPublicKeyInfoType.value;
    var array x509Extension: extensions is 0 times x509Extension.value;
    var integer: digestStartPos is 0;
    var integer: digestEndPos is 0;
(*
        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;

# The type x509cert is deprecated. Use x509Cert instead.
const type: x509cert is x509Cert;

const type: certSubjectIndexHashType is hash [string] integer;

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 type: rsaSignatureType is new struct
    var algorithmIdentifierType: algorithmIdentifier is algorithmIdentifierType.value;
    var string: signature is "";
  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: PKCS_7 is  encodeObjectIdentifier([] (1, 2, 840, 113549, 1, 7));  # "*\134;H\134;\247;\r\1;\a"
const string: PKCS_7_DATA                      is PKCS_7 & "\1;";
const string: PKCS_7_SIGNED_DATA               is PKCS_7 & "\2;";
const string: PKCS_7_ENVELOPED_DATA            is PKCS_7 & "\3;";
const string: PKCS_7_SIGNED_AND_ENVELOPED_DATA is PKCS_7 & "\4;";
const string: PKCS_7_DIGESTED_DATA             is PKCS_7 & "\5;";
const string: PKCS_7_ENCRYPTED_DATA            is PKCS_7 & "\6;";

const string: ECDSA_WITH_SHA224 is encodeObjectIdentifier([] (1, 2, 840, 10045, 4, 3, 1));
const string: ECDSA_WITH_SHA256 is encodeObjectIdentifier([] (1, 2, 840, 10045, 4, 3, 2));
const string: ECDSA_WITH_SHA384 is encodeObjectIdentifier([] (1, 2, 840, 10045, 4, 3, 3));
const string: ECDSA_WITH_SHA512 is encodeObjectIdentifier([] (1, 2, 840, 10045, 4, 3, 4));

const string: PRIME_FIELD   is encodeObjectIdentifier([] (1, 2, 840, 10045, 1, 1));     # "*\134;H\206;=\1;\1;"
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, 35));

const string: MD5_OID    is encodeObjectIdentifier([] (1, 2, 840, 113549, 2, 5));
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 integer: KEY_USAGE_DIGITAL_SIGNATURE is  7;
const integer: KEY_USAGE_NON_REPUDIATION   is  6;
const integer: KEY_USAGE_KEY_ENCIPHERMENT  is  5;
const integer: KEY_USAGE_DATA_ENCIPHERMENT is  4;
const integer: KEY_USAGE_KEY_AGREEMENT     is  3;
const integer: KEY_USAGE_KEY_CERT_SIGN     is  2;
const integer: KEY_USAGE_CRL_SIGN          is  1;
const integer: KEY_USAGE_ENCIPHER_ONLY     is  0;
const integer: KEY_USAGE_DECIPHER_ONLY     is 15;


const func algorithmIdentifierType: getAlgorithmIdentifier (in string: asn1, 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(asn1, pos);
    # writeln("in getAlgorithmIdentifier " <& classTagName[ord(dataElem.tagType)]);
    if dataElem.tagType = tagSequence then
      beyond := pos + dataElem.length;
      dataElem := getAsn1DataElement(asn1, pos);
      # writeln("in getAlgorithmIdentifier " <& classTagName[ord(dataElem.tagType)]);
      if dataElem.tagType = tagObjectIdentifier then
        algId.algorithm := getData(asn1, pos, dataElem);
      end if;
      if pos < beyond then
        dataElem := getAsn1DataElement(asn1, pos);
        # writeln("in getAlgorithmIdentifier " <& classTagName[ord(dataElem.tagType)]);
        if dataElem.tagType = tagObjectIdentifier then
          algId.subAlgorithm := getData(asn1, pos, dataElem);
          # writeln("algId.subAlgorithm: " <& literal(algId.subAlgorithm));
        elsif dataElem.tagType = tagSequence then
          algId.parameters := getData(asn1, pos, dataElem);
          # writeln("algId.parameters: " <& literal(algId.parameters));
        elsif dataElem.tagType <> tagNull then
          writeln("*** Unexpected data element " <&
                  classTagName[ord(dataElem.tagType)] <& " ***");
        end if;
      end if;
    end if;
  end func;


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


const func rsaKey: getRsaKey (in string: asn1) 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(asn1, pos);
    if dataElem.tagType = tagSequence then
      dataElem := getAsn1DataElement(asn1, pos);
      if dataElem.tagType = tagInteger then
        modulus := bytes2BigInt(getData(asn1, pos, dataElem), UNSIGNED, BE);
        dataElem := getAsn1DataElement(asn1, pos);
        if dataElem.tagType = tagInteger then
          exponent := bytes2BigInt(getData(asn1, 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: asn1 is "";
  begin
    asn1 := genAsn1Integer(bytes(anRsaKey.modulus, SIGNED, BE));
    asn1 &:= genAsn1Integer(bytes(anRsaKey.exponent, SIGNED, BE));
    asn1 := genAsn1Sequence(asn1);
  end func;


const func rsaSignatureType: getRsaSignature (in string: signatureStri) is func
  result
    var rsaSignatureType: signature is rsaSignatureType.value;
  local
    var asn1DataElement: dataElem is asn1DataElement.value;
    var integer: pos is 1;
  begin
    dataElem := getAsn1DataElement(signatureStri, pos);
    if dataElem.tagType = tagSequence then
      signature.algorithmIdentifier := getAlgorithmIdentifier(signatureStri, pos);
      dataElem := getAsn1DataElement(signatureStri, pos);
      if dataElem.tagType = tagOctetString then
        signature.signature := getData(signatureStri, pos, dataElem);
      end if;
    end if;
    if pos <> succ(length(signatureStri)) then
      # Tailing garbage bytes
      # writeln("Tailing garbage bytes");
      signature.algorithmIdentifier.algorithm := "";
      signature.algorithmIdentifier.subAlgorithm := "";
      signature.algorithmIdentifier.parameters := "";
      signature.signature := "";
    end if;
  end func;


const func ecdsaSignatureType: getEcdsaSignature (in string: signatureStri) is func
  result
    var ecdsaSignatureType: signature is ecdsaSignatureType.value;
  local
    var asn1DataElement: dataElem is asn1DataElement.value;
    var integer: pos is 1;
  begin
    dataElem := getAsn1DataElement(signatureStri, pos);
    if dataElem.tagType = tagSequence then
      dataElem := getAsn1DataElement(signatureStri, pos);
      if dataElem.tagType = tagInteger then
        signature.r := bytes2BigInt(getData(signatureStri, pos, dataElem), UNSIGNED, BE);
        dataElem := getAsn1DataElement(signatureStri, pos);
        if dataElem.tagType = tagInteger then
          signature.s := bytes2BigInt(getData(signatureStri, pos, dataElem), UNSIGNED, BE);
        else
          signature.r := 0_;
        end if;
      end if;
    end if;
    if pos <> succ(length(signatureStri)) then
      # Tailing garbage bytes
      # writeln("Tailing garbage bytes");
      signature.r := 0_;
      signature.s := 0_;
    end if;
  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 ellipticCurve: getEllipticCurve (in string: asn1) is func
  result
    var ellipticCurve: curve is ellipticCurve.value;
  local
    var integer: pos is 1;
    var asn1DataElement: dataElem is asn1DataElement.value;
    var string: data is "";
    var boolean: okay is FALSE;
  begin
    dataElem := getAsn1DataElement(asn1, pos);
    if dataElem.tagType = tagInteger and
        bytes2Int(getData(asn1, pos, dataElem), UNSIGNED, BE) = 1 then
      dataElem := getAsn1DataElement(asn1, pos);
      if dataElem.tagType = tagSequence then
        dataElem := getAsn1DataElement(asn1, pos);
        if dataElem.tagType = tagObjectIdentifier and
            getData(asn1, pos, dataElem) = PRIME_FIELD then
          dataElem := getAsn1DataElement(asn1, pos);
          if dataElem.tagType = tagInteger then
            curve.p := bytes2BigInt(getData(asn1, pos, dataElem), UNSIGNED, BE);
            curve.bits := bitLength(curve.p);
            dataElem := getAsn1DataElement(asn1, pos);
            if dataElem.tagType = tagSequence then
              dataElem := getAsn1DataElement(asn1, pos);
              if dataElem.tagType = tagOctetString then
                curve.a := bytes2BigInt(getData(asn1, pos, dataElem), UNSIGNED, BE) mod curve.p;
                dataElem := getAsn1DataElement(asn1, pos);
                if dataElem.tagType = tagOctetString then
                  curve.b := bytes2BigInt(getData(asn1, pos, dataElem), UNSIGNED, BE) mod curve.p;
                  dataElem := getAsn1DataElement(asn1, pos);
                  if dataElem.tagType = tagBitString then
                    data := getData(asn1, pos, dataElem);
                    dataElem := getAsn1DataElement(asn1, pos);
                    if dataElem.tagType = tagOctetString then
                      curve.g := ecPointDecode(curve, getData(asn1, pos, dataElem));
                      dataElem := getAsn1DataElement(asn1, pos);
                      if dataElem.tagType = tagInteger then
                        curve.n := bytes2BigInt(getData(asn1, pos, dataElem), UNSIGNED, BE);
                        dataElem := getAsn1DataElement(asn1, pos);
                        if dataElem.tagType = tagInteger and
                            bytes2Int(getData(asn1, pos, dataElem), UNSIGNED, BE) = 1 then
                          okay := TRUE;
                        end if;
                      end if;
                    end if;
                  end if;
                end if;
              end if;
            end if;
          end if;
        end if;
      end if;
    end if;
    if not okay then
      curve := ellipticCurve.value;
    end if;
  end func;


const func subjectPublicKeyInfoType: getPublicKeyInfo (in string: asn1, 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(asn1, pos);
    # writeln("tag: " <& classTagName[ord(dataElem.tagType)]);
    if dataElem.tagType = tagSequence then
      keyInfo.algorithm := getAlgorithmIdentifier(asn1, pos);
      dataElem := getAsn1DataElement(asn1, pos);
      # writeln("tag: " <& classTagName[ord(dataElem.tagType)]);
      if dataElem.tagType = tagBitString then
        # The initial octet of a bit-string encodes
        # the number of unused bits in the final octet.
        keyInfo.subjectPublicKey := getData(asn1, 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(asn1, pos, dataElem)[2 ..];
        # writeln("subjectPublicKey: " <& hex(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");
          if keyInfo.algorithm.subAlgorithm <> "" then
            keyInfo.eCurve := getEllipticCurveFromOid(keyInfo.algorithm.subAlgorithm);
            # writeln("keyInfo.eCurve.name: " <& keyInfo.eCurve.name);
            if keyInfo.eCurve.bits <> 0 then
              keyInfo.publicEccKey := ecPointDecode(keyInfo.eCurve, keyInfo.subjectPublicKey);
            end if;
          else
            keyInfo.eCurve := getEllipticCurve(keyInfo.algorithm.parameters);
            if keyInfo.eCurve.bits <> 0 then
              keyInfo.publicEccKey := ecPointDecode(keyInfo.eCurve, keyInfo.subjectPublicKey);
            end if;
          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: asn1 is "";
  local
    var string: subjectPublicKey is "";
  begin
    asn1 := genAlgorithmIdentifier(keyInfo.algorithm);
    if keyInfo.subjectPublicKey <> "" then
      subjectPublicKey := keyInfo.subjectPublicKey;
    elsif keyInfo.algorithm.algorithm = RSA_ENCRYPTION_OID then
      subjectPublicKey := genX509RsaKey(keyInfo.publicRsaKey);
    elsif keyInfo.algorithm.algorithm = EC_PUBLIC_KEY then
      subjectPublicKey := ecPointEncode(keyInfo.eCurve, keyInfo.publicEccKey);
    end if;
    # The initial octet of a bit-string encodes
    # the number of unused bits in the final octet.
    asn1 &:= genAsn1Element(tagBitString, "\0;" & subjectPublicKey);
    asn1 := genAsn1Sequence(asn1);
  end func;


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


const func x509Name: x509Name (in string: commonName,
    in string: country, in string: locality, in string: organization,
    in string: organizationUnit) is func
  result
    var x509Name: x509Name is x509Name.value;
  begin
    if commonName <> "" then
      x509Name @:= [COMMON_NAME_OID] commonName;
    end if;
    if country <> "" then
      x509Name @:= [COUNTRY_NAME_OID] country;
    end if;
    if locality <> "" then
      x509Name @:= [LOCALITY_NAME_OID] locality;
    end if;
    if organization <> "" then
      x509Name @:= [ORGANIZATION_NAME_OID] organization;
    end if;
    if organizationUnit <> "" then
      x509Name @:= [ORGANIZATION_UNIT_NAME_OID] organizationUnit;
    end if;
  end func;


const func string: genX509Name (in x509Name: name) is func
  result
    var string: asn1 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 &:= genAsn1String(attrValue);
      setElement := genAsn1Sequence(setElement);
      asn1 &:= genAsn1Set(setElement);
    end for;
    asn1 := genAsn1Sequence(asn1);
  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 fixLen 2]),  # month
                  integer(stri[ 5 fixLen 2]),  # day
                  integer(stri[ 7 fixLen 2]),  # hour
                  integer(stri[ 9 fixLen 2]),  # minute
                  integer(stri[11 fixLen 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[ 1 fixLen 4]),  # year
                  integer(stri[ 3 fixLen 2]),  # month
                  integer(stri[ 5 fixLen 2]),  # day
                  integer(stri[ 7 fixLen 2]),  # hour
                  integer(stri[ 9 fixLen 2]),  # minute
                  integer(stri[11 fixLen 2])); # second
    # writeln("getTime_yyyymmddhhmmssfffZ(" <& literal(stri) <& ") --> " <& aTime);
  end func;


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


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


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


const func tbsCertificateType: getTbsCertificate (in string: asn1, inout integer: pos, in integer: beyond) is func
  result
    var tbsCertificateType: tbsCertificate is tbsCertificateType.value;
  local
    var asn1DataElement: dataElem is asn1DataElement.value;
  begin
    dataElem := getAsn1DataElement(asn1, pos);
    if dataElem.tagClass = contextSpecificTagClass and dataElem.constructed then
      dataElem := getAsn1DataElement(asn1, pos);
      if dataElem.tagType = tagInteger then
        tbsCertificate.version := bytes2Int(getData(asn1, pos, dataElem), UNSIGNED, BE);
      end if;
      dataElem := getAsn1DataElement(asn1, pos);
      if dataElem.tagType = tagInteger then
        tbsCertificate.serialNumber := getData(asn1, pos, dataElem);
      end if;
    elsif dataElem.tagType = tagInteger then
      # Some certificates don't have an explicit tag and a version number.
      tbsCertificate.serialNumber := getData(asn1, pos, dataElem);
    end if;
    tbsCertificate.signature := getAlgorithmIdentifier(asn1, pos);
    tbsCertificate.issuer := getX509Name(asn1, pos);
    tbsCertificate.validity := getX509Validity(asn1, pos);
    tbsCertificate.subject := getX509Name(asn1, pos);
    tbsCertificate.subjectPublicKeyInfo := getPublicKeyInfo(asn1, pos);
    if pos < beyond then
      dataElem := getAsn1DataElement(asn1, pos);
      if dataElem.constructed and ord(dataElem.tagType) = 3 then
        # EXPLICIT TAG 3
        skipData(pos, dataElem);
      end if;
    end if;
    tbsCertificate.digestEndPos := pred(beyond);
  end func;


const func tbsCertificateType: getPkcs7SignedDataCert (in string: asn1, inout integer: pos) is func
  result
    var tbsCertificateType: tbsCertificate is tbsCertificateType.value;
  local
    var asn1DataElement: dataElem is asn1DataElement.value;
    var integer: version is 0;
    var string: digestAlgorithmIdentifiers is "";
    var string: contentType is "";
    var string: content is "";
    var integer: digestStartPos is 0;
  begin
    # writeln("SIGNED_DATA pos: " <& pos <& ", length(asn1): " <& length(asn1));
    dataElem := getAsn1DataElement(asn1, pos);
    if dataElem.tagClass = contextSpecificTagClass and dataElem.constructed then
      # writeln("EXPLICIT TAG: " <& ord(dataElem.tagType) <& ", length: " <& dataElem.length);
      dataElem := getAsn1DataElement(asn1, pos);
    end if;
    if dataElem.tagType = tagSequence then
      dataElem := getAsn1DataElement(asn1, pos);
      if dataElem.tagType = tagInteger then
        version := bytes2Int(getData(asn1, pos, dataElem), UNSIGNED, BE);
        # writeln("version: " <& version);
      end if;
      dataElem := getAsn1DataElement(asn1, pos);
      if dataElem.tagType = tagSet then
        digestAlgorithmIdentifiers := getData(asn1, pos, dataElem);
        # writeln(literal(digestAlgorithmIdentifiers));
      end if;
      dataElem := getAsn1DataElement(asn1, pos);
      if dataElem.tagType = tagSequence then
        dataElem := getAsn1DataElement(asn1, pos);
        if dataElem.tagType = tagObjectIdentifier then
          contentType := getData(asn1, pos, dataElem);  # E.g.: PKCS_7_DATA
          # writeln("contentType: " <& literal(contentType));
        end if;
      end if;
      dataElem := getAsn1DataElement(asn1, pos);
      if dataElem.tagClass = contextSpecificTagClass and dataElem.constructed then
        # writeln("EXPLICIT TAG: " <& ord(dataElem.tagType) <& ", length: " <& dataElem.length);
        dataElem := getAsn1DataElement(asn1, pos);
      end if;
      if dataElem.tagType = tagOctetString then
        content := getData(asn1, pos, dataElem);
        # writeln(literal(content));
        dataElem := getAsn1DataElement(asn1, pos);
        if dataElem.tagClass = contextSpecificTagClass and dataElem.constructed then
          # writeln("EXPLICIT TAG: " <& ord(dataElem.tagType) <& ", length: " <& dataElem.length);
          dataElem := getAsn1DataElement(asn1, pos);
        end if;
        digestStartPos := pos;
        dataElem := getAsn1DataElement(asn1, pos);
      elsif dataElem.tagType = tagSequence then
        digestStartPos := pos;
        dataElem := getAsn1DataElement(asn1, pos);
      end if;
      if dataElem.tagType = tagSequence then
        tbsCertificate := getTbsCertificate(asn1, pos, pos + dataElem.length);
        tbsCertificate.digestStartPos := digestStartPos;
      end if;
    end if;
  end func;


const func tbsCertificateType: getTbsCertificate (in string: asn1, inout integer: pos) is func
  result
    var tbsCertificateType: tbsCertificate is tbsCertificateType.value;
  local
    var asn1DataElement: dataElem is asn1DataElement.value;
    var integer: digestStartPos is 0;
  begin
    digestStartPos := pos;
    dataElem := getAsn1DataElement(asn1, pos);
    if dataElem.tagType = tagObjectIdentifier then
      if getData(asn1, pos, dataElem) = PKCS_7_SIGNED_DATA then
        tbsCertificate := getPkcs7SignedDataCert(asn1, pos);
      end if;
    elsif dataElem.tagType = tagSequence then
      tbsCertificate := getTbsCertificate(asn1, pos, pos + dataElem.length);
      tbsCertificate.digestStartPos := digestStartPos;
    end if;
  end func;


const func string: toAsn1 (in x509Extension: extension) is func
  result
    var string: asn1 is "";
  begin
    asn1 := genAsn1Element(tagObjectIdentifier, extension.extensionOid);
    if extension.isCritical then
      asn1 &:= genAsn1Element(tagBoolean, "\255;");
    end if;
    asn1 &:= genAsn1Element(tagOctetString, extension.octetStringData);
    asn1 := genAsn1Sequence(asn1);
  end func;


const func string: genX509TbsCertificate (in tbsCertificateType: tbsCertificate) is func
  result
    var string: asn1 is "";
  local
    var string: version is "";
    var integer: index is 0;
    var string: extensions is "";
  begin
    version := genAsn1Integer(str(chr(tbsCertificate.version)));
    asn1 := genExplicitAsn1Tag(0, version);
    asn1 &:= genAsn1Integer(tbsCertificate.serialNumber);
    asn1 &:= genAlgorithmIdentifier(tbsCertificate.signature);
    asn1 &:= genX509Name(tbsCertificate.issuer);
    asn1 &:= genX509Validity(tbsCertificate.validity);
    asn1 &:= genX509Name(tbsCertificate.subject);
    asn1 &:= genX509PublicKeyInfo(tbsCertificate.subjectPublicKeyInfo);
    if length(tbsCertificate.extensions) <> 0 then
      for index range 1 to length(tbsCertificate.extensions) do
        extensions &:= toAsn1(tbsCertificate.extensions[index]);
      end for;
      extensions := genAsn1Sequence(extensions);
      asn1 &:= genExplicitAsn1Tag(3, extensions);
    end if;
    asn1 := genAsn1Sequence(asn1);
  end func;


const func string: getDigestOidFromAlgorithm (in digestAlgorithm: digestAlg) is func
  result
    var string: digestOid is "";
  begin
    case digestAlg of
      when {MD5}:    digestOid := MD5_OID;
      when {SHA1}:   digestOid := SHA1_OID;
      when {SHA256}: digestOid := SHA256_OID;
      when {SHA384}: digestOid := SHA384_OID;
      when {SHA512}: digestOid := SHA512_OID;
    end case;
  end func;


const func digestAlgorithm: getDigestAlgorithm (in string: algorithm) is func
  result
    var digestAlgorithm: digestAlg is NO_DIGEST;
  begin
    if algorithm = MD5_OID then
      digestAlg := MD5;
    elsif algorithm = SHA1_OID then
      digestAlg := SHA1;
    elsif algorithm = SHA256_OID then
      digestAlg := SHA256;
    elsif algorithm = SHA384_OID then
      digestAlg := SHA384;
    elsif algorithm = SHA512_OID then
      digestAlg := SHA512;
    end if;
  end func;


const func digestAlgorithm: getDigestFromSignatureAlgorithm (in string: algorithm) is func
  result
    var digestAlgorithm: digestAlg is NO_DIGEST;
  begin
    if algorithm = MD5_WITH_RSA_ENCRYPTION then
      digestAlg := MD5;
    elsif algorithm = SHA1_WITH_RSA_ENCRYPTION then
      digestAlg := SHA1;
    elsif algorithm = SHA256_WITH_RSA_ENCRYPTION or
          algorithm = ECDSA_WITH_SHA256 then
      digestAlg := SHA256;
    elsif algorithm = SHA384_WITH_RSA_ENCRYPTION or
          algorithm = ECDSA_WITH_SHA384 then
      digestAlg := SHA384;
    elsif algorithm = SHA512_WITH_RSA_ENCRYPTION or
          algorithm = ECDSA_WITH_SHA512 then
      digestAlg := SHA512;
    end if;
  end func;


const func string: getDigestOidFromSignatureAlgorithm (in string: algorithm) is func
  result
    var string: digestOid is "";
  begin
    if algorithm = MD5_WITH_RSA_ENCRYPTION then
      digestOid := MD5_OID;
    elsif algorithm = SHA1_WITH_RSA_ENCRYPTION then
      digestOid := SHA1_OID;
    elsif algorithm = SHA256_WITH_RSA_ENCRYPTION or
          algorithm = ECDSA_WITH_SHA256 then
      digestOid := SHA256_OID;
    elsif algorithm = SHA384_WITH_RSA_ENCRYPTION or
          algorithm = ECDSA_WITH_SHA384 then
      digestOid := SHA384_OID;
    elsif algorithm = SHA512_WITH_RSA_ENCRYPTION or
          algorithm = ECDSA_WITH_SHA512 then
      digestOid := SHA512_OID;
    end if;
  end func;


const proc: showSignatureAlgorithm (in string: algorithm) is func
  begin
    if algorithm = MD5_WITH_RSA_ENCRYPTION then
      writeln("MD5 with RSA Encryption");
    elsif algorithm = SHA1_WITH_RSA_ENCRYPTION then
      writeln("SHA-1 with RSA Encryption");
    elsif algorithm = SHA256_WITH_RSA_ENCRYPTION then
      writeln("SHA-256 with RSA Encryption");
    elsif algorithm = SHA384_WITH_RSA_ENCRYPTION then
      writeln("SHA-384 with RSA Encryption");
    elsif algorithm = SHA512_WITH_RSA_ENCRYPTION then
      writeln("SHA-512 with RSA Encryption");
    elsif algorithm = ECDSA_WITH_SHA256 then
      writeln("ECDSA with SHA-256");
    elsif algorithm = ECDSA_WITH_SHA384 then
      writeln("ECDSA with SHA-384");
    elsif algorithm = ECDSA_WITH_SHA512 then
      writeln("ECDSA with SHA-512");
    else
      writeln("Unknown: " <& literal(algorithm));
    end if;
  end func;


(**
 *  Read a X.509 public key certificate from ''stri''.
 *  @return the X.509 public key certificate.
 *)
const func x509Cert: getX509Cert (in string: asn1) is func
  result
    var x509Cert: cert is x509Cert.value;
  local
    var integer: pos is 1;
    var asn1DataElement: dataElem is asn1DataElement.value;
    var digestAlgorithm: digestAlg is NO_DIGEST;
  begin
    dataElem := getAsn1DataElement(asn1, pos);
    # writeln("getX509Cert: tagType=" <& classTagName[ord(dataElem.tagType)]);
    if dataElem.tagType = tagSequence then
      cert.tbsCertificate := getTbsCertificate(asn1, pos);
    end if;
    cert.signatureAlgorithm := getAlgorithmIdentifier(asn1, pos);
    dataElem := getAsn1DataElement(asn1, pos);
    if dataElem.tagType = tagBitString then
      # The initial octet of a bit-string encodes
      # the number of unused bits in the final octet.
      cert.signatureValue := getData(asn1, pos, dataElem)[2 ..];
      # showSignatureAlgorithm(cert.signatureAlgorithm.algorithm);
      digestAlg := getDigestFromSignatureAlgorithm(cert.signatureAlgorithm.algorithm);
      if digestAlg <> NO_DIGEST then
        cert.messageDigest := msgDigest(digestAlg,
            asn1[cert.tbsCertificate.digestStartPos .. cert.tbsCertificate.digestEndPos]);
      end if;
    end if;
    # writeln("end getX509Cert");
  end func;


const func string: genX509Cert (inout x509Cert: cert, in rsaKey: issuerKey) is func
  result
    var string: asn1 is "";
  local
    var digestAlgorithm: digestAlg is NO_DIGEST;
    var algorithmIdentifierType: digestAlgorithmId is algorithmIdentifierType.value;
    var string: signature is "";
  begin
    asn1 := genX509TbsCertificate(cert.tbsCertificate);
    digestAlg := getDigestFromSignatureAlgorithm(cert.signatureAlgorithm.algorithm);
    if digestAlg <> NO_DIGEST then
      cert.messageDigest := msgDigest(digestAlg, asn1);
    end if;
    digestAlgorithmId.algorithm :=
        getDigestOidFromSignatureAlgorithm(cert.signatureAlgorithm.algorithm);
    signature := genAlgorithmIdentifier(digestAlgorithmId);
    signature &:= genAsn1Element(tagOctetString, cert.messageDigest);
    signature := genAsn1Sequence(signature);
    cert.signatureValue := rsassaPkcs1V15Encrypt(issuerKey, signature);
    asn1 &:= genAlgorithmIdentifier(cert.signatureAlgorithm);
    asn1 &:= genAsn1Element(tagBitString, "\0;" & cert.signatureValue);
    asn1 := genAsn1Sequence(asn1);
  end func;


const func string: genX509Cert (inout x509Cert: cert, in ellipticCurve: curve,
    in bigInteger: issuerKey) is func
  result
    var string: asn1 is "";
  local
    var digestAlgorithm: digestAlg is NO_DIGEST;
    var algorithmIdentifierType: digestAlgorithmId is algorithmIdentifierType.value;
    var ecdsaSignatureType: ecdsaSignature is ecdsaSignatureType.value;
  begin
    asn1 := genX509TbsCertificate(cert.tbsCertificate);
    digestAlg := getDigestFromSignatureAlgorithm(cert.signatureAlgorithm.algorithm);
    if digestAlg <> NO_DIGEST then
      cert.messageDigest := msgDigest(digestAlg, asn1);
    end if;
    digestAlgorithmId.algorithm :=
        getDigestOidFromSignatureAlgorithm(cert.signatureAlgorithm.algorithm);
    ecdsaSignature := sign(curve, bytes2BigInt(cert.messageDigest, UNSIGNED, BE), issuerKey);
    cert.signatureValue := genAsn1Sequence(
        genAsn1Integer(bytes(ecdsaSignature.r, UNSIGNED, BE)) &
        genAsn1Integer(bytes(ecdsaSignature.s, UNSIGNED, BE)));
    asn1 &:= genAlgorithmIdentifier(cert.signatureAlgorithm);
    asn1 &:= genAsn1Element(tagBitString, "\0;" & cert.signatureValue);
    asn1 := genAsn1Sequence(asn1);
  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 subjectPublicKeyInfoType: publicKey) is func
  result
    var boolean: okay is FALSE;
  local
    var digestAlgorithm: digestAlg is NO_DIGEST;
    var string: decrypted is "";
    var rsaSignatureType: rsaSignature is rsaSignatureType.value;
  begin
    # writeln("in validateSignature: algorithm=" <& literal(cert.signatureAlgorithm.algorithm));
    digestAlg := getDigestFromSignatureAlgorithm(cert.signatureAlgorithm.algorithm);
    if digestAlg <> NO_DIGEST then
      block
        # writeln("length(messageDigest): " <& length(cert.messageDigest));
        # writeln("messageDigest: " <& literal(cert.messageDigest));
        # writeln("algorith: " <& literal(publicKey.algorithm.algorithm));
        if publicKey.algorithm.algorithm = RSA_ENCRYPTION_OID then
          # writeln("RSA_ENCRYPTION_OID:");
          decrypted := rsassaPkcs1V15Decrypt(publicKey.publicRsaKey, cert.signatureValue);
          # writeln("length(decrypted):     " <& length(decrypted));
          # writeln("decrypted:     " <& literal(decrypted));
          # printAsn1(decrypted);
          rsaSignature := getRsaSignature(decrypted);
          okay := digestAlg = getDigestAlgorithm(rsaSignature.algorithmIdentifier.algorithm) and
                  cert.messageDigest = rsaSignature.signature;
        elsif publicKey.algorithm.algorithm = EC_PUBLIC_KEY then
          # writeln("EC_PUBLIC_KEY:");
          # writeln("signatureValue: " <& literal(cert.signatureValue));
          # writeln("length(signatureValue): " <& length(cert.signatureValue));
          # printAsn1(cert.signatureValue);
          okay := verify(publicKey.eCurve, bytes2BigInt(cert.messageDigest, UNSIGNED, BE),
                         getEcdsaSignature(cert.signatureValue), publicKey.publicEccKey);
        end if;
      exception
        otherwise: okay := FALSE;
      end block;
    end if;
  end func;


(**
 *  Create a X509 certificate that can be used for signing by the issuer.
 *  @param publicRsaKey Public RSA key to be entered to the certificate.
 *  @param serialNumber Serial number of certificate.
 *  @param issuer Common name, country, locality, organization, etc. of issuer.
 *  @param subject Common name, country, locality, organization, etc. of subject.
 *  @param validity Validity of the certificate.
 *)
const func x509Cert: createX509Cert (in rsaKey: publicRsaKey,
    in bigInteger: serialNumber, in x509Name: issuer,
    in x509Name: subject, in x509Validity: 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 := SHA256_WITH_RSA_ENCRYPTION;
    cert.tbsCertificate.issuer := issuer;
    cert.tbsCertificate.validity := validity;
    cert.tbsCertificate.subject := subject;
    cert.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm := RSA_ENCRYPTION_OID;
    cert.tbsCertificate.subjectPublicKeyInfo.publicRsaKey := publicRsaKey;
    cert.signatureAlgorithm.algorithm := SHA256_WITH_RSA_ENCRYPTION;
  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.
 *  @param validity Validity of the certificate.
 *)
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 x509Validity: 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 := SHA256_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 := SHA256_WITH_RSA_ENCRYPTION;
  end func;


(**
 *  Create a X509 certificate that can is signed by the issuer.
 *  @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 issuer Common name, country, locality, organization, etc. of issuer.
 *  @param subject Common name, country, locality, organization, etc. of subject.
 *  @param validity Validity of the certificate.
 *)
const func x509Cert: createX509Cert (in ellipticCurve: curve,
    in ecPoint: publicEccKey, in bigInteger: serialNumber, in x509Name: issuer,
    in x509Name: subject, in x509Validity: 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 := ECDSA_WITH_SHA256;
    cert.tbsCertificate.issuer := issuer;
    cert.tbsCertificate.validity := validity;
    cert.tbsCertificate.subject := subject;
    cert.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm := EC_PUBLIC_KEY;
    cert.tbsCertificate.subjectPublicKeyInfo.algorithm.subAlgorithm := getEllipticCurveOid(curve);
    cert.tbsCertificate.subjectPublicKeyInfo.eCurve := curve;
    cert.tbsCertificate.subjectPublicKeyInfo.publicEccKey := publicEccKey;
    cert.signatureAlgorithm.algorithm := ECDSA_WITH_SHA256;
  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.
 *  @param validity Validity of the certificate.
 *)
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 x509Validity: 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 := ECDSA_WITH_SHA256;
    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.subAlgorithm := getEllipticCurveOid(curve);
    cert.tbsCertificate.subjectPublicKeyInfo.eCurve := curve;
    cert.tbsCertificate.subjectPublicKeyInfo.publicEccKey := publicEccKey;
    cert.signatureAlgorithm.algorithm := ECDSA_WITH_SHA256;
  end func;


(**
 *  Create a KeyUsage X509 certificate extension from a bitset.
 *  Elements in the bitset can be KEY_USAGE_DIGITAL_SIGNATURE,
 *  KEY_USAGE_NON_REPUDIATION, KEY_USAGE_KEY_ENCIPHERMENT,
 *  KEY_USAGE_DATA_ENCIPHERMENT, KEY_USAGE_KEY_AGREEMENT,
 *  KEY_USAGE_KEY_CERT_SIGN, KEY_USAGE_CRL_SIGN,
 *  KEY_USAGE_ENCIPHER_ONLY and KEY_USAGE_DECIPHER_ONLY.
 *  @param keyUsage Set of allowed key usages.
 *)
const func x509Extension: x509KeyUsage (in bitset: keyUsage) is func
  result
    var x509Extension: keyUsageExtension is x509Extension.value;
  local
    var string: bits is "";
  begin
    keyUsageExtension.extensionOid := KEY_USAGE_OID;
    bits := bytes(integer(keyUsage), UNSIGNED, LE);
    keyUsageExtension.octetStringData := genAsn1Element(tagBitString,
        str(char(lowestSetBit(ord(bits[length(bits)])))) & bits);
  end func;


(**
 *  Create a BasicConstraints X509 certificate extension from a path length.
 *  The cA flag of this BasicConstraints extension is set to TRUE.
 *  @param pathLenConstraint Given path length constraint.
 *)
const func x509Extension: x509BasicConstraints (in integer: pathLenConstraint) is func
  result
    var x509Extension: basicConstraintsExtension is x509Extension.value;
  begin
    basicConstraintsExtension.extensionOid := BASIC_CONSTRAINTS_OID;
    basicConstraintsExtension.octetStringData := genAsn1Sequence(
        genAsn1Element(tagBoolean, "\255;") &
        genAsn1Integer(bytes(pathLenConstraint, SIGNED, BE)));
  end func;


(**
 *  Create a BasicConstraints X509 certificate extension from a cA flag.
 *  @param cA Frag to be set in the constraint.
 *)
const func x509Extension: x509BasicConstraints (in boolean: cA) is func
  result
    var x509Extension: basicConstraintsExtension is x509Extension.value;
  begin
    basicConstraintsExtension.extensionOid := BASIC_CONSTRAINTS_OID;
    basicConstraintsExtension.octetStringData := genAsn1Sequence(
        cA ? genAsn1Element(tagBoolean, "\255;") : "");
  end func;


(**
 *  Add the given ''extension'' to the X509 certificate ''cert''.
 *  The extension field is added to the standard extensions tag (tag 3).
 *   cert := createX509Cert(subjectKey, serial, issuer, subject, validity);
 *   addExtension(cert, TRUE, x509BasicConstraints(0));
 *   addExtension(cert, TRUE,
 *                x509KeyUsage({KEY_USAGE_CRL_SIGN, KEY_USAGE_KEY_CERT_SIGN}));
 *  @param cert Certificat to which the extension is added.
 *  @param isCritical TRUE if the extension is critical, FALSE otherwise.
 *  @param extension The extension to be added.
 *)
const proc: addExtension (inout x509Cert: cert, in boolean: isCritical,
    in var x509Extension: extension) is func
  begin
    extension.isCritical := isCritical;
    cert.tbsCertificate.extensions &:= extension;
  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 x509Validity: 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,
                            x509Validity(time(NOW) - 1 . YEARS,
                                         time(NOW) + 1 . YEARS));


(**
 *  Self signed X509 certificate.
 *)
const certAndKey: stdCertificate is selfSignedX509Cert(
    stdRsaKeyPair, 6_, "localhost",
    "AT", "Vienna", "Black Hole", "Super Massive",
    x509Validity(date(2022, 1, 1), date(2026, 1, 1)));


(**
 *  Create a self signed X509 certificate from an ECC 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 eccKeyPair: keyPair,
    in bigInteger: serialNumber, in string: commonName, in string: country,
    in string: locality, in string: organization,
    in string: organizationUnit, in x509Validity: validity) is func
  result
    var certAndKey: certificate is certAndKey.value;
  local
    var x509Cert: cert is x509Cert.value;
  begin
    cert := createX509Cert(keyPair.curve, keyPair.publicKey, serialNumber,
                           commonName, country, locality, organization,
                           organizationUnit, validity);
    certificate.certList := [] (genX509Cert(cert, keyPair.curve, keyPair.privateKey));
    certificate.privateEccKey := keyPair.privateKey;
    # printAsn1(certificate.certList[1]);
  end func;


(**
 *  Self signed X509 certificate.
 *)
const certAndKey: stdEccCertificate is selfSignedX509Cert(
    stdEccKeyPair, 1_, "localhost",
    "AT", "Vienna", "Black Hole", "Super Massive",
    x509Validity(date(2023, 1, 1), date(2028, 1, 1)));