diff --git a/javascript/src/utilities/emailUtilities.js b/javascript/src/utilities/emailUtilities.js index df59737b20636ffca4b5b5431c024be65ff6668f..fcb572584ecff06aa37c0f4bd17f0b23351d9625 100644 --- a/javascript/src/utilities/emailUtilities.js +++ b/javascript/src/utilities/emailUtilities.js @@ -11,9 +11,12 @@ import { getAttachment, getGlobalHeaderValue } from "../helpers/mailparser"; -import { getCertificateChain } from "./signingUtilities"; +import { + getCertificateChain, + parseSignedData +} from "./signingUtilities"; -const SIGNATURE_CONTENT_TYPE = "application/pkcs7-signature"; +export const SIGNATURE_CONTENT_TYPE = "application/pkcs7-signature"; export const DEFAULT_ATTACHMENT_NAME = "attachment"; const splitParticipants = participantsList => { @@ -44,7 +47,7 @@ export const parseSMIME = smimeString => { rawAttachment ); - if (contentType.indexOf(SIGNATURE_CONTENT_TYPE) !== -1) { + if (contentType.startsWith(SIGNATURE_CONTENT_TYPE)) { signatureBase64 = base64; } @@ -58,7 +61,12 @@ export const parseSMIME = smimeString => { }); } - const certificateChain = getCertificateChain(signatureBase64); + let signedData; //TODO remove from here, move inside if block + let certificateChain; + if (signatureBase64) { + signedData = parseSignedData(signatureBase64); + certificateChain = getCertificateChain(signedData); + } const from = splitParticipants(getGlobalHeaderValue("from", parts)); const to = splitParticipants(getGlobalHeaderValue("to", parts)); @@ -72,7 +80,10 @@ export const parseSMIME = smimeString => { html: extractHtmlBodyFromString(html), plain, attachments, - certificateChain + certificateChain, + signedData, //TODO remove + fixedSMIME: emailString, //TODO remove + parts //TODO remove }; resolve(message); diff --git a/javascript/src/utilities/signingUtilities.js b/javascript/src/utilities/signingUtilities.js index 27d23e4c4484f13701f2058cc1d4c974d0c507b2..e53cbc645f20dde384c4b00661c1f2f0da93563f 100644 --- a/javascript/src/utilities/signingUtilities.js +++ b/javascript/src/utilities/signingUtilities.js @@ -8,6 +8,20 @@ import { bufferToHexCodes, stringToArrayBuffer } from "pvutils"; import { fromBER } from "asn1js"; import { ContentInfo, SignedData } from "pkijs"; import { algomap, rdnmap } from "../constants/certificates"; +import { + fixNewLines, + getAttachment, + getAttachments, + getGlobalHeaderValue, + getHeaderValue, + parseMIME +} from "../helpers/mailparser"; +import dataUriToBlob from "data-uri-to-blob"; +import { + extractHtmlBodyFromString, + getFilenameFromHeaders, + SIGNATURE_CONTENT_TYPE +} from "./emailUtilities"; const libmime = require("libmime"); const pkijs = require("pkijs"); @@ -1006,7 +1020,19 @@ function makeBoundary() { return "W0RyLiBEYW15YW4gTWl0ZXZd--" + makeid(len); } -export const parseCertificates = signatureBase64 => { +function hexString(buffer) { //TODO remove + const byteArray = new Uint8Array(buffer); + + const hexCodes = [...byteArray].map(value => { + const hexCode = value.toString(16); + const paddedHexCode = hexCode.padStart(2, '0'); + return paddedHexCode; + }); + + return hexCodes.join(''); +} + +export const parseSignedData = signatureBase64 => { try { const certificateDecoded = atob(signatureBase64); const buffer = stringToArrayBuffer(certificateDecoded); @@ -1014,6 +1040,21 @@ export const parseCertificates = signatureBase64 => { const contentInfo = new ContentInfo({ schema: asn1.result }); const signedData = new SignedData({ schema: contentInfo.content }); + return signedData; + } catch (e) { + console.error("Error parsing signed data", e); + return null; //TODO implement proper error handling + } +}; + +export const parseCertificates = signedData => { + try { + //TODO remove block + console.log("ParseCerts", signedData.signerInfos); + console.log("DigestAttrib",signedData.signerInfos[0].signedAttrs.attributes[2]); + const digest = signedData.signerInfos[0].signedAttrs.attributes[2].values[0].valueBlock.valueHex; + const digestStr = hexString(digest); + console.log("DigestAttrib", digestStr); return signedData.certificates.map((certificate, index) => { const certificateData = { issuer: {}, subject: {}, validity: {} }; @@ -1064,11 +1105,11 @@ export const parseCertificates = signatureBase64 => { } }; -export const getCertificateChain = signatureBase64 => { +export const getCertificateChain = signedData => { const certificateChain = []; try { - const certificates = parseCertificates(signatureBase64); + const certificates = parseCertificates(signedData); // Add first certificate in the chain certificateChain.push(certificates[0]); @@ -1087,3 +1128,72 @@ export const getCertificateChain = signatureBase64 => { return certificateChain; }; + +export const verifySMIME = (smimeString, rootCaPem) => { + return new Promise(resolve => { + setTimeout(async () => { + const emailString = fixNewLines(smimeString); + const parts = parseMIME(emailString); + //const rawAttachments = getAttachments(emailString, parts); + + //const attachments = []; + let signatureBase64; + let signatureBoundary; + + for (const part of parts) { + let contentType = getHeaderValue("content-type", part); + if (contentType === null || contentType === undefined) { + continue; + } + contentType = contentType[0]; + + if (contentType.startsWith(SIGNATURE_CONTENT_TYPE)) { + signatureBase64 = getAttachment(emailString, part).base64; + signatureBoundary = part.boundary; + break; + } + } + + let verificationResult = false; + let signedData; + + if (signatureBase64) { + const dataPart = parts[0]; + if (dataPart.boundary !== signatureBoundary) { + throw new Error(`Invalid SMIME format: wrong boundary on first MIME part`); + } + + const data = emailString.slice( + dataPart.indices.from, + dataPart.indices.to + ); + const dataBuffer = stringToArrayBuffer(data); + + const rootCa = parseCertificate(rootCaPem); + + signedData = parseSignedData(signatureBase64); + for (let i = 0; i < signedData.signerInfos.length; i++) { + console.log(`Validating message for signer ${i}`); + const result = await signedData.verify({ + signer: i, + data: dataBuffer, + trustedCerts: [rootCa], + checkDate: new Date(), + checkChain: true, + extendedMode: true, + passedWhenNotRevValues: false + }); + console.log(`Result for signer ${i}:`,result); + if (!result) { + verificationResult = false; + resolve(verificationResult); + return; + } + } + } + + verificationResult = true; + resolve(verificationResult); + }, 50); + }); +};