如何在 C# 中读取 Verified PDF 签名中的签名者证书(X.509、eIDAS、证书链)

This article was translated from English: Does it need improvement?
Translated
View the article in English

一份合同进入您的审计流程。该合同已进行数字签名,签名有效,而您的合规团队需要确认:谁签署了该合同?签署人当天的证书是否有效?签发证书的 CA 是否在您的受信任列表中?您可以手动提取原始签名字节、解析 DER 格式,并逐级追踪证书链。 或者,您可以向 VerifiedSignature 对象查询其 SignerCertificate,并将答案作为属性读取。

这正是 VerifiedSignature 上的 SignerCertificateCertificateChain 属性所设计的用途。 由 PdfDocument.GetVerifiedSignatures() 返回,它们会显示签名者的完整 X.509 身份信息、签发证书颁发机构 (CA)、有效期、SHA-256 指纹、原始 DER 字节以及所有中间证书(直至根证书)。 这就是您实现 eIDAS 合规性、证书固定、审计追踪以及信任链验证所需的 API 接口。

本指南将详细介绍三类证书检查:

  • 标准 X.509 详细信息:身份属性、完整区分名称、有效期窗口,以及用于 X509Certificate2 互操作的原始 DER 字节。
  • eIDAS 和受监管行业领域:SubjectSerialNumber 中提取签名人的国民身份证号,并读取类似 2.5.4.97 的数字 OID 作为 eIDAS 组织标识符。
  • 信任链验证:SHA-256 指纹固定,并从签名者追溯至根 CA 完整验证整个信任链。

立即开始 30天试用,在您的验证流程中测试证书检查功能。

NuGet 使用 NuGet 安装

PM >  Install-Package IronPdf

IronPDF 上查看 NuGet 快速安装。超过 1000 万次下载,它正以 C# 改变 PDF 开发。 您也可以下载 DLLWindows 安装程序

快速入门:从已签名的 PDF 中读取签名者证书

在已加载的 PDF/A 上调用 GetVerifiedSignatures,并直接从结果中读取签名者的证书属性。

  1. 使用 NuGet 包管理器安装 https://www.nuget.org/packages/IronPdf

    PM > Install-Package IronPdf
  2. 复制并运行这段代码。

    var sig = PdfDocument.FromFile("signed.pdf").GetVerifiedSignatures().First();
    var cert = sig.SignerCertificate;
    Console.WriteLine($"{cert?.CommonName} ({cert?.Organization}) valid until {cert?.NotAfter}");
  3. 部署到您的生产环境中进行测试

    通过免费试用立即在您的项目中开始使用IronPDF

    arrow pointer


如何提取签名人的国民身份证号或个人身份证号?

SubjectSerialNumber 属性返回证书的"主题 DN"中 SERIALNUMBER OID 的值。 在受监管的行业(如 eIDAS 合格证书、银行业、政府颁发的数字身份)中,该字段包含签署人的国民身份证号、税务识别号或个人识别码。 这不是证书本身的序列号; 即 CertificateSerialNumber

:path=/static-assets/pdf/content-code-examples/how-to/verify-pdf-signatures-serial-number.cs
using IronPdf;
using IronPdf.Signing.Inspection;

var pdf = PdfDocument.FromFile("eidas-signed-invoice.pdf");

foreach (var sig in pdf.GetVerifiedSignatures())
{
    var cert = sig.SignerCertificate;
    if (cert is null) continue;

    // Subject DN SERIALNUMBER OID (national ID, tax ID, etc.)
    Console.WriteLine($"Signer Identity (SERIALNUMBER): {cert.SubjectSerialNumber ?? "not present"}");

    // Certificate's own serial from the issuing CA
    Console.WriteLine($"Certificate Serial (CA-issued):  {cert.CertificateSerialNumber}");
}
Imports IronPdf
Imports IronPdf.Signing.Inspection

Dim pdf = PdfDocument.FromFile("eidas-signed-invoice.pdf")

For Each sig In pdf.GetVerifiedSignatures()
    Dim cert = sig.SignerCertificate
    If cert Is Nothing Then Continue For

    ' Subject DN SERIALNUMBER OID (national ID, tax ID, etc.)
    Console.WriteLine($"Signer Identity (SERIALNUMBER): {If(cert.SubjectSerialNumber, "not present")}")

    ' Certificate's own serial from the issuing CA
    Console.WriteLine($"Certificate Serial (CA-issued):  {cert.CertificateSerialNumber}")
Next
$vbLabelText   $csharpLabel

提示关于这两者区别的简要说明:SubjectSerialNumber 告知您文档的签署(个人或组织标识,例如嵌入在"主体DN"中的国民身份证号或税务编号)。 CertificateSerialNumber 告知您使用了哪张证书; (这是签发证书的证书颁发机构为撤销列表和审计日志分配的唯一序列号。

如何访问便捷标识属性?

SignerCertificateInfo 将最常用的"主体 DN"字段作为直接属性提供,因此您无需自行解析完整的 DN 字符串。

读取签名人的姓名和所属机构

CountryEmail 属性涵盖了大多数身份工作流最先关注的内容。

:path=/static-assets/pdf/content-code-examples/how-to/verify-pdf-signatures-identity-common.cs
using IronPdf;
using IronPdf.Signing.Inspection;

var pdf = PdfDocument.FromFile("signed-agreement.pdf");

foreach (var sig in pdf.GetVerifiedSignatures())
{
    var cert = sig.SignerCertificate;
    if (cert is null) continue;

    // Common Subject DN fields exposed directly as properties
    Console.WriteLine($"CommonName (CN):  {cert.CommonName}");
    Console.WriteLine($"Organization (O): {cert.Organization}");
    Console.WriteLine($"Country (C):      {cert.Country}");
    Console.WriteLine($"Email:            {cert.Email}");
}
Imports IronPdf
Imports IronPdf.Signing.Inspection

Dim pdf = PdfDocument.FromFile("signed-agreement.pdf")

For Each sig In pdf.GetVerifiedSignatures()
    Dim cert = sig.SignerCertificate
    If cert Is Nothing Then Continue For

    ' Common Subject DN fields exposed directly as properties
    Console.WriteLine($"CommonName (CN):  {cert.CommonName}")
    Console.WriteLine($"Organization (O): {cert.Organization}")
    Console.WriteLine($"Country (C):      {cert.Country}")
    Console.WriteLine($"Email:            {cert.Email}")
Next
$vbLabelText   $csharpLabel

Email 属性首先检查"主体 DN"中是否包含 emailAddress OID。 若不存在,则回退至主题替代名称 (SAN) rfc822Name 扩展名。 Country 属性返回两个字母的 ISO 3166-1 alpha-2 代码(例如"DE"、"US"、"FR")。 如果证书中不存在相应的字段,所有便捷属性均返回 null

当您需要全面了解时,请阅读完整的DN

对于审计日志、下游解析或合规报告,SubjectDNIssuerDN 将返回完整的区分名称字符串。

:path=/static-assets/pdf/content-code-examples/how-to/verify-pdf-signatures-identity-full-dn.cs
using IronPdf;
using IronPdf.Signing.Inspection;

var pdf = PdfDocument.FromFile("signed-agreement.pdf");

foreach (var sig in pdf.GetVerifiedSignatures())
{
    var cert = sig.SignerCertificate;
    if (cert is null) continue;

    // Full DN strings for audit logging or downstream parsing
    Console.WriteLine($"Full Subject DN: {cert.SubjectDN}");
    Console.WriteLine($"Full Issuer DN:  {cert.IssuerDN}");
}
Imports IronPdf
Imports IronPdf.Signing.Inspection

Dim pdf = PdfDocument.FromFile("signed-agreement.pdf")

For Each sig In pdf.GetVerifiedSignatures()
    Dim cert = sig.SignerCertificate
    If cert Is Nothing Then Continue For

    ' Full DN strings for audit logging or downstream parsing
    Console.WriteLine($"Full Subject DN: {cert.SubjectDN}")
    Console.WriteLine($"Full Issuer DN:  {cert.IssuerDN}")
Next
$vbLabelText   $csharpLabel

如何提取任意"主题"和"发件人"字段?

GetSubjectField(string)GetIssuerField(string) 方法接受短名称或数字 OID。 它们不区分大小写,若字段不存在则返回 null

支持的短名称:CN, O, OU, C, L, ST, E, SERIALNUMBER, SURNAME, T, GIVENNAME, UID, ORGANIZATIONIDENTIFIER.

读取任意主题字段(包括 eIDAS OID)

传递一个简短名称(如 "OU")或一个数字 OID(如 "2.5.4.97",即 eIDAS 组织标识符),即可从主体 DN 中提取任意字段。

:path=/static-assets/pdf/content-code-examples/how-to/verify-pdf-signatures-field-subject.cs
using IronPdf;
using IronPdf.Signing.Inspection;

var pdf = PdfDocument.FromFile("eidas-contract.pdf");

foreach (var sig in pdf.GetVerifiedSignatures())
{
    var cert = sig.SignerCertificate;
    if (cert is null) continue;

    // Short-name lookups (case-insensitive)
    string? orgUnit = cert.GetSubjectField("OU");
    string? surname = cert.GetSubjectField("SURNAME");
    string? givenName = cert.GetSubjectField("GIVENNAME");
    string? title = cert.GetSubjectField("T");

    // Numeric OID lookup (eIDAS organizationIdentifier)
    string? orgId = cert.GetSubjectField("2.5.4.97");

    Console.WriteLine($"Name: {givenName} {surname} ({title}), OU: {orgUnit}");
    Console.WriteLine($"eIDAS Org ID (2.5.4.97): {orgId ?? "not present"}");
}
Imports IronPdf
Imports IronPdf.Signing.Inspection

Dim pdf = PdfDocument.FromFile("eidas-contract.pdf")

For Each sig In pdf.GetVerifiedSignatures()
    Dim cert = sig.SignerCertificate
    If cert Is Nothing Then Continue For

    ' Short-name lookups (case-insensitive)
    Dim orgUnit As String = cert.GetSubjectField("OU")
    Dim surname As String = cert.GetSubjectField("SURNAME")
    Dim givenName As String = cert.GetSubjectField("GIVENNAME")
    Dim title As String = cert.GetSubjectField("T")

    ' Numeric OID lookup (eIDAS organizationIdentifier)
    Dim orgId As String = cert.GetSubjectField("2.5.4.97")

    Console.WriteLine($"Name: {givenName} {surname} ({title}), OU: {orgUnit}")
    Console.WriteLine($"eIDAS Org ID (2.5.4.97): {If(orgId, "not present")}")
Next
$vbLabelText   $csharpLabel

数字 OID 查询对于 eIDAS 证书以及受监管行业的证书至关重要,这些证书嵌入了组织标识符、专业资质或标准短名称未涵盖的特定管辖区域字段。

读取发行人的字段

GetIssuerField 针对签发证书颁发机构(CA)的标识名(Distinguished Name)同样适用。 这对于信任验证、CA 报告以及按签发机构对签名进行分组非常有用。

:path=/static-assets/pdf/content-code-examples/how-to/verify-pdf-signatures-field-issuer.cs
using IronPdf;
using IronPdf.Signing.Inspection;

var pdf = PdfDocument.FromFile("eidas-contract.pdf");

foreach (var sig in pdf.GetVerifiedSignatures())
{
    var cert = sig.SignerCertificate;
    if (cert is null) continue;

    // GetIssuerField works the same way as GetSubjectField
    string? issuerCN = cert.GetIssuerField("CN");
    string? issuerO = cert.GetIssuerField("O");
    string? issuerCountry = cert.GetIssuerField("C");

    Console.WriteLine($"Issued by: {issuerCN} ({issuerO}, {issuerCountry})");
}
Imports IronPdf
Imports IronPdf.Signing.Inspection

Dim pdf = PdfDocument.FromFile("eidas-contract.pdf")

For Each sig In pdf.GetVerifiedSignatures()
    Dim cert = sig.SignerCertificate
    If cert Is Nothing Then Continue For

    ' GetIssuerField works the same way as GetSubjectField
    Dim issuerCN As String = cert.GetIssuerField("CN")
    Dim issuerO As String = cert.GetIssuerField("O")
    Dim issuerCountry As String = cert.GetIssuerField("C")

    Console.WriteLine($"Issued by: {issuerCN} ({issuerO}, {issuerCountry})")
Next
$vbLabelText   $csharpLabel

如何验证证书的有效性?

SignerCertificateInfo 提供了两个有效性检查辅助方法:

  • IsExpiredDateTime.UtcNow > NotAfter 的情况下返回 true
  • 如果给定的时间戳落在 NotAfter 时间窗口内,IsValidAt(DateTime) 将返回 true。 典型用法是确认证书在签名时是否有效。
:path=/static-assets/pdf/content-code-examples/how-to/verify-pdf-signatures-validity.cs
using IronPdf;
using IronPdf.Signing.Inspection;

var pdf = PdfDocument.FromFile("archived-contract.pdf");

foreach (var sig in pdf.GetVerifiedSignatures())
{
    var cert = sig.SignerCertificate;
    if (cert is null) continue;

    Console.WriteLine($"Valid from:  {cert.NotBefore}");
    Console.WriteLine($"Valid until: {cert.NotAfter}");
    Console.WriteLine($"Expired now: {cert.IsExpired}");

    // Confirm certificate was valid when the document was signed
    if (sig.SigningDate.HasValue)
    {
        bool validAtSigning = cert.IsValidAt(sig.SigningDate.Value);
        Console.WriteLine($"Valid at signing time ({sig.SigningDate.Value}): {validAtSigning}");
    }
}
Imports IronPdf
Imports IronPdf.Signing.Inspection

Dim pdf = PdfDocument.FromFile("archived-contract.pdf")

For Each sig In pdf.GetVerifiedSignatures()
    Dim cert = sig.SignerCertificate
    If cert Is Nothing Then Continue For

    Console.WriteLine($"Valid from:  {cert.NotBefore}")
    Console.WriteLine($"Valid until: {cert.NotAfter}")
    Console.WriteLine($"Expired now: {cert.IsExpired}")

    ' Confirm certificate was valid when the document was signed
    If sig.SigningDate.HasValue Then
        Dim validAtSigning As Boolean = cert.IsValidAt(sig.SigningDate.Value)
        Console.WriteLine($"Valid at signing time ({sig.SigningDate.Value}): {validAtSigning}")
    End If
Next
$vbLabelText   $csharpLabel

提示IsExpired 返回 false(而非 true),当无法从证书中提取有效期时。 该辅助程序采用保守策略;它不会仅因元数据缺失就推定其已过期。 对于零信任工作流,应将缺少有效期明确视为失败情况。

对于审计追踪,需同时记录 IsExpired 检查(当前有效性)和 IsValidAt(signingDate) 检查(签名时的有效性)。 一份现已过期的证书,在签署该文件时可能仍处于有效状态; 这两个数据点对合规性都至关重要。

如何使用 SHA-256 指纹进行证书固定?

Sha256Thumbprint 属性返回一个 64 字符的大写十六进制字符串,即 DER 编码证书的 SHA-256 哈希值。 这是您将其与已知有效的受信任签名证书列表进行比对所获得的值。

:path=/static-assets/pdf/content-code-examples/how-to/verify-pdf-signatures-thumbprint-pinning.cs
using IronPdf;
using IronPdf.Signing.Inspection;
using System.Collections.Generic;

// Known-good signer thumbprints from a compliance database
var pinnedThumbprints = new HashSet<string>
{
    "A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2",
    "F6E5D4C3B2A1F6E5D4C3B2A1F6E5D4C3B2A1F6E5D4C3B2A1F6E5D4C3B2A1F6E5"
};

var pdf = PdfDocument.FromFile("submitted-form.pdf");

foreach (var sig in pdf.GetVerifiedSignatures())
{
    var cert = sig.SignerCertificate;
    if (cert is null) continue;

    // Pin against trusted thumbprint list
    bool trusted = pinnedThumbprints.Contains(cert.Sha256Thumbprint);
    Console.WriteLine($"Signer: {cert.CommonName}, Thumbprint: {cert.Sha256Thumbprint}");
    Console.WriteLine($"Pinned: {trusted}");
}
Imports IronPdf
Imports IronPdf.Signing.Inspection
Imports System.Collections.Generic

' Known-good signer thumbprints from a compliance database
Dim pinnedThumbprints As New HashSet(Of String) From {
    "A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2",
    "F6E5D4C3B2A1F6E5D4C3B2A1F6E5D4C3B2A1F6E5D4C3B2A1F6E5D4C3B2A1F6E5"
}

Dim pdf = PdfDocument.FromFile("submitted-form.pdf")

For Each sig In pdf.GetVerifiedSignatures()
    Dim cert = sig.SignerCertificate
    If cert Is Nothing Then Continue For

    ' Pin against trusted thumbprint list
    Dim trusted As Boolean = pinnedThumbprints.Contains(cert.Sha256Thumbprint)
    Console.WriteLine($"Signer: {cert.CommonName}, Thumbprint: {cert.Sha256Thumbprint}")
    Console.WriteLine($"Pinned: {trusted}")
Next
$vbLabelText   $csharpLabel

如何从原始证书数据构建 X509Certificate2?

RawData 属性返回以 DER 编码的证书,格式为 byte[]。 每次调用都会返回一个防御性副本,因此内部数据绝不会通过引用暴露。 您可以使用此功能构建 System.Security.Cryptography.X509Certificates.X509Certificate2,以实现与 .NET 原生加密 API 的互操作(包括证书链构建、策略验证和 CRL 检查)。

:path=/static-assets/pdf/content-code-examples/how-to/verify-pdf-signatures-x509-interop.cs
using IronPdf;
using IronPdf.Signing.Inspection;
using System.Security.Cryptography.X509Certificates;

var pdf = PdfDocument.FromFile("signed-report.pdf");

foreach (var sig in pdf.GetVerifiedSignatures())
{
    var cert = sig.SignerCertificate;
    if (cert is null) continue;

    // Construct X509Certificate2 from defensive DER copy
    byte[] derBytes = cert.RawData;
    var x509 = new X509Certificate2(derBytes);

    Console.WriteLine($"X509 Subject:    {x509.Subject}");
    Console.WriteLine($"X509 Issuer:     {x509.Issuer}");
    Console.WriteLine($"X509 Serial:     {x509.SerialNumber}");
    Console.WriteLine($"X509 Algorithm:  {x509.SignatureAlgorithm.FriendlyName}");
    Console.WriteLine($"X509 Key Size:   {x509.PublicKey.Key.KeySize} bits");
}
Imports IronPdf
Imports IronPdf.Signing.Inspection
Imports System.Security.Cryptography.X509Certificates

Dim pdf = PdfDocument.FromFile("signed-report.pdf")

For Each sig In pdf.GetVerifiedSignatures()
    Dim cert = sig.SignerCertificate
    If cert Is Nothing Then Continue For

    ' Construct X509Certificate2 from defensive DER copy
    Dim derBytes As Byte() = cert.RawData
    Dim x509 = New X509Certificate2(derBytes)

    Console.WriteLine($"X509 Subject:    {x509.Subject}")
    Console.WriteLine($"X509 Issuer:     {x509.Issuer}")
    Console.WriteLine($"X509 Serial:     {x509.SerialNumber}")
    Console.WriteLine($"X509 Algorithm:  {x509.SignatureAlgorithm.FriendlyName}")
    Console.WriteLine($"X509 Key Size:   {x509.PublicKey.Key.KeySize} bits")
Next
$vbLabelText   $csharpLabel

请注意每次访问 RawData 都会返回一个新的防御性副本。 内部字节数组绝不会通过引用暴露,因此修改返回的字节数组不会影响后续调用。)}]

此互操作路径允许您将 IronPDF 的签名验证集成到现有的 .NET 证书验证管道中,例如 X509Chain.Build()、通过 CRL/OCSP 进行的吊销检查,或自定义的 X509CertificateValidator 实现。

如何遍历证书链?

CertificateChain 属性在 VerifiedSignature 上返回一个按签名人 → 中间 CA → 根 CA 顺序排列的 IReadOnlyList<SignerCertificateInfo>。 索引 0 始终是签名者证书(与 SignerCertificate 相同的对象)。 该列表绝非空集; 如果无法提取证书链,则该证书仅包含签名者证书。

:path=/static-assets/pdf/content-code-examples/how-to/verify-pdf-signatures-chain-walking.cs
using IronPdf;
using IronPdf.Signing.Inspection;

var pdf = PdfDocument.FromFile("enterprise-signed.pdf");

foreach (var sig in pdf.GetVerifiedSignatures())
{
    Console.WriteLine($"--- Signature: {sig.SignatureName} ---");
    Console.WriteLine($"Chain length: {sig.CertificateChain.Count}");

    // Walk from signer (index 0) through intermediates to root CA
    for (int i = 0; i < sig.CertificateChain.Count; i++)
    {
        var cert = sig.CertificateChain[i];
        string role = i == 0 ? "Signer"
                    : i == sig.CertificateChain.Count - 1 ? "Root CA"
                    : $"Intermediate CA ({i})";

        Console.WriteLine($"  [{role}]");
        Console.WriteLine($"    Subject:     {cert.SubjectDN}");
        Console.WriteLine($"    Issuer:      {cert.IssuerDN}");
        Console.WriteLine($"    Serial:      {cert.CertificateSerialNumber}");
        Console.WriteLine($"    Valid:       {cert.NotBefore} to {cert.NotAfter}");
        Console.WriteLine($"    Expired:     {cert.IsExpired}");
        Console.WriteLine($"    Thumbprint:  {cert.Sha256Thumbprint}");
    }
}
Imports IronPdf
Imports IronPdf.Signing.Inspection

Dim pdf = PdfDocument.FromFile("enterprise-signed.pdf")

For Each sig In pdf.GetVerifiedSignatures()
    Console.WriteLine($"--- Signature: {sig.SignatureName} ---")
    Console.WriteLine($"Chain length: {sig.CertificateChain.Count}")

    ' Walk from signer (index 0) through intermediates to root CA
    For i As Integer = 0 To sig.CertificateChain.Count - 1
        Dim cert = sig.CertificateChain(i)
        Dim role As String = If(i = 0, "Signer", If(i = sig.CertificateChain.Count - 1, "Root CA", $"Intermediate CA ({i})"))

        Console.WriteLine($"  [{role}]")
        Console.WriteLine($"    Subject:     {cert.SubjectDN}")
        Console.WriteLine($"    Issuer:      {cert.IssuerDN}")
        Console.WriteLine($"    Serial:      {cert.CertificateSerialNumber}")
        Console.WriteLine($"    Valid:       {cert.NotBefore} to {cert.NotAfter}")
        Console.WriteLine($"    Expired:     {cert.IsExpired}")
        Console.WriteLine($"    Thumbprint:  {cert.Sha256Thumbprint}")
    Next
Next
$vbLabelText   $csharpLabel

请注意链中的每个 SignerCertificateInfo 都暴露了与 SignerCertificate 相同的属性:标识字段、有效性、指纹、GetSubjectField() 以及 GetIssuerField()。 您可以验证每个中间证书的过期时间,将根证书的指纹与您的受信任根证书存储库进行比对,并记录完整的证书链以供合规性审计。)]

下一步

SignerCertificateCertificateChain 可在不离开 IronPDF API 接口的情况下,为您提供完整的 X.509 证书检查功能。 接下来,我们自然会深入探讨这些组件在 IronPDF 中的具体集成部分。

对于工作流中的签名环节,数字签名指南涵盖了证书创建、元数据以及增量签名等内容。 对于高安全环境中的基于 HSM 的签名,HSM 签名指南涵盖了 PKCS#11 集成。 需要可直接运行的代码片段吗? 数字签名代码示例提供了这些内容。

准备好在您自己的签名 PDF 上试用了吗? 立即开始 30天试用查看许可选项

Curtis Chau
技术作家

Curtis Chau 拥有卡尔顿大学的计算机科学学士学位,专注于前端开发,精通 Node.js、TypeScript、JavaScript 和 React。他热衷于打造直观且美观的用户界面,喜欢使用现代框架并创建结构良好、视觉吸引力强的手册。

除了开发之外,Curtis 对物联网 (IoT) 有浓厚的兴趣,探索将硬件和软件集成的新方法。在空闲时间,他喜欢玩游戏和构建 Discord 机器人,将他对技术的热爱与创造力相结合。

准备开始了吗?
Nuget 下载 18,918,602 | 版本: 2026.5 just released
Still Scrolling Icon

还在滚动吗?

想快速获得证据? PM > Install-Package IronPdf
运行示例看着你的HTML代码变成PDF文件。