From de806e19f6cfcd137b8c44386b9993ec3eff3c64 Mon Sep 17 00:00:00 2001
From: Damyan Mitev <damyan.mitev@vereign.com>
Date: Tue, 1 Oct 2019 16:52:48 +0300
Subject: [PATCH] Add new CertificateData class, move parsing of
 pkijs.Certificate object into its constructor

---
 javascript/src/utilities/signingUtilities.js | 263 +++++++++++++++----
 1 file changed, 210 insertions(+), 53 deletions(-)

diff --git a/javascript/src/utilities/signingUtilities.js b/javascript/src/utilities/signingUtilities.js
index 338c627..d16b356 100644
--- a/javascript/src/utilities/signingUtilities.js
+++ b/javascript/src/utilities/signingUtilities.js
@@ -10,7 +10,7 @@ import {
   isEqualBuffer
 } from "pvutils";
 import { fromBER } from "asn1js";
-import { ContentInfo, SignedData } from "pkijs";
+import { ContentInfo, SignedData, Certificate } from "pkijs";
 import { algomap, rdnmap } from "../constants/certificates";
 import {
   fixNewLines,
@@ -85,6 +85,175 @@ const encryptionAlgorithm = {
   length: 128
 };
 
+// Convert a hex string to a byte array
+function hexStringToBytes(hex) {
+  for (let 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
+
+    this.keyPair = null; //{publicKey, privateKey}
+
+    this.signatureAlgorithm = null; //TODO remove from here and from dashboard
+    this.algorithms = {
+      hashAlg: null, //"SHA-256"
+      signAlg: null, //"RSASSA-PKCS1-v1_5"
+      keyLength: 0 //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()
+      validYears: 1 //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.algorithms.signAlg = 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) {
+      this.keyPair = parameters.keyPair;
+    }
+
+    if ("signatureAlgorithm" in parameters) {
+      this.signatureAlgorithm = parameters.signatureAlgorithm;
+    }
+
+    if ("algorithms" in parameters) {
+      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}
 //*********************************************************************************
@@ -219,14 +388,40 @@ function createCertificate(certData, issuerData = null) {
     //region Put a static values
     certificate.version = CERTIFIATE_Version_3;
 
-    const serialNumberBuffer = new ArrayBuffer(20);
-    const serialNumberView = new Uint8Array(serialNumberBuffer);
-    pkijs.getRandomValues(serialNumberView);
-    serialNumberView[0] &= 0x7f;
-    // noinspection JSUnresolvedFunction
-    certificate.serialNumber = new asn1js.Integer({
-      valueHex: serialNumberView
-    });
+    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;
+      } else if (certData.serialNumber instanceof 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;
+      // noinspection JSUnresolvedFunction
+      certificate.serialNumber = new asn1js.Integer({
+        valueHex: serialNumberView
+      });
+    }
     //endregion Put a static values
 
     //region Subject
@@ -676,7 +871,7 @@ export function parsePrivateKey(privateKeyPEM) {
 }
 
 export function createPassportCertificate(commonNameArg) {
-  const certData = {
+  const certDataParams = {
     algorithms: {
       hashAlg: "SHA-256",
       signAlg: "RSASSA-PKCS1-v1_5",
@@ -701,7 +896,9 @@ export function createPassportCertificate(commonNameArg) {
     isCA: true // optional flag denoting if this is CA certificate or leaf certificate, defaults to false
   };
 
-  return createCertificate(certData, null);
+  const certificateData = new CertificateData(certDataParams);
+
+  return createCertificate(certificateData, null);
 }
 
 export function createOneTimePassportCertificate(
@@ -1062,48 +1259,8 @@ export const parseSignedData = signatureBase64 => {
 
 export const parseCertificates = signedData => {
   try {
-    return signedData.certificates.map((certificate, index) => {
-      const certificateData = { issuer: {}, subject: {}, validity: {} };
-      const serialNumber = bufferToHexCodes(
-        certificate.serialNumber.valueBlock.valueHex
-      );
-      const issuer = certificate.issuer.typesAndValues;
-      const subject = certificate.subject.typesAndValues;
-      const notAfter = certificate.notAfter.value;
-      const notBefore = certificate.notBefore.value;
-      let signatureAlgorithm =
-        algomap[certificate.signatureAlgorithm.algorithmId];
-      if (typeof signatureAlgorithm === "undefined") {
-        signatureAlgorithm = certificate.signatureAlgorithm.algorithmId;
-      } else {
-        signatureAlgorithm = `${signatureAlgorithm}`;
-      }
-
-      for (const typeAndValue of issuer) {
-        let typeVal = rdnmap[typeAndValue.type];
-        if (typeof typeVal === "undefined") {
-          typeVal = typeAndValue.type;
-        }
-        const subjVal = typeAndValue.value.valueBlock.value;
-        certificateData.issuer[typeVal] = subjVal;
-      }
-
-      for (const typeAndValue of subject) {
-        let typeVal = rdnmap[typeAndValue.type];
-        if (typeof typeVal === "undefined") {
-          typeVal = typeAndValue.type;
-        }
-        const subjVal = typeAndValue.value.valueBlock.value;
-        certificateData.subject[typeVal] = subjVal;
-      }
-
-      certificateData.signatureAlgorithm = signatureAlgorithm;
-      certificateData.serialNumber = serialNumber;
-      certificateData.validity = {
-        notAfter,
-        notBefore
-      };
-
+    return signedData.certificates.map((certificate) => {
+      const certificateData = new CertificateData(certificate);
       return certificateData;
     });
   } catch (e) {
-- 
GitLab