I'm having problems signing pdf's with pdfbox.
The idea is the same as used with itext (Java IText7 PDF Sign Problem - Document has been altered or corrupted since it was signed). Get bytearray from a pdf with empty signature, send byte array to an external entity that returns a hash-signature, and embed that hash in pdf with empty signature.
PDF error:
Invalid signature
There are errors in formatting or in the information contained in this signature.
The java code (small resume) that i use are:
//GET External signing content
PDDocument doc = PDDocument.load(inputPDF);
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setReason(sigReason);
signature.setLocation(sigLocation);
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
Calendar cal = Calendar.getInstance();
cal.setTime(sdf.parse(sdf.format(forcedDate)));
signature.setSignDate(cal);
SignatureOptions signatureOptions = new SignatureOptions();
signatureOptions.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 8);
doc.addSignature(signature,signatureOptions);
ExternalSigningSupport externalSigningSupport = doc.saveIncrementalForExternalSigning(null);
byte[] content = IOUtils.toByteArray(externalSigningSupport.getContent());
MessageDigest md = MessageDigest.getInstance("SHA256", new BouncyCastleProvider());
hashtosign = md.digest(content); // this is sent to client
return hashtosign;
// CALL AMA(entity - external client webservice) TO GET SIGNATURE-HASH (signatureHash)
String hashToSendAMA = SafePdfHelper.getHashtoSign(hashtosign);
String signatureHash_B64 = SafeAmaHelper.getAssinat(token,hashToSendAMA,"tst_nunommc",credentialID).getSignatures().get(0);
// SIGNATURE-HASH
byte[] signatureHash = Base64.getDecoder().decode(String.valueOf(signatureHash_B64.toCharArray()));
//InsertHash SIGNATURE-HASH in PDF
#code
ExternalSigningSupport externalSigningSupport = doc.saveIncrementalForExternalSigning(fosSigned);
externalSigningSupport.setSignature(signatureHash);
doc.save("C:/INTEGRACOES/Ama/omitf/tst_signed.pdf");
return "Signed";
Base64 format of the signature (AMA) is:
PMPk04d/sj5OxRZpZfGSJlrQthryaWjmj6tNs3a7g3CgOB02c/a9omVaZD1Upl87XI/FvVMBMoKlGcm7MRO+ENgKApr9O1/joKN5dnucm11OXL8rxov/EAV8cQOLqCVTNvsDDylDy1L7LdYSBCDzvqCKmR8OF7955wyZJeTDiCcYDm3gGN9IDQ82fwMKPuiIt7e1ToZdm7qzLBLTr38K2eM784NUNCFMI152QdtZIxQHl03qXk6IrNoWxW0axh9YcJmbBWlkUo75wm6BjuJzkPOYgaT4/CWTyjuCKtYg748gQQmh89xbBswmtZxw7NRVRXl4DtWE9ceRClKZx0JrIawzkWNhJt42+u62ntNs/Z4a/LRNg8rfmru2C9mhqZ5h6jfIJzk9bX219csIekM46pXofEizAnnMTjDZLWYedHFWojJGybdA76fVlBQx+9nTA7xD/gqDY9NUKW0bjfF9j+diHxNZklZ+0+RzUMF9l3Pq9Hlg2InaZM08yUyhdSYl
Can anyone help please?
-sorry for the bad formatting post
I already found a solution. The key was to create a CMS signature container with BouncyCastle.
public static byte[] createSignature(InputStream content, Certificate [] chain, String fileName) throws Exception {
//BufferedInputStream bis = new BufferedInputStream(content);
List<Certificate> certList = new ArrayList<>();
for (Certificate cert: chain){
certList.add(cert);
}
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
gen.addCertificates(certs);
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte [] hashBytes = digest.digest(content.readAllBytes());
String hashToSign = SafePdfHelper.getHashtoSign(hashBytes);
String signatureHash_B64 = SafeAmaHelper.getAssinat(token,hashToSign,fileName,credentialID).getSignatures().get(0); //GET SIGNHASH WITH A EXTERNAL ENTITY
byte[] signedHash = Base64.getDecoder().decode(String.valueOf(signatureHash_B64.toCharArray()));
ContentSigner nonSigner = new ContentSigner() {
#Override
public byte[] getSignature() {
return signedHash;
}
#Override
public OutputStream getOutputStream() {
return new ByteArrayOutputStream();
}
#Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSA");
}
};
JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());
sigb.setDirectSignature(true);
for (Certificate cert: chain){
//Certificate x = cert;
org.bouncycastle.asn1.x509.Certificate certx509 = org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
gen.addSignerInfoGenerator(sigb.build(nonSigner, new X509CertificateHolder(certx509)));
}
CMSTypedData msg = new CMSProcessableInputStream(new ByteArrayInputStream("not used".getBytes()));
CMSSignedData signedData = gen.generate(msg, false);
byte[] signature = signedData.getEncoded();
return signature;
}
thanks #mkl
I'm fighting with creating signature with timestamp on my pdf file. After many attempts we succeeded and signed PDF file. Adobe verified this file but there is one mistake with timestamp. There is information about:
Signature is timestamped but the timestamp could not be verified
Is this signature was created inproperly?
There is a code
public String signByPfxCert(String filePath) {
String postfix = "-signed";
try {
PdfReader reader = new PdfReader(filePath);
OutputStream os = new FileOutputStream(filePath + postfix);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason("REASON");
appearance.setLocation("LOCATION");
Security.addProvider(new BouncyCastleProvider());
FileInputStream fis = new FileInputStream(getClass().getClassLoader().
getResource("clientcert.pfx").getFile());
String password = "pwd12345";
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(fis, password.toCharArray());
String alias = ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, password.toCharArray());
X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
com.itextpdf.text.pdf.security.TSAClient tsc = new TSAClientBouncyCastle(tsaUrl);
ExternalDigest digest = new BouncyCastleDigest();
ExternalSignature signature = new PrivateKeySignature(pk, "SHA-1", "BC");
MakeSignature.signDetached(appearance, digest, signature, new Certificate[]{cert}, null, null, tsc, 0,
MakeSignature.CryptoStandard.CMS);
if (fis.available() != 0) {
fis.close();
}
File originalFile = new File(filePath);
File signedFile = new File(filePath + postfix);
boolean deleteOriginal = originalFile.delete();
File destination = new File(filePath);
boolean rename = signedFile.renameTo(destination);
if(deleteOriginal && rename){
return destination.getName();
}else {
return "";
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
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 would like to create a detached signature in a PDF file using a PKCS7 container. The data (hash) is being signed beforehand on a different device with the private key. I want to create a PKCS7 containing the signed data along with the certificate with the public key. I can't seem to create the PKCS7 with bouncy castle without supplying the private key and having the library signing the data. This doesn't seem to work:
InputStream inStream = new FileInputStream("1_public.pem");
BufferedInputStream bis = new BufferedInputStream( inStream );
CertificateFactory cf = CertificateFactory.getInstance("X.509");
List<Certificate> certList = new ArrayList<Certificate>();
Certificate certificate = cf.generateCertificate(bis);
certList.add(certificate);
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
gen.addCertificates( certs );
CMSProcessableInputStream msg = new CMSProcessableInputStream( new ByteArrayInputStream( "signedhash".getBytes() ) );
CMSSignedData signedData = gen.generate(msg, false);
byte[] pkcs7 = signedData.getEncoded() ) );
I managed to do this by providing a ContentSigner that doesn't sign, actually quite simple:
InputStream inStream = new FileInputStream("1_public.pem");
BufferedInputStream bis = new BufferedInputStream( inStream );
CertificateFactory cf = CertificateFactory.getInstance("X.509");
List<Certificate> certList = new ArrayList<Certificate>();
Certificate certificate = cf.generateCertificate(bis);
certList.add(certificate);
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
gen.addCertificates( certs );
final byte[] signedHash = "signedhash".getBytes();
ContentSigner nonSigner = new ContentSigner() {
#Override
public byte[] getSignature() {
return signedHash;
}
#Override
public OutputStream getOutputStream() {
return new ByteArrayOutputStream();
}
#Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new DefaultSignatureAlgorithmIdentifierFinder().find( "SHA256WithRSA" );
}
};
org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(certificate.getEncoded()));
JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());
sigb.setDirectSignature( true );
gen.addSignerInfoGenerator(sigb.build(nonSigner, new X509CertificateHolder(cert)));
CMSProcessableInputStream msg = new CMSProcessableInputStream( new ByteArrayInputStream( "not used".getBytes() ) );
CMSSignedData signedData = gen.generate(msg, false);
byte[] pkcs7 = signedData.getEncoded();
In case the "external signature" is performed by a hardware device it is possible that it also contains "signed attributes". In this case the code must also contain:
AttributeTable signedAttributes = signer.getSignedAttributes();
signerInfoBuilder.setSignedAttributeGenerator(new SimpleAttributeTableGenerator(signedAttributes));
signatureGenerator.addSignerInfoGenerator(signerInfoBuilder.build(nonSigner, signCertificate));
you should also remove the
signatureGenerator.setDirectSignature(true)
a complete example can be found here https://www.len.ro/work/attach-payload-into-detached-pkcs7-signature/. Since I spend a lot of time searching for a solution and this post provided a vital clue I thought I should complete with the information I still missed in an article. Thanks.
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());
}