Skip to content
Snippets Groups Projects
signingUtilities.js 37 KiB
Newer Older
Alexey Lunin's avatar
Alexey Lunin committed
import {
  canTryPincode,
  failPincodeAttempt,
  getTimeLeftInLocalStorage,
  makeid
} from "./appUtility";
import { bufferToHexCodes, stringToArrayBuffer } from "pvutils";
import { fromBER } from "asn1js";
import { ContentInfo, SignedData } from "pkijs";
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";
Alexey Lunin's avatar
Alexey Lunin committed

const libmime = require("libmime");
const pkijs = require("pkijs");
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;

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
};

//*********************************************************************************
// 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);
}

//*********************************************************************************
Alexey Lunin's avatar
Alexey Lunin committed
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;

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(
Alexey Lunin's avatar
Alexey Lunin committed
    () =>
      crypto.digest(
        { name: "SHA-1" },
        certificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex
      ),
    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;

    const serialNumberBuffer = new ArrayBuffer(20);
    const serialNumberView = new Uint8Array(serialNumberBuffer);
    pkijs.getRandomValues(serialNumberView);
Loading
Loading full blame...