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.
And I do not understand why this is happening.
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 ?
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. Could these be the missing pieces for AdobeReader to reconstruct the trust chain up to the GlobalSign root ?And if so : how do I put them into that PDF ? And if not : then what am I doing wrong that breaks that trustchain ?
An answer to these questions would save my day :-)
Here is the link to a PDF that will show the problem :
test pdf signed with DSS
(after accepting the answer, I removed the example pdf on my client's request)
Below are some pieces of the code.
The center piece that gathers the DSS info and calls the signDetached method
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());
}
The IExternalSignature implementation
#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);
}
}
The IOcspClient implementation
#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;
}
The ITSAClient implementation
#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);
}
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.
I apologize that this is so long. If you are familiar with doing client-auth in Java you can probably skim/skip to the bottom. I took me a long time to find all the relevant bits of information from disparate sources. Maybe this will help someone else.
I'm working on a proof-of-concept to get client-side authentication working from a Java client using the Windows keystore. I have a servlet that I have created that can request or require client certificates. It simply returns an HTML response containing the certificate information including subject DN and the serial number. I've worked through a number of experiments with browsers (mainly Chrome and IE) to verify it is working. I've gotten successful client authentication working with both certs I've generated with SSL and also using certs issued by my companies internal CA.
My next step is to have a Java client that works with the MSCAPI keystore in Java. The reason I went this route is that the certificates we want to use are issued by our internal CA and are automatically added to the Personal keystore in Windows and are marked as non-exportable. I know there are ways to get the private key out of the keystore with some free tools if you are an admin on your workstation. This isn't a viable option in this case for various reasons. Here's the code I ended up with:
private static SSLSocketFactory getFactory() throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException, KeyManagementException
{
KeyStore roots = KeyStore.getInstance("Windows-ROOT", new SunMSCAPI());
KeyStore personal = KeyStore.getInstance("Windows-MY", new SunMSCAPI());
roots.load(null,null);
personal.load(null,null);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(roots);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(personal, "".toCharArray());
SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return context.getSocketFactory();
}
This works but when I connect, the Java client is authenticating with the client-certs I had previously generated. I don't see an obvious way to force the selection of a specific key so I figured it was just picking the first key it came across that the host trusts. I then removed these certs that I had created from the windows keystore and it would no longer authenticate. I triple-checked that the server-side was still works with the browsers and it does. I then readded these certs to the personal keystore and removed the trust of my pseudo-CA from the server-side. Still works in the browser and not in the client.
I added -Djavax.net.debug=ssl:handshake to my VM parameters and started looking through the output. One thing I see is are lines "found key for : [alias]" for each of the certs that I have added but not for the ones added through the 'self-enrollment' feature of certmgr.msc. Initially I thought it was because they were exportable but I removed them and added one back as non-exportable and java can still use it. I'm at a loss as to why SunMSCAPI won't use these certs. Both the ones created by me and internal CA are sha256RSA. Both are enabled for client authentication. When I add the following code, I see that isKeyEntry() is false for all the key-pairs I want to use:
Enumeration<String> aliases = personal.aliases();
while (aliases.hasMoreElements())
{
String alias = aliases.nextElement();
System.out.println(alias + " " + personal.isKeyEntry(alias));
}
I found someone else with a similar issue here but no definitive answer.
I'm trying to generate a certificate from a socket input stream but it seems to get stuck when I use the method form CertificateFactory. The program keeps running in the console when it should end.
Here is the code:
public PublicKey getCertificate() throws IOException, CertificateException
{
String resp= br.readLine();
System.out.println(resp);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
System.out.println("GOING TO CREATE CERTIFICATE");
Certificate certificate = cf.generateCertificate(socket.getInputStream()); //GETS STUCK
System.out.println("SUCCESS");
pw.println(RTA+":"+OK);
return certificate.getPublicKey();
}
The console shows no error whatsoever so I don't know what could be wrong, also I know the socket is getting the information since this is part of a larger communication protocol and the previous communications worked
Whatever you're connected to either isn't sending a complete binary certificate or isn't closing the socket.
I am trying to load & use a self-signed, dynamically generated PEM (or DER) X.509 certificate from a file and use that to load content via HTTPClient or HttpsURLConnection in order to inject it into a WebView.
I have searched for how to use a WebView with a custom certificate, but apparently there is only the possibility to ignore the SSL error completely (which is not what I want to do - I am looking for certificate validation).
Coding along the lines of this Android developers training I always get exceptions from the key functions: TrustManagerFactory, KeyStore and CertificateFactory.
For TrustManagerFactory:
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
-> Unhandled exception type NoSuchAlgorithmException in getInstance()
For KeyStore I tried the syntax from the tutorial as well as syntax as described in the documentation:
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
-> Unhandled exception type KeyStoreException in getInstance()
and
//Init with empty KeyStore
KeyStore keyStoreData;
//Use no protection for key store since it contains public cert
KeyStore.ProtectionParameter keyStoreProtect = null;
//Use default type
String keyStoreType = KeyStore.getDefaultType();
KeyStore.Builder builder = KeyStore.Builder.newInstance(keyStoreType, null, keyStoreProtect);
keyStoreData = builder.getKeyStore();
-> Unhandled exception type KeyStoreException in getKeyStore()
For CertificateFactory:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
-> Unhandled exception type CertificateException
In conclusion, I cannot use any of the functions required for the job because they all fail with exceptions even though I am using default tutorial code (which should be still valid according to documentation).
Switching the IDE (tried Android Studio and Eclipse with ADT), switching from OpenJDK to Oracle JDK, nothing seems to help here.
Also, similar questions on StackOverflow and on the Net are all answered with the use of one or more of these functions - so they don't help me.
If anybody knows what I am doing wrong, I would be glad for any help!
I'm trying to verify a certificate , that was given to me using Java, in the following manner
try{
FileInputStream fr = new FileInputStream(pathtoCertificate);
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate c = (X509Certificate)
cf.generateCertificate(fr);
System.out.println("++++Certificate Verification++++++++");
PublicKey pk = c.getPublicKey();
c.verify(pk);....//63
System.out.println("++This certificate is VALID++");
.....
}
catch(CertificateException e){
e.printStackTrace();
System.out.println("Certificate is Invalid");
}
However, I keep getting the following error
java.security.SignatureException: Signature does not match.
at sun.security.x509.X509CertImpl.verify(Unknown Source)
at sun.security.x509.X509CertImpl.verify(Unknown Source)
at Main.printCertificate(Main.java:63)
at Main.main(Main.java:41)
I've only been provided with a certificate file(which I need to verify), a private key(corresponding to the above file ) and a certificate from a CA.
I'm quite new to Java Security. So any thoughts on how I could verify the certificate file would be great.
Well I finally figured it out. For folks who may reach here in the future.
The reason the error was being thrown is that the certificate in my case was not self signed . Therefore I merely had to use the certificate issued by the CA(as mentioned in my Q) to verify the public key.
The rest remained the same.