Skip to content
Snippets Groups Projects
signingUtilities.js 39.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • Alexey Lunin's avatar
    Alexey Lunin committed
        let signature = arrayBufferToBase64Formatted(cmsSignedBuffer);
        let boundary = makeBoundary();
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        template = template.replace(/{{boundary}}/g, boundary);
        template = template.replace("{{signature}}", signature);
        template = template.replace("{{headers}}", newHeaderLines);
        template = template.replace("{{mime}}", mime);
    
    Alexey Lunin's avatar
    Alexey Lunin committed
        //template = template.replace(newline, '\r\n')
        return template;
      });
    
    
      return sequence;
    }
    
    const newline = /\r\n|\r|\n/g;
    
    function capitalizeFirstLetter(string) {
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      if (string === "id") {
        return "ID";
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      if (string === "mime") {
    
        return "MIME";
      }
    
      return string.charAt(0).toUpperCase() + string.slice(1);
    }
    
    function capitalizeHeader(string) {
      let result = "";
      const tokens = string.split("-");
      for (let i = 0; i < tokens.length; i++) {
        result += capitalizeFirstLetter(tokens[i]);
        if (i !== tokens.length - 1) {
          result += "-";
        }
      }
    
      return result;
    }
    
    function makeBoundary() {
      let len = 20 + Math.random() * 20;
    
    Alexey Lunin's avatar
    Alexey Lunin committed
      return "W0RyLiBEYW15YW4gTWl0ZXZd--" + makeid(len);
    
    Damyan Mitev's avatar
    Damyan Mitev committed
    export const parseSignedData = signatureBase64 => {
    
      try {
        const certificateDecoded = atob(signatureBase64);
        const buffer = stringToArrayBuffer(certificateDecoded);
        const asn1 = fromBER(buffer);
    
        const contentInfo = new ContentInfo({ schema: asn1.result });
        const signedData = new SignedData({ schema: contentInfo.content });
    
    Damyan Mitev's avatar
    Damyan Mitev committed
        return signedData;
      } catch (e) {
    
        console.error("Error parsing signed data:", e);
        return null;
    
    Damyan Mitev's avatar
    Damyan Mitev committed
      }
    };
    
    export const parseCertificates = signedData => {
      try {
    
        return signedData.certificates.map((certificate, index) => {
          const certificateData = { issuer: {}, subject: {}, validity: {} };
          const serialNumber = bufferToHexCodes(
            certificate.serialNumber.valueBlock.valueHex
          );
          const issuer = certificate.issuer.typesAndValues;
          const subject = certificate.subject.typesAndValues;
    
          const notAfter = certificate.notAfter.value;
          const notBefore = certificate.notBefore.value;
    
          let signatureAlgorithm =
            algomap[certificate.signatureAlgorithm.algorithmId];
          if (typeof signatureAlgorithm === "undefined") {
            signatureAlgorithm = certificate.signatureAlgorithm.algorithmId;
          } else {
            signatureAlgorithm = `${signatureAlgorithm}`;
          }
    
          for (const typeAndValue of issuer) {
            let typeVal = rdnmap[typeAndValue.type];
            if (typeof typeVal === "undefined") {
              typeVal = typeAndValue.type;
            }
            const subjVal = typeAndValue.value.valueBlock.value;
            certificateData.issuer[typeVal] = subjVal;
          }
    
          for (const typeAndValue of subject) {
            let typeVal = rdnmap[typeAndValue.type];
            if (typeof typeVal === "undefined") {
              typeVal = typeAndValue.type;
            }
            const subjVal = typeAndValue.value.valueBlock.value;
            certificateData.subject[typeVal] = subjVal;
          }
    
          certificateData.signatureAlgorithm = signatureAlgorithm;
          certificateData.serialNumber = serialNumber;
          certificateData.validity = {
            notAfter,
            notBefore
          };
    
          return certificateData;
        });
      } catch (e) {
        console.error("Error parsing certificate", e);
      }
    };
    
    
    Damyan Mitev's avatar
    Damyan Mitev committed
    export const getCertificateChain = signedData => {
    
      const certificateChain = [];
    
      try {
    
    Damyan Mitev's avatar
    Damyan Mitev committed
        const certificates = parseCertificates(signedData);
    
    
        // Add first certificate in the chain
        certificateChain.push(certificates[0]);
    
        // Go through all certificates to build a chain from first certificate to the root
        certificates.forEach(certificate => {
    
    Alexey Lunin's avatar
    Alexey Lunin committed
          if (
            certificateChain[0].issuer.commonName === certificate.subject.commonName
          ) {
    
            certificateChain.unshift(certificate);
          }
        });
      } catch (e) {
        console.warn("Error getting certificate data", e);
      }
    
      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;
    };
    
    
    Damyan Mitev's avatar
    Damyan Mitev committed
    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) {
    
    Damyan Mitev's avatar
    Damyan Mitev committed
              continue;
            }
            contentType = contentType[0];
    
    
            if (contentType && contentType.startsWith(SIGNATURE_CONTENT_TYPE)) {
    
    Damyan Mitev's avatar
    Damyan Mitev committed
              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({
    
    Damyan Mitev's avatar
    Damyan Mitev committed
                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`;
    
    Damyan Mitev's avatar
    Damyan Mitev committed
                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;
    
    Damyan Mitev's avatar
    Damyan Mitev committed
          resolve(verificationResult);
        }, 50);
      });
    };
    
    
    //Initialization block
    fixPkijsRDN();