Skip to content
Snippets Groups Projects
Commit a23fd9f4 authored by Alexey Lunin's avatar Alexey Lunin
Browse files

Merge branch 'master' into delete-entity

parents d74d7801 ec688929
No related branches found
No related tags found
No related merge requests found
This commit is part of merge request !54. Comments created here will be created in the context of that merge request.
......@@ -61,7 +61,7 @@ function calculateParts(body, from, to, previousBondary) {
let boundary = findFirstBoundary(body, from, to);
if (boundary == null) {
return [{ indices: { from: from, to: to }, boundary: previousBondary }];
return [{ indices: { from: from, to: to }, boundary: previousBondary, leaf: true }];
}
const realBoundary = boundary;
......@@ -78,6 +78,9 @@ function calculateParts(body, from, to, previousBondary) {
}
let bodies = [];
if (previousBondary !== null) {
bodies.push({indices: {from: from, to: to}, boundary: previousBondary, leaf: false});
}
for (let i = 0; i < boundaryIndicesLength - 1; i++) {
const firstPair = boundaryPairs[i];
......@@ -155,13 +158,14 @@ export function parseMIME(mime) {
parts.push({
indices: { from: 0, to: mime.length, headersEnd: headersEnd },
headers,
boundary: "mimemessage"
boundary: "mimemessage",
leaf: false
});
return parts;
}
function getHeaderValue(header, part) {
export function getHeaderValue(header, part) {
if (part.headers && part.headers[header] && part.headers[header].length) {
return part.headers[header];
}
......
......@@ -24,7 +24,8 @@ import {
createPassportCertificate,
decryptMessage,
encryptMessage,
signEmail
signEmail,
verifySMIME
} from "../utilities/signingUtilities";
import { signPdf } from "../utilities/pdfUtilities";
import CryptoData from "../CryptoData";
......@@ -1051,6 +1052,37 @@ const connection = Penpal.connectToParent({
);
});
},
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"
......
......@@ -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,14 @@ export const parseSMIME = smimeString => {
});
}
const certificateChain = getCertificateChain(signatureBase64);
let certificateChain;
if (signatureBase64) {
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));
const to = splitParticipants(getGlobalHeaderValue("to", parts));
......
......@@ -4,10 +4,28 @@ 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";
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 +1024,7 @@ function makeBoundary() {
return "W0RyLiBEYW15YW4gTWl0ZXZd--" + makeid(len);
}
export const parseCertificates = signatureBase64 => {
export const parseSignedData = signatureBase64 => {
try {
const certificateDecoded = atob(signatureBase64);
const buffer = stringToArrayBuffer(certificateDecoded);
......@@ -1014,7 +1032,15 @@ 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;
}
};
export const parseCertificates = signedData => {
try {
return signedData.certificates.map((certificate, index) => {
const certificateData = { issuer: {}, subject: {}, validity: {} };
const serialNumber = bufferToHexCodes(
......@@ -1064,11 +1090,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 +1113,142 @@ export const getCertificateChain = signatureBase64 => {
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);
let signatureBase64;
let signatureBoundary;
for (const part of parts) {
let contentType = getHeaderValue("content-type", part);
if (!contentType) {
continue;
}
contentType = contentType[0];
if (contentType && contentType.startsWith(SIGNATURE_CONTENT_TYPE)) {
signatureBase64 = getAttachment(emailString, part).base64;
signatureBoundary = part.boundary;
break;
}
}
const verificationResult = {
verified: false,
message: "",
vereignSignatures: 0,
nonVereignSignatures: 0
};
if (!signatureBase64) {
verificationResult.message = "Not a signed MIME";
resolve(verificationResult);
return;
}
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);
if (rootCa.tbs.byteLength === 0) {
rootCa.tbs = rootCa.encodeTBS();
}
const signedData = parseSignedData(signatureBase64);
if (!signedData) {
verificationResult.message = "Corrupt SMIME signature";
resolve(verificationResult);
return;
}
for (let i = 0; i < signedData.signerInfos.length; i++) {
let signerResult;
try {
signerResult = await signedData.verify({
signer: i,
data: dataBuffer,
trustedCerts: [rootCa],
checkDate: new Date(),
checkChain: true,
extendedMode: true,
passedWhenNotRevValues: 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++;
}
}
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);
});
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment