Skip to content
Snippets Groups Projects
signingUtilities.js 47.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • Alexey Lunin's avatar
    Alexey Lunin committed
    import {
      canTryPincode,
      failPincodeAttempt,
      getTimeLeftInLocalStorage,
      makeid
    } from "./appUtility";
    
    import {
      bufferToHexCodes,
      stringToArrayBuffer,
      isEqualBuffer
    } from "pvutils";
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    import { fromBER } from "asn1js";
    
    import { ContentInfo, SignedData, Certificate } from "pkijs";
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    import { algomap, rdnmap } from "../constants/certificates";
    
    Damyan Mitev's avatar
    Damyan Mitev committed
    import {
      fixNewLines,
      getAttachment,
      getAttachments,
      getGlobalHeaderValue,
      getHeaderValue,
      parseMIME
    } from "../helpers/mailparser";
    import dataUriToBlob from "data-uri-to-blob";
    import {
      extractHtmlBodyFromString,
      getFilenameFromHeaders,
      SIGNATURE_CONTENT_TYPE
    } from "./emailUtilities";
    
    Damyan Mitev's avatar
    Damyan Mitev committed
    import {
      stringToUtf8ByteArray,
      utf8ByteArrayToString,
      stringToUtf8Base64,
      utf8Base64ToString,
      base64ToByteArray,
      byteArrayToBase64
    } from "../utilities/stringUtilities";
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    const libmime = require("libmime");
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    const webcryptoLiner = require("webcrypto-liner/build/index");
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    const pkijs = require("pkijs");
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    
    pkijs.setEngine(
      "webcrypto-liner",
      webcryptoLiner.crypto,
      new pkijs.CryptoEngine({ name: "webcrypto-liner", crypto: webcryptoLiner.crypto, subtle: webcryptoLiner.crypto.subtle })
    );
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    const asn1js = require("asn1js");
    const pvutils = require("pvutils");
    
    
    //*********************************************************************************
    
    const CERTIFIATE_Version_1 = 0;
    const CERTIFIATE_Version_3 = 2;
    
    //these bit fields are reversed, WTF!
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    const KEY_USAGE_DigitalSignature = 0x80; //01;
    const KEY_USAGE_NonRepudiation = 0x40; //02;
    const KEY_USAGE_KeyEncipherment = 0x20; //04;
    const KEY_USAGE_DataEncipherment = 0x10; //08;
    const KEY_USAGE_KeyAgreement = 0x08; //10;
    const KEY_USAGE_KeyCertSign = 0x04; //20;
    const KEY_USAGE_CRLSign = 0x02; //40;
    
    //const KEY_USAGE_EncipherOnly		= 0x01;//80; // Not used for now. Must be used together with KEY_USAGE_KeyAgreement (maybe should be ORed as a constant directly?)
    //const KEY_USAGE_DecipherOnly		= 0x80;//0100; // If used, modify "KeyUsage" extension array buffer size and appropriate bit operators to accomodate for extra byte
    
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    const KEY_USAGE_LeafCertificate =
      KEY_USAGE_DigitalSignature |
      KEY_USAGE_NonRepudiation |
      KEY_USAGE_KeyEncipherment |
      KEY_USAGE_DataEncipherment;
    const KEY_USAGE_CertificateAuthority =
    
      KEY_USAGE_DigitalSignature |
      KEY_USAGE_KeyCertSign |
      KEY_USAGE_CRLSign;
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    
    const OID_EXT_KEY_USAGE_Any = "2.5.29.37.0";
    const OID_ID_PKIX_ServerAuth = "1.3.6.1.5.5.7.3.1";
    const OID_ID_PKIX_ClientAuth = "1.3.6.1.5.5.7.3.2";
    const OID_ID_PKIX_CodeSigning = "1.3.6.1.5.5.7.3.3";
    const OID_ID_PKIX_EmailProtection = "1.3.6.1.5.5.7.3.4";
    const OID_ID_PKIX_TimeStamping = "1.3.6.1.5.5.7.3.8";
    const OID_ID_PKIX_OCSPSigning = "1.3.6.1.5.5.7.3.9";
    
    // const OID_EXT_KEY_USAGE_MS...	= "1.3.6.1.4.1.311.10.3.1"; // Microsoft Certificate Trust List signing
    // const OID_EXT_KEY_USAGE_MS...	= "1.3.6.1.4.1.311.10.3.4";  // Microsoft Encrypted File System
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    const OID_PKCS7_Data = "1.2.840.113549.1.7.1";
    const OID_PKCS7_SignedData = "1.2.840.113549.1.7.2";
    const OID_PKCS7_EnvelopedData = "1.2.840.113549.1.7.3";
    const OID_PKCS9_EmailAddress = "1.2.840.113549.1.9.1";
    const OID_PKCS9_ContentType = "1.2.840.113549.1.9.3";
    const OID_PKCS9_MessageDigest = "1.2.840.113549.1.9.4";
    const OID_PKCS9_SigningTime = "1.2.840.113549.1.9.5";
    
    
    const defaultAlgorithms = {
      hashAlg: "SHA-256",
      signAlg: "RSASSA-PKCS1-v1_5",
      keyLength: 2048
    };
    
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    const AES_encryptionVariant_Password = 2;
    
    const encryptionAlgorithm = {
      name: "AES-CBC",
      length: 128
    };
    
    
    // Convert a hex string to a byte array
    function hexStringToBytes(hex) {
    
    Damyan Mitev's avatar
    Damyan Mitev committed
      let bytes, c;
      if (hex.length % 2 === 1) {
        hex = "0" + hex;
      }
      for (bytes = [], c = 0; c < hex.length; c += 2) {
    
        bytes.push(parseInt(hex.substr(c, 2), 16));
      }
      return bytes;
    }
    
    export class CertificateData {
      //**********************************************************************************
      /**
       * Constructor for SignedData class
       * @param {pkijs.Certificate} [certificate]
       * @param {Object} [parameters]
       */
      constructor(parameters = {}) {
        this.serialNumber = null; //string || ArrayBuffer || Uint8Array
    
    
    Damyan Mitev's avatar
    Damyan Mitev committed
        this.keyPair = null; // write only; {publicKey, privateKey}
    
    Damyan Mitev's avatar
    Damyan Mitev committed
        this.signatureAlgorithm = null; //read-only, initialized form pkijs.Certificate object
    
        this.algorithms = null; // write only; {hashAlg: "SHA-256", signAlg: "RSASSA-PKCS1-v1_5", keyLength: 2048};
    
    
        this.issuer = null; //same as subject
    
        this.subject = {
          commonName: null, //string
          country: null, //string
          locality: null, //string
          state: null, //string
          organization: null, //string
          organizationUnit: null, //string
          email: null, //string
          url: null //string
        };
    
        this.validity = {
          notBefore: null, //new Date()
          notAfter: null, //new Date()
    
    Damyan Mitev's avatar
    Damyan Mitev committed
          validYears: null //int
    
        };
    
        this.isCA = false;
    
        if (parameters) {
          if (parameters instanceof Certificate) {
            this.fromCertificate(parameters);
          } else {
            this.fromParameters(parameters);
          }
        }
      }
    
      fromCertificate(certificate) {
        this.serialNumber = bufferToHexCodes(
          certificate.serialNumber.valueBlock.valueHex
        );
    
        let signatureAlgorithm =
          algomap[certificate.signatureAlgorithm.algorithmId];
        if (typeof signatureAlgorithm === "undefined") {
          signatureAlgorithm = certificate.signatureAlgorithm.algorithmId;
        } else {
          signatureAlgorithm = `${signatureAlgorithm}`;
        }
        this.signatureAlgorithm = signatureAlgorithm;
    
        this.issuer = {};
        const issuer = certificate.issuer.typesAndValues;
        for (const typeAndValue of issuer) {
          let typeVal = rdnmap[typeAndValue.type];
          if (typeof typeVal === "undefined") {
            typeVal = typeAndValue.type;
          }
          const subjVal = typeAndValue.value.valueBlock.value;
          this.issuer[typeVal] = subjVal;
        }
    
        const subject = certificate.subject.typesAndValues;
        for (const typeAndValue of subject) {
          let typeVal = rdnmap[typeAndValue.type];
          if (typeof typeVal === "undefined") {
            typeVal = typeAndValue.type;
          }
          const subjVal = typeAndValue.value.valueBlock.value;
          this.subject[typeVal] = subjVal;
        }
    
        this.validity.notBefore = certificate.notBefore.value;
        this.validity.notAfter = certificate.notAfter.value;
    
        this.isCA = certificate.issuer.isEqual(certificate.subject);
      }
    
      fromParameters(parameters) {
        if ("serialNumber" in parameters) {
          this.serialNumber = parameters.serialNumber;
        }
    
        if ("keyPair" in parameters) {
    
    Damyan Mitev's avatar
    Damyan Mitev committed
          this.keyPair = {};
          if ("publicKey" in parameters.keyPair) {
            this.keyPair.publicKey = parameters.keyPair.publicKey;
          }
          if ("privateKey" in parameters.keyPair) {
            this.keyPair.privateKey = parameters.keyPair.privateKey;
          }
    
        }
    
        if ("signatureAlgorithm" in parameters) {
          this.signatureAlgorithm = parameters.signatureAlgorithm;
        }
    
        if ("algorithms" in parameters) {
    
    Damyan Mitev's avatar
    Damyan Mitev committed
          this.algorithms = {};
    
          if ("hashAlg" in parameters.algorithms) {
            this.algorithms.hashAlg = parameters.algorithms.hashAlg;
          }
          if ("signAlg" in parameters.algorithms) {
            this.algorithms.signAlg = parameters.algorithms.signAlg;
          }
          if ("keyLength" in parameters.algorithms) {
            this.algorithms.keyLength = parameters.algorithms.keyLength;
          }
        }
    
        if ("subject" in parameters) {
          if ("commonName" in parameters.subject) {
            this.subject.commonName = parameters.subject.commonName;
          }
          if ("country" in parameters.subject) {
            this.subject.country = parameters.subject.country;
          }
          if ("locality" in parameters.subject) {
            this.subject.locality = parameters.subject.locality;
          }
          if ("state" in parameters.subject) {
            this.subject.state = parameters.subject.state;
          }
          if ("organization" in parameters.subject) {
            this.subject.organization = parameters.subject.organization;
          }
          if ("organizationUnit" in parameters.subject) {
            this.subject.organizationUnit = parameters.subject.organizationUnit;
          }
          if ("email" in parameters.subject) {
            this.subject.email = parameters.subject.email;
          }
          if ("url" in parameters.subject) {
            this.subject.url = parameters.subject.url;
          }
        }
    
        if ("validity" in parameters) {
          if ("notBefore" in parameters.validity) {
            this.validity.notBefore = parameters.validity.notBefore;
          }
          if ("notAfter" in parameters.validity) {
            this.validity.notAfter = parameters.validity.notAfter;
          }
          if ("validYears" in parameters.validity) {
            this.validity.validYears = parameters.validity.validYears;
          }
        }
    
        if ("isCA" in parameters) {
          this.isCA = parameters.isCA;
        }
      }
    }
    
    
    //*********************************************************************************
    // Returns promise, resolved to keyPair object {publicKey, privateKey}
    //*********************************************************************************
    function generateKeys(algorithms) {
      //region Get a "crypto" extension
      const crypto = pkijs.getCrypto();
      if (typeof crypto === "undefined") {
        return Promise.reject("No WebCrypto extension found");
      }
      //endregion Get a "crypto" extension
    
      if (!algorithms) {
        algorithms = defaultAlgorithms;
      } else {
        if (!algorithms.hashAlg) {
          algorithms.hashAlg = defaultAlgorithms.hashAlg;
        }
        if (!algorithms.signAlg) {
          algorithms.signAlg = defaultAlgorithms.signAlg;
        }
        if (!algorithms.keyLength) {
          algorithms.keyLength = defaultAlgorithms.keyLength;
        }
      }
    
      //region Get default algorithm parameters for key generation
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      const algorithm = pkijs.getAlgorithmParameters(
        algorithms.signAlg,
        "generatekey"
      );
      if ("hash" in algorithm.algorithm) {
    
        algorithm.algorithm.hash.name = algorithms.hashAlg;
      }
      algorithm.algorithm.modulusLength = algorithms.keyLength;
      //endregion
    
      return crypto.generateKey(algorithm.algorithm, true, algorithm.usages);
    }
    
    
    function fixPkijsRDN() {
      pkijs.RelativeDistinguishedNames.prototype.toSchema = function () {
        //region Decode stored TBS value
        if (this.valueBeforeDecode.byteLength === 0) // No stored encoded array, create "from scratch"
        {
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
          return new asn1js.Sequence({
    
            value: Array.from(this.typesAndValues, element => new asn1js.Set({value: [element.toSchema()]}))
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
          });
    
        }
    
        const asn1 = asn1js.fromBER(this.valueBeforeDecode);
        //endregion
    
        //region Construct and return new ASN.1 schema for this object
        return asn1.result;
        //endregion
      };
    }
    
    
    //*********************************************************************************
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    function createCertificate(certData, issuerData = null) {
    
    Damyan Mitev's avatar
    Damyan Mitev committed
      if (typeof certData === "undefined" || certData === null) {
    
        return Promise.reject("No Certificate data provided");
      }
    
    
    Damyan Mitev's avatar
    Damyan Mitev committed
      if (typeof certData.subject === "undefined" || certData.subject === null) {
    
        return Promise.reject("No Certificate subject data provided");
      }
    
    
    Damyan Mitev's avatar
    Damyan Mitev committed
      if (typeof certData.subject.commonName === "undefined" || certData.subject.commonName === null) {
        return Promise.reject("No Certificate common name provided");
      }
    
    
      //region Get a "crypto" extension
      const crypto = pkijs.getCrypto();
    
      if (typeof crypto === "undefined") {
        return Promise.reject("No WebCrypto extension found");
      }
      //endregion Get a "crypto" extension
    
      //region Initial variables
      let sequence = Promise.resolve();
    
      const certificate = new pkijs.Certificate();
      let publicKey;
      let privateKey;
    
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      let certificateBuffer; // = new ArrayBuffer(0); // ArrayBuffer with loaded or created CERT
      let privateKeyBuffer; // = new ArrayBuffer(0);
      let publicKeyBuffer; // = new ArrayBuffer(0);
    
    
      //endregion Initial variables
    
      if (certData.keyPair) {
        //region Create a new key pair
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        sequence = sequence.then(() => {
    
          return certData.keyPair;
        });
        //endregion Create a new key pair
      } else {
        //region Create a new key pair
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        sequence = sequence.then(() => {
    
          return generateKeys(certData.algorithms);
        });
        //endregion Create a new key pair
      }
    
      //region Store new key in an interim variables
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      sequence = sequence.then(
        keyPair => {
          publicKey = keyPair.publicKey;
          privateKey = keyPair.privateKey;
        },
        error => Promise.reject(`Error during key generation: ${error}`)
      );
    
      //endregion Store new key in an interim variables
    
      //region Exporting public key into "subjectPublicKeyInfo" value of certificate
      sequence = sequence.then(() =>
        certificate.subjectPublicKeyInfo.importKey(publicKey)
      );
      //endregion Exporting public key into "subjectPublicKeyInfo" value of certificate
    
      sequence = sequence.then(
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
        () =>{
    
          //IN IE11 this is NOT a PROMISE !!! but a Crypto Opearation Object
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
          return crypto.digest(
    
    Alexey Lunin's avatar
    Alexey Lunin committed
            { name: "SHA-1" },
            certificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
          )
          ;
        },
    
        error => Promise.reject(`Error during importing public key: ${error}`)
      );
    
      //region Fill in cert data
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      sequence = sequence.then(subjKeyIdBuffer => {
    
        //region Put a static values
        certificate.version = CERTIFIATE_Version_3;
    
    
        certificate.serialNumber = null;
        if (certData.serialNumber) {
          let serialNumberView = null;
    
          if (certData.serialNumber instanceof Buffer) {
            serialNumberView = new Uint8Array(certData.serialNumber);
          } else if (certData.serialNumber instanceof ArrayBuffer) {
            serialNumberView = new Uint8Array(certData.serialNumber);
          } else if (certData.serialNumber instanceof Uint8Array) {
            serialNumberView = certData.serialNumber;
    
    Damyan Mitev's avatar
    Damyan Mitev committed
          } else if (typeof certData.serialNumber === "string") {
    
            try {
              serialNumberView = new Uint8Array(hexStringToBytes(certData.serialNumber));
            } catch (ignore) {
              serialNumberView = null;
            }
          }
          if (serialNumberView !== null) {
            certificate.serialNumber = new asn1js.Integer({
              valueHex: serialNumberView
            });
          }
        }
    
        if (certificate.serialNumber === null) {
          const serialNumberBuffer = new ArrayBuffer(20);
          const serialNumberView = new Uint8Array(serialNumberBuffer);
          pkijs.getRandomValues(serialNumberView);
          serialNumberView[0] &= 0x7f;
    
          while (serialNumberView[0] === 0 && (serialNumberView[1] & 0x80) === 0) {
            const firstBytesView = new Uint8Array(serialNumberBuffer, 0, 2);
            pkijs.getRandomValues(firstBytesView);
            firstBytesView[0] &= 0x7f;
          }
    
          // noinspection JSUnresolvedFunction
          certificate.serialNumber = new asn1js.Integer({
            valueHex: serialNumberView
          });
        }
    
        //endregion Put a static values
    
        //region Subject
        // For reference http://oidref.com/2.5.4.3
        if (certData.subject.commonName) {
          // noinspection JSUnresolvedFunction
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          certificate.subject.typesAndValues.push(
            new pkijs.AttributeTypeAndValue({
              type: "2.5.4.3", // Common name
              value: new asn1js.PrintableString({
                value: certData.subject.commonName
              })
            })
          );
    
        }
    
        if (certData.subject.country) {
          // noinspection JSUnresolvedFunction
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          certificate.subject.typesAndValues.push(
            new pkijs.AttributeTypeAndValue({
              type: "2.5.4.6", // Country name
              value: new asn1js.PrintableString({ value: certData.subject.country })
            })
          );
    
        }
    
        if (certData.subject.locality) {
          // noinspection JSUnresolvedFunction
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          certificate.subject.typesAndValues.push(
            new pkijs.AttributeTypeAndValue({
              type: "2.5.4.7", // Locality Name
              value: new asn1js.PrintableString({
                value: certData.subject.locality
              })
            })
          );
    
        }
    
        if (certData.subject.state) {
          // noinspection JSUnresolvedFunction
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          certificate.subject.typesAndValues.push(
            new pkijs.AttributeTypeAndValue({
              type: "2.5.4.8", // State or Province name
              value: new asn1js.PrintableString({ value: certData.subject.state })
            })
          );
    
        }
    
        if (certData.subject.organization) {
          // noinspection JSUnresolvedFunction
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          certificate.subject.typesAndValues.push(
            new pkijs.AttributeTypeAndValue({
              type: "2.5.4.10", // Organization name
              value: new asn1js.PrintableString({
                value: certData.subject.organization
              })
            })
          );
    
        }
    
        if (certData.subject.organizationUnit) {
          // noinspection JSUnresolvedFunction
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          certificate.subject.typesAndValues.push(
            new pkijs.AttributeTypeAndValue({
              type: "2.5.4.11", // Organization unit name
              value: new asn1js.PrintableString({
                value: certData.subject.organizationUnit
              })
            })
          );
    
        }
    
        if (certData.subject.email) {
          // noinspection JSUnresolvedFunction
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          certificate.subject.typesAndValues.push(
            new pkijs.AttributeTypeAndValue({
              type: OID_PKCS9_EmailAddress, // Email, deprecated but still widely used
              value: new asn1js.IA5String({ value: certData.subject.email })
            })
          );
    
        }
        //endregion Subject
    
        //region Issuer
        if (issuerData && issuerData.certificate) {
          certificate.issuer = issuerData.certificate.subject;
        } else {
          certificate.issuer = certificate.subject;
        }
        //endregion Issuer
    
        //region Validity
        if (!certData.validity) {
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          certData.validity = {};
    
        }
    
        if (certData.validity.notBefore) {
          certificate.notBefore.value = certData.validity.notBefore; //date
        } else {
          const tmp = new Date();
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          certificate.notBefore.value = new Date(
            tmp.getFullYear(),
            tmp.getMonth(),
            tmp.getDate(),
            0,
            0,
            0
          );
    
        }
    
        if (certData.validity.notAfter) {
          certificate.notAfter.value = certData.validity.notAfter; //date
        } else {
          const tmp = certificate.notBefore.value;
          const validYears = certData.validity.validYears || 1;
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          certificate.notAfter.value = new Date(
            tmp.getFullYear() + validYears,
            tmp.getMonth(),
            tmp.getDate(),
            23,
            59,
            59
          );
    
        }
        //endregion Validity
    
        //region Extensions
        certificate.extensions = []; // Extensions are not a part of certificate by default, it's an optional array
    
        //region "BasicConstraints" extension
        const basicConstr = new pkijs.BasicConstraints({
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          cA: !!certData.isCA
    
          //pathLenConstraint: 0 //TODO add logic for leaf CA
        });
    
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        certificate.extensions.push(
          new pkijs.Extension({
            extnID: "2.5.29.19",
            critical: true,
            extnValue: basicConstr.toSchema().toBER(false),
            parsedValue: basicConstr // Parsed value for well-known extensions
          })
        );
    
        //endregion "BasicConstraints" extension
    
        //region "KeyUsage" extension
        const keyUsageBuffer = new ArrayBuffer(1);
        const keyUsageBitView = new Uint8Array(keyUsageBuffer);
    
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
        keyUsageBitView[0] = certData.isCA ?
          KEY_USAGE_CertificateAuthority :
          KEY_USAGE_LeafCertificate;
    
    
        // noinspection JSUnresolvedFunction
        const keyUsage = new asn1js.BitString({ valueHex: keyUsageBuffer });
    
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        certificate.extensions.push(
          new pkijs.Extension({
            extnID: "2.5.29.15",
            critical: true,
            extnValue: keyUsage.toBER(false),
            parsedValue: keyUsage // Parsed value for well-known extensions
          })
        );
    
        //endregion "KeyUsage" extension
    
        //region "ExtKeyUsage" extension
    
        if (!certData.isCA) {
          const keyPurposes = [];
          if (certData.subject.url) {
            keyPurposes.push(OID_ID_PKIX_ServerAuth, OID_ID_PKIX_ClientAuth);
          }
          if (certData.subject.email) {
            keyPurposes.push(OID_ID_PKIX_EmailProtection);
          }
          keyPurposes.push(OID_ID_PKIX_TimeStamping);
    
    
          const extKeyUsage = new pkijs.ExtKeyUsage({
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
            keyPurposes
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          certificate.extensions.push(
            new pkijs.Extension({
              extnID: "2.5.29.37",
              critical: false,
              extnValue: extKeyUsage.toSchema().toBER(false),
              parsedValue: extKeyUsage // Parsed value for well-known extensions
            })
          );
    
        }
        //endregion "ExtKeyUsage" extension
    
        //region "SubjAltName" extension
        if (certData.subject.email || certData.subject.url) {
          const names = [];
    
          if (certData.subject.email) {
    
    Alexey Lunin's avatar
    Alexey Lunin committed
            names.push(
              new pkijs.GeneralName({
                type: 1, // rfc822Name
                value: certData.subject.email
              })
            );
    
    Alexey Lunin's avatar
    Alexey Lunin committed
            names.push(
              new pkijs.GeneralName({
                type: 2, // dNSName
                value: certData.subject.url
              })
            );
    
          }
    
          const subjAltNames = new pkijs.GeneralNames({
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
            names
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          certificate.extensions.push(
            new pkijs.Extension({
              extnID: "2.5.29.17",
              critical: false,
              extnValue: subjAltNames.toSchema().toBER(false),
              parsedValue: subjAltNames // Parsed value for well-known extensions
            })
          );
    
        }
        //endregion "SubjAltName" extension
    
        //region "SubjectKeyIdentifier" extension
        const subjKeyId = new asn1js.OctetString({ valueHex: subjKeyIdBuffer });
    
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        certificate.extensions.push(
          new pkijs.Extension({
            extnID: "2.5.29.14",
            critical: false,
            extnValue: subjKeyId.toBER(false),
            parsedValue: subjKeyId // Parsed value for well-known extensions
          })
        );
    
        //endregion "SubjectKeyIdentifier" extension
    
    
        //TODO add policy
    
    
        /* COULD NOT GET IT WORKING
            //region "AuthorityKeyIdentifier" extension
            if (issuerData && issuerData.certificate) {
    
              let issuerSubjKeyExt = null;
    
              let extLength = issuerData.certificate.extensions.length;
    
    Alexey Lunin's avatar
    Alexey Lunin committed
              for (let i = 0; i < extLength; i++) {
    
                let ext = issuerData.certificate.extensions[i];
                if (ext.extnID == "2.5.29.14") {
                  issuerSubjKeyExt = ext;
                  break;
                }
              }
    
              if (issuerSubjKeyExt) {
    
                const asn1 = asn1js.fromBER(issuerSubjKeyExt.extnValue);
    
                const authKeyIdentifier = new AuthorityKeyIdentifier({
                  keyIdentifier: new asn1js.OctetString({
                    //isHexOnly: true,
                    //valueHex: issuerSubjKeyExt.parsedValue.valueBlock.valueHex
                    value: new asn1js.OctetString({ valueHex: subjKeyIdBuffer })
                  })
                });
                // const authKeyIdentifier = new AuthorityKeyIdentifier({
                // 	//keyIdentifier: new asn1js.OctetString({ valueHex: subjKeyIdBuffer })
    
                // });
    
                certificate.extensions.push(new Extension({
                  extnID: "2.5.29.35",
                  critical: false,
                  extnValue: authKeyIdentifier.toSchema().toBER(false),
                  parsedValue: authKeyIdentifier // Parsed value for well-known extensions
                }));
              }
            }
            //endregion "AuthorityKeyIdentifier" extension
        */
        //endregion Extensions
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
      },error => Promise.reject(`Error calculating hash of public key: ${error}`));
    
      //region Fill in cert data
    
      //region Signing final certificate
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      sequence = sequence.then(
        () => {
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
          const signerKey =
            issuerData && issuerData.privateKey ?
              issuerData.privateKey :
              privateKey;
    
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          return certificate.sign(
            signerKey,
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
            certData.algorithms && certData.algorithms.hashAlg ?
              certData.algorithms.hashAlg :
              defaultAlgorithms.hashAlg
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          );
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        error => Promise.reject(`Error during exporting public key: ${error}`)
      );
    
      //endregion
    
      //region Encode and store certificate
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      sequence = sequence.then(
        () => {
          certificateBuffer = certificate.toSchema(true).toBER(false);
        },
        error => Promise.reject(`Error during signing: ${error}`)
      );
    
      //endregion
    
      //region Exporting public key
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      sequence = sequence.then(() => crypto.exportKey("spki", publicKey));
    
      //endregion
    
      //region Store exported public key on Web page
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      sequence = sequence.then(
        result => {
          publicKeyBuffer = result;
        },
        error => Promise.reject(`Error during exporting of public key: ${error}`)
      );
    
      //endregion
    
      //region Exporting private key
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      sequence = sequence.then(() => crypto.exportKey("pkcs8", privateKey));
    
      //endregion
    
      //region Store exported key on Web page
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      sequence = sequence.then(
        result => {
          privateKeyBuffer = result;
        },
        error => Promise.reject(`Error during exporting of private key: ${error}`)
      );
    
      //endregion
    
      return sequence.then(() => {
        const result = {
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
          certificate,
    
          certificatePEM: encodePEM(certificateBuffer, "CERTIFICATE"),
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
          publicKey,
    
          publicKeyPEM: encodePEM(publicKeyBuffer, "PUBLIC KEY"),
    
    Zdravko Iliev's avatar
    Zdravko Iliev committed
          privateKey,
    
          privateKeyPEM: encodePEM(privateKeyBuffer, "PRIVATE KEY")
        };
        return result;
      });
    }
    
    function formatPEM(pemString) {
      const lineWidth = 64;
      let resultString = "";
      let start = 0;
      let piece;
      while ((piece = pemString.substring(start, start + lineWidth)).length > 0) {
        start += lineWidth;
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        resultString += piece + "\r\n";
    
      }
      return resultString;
    }
    
    function encodePEM(buffer, label) {
      const bufferString = String.fromCharCode.apply(null, new Uint8Array(buffer));
    
      const header = `-----BEGIN ${label}-----\r\n`;
      const base64formatted = formatPEM(window.btoa(bufferString));
      const footer = `-----END ${label}-----\r\n`;
      const resultString = header + base64formatted + footer;
    
      return resultString;
    }
    
    function decodePEM(pemString) {
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      const pemStripped = pemString.replace(
        /(-----(BEGIN|END) [a-zA-Z ]*-----|\r|\n)/g,
        ""
      );
    
      const pemDecoded = window.atob(pemStripped);
      const buffer = pvutils.stringToArrayBuffer(pemDecoded);
      return buffer;
    }
    
    //*********************************************************************************
    
    export function parseCertificate(certificatePEM) {
    
      const certificateBuffer = decodePEM(certificatePEM);
      const asn1 = asn1js.fromBER(certificateBuffer);
      const certificate = new pkijs.Certificate({ schema: asn1.result });
      return certificate;
    }
    
    //*********************************************************************************
    export function encryptMessage(message, password, label) {
      const buffer = pvutils.stringToArrayBuffer(message);
      const secret = pvutils.stringToArrayBuffer(password);
    
      const enveloped = new pkijs.EnvelopedData();
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      enveloped.addRecipientByPreDefinedData(
        secret,
        {},
        AES_encryptionVariant_Password
      );
      return enveloped.encrypt(encryptionAlgorithm, buffer).then(
    
        () => {
          const content = new pkijs.ContentInfo();
          content.contentType = OID_PKCS7_EnvelopedData;
          content.content = enveloped.toSchema();
          const ber = content.toSchema().toBER(false);
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          return encodePEM(ber, label);
    
        },
        error => Promise.reject(`encryption error: ${error}`)
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      );
    
    }
    
    //*********************************************************************************
    export function decryptMessage(message, password) {
      if (canTryPincode()) {
        const secret = pvutils.stringToArrayBuffer(password);
        const buffer = decodePEM(message);
    
        const asn1 = asn1js.fromBER(buffer);
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        const content = new pkijs.ContentInfo({ schema: asn1.result });
        const enveloped = new pkijs.EnvelopedData({ schema: content.content });
        return enveloped
          .decrypt(0, { preDefinedData: secret })
          .then(result => {
            return pvutils.arrayBufferToString(result);
          })
          .catch(() => {
    
    Alexey Lunin's avatar
    Alexey Lunin committed
            return new Promise((resolve, reject) => {
    
              failPincodeAttempt(password).then((message) => {
                reject(message);
              });
            });
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          });
    
      } else {
        return Promise.reject(getTimeLeftInLocalStorage());
      }
    }
    
    //*********************************************************************************
    
    export function parsePrivateKey(privateKeyPEM) {
    
      const privateKeyBuffer = decodePEM(privateKeyPEM);
      const crypto = pkijs.getCrypto();
      const privateKeyPromise = crypto.importKey(
        "pkcs8",
        privateKeyBuffer,
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        {
          //these are the algorithm options
    
          name: "RSASSA-PKCS1-v1_5",
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          hash: { name: "SHA-256" } //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
    
        },
        true,
        ["sign"]
      );
      return privateKeyPromise;
    }
    
    export function createPassportCertificate(commonNameArg) {
    
        algorithms: {
          hashAlg: "SHA-256",
          signAlg: "RSASSA-PKCS1-v1_5",
          keyLength: 2048
        },
        //keyPair: generateKeys(), //optional , if provided must be object or promise that resolves to object {publicKey, prvateKey}. If it is not provided, new ones are generated automatically
        subject: {
          commonName: commonNameArg + "-userdevice", //optional for leaf, recommended for CA
          country: "CH", //optional for leaf, recommended for CA
          locality: "Zug", //optional for leaf, recommended for CA
          state: "Zug", //optional for leaf, recommended for CA
          organization: "Vereign AG", //optional for leaf, recommended for CA
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          organizationUnit: "Business Dep" //optional for leaf, recommended for CA
    
          //email: "damyan.mitev@vereign.com", // added to DN and Subject Alternative Name extension. Optional for CA. Mandatory for leaf certificate, used for email protection
          //url: "www.vereign.com" // optional url, recommended for CA, added to Subject Alternative Name extension
        },
        validity: {
          //notBefore: new Date() // optional, defaults to today at 00:00:00
          //notAfter: new Date()  // optional, defaults to notBefore + validYears at 23:59:59
          validYears: 5 //optional, defaults to 1
        },
        isCA: true // optional flag denoting if this is CA certificate or leaf certificate, defaults to false
      };
    
    
      const certificateData = new CertificateData(certDataParams);
    
      return createCertificate(certificateData, null);
    
    Alexey Lunin's avatar
    Alexey Lunin committed
    export function createOneTimePassportCertificate(
      commonNameArg,
      emailArg,
      privateKeyIssuerArg,
      certicateIssuerArg
    ) {
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      let certData = null;
    
    Damyan Mitev's avatar
    Damyan Mitev committed
      if (emailArg !== null && emailArg === "") {
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        emailArg = null;
    
      certData = {
        algorithms: {
          hashAlg: "SHA-256",
          signAlg: "RSASSA-PKCS1-v1_5",
          keyLength: 2048
        },
        //keyPair: generateKeys(), //optional , if provided must be object or promise that resolves to object {publicKey, prvateKey}. If it is not provided, new ones are generated automatically
        subject: {
          commonName: commonNameArg + "-onetime", //optional for leaf, recommended for CA
          country: "CH", //optional for leaf, recommended for CA
          locality: "Zug", //optional for leaf, recommended for CA
          state: "Zug", //optional for leaf, recommended for CA
          organization: "Vereign AG", //optional for leaf, recommended for CA
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          organizationUnit: "Business Dep", //optional for leaf, recommended for CA
          email: emailArg // added to DN and Subject Alternative Name extension. Optional for CA. Mandatory for leaf certificate, used for email protection
    
          //url: "www.vereign.com" // optional url, recommended for CA, added to Subject Alternative Name extension
        },
        validity: {
          //notBefore: new Date() // optional, defaults to today at 00:00:00
          //notAfter: new Date()  // optional, defaults to notBefore + validYears at 23:59:59
          validYears: 5 //optional, defaults to 1
        },
        isCA: false // optional flag denoting if this is CA certificate or leaf certificate, defaults to false
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      };
    
      return parsePrivateKey(privateKeyIssuerArg).then(privateKeyDecoded => {
        const issuerData = {
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          certificate: parseCertificate(certicateIssuerArg), // vereignCACertPEM),
    
          privateKey: privateKeyDecoded
        };
        return createCertificate(certData, issuerData);
      });
    }
    
    function arrayBufferToBase64Formatted(buffer) {
      const bufferString = String.fromCharCode.apply(null, new Uint8Array(buffer));