diff --git a/javascript/src/utilities/emailUtilities.js b/javascript/src/utilities/emailUtilities.js index fcb572584ecff06aa37c0f4bd17f0b23351d9625..70ebc48aa9aab0afb438de858f4d017d6fa5d9d7 100644 --- a/javascript/src/utilities/emailUtilities.js +++ b/javascript/src/utilities/emailUtilities.js @@ -61,11 +61,13 @@ export const parseSMIME = smimeString => { }); } - let signedData; //TODO remove from here, move inside if block let certificateChain; if (signatureBase64) { - signedData = parseSignedData(signatureBase64); - certificateChain = getCertificateChain(signedData); + const signedData = parseSignedData(signatureBase64); + if (signedData) { + //TODO revise and use signedData's generation of cert chain (per signer) + certificateChain = getCertificateChain(signedData); + } } const from = splitParticipants(getGlobalHeaderValue("from", parts)); @@ -80,10 +82,7 @@ export const parseSMIME = smimeString => { html: extractHtmlBodyFromString(html), plain, attachments, - certificateChain, - signedData, //TODO remove - fixedSMIME: emailString, //TODO remove - parts //TODO remove + certificateChain }; resolve(message); diff --git a/javascript/src/utilities/signingUtilities.js b/javascript/src/utilities/signingUtilities.js index e53cbc645f20dde384c4b00661c1f2f0da93563f..db47ed62ed7c1c3fd455405cd95092a84a7129ff 100644 --- a/javascript/src/utilities/signingUtilities.js +++ b/javascript/src/utilities/signingUtilities.js @@ -4,7 +4,11 @@ import { getTimeLeftInLocalStorage, makeid } from "./appUtility"; -import { bufferToHexCodes, stringToArrayBuffer } from "pvutils"; +import { + bufferToHexCodes, + stringToArrayBuffer, + isEqualBuffer +} from "pvutils"; import { fromBER } from "asn1js"; import { ContentInfo, SignedData } from "pkijs"; import { algomap, rdnmap } from "../constants/certificates"; @@ -1020,18 +1024,6 @@ function makeBoundary() { return "W0RyLiBEYW15YW4gTWl0ZXZd--" + makeid(len); } -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); @@ -1042,20 +1034,13 @@ export const parseSignedData = signatureBase64 => { 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 + console.error("Error parsing signed data:", e); + return null; } }; 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: {} }; const serialNumber = bufferToHexCodes( @@ -1129,52 +1114,87 @@ export const getCertificateChain = signedData => { return certificateChain; }; +const isVereignSignature = (signerInfo, signerVerificationResult) => { + const signerCert = signerVerificationResult.signerCertificate; + + for (const typeAndValue of signerCert.subject.typesAndValues) { + try { + if (typeAndValue.type === "2.5.4.10" && + typeAndValue.value.valueBlock.value === "Vereign AG" + ) { + return true; + } + } catch (ignore) {} + } + + return false; +}; + 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) { + if (!contentType) { continue; } contentType = contentType[0]; - if (contentType.startsWith(SIGNATURE_CONTENT_TYPE)) { + if (contentType && contentType.startsWith(SIGNATURE_CONTENT_TYPE)) { signatureBase64 = getAttachment(emailString, part).base64; signatureBoundary = part.boundary; break; } } - let verificationResult = false; - let signedData; + const verificationResult = { + verified: false, + message: "", + vereignSignatures: 0, + nonVereignSignatures: 0 + }; - if (signatureBase64) { - const dataPart = parts[0]; - if (dataPart.boundary !== signatureBoundary) { - throw new Error(`Invalid SMIME format: wrong boundary on first MIME part`); - } + if (!signatureBase64) { + verificationResult.message = "Not a signed MIME"; + resolve(verificationResult); + return; + } - const data = emailString.slice( - dataPart.indices.from, - dataPart.indices.to - ); - const dataBuffer = stringToArrayBuffer(data); + const dataPart = parts[0]; + if (dataPart.boundary !== signatureBoundary) { + verificationResult.message = "Invalid SMIME format: wrong boundary on first MIME part"; + resolve(verificationResult); + return; + } + + const data = emailString.slice( + dataPart.indices.from, + dataPart.indices.to + ); + const dataBuffer = stringToArrayBuffer(data); - const rootCa = parseCertificate(rootCaPem); + const rootCa = parseCertificate(rootCaPem); + if (rootCa.tbs.byteLength === 0) { + rootCa.tbs = rootCa.encodeTBS(); + } + + const signedData = parseSignedData(signatureBase64); + if (!signedData) { + verificationResult.message = "Corrupt SMIME signature"; + resolve(verificationResult); + return; + } - signedData = parseSignedData(signatureBase64); - for (let i = 0; i < signedData.signerInfos.length; i++) { - console.log(`Validating message for signer ${i}`); - const result = await signedData.verify({ + for (let i = 0; i < signedData.signerInfos.length; i++) { + let signerResult; + try { + signerResult = await signedData.verify({ signer: i, data: dataBuffer, trustedCerts: [rootCa], @@ -1183,16 +1203,51 @@ export const verifySMIME = (smimeString, rootCaPem) => { extendedMode: true, passedWhenNotRevValues: false }); - console.log(`Result for signer ${i}:`,result); - if (!result) { - verificationResult = false; + } catch (e) { + verificationResult.message = e.message; + resolve(verificationResult); + return; + } + + const signerVerified = !!signerResult.signatureVerified && !!signerResult.signerCertificateVerified; + + if (!signerVerified) { + if (signerResult.message) { + verificationResult.message = signerResult.message; + } else { + verificationResult.message = "Message integrity is compromised"; + } + resolve(verificationResult); + return; + } + + if (isVereignSignature(signedData.signerInfos[i], signerResult)) { + const signerPath = signerResult.certificatePath; + const signerRoot = signerPath[signerPath.length - 1]; + if (signerRoot.tbs.byteLength === 0) { + signerRoot.tbs = signerRoot.encodeTBS(); + } + if (!isEqualBuffer(signerRoot.tbs, rootCa.tbs)) { + verificationResult.message = + `Vereign signature ${i} has root certificate, different from Vereign root CA`; resolve(verificationResult); return; } + verificationResult.vereignSignatures++; + } else { + verificationResult.nonVereignSignatures++; } } - verificationResult = true; + if (signedData.signerInfos.length === 0) { + verificationResult.message = "No signatures found"; + } else + if (verificationResult.vereignSignatures === 0) { + verificationResult.message = "Verified succesfully, but no Vereign signatures found"; + } else { + verificationResult.message = "Verified succesfully"; + } + verificationResult.verified = true; resolve(verificationResult); }, 50); });