All files / lib/helpers extractSignature.js

100% Statements 28/28
75% Branches 6/8
100% Functions 6/6
100% Lines 26/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93          4x         4x 16x         16x 2x           14x 17x   14x 17x             17x   14x                 4x 16x   16x 14x 14x   14x 1x                 13x       13x 13x 16x             16x     16x     16x   13x                  
import { Buffer } from "node:buffer";
 
import { VerifyPDFError } from "../VerifyPDFError.js";
import { getSignatureMeta, preparePDF } from "./general.js";
 
const DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********";
 
/**
 * @param {Uint8Array} pdfBuffer
 */
const getByteRange = (pdfBuffer) => {
	const byteRangeStrings = pdfBuffer
		.toString()
		.match(
			/\/ByteRange\s*\[{1}\s*(?:(?:\d*|\/\*{10})\s+){3}(?:\d+|\/\*{10}){1}\s*\]{1}/g,
		);
	if (!byteRangeStrings) {
		throw new VerifyPDFError(
			"Failed to locate ByteRange.",
			VerifyPDFError.Type.TYPE_PARSE,
		);
	}
 
	const byteRangePlaceholder = byteRangeStrings.find((s) =>
		s.includes(`/${DEFAULT_BYTE_RANGE_PLACEHOLDER}`),
	);
	const strByteRanges = byteRangeStrings.map(
		(brs) => brs.match(/[^[\s]*(?:\d|\/\*{10})/g) ?? [],
	);
 
	/**
	 * @type {[number, number, number, number][]}
	 */
	// @ts-expect-error IDK
	const byteRanges = strByteRanges.map((n) => n.map(Number));
 
	return {
		byteRangePlaceholder,
		byteRanges,
	};
};
 
/**
 * @param {Uint8Array} pdf
 */
const extractSignature = (pdf) => {
	const pdfBuffer = preparePDF(pdf);
 
	const { byteRanges } = getByteRange(pdfBuffer);
	const byteRange = byteRanges.at(-1) ?? [0, 0, 0, 0];
	const endOfByteRange = byteRange[2] + byteRange[3];
 
	if (pdfBuffer.length > endOfByteRange) {
		throw new VerifyPDFError(
			"Failed byte range verification.",
			VerifyPDFError.Type.TYPE_BYTE_RANGE,
		);
	}
 
	/**
	 * @type {string[]}
	 */
	const signatureStr = [];
	/**
	 * @type {Uint8Array[]}
	 */
	const signedData = [];
	for (const byteRange of byteRanges) {
		signedData.push(
			Buffer.concat([
				pdfBuffer.subarray(byteRange[0], byteRange[0] + byteRange[1]),
				pdfBuffer.subarray(byteRange[2], byteRange[2] + byteRange[3]),
			]),
		);
 
		const signatureHex = pdfBuffer
			.subarray(byteRange[0] + byteRange[1] + 1, byteRange[2])
			.toString("latin1");
		signatureStr.push(Buffer.from(signatureHex, "hex").toString("latin1"));
	}
 
	const signatureMeta = signedData.map((sd) => getSignatureMeta(sd));
 
	return {
		byteRanges,
		signatureStr,
		signedData,
		signatureMeta,
	};
};
 
export default extractSignature;