I've just signed a document. The signer certificate has one issuer intermediate certificate and intermediate's issuer is the root certificate.
I sign document and I see the full chain of that certificate. But when I see in PDF explorer, there is only Signer certificate:
I need that certificate chain. I'm going to get them using PDFbox, but I have no idea where it is.
You can find code to show the certificate chain in the ShowSignature.java example in the examples subproject of the PDFBox source code download or in the repository.
Here's some code reduced from there, which is only applicable for certain signature subtypes (adbe.pkcs7.detached and ETSI.CAdES.detached, the others can be seen in the mentioned example):
try (PDDocument document = PDDocument.load(file))
{
for (PDSignature sig : document.getSignatureDictionaries())
{
COSDictionary sigDict = sig.getCOSObject();
COSString contents = (COSString) sigDict.getDictionaryObject(COSName.CONTENTS);
CMSSignedData signedData = new CMSSignedData(contents.getBytes());
Store<X509CertificateHolder> certificatesStore = signedData.getCertificates();
JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter();
Collection<X509CertificateHolder> matches = certificatesStore.getMatches(null);
System.out.println("Certificates in chain: " + matches.size());
System.out.println();
int n = 0;
for (X509CertificateHolder certificateHolder : matches)
{
++n;
X509Certificate certificate = certificateConverter.getCertificate(certificateHolder);
System.out.println("Certificate " + n + ":");
System.out.println(certificate);
System.out.println();
}
}
}
The ShowSignature.java example has much more to offer: it checks the signature validity, whether the signature covers the whole document and (new in 2.0.13) checks the whole certificate chain including revocation (OCSP or CRL).
Related
still being a noob in cryptography I stumble upon simple things every day. And today is just one of those days.
I want to validate smime messages in java with the bouncy castle library, and I think I almost figured it out, but the problem at this moment is the building of the PKIXparameters object.
Let's say, I have an end-entity x509certificate with the following structure:
root certificate
+->intermediate certificate
+->end-entity certificate
In order to validate message I need to build a chain of trust first, but I cannot figure out how to extract a root and intermediate certificates from the end-entity.
I tried to use end-entity as root but it didn't work:
InputStream isCert = GetFISCertificate();
List list = new ArrayList();
X509Certificate rootCert = (X509Certificate) certificateFactory.generateCertificate(isCert);
list.add(rootCert);
CollectionCertStoreParameters params = new CollectionCertStoreParameters(list);
CertStore store = CertStore.getInstance("Collection", params, BC);
//create cert path
List certChain = new ArrayList();
certChain.add(rootCert);
CertPath certPath = certificateFactory.generateCertPath(certChain);
Set trust = Collections.singleton(new TrustAnchor(rootCert, null));
//validation
CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX", BC);
PKIXParameters pKIXParameters = new PKIXParameters(trust);
pKIXParameters.addCertStore(store);
pKIXParameters.setDate(new Date());
try {
CertPathValidatorResult result = certPathValidator.validate(certPath, pKIXParameters);
System.out.println("certificate path validated");
} catch (CertPathValidatorException e) {
System.out.println("validation failed on certificate number " + e.getIndex() + ", details: " + e.getMessage());
}
Got this exception:
validation failed on certificate number -1, details: Trust anchor for certification path not found.
And btw, can I just use only the end-entity certificate to validate messages, as if it were the self-signed certificate?
I've used BouncyCastle 1.56 for this test.
One way to get the issuer's certificate from the end entity is to look for the Authority Information Access extension.
This extension may be present (it's not mandatory) and may contain the URL to get the issuer's certificate (issuer is the certificate "above" the current one, so the end entity's issuer is the intermediate, and the intermediate's issuer is the root).
You can get this extension value with BouncyCastle:
import java.security.cert.X509Certificate;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.extension.X509ExtensionUtil;
X509Certificate cert = // end entity certificate
// get Authority Information Access extension (will be null if extension is not present)
byte[] extVal = cert.getExtensionValue(Extension.authorityInfoAccess.getId());
AuthorityInformationAccess aia = AuthorityInformationAccess.getInstance(X509ExtensionUtil.fromExtensionValue(extVal));
// check if there is a URL to issuer's certificate
AccessDescription[] descriptions = aia.getAccessDescriptions();
for (AccessDescription ad : descriptions) {
// check if it's a URL to issuer's certificate
if (ad.getAccessMethod().equals(X509ObjectIdentifiers.id_ad_caIssuers)) {
GeneralName location = ad.getAccessLocation();
if (location.getTagNo() == GeneralName.uniformResourceIdentifier) {
String issuerUrl = location.getName().toString();
// http URL to issuer (test in your browser to see if it's a valid certificate)
// you can use java.net.URL.openStream() to create a InputStream and create
// the certificate with your CertificateFactory
URL url = new URL(issuerUrl);
X509Certificate issuer = (X509Certificate) certificateFactory.generateCertificate(url.openStream());
}
}
}
So you can use this code with the end entity certificate to get the intermediate. Then you use it again with the intermediate to get the root.
Then you add the root to your TrustAnchor and the validation should work.
Note: But as I said, this extension is not mandatory and may not be present. In this case, getExtensionValue will return null, and the only alternative that I know is to search for the certificates in google and download them (those certificate chains are usually public and not hard to find)
btw, if we have out certificate installed in windows, everything is much simpler:
KeyStore ks = KeyStore.getInstance("Windows-MY");
ks.load(null, null);
String alias = "your alias";
ArrayList<X509Certificate> certsChain = new ArrayList<>();
if (ks.isCertificateEntry(alias)) {
Certificate[] chain = ks.getCertificateChain(alias);
System.out.println("Chain length: " + chain.length);
for(Certificate c : chain) certsChain.add((X509Certificate)c);
}
Collections.reverse(certsChain);
certsChain.forEach(MainClass::printDBG);
boom, and the whole certificates chain is ready
I'm generating CMS signature files with external PKCS#1 based on this thread.
The first step is obtain the signed attributes from the original file to be signed in external application which is returning PKCS#1 byte array.
Then build standard org.bouncycastle.cms.SignerInfoGenerator with original file hash, signed data (PKCS#1) and certificate to add to CMS, and finally create the attached signature.
But when i'd tried to validate it using this code:
String originalFile = "aG9sYQ0KYXNkYXMNCg0KYWZzDQo=";
String cmsSignedFile = "MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBg...j2Dwytp6kzQNwtXGO8QbWty1lOo8oYm+6LR8EWba3ikO/m9ol/G808vit9gAAAAAAAA==";
byte[] signedByte = DatatypeConverter.parseBase64Binary(cmsSignedFile);
Security.addProvider(new BouncyCastleProvider());
CMSSignedData s = new CMSSignedData(new CMSProcessableByteArray(DatatypeConverter.parseBase64Binary(originalFile)), signedByte);
SignerInformationStore signers = s.getSignerInfos();
SignerInformation signerInfo = (SignerInformation)signers.getSigners().iterator().next();
FileInputStream fis = new FileInputStream("C:/myCertificate.cer");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificates(fis).iterator().next();
boolean result = signerInfo.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert.getPublicKey()));
System.out.println("Verified: "+result);
I get Verified: false
I'm adding Content Type, Signing time, Message digest and OCSP as signed attributes and TSP Token as unsigned attribute (I'm not sure if this is right).
I'm also trying to recover data from CMS signature, using the code below:
//load cms signed file with attached data
CMSSignedData cms = new CMSSignedData(FileUtils.readFileToByteArray(new File("C:/tmp/tempFile1864328163858309463.cms")));
System.out.println(cms.getSignerInfos().getSigners().iterator().next().getDigestAlgorithmID().getAlgorithm().getId());
System.out.println(Hex.encodeHexString(cms.getSignerInfos().getSigners().iterator().next().getSignature()));
//recover signer certificate info
Store certs = cms.getCertificates();
Collection<X509CertificateHolder> col = certs.getMatches(null);
X509CertificateHolder []h1 = col.toArray(new X509CertificateHolder[col.size()]);
X509CertificateHolder firmante = h1[0];
System.out.println(firmante.getSubject());
System.out.println(h1[1].getSubject());
SignerInformation sinfo = cms.getSignerInfos().getSigners().iterator().next();
//recover OCSP information
//THIS FAILS :(
// Store infocspbasic = cms.getOtherRevocationInfo(OCSPObjectIdentifiers.id_pkix_ocsp_basic);
// Object basic = infocspbasic.getMatches(null).iterator().next();
//recover signing time
if (sinfo.getSignedAttributes() != null) {
Attribute timeStampAttr = sinfo.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_signingTime);
ASN1Encodable attrValue = timeStampAttr.getAttrValues().getObjectAt(0);
final Date signingDate;
if (attrValue instanceof ASN1UTCTime) {
ASN1UTCTime time = ASN1UTCTime.getInstance(attrValue);
Date d = time.getDate();
System.out.println("ASN1UTCTime:" + d);
} else if (attrValue instanceof Time) {
signingDate = ((Time) attrValue).getDate();
} else if (attrValue instanceof ASN1GeneralizedTime) {
System.out.println("ASN1GeneralizedTimeASN1GeneralizedTime");
} else {
signingDate = null;
}
}
//recover timestamp TOken
//unsigned attributes are null :(
if (sinfo.getUnsignedAttributes() != null) {
Attribute timeStampAttr = sinfo.getUnsignedAttributes().get(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
for (ASN1Encodable value : timeStampAttr.getAttrValues().toArray()) {
TimeStampToken token = new TimeStampToken(new CMSSignedData(value.toASN1Primitive().getEncoded()));
System.out.println(token.getTimeStampInfo().getGenTime());
}
}
But I can't retrieve OCSP response nor TSP Token information. Additionally I've downloaded this viewer software to help verify it:
Any help would be very appreciated.
I found a project named j4sign which implements CMS signature with external PKCS#1. The link goes to the project's forum where I posted the code sample using their classes and the final correction to make the validation works.
I am currently using the java Bouncy Castle libraries in order to create CMS signed data (or PKCS7 signed data). I seem however to be stuck with adding certificates (even though the certificate signer is properly added).
I checked out this question about properly signing data, but it didn't respond the needs of my SCEP server. The code I used was from EJBCA but doesn't seem to add certificates to the PKCS7 signed data.
When I parse the signed data with the openssl cms tool, I see that the "certificates" field is "EMPTY". Additionally, when I try to the print the certs with openssl pkcs7 [...] -print_certs, I get nothing.
Here is how I sign my data with Bouncy Castle (it's a lot code but enough to reproduce the issue):
CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
CMSTypedData msg;
List<X509Certificate> certList = new ArrayList<>();
// Make sure the certificate is not null
if (this.certificate != null) {
certList.add((X509Certificate) this.certificate);
}
/**
* Create the signed CMS message to be contained inside the envelope
* this message does not contain any message, and no signerInfo
**/
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Collection<JcaX509CertificateHolder> x509CertificateHolder = new ArrayList<>();
try {
for (X509Certificate certificate : certList) {
x509CertificateHolder.add(new JcaX509CertificateHolder(certificate));
}
CollectionStore<JcaX509CertificateHolder> store = new CollectionStore<>(x509CertificateHolder);
gen.addCertificates(store);
} catch (Handle all exceptions) {}
This snippet of code above should normally add certificates. I took this from EJBCA.
Here is how I complete the signed data:
CMSSignedDataGenerator gen1 = new CMSSignedDataGenerator();
// I add ALL of my attributes here
// Once they're added...
Certificate caCert = this.caCertificate;
try {
String provider = BouncyCastleProvider.PROVIDER_NAME;
ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithmName).
setProvider(provider).
build(signerKey);
JcaDigestCalculatorProviderBuilder calculatorProviderBuilder = new JcaDigestCalculatorProviderBuilder().
setProvider(provider);
JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(calculatorProviderBuilder.build());
builder.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(attributes)));
gen1.addSignerInfoGenerator(builder.build(contentSigner, (X509Certificate) ca));
} catch (Handle all exceptions) {}
// Create the signed data
CMSSignedData sd = gen1.generate(msg, true);
byte[] results = sd.getEncoded();
The bytes array results is the DER formatted PKCS7 signed data... but no certificate is added.
Am I missing something? Thank you for your help!
The CMSSignedDataGenerator gen1 has to explicitly add the certificate, which I wasn't aware of.
It can simply be done by:
Adding the certificates to a List of X509Certificates;
Converting that List into a Collection of JcaX509CertificateHolder;
Adding this collection to a CollectionStore of JcaX509CertificateHolder;
Adding the store the CMSSignedDataGenerator.
Code sample:
CMSSignedDataGenerator gen1 = new CMSSignedDataGenerator();
List<X509Certificate> certificates = new ArrayList<>();
// I chose to add the CA certificate
certificates.add((X509Certificate) this.caCertificate);
// In this case, this is a certificate that I need to add
if (this.certificate != null)
certificates.add((X509Certificate) this.certificate);
// This is the recipient certificate
if (this.recipientCert != null)
certificates.add((X509Certificate) this.recipientCert);
Collection<JcaX509CertificateHolder> x509CertificateHolder = new ArrayList<>();
// Of course, we need to handle the exceptions...
for (X509Certificate certificate : certificates) {
x509CertificateHolder.add(new JcaX509CertificateHolder(certificate));
}
CollectionStore<JcaX509CertificateHolder> store = new CollectionStore<>(x509CertificateHolder);
// The final stage.
gen1.addCertificates(store);
Hope this helps anyone in the future.
Please, could someone point me in the right direction to digitally sign an MS-Office document (docx, xlsx, pptx) in Apache POI, or any other open source library?
I have already reviewed the classes under org.apache.poi.openxml4j.opc.signature but I cannot understand how I could add a signature to a document.
Check this sample code .. this sample code uses a file keystore PFX (PKCS12) .. Signs the Document and verify it.
// loading the keystore - pkcs12 is used here, but of course jks & co are also valid
// the keystore needs to contain a private key and it's certificate having a
// 'digitalSignature' key usage
char password[] = "test".toCharArray();
File file = new File("test.pfx");
KeyStore keystore = KeyStore.getInstance("PKCS12");
FileInputStream fis = new FileInputStream(file);
keystore.load(fis, password);
fis.close();
// extracting private key and certificate
String alias = "xyz"; // alias of the keystore entry
Key key = keystore.getKey(alias, password);
X509Certificate x509 = (X509Certificate)keystore.getCertificate(alias);
// filling the SignatureConfig entries (minimum fields, more options are available ...)
SignatureConfig signatureConfig = new SignatureConfig();
signatureConfig.setKey(keyPair.getPrivate());
signatureConfig.setSigningCertificateChain(Collections.singletonList(x509));
OPCPackage pkg = OPCPackage.open(..., PackageAccess.READ_WRITE);
signatureConfig.setOpcPackage(pkg);
// adding the signature document to the package
SignatureInfo si = new SignatureInfo();
si.setSignatureConfig(signatureConfig);
si.confirmSignature();
// optionally verify the generated signature
boolean b = si.verifySignature();
assert (b);
// write the changes back to disc
pkg.close();
Here's the sample source : https://poi.apache.org/apidocs/org/apache/poi/poifs/crypt/dsig/SignatureInfo.html
I hope this could help!
Need some help with crypto routines in Java.
Given a PKCS#7 signature, I want to verify all certificates it contains against a trusted store. I assume that all certificates contained in signature are in the correct order to form a valid certificate path (or chain, whatever), so that
topmost (#0) is a signing certificate;
next one (#1) is an intermediate certificate, used to sign #0;
next one (#2) is another intermediate certificate, used to sign #1;
and so on.
The last certificate (#N) is signed by CA.
That's what I've managed to hack so far:
// Exception handling skipped for readability
//byte[] signature = ...
pkcs7 = new PKCS7(signature); // `sun.security.pkcs.PKCS7;`
// *** Checking some PKCS#7 parameters here
X509Certificate prevCert = null; // Previous certificate we've found
X509Certificate[] certs = pkcs7.getCertificates(); // `java.security.cert.X509Certificate`
for (int i = 0; i < certs.length; i++) {
// *** Checking certificate validity period here
if (cert != null) {
// Verify previous certificate in chain against this one
prevCert.verify(certs[i].getPublicKey());
}
prevCert = certs[i];
}
//String keyStorePath = ...
KeyStore keyStore = KeyStore.getInstance("JKS"); // `java.security.KeyStore`
keyStore.load(new FileInputStream(keyStorePath), null);
// Get trusted VeriSign class 1 certificate
Certificate caCert = keyStore.getCertificate("verisignclass1ca"); // `java.security.cert.Certificate`
// Verify last certificate against trusted certificate
cert.verify(caCert.getPublicKey());
So the question is -- how can this be done using standard Java classes like CertPath and friends? I have a strong feeling I'm re-inventing a bicycle. Or, if someone has an example with BouncyCastle library, that would also be fine.
Bonus question: how to verify a certificate against a trusted store so that root certificate is selected automatically?
Found the solution myself. So, here's how one can extract and validate a certificate chain against the trusted store (exception handling skipped for readability):
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// Get ContentInfo
//byte[] signature = ... // PKCS#7 signature bytes
InputStream signatureIn = new ByteArrayInputStream(signature);
DERObject obj = new ASN1InputStream(signatureIn).readObject();
ContentInfo contentInfo = ContentInfo.getInstance(obj);
// Extract certificates
SignedData signedData = SignedData.getInstance(contentInfo.getContent());
Enumeration certificates = signedData.getCertificates().getObjects();
// Build certificate path
List certList = new ArrayList();
while (certificates.hasMoreElements()) {
DERObject certObj = (DERObject) certificates.nextElement();
InputStream in = new ByteArrayInputStream(certObj.getDEREncoded());
certList.add(cf.generateCertificate(in));
}
CertPath certPath = cf.generateCertPath(certList);
// Load key store
//String keyStorePath = ...
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(keyStorePath), null);
// Set validation parameters
PKIXParameters params = new PKIXParameters(keyStore);
params.setRevocationEnabled(false); // to avoid exception on empty CRL
// Validate certificate path
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
CertPathValidatorResult result = validator.validate(certPath, params);
validate() will throw an exception if validation fails.
Docs: ASN1Set, ContentInfo, SignedData. All other exotic names and related docs can be found in java.security.cert.
No SUN-dependencies here, only BouncyCastle provider library is needed.
This question (and especially an answer) may help too.
You want CertificateFactory. The last example in the javadocs do exactly what you want.