iTextSharp使用已签名哈希值对PDF进行签名

3
我正在尝试通过签名服务签署PDF文档。该服务要求发送十六进制编码的SHA256摘要,并返回一个十六进制编码的signatureValue。除此之外,我还会收到签名证书、中间证书、OCSP响应和TimeStampToken。然而,我已经陷入了尝试使用signatureValue签署PDF的困境中。
我已经阅读了Bruno的白皮书,浏览了大量的互联网信息,并尝试了许多不同的方法,但签名仍然无效。
我的最新尝试如下:
首先,准备PDF文件。
PdfReader reader = new PdfReader(src);
FileStream os = new FileStream(dest, FileMode.Create);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Certificate = signingCertificate;
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);

string hashAlgorithm = "SHA-256";
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
PdfSignatureAppearance appearance2 = stamper.SignatureAppearance;
Stream stream = appearance2.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(stream, hashAlgorithm);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);

将哈希值byte[] sh转换为以下字符串:

private static String sha256_hash(Byte[] value)
{
    using (SHA256 hash = SHA256.Create())
    {
         return String.Concat(hash.ComputeHash(value).Select(item => item.ToString("x2"))).ToUpper();
    }
}

发送给签名服务。我收到的十六进制编码的signatureValue然后转换为字节。

private static byte[] StringToByteArray(string hex)
{
    return Enumerable.Range(0, hex.Length).Where(x => x % 2 == 0).Select(x => Convert.ToByte(hex.Substring(x, 2), 16)).ToArray();
}

最后,创建签名

private void CreateSignature(string src, string dest, byte[] sig) 
{
    PdfReader reader = new PdfReader(src); // src is now prepared pdf
    FileStream os = new FileStream(dest, FileMode.Create);
    IExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
    MakeSignature.SignDeferred(reader, "Signature1", os, external);

    reader.Close();
    os.Close();
}
private class MyExternalSignatureContainer : IExternalSignatureContainer
{
    protected byte[] sig;
    public MyExternalSignatureContainer(byte[] sig)
    {
        this.sig = sig;
    }
    public byte[] Sign(Stream s)
    {
        return sig;
    }
    public void ModifySigningDictionary(PdfDictionary signDic) { }
}

我做错了什么?非常感谢您的帮助。谢谢!

编辑:当前状态

感谢mkl提供的帮助,遵循Bruno的延迟签名示例,我已经成功解决了无效签名的问题。显然,签名服务没有向我发送完整的证书链,而只是中间证书,这导致了无效消息。不幸的是,签名仍然存在缺陷。

我按照以下方式构建证书链:

List<X509Certificate> certificateChain = new List<X509Certificate>
{
     signingCertificate,
     intermediateCertificate
}; 

在MyExternalSignatureContainer的签名方法中,我现在构建并返回签名容器:
public byte[] Sign(Stream s)
{
    string hashAlgorithm = "SHA-256";
    PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);

    byte[] ocspResponse = Convert.FromBase64String("Base64 encoded DER representation of the OCSP response received from signing service");
    byte[] hash = DigestAlgorithms.Digest(s, hashAlgorithm);
    byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocspResponse, null, CryptoStandard.CMS);

    string messageDigest = Sha256_hash(sh);
    // messageDigest sent to signing service
    byte[] signatureAsByte = StringToByteArray("Hex encoded SignatureValue received from signing service");

    sgn.SetExternalDigest(signatureAsByte, null, "RSA");

    ITSAClient tsaClient = new MyITSAClient();

    return sgn.GetEncodedPKCS7(hash, tsaClient, ocspResponse, null, CryptoStandard.CMS); 
}

public class MyITSAClient : ITSAClient
{
    public int GetTokenSizeEstimate()
    {
        return 0;
    }

    public IDigest GetMessageDigest()
    {
        return new Sha256Digest();
    }

    public byte[] GetTimeStampToken(byte[] imprint)
    {
        string hashedImprint = HexEncode(imprint);
        // Hex encoded Imprint sent to signing service

        return Convert.FromBase64String("Base64 encoded DER representation of TimeStampToken received from signing service");
    }
}

仍然收到以下消息:

  1. "签名者的身份未知,因为它未包含在可信身份列表中,且其父证书也不是可信身份"
  2. "签名已经时间戳,但无法验证时间戳"

再次提供进一步帮助将非常感激!


我做错了什么?请首先说明出了什么问题。最好分享一个由您的代码签名的示例PDF文件,以供分析。 - mkl
Adobe表示签名无效。也没有看到证书详情。这是示例PDF的链接:https://drive.google.com/file/d/1-fHe9OCEoA7hfTNWjcemc4T1OqXi0ibv/view?usp=sharing 谢谢查看。 - mrb
1个回答

2

“我错在哪里了?”

问题在于一方面,您开始使用PdfPKCS7实例构建CMS签名容器。

PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);

并且对于计算出的文档摘要 hash,检索已签名属性。
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);

发送签名文件。

到目前为止,一切顺利。

但是你忽略了你开始构建的CMS容器,而是将你从服务中获取的裸签名字节注入到PDF中。

这是不可能的,因为你的签名字节并没有直接签署文档,而是签署了这些已签署属性(因此,间接地签署了文档,因为文档哈希是已签署属性之一)。因此,通过忽略正在构建的CMS容器,你丢失了实际签名的数据...

此外,你使用的子过滤器ADBE_PKCS7_DETACHED承诺嵌入的签名是一个完整的CMS签名容器,而不是几个裸签名字节,因此格式也是错误的。

应该怎么做?

不要将你从服务中获取的裸签名字节直接注入PDF中,而是将它们设置为最初开始构建签名容器的PdfPKCS7实例的外部摘要:

sgn.SetExternalDigest(sig, null, ENCRYPTION_ALGO);

(ENCRYPTION_ALGO 必须是签名算法的加密部分,我假设在您的情况下为 "RSA"。)
然后,您可以检索生成的 CMS 签名容器:
byte[] encodedSig = sgn.GetEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);

现在这是签名容器,使用MyExternalSignatureContainer将其注入到文档中:
IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSig);
MakeSignature.SignDeferred(reader, "Signature1", os, external);

剩余问题

您已经更正了代码,但Adobe Reader仍然警告您的签名:

  1. "签名者的身份未知,因为它未包含在受信任身份列表中,并且其父证书也不是受信任的身份之一"

这个警告是可以预期的,也是正确的!

签名者的身份未知,因为您的签名服务仅使用演示证书,而不是用于生产的证书:

Signer certificate in certificate viewer

正如您所看到的,证书是由“GlobalSign Non-Public HVCA Demo”颁发的,而非公共演示颁发者由于明显原因不应受信任(除非您手动将它们添加到用于测试目的的信任存储中)。

  1. “签名已时间戳,但无法验证时间戳”

Adobe不批准您的时间戳有两个原因:

一方面,就像上面所述,时间戳证书是一个非公共的演示证书(“DSS Non-Public Demo TSA Responder”)。因此,验证者没有理由相信您的时间戳。

另一方面,您的时间戳代码存在实际错误,您对哈希算法进行了两次应用!在您的MyITSAClient类中,您有:

public byte[] GetTimeStampToken(byte[] imprint)
{
    string hashedImprint = Sha256_hash(imprint);
    // hashedImprint sent to signing service

    return Convert.FromBase64String("Base64 encoded DER representation of TimeStampToken received from signing service");
}

你的GetTimeStampToken实现中的imprint参数已经被哈希过了,因此你需要将这些字节进行十六进制编码并发送以进行时间戳。但是你应用了Sha256_hash方法,首先对其进行哈希,然后再对这个新哈希进行十六进制编码。
因此,不要使用Sha256_hash方法,只需对imprint进行十六进制编码!

我认为在CreateSignature方法中应该检索CMS容器,但那不是我构建PKCS7签名的地方。如果我在构建它的地方检索该容器,那么Signature1字段尚未创建。(我还将延迟签名移动到构建cms容器的方法中) - mrb
我还尝试了Bruno的延迟签名示例(https://developers.itextpdf.com/examples/security-itext5/digital-signatures-white-paper/digital-signatures-chapter-4),这个示例看起来非常相似,因为他使用了privatekeysignature和subfilter detached。因此,我将cms容器的构建移动到了MyExternalSignatureContainer的sign方法中,但是Adobe仍然显示签名无效。现在我完全不知所措 :( - mrb
样本pdf https://drive.google.com/file/d/1y7Ne43IxzU48LmfC1AtSPWs4KqKKpi_F/view?usp=sharing 如果您能再看一下,我将非常感激。 - mrb
延迟签名的示例确实有效,但证书链似乎有些问题。在某种程度上仍然存在这个问题,因为现在签名者的身份不被信任,但至少签名不再无效了 :) @mkl 感谢您的解释! - mrb
你分享的第二个示例是当前状态还是你得到了之前的任何样本? - mkl
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接