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.
Related
I made a cert store and want to add another certificate from another function in this class.
Make chain in class 1 with caCert and intermediateCert:
List<X509Certificate> certList = new ArrayList<X509Certificate>();
certList.add(0, interCert);
certList.add(1, caCert);
Store<X509Certificate> certs = new JcaCertStore(certList);
certStore = new JcaCertStoreBuilder().addCertificates(certs).build();
I want to add a new build user certificate in class 2:
certStore = new JcaCertStoreBuilder().addCertificate(certHolder).build();
To save the new key to my KeyStore I need an array of the certificates (X509Certificate[]). How can I convert the certStore file to an array to get the privatekeyentry working?
PrivateKeyEntry privKeyEntry = new PrivateKeyEntry(pair.getPrivate(), chain);
store.setEntry(alias, privKeyEntry, new KeyStore.PasswordProtection(storePassword));
I'm using BouncyCastle 1.56 and JDK 1.7.
I think the easiest way is to get all the certificates from the cert store and add them to an array (instead of creating another cert store and converting).
To get all the certificates in the cert store, you can do:
// get all certificates in certStore
Collection<? extends Certificate> allCerts = certStore.getCertificates(null);
Some implementations don't accept the null argument. In this case, you must create a selector like this (using java.security.cert.X509CertSelector class):
Collection<? extends Certificate> allCerts = certStore.getCertificates(new X509CertSelector() {
#Override
public boolean match(Certificate cert) {
// match all certificates (so it'll return all of them)
return true;
}
});
After that, allCerts will have the 2 certificates that are in certStore.
Now you create your array and add all the certificates you need:
// create array
X509Certificate[] certificatesArray = new X509Certificate[3];
// add certificates in allCerts (the 2 that were in certStore)
int i = 0;
for (Certificate c : allCerts) {
certificatesArray[i] = (X509Certificate) c;
i++;
}
// add the new certificate (newCert being a X509Certificate)
certificatesArray[2] = newCert;
Note:
If your new certificate's type is a org.bouncycastle.cert.X509CertificateHolder, you can convert it to a java.security.cert.X509Certificate using the org.bouncycastle.cert.jcajce.JcaX509CertificateConverter class:
X509CertificateHolder certHolder = ...;
X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder);
Or you can do it manually with a java.security.cert.CertificateFactory:
X509CertificateHolder certHolder = ...;
CertificateFactory f = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate) f.generateCertificate(new ByteArrayInputStream(certHolder.getEncoded()));
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 new to Cryptography and so please excuse me if you think this is a basic question
I have a .p7b file which I need to read and extract the individual public certificates i.e the .cer files and store it in the key store. I need not worry about persisting in the key store as there is already a service which takes in the .cer file as byte[] and saves that.
What i want to know is , how do i read the .p7b and extract the individual .cer file? I know that can be done via the openSSL commands, but i need to do the same in java. I need to also read the Issued By name as that will be used as a unique key to persist the certificate.
Thanks in advance
You can get the certificates from a PKCS#7 object with BouncyCastle. Here is a quick code sample:
public Collection<X59Certificate> getCertificates(String path) throws Exception
{
Security.addProvider(new BouncyCastleProvider());
CMSSignedData sd = new CMSSignedData(new FileInputStream(path));
X509Store store = sd.getCertificates("Collection", "BC");
Collection<X509Certificate> certificates = store.getMatches(X509CertStoreSelector.getInstance(new X509CertSelector()));
return certificates;
}
Note that a PKCS#7 may contain more than one certificate. Most of the time it includes intermediate certification authority certificates required to build the certificate chain between the end-user certificate and the root CA.
I was successfully able to read the individual .X509 certificates from the p7b files. Here are the steps
First step includes, getting a byte[] from the java.io.File. The steps include to remove the -----BEGIN PKCS7----- and -----END PKCS7----- from the file, and decode the remaining base64 encoded String.
BufferedReader reader = new BufferedReader(new FileReader(file));
StringBuilder cerfile = new StringBuilder();
String line = null;
while(( line = reader.readLine())!=null){
if(!line.contains("PKCS7")){
cerfile.append(line);
}
}
byte[] fileBytes = Base64.decode(cerfile.toString().getBytes());
The next step is to use the BouncyCastle api to parse the file
CMSSignedData dataParser = new CMSSignedData(trustBundleByte);
ContentInfo contentInfo = dataParser.getContentInfo();
SignedData signedData = SignedData.getInstance(contentInfo.getContent());
CMSSignedData encapInfoBundle = new CMSSignedData(new CMSProcessableByteArray(signedData.getEncapContentInfo().getContent().getDERObject().getEncoded()),contentInfo);
SignedData encapMetaData = SignedData.getInstance(encapInfoBundle.getContentInfo().getContent());
CMSProcessableByteArray cin = new CMSProcessableByteArray(((ASN1OctetString)encapMetaData.getEncapContentInfo().getContent()).getOctets());
CertificateFactory ucf = CertificateFactory.getInstance("X.509");
CMSSignedData unsignedParser = new CMSSignedData(cin.getInputStream());
ContentInfo unsginedEncapInfo = unsignedParser.getContentInfo();
SignedData metaData = SignedData.getInstance(unsginedEncapInfo.getContent());
Enumeration certificates = metaData.getCertificates().getObjects();
// Build certificate path
while (certificates.hasMoreElements()) {
DERObject certObj = (DERObject) certificates.nextElement();
InputStream bin = new ByteArrayInputStream(certObj.getDEREncoded());
X509Certificate cert = (X509Certificate) ucf.generateCertificate(bin);
X500Name x500name = new JcaX509CertificateHolder(cert).getSubject();
RDN cn = x500name.getRDNs(BCStyle.CN)[0];
}
The above steps are working fine, but i am sure there are other solutions with less lines of code to achieve this. I am using bcjdk16 jars.
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.