diff --git a/javascript/src/constants/statuses.js b/javascript/src/constants/statuses.js index 64fe340b5b9cbac714070c61ae1a3bceba23af3e..856d7c47f82097c8156b9f08e235e8a50a0a19c2 100644 --- a/javascript/src/constants/statuses.js +++ b/javascript/src/constants/statuses.js @@ -1 +1,2 @@ export const STATUS_DEVICE_REVOKED = "Device revoked"; +export const STATUS_USER_NOT_ACTIVATED = 'User not activated'; diff --git a/javascript/src/iframe/viamapi-iframe.js b/javascript/src/iframe/viamapi-iframe.js index 04dd65b8cd79c14376d1d9d977cdfc5332b98fae..f82a7e7bc1b6c1896f20cc7703d4c0f16569f5a7 100644 --- a/javascript/src/iframe/viamapi-iframe.js +++ b/javascript/src/iframe/viamapi-iframe.js @@ -1,8 +1,5 @@ import '../lib/textencoder.polyfill'; -import { - parseSMIME, - prepareVCardParts -} from "../utilities/emailUtilities"; +import { parseSMIME, prepareVCardParts } from "../utilities/emailUtilities"; import { stringToUtf8ByteArray, utf8ByteArrayToString, @@ -30,15 +27,19 @@ import { createOneTimePassportCertificate, createPassportCertificate, decryptMessage, - encryptMessage, parseCertificate, + encryptMessage, + parseCertificate, 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"; -import generateQrCode from '../utilities/generateQrCode'; +import { + STATUS_DEVICE_REVOKED, + STATUS_USER_NOT_ACTIVATED +} from "../constants/statuses"; +import generateQrCode from "../utilities/generateQrCode"; const penpalMethods = require("../../temp/penpal-methods").default; const WopiAPI = require("./wopiapi-iframe"); @@ -319,6 +320,20 @@ async function executeRestfulFunction(type, that, fn, config, ...args) { return response.data; } + const userNotActivated = + type === "private" && + code === "400" && + status === STATUS_USER_NOT_ACTIVATED; + + if (userNotActivated) { + destroyIdentity(); + + const event = createEvent("", "UserNotActivated"); + iframeParent.onEvent(event); + + return response.data; + } + const badSession = type === "private" && identity && @@ -538,14 +553,9 @@ const connection = Penpal.connectToParent({ newIdentity.setPinCode(pinCode); window.currentlyLoadedIdentity = newIdentity; - const { - publicKey, - x509Certificate - } = newIdentity.authentication; + const { publicKey, x509Certificate } = newIdentity.authentication; - window.loadedIdentities[ - publicKey - ] = newIdentity; + window.loadedIdentities[publicKey] = newIdentity; extendPinCodeTtl(newIdentity.authentication.publicKey, pinCode); window.viamAnonymousApi.setIdentity( @@ -1092,7 +1102,7 @@ const connection = Penpal.connectToParent({ ); }); }, - verifySMIME: async (smimeString) => { + verifySMIME: async smimeString => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); @@ -1121,10 +1131,16 @@ const connection = Penpal.connectToParent({ const rootCaPem = rootCaResponse.data; const verificationResult = await verifySMIME(smimeString, rootCaPem); - return encodeResponse("200", verificationResult.verified, verificationResult.message); + return encodeResponse( + "200", + verificationResult.verified, + verificationResult.message + ); }, validateDocument: async (documentUUID, contentType) => { - const authenticationPublicKey = localStorage.getItem("authenticatedIdentity"); + const authenticationPublicKey = localStorage.getItem( + "authenticatedIdentity" + ); if ( !authenticationPublicKey || @@ -1140,7 +1156,8 @@ const connection = Penpal.connectToParent({ window.viamApi.documentValidateDocumentByUUID, null, documentUUID, - contentType); + contentType + ); if (validateDocumentResponse.code !== "200") { return encodeResponse("400", "", validateDocumentResponse.status); @@ -1149,11 +1166,13 @@ const connection = Penpal.connectToParent({ const signatures = validateDocumentResponse.data; if (signatures) { for (const signature of signatures) { - const certificateChain = signature.certificateChainPEM.map((certificatePEM) => { - const certificate = parseCertificate(certificatePEM); - const certificateData = new CertificateData(certificate); - return certificateData; - }); + const certificateChain = signature.certificateChainPEM.map( + certificatePEM => { + const certificate = parseCertificate(certificatePEM); + const certificateData = new CertificateData(certificate); + return certificateData; + } + ); signature.certificateChain = certificateChain; } } @@ -1352,7 +1371,11 @@ const connection = Penpal.connectToParent({ return encodeResponse("200", "", "Document signed"); }, - signDocumentJava: async (passportUUID, documentUUID, documentContentType) => { + signDocumentJava: async ( + passportUUID, + documentUUID, + documentContentType + ) => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); @@ -1445,7 +1468,14 @@ const connection = Penpal.connectToParent({ // body: String if it is a text part (Content-Type = "text/...") or Uint8Array otherwise; filled for leaf MIME nodes // parts: array of instances of the same object; filled for container MIME nodes (Content-Type = "multipart/...") // } - signVCard: async (passportUUID, senderEmail, attribs, textBody, htmlBody, parts) => { + signVCard: async ( + passportUUID, + senderEmail, + attribs, + textBody, + htmlBody, + parts + ) => { const authenticationPublicKey = localStorage.getItem( "authenticatedIdentity" ); @@ -1490,15 +1520,17 @@ const connection = Penpal.connectToParent({ } let qrCodeImageData; - let qrCodeCoordinates = {fromL: -1, - fromR: -1, - toL: -1, - toR: -1}; + let qrCodeCoordinates = { fromL: -1, fromR: -1, toL: -1, toR: -1 }; - if (vCardImageClaimValue && "state" in vCardImageClaimValue && vCardImageClaimValue.state === "disabled") { + if ( + vCardImageClaimValue && + "state" in vCardImageClaimValue && + vCardImageClaimValue.state === "disabled" + ) { vCardImageData = new ImageData({ contentType: "image/png", - contentBase64: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" //1x1px transparent pixel + contentBase64: + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" //1x1px transparent pixel }); } else { const vCardImageResponse = await executeRestfulFunction( @@ -1516,17 +1548,21 @@ const connection = Penpal.connectToParent({ } vCardImageData = new ImageData(vCardImageResponse.data.Image); if (vCardImageData.contentType !== "image/png") { - return encodeResponse("400", "", "Content type of vCard mmust be 'image/png'"); + return encodeResponse( + "400", + "", + "Content type of vCard mmust be 'image/png'" + ); } qrCodeCoordinates = vCardImageResponse.data.QRCodeCoordinates; - const qrCodeBase64Content = await generateQrCode("https://" + location.host + "/check/" + messageUUID); - qrCodeImageData = new ImageData( - { - contentType: "image/png", - content: qrCodeBase64Content - } + const qrCodeBase64Content = await generateQrCode( + "https://" + location.host + "/check/" + messageUUID ); + qrCodeImageData = new ImageData({ + contentType: "image/png", + content: qrCodeBase64Content + }); } if (typeof parts === "undefined" || parts === null) { @@ -1545,7 +1581,9 @@ const connection = Penpal.connectToParent({ }; parts.unshift(htmlPart); } else { - console.log("Html body is not passed to signVCard, its value is ", {html: htmlBody}); + console.log("Html body is not passed to signVCard, its value is ", { + html: htmlBody + }); } if (textBody) { @@ -1560,10 +1598,12 @@ const connection = Penpal.connectToParent({ }; parts.unshift(textPart); } else { - console.log("Text body is not passed to signVCard, its value is ", {text: textBody}); + console.log("Text body is not passed to signVCard, its value is ", { + text: textBody + }); } - const count = prepareVCardParts(parts); + const count = await prepareVCardParts(parts); if (count.textParts === 0) { return encodeResponse("400", "", "No text parts passed to signVCard"); } @@ -1616,17 +1656,21 @@ const connection = Penpal.connectToParent({ parts, vCardAttribs, qrCodeImageData, - qrCodeCoordinates, + qrCodeCoordinates ); if (signVCardResponse.code !== "200") { return encodeResponse("400", "", signVCardResponse.status); } const signedVCardImageData = new ImageData(signVCardResponse.data); - return encodeResponse("200", { - image: signedVCardImageData, - messageUUID - }, "vCard signed"); + return encodeResponse( + "200", + { + image: signedVCardImageData, + messageUUID: messageUUID + }, + "vCard signed" + ); }, // mime - String - the MIME of the email message // vCardAttribs - optional attributes for the verification procedure in format @@ -1660,19 +1704,25 @@ const connection = Penpal.connectToParent({ } const validationResult = validateVMimeResponse.data; - const {signatures} = validationResult; + const { signatures } = validationResult; if (signatures) { for (const signature of signatures) { - const certificateChain = signature.certificateChainPEM.map((certificatePEM) => { - const certificate = parseCertificate(certificatePEM); - const certificateData = new CertificateData(certificate); - return certificateData; - }); + const certificateChain = signature.certificateChainPEM.map( + certificatePEM => { + const certificate = parseCertificate(certificatePEM); + const certificateData = new CertificateData(certificate); + return certificateData; + } + ); signature.certificateChain = certificateChain; } } - return encodeResponse("200", validationResult, "Validation result retrieved"); + return encodeResponse( + "200", + validationResult, + "Validation result retrieved" + ); }, generateQrCode, documentCreateDocument: async (passportUUID, path, contentType, title) => { @@ -1980,7 +2030,10 @@ const connection = Penpal.connectToParent({ const resourceID = createDocumentResult.data; - const accessTokenResponse = await wopiAPI.getAccessToken(passportUUID, resourceID); + const accessTokenResponse = await wopiAPI.getAccessToken( + passportUUID, + resourceID + ); if (accessTokenResponse.data.code !== "200") { return accessTokenResponse.data; @@ -2009,7 +2062,11 @@ const connection = Penpal.connectToParent({ return encodeResponse("400", "", "Identity not authenticated"); } - const response = await wopiAPI.getAccessToken(passportUUID, resourceID, contentType); + const response = await wopiAPI.getAccessToken( + passportUUID, + resourceID, + contentType + ); return response.data; }, diff --git a/javascript/src/utilities/emailUtilities.js b/javascript/src/utilities/emailUtilities.js index 988f879940b4bd1cac8eb118367d6e8f7ff05413..1a696a1b566be6ea55bab5d3788584327c41bd77 100644 --- a/javascript/src/utilities/emailUtilities.js +++ b/javascript/src/utilities/emailUtilities.js @@ -17,8 +17,10 @@ import { } from "./signingUtilities"; import { byteArrayToBase64, - stringToUtf8Base64 + stringToUtf8Base64, + stringToUtf8ByteArray } from "./stringUtilities"; +import {getCrypto} from "pkijs"; export const SIGNATURE_CONTENT_TYPE = "application/pkcs7-signature"; export const DEFAULT_ATTACHMENT_NAME = "attachment"; @@ -150,14 +152,52 @@ const capitalizeFirstLetter = (s) => { return s.charAt(0).toUpperCase() + s.slice(1); }; -export function prepareVCardParts(parts) { - if (!parts) { - return; +async function sha256(array) { + const cryptoLib = getCrypto(); + const digestTmpBuf = await cryptoLib.digest({ name: "SHA-256" }, array); + const digestTmpArray = new Uint8Array(digestTmpBuf); + return digestTmpArray; +} + +async function hashBody(part) { + const contentType = part.headers["Content-Type"]; + const origContentType = part.headers["Original-Content-Type"]; + + if (!origContentType && + !part.headers["Content-Type"].startsWith("application/hash") && + !part.headers["Content-Type"].startsWith("text/plain") && + !part.headers["Content-Type"].startsWith("text/html")) { + if (part.body) { + if (typeof part.body === "string") { + part.body = stringToUtf8ByteArray(part.body); + } + if (part.body instanceof ArrayBuffer) { + part.body = byteArrayToBase64(new Uint8Array(part.body)); + } + if (!(part.body instanceof Uint8Array)) { + throw new Error('part body is neither string, nor Uint8Array, nor ArrayBuffer'); // should not happen + } + + if (contentType) { + part.headers["Original-Content-Type"] = contentType; + } + part.headers["Content-Type"] = "application/hash; algorithm=SHA-256"; + + part.body = await sha256(part.body); + } } +} + +export async function prepareVCardParts(parts) { const count = { textParts: 0, htmlParts: 0 }; + + if (!parts) { + return count; + } + for (const part of parts) { if (!part.headers) { part.headers = { @@ -169,17 +209,18 @@ export function prepareVCardParts(parts) { capitalizedHeaders[capitalizeHeaderName(key)] = part.headers[key]; } part.headers = capitalizedHeaders; - try { + if (!part.headers["Content-Type"]) { + part.headers["Content-Type"] = "application/octet-stream"; + } else { if (part.headers["Content-Type"].startsWith("text/plain")) { count.textParts++; } else if (part.headers["Content-Type"].startsWith("text/html")) { count.htmlParts++; } - } catch (ignore) { - //ignore } } if (part.body) { + await hashBody(part); if (typeof part.body === "string") { part.body = stringToUtf8Base64(part.body); } else @@ -193,7 +234,7 @@ export function prepareVCardParts(parts) { } } if (part.parts) { - const subcount = prepareVCardParts(part.parts); + const subcount = await prepareVCardParts(part.parts); count.textParts += subcount.textParts; count.htmlParts += subcount.htmlParts; }