Java, Digital Signature with BouncyCastle - java

In Java, I tried to sign a byte[] (which is my sha256 digest of my document) with bouncy castle and a certificate in this specification:
http://www.ebics.org/fileadmin/unsecured/specification/spec_current_EN/EBICS_Specification_2.5_final-16-05-2011.pdf
in chapter 14.1.4.1.1 Digital signature generation.
I found in bouncy's java doc this method:
public static byte[] signer(byte[] datas, Certificat cert) {
try {
List<X509Certificate> certList = new ArrayList<X509Certificate>();
CMSTypedData msg = new CMSProcessableByteArray(datas);
certList.add(cert.getCertificat());
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner sha256signer = new JcaContentSignerBuilder(
"SHA256withRSA").setProvider("BC").build(
cert.getPrivateKey());
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC")
.build()).build(sha256signer, cert.getCertificat()));
gen.addCertificates(certs);
CMSSignedData sigData = gen.generate(msg, true);
return sigData.getEncoded();
}
catch (Exception e) {
throw new RuntimeException(
"Erreur lors de la signature du document", e);
}
I don't know if this signature is really in accordance with PKCS#1 1.5 required by the specification. Do I have to add the padding manually? And the OID for RSA256?

EBICS signature A005 is a RSA signature with SHA-256 digest algorithm and PKCS#1 1.5 padding. However the code sample you pasted here is creating a CMS signature which uses a "low level" RSA signature but is a much more complex structure (for comprehensive details, see RFC 5652 http://www.rfc-editor.org/rfc/rfc5652.txt).
Hopefully, generating the signature you are trying to get is really simple with the java crypto API:
public static byte[] signer(byte[] data, PrivateKey key) {
Signature signer = Signature.getInstance("SHA256WithRSA", "BC");
signer.initSign(key);
signer.update(data);
return signer.sign();
}

Related

Bouncy Castle's CMSSignedData PEM produced data causing parsing issues

I'm trying to wrap PKCS#10 request with PKCS#7/CMS signed object, as there almost no examples on how to do that I've started by wrapping a X.509 instead.
I've used Bouncy Castel's Example, produced the CMSSignedData object, decoded it to PEM, and stored it in the file system, and that works.
The issue is that my CA rejects it with "Error Parsing - ASN bad tag value met", also ASN.1 Editor failed to open the file.
private static void generateCMS(X509Certificate signCert, KeyPair signKP, X509Certificate signedCert) {
CMSTypedData msg = new CMSProcessableByteArray("Hello world!".getBytes());
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256withRSA")
.setProvider("BC").build(signKP.getPrivate());
gen.addCertificate(new X509CertificateHolder(signedCert.getEncoded()));
gen.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
.build(sha1Signer, signCert));
CMSSignedData sigData = gen.generate(msg, true);
ContentInfo cmsSignedDataAsASN1 = sigData.toASN1Structure();
JcaPEMWriter writer = new JcaPEMWriter(new FileWriter("test.p7b"));
writer.writeObject(cmsSignedDataAsASN1);
writer.close();
}
I've noticed something weird, I'm not sure it's related, but when using OpenSSL CMS module for signing certificates the PEM encoded Base 64 always starts with the letters "MII", while my code produced PEM consistently starts with the letters "MIA".
Can someone point me to what I'm missing here?
I figured it out, when org.bouncycastle.asn1.cms.ContentInfo ASN.1 is being written to an OutputStream it's using BER encoding, all I had to do is to get the ASN1Primitive and instruct the decoder to use DER instead.
Here's the code:
ASN1Primitive cmsSignedDataAsASN1 = cmsSignedDataAsASN1.toASN1Primitive()
sigData.toASN1Structure().toASN1Primitive()
PemObject pemObject = new PemObject("CMS",
cmsSignedDataAsASN1.getEncoded(ASN1Encoding.DER));
PemWriter pemWriter = new PemWriter(new FileWriter(fileName));
pemWriter.writeObject(pemObject);
pemWriter.close();

How to extend/add a Bouncycastle digest algorithm for CMS?

With Java I want to cryptographically sign given byte[] hash with RSA and return byte[] signature in the Cryptographic Message Syntax (CMS) format.
I am using the Bouncycastle Java API (BC) for this purpose and face the problem of the not existing NONEwithRSA signature type. (java.lang.IllegalArgumentException: Unknown signature type requested: NONEWITHRSA).
I, unfortunately, cannot change the input nor the required output format so I am required to extend/add to the existing BC algorithm so that I can use NONEwithRSA instead of all the other existing (e.g. SHA256withRSA). How do I do this? I have not found any example in the BC documentation.
My desired usage would be similar to this
byte[] doSigningCMS(byte[] data, X509Certificate cert, PrivateKey key) throws Exception {
CMSSignedDataGenerator signGen = new CMSSignedDataGenerator();
CMSTypedData content = new CMSProcessableByteArray(data);
ContentSigner signer = new JcaContentSignerBuilder("NONEwithRSA").build(key);
DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().build();
signGen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(dcp).build(signer, cert));
return signGen.generate(content, false).getEncoded();
}
What I have tried so far
Adding my own provider for the JCA with "copying" a bunch of BC code and "fake" SHA256withRSA to basically return a NoneDigest instead of SHA256Digest. But this seems to be very wrong.
Adding my own algorithm to BC with ((BouncyCastleProvider) Security.getProvider("BC")).addAlgorithm(ALG_ALIAS, NoneDigest.class.getName()) which does not seem to work (algorithm is not found)
I hope for some advice and guidance to do this (if even possible) "the right way". Thanks.
"NoneWithRSA" is a RSA digital signature without adding the OID of the digest algorithm. The cryptographic provider does not make the digest either. Basically it is a PKCS#1_v15 encryption with the private key.
You can see how it works in this OpenJDK test https://github.com/ddopson/openjdk-test/blob/master/java/security/Signature/NONEwithRSA.java
Since Bouncycastle seems does not support it, I think you can supply your own ContentSigner implementation using the default provider instead of using JcaContentSignerBuilder
It is supposed that the input data is already hashed, so if you are doing a PKCS#1 signature, I think you need to provide the signature algorithm. Looking at the RFC3477 it depends on the hash algorithm used.
A.2.4 RSASSA-PKCS1-v1_5
The object identifier for RSASSA-PKCS1-v1_5 shall be one of the
following. The choice of OID depends on the choice of hash
algorithm: MD2, MD5, SHA-1, SHA-256, SHA-384, or SHA-512.
String sigAlgo = "SHA256WithRSAEncryption"; // "SHA256WithRSAEncryption" for SHA256, "SHA1WithRSAEncryption" for SHA1, etc.
ContentSigner signer = new CustomContentSigner(key, sigAlgo );
public class CustomContentSigner implements ContentSigner {
private AlgorithmIdentifier algorithmIdentifier;
private Signature signature;
private ByteArrayOutputStream outputStream;
public CustomContentSigner(PrivateKey privateKey, String sigAlgo) {
//Utils.throwIfNull(privateKey, sigAlgo);
this.algorithmIdentifier = new DefaultSignatureAlgorithmIdentifierFinder().find(sigAlgo);
try {
this.outputStream = new ByteArrayOutputStream();
this.signature = Signature.getInstance("NONEwithRSA");
this.signature.initSign(privateKey);
} catch (GeneralSecurityException gse) {
throw new IllegalArgumentException(gse.getMessage());
}
}
#Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return algorithmIdentifier;
}
#Override
public OutputStream getOutputStream() {
return outputStream;
}
#Override
public byte[] getSignature() {
try {
signature.update(outputStream.toByteArray());
return signature.sign();
} catch (GeneralSecurityException gse) {
gse.printStackTrace();
return null;
}
}
}
Disclaimer: I do not know if it will work but you can try it

Generate a CSR request in java via SCEP

I am trying to send a Certificate Signing Request from an Android device to a server. The server is working properly with iOS devices and follows a SCEP procedure with OpenSSL.
So here is my problem :
I can send the signed enveloped CSR but the server can't read the enveloped CSR. I have the following error from the server :
pki.rb:26:in initialize: Could not parse the PKCS7: header too long (ArgumentError)
Related ruby server code :
#receive object and put it in object data
[...]
# Verify Input Data
p7sign = OpenSSL::PKCS7.new(data)
store = OpenSSL::X509::Store.new
p7sign.verify(nil, store, nil, OpenSSL::PKCS7::NOVERIFY)
signers = p7sign.signers
# Encrypted data (LINE 26 :)
p7enc = OpenSSL::PKCS7.new(p7sign.data)
# Certificate Signing Request
csr = p7enc.decrypt(ssl.key, ssl.certificate)
# Signed Certificate
request = OpenSSL::X509::Request.new(csr)
Java Code (Android) :
I am using Bouncy Castle to generate the CSR and Volley (Google) to send it.
Main :
//Generate PEM formated CSR
byte[] pemCsr = getPemFromCsr(generateCSR());
//Envelop it in a PKCS#7 object
byte[] envelopedData = getDerFromCMSEnvelopedData(envelopData(pemCsr));
//Sign it in a PKCS#7 object
byte[] signedData = getDerFromCMSSignedData(signData(envelopedData));
sendCsrRequest(signedData);
CSR :
//Generate the CSR
private static PKCS10CertificationRequest genrateCertificationRequest(){
// Build the CN for the cert we
X500NameBuilder nameBld = new X500NameBuilder(BCStyle.INSTANCE);
nameBld.addRDN(BCStyle.CN, "cn");
nameBld.addRDN(BCStyle.O, "o");
nameBld.addRDN(BCStyle.NAME, "name");
X500Name principal = nameBld.build();
// Generate the certificate signing request (csr = PKCS10)
String sigAlg = "SHA1withRSA";
JcaContentSignerBuilder csb = new JcaContentSignerBuilder(sigAlg);
ContentSigner cs = csb.build(privateKey);
DERPrintableString password = new DERPrintableString("mychallenge");
PKCS10CertificationRequestBuilder crb = new JcaPKCS10CertificationRequestBuilder(principal, publicKey);
crb.addAttribute((ASN1ObjectIdentifier) PKCSObjectIdentifiers.pkcs_9_at_challengePassword, password);
PKCS10CertificationRequest csr = crb.build(cs);
return csr;
}
//Envelop the CSR
private static CMSEnvelopedData envelopData(byte[] pemCsr) {
CMSTypedData msg = new CMSProcessableByteArray(pemCsr);
CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();
edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(x509Certificate).setProvider("BC"));
CMSEnvelopedData ed = edGen.generate(msg,new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider("BC").build());
return ed;
}
//Sign the enveloped CSR
private static CMSSignedData signData(byte[] data){
ContentSigner signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privateKey);
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(signer, (X509Certificate) x509Certificate));
CMSTypedData cmsdata = new CMSProcessableByteArray(data);
CMSSignedData signedData = generator.generate(cmsdata, true);
return signedData;
}
I have other code ready to paste (the Volley request, the utils converter) but maybe it is enough for the moment.
SCEP is already working with iOS devices, so the server is clean.
Ruby can create the signed PKCS#7 so I guess that my signing step is OK.
But if I send an empty signed PKCS#7, I surprisingly have the same error.
Thanks in advance for any help.
It seems that the ASN1 of the envelop isn't correct for OpenSSL.
In parallel, Google Volley automatically adds "\n" in the response which also raises problems.

OpenSSL and Bouncy Castle CMS implementation

I'm porting some code from Java to C++ and noticed some difference in CMS generation. In OpenSSL if I use CMS_sign(X509 *signcert, EVP_PKEY *pkey, STACK_OF(X509) *certs, BIO *data, unsigned int flags) and provide pkey with public key different from public key of signer certificate signcert I getting validation error and that is right.
If I do the same thing with CMSSignedDataGenerator class I will get SignedData without any errors. Here is code example:
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
DigestCalculatorProvider dp = new JcaDigestCalculatorProviderBuilder().setProvider(CryptoUtils.BC).build();
JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(dp);
ContentSigner signer = new JcaContentSignerBuilder(cert.getSigAlgName()).setProvider(CryptoUtils.BC).build(privateKey);
gen.addSignerInfoGenerator(builder.build(signer, cert));
CMSSignedData s = gen.generate(msg, true);
In this code example I use certificate with public key not same as public key from private key object and no any exceptions or validation errors. Is it OK or not?

Convert message and signature to BouncyCastle CMSSignedData object

I have an X509CertificateObject, a matching RSAPublicKey and managed to create a byte array containing a valid digital certificate for some message object also as a byte array.
Unfortunately the system I'm building upon only accepts CMSSignedData objects as input.
How do I convert my basic building blocks into such a valid CMSSignedData object?
Background: I'm experimenting with Java Bouncy Castle RSA blind signatures according to this example (digest is SHA512) and need to feed the result into the standard signature processing.
First, you'll probably want to sign your data with a private key. The idea being that the signature should be something only you can create. One you get that the rest should be as follows:
X509Certificate signingCertificate = getSigningCertificate();
//The chain of certificates that issued your signing certificate and so on
Collection&ltX509Certificate&gt certificateChain = getCertificateChain();
PrivateKey pk = getPrivateKey();
byte[] message = "SomeMessage".getBytes();
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
certificateChain.add(signingCertificate);
generator.addCertificates(new CollectionStore(certificateChain));
JcaDigestCalculatorProviderBuilder jcaDigestProvider = new JcaDigestCalculatorProviderBuilder();
jcaDigestProvider.setProvider(new BouncyCastleProvider());
JcaSignerInfoGeneratorBuilder singerInfoGenerator = new JcaSignerInfoGeneratorBuilder(jcaDigestProvider.build());
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
AsymmetricKeyParameter privateKeyParam = PrivateKeyFactory.createKey(pk.getEncoded());
ContentSigner cs = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(privateKeyParam);
SignerInfoGenerator sig = singerInfoGenerator.build(cs, signingCertificate);
generator.addSignerInfoGenerator(sig);
CMSSignedData data = generator.generate(new CMSProcessableByteArray(message), true);

Categories

Resources