Skip to content
Snippets Groups Projects
viamapi-iframe.js 83.9 KiB
Newer Older
import {createDeviceHash} from '../utilities/appUtility';
Markin Igor's avatar
Markin Igor committed
import {LOGIN_MODES} from '../constants';
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({
Loading
Loading full blame...