import { parseSMIME } from "../utilities/emailUtilities"; import { stringToUtf8ByteArray, utf8ByteArrayToString, stringToUtf8Base64, utf8Base64ToString, base64ToByteArray, byteArrayToBase64 } from "../utilities/stringUtilities"; const QRCode = require("qrcode"); const Penpal = require("penpal").default; import { createDeviceHash, destroyIdentityFromLocalStorage, encodeResponse, listIdentitiesFromLocalStorage, makeid } from "../utilities/appUtility"; import { LOGIN_MODES } from "../constants/authentication"; import { createOneTimePassportCertificate, createPassportCertificate, decryptMessage, encryptMessage, signEmail, verifySMIME } from "../utilities/signingUtilities"; import { signPdf } from "../utilities/pdfUtilities"; import CryptoData from "../CryptoData"; import Identity from "../Identity"; import { STATUS_DEVICE_REVOKED } from "../constants/statuses"; const penpalMethods = require("../../temp/penpal-methods").default; const WopiAPI = require("./wopiapi-iframe"); const CollaboraAPI = require("./collaboraapi-iframe"); const ViamAPI = require("../../temp/viamapi"); let identityColors = ["#994392", "#cb0767", "#e51d31", "#ec671b", "#fab610"]; function getNextColor() { let colorIndex = localStorage.getItem("colorIndex"); if (colorIndex == null || colorIndex === "") { colorIndex = 0; } let color = identityColors[colorIndex]; colorIndex++; colorIndex = colorIndex % identityColors.length; localStorage.setItem("colorIndex", colorIndex); return color; } function setKeyForUUID(uuid, key) { let storedIdentityForUuid = localStorage.getItem("keyperuuid/" + uuid); if ( storedIdentityForUuid !== key && storedIdentityForUuid != null && storedIdentityForUuid !== "" ) { destroyIdentityFromLocalStorage(storedIdentityForUuid); } localStorage.setItem("keyperuuid/" + uuid, key); } function getColorForIdentity(key) { let storedColor = localStorage.getItem("colors/" + key); if (storedColor == null || storedColor === "") { storedColor = getNextColor(); localStorage.setItem("colors/" + key, storedColor); } return storedColor; } function setIdentityInLocalStorage(identityToStore, extendKey = true) { let 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 => { let 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, null ).then(executeResult => { if (executeResult.code === "200") { let 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({}); } }); }); } async function getIdentityFromLocalStorage(key, pinCode, extendTtl = true) { const encryptedIdentity = localStorage.getItem(key); if (!encryptedIdentity) { console.log("No such identity for public key"); return null; } const serializedIdentity = await decryptMessage(encryptedIdentity, pinCode); const identity = new Identity(serializedIdentity); if (extendTtl) { const success = extendPinCodeTtl(key, pinCode); if (!success) { console.log("Can not extend pincode ttl"); return null; } } return identity; } function extendPinCodeTtl(key, pinCode) { if (pinCode == null || pinCode === "") { let now = new Date(); let nowMillis = now.getTime(); let ttl = window.sessionStorage.getItem("pincodettls/" + key); if (ttl == null || ttl === "" || nowMillis >= parseInt(ttl)) { clearPinCodeTtl(key); return false; } else { let ttl = now.getTime() + 4 * 60 * 60 * 1000; window.sessionStorage.setItem("pincodettls/" + key, ttl); } } else { let now = new Date(); let 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) { let now = new Date(); let nowMillis = now.getTime(); let 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 }; } const destroyAuthentication = () => { const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); window.viamApi.setIdentity(""); window.viamApi.setSessionData("", ""); clearPinCodeTtl(authenticationPublicKey); localStorage.removeItem("uuid"); localStorage.removeItem("token"); localStorage.removeItem("authenticatedIdentity"); window.currentlyAuthenticatedIdentity = null; window.lastTimeGetProfile = 0; }; const destroyIdentity = () => { destroyAuthentication(); if (window.currentlyLoadedIdentity) { const { publicKey } = window.currentlyLoadedIdentity.authentication; delete window.loadedIdentities[publicKey]; window.currentlyLoadedIdentity = null; destroyIdentityFromLocalStorage(publicKey); } }; window.loadedIdentities = {}; window.wopiAPI = new WopiAPI(); window.collaboraApi = new CollaboraAPI(); window.viamApi = new ViamAPI(); window.viamAnonymousApi = new ViamAPI(); window.currentlyAuthenticatedIdentity = null; window.currentlyLoadedIdentity = null; window.lastTimeGetProfile = 0; let iframeParent = null; 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); }; async function executeRestfulFunction(type, that, fn, config, ...args) { const { currentlyAuthenticatedIdentity, viamApi, currentlyLoadedIdentity } = window; let response; try { response = await fn.apply(that, [config, ...args]); } catch (error) { if (error.response) { //Resposnse with status code != 2xx still has valid response response = error.response; } else { //Connection error or similar const data = { data: "", code: "999", status: error.message }; return data; } } const identity = currentlyAuthenticatedIdentity || currentlyLoadedIdentity; const { code, status } = response.data; const deviceRevoked = type === "private" && code === "401" && status === STATUS_DEVICE_REVOKED; if (deviceRevoked) { destroyIdentity(); const event = createEvent("", "DeviceRevoked"); iframeParent.onEvent(event); return response.data; } const badSession = type === "private" && identity && code === "400" && status === "Bad session"; if (!badSession) return response.data; const loginResponse = await viamApi.identityLogin( null, "previousaddeddevice" ); if (loginResponse.data.code !== "200") return loginResponse.data; const uuid = loginResponse.data.data["Uuid"]; const token = loginResponse.data.data["Session"]; handleIdentityLogin(identity, uuid, token); try { response = await fn.apply(that, [config, ...args]); } catch (error) { response = error.response; } return 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"); window.loadedIdentities[identityKey] = loadedIdentity; window.currentlyLoadedIdentity = loadedIdentity; if (identityKey === localStorage.getItem("authenticatedIdentity")) { window.currentlyAuthenticatedIdentity = loadedIdentity; 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.viamAnonymousApi.setIdentity( window.currentlyLoadedIdentity.authentication.publicKey ); const { publicKey, x509Certificate } = loadedIdentity.authentication; result({ data: { authentication: { publicKey, x509Certificate } }, code: "200", status: "Identity loaded" }); }) .catch(e => { result({ data: "", code: "400", status: "" + e }); }); }); } function getCertificateForPassport(passportUUID, internal) { return new Penpal.Promise(certificateResult => { if (window.currentlyAuthenticatedIdentity === null) { return { data: "", code: "400", status: "Identity not authenticated" }; } const passportIdentity = window.currentlyAuthenticatedIdentity; let passport = passportIdentity.getPassport(passportUUID); if (passport === undefined || passport === null) { createPassportCertificate(passportUUID).then(function(keys) { let cryptoData = new CryptoData(); cryptoData.setPublicKey(keys["publicKeyPEM"]); cryptoData.setPrivateKey(keys["privateKeyPEM"]); let certificate = keys["certificatePEM"]; //download("passportCertificateBeforeSigning.crt", "text/plain", certificate) //cryptoData.setx509Certificate(keys["certificate"]) executeRestfulFunction( "private", viamApi, viamApi.signSignCertificate, null, btoa(certificate), passportUUID ).then(executeResult => { if (executeResult.code === "200") { let signedCertificate = atob( executeResult.data["SignedCertificate"] ); //download("passportCertificateAfterSigning.crt", "text/plain", signedCertificate) let keyUUID = executeResult.data["CertificateUUID"]; let encodedChain = executeResult.data["Chain"]; //download("rootCertificate.crt", "text/plain", atob(encodedChain[0])) let chain = []; for (let 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 { let 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, collaboraUrl) => { if (!apiUrl) { apiUrl = `${window.location.origin}/api/`; console.warn(`API host URL not specified. Fall back to ${apiUrl}`); // eslint-disable-line no-console } if (!wopiUrl) { wopiUrl = `${window.location.origin}/wopi/`; console.warn(`WOPI host URL not specified. Fall back to ${wopiUrl}`); // eslint-disable-line no-console } if (!collaboraUrl) { collaboraUrl = window.location.origin; console.warn( `Collabora host URL not specified. Fall back to ${collaboraUrl}` ); // eslint-disable-line no-console } window.API_HOST = apiUrl.charAt(apiUrl.length - 1) === "/" ? apiUrl : apiUrl + "/"; window.WOPI_URL = wopiUrl.charAt(wopiUrl.length - 1) === "/" ? wopiUrl : wopiUrl + "/"; window.COLLABORA_URL = collaboraUrl.charAt(collaboraUrl.length - 1) === "/" ? collaboraUrl : collaboraUrl + "/"; }, ...penpalMethods, createIdentity(pinCode) { return new Penpal.Promise(result => { createPassportCertificate(makeid()).then(function(keys) { let newIdentity = new Identity(); let 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 => { let identities = listIdentitiesFromLocalStorage(); result({ data: identities, code: "200", status: "Identities listed" }); }); }, loadIdentity(identityKey, pinCode) { return loadIdentityInternal(identityKey, pinCode); }, checkIdentityPinCode: async (key, pinCode) => { try { const identity = await getIdentityFromLocalStorage(key, pinCode, false); if (identity) { return encodeResponse("200", null, "Pincode check successful"); } else { return encodeResponse("400", null, "Pincode check failed"); } } catch (e) { return encodeResponse("400", e, "Pincode check error"); } }, changeIdentityPinCode: async (key, oldPinCode, newPinCode) => { try { const identity = await getIdentityFromLocalStorage( key, oldPinCode, false ); if (identity) { identity.pinCode = newPinCode; await setIdentityInLocalStorage(identity); window.currentlyAuthenticatedIdentity = identity; window.currentlyLoadedIdentity = identity; return encodeResponse("200", null, "Successfully changed pincode"); } else { return encodeResponse("400", null, "Identity not found"); } } catch (e) { return encodeResponse("400", e.message, "Change pincode error"); } }, 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: async () => { destroyAuthentication(); const identitiesTemp = listIdentitiesFromLocalStorage(); for (const i in identitiesTemp) { destroyIdentityFromLocalStorage(i); } return encodeResponse("200", "", "Identities cleared"); }, confirmIdentificator(identity, confirmationCodeArg) { return new Penpal.Promise(result => { viamApi.setIdentity(identity.authentication.publicKey); executeRestfulFunction( "public", viamApi, viamApi.identityConfirmIdentificator, null, confirmationCodeArg ).then(executeResult => { result(executeResult); }); }); }, identityGetIdentificatorByRegisterToken(identity, tokenArg) { return new Penpal.Promise(result => { viamApi.setIdentity(identity.authentication.publicKey); executeRestfulFunction( "public", viamApi, viamApi.identityGetIdentificatorByRegisterToken, null, tokenArg ).then(executeResult => { result(executeResult); }); }); }, submitIdentificator(identity, identificatorArg, registerToken) { return new Penpal.Promise(result => { viamApi.setIdentity(identity.authentication.publicKey); executeRestfulFunction( "public", viamApi, viamApi.identitySubmitIdentificator, null, 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, null, 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, null ).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, null, 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.setSessionData("", ""); window.viamApi.setDeviceHash(deviceHash); window.viamApi.setIdentity(loginIdentity.authentication.publicKey); const identityLoginResponse = await executeRestfulFunction( "public", window.viamApi, window.viamApi.identityLogin, null, 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" }); } let success = extendPinCodeTtl(authenticationPublicKey); if (success === false) { result({ data: "", code: "400", status: "Identity not authenticated" }); } executeRestfulFunction( "private", viamApi, viamApi.identityAddNewDevice, null ).then(executeResult => { if (executeResult.code === "200") { let actionID = executeResult.data["ActionID"]; let 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" }); } let success = extendPinCodeTtl(authenticationPublicKey); if (success === false) { result({ data: "", code: "400", status: "Identity not authenticated" }); } executeRestfulFunction( "private", viamApi, viamApi.identityDestroyKeysForDevice, null, btoa(authenticationPublicKeyArg) ).then(executeResult => { result(executeResult); }); }); }, logout: async () => { try { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); if ( !authenticationPublicKey || !window.loadedIdentities[authenticationPublicKey] ) { return { data: "", code: "400", status: "Identity not loaded" }; } // Clone headers to be able destroy authentication first. // We need it because clients should be able reload page right after logout invocation and not wait until request completed const headers = { ...window.viamApi.getConfig().headers }; destroyAuthentication(); return executeRestfulFunction( "private", window.viamApi, window.viamApi.identityLogout, { headers } ); } catch (e) { return { data: "", code: "400", status: e.message }; } }, identityRestoreAccess(restoreAccessIdentity, identificator) { return new Penpal.Promise(result => { viamApi.setSessionData("", ""); viamApi.setIdentity(restoreAccessIdentity.authentication.publicKey); executeRestfulFunction( "public", viamApi, viamApi.identityRestoreAccess, null, identificator ).then(executeResult => { result(executeResult); }); }); }, parseSMIME, 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" }; } let 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" }; } let 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" }; } let success = extendPinCodeTtl(authenticationPublicKey); if (success === false) { result({ data: "", code: "400", status: "Identity not authenticated" }); } getCertificateForPassport(passportUUID, true).then( certificateResult => { if (certificateResult.code === "200") { let passportCertificate = certificateResult.data["x509Certificate"]; let passportPrivateKey = certificateResult.data["privateKey"]; let passportChain = certificateResult.data["chain"]; createOneTimePassportCertificate( makeid() + "-" + passportUUID, emailArg, passportPrivateKey, passportCertificate ).then(function(keys) { let publicKeyOneTime = keys["publicKeyPEM"]; let privateKeyOneTime = keys["privateKeyPEM"]; let certificateOneTime = keys["certificatePEM"]; passportChain.push(passportCertificate); let 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" }); } } ); }); }, verifySMIME: async (smimeString) => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); if ( !authenticationPublicKey || !window.loadedIdentities[authenticationPublicKey] || !extendPinCodeTtl(authenticationPublicKey) ) { return encodeResponse("400", "", "Identity not authenticated"); } //TODO cache (for some time) the root certificate // either as PEM or as certificate object (preferred) const rootCaResponse = await executeRestfulFunction( "private", window.viamApi, window.viamApi.signRetrieveRootCertificate, null ); if (rootCaResponse.code !== "200") { return encodeResponse("400", "", rootCaResponse.status); } const rootCaPem = rootCaResponse.data; const verificationResult = await verifySMIME(smimeString, rootCaPem); return encodeResponse("200", verificationResult.verified, verificationResult.message); }, signEmail: async (passportUUID, emailArg, emailMessage) => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); if ( !authenticationPublicKey || !window.loadedIdentities[authenticationPublicKey] || !extendPinCodeTtl(authenticationPublicKey) ) { return encodeResponse("400", "", "Identity not authenticated"); } let response = await getCertificateForPassport(passportUUID, true); if (response.code !== "200") { return encodeResponse("400", "", response.status); } const { x509Certificate: passportCertificate, privateKey: passportPrivateKey, chain: passportChain } = response.data; const keys = await createOneTimePassportCertificate( makeid() + "-" + passportUUID, emailArg, passportPrivateKey, passportCertificate ); const { privateKeyPEM: privateKeyOneTime, certificatePEM: certificateOneTime } = keys; passportChain.push(passportCertificate); response = await executeRestfulFunction( "private", window.viamApi, window.viamApi.passportGetEmailWithHeaderByPassport, null, passportUUID, emailMessage ); if (response.code !== "200") { return encodeResponse("400", "", response.status); } const signedEmail = await signEmail( response.data, certificateOneTime, passportChain, privateKeyOneTime ); response = await executeRestfulFunction( "private", window.viamApi, window.viamApi.signResignEmail, null, passportUUID, signedEmail ); if (response.code !== "200") { return encodeResponse("400", "", response.status); } return encodeResponse("200", response.data, "Email signed"); }, signDocument: async (passportUUID, documentUUID, documentContentType) => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); if ( !authenticationPublicKey || !window.loadedIdentities[authenticationPublicKey] || !extendPinCodeTtl(authenticationPublicKey) ) { return encodeResponse("400", "", "Identity not authenticated"); } const certResponse = await getCertificateForPassport(passportUUID, true); if (certResponse.code !== "200") { return encodeResponse("400", "", certResponse.status); } const { x509Certificate: passportCertificate, privateKey: passportPrivateKey, chain: passportChain } = certResponse.data; const keys = await createOneTimePassportCertificate( makeid() + "-" + passportUUID, null, passportPrivateKey, passportCertificate ); const { privateKeyPEM: privateKeyOneTime, certificatePEM: certificateOneTime } = keys; passportChain.reverse(); passportChain.push(passportCertificate); passportChain.reverse(); const pdfContentType = "application/pdf"; if (documentContentType !== pdfContentType) { const convResponse = await executeRestfulFunction( "private", window.viamApi, window.viamApi.documentConvertDocumentByUUID, null, documentUUID, documentContentType, pdfContentType ); if (convResponse.code !== "200") { return encodeResponse("400", "", convResponse.status); } } const downloadResponse = await executeRestfulFunction( "private", window.viamApi, window.viamApi.documentGetDocumentByUUID, null, documentUUID, pdfContentType ); if (downloadResponse.code !== "200") { return encodeResponse("400", "", downloadResponse.status); } const pdfRaw = base64ToByteArray(downloadResponse.data); let signedPdf; try { signedPdf = await signPdf( pdfRaw, certificateOneTime, passportChain, privateKeyOneTime ); } catch (err) { console.error(err); return encodeResponse("500", "", err.message); } const signedPdfB64 = byteArrayToBase64(signedPdf); const uploadResponse = await executeRestfulFunction( "private", window.viamApi, window.viamApi.documentPutDocumentByUUID, null, documentUUID, pdfContentType, signedPdfB64 ); if (uploadResponse.code !== "200") { return encodeResponse("400", "", uploadResponse.status); } const signResponse = await executeRestfulFunction( "private", window.viamApi, window.viamApi.documentSignDocumentByUUID, null, passportUUID, documentUUID, pdfContentType ); if (signResponse.code !== "200") { return encodeResponse("400", "", signResponse.status); } return encodeResponse("200", "", "Document signed"); }, signDocumentJava: async (passportUUID, documentUUID, documentContentType) => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); if ( !authenticationPublicKey || !window.loadedIdentities[authenticationPublicKey] || !extendPinCodeTtl(authenticationPublicKey) ) { return encodeResponse("400", "", "Identity not authenticated"); } const certResponse = await getCertificateForPassport(passportUUID, true); if (certResponse.code !== "200") { return encodeResponse("400", "", certResponse.status); } const { x509Certificate: passportCertificate, privateKey: passportPrivateKey, chain: passportChain } = certResponse.data; const keys = await createOneTimePassportCertificate( makeid() + "-" + passportUUID, null, passportPrivateKey, passportCertificate ); const { privateKeyPEM: privateKeyOneTime, certificatePEM: certificateOneTime } = keys; passportChain.reverse(); passportChain.push(passportCertificate); passportChain.push(certificateOneTime); passportChain.reverse(); const pdfContentType = "application/pdf"; if (documentContentType !== pdfContentType) { const convResponse = await executeRestfulFunction( "private", window.viamApi, window.viamApi.documentConvertDocumentByUUID, null, documentUUID, documentContentType, pdfContentType ); if (convResponse.code !== "200") { return encodeResponse("400", "", convResponse.status); } } const signResponse = await executeRestfulFunction( "private", window.viamApi, window.viamApi.documentSignDocumentJavaService, null, privateKeyOneTime, passportChain, passportUUID, documentUUID, pdfContentType ); if (signResponse.code !== "200") { return encodeResponse("400", "", signResponse.status); } return encodeResponse("200", "", "Document signed"); }, documentCreateDocument: async (passportUUID, path, contentType, title) => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); if ( !authenticationPublicKey || !window.loadedIdentities[authenticationPublicKey] || !extendPinCodeTtl(authenticationPublicKey) ) { return encodeResponse("400", "", "Identity not authenticated"); } path = encodeURI(path); contentType = encodeURI(contentType); title = encodeURI(title); const config = { headers: { path, passportuuid: passportUUID, contentType, title } }; const response = await executeRestfulFunction( "private", window.viamApi, window.viamApi.documentCreateDocument, config ); if (response.code !== "200") { return encodeResponse("400", "", response.status); } return encodeResponse("200", response.data, "Document created"); }, documentPutDocument: async ( passportUUID, resourceid, contentType, file, upload ) => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); if ( !authenticationPublicKey || !window.loadedIdentities[authenticationPublicKey] || !extendPinCodeTtl(authenticationPublicKey) ) { return encodeResponse("400", "", "Identity not authenticated"); } resourceid = encodeURI(resourceid); contentType = encodeURI(contentType); const config = { headers: { "Content-Type": "multipart/form-data", passportuuid: passportUUID, resourceid, contentType, upload } }; const response = await executeRestfulFunction( "private", window.viamApi, window.viamApi.documentPutDocument, config, file ); if (response.code !== "200") { return encodeResponse("400", "", response.status); } return encodeResponse("200", response.data, "Document stored"); }, 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" }); } let success = extendPinCodeTtl(authenticationPublicKey); if (success === false) { result({ data: "", code: "400", status: "Identity not authenticated" }); } executeRestfulFunction( "private", viamApi, viamApi.identityHasSession, null ).then(executeResult => { result(executeResult); }); }); }, marketingSignUpIdentificator(identificator, reference) { return new Penpal.Promise(result => { viamApi.setIdentity("marketingapppublickey"); executeRestfulFunction( "public", viamApi, viamApi.marketingSignUpIdentificator, null, 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, null, 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, null, identificator, pincode, event ).then(executeResult => { viamApi.setIdentity(""); viamApi.setSessionData("", ""); result(executeResult); }); }); }, getCurrentlyAuthenticatedIdentity() { const { publicKey, x509Certificate } = window.currentlyAuthenticatedIdentity.authentication; return encodeResponse( "200", { authentication: { publicKey, x509Certificate } }, "Currently authenticated identity" ); }, stringToUtf8ByteArray(str) { return new Penpal.Promise(result => { result(stringToUtf8ByteArray(str)); }); }, utf8ByteArrayToString(ba) { return new Penpal.Promise(result => { result(utf8ByteArrayToString(ba)); }); }, stringToUtf8Base64(str) { return new Penpal.Promise(result => { result(stringToUtf8Base64(str)); }); }, utf8Base64ToString(strBase64) { return new Penpal.Promise(result => { result(utf8Base64ToString(strBase64)); }); }, base64ToByteArray(strBase64) { return new Penpal.Promise(result => { result(base64ToByteArray(strBase64)); }); }, byteArrayToBase64(ba) { return new Penpal.Promise(result => { result(byteArrayToBase64(ba)); }); }, // Collabora APIs collaboraDiscovery() { return collaboraApi.discovery().then(apps => apps); }, // WOPI getPassportsNewProtocol: async (resourceID, contentType) => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); if ( !authenticationPublicKey || !window.loadedIdentities[authenticationPublicKey] || !extendPinCodeTtl(authenticationPublicKey) ) { return encodeResponse("400", "", "Identity not authenticated"); } const response = await wopiAPI.getPassportsNewProtocol( resourceID, contentType ); return response.data; }, getPassports: async fileId => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); if ( !authenticationPublicKey || !window.loadedIdentities[authenticationPublicKey] || !extendPinCodeTtl(authenticationPublicKey) ) { return encodeResponse("400", "", "Identity not authenticated"); } const response = await wopiAPI.getPassports(fileId); return response.data; }, wopiCreateDocument: async (passportUUID, path, title) => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); if ( !authenticationPublicKey || !window.loadedIdentities[authenticationPublicKey] || !extendPinCodeTtl(authenticationPublicKey) ) { return encodeResponse("400", "", "Identity not authenticated"); } const config = { headers: { path, passportuuid: passportUUID, title } }; const createDocumentResult = await executeRestfulFunction( "private", window.viamApi, window.viamApi.documentCreateDocument, config ); if (createDocumentResult.code !== "200") { return createDocumentResult; } const resourceID = createDocumentResult.data; const accessTokenResponse = await wopiAPI.getAccessToken(passportUUID, resourceID); if (accessTokenResponse.data.code !== "200") { return accessTokenResponse.data; } const accessToken = accessTokenResponse.data.data; const result = { resourceID, accessToken }; return encodeResponse("200", result, "ok"); }, wopiGetAccessToken: async (passportUUID, resourceID, contentType) => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); if ( !authenticationPublicKey || !window.loadedIdentities[authenticationPublicKey] || !extendPinCodeTtl(authenticationPublicKey) ) { return encodeResponse("400", "", "Identity not authenticated"); } const response = await wopiAPI.getAccessToken(passportUUID, resourceID, contentType); return response.data; }, wopiPutFile: async (resourceID, accessToken, file) => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); if ( !authenticationPublicKey || !window.loadedIdentities[authenticationPublicKey] || !extendPinCodeTtl(authenticationPublicKey) ) { return encodeResponse("400", "", "Identity not authenticated"); } const response = await wopiAPI.putDocument(resourceID, accessToken, file); return response.data; } } }); connection.promise.then(parent => { iframeParent = 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 ) { const event = createEvent("", "NotAuthenticated"); parent.onEvent(event); 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); } }); } } let anynomousDeviceKeyEventsProcessing = false; let maxDeviceKeyAnonymousEventTime = 0; let eventsDeviceEventsProcessing = false; let maxDeviceKeyEventTime = 0; let eventsEntityEventsProcessing = false; let maxEntityEventTime = 0; let identityLoadedEvent = false; let identityAuthenticatedEvent = false; let previousLocalStorageUUID; let previousLocalStorageToken; let previousLocalStorageIdentity; setInterval(async function() { if (window.currentlyAuthenticatedIdentity) { const { authentication } = window.currentlyAuthenticatedIdentity; const pinCode = getPincode(authentication.publicKey); if (pinCode) { const identity = await getIdentityFromLocalStorage( authentication.publicKey, pinCode, false ); window.currentlyLoadedIdentity = identity; if (!identityAuthenticatedEvent && identity) { const event = createEvent("IdentityAuthenticated", "Authenticated", [ identity.authentication.publicKey ]); parent.onEvent(event); identityAuthenticatedEvent = true; } } else { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); if (authenticationPublicKey) { const result = await loadIdentityInternal( authenticationPublicKey, "00000000" ); if (result.code !== "200") { const event = createEvent( "CanNotGetPincodeForAuthenticatedIdentity", "IdentityNotLoaded", [authenticationPublicKey] ); parent.onEvent(event); clearPinCodeTtl(authenticationPublicKey); window.currentlyAuthenticatedIdentity = null; } } identityAuthenticatedEvent = false; window.currentlyLoadedIdentity = null; } } if (window.currentlyLoadedIdentity) { const pinCode = getPincode( window.currentlyLoadedIdentity.authentication.publicKey ); if (!pinCode) { if (!identityLoadedEvent) { const result = await loadIdentityInternal( window.currentlyLoadedIdentity.authentication.publicKey, "00000000" ); if (window.currentlyLoadedIdentity && result.code !== "200") { const event = createEvent( "CanNotLoadPincodeForLoadedIdentity", "IdentityNotLoaded", [window.currentlyLoadedIdentity.authentication.publicKey] ); parent.onEvent(event); identityLoadedEvent = true; } } } else { identityLoadedEvent = false; } } if (window.currentlyAuthenticatedIdentity) { const now = new Date().getTime(); if (now - window.lastTimeGetProfile > 30000) { getProfileData(window.currentlyAuthenticatedIdentity); window.lastTimeGetProfile = now; } } const currentLocalStorageUUID = localStorage.getItem("uuid"); const currentLocalStorageToken = localStorage.getItem("token"); const currentLocalStorageIdentity = localStorage.getItem( "authenticatedIdentity" ); if ( (!currentLocalStorageUUID && previousLocalStorageUUID) || (!currentLocalStorageToken && previousLocalStorageToken) || (!currentLocalStorageIdentity && previousLocalStorageIdentity) ) { previousLocalStorageUUID = null; previousLocalStorageToken = null; previousLocalStorageIdentity = null; destroyAuthentication(); const event = createEvent("", "LogoutExternal"); parent.onEvent(event); } else { previousLocalStorageUUID = currentLocalStorageUUID; previousLocalStorageToken = currentLocalStorageToken; previousLocalStorageIdentity = currentLocalStorageIdentity; } }, 50); const getNewEventsWithoutSession = async () => { anynomousDeviceKeyEventsProcessing = true; try { const executeResult = await executeRestfulFunction( "public", viamAnonymousApi, viamAnonymousApi.eventGetNewEventsWithoutSession, null, "devicekey" ); if (executeResult.code === "200") { const eventsLen = executeResult.data.length; let changedMaxDeviceKeyAnonymousEventTime = false; for (let i = 0; i < eventsLen; i++) { const event = executeResult.data[i]; switch (event.type) { case "DeviceConfirmed": { await setIdentityInLocalStorage(window.currentlyLoadedIdentity); parent.onEvent(event); break; } case "QRCodeUpdated": { const actionID = event["actionID"]; const QrCode = event["payloads"][1]; const 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) { await executeRestfulFunction( "public", viamAnonymousApi, viamAnonymousApi.eventUpdateLastViewedWithoutSession, null, "devicekey", maxDeviceKeyAnonymousEventTime.toString() ); } } } catch (e) { console.warn(e); } anynomousDeviceKeyEventsProcessing = false; }; const getNewDeviceEvents = async () => { eventsDeviceEventsProcessing = true; try { const executeResult = await executeRestfulFunction( "private", viamApi, viamApi.eventGetNewEvents, null, "devicekey" ); if (executeResult.code === "200") { const eventsLen = executeResult.data.length; const changedMaxDeviceKeyEventTime = false; for (let i = 0; i < eventsLen; i++) { const event = executeResult.data[i]; if (event.type === "QRCodeUpdated") { const actionID = event["actionID"]; const QrCode = event["payloads"][1]; const 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) { await executeRestfulFunction( "private", viamApi, viamApi.eventUpdateLastViewed, null, "devicekey", maxDeviceKeyEventTime.toString() ); } } } catch (e) { console.warn(e); } eventsDeviceEventsProcessing = false; }; const getNewEntityEvents = async () => { eventsEntityEventsProcessing = true; try { const executeResult = await executeRestfulFunction( "private", viamApi, viamApi.eventGetNewEvents, null, "entity" ); if (executeResult.code === "200") { const eventsLen = executeResult.data.length; let changedMaxEntityEventTime = false; for (let i = 0; i < eventsLen; i++) { const event = executeResult.data[i]; if (event.type === "QRCodeUpdated") { const actionID = event["actionID"]; const QrCode = event["payloads"][1]; const 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) { await executeRestfulFunction( "private", viamApi, viamApi.eventUpdateLastViewed, null, "entity", maxEntityEventTime.toString() ); } } } catch (e) { console.warn(e); } eventsEntityEventsProcessing = false; }; setInterval(() => { if ( window.currentlyLoadedIdentity && !anynomousDeviceKeyEventsProcessing && !window.currentlyAuthenticatedIdentity ) { getNewEventsWithoutSession(); } if (window.currentlyAuthenticatedIdentity) { // These functions has to be executed at the same time. !eventsDeviceEventsProcessing && getNewDeviceEvents(); !eventsEntityEventsProcessing && getNewEntityEvents(); } }, 1000); });