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

时间:2019-09-25 15:52:10

标签: java pdf pdfbox

我已经加载了PDDocument。

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

签名的字节范围由 sig.getByteRange()提供。以我为例:

0-18373 43144-46015

我要验证签名的字节范围有效。 因为签名必须验证整个文件本身。 签名还提供了字节范围,因此我不能依靠它。

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

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

我怎么得到这些?

1 个答案:

答案 0 :(得分:4)

看看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:
        // https://stackoverflow.com/a/48185913/535646
        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
{
    // https://stackoverflow.com/questions/55049270
    // 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);
        }
    }
}

(严格地说,在普通括号中的二进制字符串也可以,只要能填满整个间隙,就不必是十六进制字符串。)