import {createDeviceHash} from '../utilities/appUtility'; 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"; const OID_PKCS9_SigningTime = "1.2.840.113549.1.9.5"; const defaultAlgorithms = { hashAlg: "SHA-256", signAlg: "RSASSA-PKCS1-v1_5", keyLength: 2048 }; 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); 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; 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(() => { 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") }; 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) { if (canTryPincode()) { 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(() => { return Promise.reject(failPincodeAttempt(password)); }); } else { return Promise.reject(getTimeLeftInLocalStorage()); } } //********************************************************************************* function getBlockFinishTimeInLocalStorage() { return localStorage.getItem("blockFinishTime") || getCurrentTime(); } function getCurrentTime() { return Math.floor(new Date().getTime() / 1000); } function getTimeLeftInLocalStorage() { const blockFinishTime = getBlockFinishTimeInLocalStorage(); const timeNow = getCurrentTime(); const seconds = (blockFinishTime - timeNow) % 60; let minutes = Math.floor((blockFinishTime - timeNow) / 60); minutes %= 60; const left = "Your identity has been locked. Try again in " + minutes + " minutes and " + seconds + " seconds."; return left; } function failPincodeAttempt(password) { let message = "Wrong pincode"; if (password !== '00000000') { let attempt = localStorage.getItem("attempt") || 1; attempt = parseInt(attempt); if (attempt === 9) { const identitiesTemp = listIdentitiesFromLocalStorage(); for (let i in identitiesTemp) { destroyIdentityFromLocalStorage(i); } message = "9 failed attempts. Identity is revoked!"; localStorage.removeItem("attempt"); } else if (attempt % 3 === 0) { const timeNow = getCurrentTime(); const blockFinishTime = timeNow + 300; localStorage.setItem("blockFinishTime", blockFinishTime); localStorage.setItem("attempt", attempt + 1); message = "3 failed attempts. Identity is locked!"; } else { localStorage.setItem("attempt", attempt + 1); } } return message; } function canTryPincode() { const timeNow = getCurrentTime(); const blockFinishTime = getBlockFinishTimeInLocalStorage(); if (blockFinishTime <= timeNow) { localStorage.removeItem("blockFinishTime"); return true; } else { return false; } } //********************************************************************************* 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 }; return createCertificate(certData, null) } function createOneTimePassportCertificate(commonNameArg, emailArg, privateKeyIssuerArg, certicateIssuerArg) { 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 }; return createCertificate(certData, issuerData); }); } 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) { let result = ""; const tokens = string.split("-"); for (let i = 0; i < tokens.length; i++) { result += capitalizeFirstLetter(tokens[i]); if (i !== tokens.length - 1) { result += "-"; } } return result; } function signEmail(mime, signingCert, certificateChain, privateKey) { const signingCertObj = parseCertificate(signingCert); const certificateChainObj = []; certificateChainObj[0] = parseCertificate(signingCert); for (let i = 0; i < certificateChain.length; i++) { certificateChainObj[i + 1] = parseCertificate(certificateChain[i]) } return parsePrivateKey(privateKey).then(privateKeyDecoded => { return signEmailObjects(mime, signingCertObj, certificateChainObj, privateKeyDecoded); }); } 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" ]; mime = mime.replace(newline, '\r\n'); 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) { let mimeHeaders = {}; let mimeBody = mime.substring(headersEnd + 4); let mimeHeadersStr = mime.substring(0, headersEnd); let headers = libmime.decodeHeaders(mimeHeadersStr); for (var i = 0; i < mimeHeadersTitles.length; i++) { let key = mimeHeadersTitles[i].toLowerCase(); if(key in headers) { 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'; } } 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 } 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); let boundary = makeBoundary(); 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() { let len = 20 + Math.random() * 20; return 'W0RyLiBEYW15YW4gTWl0ZXZd--' + makeid(len) } function makeid(len) { if (typeof len === 'undefined') { len = 10 } var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (var i = 0; i < len; i++) text += possible.charAt(Math.floor(Math.random() * possible.length)); return text; } function CryptoData() { } CryptoData.prototype.set = function(obj) { for(var member in obj) { this[member] = JSON.parse(JSON.stringify(obj[member])) } }; CryptoData.prototype.serialize = function() { return JSON.stringify(this) }; CryptoData.prototype.deserialize = function(serialized) { var obj = JSON.parse(serialized); this.set(obj) }; CryptoData.prototype.setPublicKey = function(publicKey) { this["publicKey"] = publicKey }; CryptoData.prototype.getPublicKey = function() { return this["publicKey"] }; CryptoData.prototype.setPrivateKey = function(privateKey) { this["privateKey"] = privateKey }; CryptoData.prototype.getPrivateKey = function() { return this["privateKey"] }; CryptoData.prototype.setx509Certificate = function(x509Certificate) { this["x509Certificate"] = x509Certificate }; CryptoData.prototype.getx509Certificate = function() { return this["x509Certificate"] }; CryptoData.prototype.setKeyUUID = function(keyUUID) { this["keyUUID"] = keyUUID }; CryptoData.prototype.getKeyUUID = function() { return this["keyUUID"] }; CryptoData.prototype.setChain = function(chain) { this["chain"] = chain }; CryptoData.prototype.getChain = function() { return this["chain"] }; function Identity() { } Identity.prototype.set = function(obj) { for(var member in obj) { this[member] = JSON.parse(JSON.stringify(obj[member])) } }; Identity.prototype.serialize = function() { return JSON.stringify(this) }; Identity.prototype.deserialize = function(serialized) { var obj = JSON.parse(serialized); this.set(obj) }; Identity.prototype.setAuthentication = function(cryptoData) { this["authentication"] = cryptoData }; Identity.prototype.getAuthentication = function() { return this["authentication"] }; Identity.prototype.setPinCode = function(pinCode) { this["pinCode"] = pinCode }; Identity.prototype.getPinCode = function() { return this["pinCode"] }; Identity.prototype.setPassport = function(passportUUID, cryptoData) { if(this["passports"] === undefined || this["passports"] === null) { this["passports"] = {} } this["passports"][passportUUID] = cryptoData }; Identity.prototype.getPassport = function(passportUUID) { if(this["passports"] === undefined || this["passports"] === null) { this["passports"] = {} } return this["passports"][passportUUID] }; var identityColors = ["#994392", "#cb0767", "#e51d31", "#ec671b", "#fab610"]; function getNextColor() { var colorIndex = localStorage.getItem("colorIndex"); if (colorIndex == null || colorIndex === "") { colorIndex = 0 } var color = identityColors[colorIndex]; colorIndex++; colorIndex = colorIndex % identityColors.length; localStorage.setItem("colorIndex", colorIndex); return color } function setKeyForUUID(uuid, key) { var storedIdentityForUuid = localStorage.getItem("keyperuuid/" + uuid); if(storedIdentityForUuid !== key && storedIdentityForUuid != null && storedIdentityForUuid !== "") { destroyIdentityFromLocalStorage(storedIdentityForUuid) } localStorage.setItem("keyperuuid/" + uuid, key) } function getColorForIdentity(key) { var storedColor = localStorage.getItem("colors/" + key); if(storedColor == null || storedColor === "") { storedColor = getNextColor(); localStorage.setItem("colors/" + key, storedColor) } return storedColor } function setIdentityInLocalStorage(identityToStore, extendKey = true) { var pinCode = identityToStore.pinCode; const serializedIdentity = JSON.stringify(identityToStore); const key = identityToStore.authentication.publicKey; if(pinCode == null || pinCode === "") { pinCode = getPincode(key) } if(pinCode == null || pinCode === "") { return null; } return encryptMessage(serializedIdentity, pinCode, "identity").then((encryptedIdentity) => { var success = true; if(extendKey === true) { success = extendPinCodeTtl(key, pinCode) } if (success === true) { localStorage.setItem(key, encryptedIdentity); let serializedIdentitiesList = localStorage.getItem("identities"); let identities = JSON.parse(serializedIdentitiesList); identities[key] = true; localStorage.setItem("identities", JSON.stringify(identities)) } else { console.log("Can not extend pincode ttl"); } }); } function getProfileData(identity) { return new Penpal.Promise(executeResultUpper => { executeRestfulFunction("private", viamApi, viamApi.identityGetIdentityProfileData).then(executeResult => { if(executeResult.code === "200") { var listItem = {}; listItem.identityColor = getColorForIdentity(identity.authentication.publicKey); listItem.initials = executeResult.data.initials; if(listItem.initials === null || listItem.initials === "") { listItem.initials = "JD"; } localStorage.setItem("profiles/" + identity.authentication.publicKey, JSON.stringify(listItem)); executeResultUpper(listItem) } else { executeResultUpper({}) } }); }); } function getIdentityFromLocalStorage(key, pinCode, extendTtl = true) { const encryptedIdentity = localStorage.getItem(key); if (encryptedIdentity == null) { console.log("No such identity for public key"); return Promise.resolve(null) } return decryptMessage(encryptedIdentity, pinCode).then((serializedIdentity) => { var parsedIdentity = JSON.parse(serializedIdentity); parsedIdentity["pinCode"] = ""; if(extendTtl === true) { var success = extendPinCodeTtl(key, pinCode); if (success === true) { return parsedIdentity } else { console.log("Can not extend pincode ttl"); return null } } else { return parsedIdentity } }); } function listIdentitiesFromLocalStorage() { var serializedIdentitiesList = localStorage.getItem("identities"); var identities = JSON.parse(serializedIdentitiesList); var identitiesResult = {}; for(var key in identities) { var profile = JSON.parse(JSON.stringify(localStorage.getItem("profiles/" + key))); if(profile != null && profile !== "") { identitiesResult[key] = JSON.parse(profile) } else { identitiesResult[key] = {} } } return identitiesResult } function extendPinCodeTtl(key, pinCode) { if(pinCode == null || pinCode === "") { var now = new Date(); var nowMillis = now.getTime(); var ttl = window.sessionStorage.getItem("pincodettls/" + key); if (ttl == null || ttl === "" || nowMillis >= parseInt(ttl)) { clearPinCodeTtl(key); return false } else { var ttl = now.getTime() + 4 * 60 * 60 * 1000; window.sessionStorage.setItem("pincodettls/" + key, ttl); } } else { var now = new Date(); var ttl = now.getTime() + 4 * 60 * 60 * 1000; window.sessionStorage.setItem("pincodettls/" + key, ttl); window.sessionStorage.setItem("pincodes/" + key, pinCode); } return true; } window.extendPinCodeTtl = extendPinCodeTtl; function clearPinCodeTtl(key) { window.sessionStorage.removeItem("pincodettls/" + key); window.sessionStorage.removeItem("pincodes/" + key) } function getPincode(key) { var now = new Date(); var nowMillis = now.getTime(); var ttl = window.sessionStorage.getItem("pincodettls/" + key); if (ttl == null || ttl === "") { return null } else { if(nowMillis >= parseInt(ttl)) { clearPinCodeTtl(key); return null } else { return window.sessionStorage.getItem("pincodes/" + key); } } } function createEvent(actionId, type, payloads) { return { "actionID": actionId, "type": type, "stamp": new Date().getTime(), "payloads" : payloads } } function destroyIdentityFromLocalStorage(key) { localStorage.removeItem(key); localStorage.removeItem("profiles/" + key); localStorage.removeItem("colors/" + key); var serializedIdentitiesList = localStorage.getItem("identities"); var identities = JSON.parse(serializedIdentitiesList); identities[key] = null; delete identities[key]; localStorage.setItem("identities", JSON.stringify(identities)) } window.loadedIdentities = {}; window.wopiAPI = new WopiAPI(); window.viamApi = new ViamAPI(); window.viamAnonymousApi = new ViamAPI(); window.currentlyAuthenticatedIdentity = null; window.currentlyLoadedIdentity = null; window.lastTimeGetProfile = 0; const handleIdentityLogin = (identity, uuid, token) => { const { loadedIdentities, viamApi } = window; const { publicKey } = identity.authentication; viamApi.setSessionData(uuid, token); localStorage.setItem("uuid", uuid); localStorage.setItem("token", token); localStorage.setItem("authenticatedIdentity", publicKey); window.currentlyAuthenticatedIdentity = loadedIdentities[publicKey]; window.lastTimeGetProfile = 0; setKeyForUUID(uuid, publicKey); }; function executeRestfulFunction(type, that, fn, ...args) { const { currentlyAuthenticatedIdentity, viamApi, currentlyLoadedIdentity } = window; return new Penpal.Promise(executeResult => { fn.apply(that, args).then((response) => { const identity = currentlyAuthenticatedIdentity || currentlyLoadedIdentity; if (type === "private" && identity && response.data.code === "400" && response.data.status === "Bad session") { viamApi.identityLogin("previousaddeddevice") .then((response) => { if (response.data.code === "200") { const uuid = response.data.data["Uuid"]; const token = response.data.data["Session"]; handleIdentityLogin(identity, uuid, token); // TODO: Previously there was fn.apply(null, args) where null is probably wrong context for fn.apply() fn.apply(that, args).then(({data}) => executeResult(data)); } else { executeResult(response.data); } }) .catch(console.warn); } else { executeResult(response.data); } }); }); } window.executeRestfulFunction = executeRestfulFunction; function loadIdentityInternal(identityKey, pinCode) { return new Penpal.Promise(result => { getIdentityFromLocalStorage(identityKey, pinCode).then(async (loadedIdentity) => { if (loadedIdentity == null) { result({ "data": "", "code": "400", "status": "Please restore or authorize your account via another device." }); } localStorage.removeItem("attempt"); const copiedIdentity = JSON.parse(JSON.stringify(loadedIdentity)); window.loadedIdentities[identityKey] = loadedIdentity; if (identityKey === localStorage.getItem("authenticatedIdentity")) { window.currentlyAuthenticatedIdentity = copiedIdentity; const uuid = localStorage.getItem("uuid"); const token = localStorage.getItem("token"); const deviceHash = await createDeviceHash(identityKey); window.viamApi.setIdentity(identityKey); window.viamApi.setDeviceHash(deviceHash); window.viamApi.setSessionData(uuid, token); } window.currentlyLoadedIdentity = copiedIdentity; window.viamAnonymousApi.setIdentity(window.currentlyLoadedIdentity.authentication.publicKey); copiedIdentity.pinCode = ""; copiedIdentity.authentication.privateKey = ""; result({ "data": copiedIdentity, "code": "200", "status": "Identity loaded" }); }).catch((e) => { result({ "data": "", "code": "400", "status": "" + e }); }); }); } function changeIdentityPinCodeInternal(key, oldPinCode, newPinCode) { return new Penpal.Promise(result => { getIdentityFromLocalStorage(key, oldPinCode, false).then((identity) => { identity.pinCode = newPinCode; setIdentityInLocalStorage(identity).then(() => { result({ "data": "", "code": "200", "status": "Successfully changed pincode" }); }).catch((e) => { result({ "data": "", "code": "400", "status": "Cannot store identity " + e }); }); }).catch((e) => { result({ "data": "", "code": "400", "status": "Cannot get identity " + e }); }); }); } function getCertificateForPassport(passportUUID, internal) { return new Penpal.Promise(certificateResult => { if (window.currentlyAuthenticatedIdentity === null) { return {"data" : "", "code" : "400", "status" : "Identity not authenticated" } } var passportIdentity = new Identity(); passportIdentity.set(window.currentlyAuthenticatedIdentity); var passport = passportIdentity.getPassport(passportUUID); if(passport === undefined || passport === null) { createPassportCertificate(passportUUID).then(function(keys){ var cryptoData = new CryptoData(); cryptoData.setPublicKey(keys["publicKeyPEM"]); cryptoData.setPrivateKey(keys["privateKeyPEM"]); var certificate = keys["certificatePEM"]; //download("passportCertificateBeforeSigning.crt", "text/plain", certificate) //cryptoData.setx509Certificate(keys["certificate"]) executeRestfulFunction("private", viamApi, viamApi.signSignCertificate, btoa(certificate), passportUUID).then(executeResult => { if(executeResult.code === "200") { var signedCertificate = atob(executeResult.data["SignedCertificate"]); //download("passportCertificateAfterSigning.crt", "text/plain", signedCertificate) var keyUUID = executeResult.data["CertificateUUID"]; var encodedChain = executeResult.data["Chain"]; //download("rootCertificate.crt", "text/plain", atob(encodedChain[0])) var chain = []; for(var i = 0; i < encodedChain.length; i++) { chain.push(atob(encodedChain[i])) } cryptoData.setx509Certificate(signedCertificate); cryptoData.setKeyUUID(keyUUID); cryptoData.setChain(chain); passportIdentity.setPassport(passportUUID, cryptoData); getProfileData(passportIdentity).then(executeResult1 => { setIdentityInLocalStorage(passportIdentity).then(() => { window.currentlyAuthenticatedIdentity = passportIdentity; window.lastTimeGetProfile = 0; window.currentlyLoadedIdentity = passportIdentity; const copyOfCryptoData = JSON.parse(JSON.stringify(cryptoData)); if (internal === false) { copyOfCryptoData["privateKey"] = ""; } certificateResult({ "data": copyOfCryptoData, "code": "200", "status": "Certificate got" }); }).catch((e) => { certificateResult({ "data": "", "code": "400", "status": "Can not store certificate " + e }); }); }); } else { certificateResult(executeResult); } }); }); } else { var copyOfCryptoData = JSON.parse(JSON.stringify(passport)); if(internal === false) { copyOfCryptoData["privateKey"] = "" } certificateResult({"data" : copyOfCryptoData, "code" : "200", "status" : "Certificate got" }); } }); } const connection = Penpal.connectToParent({ // Methods child is exposing to parent methods: { initialize: (apiUrl, wopiUrl) => { window.API_HOST = apiUrl.charAt(apiUrl.length - 1) === "/" ? apiUrl : apiUrl + "/"; window.WOPI_URL = `${wopiUrl.charAt(wopiUrl.length - 1) === "/" ? wopiUrl : wopiUrl + "/"}getPassports`; }, createIdentity(pinCode) { return new Penpal.Promise(result => { createPassportCertificate(makeid()).then(function(keys){ var newIdentity = new Identity(); var cryptoData = new CryptoData(); cryptoData.setPublicKey(keys["publicKeyPEM"]); cryptoData.setPrivateKey(keys["privateKeyPEM"]); cryptoData.setx509Certificate(keys["certificatePEM"]); newIdentity.setAuthentication(cryptoData); newIdentity.setPinCode(pinCode); window.currentlyLoadedIdentity = newIdentity; window.loadedIdentities[newIdentity.authentication.publicKey] = newIdentity; extendPinCodeTtl(newIdentity.authentication.publicKey, pinCode); window.viamAnonymousApi.setIdentity(newIdentity.authentication.publicKey); result({"data" : newIdentity, "code" : "200", "status" : "Identity created" }) }); }) }, listIdentities() { return new Penpal.Promise(result => { var identities = listIdentitiesFromLocalStorage(); result({"data" : identities, "code" : "200", "status" : "Identities listed" }) }); }, loadIdentity(identityKey, pinCode) { return loadIdentityInternal(identityKey, pinCode) }, changeIdentityPinCode(key, oldPinCode, newPinCode) { return changeIdentityPinCodeInternal(key, oldPinCode, newPinCode) }, getIdentityProfile(identityKey) { return new Penpal.Promise(result => { const serializedProfile = localStorage.getItem("profiles/" + identityKey); if (serializedProfile === null || serializedProfile === "") { result({"data" : "", "code" : "400", "status" : "Profile is empty" }); } else { result({"data" : JSON.parse(serializedProfile), "code" : "200", "status" : "Identities cleared" }) } }); }, clearIdentities() { return new Penpal.Promise(result => { var identitiesTemp = listIdentitiesFromLocalStorage(); for(var i in identitiesTemp) { destroyIdentityFromLocalStorage(i) } result({"data" : "", "code" : "200", "status" : "Identities cleared" }) }); }, confirmIdentificator(identity, confirmationCodeArg) { return new Penpal.Promise(result => { viamApi.setIdentity(identity.authentication.publicKey); executeRestfulFunction("public", viamApi, viamApi.identityConfirmIdentificator,confirmationCodeArg).then(executeResult => { result(executeResult); }); }); }, identityGetIdentificatorByRegisterToken(identity, tokenArg) { return new Penpal.Promise(result => { viamApi.setIdentity(identity.authentication.publicKey); executeRestfulFunction("public", viamApi, viamApi.identityGetIdentificatorByRegisterToken,tokenArg).then(executeResult => { result(executeResult); }); }); }, submitIdentificator(identity, identificatorArg, registerToken) { return new Penpal.Promise(result => { viamApi.setIdentity(identity.authentication.publicKey); executeRestfulFunction("public", viamApi, viamApi.identitySubmitIdentificator,identificatorArg, registerToken).then(executeResult => { result(executeResult); }); }); }, submitRegisterClaims(identity, givennameArg,familynameArg,emailArg,phonenumberArg) { return new Penpal.Promise(result => { viamApi.setIdentity(identity.authentication.publicKey); executeRestfulFunction("public", viamApi, viamApi.identitySubmitRegisterClaims,givennameArg,familynameArg,emailArg,phonenumberArg).then(executeResult => { result(executeResult); }); }); }, agreeOnRegistration(registerIdentity) { return new Penpal.Promise(result => { viamApi.setIdentity(registerIdentity.authentication.publicKey); executeRestfulFunction("public", viamApi, viamApi.identityAgreeOnRegistration).then(executeResult => { let sequence = Promise.resolve(); if (executeResult.code === "200") { sequence = sequence.then(() => { setIdentityInLocalStorage(registerIdentity) } ) } sequence.then(() => { result(executeResult); }).catch((e) => { result({ "data": "", "code": "400", "status": "Can not store identity: " + e }) }) }); }); }, resendConfirmationCode(identity, identificatorArg) { return new Penpal.Promise(result => { viamApi.setIdentity(identity.authentication.publicKey); executeRestfulFunction("public", viamApi, viamApi.identityResendConfirmationCode,identificatorArg).then(executeResult => { result(executeResult); }); }); }, login: async (loginIdentity, mode, requestCode, requestActionID) => { if (!window.loadedIdentities[loginIdentity.authentication.publicKey]) { return { data: "", code: "400", status: "Identity not loaded" }; } const deviceHash = await createDeviceHash(loginIdentity.authentication.publicKey); window.viamApi.setDeviceHash(deviceHash); window.viamApi.setIdentity(loginIdentity.authentication.publicKey); const identityLoginResponse = await executeRestfulFunction( "public", window.viamApi, window.viamApi.identityLogin, mode, requestCode, requestActionID ); const { code, data } = identityLoginResponse; const responseToClient = Object.assign({}, identityLoginResponse); if (code === "200") { if (mode === LOGIN_MODES.SMS || mode === LOGIN_MODES.PREVIOUSLY_ADDED_DEVICE) { handleIdentityLogin(loginIdentity, data.Uuid, data.Session); await getProfileData(loginIdentity); if (mode === LOGIN_MODES.SMS) { await setIdentityInLocalStorage(loginIdentity); } } else if (mode === LOGIN_MODES.NEW_DEVICE) { const dataUrl = await QRCode.toDataURL(`${data.ActionID},${data.QrCode}`); Object.assign(responseToClient.data, { image: dataUrl }); } } return responseToClient; }, identityAddNewDevice() { return new Penpal.Promise(result => { const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); if (authenticationPublicKey === null) { result({"data" : "", "code" : "400", "status" : "Identity not authenticated" }) } if (window.loadedIdentities[authenticationPublicKey] === null) { result({"data" : "", "code" : "400", "status" : "Identity not authenticated" }) } var success = extendPinCodeTtl(authenticationPublicKey); if(success === false) { result({"data" : "", "code" : "400", "status" : "Identity not authenticated" }) } executeRestfulFunction("private", viamApi, viamApi.identityAddNewDevice).then(executeResult => { if (executeResult.code === "200") { var actionID = executeResult.data["ActionID"]; var QrCode = executeResult.data["QrCode"]; QRCode.toDataURL(actionID + "," + QrCode, function (err, url) { executeResult.data["image"] = url; result(executeResult); }) } else { result(executeResult); } }); }); }, identityDestroyKeysForDevice(authenticationPublicKeyArg) { return new Penpal.Promise(result => { const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); if (authenticationPublicKey === null) { result({"data" : "", "code" : "400", "status" : "Identity not authenticated" }) } if (window.loadedIdentities[authenticationPublicKey] === null) { result({"data" : "", "code" : "400", "status" : "Identity not authenticated" }) } var success = extendPinCodeTtl(authenticationPublicKey); if(success === false) { result({"data" : "", "code" : "400", "status" : "Identity not authenticated" }) } executeRestfulFunction("private", viamApi, viamApi.identityDestroyKeysForDevice, btoa(authenticationPublicKeyArg)).then(executeResult => { result(executeResult); }); }); }, logout() { const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); if (authenticationPublicKey === null) { return {"data" : "", "code" : "400", "status" : "Identity not loaded" } } if (window.loadedIdentities[authenticationPublicKey] === null) { return {"data" : "", "code" : "400", "status" : "Identity not loaded" } } return new Penpal.Promise(result => { executeRestfulFunction("private", viamApi, viamApi.identityLogout).then(executeResult => { viamApi.setIdentity(""); viamApi.setSessionData("", ""); clearPinCodeTtl(authenticationPublicKey); localStorage.removeItem("uuid"); localStorage.removeItem("token"); localStorage.removeItem("authenticatedIdentity"); delete window.loadedIdentities[authenticationPublicKey]; window.currentlyLoadedIdentity = null; window.currentlyAuthenticatedIdentity = null; window.lastTimeGetProfile = 0; result(executeResult); }); }); }, identityRestoreAccess(restoreAccessIdentity, identificator) { return new Penpal.Promise(result => { viamApi.setIdentity(restoreAccessIdentity.authentication.publicKey); executeRestfulFunction("public", viamApi, viamApi.identityRestoreAccess, identificator).then(executeResult => { result(executeResult); }); }); }, getCurrentlyLoggedInUUID() { return new Penpal.Promise(result => { const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); if (authenticationPublicKey === null) { return {"data" : "", "code" : "400", "status" : "Identity not loaded" } } if (window.loadedIdentities[authenticationPublicKey] === null) { return {"data" : "", "code" : "400", "status" : "Identity not loaded" } } var success = extendPinCodeTtl(authenticationPublicKey); if(success === false) { result({"data" : "", "code" : "400", "status" : "Identity not authenticated" }) } if(localStorage.getItem("uuid") === null) { result({"data" : "", "code" : "400", "status" : "Not logged in UUID" }) } result({"data" : localStorage.getItem("uuid"), "code" : "200", "status" : "UUID loaded" }) }); }, getCertificateByPassport(passportUUID) { return new Penpal.Promise(result => { const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); if (authenticationPublicKey === null) { return {"data" : "", "code" : "400", "status" : "Identity not loaded" } } if (window.loadedIdentities[authenticationPublicKey] === null) { return {"data" : "", "code" : "400", "status" : "Identity not loaded" } } var success = extendPinCodeTtl(authenticationPublicKey); if(success === false) { result({"data" : "", "code" : "400", "status" : "Identity not authenticated" }) } getCertificateForPassport(passportUUID, false).then(certificateResult => { result(certificateResult) }) }); }, getOneTimeCertificateByPassport(passportUUID, emailArg) { return new Penpal.Promise(result => { const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); if (authenticationPublicKey === null) { return {"data" : "", "code" : "400", "status" : "Identity not loaded" } } if (window.loadedIdentities[authenticationPublicKey] === null) { return {"data" : "", "code" : "400", "status" : "Identity not loaded" } } var success = extendPinCodeTtl(authenticationPublicKey); if(success === false) { result({"data" : "", "code" : "400", "status" : "Identity not authenticated" }) } getCertificateForPassport(passportUUID, true).then(certificateResult => { if(certificateResult.code === "200") { var passportCertificate = certificateResult.data["x509Certificate"]; var passportPrivateKey = certificateResult.data["privateKey"]; var passportChain = certificateResult.data["chain"]; createOneTimePassportCertificate(makeid() + "-" + passportUUID, emailArg, passportPrivateKey, passportCertificate).then(function(keys){ var publicKeyOneTime = keys["publicKeyPEM"]; var privateKeyOneTime = keys["privateKeyPEM"]; var certificateOneTime = keys["certificatePEM"]; passportChain.push(passportCertificate); var oneTimeCryptoData = new CryptoData(); oneTimeCryptoData.setx509Certificate(certificateOneTime); oneTimeCryptoData.setPrivateKey(privateKeyOneTime); oneTimeCryptoData.setPublicKey(publicKeyOneTime); oneTimeCryptoData.setChain(passportChain); result({"data" : oneTimeCryptoData, "code" : "200", "status" : "One time certificate generated" }) // Prints PEM formatted signed certificate // -----BEGIN CERTIFICATE-----MIID....7Hyg==-----END CERTIFICATE----- }); } else { result({"data" : "", "code" : "400", "status" : "Can not generate one time certificate" }) } }) }); }, signEmail(passportUUID, emailArg, emailMessage) { return new Penpal.Promise(result => { const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); if (authenticationPublicKey === null) { return {"data" : "", "code" : "400", "status" : "Identity not authenticated" } } if (window.loadedIdentities[authenticationPublicKey] === null) { return {"data" : "", "code" : "400", "status" : "Identity not authenticated" } } var success = extendPinCodeTtl(authenticationPublicKey); if(success === false) { result({"data" : "", "code" : "400", "status" : "Identity not authenticated" }) } getCertificateForPassport(passportUUID, true).then(certificateResult => { if(certificateResult.code === "200") { var passportCertificate = certificateResult.data["x509Certificate"]; var passportPrivateKey = certificateResult.data["privateKey"]; var passportChain = certificateResult.data["chain"]; createOneTimePassportCertificate(makeid() + "-" + passportUUID, emailArg, passportPrivateKey, passportCertificate).then(function(keys){ var publicKeyOneTime = keys["publicKeyPEM"]; var privateKeyOneTime = keys["privateKeyPEM"]; var certificateOneTime = keys["certificatePEM"]; //download("certificateOneTime.crt", "text/plain", certificateOneTime) passportChain.push(passportCertificate); executeRestfulFunction("private", viamApi, viamApi.passportGetEmailWithHeaderByPassport, passportUUID, emailMessage).then(executeResult2 => { var emailWithHeader = executeResult2.data; //download("withheader.eml", "message/rfc822", emailWithHeader) var signedEmailPromise = signEmail(emailWithHeader, certificateOneTime, passportChain, privateKeyOneTime); signedEmailPromise.then(signedEmail => { executeRestfulFunction("private", viamApi, viamApi.signResignEmail, passportUUID, signedEmail).then(executeResult => { result({"data" : executeResult.data, "code" : "200", "status" : "Email signed" }) }); /*result({"data" : signedEmail, "code" : "200", "status" : "Email signed" })*/ }); }); // Prints PEM formatted signed certificate // -----BEGIN CERTIFICATE-----MIID....7Hyg==-----END CERTIFICATE----- }); } else { result({"data" : "", "code" : "400", "status" : "Can not sign email" }) } }) }); }, hasSession() { return new Penpal.Promise(result => { const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); if (authenticationPublicKey === null) { result({"data" : "", "code" : "400", "status" : "Identity not authenticated" }); } if (window.loadedIdentities[authenticationPublicKey] === null) { result({"data" : "", "code" : "400", "status" : "Identity not authenticated" }); } var success = extendPinCodeTtl(authenticationPublicKey); if(success === false) { result({"data" : "", "code" : "400", "status" : "Identity not authenticated" }) } executeRestfulFunction("private", viamApi, viamApi.identityHasSession).then(executeResult => { result(executeResult); }); }); }, marketingSignUpIdentificator(identificator, reference) { return new Penpal.Promise(result => { viamApi.setIdentity("marketingapppublickey"); executeRestfulFunction("public", viamApi, viamApi.marketingSignUpIdentificator, identificator, reference).then(executeResult => { viamApi.setIdentity(""); viamApi.setSessionData("", ""); result(executeResult); }); }); }, marketingGetIdentificatorProfile(identificator, pincode) { return new Penpal.Promise(result => { viamApi.setIdentity("marketingapppublickey"); executeRestfulFunction("public", viamApi, viamApi.marketingGetIdentificatorProfile, identificator, pincode).then(executeResult => { viamApi.setIdentity(""); viamApi.setSessionData("", ""); result(executeResult); }); }); }, marketingExecuteEventForIdentificator(identificator, pincode, event) { return new Penpal.Promise(result => { viamApi.setIdentity("marketingapppublickey"); executeRestfulFunction("public", viamApi, viamApi.marketingExecuteEventForIdentificator, identificator, pincode, event).then(executeResult => { viamApi.setIdentity(""); viamApi.setSessionData("", ""); result(executeResult); }); }); }, getCurrentlyAuthenticatedIdentity() { return new Penpal.Promise(result => { result({"data" : window.currentlyAuthenticatedIdentity, "code" : "200", "status" : "Currently authenticated identity" }) }); }, stringToUtf8ByteArray(str) { if (typeof str !== 'string') { str = str.toString() } let res = Buffer.from(str,'utf-8'); return new Penpal.Promise(result => { result(res) }) }, utf8ByteArrayToString(ba) { if (!Buffer.isBuffer(ba)) { ba = Buffer.from(ba) } let res = ba.toString('utf-8'); return new Penpal.Promise(result => { result(res) }) }, stringToUtf8Base64(str) { if (!Buffer.isBuffer(str)) { if (typeof str !== 'string') { str = str.toString() } str = Buffer.from(str, 'utf-8') } let res = str.toString('base64'); return new Penpal.Promise(result => { result(res) }) }, utf8Base64ToString(strBase64) { if (!Buffer.isBuffer(strBase64)) { if (typeof strBase64 !== 'string') { strBase64 = strBase64.toString() } strBase64 = Buffer.from(strBase64, 'base64') } let res = strBase64.toString('utf-8'); return new Penpal.Promise(result => { result(res) }) }, base64ToByteArray(strBase64) { if (typeof strBase64 !== 'string') { strBase64 = strBase64.toString() } let res = Buffer.from(strBase64, 'base64'); return new Penpal.Promise(result => { result(res) }) }, byteArrayToBase64(ba) { if (!Buffer.isBuffer(ba)) { ba = Buffer.from(ba) } let res = ba.toString('base64'); return new Penpal.Promise(result => { result(res) }) }, ...penpalMethods } }); connection.promise.then(parent => { if (!navigator.cookieEnabled) { console.warn("Cookie disabled. Can't start library."); return; } window.addEventListener('storage', event => { if (event.key === "authenticatedIdentity" && event.newValue === null) { const publicKey = window.currentlyAuthenticatedIdentity.authentication.publicKey; window.currentlyLoadedIdentity = null; window.currentlyAuthenticatedIdentity = null; const event = createEvent("LogoutFromAnotherTab", "Logout", [publicKey]); parent.onEvent(event); } }); const identities = localStorage.getItem("identities"); console.log("Library loaded at: " + new Date().toISOString()); if (identities === "" || identities === null) { localStorage.setItem("identities", JSON.stringify({})); } if ( localStorage.getItem("uuid") === null || localStorage.getItem("token") === null || localStorage.getItem("authenticatedIdentity") === null ) { localStorage.removeItem("uuid"); localStorage.removeItem("token"); localStorage.removeItem("authenticatedIdentity"); } else { const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); const pinCode = getPincode(authenticationPublicKey); if (pinCode === "" || pinCode === null) { loadIdentityInternal(authenticationPublicKey, "00000000").then(result => { if (result.code !== "200") { const event = createEvent( "CanNotGetPincodeForAuthenticatedIdentity", "IdentityNotLoaded", [authenticationPublicKey] ); parent.onEvent(event); } }); } else { loadIdentityInternal(authenticationPublicKey, pinCode).then(result => { if (result.code !== "200") { const event = createEvent( "CanNotLoadIdentity", "ErrorDuringLoadingIdentity", [authenticationPublicKey] ); parent.onEvent(event); } }); } } var anynomousDeviceKeyEventsProcessing = false; var maxDeviceKeyAnonymousEventTime = 0; var eventsDeviceEventsProcessing = false; var maxDeviceKeyEventTime = 0; var eventsEntityEventsProcessing = false; var maxEntityEventTime = 0; var identityLoadedEvent = false; var identityAuthenticatedEvent = false; setInterval(function() { if(window.currentlyAuthenticatedIdentity != null) { var pinCode = getPincode(window.currentlyAuthenticatedIdentity.authentication.publicKey); if(pinCode != null && pinCode !== "") { getIdentityFromLocalStorage(window.currentlyAuthenticatedIdentity.authentication.publicKey, pinCode, false).then((gotIdentity) => { window.currentlyAuthenticatedIdentity = gotIdentity; window.currentlyLoadedIdentity = gotIdentity; if(identityAuthenticatedEvent === false && gotIdentity != null) { var event = createEvent("IdentityAuthenticated", "Authenticated", [gotIdentity.authentication.publicKey]); parent.onEvent(event); identityAuthenticatedEvent = true } }) } else { const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); if(authenticationPublicKey != null && authenticationPublicKey !== "") { loadIdentityInternal(authenticationPublicKey, "00000000").then(result => { if(result.code !== "200") { var event = createEvent("CanNotGetPincodeForAuthenticatedIdentity", "IdentityNotLoaded", [authenticationPublicKey]); parent.onEvent(event); clearPinCodeTtl(authenticationPublicKey); window.currentlyAuthenticatedIdentity = null; } }); } identityAuthenticatedEvent = false; window.currentlyLoadedIdentity = null; } } if(window.currentlyLoadedIdentity != null) { var pinCode = getPincode(window.currentlyLoadedIdentity.authentication.publicKey); if(pinCode === "" || pinCode == null) { if(identityLoadedEvent === false) { /*var event = createEvent("CanNotLoadPincodeForLoadedIdentity", "IdentityNotLoaded", [window.currentlyLoadedIdentity.authentication.publicKey]) parent.onEvent(event) identityLoadedEvent = true*/ loadIdentityInternal(window.currentlyLoadedIdentity.authentication.publicKey, "00000000").then(result => { if(result.code !== "200") { var event = createEvent("CanNotLoadPincodeForLoadedIdentity", "IdentityNotLoaded", [window.currentlyLoadedIdentity.authentication.publicKey]); parent.onEvent(event); identityLoadedEvent = true } }); } } else { identityLoadedEvent = false } } if (window.currentlyAuthenticatedIdentity != null) { var now = new Date().getTime(); if(now - window.lastTimeGetProfile > 30000) { var identityToStore = window.currentlyAuthenticatedIdentity; getProfileData(identityToStore); window.lastTimeGetProfile = now; } } }, 50); setInterval(function() { if (window.currentlyLoadedIdentity != null && anynomousDeviceKeyEventsProcessing === false) { anynomousDeviceKeyEventsProcessing = true; executeRestfulFunction("public", viamAnonymousApi, viamAnonymousApi.eventGetNewEventsWithoutSession, "devicekey").then(async executeResult => { if(executeResult.code === "200") { var eventsLen = executeResult.data.length; let changedMaxDeviceKeyAnonymousEventTime = false; for (var i = 0; i < eventsLen; i++) { var event = executeResult.data[i]; switch (event.type) { case "Authenticated" : { const uuid = event.payloads[0]; const token = event.payloads[1]; handleIdentityLogin(window.currentlyLoadedIdentity, uuid, token); const identityToStore = window.currentlyAuthenticatedIdentity; event.payloads = [{fromQRCode: true}]; setIdentityInLocalStorage(identityToStore).then(() => { getProfileData(identityToStore).then(() => { parent.onEvent(event); }); }); break; } case "QRCodeUpdated" : { var actionID = event["actionID"]; var QrCode = event["payloads"][1]; var eventCopy = JSON.parse(JSON.stringify(event)); QRCode.toDataURL(actionID + "," + QrCode, function (err, url) { eventCopy["payloads"].push(url); parent.onEvent(eventCopy) }); break } case "KeyDeleted" : { const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); clearPinCodeTtl(authenticationPublicKey); localStorage.removeItem("uuid"); localStorage.removeItem("token"); localStorage.removeItem("authenticatedIdentity"); delete window.loadedIdentities[authenticationPublicKey]; window.currentlyLoadedIdentity = null; window.currentlyAuthenticatedIdentity = null; window.lastTimeGetProfile = 0; destroyIdentityFromLocalStorage(authenticationPublicKey); break } default : { parent.onEvent(event) } } changedMaxDeviceKeyAnonymousEventTime = true; maxDeviceKeyAnonymousEventTime = Math.max(maxDeviceKeyAnonymousEventTime, event.stamp) } if(changedMaxDeviceKeyAnonymousEventTime) { executeRestfulFunction("public", viamAnonymousApi, viamAnonymousApi.eventUpdateLastViewedWithoutSession, "devicekey", maxDeviceKeyAnonymousEventTime.toString()).then(() => { anynomousDeviceKeyEventsProcessing = false; }); } else { anynomousDeviceKeyEventsProcessing = false; } } else { anynomousDeviceKeyEventsProcessing = false; } }); } if (window.currentlyAuthenticatedIdentity != null && eventsDeviceEventsProcessing === false) { eventsDeviceEventsProcessing = true; executeRestfulFunction("private", viamApi, viamApi.eventGetNewEvents, "devicekey").then(executeResult => { if(executeResult.code === "200") { var eventsLen = executeResult.data.length; const changedMaxDeviceKeyEventTime = false; for(var i = 0; i < eventsLen; i++) { var event = executeResult.data[i]; if(event.type === "QRCodeUpdated") { var actionID = event["actionID"]; var QrCode = event["payloads"][1]; var eventCopy = JSON.parse(JSON.stringify(event)); QRCode.toDataURL(actionID + "," + QrCode, function (err, url) { eventCopy["payloads"].push(url); parent.onEvent(eventCopy) }) } else { parent.onEvent(event) } maxDeviceKeyEventTime = Math.max(maxDeviceKeyEventTime, event.stamp) } if(changedMaxDeviceKeyEventTime) { executeRestfulFunction("private", viamApi, viamApi.eventUpdateLastViewed, "devicekey", maxDeviceKeyEventTime.toString()).then(executeResult1 => { eventsDeviceEventsProcessing = false }) } else { eventsDeviceEventsProcessing = false } } else { eventsDeviceEventsProcessing = false } }); } if (window.currentlyAuthenticatedIdentity != null && eventsEntityEventsProcessing === false) { eventsEntityEventsProcessing = true; executeRestfulFunction("private", viamApi, viamApi.eventGetNewEvents, "entity").then(executeResult => { if(executeResult.code === "200") { var eventsLen = executeResult.data.length; let changedMaxEntityEventTime = false; for(var i = 0; i < eventsLen; i++) { event = executeResult.data[i]; if(event.type === "QRCodeUpdated") { var actionID = event["actionID"]; var QrCode = event["payloads"][1]; var eventCopy = JSON.parse(JSON.stringify(event)); QRCode.toDataURL(actionID + "," + QrCode, function (err, url) { eventCopy["payloads"].push(url); parent.onEvent(eventCopy) }); continue } parent.onEvent(event); changedMaxEntityEventTime = true; maxEntityEventTime = Math.max(maxEntityEventTime, event.stamp) } if(changedMaxEntityEventTime) { executeRestfulFunction("private", viamApi, viamApi.eventUpdateLastViewed, "entity", maxEntityEventTime.toString()).then(executeResult1 => { eventsEntityEventsProcessing = false }) } else { eventsEntityEventsProcessing = false } } else { eventsEntityEventsProcessing = false } }); } }, 1000); });