使用 ItextSharp 验证数字签名

Verify digital signature with ItextSharp

我正在尝试使用 iTextSharp 在 c# 中验证数字签名。

我已经按照 iText 网络 (http://gitlab.itextsupport.com/itextsharp/tutorial/blob/master/signatures/chapter5/C5_03_CertificateValidation/C5_03_CertificateValidation.cs) 中的示例进行操作,但结果不是我所期望的。具体来说,当尝试通过 OCSP 或 CRL 验证签名时,结果通常是无法验证签名。我认为这不应该发生,因为 Adob​​e Reader 验证签名正常。

我用来测试验证的pdf可以在这个link中找到:https://blogs.adobe.com/security/SampleSignedPDFDocument.pdf

这是我正在使用的代码(上面 link 中示例的简短版本):

static void Main(String[] args)
{
    LoggerFactory.GetInstance().SetLogger(new SysoLogger());
    C5_03_CertificateValidation app = new C5_03_CertificateValidation();
    app.VerifySignatures(EXAMPLE); //Pdf file I'm using to test the verification
}

public void VerifySignatures(String path)
{
    Console.WriteLine(path);
    PdfReader reader = new PdfReader(path);
    AcroFields fields = reader.AcroFields;
    List<String> names = fields.GetSignatureNames();
    foreach (string name in names)
    {
        Console.WriteLine("===== " + name + " =====");
        VerifySignature(fields, name);
    }
    Console.WriteLine();
}

public PdfPKCS7 VerifySignature(AcroFields fields, String name)
{
    PdfPKCS7 pkcs7 = fields.VerifySignature(name);
    X509Certificate[] certs = pkcs7.SignCertificateChain;
    DateTime cal = pkcs7.SignDate;

    X509Certificate signCert = certs[0];
    X509Certificate issuerCert = (certs.Length > 1 ? certs[1] : null);
    Console.WriteLine("=== Checking validity of the document at the time of signing ===");
    CheckRevocation(pkcs7, signCert, issuerCert, cal);
    Console.WriteLine("=== Checking validity of the document today ===");
    CheckRevocation(pkcs7, signCert, issuerCert, DateTime.Now);
    return pkcs7;
}

public static void CheckRevocation(PdfPKCS7 pkcs7, X509Certificate signCert, X509Certificate issuerCert, DateTime date)
{
    List<BasicOcspResp> ocsps = new List<BasicOcspResp>();
    if (pkcs7.Ocsp != null)
        ocsps.Add(pkcs7.Ocsp);
    OcspVerifier ocspVerifier = new OcspVerifier(null, ocsps);
    List<VerificationOK> verification =
        ocspVerifier.Verify(signCert, issuerCert, date);
    if (verification.Count == 0)
    {
        List<X509Crl> crls = new List<X509Crl>();
        if (pkcs7.CRLs != null)
            foreach (X509Crl crl in pkcs7.CRLs)
                crls.Add(crl);
        CrlVerifier crlVerifier = new CrlVerifier(null, crls);
        verification.AddRange(crlVerifier.Verify(signCert, issuerCert, date));
    }
    if (verification.Count == 0)
        Console.WriteLine("The signing certificate couldn't be verified with the example");
    else
        foreach (VerificationOK v in verification)
            Console.WriteLine(v);


    //Code not in the example, added by me
    //This way, I can find out if the certificate is revoked or not (through CRL). Not sure if it's the right way though
    if (verification.Count == 0 && pkcs7.CRLs != null && pkcs7.CRLs.Count != 0)
    {
        bool revoked = false;
        foreach (X509Crl crl in pkcs7.CRLs)
        {
            revoked = crl.IsRevoked(pkcs7.SigningCertificate);
            if (revoked)
                break;
        }

        Console.WriteLine("Is certificate revoked?: " + revoked.ToString());
    }
}

这是我得到的输出:

===== Signature2 =====

=== Checking validity of the document at the time of signing ===
i.t.p.s.OcspClientBouncyCastle INFO  Getting OCSP from http://adobe-ocsp.geotrust.com/responder
iTextSharp.text.pdf.security.OcspClientBouncyCastle ERROR Error en el servidor remoto: (502) Puerta de enlace no válida.
i.t.p.s.OcspVerifier INFO  Valid OCSPs found: 0
i.t.p.s.CrlVerifier INFO  Getting CRL from http://crl.geotrust.com/crls/adobeca1.crl
i.t.p.s.CrlVerifier INFO  Valid CRLs found: 0
The signing certificate couldnt be verified with the example
Is certificate revoked?: False

=== Checking validity of the document today ===
i.t.p.s.OcspClientBouncyCastle INFO  Getting OCSP from http://adobe-ocsp.geotrust.com/responder
iTextSharp.text.pdf.security.OcspClientBouncyCastle ERROR Error en el servidor remoto: (502) Puerta de enlace no válida.
i.t.p.s.OcspVerifier INFO  Valid OCSPs found: 0
i.t.p.s.CrlVerifier INFO  Getting CRL from http://crl.geotrust.com/crls/adobeca1.crl
i.t.p.s.CrlVerifier INFO  Valid CRLs found: 0
The signing certificate couldnt be verified with the example
Is certificate revoked?: False

我不明白为什么无法验证签名,因为 Adob​​e 可以验证。任何想法表示赞赏。

这个问题有两个方面:

  • 为什么 iTextSharp 似乎无法验证 Adob​​e 提供的给定示例签名?
  • 应该在哪个日期、使用哪个 CRL 进行撤销检查?

使用 iTextSharp 验证给定的签名

使用 C5_03_CertificateValidation.cs 中的示例代码,OP 无法验证相关证书(尤其是签名者证书)是否未被撤销,"at the time of signing""today"。另一方面,我可以立即验证 "at the time of signing".

OP 的测试和我的测试之间的主要区别在于,前者使用 OP 的时区 UTC-3,而我的使用 UTC+2。

因此,我 运行 使用不同系统时区的代码,确实:验证仅在 UTC-01 及以上时区成功,即 UTC-01、UTC、UTC+01、.. .

测试中DateTime cal = pkcs7.SignDate返回的时间原来是使用当前本地时区给定的。

显然,因此,CRL 验证代码 不是 使用根据本地时区的时间,而是使用某个固定时区,大概是 UTC 本身。

因此,可以通过

使示例代码通用

changing

crlVerifier.Verify(signCert, issuerCert, date)

to

crlVerifier.Verify(signCert, issuerCert, date.ToUniversalTime())

因为 OP 可以在测试后确认。

为撤销检查选择正确的日期和 CRL

OP 提到他更愿意使用当前时间和当前版本的 PKI 的 CRL 执行吊销检查。

虽然此方法似乎利用了可用的最新信息,但其用处有限:

  • 如果受审查的证书现在已经超过了它的原始有效期(即它的有效期不是在过去的日期)但在签署时不是,关于它可能被撤销的信息签名时可能不再在 CRL 上。事实上,根据 RFC 5280

    An entry MUST NOT be removed from the CRL until it appears on one regularly scheduled CRL issued beyond the revoked certificate's validity period.

    因此,在 有效期后,可能 从任何版本的 CRL 中删除它。

    因此,使用比证书有效期结束时更新的 CRL 是没有意义的。

  • 即使受审查的证书尚未超过其原始有效期,PKI 提供商也可能已经倒闭。在那种情况下,所有吊销信息的访问点现在可能只服务于 PKI 仍处于活动状态或 none 时创建的 CRL 的最终版本。

    在那种情况下,只要 CRL 足够新,就可以使用最终的 CRL 或仍然可用的最新 CRL(例如,在某些 CRL 缓存中)。

因此,验证策略通常允许甚至要求使用较旧的 CRL,只要它足够新,至少 CRL 的 nextUpdate 值必须在签名时间之后,同时够老了,至少CRL的thisUpdate值必须在证书有效期结束前。

不过,需要考虑的一项是在检查是否可以使用较旧的 CRL 时,将签名时间与什么时间进行比较。

上面的iText示例代码中使用的pkcs7.SignDate可能只是PDF或CMS容器中的一个字段的内容,可能是伪造的:有人可能已经掌握了私钥;在相关证书被吊销后,该人可能仍会滥用将签名时间信息设置为吊销前日期的密钥。

因此,您可能会以不同方式确定签署日期。例如

  • 如果签名容器中包含签名时间戳或PDF文档中包含较晚的文档时间戳,如果该时间戳可以信任,则可以使用该时间戳的时间;

  • 如果已签名的文档已经在本地存储了一段时间(例如在某些归档系统中)并且存储时间已知且可以信任,则可以使用该时间;

  • ...