英文:
iText7 pdf signing with GlobalSign DSS AATL certificate shows broken trustchain in Adobe Reader
问题
我使用iText7和GlobalSign DSS对PDF进行数字签名。我将GlobalSign DSS API调用实现到必要的iText类中。我收到了正确的服务器响应,我可以使用所有必要的参数调用pdfSigner.signDetached()方法。使用pdfSigner进行签名也成功了,我得到了一份乍一看很好的已签名PDF文件。但是,当我在Adobe Reader中打开已签名的PDF时,它告诉我签名证书的信任链已损坏,无法将其追溯到CA根证书。这很奇怪,因为它是AATL证书,而Adobe Reader的AATL列表是最新的。
我不明白为什么会出现这种情况。
我做了以下操作:
- 调用DSS以获取身份信息:返回一个id字符串、签名证书和ocsp响应
- 调用DSS以获取信任链:返回用于签署签名证书的证书链,一直到GlobalSign根证书,以及它们的ocsp响应(除了根证书)
- 我创建了一个包含签名证书、2个中间证书和GlobalSign根证书的X509Certificate对象数组(按照顺序)
- 我实现了一个IOcspClient,使用DSS调用的身份的ocsp响应
- 我实现了一个ITsaClient,调用DSS API的/timestamp/{digest}端点
- 最后,我执行了:pdfSigner.signDetached(externalDigest, externalSignature, chain.toArray(new X509Certificate[]{}), null, dssOcspClient, dssTSAClient, 0, PdfSigner.CryptoStandard.CMS);
- 在这个过程中,externalSignature(IExternalSignature的实现)将调用DSS的identity/{id}/sign/{digest} API
在调试signDetached方法以及更深入pdfSigner代码时,我清楚地看到所有证书按照正确的顺序在链中。我看到它们在PdfPKCS7类中被处理(尽管我不知道/理解其中的具体情况)。我看到签名正在进行,没有抛出异常,最后生成的PDF文件看起来是正确签名的。但Adobe表示不是。
我在这里漏掉了什么?
来自DSS API的信任链响应不仅返回签名证书信任链上的证书,还返回两个中间证书和GlobalSign根证书之间的ocsp响应(根证书除外)。这些从未被使用过。实际上,我也不知道如何处理它们。这些可能是Adobe Reader重建信任链直到GlobalSign根证书所需的缺失部分吗?如果是这样的话,我该如何将它们放入PDF中?如果不是,那么我做错了什么导致信任链中断?
回答这些问题将解救我的一天
以下是一些代码片段。
汇集DSS信息并调用signDetached方法的核心部分
private InputStream sign(byte[] unsignedDocument) throws IOException, DssServiceException, GeneralSecurityException {
SigningIdentity signingIdentity = signingIdentityService.getValidSigningIdentity();
DssOcspClient dssOcspClient = new DssOcspClient(signingIdentity);
TrustChainResponse trustChainResponse = digitalSigningService.getTrustChain();
List<X509Certificate> chain = new ArrayList<>();
chain.add(signingIdentity.getCertificate());
chain.addAll(trustChainResponse.getTrustChain());
IExternalDigest externalDigest = new ProviderDigest(BC_SECURITY_PROVIDER);
IExternalSignature externalSignature = new DssExternalSignature(signingIdentity.getIdentity(), digitalSigningService);
ByteArrayOutputStream signedPdfOut = new ByteArrayOutputStream();
PdfSigner pdfSigner = createPdfSigner(new ByteArrayInputStream(unsignedDocument), signedPdfOut);
pdfSigner.signDetached(externalDigest, externalSignature, chain.toArray(new X509Certificate[]{}), null, dssOcspClient, dssTSAClient, 0, PdfSigner.CryptoStandard.CADES);
return new ByteArrayInputStream(signedPdfOut.toByteArray());
}
IExternalSignature实现
@Override
public byte[] sign(byte[] message) throws GeneralSecurityException {
MessageDigest messageDigest = new BouncyCastleDigest().getMessageDigest(DEFAULT_DIGEST_ALGORITHM);
byte[] documentHash = messageDigest.digest(message);
try {
return digitalSigningService.getSignature(signingIdentity, documentHash);
}
catch (DssServiceException e) {
LOGGER.error("error getting signature", e);
throw new GeneralSecurityException(e);
}
}
IOcspClient实现
@Override
public byte[] getEncoded(X509Certificate checkCert, X509Certificate issuerCert, String url) {
try {
if (Objects.equals(signingIdentity.getCertificate(), checkCert)) {
OCSPResp response = new OCSPResp(signingIdentity.getOcsp());
BasicOCSPResp basicResponse = (BasicOCSPResp) response.getResponseObject();
return basicResponse.getEncoded();
}
} catch (CertificateException | IOException | OCSPException e) {
LOGGER.warn("OCSP validatie gefaald!", e.getMessage());
}
return null;
}
ITSAClient实现
@Override
public byte[] getTimeStampToken(byte[] imprint) throws Exception {
String digestAlgorithmOID = DigestAlgorithms.getAllowedDigest(DEFAULT_DIGEST_ALGORITHM);
ASN1ObjectIdentifier digestAlgOID = new ASN1ObjectIdentifier(digestAlgorithmOID);
AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, DERNull.INSTANCE);
MessageImprint messageImprint = new MessageImprint(algID, imprint);
byte[] hash = messageImprint.getHashedMessage();
return digitalSigningService.getTimeStamp(hash);
}
英文:
I am digitally signing a PDF with iText7 and GlobalSign DSS. I implemented the GlobalSing DSS API calls into the necessary iText classes. I get the proper server responses and I am able to call the pdfSigner.signDetached() method with all the needed arguments. Signing with the pdfSigner also succeeds and I get a signed PDF that looks good at first sight. But when I open the signed pdf in Adobe Reader it tells me that the trust chain of the signing certificate is broken and that it can not trace it back to the CA root. Which is strange because it is an AATL certificate and the AATL list of the Adobe Reader is up to date.<br/>
And I do not understand why this is happening.<br/><br/>
This is what I do :
-
call DSS for an identity : returns an id string, the signing certificate
and an ocsp response -
call DSS for the trustchain : returns the chain of certificates used to
sign the signing certicate, up to the GlobalSign root, together with
their oscp responses (except for the root) -
I create an array of X509Certificate objects containing the signing
certificate, 2 intermediates and the GlobalSign root certificate (in
that order) -
I implement an IOcspClient that uses the ocsp response from the DSS call
for the identity -
I implement an ITsaClient that calls the DSS API /timestamp/{digest}
-
and finally I execute : pdfSigner.signDetached(externalDigest, externalSignature, chain.toArray(new X509Certificate[]{}), null, dssOcspClient, dssTSAClient, 0, PdfSigner.CryptoStandard.CMS);
-
in which the externalSignature (an implementation of
IExternalSignature) will call the DSS identity/{id}/sign/{digest} API
While debugging into the signDetached method and deeper into the pdfSigner code, I clearly see that all certificates are in the chain in the right order. I see them being processed in the PdfPKCS7 class (however I don't know/understand exactly what is going on there). I see the signing taking place, no exceptions are thrown and at the end the produced PDF looks like it is correctly signed. Which Adobe says is not.
What am I missing here ?<br/><br/>
The trustchain response from de DSS API not only returns the certificates from the chain of trust of the signing certificate, but also the ocsp responses for the two intermediates between the signing certificate and the GlobalSign root. These are never used. And in fact I don't know what to do with them either.<br/> Could these be the missing pieces for AdobeReader to reconstruct the trust chain up to the GlobalSign root ?<br/>And if so : how do I put them into that PDF ?<br/> And if not : then what am I doing wrong that breaks that trustchain ?<br/><br/>
An answer to these questions would save my day :-)<br/><br/>
Here is the link to a PDF that will show the problem : <br/>
test pdf signed with DSS<br/>
(after accepting the answer, I removed the example pdf on my client's request)<br/>
<br/>Below are some pieces of the code.<br/><br/>
The center piece that gathers the DSS info and calls the signDetached method<br/>
private InputStream sign(byte[] unsignedDocument) throws IOException, DssServiceException, GeneralSecurityException {
SigningIdentity signingIdentity = signingIdentityService.getValidSigningIdentity();
DssOcspClient dssOcspClient = new DssOcspClient(signingIdentity);
TrustChainResponse trustChainResponse = digitalSigningService.getTrustChain();
List<X509Certificate> chain = new ArrayList<>();
chain.add(signingIdentity.getCertificate());
chain.addAll(trustChainResponse.getTrustChain());
IExternalDigest externalDigest = new ProviderDigest(BC_SECURITY_PROVIDER);
IExternalSignature externalSignature = new DssExternalSignature(signingIdentity.getIdentity(), digitalSigningService);
ByteArrayOutputStream signedPdfOut = new ByteArrayOutputStream();
PdfSigner pdfSigner = createPdfSigner(new ByteArrayInputStream(unsignedDocument), signedPdfOut);
pdfSigner.signDetached(externalDigest, externalSignature, chain.toArray(new X509Certificate[]{}), null, dssOcspClient, dssTSAClient, 0, PdfSigner.CryptoStandard.CADES);
return new ByteArrayInputStream(signedPdfOut.toByteArray());
}
<br/>
The IExternalSignature implementation<br/>
@Override
public byte[] sign(byte[] message) throws GeneralSecurityException {
MessageDigest messageDigest = new BouncyCastleDigest().getMessageDigest(DEFAULT_DIGEST_ALGORITHM);
byte[] documentHash = messageDigest.digest(message);
try {
return digitalSigningService.getSignature(signingIdentity, documentHash);
}
catch (DssServiceException e) {
LOGGER.error("error getting signature", e);
throw new GeneralSecurityException(e);
}
}
<br/>
The IOcspClient implementation<br/>
@Override
public byte[] getEncoded(X509Certificate checkCert, X509Certificate issuerCert, String url) {
try {
if(Objects.equals(signingIdentity.getCertificate(), checkCert)) {
OCSPResp response = new OCSPResp(signingIdentity.getOcsp());
BasicOCSPResp basicResponse = (BasicOCSPResp)response.getResponseObject();
return basicResponse.getEncoded();
}
}
catch (CertificateException | IOException | OCSPException e) {
LOGGER.warn("OCSP validatie gefaald!", e.getMessage());
}
return null;
}
<br/>
The ITSAClient implementation<br/>
@Override
public byte[] getTimeStampToken(byte[] imprint) throws Exception {
String digestAlgorithmOID = DigestAlgorithms.getAllowedDigest(DEFAULT_DIGEST_ALGORITHM);
ASN1ObjectIdentifier digestAlgOID = new ASN1ObjectIdentifier(digestAlgorithmOID);
AlgorithmIdentifier algID = new AlgorithmIdentifier(digestAlgOID, DERNull.INSTANCE);
MessageImprint messageImprint = new MessageImprint(algID, imprint);
byte[] hash = messageImprint.getHashedMessage();
return digitalSigningService.getTimeStamp(hash);
}
答案1
得分: 2
简言之
您的签名证书无效。
详细描述
您的签名证书及其证书链(根据颁发者/主题匹配)嵌入在签名中,特别是具有以下主题的证书:
> cn=Homologatie Voertuigen,
ou=Departement Mobiliteit en Openbare Werken,
ou=Vlaams Huis voor de Verkeersveiligheid,
o=Ministeries van de Vlaamse Gemeenschap,
l=Brussel,
st=Brussel,
c=BE
以及其声明的颁发者
> cn=GlobalSign CA 5 for AATL,
o=GlobalSign nv-sa,
c=BE
因此,可以使用签名来检查签署您的证书的签名。在这样做的过程中,可以看到您的签名证书(待签名部分)的TBSCertificate
部分具有此摘要值
C8751FDC7F679CB627F61028ACDD0D09613AFA782412ACFC7E189EA5DA625831
但实际上,签名确实签署了此摘要值
16090737B41E6E0466E7EB7A7EBD79F5494E438C11D0FB408BCA663A5923AD03
因此,您的签名证书未正确签名。
这意味着什么
在一条评论中,您问道
> 但我对其确切含义有点困惑。在签名期间我们是否做错了什么?是将错误的文档哈希发送到签名服务器?还是您的GlobalSign颁发的服务器端签名证书存在问题,用于签署文档哈希?
在签名期间,您没有做错任何事情,至少我不这么认为。损坏的签名不是签署文档的签名,而是您的CA签署您的证书的签名。
我基本上看到三个可能的原因:
-
证书签名可能简单地损坏了,并且在任何地方都不匹配您的证书。
这会让我感到惊讶。
-
证书签名可能不是针对DER编码形式的待签名证书部分计算的,而是针对其他形式。
这并不罕见,如果您的证书最初不是以DER形式存在,但证书签署过程假定它是DER形式,可能已签署了非DER形式(尽管根据规范,必须签署DER形式)。然后,如果某个验证器检查了签名,它也不会确保DER形式,而是将TBSCertificate原样采用,该验证器甚至会指示签名是有效的。
在嵌入PDF签名中的证书中,待签名部分是DER编码的,但这可能是在初始证书生成之后的某个步骤中强制执行的。
-
创建后可能对您的证书进行了微小的更改。
这也是可能的。
您可以尝试从您的CA那里获得一份尽可能接近原始形式的证书副本,并将其与嵌入在您的签名中的证书进行比较。如果发现差异,那么分析这些差异很可能会进一步阐明问题的原因。
英文:
In short
Your signer certificate is invalid.
In detail
Your signer certificate and its certificate chain (according to issuer/subject match) are embedded in the signature, in particular your certificate with subject
> cn=Homologatie Voertuigen,
ou=Departement Mobiliteit en Openbare Werken,
ou=Vlaams Huis voor de Verkeersveiligheid,
o=Ministeries van de Vlaamse Gemeenschap,
l=Brussel,
st=Brussel,
c=BE
and its claimed issuer
> cn=GlobalSign CA 5 for AATL,
o=GlobalSign nv-sa,
c=BE
Thus, one can check the signature with which your certificate is signed. And while doing so one sees that the TBSCertificate
part of your signer certificate (the to-be-signed part) has this digest value
C8751FDC7F679CB627F61028ACDD0D09613AFA782412ACFC7E189EA5DA625831
but the signature actually signs this digest value
16090737B41E6E0466E7EB7A7EBD79F5494E438C11D0FB408BCA663A5923AD03
Thus, your signer certificate is not correctly signed.
What does this mean
In a comment you ask
> But I am a little confused about what it means exactly. Are we actually doing something wrong during signing, sending the wrong document hash to the signing server ? Or do you mean there is something wrong with the server side signing certificate issued by GlobalSign that they use to sign that document hash?
You're not doing anything wrong during signing, at least I don't think so. The broken signature is not the signature signing the document but its the signature signing your certificate by your CA.
I see essentially three possible reasons for that:
-
The certificate signature simply is broken and doesn't match your certificate anywhere, anyhow.
This would surprise me.
-
The certificate signature has been calculated not for the DER encoded form of your to-be-signed certificate part but some other form.
This is not unheard of, if your certificate originally was not in DER form but the certificate signing process assumed it to be, a non-DER form may have been signed (even though according to specification the DER form has to be signed). If some validator then checked the signature which also does not ensure DER form but takes the TBSCertificate as is, that validator would even have indicated that the signature was valid.
In the certificate as embedded in the PDF signature the to-be-signed part is DER encoded but this may have been enforced at some step after the initial certificate generation.
-
Some minute change may have happened to your certificate after creation.
This also is possible.
You could try and receive a copy of your certificate from your CA in a form that is as original as possible and compare to the certificate that is embedded in your signature. If you find differences, then analyzing the differences most likely will further illuminate the cause of the problem.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论