diff --git a/javascript/src/iframe/viamapi-iframe.js b/javascript/src/iframe/viamapi-iframe.js index 58b6e00e6147e860f181b4b3e54bb38b45c8ba66..be23ef7306eec8c2d37f2ad0b6ad658b7cec2a18 100644 --- a/javascript/src/iframe/viamapi-iframe.js +++ b/javascript/src/iframe/viamapi-iframe.js @@ -1,4 +1,7 @@ -import { parseSMIME } from "../utilities/emailUtilities"; +import { + parseSMIME, + prepareVCardParts +} from "../utilities/emailUtilities"; import { stringToUtf8ByteArray, utf8ByteArrayToString, @@ -1406,17 +1409,17 @@ const connection = Penpal.connectToParent({ return encodeResponse("200", "", "Document signed"); }, - // passportUUID - passport to sign the vCard - // text, html - the text and html part of the email - // related, attachments - array of objects, containing hashes of images/objects, related to the html in format: + // passportUUID - String - passport to sign the vCard + // text, html - String - the text and html part of the email, both optional + // parts - array of objects, representing the MIME structure in format: // { // headers: { - // "Content-Type": "application/hash; algorithm=SHA-256", - // "Original-Content-Type": "image/jpeg", //original content type - // "Content-Disposition": "inline" or "attachment", - // ... //other headers + // "Content-Type": "image/jpeg", + // "Content-Disposition": "inline" or "attachment" with additional attributes, + // ... //other headers from MIME // }, - // body: "base64 encoded hash" + // 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, text, html, parts = null) => { const authenticationPublicKey = localStorage.getItem( @@ -1431,6 +1434,13 @@ const connection = Penpal.connectToParent({ return encodeResponse("400", "", "Identity not authenticated"); } + const messageUUID = makeid(); + + const vCardAttribs = { + passportUUID: passportUUID, + messageUUID: messageUUID + }; + let vCardImageData; let vCardImageClaimValue; @@ -1454,7 +1464,8 @@ const connection = Penpal.connectToParent({ vCardImageClaimValue = vCardClaimResponse.data; } - var coordinates = {fromL: -1, fromR:-1, toL: -1, toR: -1} + let qrCodeImageData; + let qrCodeCoordinates = {fromL: -1, fromR:-1, toL: -1, toR: -1}; if (vCardImageClaimValue && "state" in vCardImageClaimValue && vCardImageClaimValue.state === "disabled") { vCardImageData = new ImageData({ @@ -1480,21 +1491,16 @@ const connection = Penpal.connectToParent({ return encodeResponse("400", "", "Content type of vCard mmust be 'image/png'"); } - coordinates = vCardImageResponse.data.QRCodeCoordinates + qrCodeCoordinates = vCardImageResponse.data.QRCodeCoordinates; + const qrCodeBase64Content = await generateQrCode("https://" + location.host + "/check/" + messageUUID); + qrCodeImageData = new ImageData( + { + contentType: "image/png", + content: qrCodeBase64Content + } + ); } - var messageUUID = makeid() - - var qrCodeBase64Content = await generateQrCode("https://" + location.host + "/check/" + messageUUID) - - var qrCode = new ImageData( - { - contentType: "image/png", - content: qrCodeBase64Content, - } - ) - - if (!parts) { parts = []; } @@ -1504,7 +1510,7 @@ const connection = Penpal.connectToParent({ headers: { "Content-Type": "text/html" }, - body: stringToUtf8Base64(html) + body: html }; parts.unshift(htmlPart); } @@ -1514,11 +1520,13 @@ const connection = Penpal.connectToParent({ headers: { "Content-Type": "text/plain" }, - body: stringToUtf8Base64(text) + body: text }; parts.unshift(textPart); } + prepareVCardParts(parts); + const certResponse = await getCertificateForPassport(passportUUID, true); if (certResponse.code !== "200") { @@ -1550,8 +1558,8 @@ const connection = Penpal.connectToParent({ passportChain.reverse(); - console.log(qrCode); - console.log(coordinates); + console.log(qrCodeImageData); + console.log(qrCodeCoordinates); const signVCardResponse = await executeRestfulFunction( "private", @@ -1562,8 +1570,9 @@ const connection = Penpal.connectToParent({ privateKeyOneTime, passportChain, parts, - qrCode, - coordinates, + vCardAttribs, + qrCodeImageData, + qrCodeCoordinates, ); if (signVCardResponse.code !== "200") { return encodeResponse("400", "", signVCardResponse.status); @@ -1572,9 +1581,43 @@ const connection = Penpal.connectToParent({ const signedVCardImageData = new ImageData(signVCardResponse.data); return encodeResponse("200", { image: signedVCardImageData, - messageUUID: messageUUID, + messageUUID: messageUUID }, "vCard signed"); }, + // mime - String - the MIME of the email message + // vCardAttribs - optional attributes for the verification procedure in format + // { + // passportUUID: passportUUID, + // messageUUID: messageUUID + // }; + validateVMime: async (vMime, vCardAttribs = null) => { + const authenticationPublicKey = localStorage.getItem( + "authenticatedIdentity" + ); + + if ( + !authenticationPublicKey || + !window.loadedIdentities[authenticationPublicKey] || + !extendPinCodeTtl(authenticationPublicKey) + ) { + return encodeResponse("400", "", "Identity not authenticated"); + } + + const validateVMimeResponse = await executeRestfulFunction( + "private", + window.viamApi, + window.viamApi.signValidateVMime, + null, + vMime, + vCardAttribs + ); + if (validateVMimeResponse.code !== "200") { + return encodeResponse("400", "", validateVMimeResponse.status); + } + + const validationResult = validateVMimeResponse.data; + return encodeResponse("200", validationResult, "Validation result retrieved"); + }, generateQrCode, documentCreateDocument: async (passportUUID, path, contentType, title) => { const authenticationPublicKey = localStorage.getItem( diff --git a/javascript/src/utilities/emailUtilities.js b/javascript/src/utilities/emailUtilities.js index 6cade6b77ca2d5dcfbf455cacd1d5cb6cb079a91..d9131fb2f095912e1ee65552d3bcaa756ea7b674 100644 --- a/javascript/src/utilities/emailUtilities.js +++ b/javascript/src/utilities/emailUtilities.js @@ -15,6 +15,10 @@ import { getCertificateChain, parseSignedData } from "./signingUtilities"; +import { + byteArrayToBase64, + stringToUtf8Base64 +} from "./stringUtilities"; export const SIGNATURE_CONTENT_TYPE = "application/pkcs7-signature"; export const DEFAULT_ATTACHMENT_NAME = "attachment"; @@ -131,3 +135,27 @@ export const extractHtmlBodyFromString = string => { .replace(/<!--[\s\S]*?-->/gm, "") .trim(); }; + +export const prepareVCardParts = parts => { + if (!parts) { + return; + } + for (const part of parts) { + if (part.body) { + if (typeof part.body === "string") { + part.body = stringToUtf8Base64(part.body); + } else + if (part.body instanceof Uint8Array) { + part.body = byteArrayToBase64(part.body); + } else + if (part.body instanceof ArrayBuffer) { + part.body = byteArrayToBase64(new Uint8Array(part.body)); + } else { + throw new Error('part body is neither string, nor Uint8Array, nor ArrayBuffer'); + } + } + if (part.parts) { + prepareVCardParts(part.parts); + } + } +}; diff --git a/javascript/src/utilities/signingUtilities.js b/javascript/src/utilities/signingUtilities.js index 00d08544476fa4765696064f42de291a01811ad2..1c94f7c2e78603e7e6cad83cb40dd7a5887b28eb 100644 --- a/javascript/src/utilities/signingUtilities.js +++ b/javascript/src/utilities/signingUtilities.js @@ -61,7 +61,9 @@ const KEY_USAGE_LeafCertificate = KEY_USAGE_KeyEncipherment | KEY_USAGE_DataEncipherment; const KEY_USAGE_CertificateAuthority = - KEY_USAGE_DigitalSignature | KEY_USAGE_KeyCertSign | KEY_USAGE_CRLSign; + 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"; @@ -416,7 +418,7 @@ function createCertificate(certData, issuerData = null) { serialNumberView = new Uint8Array(certData.serialNumber); } else if (certData.serialNumber instanceof Uint8Array) { serialNumberView = certData.serialNumber; - } else if (certData.serialNumber instanceof String) { + } else if (typeof certData.serialNumber === "string") { try { serialNumberView = new Uint8Array(hexStringToBytes(certData.serialNumber)); } catch (ignore) { @@ -612,9 +614,18 @@ function createCertificate(certData, issuerData = null) { //endregion "KeyUsage" extension //region "ExtKeyUsage" extension - if (!certData.isCA && certData.subject.email) { + if (!certData.isCA) { + const keyPurposes = []; + if (certData.subject.url) { + keyPurposes.push(OID_ID_PKIX_ServerAuth, OID_ID_PKIX_ClientAuth); + } + if (certData.subject.email) { + keyPurposes.push(OID_ID_PKIX_EmailProtection); + } + keyPurposes.push(OID_ID_PKIX_TimeStamping); + const extKeyUsage = new pkijs.ExtKeyUsage({ - keyPurposes: [OID_ID_PKIX_EmailProtection] + keyPurposes: keyPurposes }); certificate.extensions.push( @@ -678,6 +689,8 @@ function createCertificate(certData, issuerData = null) { ); //endregion "SubjectKeyIdentifier" extension + //TODO add policy + /* COULD NOT GET IT WORKING //region "AuthorityKeyIdentifier" extension if (issuerData && issuerData.certificate) { @@ -1530,3 +1543,4 @@ export class ImageData { //Initialization block fixPkijsRDN(); +