My java applet is able to generate a CMSSignedData using Bouncy Castle 1.4.9 of the type detached. The byte array sigData.getEncoded() is then stored in a table on the server (which has access to the content data not enclosed). Now I would like to create the enveloped CMSSignedData in the server in order for the user to download a .p7m file.
The function I need to develop has the byte array of the detached signature and the byte array of the content data and must return the byte array of a CaDES enveloped signature, which will be used to download the .p7m file.
The problem is that I was not able to convert the detached signatore to an enveloped one.
Here is some code I used in my applet:
signCAdeS signs the document using a detached signature, then calls attach (just for testing) to convert the detached to enveloped but without success: Opening the created .p7m file with Dike it is not possible to view the content data.
private byte[] signCAdES(byte[] aDocument, PrivateKey aPrivateKey, Certificate[] certChain)
throws GeneralSecurityException {
byte[] digitalSignature = null;
try {
Security.addProvider(new BouncyCastleProvider());
ArrayList<X509Certificate> certsin = new ArrayList<X509Certificate>();
for (int i = 0; i < certChain.length; i++) {
certsin.add((X509Certificate) certChain[i]);
}
X509Certificate cert = certsin.get(0);
//Nel nuovo standard di firma digitale e' richiesto l'hash del certificato di sottoscrizione:
String digestAlgorithm = "SHA-256";
String digitalSignatureAlgorithmName = "SHA256withRSA";
MessageDigest sha = MessageDigest.getInstance(digestAlgorithm);
byte[] digestedCert = sha.digest(cert.getEncoded());
//Viene ora create l'attributo ESSCertID versione 2 cosi come richiesto nel nuovo standard:
AlgorithmIdentifier aiSha256 = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256);
ESSCertIDv2 essCert1 = new ESSCertIDv2(aiSha256, digestedCert);
ESSCertIDv2[] essCert1Arr = {essCert1};
SigningCertificateV2 scv2 = new SigningCertificateV2(essCert1Arr);
Attribute certHAttribute = new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new DERSet(scv2));
//Aggiungiamo l'attributo al vettore degli attributi da firmare:
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(certHAttribute);
AttributeTable at = new AttributeTable(v);
CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(at);
//Creaiamo l'oggetto che firma e crea l'involucro attraverso le librerie di Bouncy Castle:
SignerInfoGeneratorBuilder genBuild = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider());
genBuild.setSignedAttributeGenerator(attrGen);
//Si effettua la firma con l'algoritmo SHA256withRSA che crea l'hash e lo firma con l'algoritmo RSA:
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner shaSigner = new JcaContentSignerBuilder("SHA256withRSA").build(aPrivateKey);
SignerInfoGenerator sifGen = genBuild.build(shaSigner, new X509CertificateHolder(cert.getEncoded()));
gen.addSignerInfoGenerator(sifGen);
X509CollectionStoreParameters x509CollectionStoreParameters = new X509CollectionStoreParameters(certsin);
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();
FileOutputStream fos = new FileOutputStream("H:\\prova2.txt.p7m");
fos.write(attach(aDocument, encoded));
fos.flush();
fos.close();
digitalSignature = encoded;
} catch (CMSException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
} catch (OperatorCreationException ex) {
ex.printStackTrace();
}
return digitalSignature;
}
Here is the attach function:
public static byte[] attach(byte[] originalData, byte[] detached) {
try {
ASN1InputStream in = new ASN1InputStream(detached);
CMSSignedData sigData = new CMSSignedData(new CMSProcessableByteArray(originalData), in);
return sigData.getEncoded();
} catch (Exception e) {
return null;
}
}
This response comes a little late. Surely you will have found an alternative because it is not possible to convert a detached CAdES into enveloping
Take a look to the ASN.1 structure of SignedData node of a CAdES signature as defined in RFC 3852 -CMS
SignedData includes encapContentInfo that contains the original data to sign for enveloping signatures but is not present in a detached signature. Since SignedData is signed, it is not possible to add the original data to convert a detached into enveloping
Note: The correct term is enveloping, because signature wraps the data. An enveloped signature would be embedded into the data
adapted from http://bouncy-castle.1462172.n4.nabble.com/Add-signed-content-to-detached-signatures-td1467150.html
public static byte[] attach(byte[] originalData, byte[] detached) throws IOException {
try (ASN1InputStream in = new ASN1InputStream(detached);) {
BERSequence seqContentData = (BERSequence) in.readObject();
ContentInfo contentInfo = ContentInfo.getInstance(seqContentData);
SignedData sdDetached = new SignedData((ASN1Sequence) contentInfo.getContent());
ContentInfo encapContentInfo = new ContentInfo(CMSObjectIdentifiers.data, new BEROctetString(originalData));
SignedData sdAttached = new SignedData(
sdDetached.getVersion(),
sdDetached.getDigestAlgorithms(),
encapContentInfo,
sdDetached.getCertificates(),
sdDetached.getCRLs(),
sdDetached.getSignerInfos());
ContentInfo contentInfoAttached = new ContentInfo(PKCSObjectIdentifiers.signedData, sdAttached);
return contentInfoAttached.getEncoded();
}
}
tested with BC 1.64
Related
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 am trying to digitally sign xml document and verify the signature with the original xml file with public key and signed document. I have a java code for reference. I need to convert java code to C# where I have java code like this:
certList = new ArrayList<X509Certificate>();
certList.add(signerCert);
certStore = new JcaCertStore(certList);
signedDataGenerator = new CMSSignedDataGenerator();
ContentSigner sha2Signer = new JcaContentSignerBuilder("SHA512with" + privateKey.getAlgorithm()).build(privateKey);
ignedDataGenerator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).setDirectSignature(true).build(sha2Signer, signerCert));
signedDataGenerator.addCertificates(certStore);
CMSSignedData sigData = signedDataGenerator.generate(new CMSProcessableFile(inputXmlFile), false);
signedBytes = sigData.getEncoded();
I have converted java code to C# like this:
X509Store my = new X509Store(StoreName.My, StoreLocation.LocalMachine);
my.Open(OpenFlags.ReadOnly);
// Find the certificate we’ll use to sign
RSACryptoServiceProvider csp = null;
foreach (X509Certificate2 cert in my.Certificates)
{
if (cert.Subject.Contains(certSubject))
{
// We found it.
// Get its associated CSP and private key
csp = (RSACryptoServiceProvider)cert.PrivateKey;
}
}
if (csp == null)
{
throw new Exception("oppose no valid application was found");
}
// Hash the data
SHA512Managed sha1 = new SHA512Managed();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(text);
byte[] hash = sha1.ComputeHash(data);
// Sign the hash
return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
I am trying to convert it since two days, It is generating sign byte array but not been able to verify. While verifying it is throwing bad hash\r\n error I shall be highly grateful for any assistance. I know I am somewhere wrong in converting the java code to C#. I am able to verify the code but not been able to sign the document
I have generated Signature using System.Security.Cryptography.Pkcs library like this
public static byte[] Sign(byte[] data, X509Certificate2 certificate)
{
if (data == null)
throw new ArgumentNullException("data");
if (certificate == null)
throw new ArgumentNullException("certificate");
// setup the data to sign
ContentInfo content = new ContentInfo(data);
SignedCms signedCms = new SignedCms(content, false);
CmsSigner signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, certificate);
// create the signature
signedCms.ComputeSignature(signer);
return signedCms.Encode();
}
and verify the signature like this
private static bool VerifySignatures(FileInfo contentFile, Stream signedDataStream)
{
CmsProcessable signedContent = null;
CmsSignedData cmsSignedData = null;
Org.BouncyCastle.X509.Store.IX509Store store = null;
ICollection signers = null;
bool verifiedStatus = false;
try
{
//Org.BouncyCastle.Security.addProvider(new BouncyCastleProvider());
signedContent = new CmsProcessableFile(contentFile);
cmsSignedData = new CmsSignedData(signedContent, signedDataStream);
store = cmsSignedData.GetCertificates("Collection");//.getCertificates();
IX509Store certStore = cmsSignedData.GetCertificates("Collection");
signers = cmsSignedData.GetSignerInfos().GetSigners();
foreach (var item in signers)
{
SignerInformation signer = (SignerInformation)item;
var certCollection = certStore.GetMatches(signer.SignerID);
IEnumerator iter = certCollection.GetEnumerator();
iter.MoveNext();
var cert = (Org.BouncyCastle.X509.X509Certificate)iter.Current;
verifiedStatus = signer.Verify(cert.GetPublicKey());
}
}
catch (Exception e)
{
throw e;
}
return verifiedStatus;
}
It is working for me
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'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...