You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在无完整X509证书时使用PdfPkcs7实现PDF外部签章?

基于土耳其MSSP的PDF外部签章解决方案

你提到的这种通过移动签名服务(MSSP)进行PDF外部签章的场景,在土耳其开发者群体里确实很常见——尤其是受个人数据保护法规限制,无法提前获取签名者完整证书链的情况。结合你已完成的工作,我来一步步拆解可行的实现方案:

现有资源与已完成工作梳理

MSSP Profile查询响应

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  <soap:Body>
    <ns1:MSS_ProfileQueryResponse xmlns:ns1="http://turkcelltech.com/mobilesignature/validation/soap">
      <MSS_ProfileResp xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" xmlns:ns3="http://www.w3.org/2001/04/xmlenc#" xmlns:ns4="http://www.w3.org/2003/05/soap-envelope" xmlns:ns5="http://uri.etsi.org/TS102204/v1.1.2#" MajorVersion="1" MinorVersion="1">
        <ns5:AP_Info AP_ID="http://localhost" AP_TransID="_1264751036088" AP_PWD="secret" Instant="2013-06-12T04:54:43.260Z"/>
        <ns5:MSSP_Info Instant="2020-07-22T18:43:33.723+03:00">
          <ns5:MSSP_ID/>
        </ns5:MSSP_Info>
        <ns5:SignatureProfile>
          <ns5:mssURI>http://uri.turkcellteknoloji.com.tr/MobileSignature/profile2</ns5:mssURI>
          <ns5:CertIssuerDN>C=TR,O=Elektronik Bilgi Guvenligi A.S.,CN=Turkcell Mobil NES Hizmet Saglayicisi S2</ns5:CertIssuerDN>
          <ns5:CertSerialNumber>313460597714010174158784514408553193353</ns5:CertSerialNumber>
          <ns5:CertHash>
            <ns5:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ns5:DigestValue>MVI4FeeToX6TDPLh3h9zEw19ulzTomxPB/zDvnyWzKA=</ns5:DigestValue>
          </ns5:CertHash>
          <ns5:msspUrl>http://localhost</ns5:msspUrl>
          <ns5:certIssuerDN-DER>MGoxCzAJBgNVBAYTAlRSMSgwJgYDVQQKDB9FbGVrdHJvbmlrIEJpbGdpIEd1dmVubGlnaSBBLlMuMTEwLwYDVQQDDChUdXJrY2VsbCBNb2JpbCBORVMgSGl6bWV0IFNhZ2xheWljaXNpIFMy</ns5:certIssuerDN-DER>
        </ns5:SignatureProfile>
        <ns5:Status>
          <ns5:StatusCode Value="100"/>
          <ns5:StatusMessage>REQUEST_OK</ns5:StatusMessage>
        </ns5:Status>
      </MSS_ProfileResp>
    </ns1:MSS_ProfileQueryResponse>
  </soap:Body>
</soap:Envelope>

已实现的SigningCertificateV2创建代码

var certAlgorithmIdentifier = new AlgorithmIdentifier(certHashDigestAlgorithm);
var certHash = Convert.FromBase64String(certHashDigestValue);
var issuer = new GeneralNames(new GeneralName(new X509Name(certIssuerDN)));
var serial = new DerInteger(new BigInteger(certSerialNumber));
var issuerSerial = new IssuerSerial(issuer, serial);
var certIDv2 = new EssCertIDv2(certAlgorithmIdentifier, certHash, issuerSerial);
return new SigningCertificateV2(certIDv2);

对应的ASN.1结构

SEQUENCE (1 elem)
SEQUENCE (1 elem)
SEQUENCE (3 elem)
SEQUENCE (1 elem)
OBJECT IDENTIFIER 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
OCTET STRING (32 byte) 31523815E793A17E930CF2E1DE1F73130D7DBA5CD3A26C4F07FCC3BE7C96CCA0
SEQUENCE (2 elem)
SEQUENCE (1 elem)
[4] (1 elem)
SEQUENCE (3 elem)
SET (1 elem)
SEQUENCE (2 elem)
OBJECT IDENTIFIER 2.5.4.6 countryName (X.520 DN component)
PrintableString TR
SET (1 elem)
SEQUENCE (2 elem)
OBJECT IDENTIFIER 2.5.4.10 organizationName (X.520 DN component)
UTF8String Elektronik Bilgi Guvenligi A.S.
SET (1 elem)
SEQUENCE (2 elem)
OBJECT IDENTIFIER 2.5.4.3 commonName (X.520 DN component)
UTF8String Turkcell Mobil NES Hizmet Saglayicisi S2
INTEGER (128 bit) 313460597714010174158784514408553193353

可用CA证书链

你可以获取到对应的根证书与中间证书(来自土耳其e-guven体系),这部分是后续补全CMS证书链的关键。

MSSP返回的XML签名结构(签名后获取)

<?xml version="1.0" encoding="UTF-8" ?>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm=""/>
<SignatureMethod Algorithm=""/>
<Reference>
<DigestMethod Algorithm=""/>
<DigestValue/>
</Reference>
</SignedInfo>
<SignatureValue> ... </SignatureValue>
<KeyInfo>
<KeyValue>
<RSAKeyValue>
<Modulus> ... </Modulus>
<Exponent> ... </Exponent>
</RSAKeyValue>
</KeyValue>
<X509Data>
<X509Certificate> ... </X509Certificate>
</X509Data>
</KeyInfo>
</Signature>

核心疑问解答与流程验证

你提出的流程是完全可行的,这也是外部签章场景下的标准做法——本地构建CMS容器框架,获取远程签名后补全容器,最终嵌入PDF。下面是细化后的步骤:

具体实现步骤

1. 计算PDF待签内容的摘要

使用iText的PdfSigner先初始化签名结构,获取需要签名的字节范围:

PdfSigner signer = new PdfSigner(new PdfReader(inputPdf), new FileStream(outputPdf, FileMode.Create), new StampingProperties());
signer.SetFieldName("SignatureField");
// 预留足够空间,CMS签名通常需要8-16KB
signer.SetSignatureSize(16384);
// 获取ByteRange对应的待签字节
byte[] bytesToSign = signer.GetDocumentBytesForSigning();
// 计算SHA-256摘要
byte[] pdfDigest = DigestUtilities.CalculateDigest("SHA-256", bytesToSign);

2. 构建初始CMS容器(待签名状态)

因为还没有签名者证书,我们需要先构建包含核心属性的CMS SignedAttributes,其中关键是添加你已经实现的signing-certificate-v2属性:

// 初始化CMS生成器
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();

// 添加SignedAttributes:contentType、messageDigest,以及signing-certificate-v2
AttributeTable signedAttributes = new AttributeTable(new Dictionary<DerObjectIdentifier, Asn1Encodable>
{
    { CmsAttributes.ContentType, new DerObjectIdentifier(PdfName.ApplicationPdf.ToString()) },
    { CmsAttributes.MessageDigest, new DerOctetString(pdfDigest) },
    { EssAttributes.SigningCertificateV2, yourCreatedSigningCertificateV2 }
});

// 创建待签名的CMS结构,这里用一个空的签名者信息占位
generator.AddSignerInfoGenerator(new SignerInfoGeneratorBuilder()
    .WithSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(signedAttributes))
    .Build(new Asn1SignatureFactory("SHA256WITHRSA", null), null));

// 添加CA证书链(根和中间)
X509CertificateParser parser = new X509CertificateParser();
foreach (var cert in new[] { rootCertBytes, intermediateCertBytes })
{
    generator.AddCertificate(parser.ReadCertificate(cert));
}

// 生成待签名的CMS(注意这里传入的是空内容,因为我们只需要属性部分的摘要)
CMSSignedData unsignedCms = generator.Generate(new CMSProcessableByteArray(new byte[0]), false);

// 提取SignedAttributes的DER编码,计算其摘要作为发给MSSP的待签值
SignerInformation signerInfo = unsignedCms.GetSignerInfos().GetSigners().Cast<SignerInformation>().First();
byte[] signedAttributesDer = signerInfo.SignedAttributes.GetDerEncoded();
byte[] msspDigest = DigestUtilities.CalculateDigest("SHA-256", signedAttributesDer);
string msspDigestBase64 = Convert.ToBase64String(msspDigest);

3. 调用MSSP获取签名

将msspDigestBase64发送给MSSP,获取返回的XML签名,从中提取:

  • SignatureValue:签名后的字节数组(Base64解码)
  • X509Certificate:签名者的完整证书(Base64解码后转为BouncyCastle的X509Certificate)

4. 补全CMS容器

把MSSP返回的签名和证书补到之前的CMS结构中:

// 解析签名者证书
X509Certificate signerCert = parser.ReadCertificate(Convert.FromBase64String(xmlSignerCert));

// 创建新的SignerInfo,替换原有的占位签名
SignerInformation newSignerInfo = SignerInformation.ReplaceUnsignedAttributes(signerInfo, new AttributeTable());
newSignerInfo = SignerInformation.ReplaceSignature(newSignerInfo, Convert.FromBase64String(xmlSignatureValue));

// 生成最终的CMS签名数据
CMSSignedData finalCms = CMSSignedData.ReplaceSigners(unsignedCms, new SignerInformationStore(new[] { newSignerInfo }));
// 添加签名者证书到CMS
finalCms = finalCms.AddCertificates(new X509CertificateStore(new[] { signerCert }));

// 获取DER编码的CMS数据
byte[] cmsDer = finalCms.GetEncoded();

5. 完成PDF签章

将CMS数据填入PdfSigner的签名内容:

signer.SignDetached((byte[] message) => msspDigest, cmsDer, null, null, null, 0, PdfSigner.CryptoStandard.CMS);

关键注意事项

  • 签名算法匹配:确保MSSP使用的签名算法(通常是SHA256WithRSA)与你本地设置的一致
  • 证书链完整性:最终CMS必须包含签名者证书、中间证书和根证书,否则PDF阅读器无法验证签名
  • ESS属性正确性:signing-certificate-v2是符合ETSIA和RFC5126要求的关键属性,确保你的ASN.1编码正确

内容的提问来源于stack exchange,提问作者Necati Ozal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 19:37:29