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.