使用 PDFBox 验证 pdf 文件中 PDSignature 的字节范围

Verify the byte range of a PDSignature in a pdf file using PDFBox

我已经加载了一个 PDDocument。

我检索到了名为 sig 的 PDSignature 对象。

签名的字节范围由sig.getByteRange()提供。在我的例子中是:

0-18373 43144-46015

我想验证签名的字节范围是否有效。 因为签名必须验证整个文件本身。 字节范围也由签名提供,所以我不能依赖它。

我可以检查第一个值为 0,最后一个值必须为文件大小 -1。

但我还需要验证第二个和第三个值(18373 和 43144)。因此我需要知道 PDSignature 在文档中的位置及其长度。

我如何获得这些?

查看 PDFBox 示例 ShowSignature。它间接地这样做:它检查字节范围间隙中的字节是否与文档解析确定的签名值完全一致。

方法中showSignature:

int[] byteRange = sig.getByteRange();
if (byteRange.length != 4)
{
    System.err.println("Signature byteRange must have 4 items");
}
else
{
    long fileLen = infile.length();
    long rangeMax = byteRange[2] + (long) byteRange[3];
    // multiply content length with 2 (because it is in hex in the PDF) and add 2 for < and >
    int contentLen = contents.getString().length() * 2 + 2;
    if (fileLen != rangeMax || byteRange[0] != 0 || byteRange[1] + contentLen != byteRange[2])
    {
        // a false result doesn't necessarily mean that the PDF is a fake
        // see this answer why:
        // 
        System.out.println("Signature does not cover whole document");
    }
    else
    {
        System.out.println("Signature covers whole document");
    }
    checkContentValueWithFile(infile, byteRange, contents);
}

辅助方法checkContentValueWithFile:

private void checkContentValueWithFile(File file, int[] byteRange, COSString contents) throws IOException
{
    // 
    // comment by mkl: check whether gap contains a hex value equal
    // byte-by-byte to the Content value, to prevent attacker from using a literal string
    // to allow extra space
    try (RandomAccessBufferedFileInputStream raf = new RandomAccessBufferedFileInputStream(file))
    {
        raf.seek(byteRange[1]);
        int c = raf.read();
        if (c != '<')
        {
            System.err.println("'<' expected at offset " + byteRange[1] + ", but got " + (char) c);
        }
        byte[] contentFromFile = raf.readFully(byteRange[2] - byteRange[1] - 2);
        byte[] contentAsHex = Hex.getString(contents.getBytes()).getBytes(Charsets.US_ASCII);
        if (contentFromFile.length != contentAsHex.length)
        {
            System.err.println("Raw content length from file is " +
                    contentFromFile.length +
                    ", but internal content string in hex has length " +
                    contentAsHex.length);
        }
        // Compare the two, we can't do byte comparison because of upper/lower case
        // also check that it is really hex
        for (int i = 0; i < contentFromFile.length; ++i)
        {
            try
            {
                if (Integer.parseInt(String.valueOf((char) contentFromFile[i]), 16) !=
                    Integer.parseInt(String.valueOf((char) contentAsHex[i]), 16))
                {
                    System.err.println("Possible manipulation at file offset " +
                            (byteRange[1] + i + 1) + " in signature content");
                    break;
                }
            }
            catch (NumberFormatException ex)
            {
                System.err.println("Incorrect hex value");
                System.err.println("Possible manipulation at file offset " +
                        (byteRange[1] + i + 1) + " in signature content");
                break;
            }
        }
        c = raf.read();
        if (c != '>')
        {
            System.err.println("'>' expected at offset " + byteRange[2] + ", but got " + (char) c);
        }
    }
}

(严格来说,普通括号中的二进制字符串也可以只要它能填满整个空白,它不必是十六进制字符串。)