I need to generate p7b certificate chain using bouncy castle 1.58.
In the older version we used(1.46), this code worked:
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Certificate [] chain = certificate.getCertificateChain();
CertStore certStore;
try {
certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(Arrays.asList(chain)));
gen.addCertificatesAndCRLs(certStore);
CMSSignedData signedData = gen.generate(null,(Provider)null);
return signedData.getEncoded();
} catch (Exception ex) {
logger.error("Failed to construct P7B response",ex);
throw new RuntimeException(ex);
}
However, there are some changes of the CMSSignedDataGenerator with the new version of Bouncy Castle, so I modified my code like this:
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Certificate [] chain = certificate.getCertificateChain();
try {
JcaCertStore store = new JcaCertStore(Arrays.asList(chain));
gen.addCertificates(store);
CMSSignedData signedData = gen.generate(null);
return signedData.getEncoded();
} catch (Exception ex) {
logger.error("Failed to construct P7B response",ex);
throw new RuntimeException(ex);
}
However, I get a null pointer exception on this line inside the generate:
CMSSignedData signedData = gen.generate(null);
I tried to debug and I checked that the certificates are loaded to JcaCertStore, so that part is ok.
However, when I try to debug bouncy castle library the debugger can't seem to find line numbers of the CMSSignedDataGenerator class.
I'm using Wildfly to deploy my project and I've attached the jar with sources to the debugger, however I see the code but right next to class name I get line not available, so I'm not able to see where the null pointer exception occurs.
What's also interesting is that I see a hollow Java icon on that class:
I solved the issue using the following code:
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Certificate [] chain = certificate.getCertificateChain();
try {
CMSProcessableByteArray msg = new CMSProcessableByteArray("".getBytes());
JcaCertStore store = new JcaCertStore(Arrays.asList(chain));
gen.addCertificates(store);
CMSSignedData signedData = gen.generate(msg);
return signedData.getEncoded();
} catch (Exception ex) {
logger.error("Failed to construct P7B response",ex);
throw new RuntimeException(ex);
}
However, I see this as a kind of a hack as you use CMSSignedDataGenerator which is meant for signing to generate the p7b certificate chain.
In the older version you could use null as data that is signed, but now you must input some data, even if it is just an empty byte array.
Related
I implemented a signature feature in JAVA using PDFBox.
The signing part of my code is :
ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(output);
byte[] cmsSignature = new byte[0];
try {
Certificate[] certificationChain = SignatureUtils.getCertificateChain(alias);
X509Certificate certificate = (X509Certificate) certificationChain[0];
PrivateKey privateKey = SignatureUtils.getSignaturePrivateKey(alias, password);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey);
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build())
.build(sha1Signer, certificate));
gen.addCertificates(new JcaCertStore(Arrays.asList(certificationChain)));
CMSProcessableInputStream msg = new CMSProcessableInputStream(externalSigning.getContent());
CMSSignedData signedData = gen.generate(msg, false);
if (tsaUrl != null && !tsaUrl.isEmpty()) {
ValidationTimeStamp validation;
validation = new ValidationTimeStamp(tsaUrl);
signedData = validation.addSignedTimeStamp(signedData);
}
cmsSignature = signedData.getEncoded();
if (logger.isDebugEnabled()) {
logger.debug("signature length = " + cmsSignature.length);
logger.debug("certificate = " + certificate.toString());
}
} catch (GeneralSecurityException | CMSException | OperatorCreationException | IOException e) {
throw new SignatureException(e.getMessage());
}
externalSigning.setSignature(cmsSignature);
Everything works fine if I use my testing auto-signed certificate I generated with the keytool command.
The problem is, when I try this code with an existing certificate which is truly certified, I get the exeption :
Caused by: java.io.IOException: Can't write signature, not enough space
at org.apache.pdfbox.pdfwriter.COSWriter.writeExternalSignature(COSWriter.java:797)
at org.apache.pdfbox.pdmodel.interactive.digitalsignature.SigningSupport.setSignature(SigningSupport.java:48)
I have no idea why this doesn't work...
Any help would be appreciated ! =)
In a comment you mention that you get
signature length = 10721
in the log files and that you
didn't set any preferred size
By default PDFBox reserves 0x2500 bytes for the signature. That's 9472 bytes decimally. Thus, just like the exception says, there's not enough space.
You can set the size PDFBox reserves for the signature by using a SignatureOptions object during document.addSignature:
SignatureOptions signatureOptions = new SignatureOptions();
signatureOptions.setPreferredSignatureSize(20000);
document.addSignature(signature, signatureOptions);
I'm trying to sign a pdf document on the Java webapp running on the server, I have used hwcrypto.js and was able to successfully get the signature as well as the certificate. Now I'm trying to generate a pkcs7 encoded CMS signed data using below code:
String generatePkcs7EncodedSignature(String certString, String signature) {
try {
BASE64Decoder decoder = new BASE64Decoder()
byte[] signatureByte = decoder.decodeBuffer(signature)
List<Certificate> certList = getPublicCertificates(certString)
Store certs = new JcaCertStore(certList)
CMSProcessableByteArray msg = new CMSProcessableByteArray(signatureByte)
CMSSignedDataGenerator gen = new CMSSignedDataGenerator()
gen.addCertificates(certs)
CMSSignedData data = gen.generate(msg, true)
return Base64.getEncoder().encodeToString(data.getEncoded())
}
catch (Exception ex) {
println(ex.stackTrace)
}
return null
}
getPublicCertificates method is as follows:
X509Certificate getPublicCertificate(String certificateString)
throws IOException, CertificateException {
InputStream is = new ByteArrayInputStream(certificateString.getBytes(StandardCharsets.UTF_8))
CertificateFactory cf = CertificateFactory.getInstance("X.509")
Certificate cert = (X509Certificate) cf.generateCertificate(is)
return cert
}
But I'm getting "No Certificate to validate signature" error when I open the pdf in Adobe Reader
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.
I am trying to do signature validation on SAML2 Response which is obtained from an identity provider using OpenSAML. I am trying to read the response from the localfile system.
Here is my code:
DefaultBootstrap.bootstrap();
BasicParserPool ppMgr = new BasicParserPool();
ppMgr.setNamespaceAware(true);
//Read file from the filesystem
File file1=new File("F:/Softwares/Assertion.xml");
InputStream inCommonSaml=new FileInputStream(file1);
// Parse file
Document inCommonSamlDoc = ppMgr.parse(inCommonSaml);
Element metadataRoot = inCommonSamlDoc.getDocumentElement();
UnmarshallerFactory unmarshallerFactory=configuration.getUnmarshallerFactory();
Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(metadataRoot);
Response inCommonSamlRes = (Response) unmarshaller.unmarshall(metadataRoot);
//Get certificate
SignatureValidator signatureValidator = new SignatureValidator(cert);
Signature signature=inCommonSamlRes.getSignature();
signatureValidator.validate(signature);
try {
BasicX509Credential credential = new BasicX509Credential();
File file2=new File("F:/Softwares/publicKey.crt");
InputStream samlCertificate=new FileInputStream(file2);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
//
#SuppressWarnings("deprecation")
java.security.cert.X509Certificate certificate = (java.security.cert.X509Certificate) certificateFactory.generateCertificate(samlCertificate);
//
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec((certificate).getPublicKey().getEncoded());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey key = keyFactory.generatePublic(publicKeySpec);
credential.setPublicKey(key);
Object obj = (credential).getPublicKey();
if (obj instanceof RSAPublicKey) {
BigInteger modulus = ((RSAPublicKey) obj).getModulus();
BigInteger exponent = ((RSAPublicKey) obj).getPublicExponent();
System.out.println("modulus");
System.out.println (org.apache.commons.codec.binary.Base64.encodeBase64String(modulus.toByteArray()));
System.out.println("public exponent:");
System.out.println (org.apache.commons.codec.binary.Base64.encodeBase64String(exponent.toByteArray()));
}
// System.out.println ("public key is: //n//r"+ credential.getPublicKey());
return credential;
} catch (Exception e) {
throw e; //Throws a 'Signature did not validate against the credential's key' exception
}
Note: I use the same certificate(publicKey.crt) to sign the assertion also.
I am getting the following error:
signature cryptographic validation not successful.
Please let me know where am I wrong? What does the error mean? Does it say that public and private keys are the same?
Thanks,
aswini J
I think you need to get .jks file from IdP (Identity Provider) server. Also when you're getting SAMLResponse from for your POST from IDP, this SAMLResponse should be containing Signature (FYI - will be an encoded string, you can decode and read using Open SAML library, available on Maven Central Repository).
Once you get signature from , you can validate that using OpenSAML
sigValidator.validate(response.getSignature());
This method will give you meesage if everything is OK. The message for reference "Signature validated with key from supplied credentia"
You can follow this link: http://sureshatt.blogspot.in/2012/11/how-to-read-saml-20-response-with.html to get Signature and
(IMP): Your IDP (Identity Provider) should be send (may be via HTTP POST), so that you can get from which can have NameID i.e, ClinetId
This answer is for incoming people getting this error and searching on Google.
In my case, I am facing the same error :"error: signature cryptographic validation not successful", with a slightly different case. I am validating with SimpleSamlPHP as SP and CAS as IDP.
The problem was that my SP uses one (self-signed) cert for SP validation to IDP, and another cert for SP in Apache to make allow SSL (so that I can have https)
Using two separate cert might works fine for a lot of process like login and metadata validation, but with prcess like logout the above error will happen.
The solution is to use only one cert for both process, and my error is gone.
I am currently trying to adapt a few scripts we use to sign an encrypt/decrypt xml files using OpenSSL and S/MIME using Java and BouncyCastle.
The command to sign and encrypt our file:
openssl smime -sign -signer Pub1.crt -inkey Priv.key -in foo.xml | openssl smime -encrypt -out foo.xml.smime Pub2.crt Pub1.crt
This generates a signed and encrypted smime-file containing our xml file. Currently this happens using a set of shell scripts under linux using the OpenSSL library. In the future we want to integrate this process into our Java application.
I've found out that this should be possible using the BouncyCastle library (see this post). The answer there provides two Java classes showing how to sign and encrypt an email using BouncyCastle and S/MIME. Comparing this to our OpenSSL command it seems that many of the things needed to sign an encrypt an email is not needed in our approach.
Some more meta information from our generated files:
Signed file
MIME-Version: 1.0
Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha-256"; boundary="----709621D94E0377688356FAAE5A2C1321"
Encrypted file
MIME-Version: 1.0
Content-Disposition: attachment; filename="smime.p7m"
Content-Type: application/x-pkcs7-mime; smime-type=enveloped-data; name="smime.p7m"
Content-Transfer-Encoding: base64
Is it even possible to sign and encrypt a simple file in the way we did it using OpenSSL? My current knowledge of signing and de/encryption is not very high at the moment so forgive me for not providing code samples. I guess what I am looking for is more input into what I need to do and maybe some expertise from people who have already done this. I hope this is the right place to ask this. If not, please correct me.
I had a similar question as you but I managed to solve it. I have to warn you, my knowledge about signing and encryption isn't that high either. But this code seemed to work for me.
In my case I used a personalsign pro 3 certificate from globalsign, Previously I just called openssl from within java. But the I wanted to clean my code and decided to use bouncy castle instead.
public static boolean signAllFiles(List<File> files) {
Boolean signingSucceeded = true;
KeyStore ks = null;
char[] password = null;
Security.addProvider(new BouncyCastleProvider());
try {
ks = KeyStore.getInstance("PKCS12");
password = "yourpass".toCharArray();
ks.load(new FileInputStream("full/path/to/your/original/certificate.pfx"), password);
} catch (Exception e) {
signingSucceeded = false;
}
// Get privatekey and certificate
X509Certificate cert = null;
PrivateKey privatekey = null;
try {
Enumeration<String> en = ks.aliases();
String ALIAS = "";
Vector<Object> vectaliases = new Vector<Object>();
while (en.hasMoreElements())
vectaliases.add(en.nextElement());
String[] aliases = (String[])(vectaliases.toArray(new String[0]));
for (int i = 0; i < aliases.length; i++)
if (ks.isKeyEntry(aliases[i]))
{
ALIAS = aliases[i];
break;
}
privatekey = (PrivateKey)ks.getKey(ALIAS, password);
cert = (X509Certificate)ks.getCertificate(ALIAS);
// publickey = ks.getCertificate(ALIAS).getPublicKey();
} catch (Exception e) {
signingSucceeded = false;
}
for (File source : files) {
String fileName = "the/path/andNameOfYourOutputFile";
try {
// Reading files which need to be signed
File fileToSign = source;
byte[] buffer = new byte[(int)fileToSign.length()];
DataInputStream in = new DataInputStream(new FileInputStream(fileToSign));
in.readFully(buffer);
in.close();
// Generate signature
ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
certList.add(cert);
Store<?> certs = new JcaCertStore(certList);
CMSSignedDataGenerator signGen = new CMSSignedDataGenerator();
ContentSigner sha1signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(
privatekey);
signGen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().build()).build(sha1signer, cert));
signGen.addCertificates(certs);
CMSTypedData content = new CMSProcessableByteArray(buffer);
CMSSignedData signedData = signGen.generate(content, false);
byte[] signeddata = signedData.getEncoded();
// Write signature to Fi File
FileOutputStream envfos = new FileOutputStream(fileName);
byte[] outputString = Base64.encode(signeddata);
int fullLines = (int)Math.floor(outputString.length / 64);
for (int i = 0; i < fullLines; i++) {
envfos.write(outputString, i * 64, 64);
envfos.write("\r\n".getBytes());
}
envfos.write(outputString, fullLines * 64, outputString.length % 64);
envfos.close();
} catch (Exception e) {
signingSucceeded = false;
}
}
return signingSucceeded;
}
This is only the code to sign a file, I hope it helps.