Skip to content
Snippets Groups Projects
viamapi-iframe.js 89.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • const libmime = require('libmime');
    const QRCode = require('qrcode');
    const pkijs = require('pkijs');
    const asn1js = require('asn1js');
    const pvutils = require('pvutils');
    const Penpal = require('penpal').default;
    
    const penpalMethods = require('../../temp/penpal-methods').default;
    
    const WopiAPI = require('./wopiapi-iframe');
    const ViamAPI = require('../../temp/viamapi');
    
    //*********************************************************************************
    
    const CERTIFIATE_Version_1 = 0;
    const CERTIFIATE_Version_3 = 2;
    
    //these bit fields are reversed, WTF!
    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
    
    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;
    
    
    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
    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";
    
    Markin Igor's avatar
    Markin Igor committed
    const OID_PKCS9_SigningTime         = "1.2.840.113549.1.9.5";
    
    
    const defaultAlgorithms = {
      hashAlg: "SHA-256",
      signAlg: "RSASSA-PKCS1-v1_5",
      keyLength: 2048
    
    Markin Igor's avatar
    Markin Igor committed
    };
    
    
    const AES_encryptionVariant_Password  = 2;
    const encryptionAlgorithm = {
      name: "AES-CBC",
      length: 128
    };
    
    //*********************************************************************************
    // 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
      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 createCertificate(certData, issuerData = null)
    {
    
      if (typeof certData === "undefined") {
        return Promise.reject("No Certificate data provided");
      }
    
      if (typeof certData.subject === "undefined") {
        return Promise.reject("No Certificate subject data 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;
    
      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
        sequence = sequence.then(() =>
        {
          return certData.keyPair;
        });
        //endregion Create a new key pair
    
      } else {
        //region Create a new key pair
        sequence = sequence.then(() =>
        {
          return generateKeys(certData.algorithms);
        });
        //endregion Create a new key pair
      }
    
      //region Store new key in an interim variables
      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(
        () => crypto.digest({ name: "SHA-1" }, certificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex),
        error => Promise.reject(`Error during importing public key: ${error}`)
      );
    
      //region Fill in cert data
      sequence = sequence.then(subjKeyIdBuffer =>
      {
    
        //region Put a static values
        certificate.version = CERTIFIATE_Version_3;
    
        const serialNumberBuffer = new ArrayBuffer(20);
        const serialNumberView = new Uint8Array(serialNumberBuffer);
    
    Markin Igor's avatar
    Markin Igor committed
        pkijs.getRandomValues(serialNumberView);
    
        // 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
          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
          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
          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
          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
          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
          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
          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) {
          certData.validity = {}
        }
    
        if (certData.validity.notBefore) {
          certificate.notBefore.value = certData.validity.notBefore; //date
        } else {
          const tmp = new Date();
          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;
          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({
          cA: !!certData.isCA,
          //pathLenConstraint: 0 //TODO add logic for leaf CA
        });
    
        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);
    
        keyUsageBitView[0] = !!certData.isCA ? KEY_USAGE_CertificateAuthority : KEY_USAGE_LeafCertificate;
    
        // noinspection JSUnresolvedFunction
        const keyUsage = new asn1js.BitString({ valueHex: keyUsageBuffer });
    
        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 && certData.subject.email) {
          const extKeyUsage = new pkijs.ExtKeyUsage({
            keyPurposes: [
              OID_ID_PKIX_EmailProtection
            ]
          });
    
          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) {
            names.push(new pkijs.GeneralName({
              type: 1, // rfc822Name
              value: certData.subject.email
            }));
          }
    
          if (certData.subject.url) {
            names.push(new pkijs.GeneralName({
              type: 2, // dNSName
              value: certData.subject.url
            }));
          }
    
          const subjAltNames = new pkijs.GeneralNames({
            names: names
          });
    
          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 });
    
        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
    
        /* COULD NOT GET IT WORKING
            //region "AuthorityKeyIdentifier" extension
            if (issuerData && issuerData.certificate) {
    
              let issuerSubjKeyExt = null;
    
              let extLength = issuerData.certificate.extensions.length;
              for (var 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
      });
      //region Fill in cert data
    
    
      //region Signing final certificate
      sequence = sequence.then(() => {
          let signerKey = (issuerData && issuerData.privateKey) ? issuerData.privateKey : privateKey;
          //console.log(signerKey)
          return certificate.sign(signerKey, (certData.algorithms && certData.algorithms.hashAlg) ? certData.algorithms.hashAlg : defaultAlgorithms.hashAlg)
        },
        error => Promise.reject(`Error during exporting public key: ${error}`));
      //endregion
    
      //region Encode and store certificate
      sequence = sequence.then(() =>
      {
        //console.log(certificate)
        certificateBuffer = certificate.toSchema(true).toBER(false);
      }, error => Promise.reject(`Error during signing: ${error}`));
      //endregion
    
      //region Exporting public key
      sequence = sequence.then(() =>
        crypto.exportKey("spki", publicKey)
      );
      //endregion
    
      //region Store exported public key on Web page
      sequence = sequence.then(result =>
      {
        publicKeyBuffer = result;
      }, error => Promise.reject(`Error during exporting of public key: ${error}`));
      //endregion
    
      //region Exporting private key
      sequence = sequence.then(() =>
        crypto.exportKey("pkcs8", privateKey)
      );
      //endregion
    
      //region Store exported key on Web page
      sequence = sequence.then(result =>
      {
        privateKeyBuffer = result;
      }, error => Promise.reject(`Error during exporting of private key: ${error}`));
      //endregion
    
      return sequence.then(() => {
    
        const result = {
          certificate: certificate,
          certificatePEM: encodePEM(certificateBuffer, "CERTIFICATE"),
          publicKey: publicKey,
          publicKeyPEM: encodePEM(publicKeyBuffer, "PUBLIC KEY"),
          privateKey: privateKey,
          privateKeyPEM: encodePEM(privateKeyBuffer, "PRIVATE KEY")
    
    Markin Igor's avatar
    Markin Igor committed
        };
    
        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;
        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) {
      const pemStripped = pemString.replace(/(-----(BEGIN|END) [a-zA-Z ]*-----|\r|\n)/g, '');
      const pemDecoded = window.atob(pemStripped);
      const buffer = pvutils.stringToArrayBuffer(pemDecoded);
      return buffer;
    }
    
    //*********************************************************************************
    function parseCertificate(certificatePEM) {
      const certificateBuffer = decodePEM(certificatePEM);
      const asn1 = asn1js.fromBER(certificateBuffer);
      const certificate = new pkijs.Certificate({ schema: asn1.result });
      return certificate;
    }
    
    //*********************************************************************************
    function parsePublicKey(publicKeyPEM) {
      const publicKeyBuffer = decodePEM(publicKeyPEM);
      const crypto = pkijs.getCrypto();
      const publicKeyPromise = crypto.importKey(
        "spki",
        publicKeyBuffer,
        {   //these are the algorithm options
          name: "RSASSA-PKCS1-v1_5",
          hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
        },
        true,
        ["verify"]
      );
      return publicKeyPromise;
    }
    
    //*********************************************************************************
    function encryptMessage(message, password, label) {
      const buffer = pvutils.stringToArrayBuffer(message);
      const secret = pvutils.stringToArrayBuffer(password);
    
      const enveloped = new pkijs.EnvelopedData();
      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);
          return encodePEM(ber, label)
        },
        error => Promise.reject(`encryption error: ${error}`)
      )
    }
    
    //*********************************************************************************
    function decryptMessage(message, password) {
      const secret = pvutils.stringToArrayBuffer(password);
      const buffer = decodePEM(message);
    
      const asn1 = asn1js.fromBER(buffer);
      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(() => {
        throw("Wrong pincode")
      })
    }
    
    //*********************************************************************************
    function parsePrivateKey(privateKeyPEM) {
      const privateKeyBuffer = decodePEM(privateKeyPEM);
      const crypto = pkijs.getCrypto();
      const privateKeyPromise = crypto.importKey(
        "pkcs8",
        privateKeyBuffer,
        {   //these are the algorithm options
          name: "RSASSA-PKCS1-v1_5",
          hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
        },
        true,
        ["sign"]
      );
      return privateKeyPromise;
    }
    
    
    function createPassportCertificate(commonNameArg) {
      const 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 + "-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
          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
    
    Markin Igor's avatar
    Markin Igor committed
      };
    
    
      return createCertificate(certData, null)
    }
    
    function createOneTimePassportCertificate(commonNameArg, emailArg, privateKeyIssuerArg, certicateIssuerArg) {
    
    Markin Igor's avatar
    Markin Igor committed
      var certData = null;
    
      if(emailArg != null && emailArg != "") {
        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
            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
        }
      } else {
        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
            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
        }
      }
    
      return parsePrivateKey(privateKeyIssuerArg).then(privateKeyDecoded => {
        const issuerData = {
          certificate: parseCertificate(certicateIssuerArg),// vereignCACertPEM),
          privateKey: privateKeyDecoded
    
    Markin Igor's avatar
    Markin Igor committed
        };
    
        return createCertificate(certData, issuerData);
        //console.log(vereignIntermediateKey)
      });
    }
    
    function download(filename, contentType, text) {
      var element = document.createElement('a');
      element.setAttribute('href', 'data:' + contentType + ';charset=utf-8,' + encodeURIComponent(text));
      element.setAttribute('download', filename);
    
      element.style.display = 'none';
      document.body.appendChild(element);
    
      element.click();
    
      document.body.removeChild(element);
    }
    
    function arrayBufferToBase64Formatted(buffer) {
      const bufferString = String.fromCharCode.apply(null, new Uint8Array(buffer));
      const base64formatted = formatPEM(window.btoa(bufferString));
      return base64formatted;
    }
    
    function arrayBufferToBase64(buffer) {
      const bufferString = String.fromCharCode.apply(null, new Uint8Array(buffer));
      const base64 = window.btoa(bufferString);
      return base64;
    }
    
    const newline = /\r\n|\r|\n/g;
    
    function capitalizeFirstLetter(string) {
      if(string == "id") {
        return "ID"
      }
    
      if(string == "mime") {
        return "MIME";
      }
    
      return string.charAt(0).toUpperCase() + string.slice(1);
    }
    
    function capitalizeHeader(string) {
    
    Markin Igor's avatar
    Markin Igor committed
      result = "";
      tokens = string.split("-");
    
      for(var i = 0; i < tokens.length; i++) {
    
    Markin Igor's avatar
    Markin Igor committed
        result += capitalizeFirstLetter(tokens[i]);
    
        if(i != tokens.length - 1) {
          result += "-"
        }
      }
    
      return result
    }
    
    function signEmail(mime, signingCert, certificateChain, privateKey) {
    
    Markin Igor's avatar
    Markin Igor committed
      signingCertObj = parseCertificate(signingCert);
      certificateChainObj = [];
      certificateChainObj[0] = parseCertificate(signingCert);
    
      for(var i = 0; i < certificateChain.length; i++) {
        certificateChainObj[i + 1] = parseCertificate(certificateChain[i])
      }
      //console.log(certificateChainObj)
    
      return parsePrivateKey(privateKey).then(privateKeyDecoded => {
    
        return signEmailObjects(mime, signingCertObj, certificateChainObj, privateKeyDecoded);
        //console.log(vereignIntermediateKey)
      });
    }
    
    function signEmailObjects(mime, signingCert, certificateChain, privateKey) {
    
      //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
    
      let template =
        `{{headers}}Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha-256; boundary="{{boundary}}"
    MIME-Version: 1.0
    
    This is a cryptographically signed message in MIME format.
    
    --{{boundary}}
    {{mime}}
    --{{boundary}}
    Content-Type: application/pkcs7-signature; name="smime.p7s"
    Content-Transfer-Encoding: base64
    Content-Disposition: attachment; filename="smime.p7s"
    Content-Description: S/MIME Cryptographic Signature
    
    {{signature}}
    --{{boundary}}--
    
    Vereign - Authentic Communication
    `.replace(newline, '\r\n');
    
      const detachedSignature = true;
      const addExt = true;
      const hashAlg = "SHA-256";
      let cmsSignedSimpl;
    
      var mimeHeadersTitles = [
        "Content-Type",
        "Content-Transfer-Encoding",
        "Content-ID",
        "Content-Description",
        "Content-Disposition",
        "Content-Language",
        "Content-Location"
    
    Markin Igor's avatar
    Markin Igor committed
      ];
    
    
      mime = mime.replace(newline, '\r\n');
    
    
    Markin Igor's avatar
    Markin Igor committed
      let newHeaderLines = "";
      let headersEnd = mime.indexOf('\r\n\r\n'); //the first empty line
    
    
      if (headersEnd < 0 && mime.startsWith('\r\n')) {
    
        mime = mime.substring(2) //should not happen
    
      } else
      if (headersEnd >= 0) {
    
    
    Markin Igor's avatar
    Markin Igor committed
        let mimeHeaders = {};
        let mimeBody = mime.substring(headersEnd + 4);
    
    Markin Igor's avatar
    Markin Igor committed
        let mimeHeadersStr = mime.substring(0, headersEnd);
    
    Markin Igor's avatar
    Markin Igor committed
        let headers = libmime.decodeHeaders(mimeHeadersStr);
    
        for (var i = 0; i < mimeHeadersTitles.length; i++) {
    
    Markin Igor's avatar
    Markin Igor committed
          let key = mimeHeadersTitles[i].toLowerCase();
    
          if(key in headers) {
    
    Markin Igor's avatar
    Markin Igor committed
            mimeHeaders[key] = headers[key];
    
            delete headers[key]
          }
        }
    
        for(let key in headers) {
          if(!(key === "" || key === "MIME-Version".toLowerCase())) { //we have MIME-Version in the template
            newHeaderLines += capitalizeHeader(key) + ": " + headers[key] + '\r\n';
          }
        }
    
    
    Markin Igor's avatar
    Markin Igor committed
        let newMimeHeaderLines = "";
    
        for(let key in mimeHeaders) {
          if(!(key === "")) {
            newMimeHeaderLines += capitalizeHeader(key) + ": " + mimeHeaders[key] + '\r\n';
          }
        }
    
        if (newMimeHeaderLines === "") {
          newMimeHeaderLines = 'Content-Type: text/plain\r\n' //should not happen
        }
    
        mime = newMimeHeaderLines + '\r\n' + mimeBody
      }
    
    
    Markin Igor's avatar
    Markin Igor committed
      let dataBuffer = Buffer.from(mime,'utf-8');
    
    
      let sequence = Promise.resolve();
    
      //region Check if user wants us to include signed extensions
      if(addExt)
      {
        //region Create a message digest
        sequence = sequence.then(
    
          () => crypto.digest({ name: hashAlg }, dataBuffer)
    
        );
        //endregion
    
        //region Combine all signed extensions
        sequence = sequence.then(
          messageHash =>
          {
            const signedAttr = [];
            /*
                    1.2.840.113549.1.9.1 - e-mailAddress
                    1.2.840.113549.1.9.2 - PKCS-9 unstructuredName
                    1.2.840.113549.1.9.3 - contentType
                    1.2.840.113549.1.9.4 - messageDigest
                    1.2.840.113549.1.9.5 - Signing Time
                    1.2.840.113549.1.9.6 - counterSignature
            */
            signedAttr.push(new pkijs.Attribute({
              type: OID_PKCS9_ContentType, //contentType
              values: [
                new asn1js.ObjectIdentifier({ value: OID_PKCS7_Data}) //data
              ]
              /*
                          1.2.840.113549.1.7.1 - data
                          1.2.840.113549.1.7.2 - signedData
                          1.2.840.113549.1.7.3 - envelopedData
                          1.2.840.113549.1.7.4 - signedAndEnvelopedData
                          1.2.840.113549.1.7.5 - digestedData
                          1.2.840.113549.1.7.6 - encryptedData
              */
            })); // contentType
    
            signedAttr.push(new pkijs.Attribute({
              type: OID_PKCS9_SigningTime, //Signing Time
              values: [
                new asn1js.UTCTime({ valueDate: new Date() })
              ]
            })); // signingTime
    
            signedAttr.push(new pkijs.Attribute({
              type: OID_PKCS9_MessageDigest, //messageDigest
              values: [
                new asn1js.OctetString({ valueHex: messageHash })
              ]
            })); // messageDigest
    
            return signedAttr;
          }
        );
        //endregion
      }
      //endregion
    
      //region Initialize CMS Signed Data structures and sign it
      sequence = sequence.then(
        signedAttr =>
        {
          cmsSignedSimpl = new pkijs.SignedData({
            version: 1,
            encapContentInfo: new pkijs.EncapsulatedContentInfo({
              eContentType: OID_PKCS7_Data // "data" content type
            }),
            signerInfos: [
              new pkijs.SignerInfo({
                version: 1,
                sid: new pkijs.IssuerAndSerialNumber({
                  issuer: signingCert.issuer,
                  serialNumber: signingCert.serialNumber
                })
              })
            ],
            certificates: certificateChain //array
          });
    
          if(addExt)
          {
            cmsSignedSimpl.signerInfos[0].signedAttrs = new pkijs.SignedAndUnsignedAttributes({
              type: 0,
              attributes: signedAttr
            });
          }
    
          if(detachedSignature === false)
          {
            const contentInfo = new pkijs.EncapsulatedContentInfo({
              eContent: new asn1js.OctetString({ valueHex: dataBuffer })
            });
    
            cmsSignedSimpl.encapContentInfo.eContent = contentInfo.eContent;
    
            return cmsSignedSimpl.sign(privateKey, 0, hashAlg);
          }
    
          return cmsSignedSimpl.sign(privateKey, 0, hashAlg, dataBuffer);
        }
      );
      //endregion
    
      //region Create final result
      sequence = sequence.then(
        (result) =>
        {
          const cmsSignedSchema = cmsSignedSimpl.toSchema(true);
    
          const cmsContentSimp = new pkijs.ContentInfo({
            contentType: OID_PKCS7_SignedData, //signedData
            content: cmsSignedSchema
          });
    
          const _cmsSignedSchema = cmsContentSimp.toSchema();
    
          //region Make length of some elements in "indefinite form"
          _cmsSignedSchema.lenBlock.isIndefiniteForm = true;
    
          const block1 = _cmsSignedSchema.valueBlock.value[1];
          block1.lenBlock.isIndefiniteForm = true;
    
          const block2 = block1.valueBlock.value[0];
          block2.lenBlock.isIndefiniteForm = true;
    
          if(detachedSignature === false)
          {
            const block3 = block2.valueBlock.value[2];
            block3.lenBlock.isIndefiniteForm = true;
            block3.valueBlock.value[1].lenBlock.isIndefiniteForm = true;
            block3.valueBlock.value[1].valueBlock.value[0].lenBlock.isIndefiniteForm = true;
          }
          //endregion
    
          const cmsSignedBuffer = _cmsSignedSchema.toBER(false);
          return cmsSignedBuffer;
        },
        error => Promise.reject(`Erorr during signing of CMS Signed Data: ${error}`)
      );
      //endregion
    
      sequence = sequence.then(
        (cmsSignedBuffer) =>
        {
          let signature = arrayBufferToBase64Formatted(cmsSignedBuffer);
    
    Markin Igor's avatar
    Markin Igor committed
          let boundary = makeBoundary();
    
    Markin Igor's avatar
    Markin Igor committed
          template = template.replace(/{{boundary}}/g, boundary);
          template = template.replace("{{signature}}", signature);
          template = template.replace("{{headers}}", newHeaderLines);
          template = template.replace("{{mime}}", mime);
    
    
          //template = template.replace(newline, '\r\n')
          return template
        }
      );
    
      return sequence;
    }
    
    function makeBoundary() {
    
    Markin Igor's avatar
    Markin Igor committed
      let len = 20 + Math.random() * 20;
    
      return 'W0RyLiBEYW15YW4gTWl0ZXZd--' + makeid(len)
    }
    
    function makeid(len) {
      if (typeof len === 'undefined') {
        len = 10
      }
      var text = "";