I'm trying to create a certificate (A) which is signed for other certificate (B) stored in a p12 keystore. This stored certificate (B) was added to the trusted certificate store of my local machine.
Certificate A is used to sign a pdf document using bouncy castle 1.52 library, but the digital signature that I obtain in the signed document is invalid.
I'm going to explain the steps done just if somebody can help me.
First, I create a CSR from the p12 keystore(B):
private static PKCS10CertificationRequest generateCSR() {
PKCS10CertificationRequest csr = null;
try {
initCACert();
PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(
new X500Principal("CN=Requested Test Certificate"), CAcert.getPublicKey());
JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SHA256withRSA");
ContentSigner signer = csBuilder.build(CApk);
csr = p10Builder.build(signer);
} catch (Exception e) {
log.error(e);
}
return csr;
}
Then, a certificate was generated (A) with this CSR.
private static Certificate signCSR() throws Exception {
PKCS10CertificationRequest csr = generateCSR();
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
X500Name issuer = X500Name.getInstance(CAcert.getSubjectX500Principal().getEncoded());
BigInteger serial = new BigInteger(32, new SecureRandom());
Calendar c = Calendar.getInstance();
c.add(Calendar.SECOND, -1);
Date from = c.getTime();
c.add(Calendar.YEAR, 5);
Date to = c.getTime();
X509v1CertificateBuilder certBuilder = new X509v1CertificateBuilder(issuer, serial, from, to, csr.getSubject(),
csr.getSubjectPublicKeyInfo());
ContentSigner signer = new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
.build(PrivateKeyFactory.createKey(CApk.getEncoded()));
X509CertificateHolder holder = certBuilder.build(signer);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(holder.getEncoded());
Certificate cert = certFactory.generateCertificate(in);
return cert;
}
Finally, I use this generated certificate (A) to sign my pdf.
Certificate cert = signCSR();
SignerInfoGeneratorBuilder signerInfoBuilder = new SignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()
);
signerInfoBuilder.setSignedAttributeGenerator( signedAttributeGenerator );
JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder( "SHA1WITHRSA" );
contentSignerBuilder.setProvider("BC");
X509CertificateHolder certificateHolder = new X509CertificateHolder( cert.getEncoded( ) );
generator.addSignerInfoGenerator(
signerInfoBuilder.build( contentSignerBuilder.build( CApk ),
certificateHolder )
);
ArrayList<X509CertificateHolder> signingChainHolder = new ArrayList<X509CertificateHolder>( );
certificateHolder = new X509CertificateHolder( cert.getEncoded() );
certificateHolder = new X509CertificateHolder( CAcert.getEncoded() );
signingChainHolder.add( certificateHolder );
Store certs = new JcaCertStore( signingChainHolder );
generator.addCertificates( certs );
CMSTypedData content = new CMSProcessableByteArray(datos);
CMSSignedData signedData = generator.generate( content, true );
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new DEROutputStream(baos).writeObject(signedData.toASN1Structure());
result = baos.toByteArray();
All the process is executed apparently right, but when I open the pdf the signature is invalid:
EDIT: I have made an export of the generated certificate. This is the result obtained.
I will appreciate any comment or information that can help me to fix this.
Thanks in advance.
generator.addSignerInfoGenerator(
signerInfoBuilder.build( contentSignerBuilder.build( CApk ),
certificateHolder )
);
If I see that right, you're using the CA's private key to sign the data. It should be the certificate's. So private and public key don't match and therefor a signature validation check is failing.
I have identified the problem: I was building the certificate chain in the opposite order that should be.
I had this order:
certificateHolder = new X509CertificateHolder( cert.getEncoded() );
certificateHolder = new X509CertificateHolder( CAcert.getEncoded() );
And the right order is this:
certificateHolder = new X509CertificateHolder( CAcert.getEncoded() );
certificateHolder = new X509CertificateHolder( cert.getEncoded() );
I hope somebody can find this information useful!
Currently i have a client-server application that, given a PDF file, signs it (with the server certificate), attachs the signature with the original file and returns the output back to the client (all of this is achieved with PDFBox).
I have a Signature handler, which is my External Signing Support (where content is the PDF file)
public byte[] sign(InputStream content) throws IOException {
try {
System.out.println("Generating CMS signed data");
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
ContentSigner sha1Signer = new JcaContentSignerBuilder("Sha1WithRSA").build(privateKey);
generator.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build())
.build(sha1Signer, new X509CertificateHolder(certificate.getEncoded())));
CMSTypedData cmsData = new CMSProcessableByteArray(IOUtils.toByteArray(content));
CMSSignedData signedData = generator.generate(cmsData, false);
return signedData.getEncoded();
} catch (GeneralSecurityException e) {
throw new IOException(e);
} catch (CMSException e) {
throw new IOException(e);
} catch (OperatorCreationException e) {
throw new IOException(e);
}
}
It works fine, but i was thinking - what if the PDF file is too big to be uploaded? ex: 100mb... it would take forever!
Given that, i am trying to figure out, if instead of signing the PDF file, is it possible to just sign the Hash (ex SHA1) of that file and than the client puts it all together in the end?
Update:
I have been trying to figure this out, and now my signing method is:
#Override
public byte[] sign(InputStream content) throws IOException {
// testSHA1WithRSAAndAttributeTable
try {
MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
List<Certificate> certList = new ArrayList<Certificate>();
CMSTypedData msg = new CMSProcessableByteArray(IOUtils.toByteArray(content));
certList.add(certificate);
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Attribute attr = new Attribute(CMSAttributes.messageDigest,
new DERSet(new DEROctetString(md.digest(IOUtils.toByteArray(content)))));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
AlgorithmIdentifier sha1withRSA = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(certificate.getEncoded());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
gen.addSignerInfoGenerator(builder.build(
new BcRSAContentSignerBuilder(sha1withRSA,
new DefaultDigestAlgorithmIdentifierFinder().find(sha1withRSA))
.build(PrivateKeyFactory.createKey(privateKey.getEncoded())),
new JcaX509CertificateHolder(cert)));
gen.addCertificates(certs);
CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
return new CMSSignedData(msg, s.getEncoded()).getEncoded();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new IOException(e);
}
}
And i am merging the signature with the PDF with pdfbox
ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(output);
byte[] cmsSignature = sign(externalSigning.getContent());
externalSigning.setSignature(cmsSignature);
The problem is that Adobe says the signature is invalid because the "document has been altered or corrupted since it was signed".
Can anyone help?
In his update the OP nearly has it right, there merely are two errors:
He tries to read the InputStream parameter content twice:
CMSTypedData msg = new CMSProcessableByteArray(IOUtils.toByteArray(content));
[...]
Attribute attr = new Attribute(CMSAttributes.messageDigest,
new DERSet(new DEROctetString(md.digest(IOUtils.toByteArray(content)))));
Thus, all data had already been read from the stream before the second attempt which consequently returned an empty byte[]. So the message digest attribute contained a wrong hash value.
He creates the final CMS container in a convoluted way:
return new CMSSignedData(msg, s.getEncoded()).getEncoded();
Reducing the latter to what is actually needed, it turns out that there is no need for the CMSTypedData msg anymore. Thus, the former is implicitly resolved.
After re-arranging the digest calculation to the top of the method and additionally switching to SHA256 (as SHA1 is deprecated in many contexts, I prefer to use a different hash algorithm) and allowing for a certificate chain instead of a single certificate, the method looks like this:
// Digest generation step
MessageDigest md = MessageDigest.getInstance("SHA256", "BC");
byte[] digest = md.digest(IOUtils.toByteArray(content));
// Separate signature container creation step
List<Certificate> certList = Arrays.asList(chain);
JcaCertStore certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Attribute attr = new Attribute(CMSAttributes.messageDigest,
new DERSet(new DEROctetString(digest)));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
AlgorithmIdentifier sha256withRSA = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withRSA");
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(chain[0].getEncoded());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
gen.addSignerInfoGenerator(builder.build(
new BcRSAContentSignerBuilder(sha256withRSA,
new DefaultDigestAlgorithmIdentifierFinder().find(sha256withRSA))
.build(PrivateKeyFactory.createKey(pk.getEncoded())),
new JcaX509CertificateHolder(cert)));
gen.addCertificates(certs);
CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
return s.getEncoded();
(CreateSignature method signWithSeparatedHashing)
Used in a fairly minimal signing code frame
void sign(PDDocument document, OutputStream output, SignatureInterface signatureInterface) throws IOException
{
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("Example User");
signature.setLocation("Los Angeles, CA");
signature.setReason("Testing");
signature.setSignDate(Calendar.getInstance());
document.addSignature(signature);
ExternalSigningSupport externalSigning =
document.saveIncrementalForExternalSigning(output);
byte[] cmsSignature = signatureInterface.sign(externalSigning.getContent());
externalSigning.setSignature(cmsSignature);
}
(CreateSignature method sign)
like this
try ( InputStream resource = getClass().getResourceAsStream("test.pdf");
OutputStream result = new FileOutputStream(new File(RESULT_FOLDER, "testSignedWithSeparatedHashing.pdf"));
PDDocument pdDocument = PDDocument.load(resource) )
{
sign(pdDocument, result, data -> signWithSeparatedHashing(data));
}
(CreateSignature test method testSignWithSeparatedHashing)
results in properly signed PDFs, as proper at least as the certificates and private key in question are for the task at hand.
One remark:
The OP used IOUtils.toByteArray(content)) (and so do I in the code above). But considering the OP's starting remark
what if the PDF file is too big to be uploaded? ex: 100mb
doing so is not such a great idea as it loads a big file into memory at once only for hashing. If one really wants to consider the resource footprint of one's application, one should read the stream a few KB at a time and consecutively digest the data using MessageDigest.update and only use MessageDigest.digest at the end to get the result hash value.
I have been looking for the past few days for a solution on my Problem and couldn't find anything. I am missing something in my Code but i cant figure out what :( Somehow when I sign my PKCS#10 the chain is broken.
Basically i have a server and a client. I want to have the client send a CSR to the server and the server signs it so they can communicate. Now i did set up a PKCS#12 with BouncyCastle for the Client and i did set up a RootCertificate for the Server (again with BouncyCastle, which is in my understanding just a PKCS#12 with the extension to be able to sign Certificates)
In Code it looks like this:
Provider BC = new BouncyCastleProvider();
Security.addProvider(BC);
//create KeyPair
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
kpGen.initialize(2048, new SecureRandom());
pair = kpGen.generateKeyPair();
//building groundbase for certificate
X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
builder.addRDN(BCStyle.CN, commonName);
builder.addRDN(BCStyle.OU, organizationalUnit);
builder.addRDN(BCStyle.O, organization);
builder.addRDN(BCStyle.L, city);
builder.addRDN(BCStyle.ST, state);
builder.addRDN(BCStyle.C, country);
Date notBefore = new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24); //Yesterday
Date notAfter = new Date(System.currentTimeMillis() + 1000L * 365L * 24L * 60L * 60L); //in a year
BigInteger serial = BigInteger.valueOf(new SecureRandom().nextLong());
//creating a self-signed certificate from information in builder
X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(),
serial, notBefore, notAfter, builder.build(), pair.getPublic());
//The next line will make the difference between a Certificate and a Ca Certificate
certGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
ContentSigner sigGen = new JcaContentSignerBuilder(").setProvider(BC).build(pair.getPrivate());
X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
Now i create a CSR for the Client (Client keystore has the just created PKCS#12 at first position):
String alias = keystore.aliases().nextElement();
X509Certificate cert = (X509Certificate) keystore.getCertificate(alias);
X500Name x500name = new JcaX509CertificateHolder(cert).getSubject();
//builder for the PKCS10
PKCS10CertificationRequestBuilder requestBuilder = new JcaPKCS10CertificationRequestBuilder(x500name, cert.getPublicKey());
//algorithm identifier
DefaultSignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
DefaultDigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
AlgorithmIdentifier sigAlgId = sigAlgFinder.find("SHA512WithRSA");
digAlgFinder.find(sigAlgId);
//content Signer
JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA512WithRSA");
//and build the Cert
ContentSigner signer = contentSignerBuilder.build((PrivateKey) keystore.getKey(alias, password));
PKCS10CertificationRequest req = requestBuilder.build(signer);
JcaPKCS10CertificationRequest req2 = new JcaPKCS10CertificationRequest(req.getEncoded()).setProvider("BC");
I send this JcaPKCS10CertificationRequest encoded over the network. The Server gets it and creates his CA Certificate and now has to sign the PKCS#10 but i am missing something here because he is not including the chain. The Certificate he is creating has the information about the issuer and BasicConstraints but the certification path is only including the Clients Certificate and NOT the Certificate of the Server so it is not trustworthy since the chain is broken.
This is what i do (Server Keystore has the CA Certificate at position 0, CSR is the JcaPKCS10CertificationRequest):
String alias = keystore.aliases().nextElement();
// PKCS#12 Root Certificate
X509Certificate cert = (X509Certificate) keystore.getCertificate(alias);
// generated Serial
BigInteger serial = BigInteger.valueOf(new SecureRandom().nextLong());
//identify algorithm
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA512WithRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find( sigAlgId );
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
JcaX509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(cert, serial, cert.getNotBefore(), cert.getNotAfter(),
CSR.getSubject(), CSR.getPublicKey());
certGen.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(cert));
certGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(false));
certGen.addExtension(Extension.subjectKeyIdentifier, true, extUtils.createSubjectKeyIdentifier(inputCSR.getPublicKey()));
certGen.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.nonRepudiation));
ContentSigner signer = new JcaContentSignerBuilder(sigAlgName).setProvider("BC").build((PrivateKey)keystore.getKey(alias, password));
X509CertificateHolder holder = certGen.build(signer);
X509Certificate signedCert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(holder);
signedCert.verify(cert.getPublicKey());
JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(new File("cer.cer")));
pemWriter.writeObject(signedCert);
pemWriter.writeObject(cert);
pemWriter.close();
Now as i said the generated File "cer.cer" has not the Chain in it. How can i add the chain? Can i then send that signedCert back to the client and it can be used in a ssl handshake?
To add the chain, this worked for me
After X509CertificateHolder holder = certGen.build(signer);
byte[] certencoded = holder.toASN1Structure().getEncoded();
ContentSigner signer = new JcaContentSignerBuilder("SHA1withRSA").build(caPrivateKkey);
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(signer, cacert));
generator.addCertificate(new X509CertificateHolder(certencoded));
generator.addCertificate(new X509CertificateHolder(cacert.getEncoded()));
CMSTypedData content = new CMSProcessableByteArray(certencoded);
CMSSignedData signeddata = generator.generate(content, true);
byte certificateP7b[] = signedData.getEncoded();
With this code you get a Certificate with the full chain in PCKS#7 format. If you prefer to work with X509 format
public static List<X509Certificate> p7BToX509(byte signedCert[]) throws CertificateException{
ByteArrayInputStream is = new ByteArrayInputStream( signedCert);
CertificateFactory cf = CertificateFactory.getInstance( "X.509" );
ArrayList<X509Certificate> certificates = new ArrayList<X509Certificate>();
Iterator i = cf.generateCertificates( is ).iterator();
while ( i.hasNext() ){
X509Certificate c = (X509Certificate)i.next();
certificates.add(c);
}
return certificates;
}
This is the public certificate. In your client you should have the private key. These are all elements you need to perform and ssl handshake
I've been trying to implement digital signing (CAdES) for PDF files using Portuguese Citizen Card, however I'm having a hard time figuring out the perfectly working solution. Currently I have two sets of code.
First one:
public void signCAdES(...)
{
String pkcs11Config = "name=GemPC" + "\n" + "library=C:\\WINDOWS\\SysWOW64\\pteidpkcs11.dll";
ByteArrayInputStream configStream = new ByteArrayInputStream(pkcs11Config.getBytes());
Provider pkcs11Provider = new sun.security.pkcs11.SunPKCS11(configStream);
//provider_name: SunPKCS11-GemPC
Security.addProvider(pkcs11Provider);
javax.security.auth.callback.CallbackHandler cmdLineHdlr = new DialogCallbackHandler();
KeyStore.Builder builder = KeyStore.Builder.newInstance("PKCS11", pkcs11Provider,
new KeyStore.CallbackHandlerProtection(cmdLineHdlr));
KeyStore ks= builder.getKeyStore();
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', new File(tempPath), true);
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason(reason);
appearance.setLocation(location);
appearance.setCertificationLevel(level);
String alias = "CITIZEN SIGNATURE CERTIFICATE";
//certificates from electronic card and resources folder
Certificate[] certs = getSignatureCertificatesChain(ks);
PrivateKey pk = (PrivateKey) ks.getKey(alias, null);
ExternalSignature es = new PrivateKeySignature(pk, "SHA-1", pkcs11Provider.getName());
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance, digest, es, certs, null, null, null, 0, MakeSignature.CryptoStandard.CADES);
}
The first one works, however I have a validator given to me that verifies if the signatures of a PDF satisfies the standards, and it seems that one of the attributes is missing (sigining certificate issuer's serial number).
The second one is different, and I have to add the attributes manually, however the generated PDF is corrupted (and then I might need to add the issuer serial attribute too):
private static void signCAdES(byte[] aDocument, PrivateKey aPrivateKey, Certificate[] certChain, String outputPath) {
try {
Security.addProvider(new BouncyCastleProvider());
ArrayList<X509Certificate> certsin = new ArrayList<X509Certificate>();
for (Certificate certChain1 : certChain) {
certsin.add((X509Certificate) certChain1);
}
X509Certificate signingCertificate= certsin.get(0);
MessageDigest dig = MessageDigest.getInstance("SHA-1");
byte[] certHash = dig.digest(signingCertificate.getEncoded());
ESSCertID essCertid = new ESSCertID(certHash);
DERSet set = new DERSet(new SigningCertificate(essCertid));
Attribute certHAttribute = new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificate, set);
AttributeTable at = getAttributeTableWithSigningCertificateAttribute(certHAttribute);
CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(at);
SignerInfoGeneratorBuilder genBuild = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider());
genBuild.setSignedAttributeGenerator(attrGen);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner shaSigner = new JcaContentSignerBuilder("SHA1withRSA").build(aPrivateKey);
SignerInfoGenerator sifGen = genBuild.build(shaSigner, new X509CertificateHolder(signingCertificate.getEncoded()));
gen.addSignerInfoGenerator(sifGen);
JcaCertStore jcaCertStore = new JcaCertStore(certsin);
gen.addCertificates(jcaCertStore);
CMSTypedData msg = new CMSProcessableByteArray(aDocument);
CMSSignedData sigData = gen.generate(msg, false); // false=detached
byte[] encoded = sigData.getEncoded();
ASN1InputStream in = new ASN1InputStream(encoded);
CMSSignedData sigData2 = new CMSSignedData(new CMSProcessableByteArray(aDocument), in);
byte[] encoded2 = sigData2.getEncoded();
FileOutputStream fos = new FileOutputStream(outputPath);
fos.write(encoded2);
// fos.write(encoded);
fos.flush();
fos.close();
} catch (CMSException | IOException | OperatorCreationException | CertificateEncodingException ex) {
log("signCAdES", "Error: " + ex.toString());
}
}
Is there anyone who understands CAdES digital signature using Java? Any help would be appreciated!
The 'issuer-serial' attribute is absent or does not match!
It means that your cades signature has not signed attribute: the signed reference to the signing certificate or that this reference is tampered.
Please check: ETSI TS 101 733 V2.2.1 (2013-04) for more information:
5.7.3 Signing Certificate Reference Attributes
The Signing certificate reference attributes are supported by using either the
ESS signing-certificate attribute or the ESS-signing-certificate-v2 attribute...
I cannot find any code/doc describing how to sign a CSR using BC. As input I have a CSR as a byte array and would like to get the cert in PEM and/or DER format.
I have gotten this far
def signCSR(csrData:Array[Byte], ca:CACertificate, caPassword:String) = {
val csr = new PKCS10CertificationRequestHolder(csrData)
val spi = csr.getSubjectPublicKeyInfo
val ks = new java.security.spec.X509EncodedKeySpec(spi.getDEREncoded())
val kf = java.security.KeyFactory.getInstance("RSA")
val pk = kf.generatePublic(ks)
val (caCert, caPriv) = parsePKCS12(ca.pkcs12data, caPassword)
val fromDate : java.util.Date = new java.util.Date // FixMe
val toDate = fromDate // FixMe
val issuer = PrincipalUtil.getIssuerX509Principal(caCert)
val contentSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(caPriv)
val serial = BigInt(CertSerialnumber.nextSerialNumber)
val certgen = new JcaX509v3CertificateBuilder(new X500Name(issuer.getName), serial.bigInteger, fromDate, toDate, csr.getSubject, pk)
I have trouble figuring out get from a certificate generator to store this in PEM or DER format.
Or am I going down the wrong path all together?
Ok ... I was looking to do the same stuff and for the life of me I couldn't figure out how. The APIs all talk about generating the key pairs and then generating the cert but not how to sign a CSR. Somehow, quite by chance - here's what I found.
Since PKCS10 represents the format of the request (of the CSR), you first need to put your CSR into a PKCS10Holder. Then, you pass it to a CertificateBuilder (since CertificateGenerator is deprecated). The way you pass it is to call getSubject on the holder.
Here's the code (Java, please adapt as you need):
public static X509Certificate sign(PKCS10CertificationRequest inputCSR, PrivateKey caPrivate, KeyPair pair)
throws InvalidKeyException, NoSuchAlgorithmException,
NoSuchProviderException, SignatureException, IOException,
OperatorCreationException, CertificateException {
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder()
.find("SHA1withRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder()
.find(sigAlgId);
AsymmetricKeyParameter foo = PrivateKeyFactory.createKey(caPrivate
.getEncoded());
SubjectPublicKeyInfo keyInfo = SubjectPublicKeyInfo.getInstance(pair
.getPublic().getEncoded());
PKCS10CertificationRequestHolder pk10Holder = new PKCS10CertificationRequestHolder(inputCSR);
//in newer version of BC such as 1.51, this is
//PKCS10CertificationRequest pk10Holder = new PKCS10CertificationRequest(inputCSR);
X509v3CertificateBuilder myCertificateGenerator = new X509v3CertificateBuilder(
new X500Name("CN=issuer"), new BigInteger("1"), new Date(
System.currentTimeMillis()), new Date(
System.currentTimeMillis() + 30 * 365 * 24 * 60 * 60
* 1000), pk10Holder.getSubject(), keyInfo);
ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
.build(foo);
X509CertificateHolder holder = myCertificateGenerator.build(sigGen);
X509CertificateStructure eeX509CertificateStructure = holder.toASN1Structure();
//in newer version of BC such as 1.51, this is
//org.spongycastle.asn1.x509.Certificate eeX509CertificateStructure = holder.toASN1Structure();
CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
// Read Certificate
InputStream is1 = new ByteArrayInputStream(eeX509CertificateStructure.getEncoded());
X509Certificate theCert = (X509Certificate) cf.generateCertificate(is1);
is1.close();
return theCert;
//return null;
}
As you can see, I've generated the request outside this method, but passed it in. Then, I have the PKCS10CertificationRequestHolder to accept this as a constructor arg.
Next, in the X509v3CertificateBuilder arguments, you'll see the pk10Holder.getSubject - this is apparently all you need? If something is missing, please let me know too!!! It worked for me. The cert I generated correctly had the DN info I needed.
Wikipedia has a killer section on PKCS - http://en.wikipedia.org/wiki/PKCS
The following code is based on the above answers but will compile and, given a PEM encoded CSR (of the kind exported by keytool), will return a valid PEM-encoded signedData object containing a signed Certificate chain (of the type that can be imported by keytool).
Oh and it's against BouncyCastle 1.49.
import java.security.*;
import java.io.*;
import java.util.Date;
import java.math.BigInteger;
import java.security.cert.X509Certificate;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.asn1.x500.*;
import org.bouncycastle.asn1.pkcs.*;
import org.bouncycastle.openssl.*;
import org.bouncycastle.pkcs.*;
import org.bouncycastle.cert.*;
import org.bouncycastle.cms.*;
import org.bouncycastle.cms.jcajce.*;
import org.bouncycastle.crypto.util.*;
import org.bouncycastle.operator.*;
import org.bouncycastle.operator.bc.*;
import org.bouncycastle.operator.jcajce.*;
import org.bouncycastle.util.encoders.Base64;
/**
* Given a Keystore containing a private key and certificate and a Reader containing a PEM-encoded
* Certificiate Signing Request (CSR), sign the CSR with that private key and return the signed
* certificate as a PEM-encoded PKCS#7 signedData object. The returned value can be written to a file
* and imported into a Java KeyStore with "keytool -import -trustcacerts -alias subjectalias -file file.pem"
*
* #param pemcsr a Reader from which will be read a PEM-encoded CSR (begins "-----BEGIN NEW CERTIFICATE REQUEST-----")
* #param validity the number of days to sign the Certificate for
* #param keystore the KeyStore containing the CA signing key
* #param alias the alias of the CA signing key in the KeyStore
* #param password the password of the CA signing key in the KeyStore
*
* #return a String containing the PEM-encoded signed Certificate (begins "-----BEGIN PKCS #7 SIGNED DATA-----")
*/
public static String signCSR(Reader pemcsr, int validity, KeyStore keystore, String alias, char[] password) throws Exception {
PrivateKey cakey = (PrivateKey)keystore.getKey(alias, password);
X509Certificate cacert = (X509Certificate)keystore.getCertificate(alias);
PEMReader reader = new PEMReader(pemcsr);
PKCS10CertificationRequest csr = new PKCS10CertificationRequest((CertificationRequest)reader.readObject());
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
X500Name issuer = new X500Name(cacert.getSubjectX500Principal().getName());
BigInteger serial = new BigInteger(32, new SecureRandom());
Date from = new Date();
Date to = new Date(System.currentTimeMillis() + (validity * 86400000L));
X509v3CertificateBuilder certgen = new X509v3CertificateBuilder(issuer, serial, from, to, csr.getSubject(), csr.getSubjectPublicKeyInfo());
certgen.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
certgen.addExtension(X509Extension.subjectKeyIdentifier, false, new SubjectKeyIdentifier(csr.getSubjectPublicKeyInfo()));
certgen.addExtension(X509Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifier(new GeneralNames(new GeneralName(new X509Name(cacert.getSubjectX500Principal().getName()))), cacert.getSerialNumber()));
ContentSigner signer = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(PrivateKeyFactory.createKey(cakey.getEncoded()));
X509CertificateHolder holder = certgen.build(signer);
byte[] certencoded = holder.toASN1Structure().getEncoded();
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
signer = new JcaContentSignerBuilder("SHA1withRSA").build(cakey);
generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(signer, cacert));
generator.addCertificate(new X509CertificateHolder(certencoded));
generator.addCertificate(new X509CertificateHolder(cacert.getEncoded()));
CMSTypedData content = new CMSProcessableByteArray(certencoded);
CMSSignedData signeddata = generator.generate(content, true);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write("-----BEGIN PKCS #7 SIGNED DATA-----\n".getBytes("ISO-8859-1"));
out.write(Base64.encode(signeddata.getEncoded()));
out.write("\n-----END PKCS #7 SIGNED DATA-----\n".getBytes("ISO-8859-1"));
out.close();
return new String(out.toByteArray(), "ISO-8859-1");
}
Archie thanks!
I made some changes to you code, see below.
The main changes are to pass the issuer's name and use the public key from the CSR.
val caCert = PEMToCert(issuerPEM).get
val issuer = PrincipalUtil.getIssuerX509Principal(caCert)
val csr = new PKCS10CertificationRequestHolder(csrData)
val serial = BigInt(CertSerialNumber.nextSerialNumber)
val spi = csr.getSubjectPublicKeyInfo();
val certgen = new X509v3CertificateBuilder(
new X500Name(issuer.getName),
serial.bigInteger,
new java.util.Date(),
new Date(System.currentTimeMillis() + 30 * 365 * 24 * 60 * 60 * 1000),
csr.getSubject,
csr.getSubjectPublicKeyInfo())
certgen.addExtension(
X509Extension.subjectKeyIdentifier,
false,
spi
)
val issuerPK = PEMToPK(issuerPKPEM, caPassword).get
val contentSigner = new JcaContentSignerBuilder(contentSignerAlg).setProvider(BC).build(issuerPK.getPrivate())
val x509 = (new JcaX509CertificateConverter).setProvider(BC).getCertificate(certgen.build(contentSigner))
#Mike B - have you tested your example thoroughly ? I get a strange behavior with your code:
Im using bc15on version. When I sign the client request with a self signed CA I import it in IE and it shows the certificate as valid with the CA in the chain
However you can see that when imported in FF the images on the right the CA in the chain is missing and ff cannot verify it to a Trusted Authority. Also with IE or FF when attempting to authenticate to the web server with it it fails as http cannot verify it to a trusted authority too.
Ive made some changes to your code just to suit my needs but in general it should be the same, can anyone give me some pointers onto what Im doing wrong here:
public static String GenCert(long SerNum, int addYear, int addHours,
String reqText,
String reqName) throws Exception,
SQLException {
String result = "";
reqText = csr; // hard code base64 csr for testing purposes
reqText =
"-----BEGIN CERTIFICATE REQUEST-----\n" + reqText +
"\n-----END CERTIFICATE REQUEST-----\n";
try {
String castr = ca + "\n"; // hard code base64 CA pub key for testing
String strPriv = caPrivk + "\n"; // hard code base64 CA private key for testing
byte[] encKey = castr.getBytes();
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert =
(X509Certificate)cf.generateCertificate(new ByteArrayInputStream(encKey));
PEMParser pr = new PEMParser(new StringReader(strPriv));
Object obj = pr.readObject();
JcaPEMKeyConverter converter =
new JcaPEMKeyConverter().setProvider("BC");
KeyPair kp;
kp = converter.getKeyPair((PEMKeyPair)obj);
PrivateKey privateKey = kp.getPrivate();
// parse the request
PEMParser pRd =
new PEMParser(new InputStreamReader(new ByteArrayInputStream(reqText.getBytes())));
PKCS10CertificationRequest csr =
(PKCS10CertificationRequest)pRd.readObject();
String strReq = csr.getSubject().toString();
strReq = strReq.substring(strReq.indexOf("CN=") + 3).trim();
if (strReq.indexOf(",") > 0)
strReq = strReq.substring(0, strReq.indexOf(",")).trim();
if (!strReq.equals(reqName)) {
return "";
}
AlgorithmIdentifier sigAlgId =
new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA"); //SHA1withRSA
AlgorithmIdentifier digAlgId =
new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
X500Name issuer =
new X500Name(caCert.getSubjectX500Principal().getName());
BigInteger serial = BigInteger.valueOf(SerNum);
// The date object returns GMT format
Date date = new Date(System.currentTimeMillis() - 180 * 1000);
date.setHours(date.getHours() + addHours);
Calendar cal = Calendar.getInstance();
Date from = date;
cal.setTime(date);
cal.add(1, addYear);
Date to = cal.getTime();
SubjectPublicKeyInfo pkInfo = csr.getSubjectPublicKeyInfo();
//SubjectPublicKeyInfo pkInfo = SubjectPublicKeyInfo.getInstance(kp.getPublic().getEncoded());
RSAKeyParameters rsa =
(RSAKeyParameters)PublicKeyFactory.createKey(pkInfo);
RSAPublicKeySpec rsaSpec =
new RSAPublicKeySpec(rsa.getModulus(), rsa.getExponent());
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey rsaPub = kf.generatePublic(rsaSpec);
X509v3CertificateBuilder certgen =
new X509v3CertificateBuilder(issuer, serial, from, to,
csr.getSubject(),
csr.getSubjectPublicKeyInfo());
certgen.addExtension(X509Extension.basicConstraints, false,
new BasicConstraints(false));
certgen.addExtension(X509Extension.subjectKeyIdentifier, false,
new SubjectKeyIdentifier(pkInfo));
// certgen.addExtension(X509Extension.subjectKeyIdentifier, false,
// new SubjectKeyIdentifierStructure(rsaPub)); // In old version done with much more extensive parsing
certgen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
new AuthorityKeyIdentifierStructure(caCert));
// certgen.addExtension(X509Extension.authorityKeyIdentifier, false,
// new AuthorityKeyIdentifier(new GeneralNames(new GeneralName(new X509Name(caCert.getSubjectX500Principal().getName()))),
// caCert.getSerialNumber()));
// add certificate purposes
ASN1EncodableVector vector = new ASN1EncodableVector();
vector.add(new DERObjectIdentifier("1.3.6.1.5.5.7.3.2"));
vector.add(new DERObjectIdentifier("1.3.6.1.4.1.311.20.2.2"));
vector.add(new DERObjectIdentifier("1.3.6.1.4.1.311.10.3.12"));
vector.add(new DERObjectIdentifier("1.3.6.1.5.5.7.3.4"));
DERSequence seq = new DERSequence(vector);
certgen.addExtension(X509Extensions.ExtendedKeyUsage, false, seq);
ContentSigner signer =
new BcRSAContentSignerBuilder(sigAlgId,
digAlgId).build(PrivateKeyFactory.createKey(privateKey.getEncoded()));
X509CertificateHolder holder = certgen.build(signer);
byte[] certencoded = holder.toASN1Structure().getEncoded();
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
signer =
new JcaContentSignerBuilder("SHA1withRSA").build(privateKey);
generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(signer,
caCert));
generator.addCertificate(new X509CertificateHolder(certencoded));
generator.addCertificate(new X509CertificateHolder(caCert.getEncoded()));
CMSTypedData content = new CMSProcessableByteArray(certencoded);
CMSSignedData signeddata = generator.generate(content, true);
result = Base64Utils.base64Encode(signeddata.getEncoded());
} catch (Exception e) {
result = e.toString();
getStackTrace(e);
}
return result;
}
In the old version of my code where I used bouncy castle 1.4 we used the X509V3CertificateGenerator and just before returning the content we used to build the chain like so:
X509Certificate newCert =
certGen.generateX509Certificate(privateKey, "BC");
//=============================
List chain = new ArrayList();
chain.add(newCert);
//-------------------------------------------------
// create the CertPath with old BouncyCastle
CertificateFactory fact =
CertificateFactory.getInstance("X.509", "BC");
CertPath path = fact.generateCertPath(chain);
result = Base64Utils.base64Encode(path.getEncoded("PKCS7"));
UPDATE: OK Case solved. Thanks to this thread Obviously when using:
cacert.getSubjectX500Principal().getName()
I got the names of the issuer in reverse, which broke the chain, using instead:
cert.getSubjectX500Principal().getEncoded() solved it for me! So when your CA does not get verified upto trusted authority make sure you are getting the names correctly.
In the end, this is what worked for me:
KeyPair serverKeyPair = keyPairLoader.getKeyPair(); //my own class
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509", "BC");
X509Certificate serverCertificate = getServerCertificate(certificateFactory);
org.spongycastle.asn1.x509.Certificate eeX509CertificateStructure = signCertificateSigningRequest(
jcaPKCS10CertificationRequest, keyPair, serverCertificate);
java.security.cert.X509Certificate signedCertificate = readCertificateFromASN1Certificate(
eeX509CertificateStructure, certificateFactory);
Where code is
private org.spongycastle.asn1.x509.Certificate signCertificateSigningRequest(
JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest,
KeyPair keyPair, X509Certificate serverCertificate)
throws IOException, OperatorCreationException, NoSuchAlgorithmException, InvalidKeyException
{
// Signing CSR
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder()
.find("SHA1withRSA");
X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
serverCertificate,
new BigInteger("1"), //serial
new Date(System.currentTimeMillis()),
new Date(System.currentTimeMillis() + 30L * 365L * 24L * 60L * 60L * 1000L),
jcaPKCS10CertificationRequest.getSubject(),
jcaPKCS10CertificationRequest.getPublicKey()
/*).addExtension(
new ASN1ObjectIdentifier("2.5.29.35"),
false,
new AuthorityKeyIdentifier(keyPair.getPublic().getEncoded())*/
).addExtension(
new ASN1ObjectIdentifier("2.5.29.19"),
false,
new BasicConstraints(false) // true if it is allowed to sign other certs
).addExtension(
new ASN1ObjectIdentifier("2.5.29.15"),
true,
new X509KeyUsage(
X509KeyUsage.digitalSignature |
X509KeyUsage.nonRepudiation |
X509KeyUsage.keyEncipherment |
X509KeyUsage.dataEncipherment));
AsymmetricKeyParameter asymmetricKeyParameter =
PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded());
//ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(asymmetricKeyParameter);
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").build(keyPair.getPrivate());
X509CertificateHolder x509CertificateHolder = certificateBuilder.build(sigGen);
org.spongycastle.asn1.x509.Certificate eeX509CertificateStructure =
x509CertificateHolder.toASN1Structure();
return eeX509CertificateStructure;
}
private X509Certificate readCertificateFromASN1Certificate(
org.spongycastle.asn1.x509.Certificate eeX509CertificateStructure,
CertificateFactory certificateFactory)
throws IOException, CertificateException {
// Read Certificate
InputStream is1 = new ByteArrayInputStream(eeX509CertificateStructure.getEncoded());
X509Certificate signedCertificate =
(X509Certificate) certificateFactory.generateCertificate(is1);
return signedCertificate;
}
And this can be converted to PEM:
private String convertCertificateToPEM(X509Certificate signedCertificate) throws IOException {
StringWriter signedCertificatePEMDataStringWriter = new StringWriter();
JcaPEMWriter pemWriter = new JcaPEMWriter(signedCertificatePEMDataStringWriter);
pemWriter.writeObject(signedCertificate);
pemWriter.close();
log.info("PEM data:");
log.info("" + signedCertificatePEMDataStringWriter.toString());
return signedCertificatePEMDataStringWriter.toString();
}
Here's my solution in C# using BouncyCastle NuGet package version 1.8.9:
public static X509Certificate2 SignCertificate(TextReader pemEncodedCsrReader, X509Certificate2 caCertificate, AsymmetricKeyParameter issuerPrivateKey)
{
var csr = (Pkcs10CertificationRequest)new PemReader(pemEncodedCsrReader).ReadObject();
var certificateGenerator = new X509V3CertificateGenerator();
certificateGenerator.SetNotBefore(DateTime.Now.Date);
certificateGenerator.SetNotAfter(DateTime.Now.Date.AddYears(20));
certificateGenerator.SetIssuerDN(new X509Name(caCertificate.IssuerName.Name));
certificateGenerator.SetSubjectDN(csr.GetCertificationRequestInfo().Subject);
certificateGenerator.SetPublicKey(PublicKeyFactory.CreateKey(csr.GetCertificationRequestInfo().SubjectPublicKeyInfo);
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
certificateGenerator.AddExtension(X509Extensions.BasicConstraints, false, new BasicConstraints(cA: false));
certificateGenerator.AddExtension(X509Extensions.SubjectKeyIdentifier, false,
new SubjectKeyIdentifier(csr.GetCertificationRequestInfo().SubjectPublicKeyInfo));
certificateGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false,
new AuthorityKeyIdentifier(new GeneralNames(new GeneralName(new X509Name(caCertificate.SubjectName.Name))), new BigInteger(caCertificate.SerialNumber, 16)));
var signatureFactory = new Asn1SignatureFactory(SignatureAlgorithm, issuerPrivateKey, random);
var certificate = certificateGenerator.Generate(signatureFactory);
return new X509Certificate2(certificate.GetEncoded());
}