I am trying to use JarSigner to sign .jar files with XMSS Signatures.
With the use of the JCA/JCE Post-Quantum Cryptography Provider from BouncyCastle it is possible to generate XMSS and XMSSMT KeyPairs programmatically (example).
In order to use JarSigner it is, as far as I know, crucial to provide a KeyStore and the alias of the entry one wants to sign its code with: jarsigner -keystore myKeystore -storetype JKS -storepass password -keypass password myjarfile.jar keystoreEntryAlias The KeyStore entry contains the Public/Secret KeyPair and the assosiated X.509 Certificate.
The 'normal' way of signing a Jar file with the JarSigner is as follows:
Use keytool to generate the Public/Secret KeyPair and a Certificate then store them in a KeyStore (keytool -genkeypair -alias keystoreEntryAlias -keyalg RSA -sigalg SHA256withRSA -dname CN=MyCompanyName -storetype JKS -keypass password -keystore mykeystore.jks -storepass password)
Use JarSigner to sign the .jar using the SecretKey stored in mykeystore.jks with the alias keysotreEntryAlias (jarsigner -keystore mykeystore.jks -storetype jks -storepass passeword -keypass password myjarfile.jar keystoreEntryAlias)
In order to Sign my file with an XMSS Key I have theoretically two possibilities:
Use BCPQC to create XMSS KeyPairs programmatically, store them in mykeystore and use jarsigner -keystore mykeystore -alias xmss via CLI to sign my file.
Use BCPQC-Provider with the keytool in order to directly via CLI generate XMSS KeyPairs and store them in mykeystore (keytool would here need 2 more arguments: -providerclass org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider and -providerpath C:\Path\to\BouncyCastle\provider\bcprov-jdk15on-160.jar) Then sign the file with the keystore entry using JarSigner
Sadly I run into problems with both possibilities:
As I have not found a way yet to use JarSigner without CLI I need to put my generated XMSS KeyPairs in a KeyStore, but therefor I need the Certificate which includes the Public XMSS Key. BouncyCastle does provide a X.509CertificateBuilder which can be used to generate the needed Certificate, however something seems to go wrong if you take a look at the generated Certificate, especially at Signature Algorithm and Public Key(Source code at bottom [CertificateBuilderExample], using XMSSMT)
It appears keytool only uses the init(int) overload from BCPQCProvider and XMSSKeyPairGeneratorSpi rejects that; it wants AlgorithmParameterSpec specifically XMSSParameterSpec, or no init at all -- and if I try the latter, it does generates a keypair, but the resulting keys can't be encoded and thus can't be stored in KeyStore.
My Question would now be:
Does anyone know of a way to sign .jar Files using XMSS / XMSSMT with JarSigner and can provide a more or less detailed explanation on what he/her did right that I did wrong?
Or if I was wrong about anything I mentioned above provide a correction and point out a way to do so ?
UPDATE 1: I am now able, with the use of another X509CertificateGenerator (Source code at bottom [X509CertificateGenerator]) and intel gathered from here, here and here, to successfully sign jar files programmatically with RSA provided from BouncyCastle (Source code for signing at bottom [RSA_JarSigner]).
If I try to apply the same scheme used to sign with RSA to sign with XMSS or XMSSMT I run into a JarSignerException: Error in signer materialscaused by NoSuchAlgorithmException: unrecognized algorithm name: XMSS (Source code for XMSS/XMSSMT at bottom [SignXMSS] [SignXMSSMT].
Hopefully someone can help me figure out where the problem is!
UPDATE 2: It seems the problem with the generated XMSS (or XMSSMT) certificates is due to the fact that the entry for the Signature Algorithm (which is SHA256withXMSS) is passed as an ASN1ObjectIdentifier which is unknown to the system yet. Thus I did some research to see if BouncyCastle does not by accident have an XMSS Certificate Generator laying around somewhere.. Bingo, here is one!
I shortened the code a bit and came up with 1 generator and 1 verifier (Source code at bottom [XMSSGen] [XMSSVer].
The generator tho gives me the same Certificates I already got with the other methods (such as [X509CertificateGenerator]).
The verifier sadly prompts me this ugly error: Exception in thread "main" java.security.spec.InvalidKeySpecException: java.lang.ClassCastException: org.bouncycastle.asn1.DLSequence cannot be cast to org.bouncycastle.asn1.ASN1Integer at org.bouncycastle.pqc.jcajce.provider.xmss.XMSSKeyFactorySpi.engineGeneratePrivate(Unknown Source) at java.base/java.security.KeyFactory.generatePrivate(KeyFactory.java:384) at BCXMSSCertificateVerifyer.verifyCertificate(BCXMSSCertificateVerifyer.java:32) at BCXMSSCertificateTester.main(BCXMSSCertificateTester.java:23)
Maybe someone has an idea where it comes from / how to fix it. In order to see if BC it self can work with its own created XMSS Certificates.
Edit: One problem with the verifier: PrivateKey privKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); why would we need a private Key to verify the Certificate ? Makes no sense - just remove it and it works ^^ (or at least should)
UPDATE 3: I finally managed to get the the verifier to work properly, so I am currently able to produce and verify XMSS Certificates. I am also able to store XMSS KeyPairs with a Certificate containing the PublicKey in a KeyStore. Though I am still not able to sign any .jar file with.
Now comes something a bit strange: I am able to sign things with the BC XMSS KeyPairs (of course I am, thats what they got made for) tho if I save them (or at least the PrivateKey, as he is required to sign stuff) and reload it afterwards to sign stuff again with it, it does not work. Neither if I store them in a KeyStore and retrieve them nor if I save the Key as encoded Bytes to a file and load them again. (If your interested in the code, just comment and I'll post it here)
My suggestion is: as the XMSS Signature Scheme requires a state (the state of the OTS already used) to be saved, that this state somehow cannot be retrieved from the PrivateKey as it is loaded again (whether from KeyStore or file doesnt matter) and thus cannot be used to sign something with.
[CertificateBuilderExample]
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
import org.bouncycastle.pqc.jcajce.spec.XMSSMTParameterSpec;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Random;
public class App {
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastlePQCProvider());
SimpleDateFormat sdf = new SimpleDateFormat("dd-M-yyyy hh:mm:ss");
String datefrom = "12-08-2018 10:20:56";
String dateuntil = "12-05-2020 10:20:56";
Date from = sdf.parse(datefrom);
Date until = sdf.parse(dateuntil);
// Create self signed Root CA certificate
KeyPair rootCAKeyPair = generateKeyPair();
X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
new X500Name("CN=rootCA"), // issuer authority
BigInteger.valueOf(new Random().nextInt()), //serial number of certificate
from, // start of validity
until, //end of certificate validity
new X500Name("CN=rootCA"), // subject name of certificate
rootCAKeyPair.getPublic()); // public key of certificate
// key usage restrictions
builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign));
builder.addExtension(Extension.basicConstraints, false, new BasicConstraints(true));
X509Certificate rootCA = new JcaX509CertificateConverter().getCertificate(builder
.build(new JcaContentSignerBuilder("SHA256withXMSSMT").setProvider("BCPQC").
build(rootCAKeyPair.getPrivate()))); // private key of signing authority , here it is self signed
saveToFile(rootCA, "rootCA.cer");
}
private static KeyPair generateKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
kpGen.initialize(new XMSSMTParameterSpec(20, 10, XMSSMTParameterSpec.SHA256), new SecureRandom());
KeyPair kp = kpGen.generateKeyPair();
System.out.print("Public key:" + Arrays.toString(kp.getPublic().getEncoded()));
return kp;
}
private static void saveToFile(X509Certificate certificate, String filePath) throws IOException, CertificateEncodingException {
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
fileOutputStream.write(certificate.getEncoded());
fileOutputStream.flush();
fileOutputStream.close();
}
}
[X509CertificateGenerator]
public X509Certificate generateCertificate(String dn, KeyPair keyPair, int validity, String sigAlgName) throws GeneralSecurityException, IOException {
PrivateKey privateKey = keyPair.getPrivate();
X509CertInfo info = new X509CertInfo();
Date from = new Date();
Date to = new Date(from.getTime() + validity * 1000L * 24L * 60L * 60L);
CertificateValidity interval = new CertificateValidity(from, to);
BigInteger serialNumber = new BigInteger(64, new SecureRandom());
X500Name owner = new X500Name(dn);
AlgorithmId sigAlgId = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
info.set(X509CertInfo.VALIDITY, interval);
info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(serialNumber));
info.set(X509CertInfo.SUBJECT, owner);
info.set(X509CertInfo.ISSUER, owner);
info.set(X509CertInfo.KEY, new CertificateX509Key(keyPair.getPublic()));
info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(sigAlgId));
// Sign the cert to identify the algorithm that's used.
X509CertImpl certificate = new X509CertImpl(info);
certificate.sign(privateKey, sigAlgName);
// Update the algorith, and resign.
sigAlgId = (AlgorithmId) certificate.get(X509CertImpl.SIG_ALG);
info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, sigAlgId);
certificate = new X509CertImpl(info);
certificate.sign(privateKey, sigAlgName);
return certificate;
}
[RSA_JarSigner]
public class JarSignerTest {
public static void main(String[] args) throws Exception{
JarSignerTest jst = new JarSignerTest();
jst.SignRSA();
}
public void SignRSA() throws Exception{
Security.addProvider(new BouncyCastleProvider());
File inputFile = new File("C:\\Path\\to\\jar\\toSign\\jarfile.jar"),
outputfile = new File("C:\\Path\\to\\signedJar\\jarfile.jar");
X509CertificateGen x509certgen = new X509CertificateGen();
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
kpGen.initialize(1024, new SecureRandom());
KeyPair keyPair = kpGen.generateKeyPair();
Certificate[] chain = {x509certgen.generateCertificate("cn=Unknown", keyPair, 356, "SHA256withRSA")};
List<? extends Certificate> foo = Arrays.asList(chain);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
CertPath certPath = certificateFactory.generateCertPath(foo);
JarSigner signer = new JarSigner.Builder(keyPair.getPrivate(), certPath)
.digestAlgorithm("SHA-256")
.signatureAlgorithm("SHA256withRSA")
.build();
try (ZipFile in = new ZipFile(inputFile);
FileOutputStream out = new FileOutputStream(outputfile)){
signer.sign(in, out);
}
}
}
[SignXMSS]
public void SignXMSS() throws Exception{
Security.addProvider(new BouncyCastlePQCProvider());
File inputFile = new File("C:\\Path\\to\\jar\\toSign\\jarfile.jar"),
outputfile = new File("C:\\Path\\to\\signedJar\\jarfile.jar");
X509CertificateGen x509certgen = new X509CertificateGen();
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("XMSS", "BCPQC");
kpGen.initialize(new XMSSParameterSpec(10, XMSSParameterSpec.SHA256), new SecureRandom());
KeyPair keyPair = kpGen.generateKeyPair();
Certificate[] chain = {x509certgen.generateCertificate("cn=Unknown", keyPair, 356, "SHA256withXMSS")};
List<? extends Certificate> foo = Arrays.asList(chain);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
CertPath certPath = certificateFactory.generateCertPath(foo);
JarSigner signer = new JarSigner.Builder(keyPair.getPrivate(), certPath)
.digestAlgorithm("SHA-256")
.signatureAlgorithm("SHA256withXMSS", new BouncyCastlePQCProvider())
.build();
try (ZipFile in = new ZipFile(inputFile);
FileOutputStream out = new FileOutputStream(outputfile)){
signer.sign(in, out);
}
}
[SignXMSSMT]
public void SignXMSSMT() throws Exception{
Security.addProvider(new BouncyCastlePQCProvider());
File inputFile = new File("C:\\Path\\to\\jar\\toSign\\jarfile.jar"),
outputfile = new File("C:\\Path\\to\\signedJar\\jarfile.jar");
X509CertificateGen x509certgen = new X509CertificateGen();
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
kpGen.initialize(new XMSSMTParameterSpec(20, 10, XMSSMTParameterSpec.SHA256), new SecureRandom());
KeyPair keyPair = kpGen.generateKeyPair();
Certificate[] chain = {x509certgen.generateCertificate("cn=Unknown", keyPair, 356, "SHA256withXMSSMT")};
List<? extends Certificate> foo = Arrays.asList(chain);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
CertPath certPath = certificateFactory.generateCertPath(foo);
JarSigner signer = new JarSigner.Builder(keyPair.getPrivate(), certPath)
.digestAlgorithm("SHA-256")
.signatureAlgorithm("SHA256withXMSSMT")
.build();
try (ZipFile in = new ZipFile(inputFile);
FileOutputStream out = new FileOutputStream(outputfile)){
signer.sign(in, out);
}
}
[XMSSGen]
public class BCXMSSCertificateGenerator {
public static X509Certificate generateCertificate(PrivateKey privKey, PublicKey pubKey, int duration, boolean isSelfSigned) throws Exception {
Provider BC = new BouncyCastleProvider();
//
// distinguished name table.
//
X500NameBuilder builder = createStdBuilder();
//
// create the certificate - version 3
//
ContentSigner sigGen = new JcaContentSignerBuilder("SHA256withXMSS").setProvider("BCPQC").build(privKey);
X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(new X500Name("cn=Java"), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date((long)(System.currentTimeMillis() + duration*8.65*Math.pow(10,7))), builder.build(), pubKey);
X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
cert.checkValidity(new Date());
if (isSelfSigned) {
//
// check verifies in general
//
cert.verify(pubKey);
//
// check verifies with contained key
//
cert.verify(cert.getPublicKey());
}
ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
return (X509Certificate) fact.generateCertificate(bIn);
}
private static X500NameBuilder createStdBuilder() {
X500NameBuilder builder = new X500NameBuilder(RFC4519Style.INSTANCE);
builder.addRDN(RFC4519Style.c, "AU");
builder.addRDN(RFC4519Style.o, "The Legion of the Bouncy Castle");
builder.addRDN(RFC4519Style.l, "Melbourne");
builder.addRDN(RFC4519Style.st, "Victoria");
builder.addRDN(PKCSObjectIdentifiers.pkcs_9_at_emailAddress, "feedback-crypto#bouncycastle.org");
return builder;
}
}
[XMSSVer]
public class BCXMSSCertificateVerifyer {
public static boolean verifyCertificate(byte[] certBytes, String sigAlgorithm, byte[] keyBytes) throws Exception{
ByteArrayInputStream bIn;
bIn = new ByteArrayInputStream(certBytes);
CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
Certificate cert = fact.generateCertificate(bIn);
PublicKey k = cert.getPublicKey();
X509CertificateHolder certHldr = new X509CertificateHolder(certBytes);
certHldr.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BCPQC").build(k));
System.out.println(cert);
KeyFactory keyFactory = KeyFactory.getInstance(k.getAlgorithm(), "BCPQC");
PrivateKey privKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); // ERROR at this line
/*_______________________________________________________________________________________________________________*\
Exception in thread "main" java.security.spec.InvalidKeySpecException: java.lang.ClassCastException: org.bouncycastle.asn1.DLSequence cannot be cast to org.bouncycastle.asn1.ASN1Integer
at org.bouncycastle.pqc.jcajce.provider.xmss.XMSSKeyFactorySpi.engineGeneratePrivate(Unknown Source)
at java.base/java.security.KeyFactory.generatePrivate(KeyFactory.java:384)
at BCXMSSCertificateVerifyer.verifyCertificate(BCXMSSCertificateVerifyer.java:32)
at BCXMSSCertificateTester.main(BCXMSSCertificateTester.java:23)
_________________________________________________________________________________________________________________
/* */
Signature signer = Signature.getInstance(sigAlgorithm, "BCPQC");
signer.initSign(privKey);
signer.update(certBytes);
byte[] sig = signer.sign();
signer.initVerify(cert);
signer.update(certBytes);
signer.verify(sig);
return true;
}
}
TL;DR: Signing .jar Files with PQC Signature schemes such as XMSS provided by BC using the built in JarSigner is not possible. However one can wright its one JarSigner which then can.
Although Oracle provides with JCA/JCE an out of the box integration for Crypto Provider such as BC those registered providers are not used from JarSigner neither for signing nor for verifying.
Considering the JarSigner: the supported algorithms are hard-coded and can thus not be extended.
The only way to use JarSigner with the BC Provider is to rebuild it entirely. However this is not recommended.
For those which are not afraid of hijacking jdk source code, your project has to "override" the following classes from the jdk:
AlgorithmId
Attributes
ContentInfo
EndEntityChecker
HttpTimestamper
InvalidJarIndexError
Jarentry
JarException
JarFile
JarIndex
JarInputStream
JarOutputStream
JarSigner
JarVerifier
JavaUtilJarAccess
JavaUtilJarAccessImpl
JavaUtilZipFileAccess
Main (jdk.jartool.sun.security.tools.jarsigner)
Manifest
ManifestEntryVerifier
Pack200
PKCS7
PKIXValidator
Resources
Resources_ja
Resources_zh_CN
SharedSecrets
SignatureFileVerifier
SignerInfo
SimpleValidator
TimestampedSigner
Timestamper
TimestampToken
TSResponse
Validator
VersionedStream
Therefor copy paste the original code into your project and remove the import of all classes which you hijack so your "custom" classes will be taken instead of the official ones.
Note: Most of the classes mentioned above can be found in the java.base module, although some are in the jdk.jartool module (such as the JarSigner itself).
After cloning the necessary part of the jdk to get the JarSigner to work you can finally move on implementing your BC provider and the support of the PQC Signature Schemes, especially XMSSMT and SPHINCS, as for the moment it seems like XMSS has some major problems.
Note that for the verification of a signed jar file the JarVerifier takes the file extension of the signature block file and checks rather if its .RSA, .DCA or .EC. So you will have to add .XMSS .XMSSMT .SPHINCS256 etc. You will also have to tell the PKCS7 class in the parseSignedData method to use a BC certificate generator.
There are a few other things to change as well (e.g. AlgorithmID) which I don't remember, but once you have done all the necessary steps, your JarSigner is able to sign and verify .jar files using BC in addition to using the "normal" RSA DCA and EC signing and verifying.
Sadly I will not be able to share the final Source Code with you, although I will try answer as much of your questions as possible.
I am trying to create a CMS Enveloped encrypted message using BouncyCastle FIPS 1.0.0 for Java. I receive the following error indicating that it is trying to use AES for random number generation (which is not an approved algorithm for FIPS mode).
Exception in thread "main" org.bouncycastle.crypto.fips.FipsUnapprovedOperationError: Attempt to create key with unapproved RNG: AES
at org.bouncycastle.crypto.fips.Utils.validateRandom(Unknown Source)
at org.bouncycastle.crypto.fips.Utils.validateKeyGenRandom(Unknown Source)
at org.bouncycastle.crypto.fips.FipsAES$KeyGenerator.<init>(Unknown Source)
at org.bouncycastle.crypto.fips.FipsAES$KeyGenerator.<init>(Unknown Source)
at org.bouncycastle.jcajce.provider.ProvAES$39$1.createInstance(Unknown Source)
at org.bouncycastle.jcajce.provider.BaseKeyGenerator.engineInit(Unknown Source)
at javax.crypto.KeyGenerator.init(KeyGenerator.java:510)
at org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder$CMSOutputEncryptor.<init>(Unknown Source)
at org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder.build(Unknown Source)
First, I make sure BouncyCastle is loaded as a JCE provider and then I make sure that it is running in FIPS approved only mode.
if(!CryptoServicesRegistrar.isInApprovedOnlyMode()) {
CryptoServicesRegistrar.setApprovedOnlyMode(true);
}
After that I am basically just using code like the example in the BC FIPS in 100 mini-book. The code I have so far is as follows:
private static final String FIPS_PROVIDER = "BCFIPS";
public byte[] encrypt(X509Certificate cert, byte[] dataToEncrypt) throws CertificateEncodingException, CMSException, IOException, InvalidAlgorithmParameterException {
CMSEnvelopedDataGenerator envelopedGen = new CMSEnvelopedDataGenerator();
JcaAlgorithmParametersConverter paramsConverter = new JcaAlgorithmParametersConverter();
AlgorithmIdentifier algId = paramsConverter.getAlgorithmIdentifier(PKCSObjectIdentifiers.id_RSAES_OAEP, OAEPParameterSpec.DEFAULT);
JceKeyTransRecipientInfoGenerator recipientInfo = new JceKeyTransRecipientInfoGenerator(cert, algId);
recipientInfo.setProvider(FIPS_PROVIDER);
envelopedGen.addRecipientInfoGenerator(recipientInfo);
CMSProcessableByteArray processableArray = new CMSProcessableByteArray(dataToEncrypt);
JceCMSContentEncryptorBuilder encryptorBuilder = new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES256_CBC);
encryptorBuilder.setProvider(FIPS_PROVIDER);
OutputEncryptor outputEncryptor = encryptorBuilder.build();
return envelopedGen.generate(processableArray, outputEncryptor).getEncoded();
}
If I do not put BouncyCastle into the FIPS approved only mode then this code works fine, but I need to be able to run in this mode. Is there some way to tell the CMSOutputEncryptor to use a different RNG algorithm?
Have you tried setting up a FIPS-approved SecureRandom?
CryptoServicesRegistrar.setSecureRandom(
new FipsDRBG.Builder(
new BasicEntropySourceProvider(new SecureRandom(), true))
.build(FipsDRBG.SHA512_HMAC, null, false)
);
then on your builder (and wherever else you may need it):
encryptorBuilder.setSecureRandom(CryptoServicesRegistrar.getSecureRandom());
I have two text files, one with a "-----BEGIN CERTIFICATE-----" header, one with a "-----BEGIN RSA PRIVATE KEY-----" header. I need to use CXF ClientBuilder to make a REST service call to a remote host.
I think my loading of the cert file is ok, but I can't figure out how to handle the private key file properly.
I have the following tentative code (which doesn't quite compile yet, as you'll see) to initialize the Client object (with some minor elisions):
private Certificate buildCertFromFile(String fileName) throws CertificateException {
return CertificateFactory.getInstance("X.509").generateCertificate(ClassLoaderUtils.getResourceAsStream(fileName, <ThisClass>.class));
}
#PostConstruct
public void init() {
try {
KeyStore trustStore = KeyStore.getInstance("jks");
trustStore.load(null, null);
trustStore.setCertificateEntry("cert", buildCertFromFile("<path to cert file>"));
KeyStore keyStore = KeyStore.getInstance("jks");
keyStore.load(null, "abc".toCharArray());
// Need something here.
keyStore.setKeyEntry("key", key, "abc", chain);
ClientBuilder builder = ClientBuilder.newBuilder();
builder.trustStore(trustStore);
builder.keyStore(keyStore, "abc");
builder.hostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String host, SSLSession session) {
try {
Certificate[] certs = session.getPeerCertificates();
return certs != null && certs[0] instanceof X509Certificate;
}
catch (SSLException ex) {
return false;
}
}
});
client = builder.build();
}
catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException ex) {
ex.printStackTrace();
}
}
Update:
I'm experimenting with the following method to load the private key file:
public static PrivateKey getPrivateKey(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
String contents = IOUtils.toString(ClassLoaderUtils.getResourceAsStream(filename, TguardService.class), "UTF-8");
contents = contents.replaceAll("-----[A-z ]+-----", "").trim();
System.out.println("contents[" + contents + "]");
byte[] bytes = Base64.getDecoder().decode(contents);
System.out.println("decoded[" + new String(bytes) + "]");
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}
However, this is giving me the following error:
Caused by: java.lang.IllegalArgumentException: Illegal base64 character a
at java.util.Base64$Decoder.decode0(Unknown Source)
at java.util.Base64$Decoder.decode(Unknown Source)
at java.util.Base64$Decoder.decode(Unknown Source)
at com.att.detsusl.tguardrest.TguardService.getPrivateKey(TguardService.java:58)
The debugging output shows that "contents" looks somewhat like this:
contents[MIIEp....
...
...
...kdOA=]
Update:
Ok, I managed to figure out that I had to remove ALL the newlines from the encoded string, so now it gets through base64 decode, but now it fails on the call to "generatePrivate()", with the following:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(Unknown Source)
at java.security.KeyFactory.generatePrivate(Unknown Source)
at com.att.detsusl.tguardrest.TguardService.getPrivateKey(TguardService.java:63)
I've seen some notes that imply that I must have a "PKCS#1" format, instead of "PKSC#8" and talk about calling "openssl" to convert the file. I'd really rather not do that. Is there a straightforward way to do this conversion (assuming this is what I need) in Java? I intend this code to execute only once at application startup.
Update:
Ok, after considering the alternatives, I used openssl to convert the file to PKCS#8 format. After that, I was able to complete the construction of the Client object, but I get another error while trying to make a connection. I considered it possible that this is a related but different problem (and I wanted to reduce the number of "Updates" on a single posting), so I posted that as a separate question at CXF REST client call with 2-way auth failing with "unable to find valid certification path to requested target" .
Also note that I've converted the key file from PKCS#1 to PKCS#8, but I never did anything with the cert file.
In the meantime, I could use a little more background on PKCS#1 vs. PKCS#8. How "legacy" is PKCS#1? I note that the original name of the key file that was given to me used an acronym representing the previous name of our organization, which changed several years ago.
ACRA set up with standard options:
#ReportsCrashes(
formUri = "https://XXXXXXXXXX.php",
mode = ReportingInteractionMode.TOAST,
resToastText = R.string.str_acra_crash_report_info)
Tried to copy the server certificate to assets and create a custom KeyStore:
try {
KeyStore ksTrust = KeyStore.getInstance("BKS");
InputStream instream = new BufferedInputStream(getAssets().open("keystore.bks"));
ksTrust.load(instream, "ez24get".toCharArray());
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder(this);
configurationBuilder.setKeyStore(ksTrust);
final ACRAConfiguration config = configurationBuilder.build();
ACRA.init(this, config);
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
e.printStackTrace();
}
or another way:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(getAssets().open("ssl-cert-snakeoil.pem"));
Certificate ca = cf.generateCertificate(caInput);
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
Unfortunately after hours of tests, still no luck, still getting an exception:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
Any hints?
EDIT: Created another certificate, with CA:TRUE (standard ssl-cert-snakeoil.pem had CA:FALSE), but still no luck.
EDIT 2: Certificates made as they should be: main CA cert. + server cert., but still the same exception.
#Matthew you will need to use the head of the ACRA's master as it has this https://github.com/ACRA/acra/pull/388 pull request added.
We'll probably cut another release within a week or so.
I want to use BouncyCastle to encrypt and decrypt with pkcs7 format. I have a hardware token. when I use Keypair in jks file in my hard drive it work fine but when i use key pair in token
its not work. this is my exception:
Exception in thread "main" org.bouncycastle.cms.CMSException: cannot create cipher: No such algorithm: 2.16.840.1.101.3.4.1.2
at org.bouncycastle.cms.jcajce.EnvelopedDataHelper.createCipher(Unknown Source)
at org.bouncycastle.cms.jcajce.EnvelopedDataHelper$1.doInJCE(Unknown Source)
at org.bouncycastle.cms.jcajce.EnvelopedDataHelper.execute(Unknown Source)
at org.bouncycastle.cms.jcajce.EnvelopedDataHelper.createContentCipher(Unknown Source)
at org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient.getRecipientOperator(Unknown Source)
at org.bouncycastle.cms.KeyTransRecipientInformation.getRecipientOperator(Unknown Source)
at org.bouncycastle.cms.RecipientInformation.getContentStream(Unknown Source)
at org.bouncycastle.cms.RecipientInformation.getContent(Unknown Source)
at pktb.PKTB.CmsDecrypt(PKTB.java:288)
at pktb.PKTB.main(PKTB.java:419)
Caused by: java.security.NoSuchAlgorithmException: No such algorithm: 2.16.840.1.101.3.4.1.2
at javax.crypto.Cipher.getInstance(DashoA13*..)
at javax.crypto.Cipher.getInstance(DashoA13*..)
at org.bouncycastle.jcajce.NamedJcaJceHelper.createCipher(Unknown Source)
... 10 more
Java Result: 1
this is my Encryption code:
public byte[] CmsEncrypt(byte[] message, KeyContainer keyContainer) throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, IOException
{
Security.addProvider(new BouncyCastleProvider());
X509Certificate cert = (X509Certificate) keyContainer.certificate;
CMSEnvelopedDataGenerator gen = new CMSEnvelopedDataGenerator();
gen.addKeyTransRecipient(cert);
CMSProcessable data = new CMSProcessableByteArray(message);
CMSEnvelopedData enveloped = gen.generate(data,
CMSEnvelopedDataGenerator.AES128_CBC, "BC");
return enveloped.getEncoded();
}
and this is my decryption code:
public byte[] CmsDecrypt(byte[] cipher, KeyContainer keyContainer) throws CMSException, IOException, NoSuchProviderException
{
Security.addProvider(new BouncyCastleProvider());
byte[] contents=null;
CMSEnvelopedDataParser envelopedDataParser = new CMSEnvelopedDataParser(new ByteArrayInputStream(cipher));
PrivateKey key = keyContainer.privateKey;
X509Certificate cert = keyContainer.certificate;
CMSEnvelopedData enveloped = new CMSEnvelopedData(cipher);
Collection recip = enveloped.getRecipientInfos().getRecipients();
KeyTransRecipientInformation rinfo = (KeyTransRecipientInformation) recip
.iterator().next();
if(keyContainer.provider.equals("Software"))
contents = rinfo.getContent(
new JceKeyTransEnvelopedRecipient(key).setProvider("BC"));
else
contents = rinfo.getContent(
new JceKeyTransEnvelopedRecipient(key).setProvider("SunPKCS11-" + keyContainer.provider));
System.out.println(new String(contents));
return contents;
}
I must say that i use this token provider for cmsSign and cmsVerify and it works fine therefore i think the problem isn't for provider.
You can use PKCS#11 to extract private and public keys from hardware token and then use these extracted public and private keys to encrypt and decrypt data with BouncyCastle PKCS7. which token you are using ? Also I cannot find the code to extract keys from hardware token. Go through the answer in following Link for extracting keys from hardware token. Click here