diff --git a/javascript/src/iframe/viamapi-iframe.js b/javascript/src/iframe/viamapi-iframe.js index aec4b16e3ef7deb60d5d35927e56c967b261bc4b..1df7b53b1e79009ecb58efda7ad7f7f4ca658f41 100644 --- a/javascript/src/iframe/viamapi-iframe.js +++ b/javascript/src/iframe/viamapi-iframe.js @@ -20,10 +20,11 @@ import { } from "../utilities/appUtility"; import { LOGIN_MODES } from "../constants/authentication"; import { + CertificateData, createOneTimePassportCertificate, createPassportCertificate, decryptMessage, - encryptMessage, + encryptMessage, parseCertificate, signEmail, verifySMIME } from "../utilities/signingUtilities"; @@ -1095,6 +1096,43 @@ const connection = Penpal.connectToParent({ return encodeResponse("200", verificationResult.verified, verificationResult.message); }, + validateDocument: async (documentUUID, contentType) => { + const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); + + if ( + !authenticationPublicKey || + !window.loadedIdentities[authenticationPublicKey] || + !extendPinCodeTtl(authenticationPublicKey) + ) { + return encodeResponse("400", "", "Identity not authenticated"); + } + + const validateDocumentResponse = await executeRestfulFunction( + "private", + window.viamApi, + window.viamApi.documentValidateDocumentByUUID, + null, + documentUUID, + contentType); + + if (validateDocumentResponse.code !== "200") { + return encodeResponse("400", "", validateDocumentResponse.status); + } + + const signatures = validateDocumentResponse.data; + if (signatures) { + for (const signature of signatures) { + const certificateChain = signature.certificateChainPEM.map((certificatePEM) => { + const certificate = parseCertificate(certificatePEM); + const certificateData = new CertificateData(certificate); + return certificateData; + }); + signature.certificateChain = certificateChain; + } + } + + return validateDocumentResponse; + }, signEmail: async (passportUUID, emailArg, emailMessage) => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" diff --git a/javascript/src/utilities/signingUtilities.js b/javascript/src/utilities/signingUtilities.js index bb6027a04133449d4044806027472b419798a70e..3fad4cbc4c74e05a39c3eb93f39a5c16ec5d406e 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,182 @@ const encryptionAlgorithm = { length: 128 }; +// Convert a hex string to a byte array +function hexStringToBytes(hex) { + 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 + + this.keyPair = null; // write only; {publicKey, privateKey} + + 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() + 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) { + 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) { + 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} //********************************************************************************* @@ -146,14 +322,18 @@ function fixPkijsRDN() { //********************************************************************************* function createCertificate(certData, issuerData = null) { - if (typeof certData === "undefined") { + if (typeof certData === "undefined" || certData === null) { return Promise.reject("No Certificate data provided"); } - if (typeof certData.subject === "undefined") { + if (typeof certData.subject === "undefined" || certData.subject === null) { return Promise.reject("No Certificate subject data provided"); } + 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(); @@ -219,14 +399,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 @@ -680,7 +886,7 @@ export function parsePrivateKey(privateKeyPEM) { } export function createPassportCertificate(commonNameArg) { - const certData = { + const certDataParams = { algorithms: { hashAlg: "SHA-256", signAlg: "RSASSA-PKCS1-v1_5", @@ -705,7 +911,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( @@ -1066,48 +1274,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) {