I am currently developing a SOAP-Server/Client using CXF rev 3.1.10.
Everything is set up and works quite fine as long as I don't try to use https.
I am not using any xml-files/beans, except the ones that might be used 'behind the scenes' by the framework.
I would actually like to leave it that way.
I am using a self signed certificate and its extracted keys, just in case this might be necessary.
Server Class
public class Server extends Thread {
private static final Logger LOG = Logger.getLogger(Server.class);
#WebService(name = "SoapService", serviceName = "SoapService", endpointInterface = "playground.mstahl.cxf_soap.SoapServiceDefinition")
private static final class ServerImpl implements SoapServiceDefinition {
#Override
public boolean handleStateDataRecipience(String user, String pass, String restri) throws Exception {
return true;
}
}
private final int usedPort;
private final String ksPath;
private final String ksPass;
private final boolean sslEnabled;
public Server(int port, boolean sslEnabled, String ksPath, String ksPass) {
super("CXF-SOAP-Playground");
setDaemon(true);
usedPort = port;
this.sslEnabled = sslEnabled;
this.ksPath = ksPath;
this.ksPass = ksPass;
start();
}
#Override
public void run() {
JaxWsServerFactoryBean sf = new JaxWsServerFactoryBean();
sf.setAddress(String.format("http" + (sslEnabled ? "s" : "") + "://localhost:%d/signtest/", Integer.valueOf(usedPort))); // <- Yah, pretty ugly, but its just for testing purposes ;)
sf.setServiceClass(ServerImpl.class);
ServerImpl serviceBean = new ServerImpl();
sf.setServiceBean(serviceBean);
if (sslEnabled) {
try {
JettyHTTPServerEngineFactory factory = sf.getBus().getExtension(JettyHTTPServerEngineFactory.class);
factory.setTLSServerParametersForPort(usedPort, getTLSServerParameters(ksPath, ksPass));
} catch (Exception e) {
e.printStackTrace();
}
}
org.apache.cxf.endpoint.Server server = sf.create();
if (!server.isStarted()) {
return;
}
LOG.debug("... done.");
while (!IsInterrupted()) {
try {
Thread.sleep(100);
} catch (Exception e) {
//meh, just a test
}
}
server.stop();
server.destroy();
}
private final TLSServerParameters getTLSServerParameters(final String ksPath, final String ksPass) {
TLSServerParameters tlsParams = null;
try {
tlsParams = new TLSServerParameters();
File truststore = new File(ksPath);
LOG.info("Try to load file " + truststore.getCanonicalPath());
final KeyStore keyStore = KeyStore.getInstance("JKS");
FileInputStream stream = new FileInputStream(truststore);
final char[] keyStorePassphraseAsChar = ksPass.toCharArray();
keyStore.load(stream, keyStorePassphraseAsChar);
stream.close();
final KeyManagerFactory keyFactory = KeyManagerFactory.getInstance("PKIX");
keyFactory.init(keyStore, keyStorePassphraseAsChar);
final KeyManager[] km = keyFactory.getKeyManagers();
tlsParams.setKeyManagers(km);
truststore = new File(ksPath);
stream = new FileInputStream(truststore);
keyStore.load(stream, keyStorePassphraseAsChar);
stream.close();
final TrustManagerFactory trustFactory = TrustManagerFactory.getInstance("PKIX");
trustFactory.init(keyStore);
final TrustManager[] tm = trustFactory.getTrustManagers();
tlsParams.setTrustManagers(tm);
final SSLContext context = SSLContext.getDefault();
final SSLSocketFactory sf = context.getSocketFactory();
final List<String> cipherSuites = Arrays.asList(sf.getSupportedCipherSuites());
LOG.info(String.format("Suppored cipher suites : %s", cipherSuites.toString()));
final FiltersType filter = new FiltersType();
final List<String> include = filter.getInclude();
include.add(".*_EXPORT_.*");
include.add(".*_EXPORT1024_.*");
include.add(".*_WITH_DES_.*");
include.add(".*_WITH_AES_.*");
include.add(".*_WITH_NULL_.*");
include.add(".*_RSA_WITH_AES_.*");
include.add(".*_DH_anon_.*");
tlsParams.setCipherSuitesFilter(filter);
final ClientAuthentication ca = new ClientAuthentication();
ca.setRequired(false);
ca.setWant(false);
tlsParams.setClientAuthentication(ca);
} catch (final Exception e) {
LOG.error("Security configuration failed with the following: " + e.getMessage() + " " + e.getCause());
tlsParams = null;
}
return tlsParams;
}
}
My Server currently starts up quite fine. (At least no errors gets thrown...)
I can also access the given soap method as long as I am using http...
Client class
public class Client {
private static final Logger LOG = Logger.getLogger(Client.class);
private static SoapServiceDefinition client;
public Client(String address, boolean sslEnabled, String ksFile, String ksPass) {
// set keystore setting for plain httpclient
if (sslEnabled) {
LOG.debug(" ... collecting keystore file and passphrase due to enabled ssl.");
System.setProperty("javax.net.ssl.keyStore", ksFile);
System.setProperty("javax.net.ssl.trustStore", ksFile);
System.setProperty("javax.net.ssl.keyStorePassword", ksPass);
System.setProperty("javax.net.ssl.trustStorePassword", ksPass);
}
LOG.debug(" ... creating service factory.");
final JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(SoapServiceDefinition.class);
LOG.debug(" ... setting host address to '" + address + "'.");
factory.setAddress(address);
LOG.debug(" ... creating actual SOAP-client.");
client = (SoapServiceDefinition) factory.create();
final HTTPConduit httpConduit = (HTTPConduit) ClientProxy.getClient(client).getConduit();
if (sslEnabled) {
LOG.debug(" ... configuring SSL.");
configureClientSideSSL(httpConduit, ksFile, ksPass);
LOG.debug(" ... done.");
}
LOG.debug(" ... setting timeouts.");
final HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnectionTimeout(0);
httpClientPolicy.setReceiveTimeout(0);
httpClientPolicy.setContentType("application/soap+xml");
httpConduit.setClient(httpClientPolicy);
retrieveAndStoreWSDL(address);
}
private void configureClientSideSSL(final HTTPConduit conduit, final String keyStorePath, final String trustpass) {
try {
final TLSClientParameters tlsParams = new TLSClientParameters();
tlsParams.setDisableCNCheck(true);
final KeyStore keyStore = KeyStore.getInstance("jceks");
final File truststore = new File(keyStorePath);
final FileInputStream stream = new FileInputStream(truststore);
keyStore.load(stream, trustpass.toCharArray());
final TrustManagerFactory trustFactory = TrustManagerFactory.getInstance("PKIX");
trustFactory.init(keyStore);
final TrustManager[] tm = trustFactory.getTrustManagers();
tlsParams.setTrustManagers(tm);
final KeyManagerFactory keyFactory = KeyManagerFactory.getInstance("PKIX");
keyFactory.init(keyStore, trustpass.toCharArray());
final KeyManager[] km = keyFactory.getKeyManagers();
tlsParams.setKeyManagers(km);
final FiltersType filter = new FiltersType();
final List<String> include = filter.getInclude();
include.add(".*");
include.add(".*_EXPORT_.*");
include.add(".*_EXPORT1024_.*");
include.add(".*_WITH_DES_.*");
include.add(".*_WITH_AES_.*");
include.add(".*_WITH_NULL_.*");
include.add(".*_RSA_WITH_AES_.*");
include.add(".*_DH_anon_.*");
tlsParams.setCipherSuitesFilter(filter);
conduit.setTlsClientParameters(tlsParams);
stream.close();
} catch (final Exception e) {
System.out.println("Security configuration failed with the following: " + e.getCause());
}
}
private void retrieveAndStoreWSDL(final String address) {
LOG.info(" ... retrieving the WSDL-file.");
final HttpClient httpclient = new HttpClient();
httpclient.getParams().setSoTimeout(0); // No timeout at all...in case of big wsdls
final GetMethod get = new GetMethod(address);
get.setQueryString("?wsdl");
try {
final int result = httpclient.executeMethod(get);
final String str = IOUtils.toString(get.getResponseBodyAsStream(), "UTF-8");
LOG.debug(" ... Response status code: " + result);
} catch (final Throwable e) {
LOG.debug("-", e);
LOG.error(e.getClass().getSimpleName() + " occurred during WSDL-retrieval. Won't store current WSDL.");
} finally {
get.releaseConnection();
}
}
public String helloReturn() throws Exception {
return "haha:" + client.handleStateDataRecipience("", "", "");
}
}
The Client is siarting up as well , however, the moment the client tries to retrieve the WSDL and/or tries to execute any of its methods i get:
javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
...
As I previously mentioned, everything works fine as long as I use http instead of https.
I don't think that the problem are the keystores as I can finally read them and get their keys by using e.g.
private static void displayKeys(String ksForm, String alias, char[] ksPass, char[] kePass, String keystore) throws Exception {
System.out
.println("---------------------------------------------------------------------------------------------------------------------------");
KeyStore keyStore = KeyStore.getInstance(ksForm);
keyStore.load(new FileInputStream(keystore), ksPass);
Key key = keyStore.getKey(alias, kePass);
if (key instanceof PrivateKey) {
System.out.println("Get private key : ");
System.out.println(key.toString());
java.security.cert.Certificate[] certs = keyStore.getCertificateChain(alias);
System.out.println("Certificate chain length : " + certs.length);
for (Certificate cert : certs) {
System.out.println(cert.toString());
}
} else {
System.out.println("Get public key : ");
System.out.println(key.toString());
}
System.out
.println("---------------------------------------------------------------------------------------------------------------------------");
}
Caused by: java.io.EOFException: SSL peer shut down incorrectly
Thanks in advance and kind regards.
So, I was finally able to figure it out.
Actually there were several Problems at hand
The created keystore itself was fine, the extracted (for client use) cert wasn't
Loading keystore and truststore in both, server and client, was a huge mistake, especially since I've used the same store/certs for both (for this I think WSS4J Interceptors and CallBackHandlers are necessary)
During several trial and error periods I also seems to have mixed up entry and store password.
Below I will give you the code for all the classes that I've used to get a fully running example.
Key And Cert Generation
As I always had problems with the SunAPI and its code examples for certificate creation I decided to use BouncyCastle instead.
Even though I previously decided to not use a 3rd party tool, I changed my mind due to the fact that I use this only for keystore/cert creation.
The class you are about to see is a slightly modified version of the answer from 'Maarten Bodewes' to this question:
How to store and reuse keypair in Java?
The class is pretty straight forward, hence no method comments were added...
package playground.TEST.cxf_soap;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStore.Entry;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Calendar;
import java.util.Date;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
public class BCCertUtils {
public static KeyPair generateKeyPair(int keySize, String keyAlgo, String secureAlgo) throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(keyAlgo != null && !keyAlgo.trim().isEmpty() ? keyAlgo : "RSA");
keyGen.initialize(keySize, secureAlgo != null && !secureAlgo.trim().isEmpty() ? SecureRandom.getInstance(secureAlgo) : new SecureRandom());
KeyPair pair = keyGen.generateKeyPair();
return pair;
}
public static Certificate generateSelfSignedCertificate(KeyPair keyPair, String dn, String sigAlg, Date endDate)
throws OperatorCreationException, CertificateException {
// Setting bouncy castle provider to be able to create certs at all...
Provider bcProvider = new BouncyCastleProvider();
Security.addProvider(bcProvider);
X500Name dnName = new X500Name(dn);
// Using the current timestamp as the certificate serial number
BigInteger certSerialNum = new BigInteger(String.valueOf(System.currentTimeMillis()));
// Setting start date
Date startDate = Calendar.getInstance().getTime();
// Use appropriate signature algorithm based on your keyPair algorithm.
String sigAlgorithm = sigAlg == null || sigAlg.trim().isEmpty() ? "SHA256WithRSA" : sigAlg;
SubjectPublicKeyInfo certPubKey = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(dnName, certSerialNum, startDate, endDate, dnName, certPubKey);
ContentSigner contentSigner = new JcaContentSignerBuilder(sigAlgorithm).setProvider(bcProvider).build(keyPair.getPrivate());
X509CertificateHolder certificateHolder = certBuilder.build(contentSigner);
return new JcaX509CertificateConverter().getCertificate(certificateHolder);
}
public static void storeToPKCS12File(String alias, Certificate selfCert, String filename, char[] ksPass, char[] kePass, PrivateKey privKey)
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, OperatorCreationException {
KeyStore p12Store = createP12Store(alias, selfCert, privKey, kePass);
try (FileOutputStream fos = new FileOutputStream(filename)) {
p12Store.store(fos, ksPass);
}
}
public static byte[] storeToPKCS12ByteArray(String alias, Certificate selfCert, char[] ksPass, char[] kePass, PrivateKey privKey)
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, OperatorCreationException {
KeyStore p12Store = createP12Store(alias, selfCert, privKey, kePass);
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
p12Store.store(bos, ksPass);
return bos.toByteArray();
}
}
private static KeyStore createP12Store(String alias, Certificate selfCert, PrivateKey privKey, char[] kePass)
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
KeyStore p12KeyStore = KeyStore.getInstance("PKCS12");
p12KeyStore.load(null, null);
KeyStore.Entry entry = new PrivateKeyEntry(privKey, new Certificate[] { selfCert });
KeyStore.ProtectionParameter param = new KeyStore.PasswordProtection(kePass);
p12KeyStore.setEntry(alias, entry, param);
return p12KeyStore;
}
public static boolean moduliMatch(PublicKey originPubKey, PrivateKey certPrivKey) {
return ((RSAPublicKey) originPubKey).getModulus().equals(((RSAPrivateKey) certPrivKey).getModulus());
}
public static KeyPair loadKeysFromPKCS12File(String alias, String filename, char[] storePass, char[] entryPass) throws KeyStoreException,
NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableEntryException {
KeyStore pkcs12KeyStore = KeyStore.getInstance("PKCS12");
try (FileInputStream fis = new FileInputStream(filename);) {
pkcs12KeyStore.load(fis, storePass);
}
return loadKeyPair(pkcs12KeyStore, alias, entryPass);
}
public static KeyPair loadKeysFromPKCS12ByteArray(String alias, byte[] storeBytes, char[] storePass, char[] entryPass) throws KeyStoreException,
NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableEntryException {
KeyStore pkcs12KeyStore = KeyStore.getInstance("PKCS12");
try (ByteArrayInputStream bis = new ByteArrayInputStream(storeBytes);) {
pkcs12KeyStore.load(bis, storePass);
}
return loadKeyPair(pkcs12KeyStore, alias, entryPass);
}
private static KeyPair loadKeyPair(KeyStore ks, String alias, char[] entryPass)
throws NoSuchAlgorithmException, UnrecoverableEntryException, KeyStoreException {
KeyStore.ProtectionParameter param = new KeyStore.PasswordProtection(entryPass);
Entry entry = ks.getEntry(alias, param);
if (!(entry instanceof PrivateKeyEntry)) {
throw new KeyStoreException("That's not a private key!");
}
PrivateKeyEntry privKeyEntry = (PrivateKeyEntry) entry;
PublicKey publicKey = privKeyEntry.getCertificate().getPublicKey();
PrivateKey privateKey = privKeyEntry.getPrivateKey();
return new KeyPair(publicKey, privateKey);
}
public static Certificate loadCertFromPKCS12File(String alias, String filename, char[] storePass, char[] entryPass) throws KeyStoreException,
NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableEntryException {
KeyStore pkcs12KeyStore = KeyStore.getInstance("PKCS12");
try (FileInputStream fis = new FileInputStream(filename);) {
pkcs12KeyStore.load(fis, storePass);
}
return loadCert(pkcs12KeyStore, alias, entryPass);
}
public static Certificate loadCertFromPKCS12ByteArray(String alias, byte[] storeBytes, char[] storePass, char[] entryPass)
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException,
UnrecoverableEntryException {
KeyStore pkcs12KeyStore = KeyStore.getInstance("PKCS12");
try (ByteArrayInputStream bis = new ByteArrayInputStream(storeBytes);) {
pkcs12KeyStore.load(bis, storePass);
}
return loadCert(pkcs12KeyStore, alias, entryPass);
}
private static Certificate loadCert(KeyStore ks, String alias, char[] entryPass)
throws NoSuchAlgorithmException, UnrecoverableEntryException, KeyStoreException {
KeyStore.ProtectionParameter param = new KeyStore.PasswordProtection(entryPass);
Entry entry = ks.getEntry(alias, param);
if (!(entry instanceof PrivateKeyEntry)) {
throw new KeyStoreException("That's not a private key!");
}
PrivateKeyEntry privKeyEntry = (PrivateKeyEntry) entry;
return privKeyEntry.getCertificate();
}
public static void storeToPEMFile(Certificate pubCert, String certPath) throws IOException {
JcaPEMWriter pw = new JcaPEMWriter(new FileWriter(certPath));
pw.writeObject(pubCert);
pw.flush();
pw.close();
}
public static byte[] storeToPEMByteArray(Certificate pubCert) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(baos));
JcaPEMWriter pw = new JcaPEMWriter(writer);
pw.writeObject(pubCert);
pw.flush();
pw.close();
return baos.toByteArray();
}
}
Starter Class
This is the code in which I will actually generate the keys and startup the server and the client, as well as using the clients' methods.
package playground.test.cxf_soap;
import java.security.KeyPair;
import java.security.cert.Certificate;
import java.util.Calendar;
public class Starter {
public static void main(String[] args) {
try {
boolean enableSSL = true;
char[] entryPass = "entryPass".toCharArray();
char[] storePass = "storePass".toCharArray();
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.YEAR, 100);
// Server Store and Client cert.
KeyPair srvKeyPair = BCCertUtils.generateKeyPair(2048, "RSA", "SHA1PRNG");
Certificate srvPrivCert = BCCertUtils.generateSelfSignedCertificate(srvKeyPair, "CN=Test", "SHA256WithRSA", calendar.getTime());
byte[] srvStoreBytes = BCCertUtils.storeToPKCS12ByteArray("alias", srvPrivCert, storePass, entryPass, srvKeyPair.getPrivate());
KeyPair SvrCertKeys = BCCertUtils.loadKeysFromPKCS12ByteArray("alias", srvStoreBytes, storePass, entryPass);
if (!BCCertUtils.moduliMatch(srvKeyPair.getPublic(), SvrCertKeys.getPrivate())) {
System.err.println("ARRGL");
return;
}
Certificate clientCert = BCCertUtils.loadCertFromPKCS12ByteArray("alias", srvStoreBytes, storePass, entryPass);
byte[] clientCertBytes = BCCertUtils.storeToPEMByteArray(clientCert);
Server server = new Server(443, enableSSL, srvStoreBytes, storePass, entryPass);
while (!server.isRunning()) {
Thread.sleep(10);
}
Client client = new Client("https://localhost:" + 443 + "/signtest/", enableSSL, clientCertBytes);
System.out.println("Hello SOAP-Server :)");
System.out.println(" -> " + client.helloReturn("Stahler"));
System.out.println("Could you tell me if it is working?");
System.out.println(" -> " + client.isItWorking());
System.out.println("Awww finally, thank you server and goodbye.");
System.out.println(" -> " + client.gbyeReturn("Stahler"));
System.exit(0);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
Server class
Following now I will show you my Server class In which I import the previously created PKCS12 store and adjust TLS Settings to work with the client.
package playground.mstahl.cxf_soap;
import java.io.ByteArrayInputStream;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.List;
import javax.jws.WebService;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import org.apache.cxf.configuration.jsse.TLSServerParameters;
import org.apache.cxf.configuration.security.ClientAuthentication;
import org.apache.cxf.configuration.security.FiltersType;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
import org.apache.cxf.transport.http_jetty.JettyHTTPServerEngineFactory;
public class Server extends Thread {
private boolean isRunning = false;
#WebService(name = "SoapService", serviceName = "SoapService", endpointInterface = "playground.mstahl.cxf_soap.SoapServiceDefinition")
private static final class ServerImpl implements SoapServiceDefinition {
#Override
public String sayHelloToMe(String caller) throws Exception {
return "oh Hello " + caller + ".";
}
#Override
public String askFunctionality() throws Exception {
return "Well, as I am answering I guess its working...duh";
}
#Override
public String sayGoodbyeToMe(String caller) throws Exception {
return "Goodbye doucheb...i mean..." + caller + ".";
}
}
private final int usedPort;
private final byte[] storeBytes;
private final char[] storePass;
private final char[] entryPass;
private final boolean sslEnabled;
public Server(int port, boolean sslEnabled, byte[] storeBytes, char[] storePass, char[] entryPass) {
super("CXF-SOAP-Playground");
setDaemon(true);
usedPort = port;
this.sslEnabled = sslEnabled;
this.storeBytes = storeBytes;
this.storePass = storePass;
this.entryPass = entryPass;
start();
}
#Override
public void run() {
System.out.println(" ... creating factory.");
JaxWsServerFactoryBean sf = new JaxWsServerFactoryBean();
System.out.println(" ... setting address and implementing service.");
sf.setAddress(String.format("http" + (sslEnabled ? "s" : "") + "://localhost:%d/signtest/", Integer.valueOf(usedPort)));
sf.setServiceClass(ServerImpl.class);
System.out.println(" ... setting up service bean.");
ServerImpl serviceBean = new ServerImpl();
sf.setServiceBean(serviceBean);
if (sslEnabled) {
try {
JettyHTTPServerEngineFactory factory = sf.getBus().getExtension(JettyHTTPServerEngineFactory.class);
factory.setTLSServerParametersForPort(usedPort, getTLSServerParameters());
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(" ... starting actual SOAP-server.");
org.apache.cxf.endpoint.Server server = sf.create();
Endpoint endpoint = server.getEndpoint();
String endpointAddr = endpoint.getEndpointInfo().getAddress();
System.out.println("Server started at " + endpointAddr);
if (!server.isStarted()) {
return;
}
isRunning = true;
System.out.println("... done.");
while (!isInterrupted()) {
try {
Thread.sleep(100);
} catch (Exception e) {
}
}
System.out.println("... stopping actual SOAP-server.");
server.stop();
System.out.println("... destroying its remnants.");
server.destroy();
}
public boolean isRunning() {
return isRunning;
}
private final TLSServerParameters getTLSServerParameters() {
TLSServerParameters tlsParams = null;
try {
// 1 - Load key store
KeyStore localKeyStore = KeyStore.getInstance("PKCS12");
localKeyStore.load(new ByteArrayInputStream(storeBytes), storePass);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(localKeyStore, entryPass);
// 2 - Add the new keyManager to the tls settings.
tlsParams = new TLSServerParameters();
tlsParams.setKeyManagers(kmf.getKeyManagers());
// 3 - Adjust cipher suite filters
final List<String> cipherSuites = Arrays.asList(SSLContext.getDefault().getSocketFactory().getSupportedCipherSuites());
System.out.println(String.format("Suppored cipher suites : %s", cipherSuites.toString()));
final FiltersType filter = new FiltersType();
final List<String> include = filter.getInclude();
include.add(".*");
include.add(".*_EXPORT1024_.*");
include.add(".*_WITH_DES_.*");
include.add(".*_WITH_AES_.*");
include.add(".*_WITH_NULL_.*");
include.add(".*_RSA_WITH_AES_.*");
include.add(".*_DH_anon_.*");
tlsParams.setCipherSuitesFilter(filter);
// 4 - Disable client authentication
final ClientAuthentication ca = new ClientAuthentication();
ca.setRequired(false);
ca.setWant(false);
tlsParams.setClientAuthentication(ca);
} catch (final Exception e) {
e.printStackTrace();
System.err.println("Security configuration failed with the following: " + e.getMessage() + " " + e.getCause());
tlsParams = null;
}
return tlsParams;
}
}
Client Class
Last but not least, a small client class in which I imported the certificate which I previously exported from the servers keystore.
package playground.mstahl.cxf_soap;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManagerFactory;
import org.apache.commons.io.IOUtils;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.configuration.security.FiltersType;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.apache.System.out.println4j.System.out.printlnger;
public class Client {
private static SoapServiceDefinition client;
public Client(String address, boolean sslEnabled, byte[] remoteCertBytes) {
System.out.println(" ... creating service factory.");
final JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(SoapServiceDefinition.class);
System.out.println(" ... setting host address to '" + address + "'.");
factory.setAddress(address);
System.out.println(" ... creating actual SOAP-client.");
client = (SoapServiceDefinition) factory.create();
final HTTPConduit httpConduit = (HTTPConduit) ClientProxy.getClient(client).getConduit();
if (sslEnabled) {
System.out.println(" ... configuring SSL.");
configureClientSideSSL(httpConduit, remoteCertBytes);
System.out.println(" ... done.");
}
System.out.println(" ... setting timeouts.");
final HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnectionTimeout(0);
httpClientPolicy.setReceiveTimeout(0);
httpClientPolicy.setContentType("application/soap+xml");
httpConduit.setClient(httpClientPolicy);
try {
retrieveAndStoreWSDL(sslEnabled, address);
} catch (Exception e) {
e.printStackTrace();
}
}
private void configureClientSideSSL(final HTTPConduit conduit, byte[] remoteCertBytes) {
TLSClientParameters tlsParams = null;
try {
// 1 - Load the remote certificate
ByteArrayInputStream bis = new ByteArrayInputStream(remoteCertBytes);
X509Certificate remoteCert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(new BufferedInputStream(bis));
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry(Integer.toString(1), remoteCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
// 2 - Add the new trustmanager to the tls settings.
tlsParams = new TLSClientParameters();
tlsParams.setTrustManagers(tmf.getTrustManagers());
// 3 - Disable CN check
tlsParams.setDisableCNCheck(true);
// 4 - Set default SSL-context (necessary for e.g. the wsdl retrieval)
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
SSLContext.setDefault(context);
final FiltersType filter = new FiltersType();
final List<String> include = filter.getInclude();
include.add(".*");
include.add(".*_EXPORT_.*");
include.add(".*_EXPORT1024_.*");
include.add(".*_WITH_DES_.*");
include.add(".*_WITH_AES_.*");
include.add(".*_WITH_NULL_.*");
include.add(".*_RSA_WITH_AES_.*");
include.add(".*_DH_anon_.*");
tlsParams.setCipherSuitesFilter(filter);
conduit.setTlsClientParameters(tlsParams);
} catch (final Exception e) {
e.printStackTrace();
System.out.println("Security configuration failed with the following: " + e.getCause());
}
}
private void retrieveAndStoreWSDL(boolean sslEnabled, final String address) throws Exception {
System.out.println(" ... retrieving the WSDL-file."); // TODO ssl enabled check (Necessary if we do this beforehand?)
URL wsdlUrl = new URL(address + "?wsdl");
URLConnection connection = wsdlUrl.openConnection();
HttpsURLConnection conn = (HttpsURLConnection) connection;
if (sslEnabled) {
conn.setHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
}
conn.setRequestMethod("GET");
conn.connect();
String wsdl = IOUtils.toString(conn.getInputStream(), Charset.defaultCharset());
System.err.println(wsdl);
conn.disconnect();
}
public String helloReturn(String caller) throws Exception {
return client.sayHelloToMe(caller);
}
public String isItWorking() throws Exception {
return client.askFunctionality();
}
public String gbyeReturn(String caller) throws Exception {
return client.sayGoodbyeToMe(caller);
}
}
Thanks to everyone who read my question and thought of a possible solution.
Hopefully this can help others .
Kind regards
I have a file containing a public RSA key (generated with ssh-keygen). I'd like to read the file and generate a PublicKey object.
Prior to that I converted the file, since reading the original files seems to be impossible:
# http://unix.stackexchange.com/questions/220354/how-to-convert-public-key-from-pem-to-der-format/220356#220356
ssh-keygen -f ~/.ssh/id_rsa.pub -e -m PEM > ~/.ssh/id_rsa.pub.pem
openssl rsa -RSAPublicKey_in -in ~/.ssh/id_rsa.pub.pem -inform PEM -outform DER -out ~/.ssh/id_rsa.pub.der -RSAPublicKey_out
From Java - Encrypt String with existing public key file I defined the function readFileBytes:
public static byte[] readFileBytes(String filename) throws IOException {
Path path = Paths.get(System.getProperty("user.home") + filename);
return Files.readAllBytes(path);
}
Now I'd like to read the file and generate the PublicKey object, but I could not find a way to do that; java.security.spec.RSAPublicKeySpec does not provide a fitting constructor and java.security.spec.X509EncodedKeySpec throws an error java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: algid parse error, not a sequence:
//RSAPublicKeySpec publicSpec = new RSAPublicKeySpec(readFileBytes("/.ssh/id_rsa.pub.der"));
// No fitting construktor
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(readFileBytes("/.ssh/id_rsa.pub.der"));
// Gives: "algid parse error, not a sequence"
I had a project in which (RSA) encryption was necessary, this is how I reconstructed the publicKey given the publicKey's byte array, that was just read from the file.
public PublicKey reconstruct_public_key(String algorithm, byte[] pub_key) {
PublicKey public_key = null;
try {
KeyFactory kf = KeyFactory.getInstance(algorithm);
EncodedKeySpec pub_key_spec = new X509EncodedKeySpec(pub_key);
public_key = kf.generatePublic(pub_key_spec);
} catch(NoSuchAlgorithmException e) {
System.out.println("Could not reconstruct the public key, the given algorithm oculd not be found.");
} catch(InvalidKeySpecException e) {
System.out.println("Could not reconstruct the public key");
}
return public_key;
}
Then you could call the procedure similar to this call, reconstruct_public_key("RSA", readFileBytes("path/to/your/publicKey/file"));
EDIT : I tried to do it myself (write the public key to a file, read that file and reconstruct the key). This works :
public static void main(String args[]) {
String path = "./pub_key_test.txt";
// Generate a keypair to write to file
KeyPair kp = generate_key();
PublicKey pub_key = kp.getPublic();
File file = new File(path);
try {
// Write to file
file.createNewFile();
FileOutputStream out = new FileOutputStream(path);
out.write(pub_key.getEncoded()); // Write public key to the file
out.close();
// Read from file
FileInputStream in = new FileInputStream(path);
byte[] pub_key_arr = new byte[in.available()];
in.read(pub_key_arr, 0, in.available());
in.close();
// Reconstruct public key
PublicKey reconstructed_pub_key = reconstruct_public_key("RSA", pub_key_arr);
} catch(IOException e) {
System.out.println("Could not open the file : " + e.getStackTrace());
}
}
And this is the generate_key procedure :
public KeyPair generate_key() {
while(true) { // Else the compiler will complain that this procedure does not always return a "KeyPair"
try {
final KeyPairGenerator key_generator = KeyPairGenerator.getInstance("RSA");
key_generator.initialize(2048); // Keys of 2048 bits (minimum key length for RSA keys) are safe enough (according to the slides 128bit keys > 16 years to brute force it)
final KeyPair keys = key_generator.generateKeyPair();
return keys;
} catch(NoSuchAlgorithmException e) {
System.out.println("The given encryption algorithm (RSA) does not exist. -- generate_key() - Cryptography.");
}
}
}
If you test this, you will see that the publicKey is reconstructed successfully.
EDIT : I tried doing it myself, using the ssh-keygen tool. This is what i did :
First I generated a RSA private key (.PEM format)
Outputted the public key portion to .DER format, so it can be used by Java.
This is how I did the conversion, which is a bit different of yours :
openssl rsa -in private_key_file.pem -pubout -outform DER -out java_readable_file.der
And I did the file reading like here, which doesn't differ much of yours. I tested this and Java successfully reconstructed the public key.
Creating RSA Private Key
openssl genrsa -out rsaprivkey.pem 1024
Generates the public key in DER format.
openssl rsa -in rsaprivkey.pem -pubout -outform DER -out rsapubkey.der
We use this code extracts the public key RSA OR DSA from the X.509 certificate.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
/**
* This class is capable of extracting a public key from a X.509 certficate
* and returning the PublicKey representation from a referenced byte array.
*
*/
public class ExtractPublicKey {
// Certificate Filename (Including Path Info)
private static final String certFilename = "cacert.pem";
// Public Key Filename (Including Path Info)
private static final String pubKeyFilename = "rsapublic.key";
public static PublicKey generatePublicKey(byte[] encodedKey)
throws NoSuchAlgorithmException, InvalidKeySpecException {
X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(encodedKey);
boolean isSupportedKey = false;
KeyFactory factory;
PublicKey retKey = null;
//first try the DSA alg
try {
factory = KeyFactory.getInstance("DSA");
retKey = factory.generatePublic(pubSpec);
isSupportedKey = true;
} catch (InvalidKeySpecException e) {
System.out.println("Could not create DSA Public Key: " + e.toString());
}
//if DSA didnt work, then try RSA
if (!isSupportedKey) {
try {
factory = KeyFactory.getInstance("RSA");
retKey = factory.generatePublic(pubSpec);
isSupportedKey = true;
} catch (InvalidKeySpecException e) {
System.out.println("Could not create RSA Public Key: " + e.toString());
}
}
// if not DSA or RSA
if (!isSupportedKey) {
throw new InvalidKeySpecException("Unsupported key spec: Not RSA or DSA");
}
return retKey;
}
}
I did look at this post: Cannot generate RSA private Key on Android but it did not work for me.
My idea is to encrypt an access token using RSA encryption and store the Private key on the device. I have successfully encrypted the token using RSA but i am lost as to where the best place to store this key is. I tried storing it using KeyStore, however i do not know enough about this to debug as to why it is not working. Keep getting a Error: java.security.UnrecoverableKeyException: no match.
My keys do match, but again, no idea whats wrong as i do not know enough about this. I was using setEntry and storing the private key in weird and wonderful ways which im sure, if it worked would not have been the same key when it was returned.
What is the best way to store this Private Key and where???
I am no security expert so any advice on this will be appreciated as well as if i should rather use AES?
My code is below, I am only using 1 activity.
package com.example.rsatest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStore.LoadStoreParameter;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.ProtectionParameter;
import java.security.KeyStore.SecretKeyEntry;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.DropBoxManager.Entry;
import android.util.Base64;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends Activity {
String keyStoreFile;
Key privateKey = null;
boolean isUnlocked = false;
KeyStore keyStore = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
keyStoreFile = this.getFilesDir() + "/bpstore.keystore";
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
startActivity(new Intent("android.credentials.UNLOCK"));
isUnlocked = true;
} else {
startActivity(new Intent("com.android.credentials.UNLOCK"));
isUnlocked = true;
}
} catch (ActivityNotFoundException e) {
Log.e("TAG", "No UNLOCK activity: " + e.getMessage(), e);
isUnlocked = false;
}
if(isUnlocked){
privateKey = GetPrivateKey();
try{
char[] pw =("123").toCharArray();
keyStore = createKeyStore(this,keyStoreFile, pw);
PasswordProtection keyPassword = new PasswordProtection("pw-secret".toCharArray());
SecretKey sk = new SecretKey() {
#Override
public String getFormat() {
// TODO Auto-generated method stub
return privateKey.getFormat();
}
#Override
public byte[] getEncoded() {
// TODO Auto-generated method stub
return privateKey.getEncoded();
}
#Override
public String getAlgorithm() {
// TODO Auto-generated method stub
return privateKey.getAlgorithm();
}
};
System.out.println(sk.getEncoded());
System.out.println(privateKey.getEncoded());
KeyStore.SecretKeyEntry ent = new SecretKeyEntry(sk);
keyStore.setEntry("pk", ent, keyPassword);
keyStore.store(new FileOutputStream(keyStoreFile), pw);
KeyStore keyStore2;
keyStore2 = KeyStore.getInstance("BKS");
keyStore2.load(new FileInputStream(keyStoreFile), pw);
KeyStore.Entry entry = keyStore2.getEntry("pk", keyPassword);
KeyStore.SecretKeyEntry entOut = (KeyStore.SecretKeyEntry)entry;
}catch(Exception ex){
System.out.println("Error: " + ex.toString());
}
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
private KeyStore createKeyStore(Context context, String fileName, char[] pw) throws Exception {
System.out.println("[DIR]:" + fileName);
File file = new File(fileName);
keyStore = KeyStore.getInstance("BKS");
if (file.exists())
{
keyStore.load(new FileInputStream(file), pw);
} else
{
keyStore.load(null, null);
keyStore.store(new FileOutputStream(fileName), pw);
}
return keyStore;
}
private Key GetPrivateKey(){
String theTestText = "This is just a simple test!";
Key publicKey = null;
Key privateKey = null;
try {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair kp = kpg.genKeyPair();
publicKey = kp.getPublic();
privateKey = kp.getPrivate();
} catch (Exception e) {
Log.e("", "RSA key pair error");
}
// Encode the original data with RSA private key
byte[] encodedBytes = null;
try {
Cipher c = Cipher.getInstance("RSA");
c.init(Cipher.ENCRYPT_MODE, privateKey);
encodedBytes = c.doFinal(theTestText.getBytes());
} catch (Exception e) {
Log.e("", "RSA encryption error");
}
// Decode the encoded data with RSA public key
byte[] decodedBytes = null;
try {
Cipher c = Cipher.getInstance("RSA");
c.init(Cipher.DECRYPT_MODE, publicKey);
decodedBytes = c.doFinal(encodedBytes);
} catch (Exception e) {
Log.e("", "RSA decryption error");
}
return privateKey;
}
}
Thanks in advance,
Warren
Instead of trying to add RSA private key to the keystore we ended up using AES instead and wrap it using cipher. We have also included ProGuard for our Android project to make it harder to decompile our APK.
Thank you Maarten Bodewes for your answer and help.
That other post was a pretty specific error. It didn't have the correct tags so everybody missed it. About your code; why are you trying to store an asymmetric key as a symmetric key (SecretKey)? That will certainly not work. Note that the Java keystore interface is pretty much aimed at storing keys + certificates. You may want to use another storing method for just RSA private keys (e.g. wrap them yourself using Cipher).
I've really been struggling to get working code, good examples, and most importantly, good documentation on how to use Paypal's Java SDK for Encrypting Website Payments. I've looked to Paypal for help (posted on their forum, contacted support), but haven't gotten any help thus far.
I went to https://www.paypal.com/us/cgi-bin/?cmd=p/xcl/rec/ewp-code and downloaded the Paypal Java SDK. Within the zip, there is a ReadMe.txt file with instructions for setup. The instructions are simple enough.
I went to Bouncy Castle's site - http://www.bouncycastle.org/latest_releases.html - to download the latest versions of the following jars :
bcmail-jdk16-146.jar
bcpg-jdk16-146.jar
bcprov-jdk16-146.jar
bctest-jdk16-146.jar
I then went to http://www.oracle.com/technetwork/java/javase/downloads/index.html to download the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files.
I put all the JARS in the appropriate folders, updated the classpath and then tried to compile the ClientSide.java class that came with the Paypal Java SDK.
The compiler tells me that there are deprecated classes, showing me the following errors after recompiling with -Xlint.
.\palmb\servlets\paypal\ClientSide.java:98: warning: [deprecation] addSigner(jav
a.security.PrivateKey,java.security.cert.X509Certificate,java.lang.String) in org.bouncycastle.cms.CMSSignedDataGenerator has been deprecated
signedGenerator.addSigner( privateKey, certificate, CMSSignedDataGenerator.DIGEST_SHA1 );
^
.\palmb\servlets\paypal\ClientSide.java:101: warning: [unchecked] unchecked call
to add(E) as a member of the raw type java.util.ArrayList
certList.add(certificate);
^
.\palmb\servlets\paypal\ClientSide.java:103: warning: [deprecation] addCertificatesAndCRLs(java.security.cert.CertStore) in org.bouncycastle.cms.CMSSignedGenerator has been deprecated
signedGenerator.addCertificatesAndCRLs(certStore);
^
.\palmb\servlets\paypal\ClientSide.java:110: warning: [deprecation] generate(org.bouncycastle.cms.CMSProcessable,boolean,java.lang.String) in org.bouncycastle.cms.CMSSignedDataGenerator has been deprecated
CMSSignedData signedData = signedGenerator.generate(cmsByteArray, true, "BC");
^
.\palmb\servlets\paypal\ClientSide.java:115: warning: [deprecation] addKeyTransRecipient(java.security.cert.X509Certificate) in org.bouncycastle.cms.CMSEnvelopedGenerator has been deprecated envGenerator.addKeyTransRecipient(payPalCert);
^
.\palmb\servlets\paypal\ClientSide.java:116: warning: [deprecation] generate(org.bouncycastle.cms.CMSProcessable,java.lang.String,java.lang.String) in org.bouncycastle.cms.CMSEnvelopedDataGenerator has been deprecated
CMSEnvelopedData envData = envGenerator.generate( new CMSProcessableByteArray(signed),
^
6 warnings
I have Java 1.6 running on my machine. I'm disappointed in Paypal, in that they haven't provided nearly adequate, easy to understand documentation, and on to of that, for someone who needs an out of the box setup, their code doesn't work.
I went to Bouncy Castle's site (www.bouncycastle.org) and briefly looked at the documentation (http://www.bouncycastle.org/documentation.html) for version 1.6 - but I honestly have no clue how to use the methods that replace the deprecated ones.
Does anybody have experience with this Java Paypal code? Or experience with BouncyCastle and their code? If so, I'm in great need of some help.
ClientSide class
package palmb.servlets.paypal;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertStore;
import java.security.cert.CertStoreException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import org.bouncycastle.cms.CMSEnvelopedData;
import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.util.encoders.Base64;
/**
*/
public class ClientSide
{
private String keyPath;
private String certPath;
private String paypalCertPath;
private String keyPass;
public ClientSide( String keyPath, String certPath, String paypalCertPath, String keyPass )
{
this.keyPath = keyPath;
this.certPath = certPath;
this.paypalCertPath = paypalCertPath;
this.keyPass = keyPass;
}
public String getButtonEncryptionValue(String _data, String _privateKeyPath, String _certPath, String _payPalCertPath,
String _keyPass) throws IOException,CertificateException,KeyStoreException,
UnrecoverableKeyException,InvalidAlgorithmParameterException,NoSuchAlgorithmException,
NoSuchProviderException,CertStoreException,CMSException {
_data = _data.replace(',', '\n');
CertificateFactory cf = CertificateFactory.getInstance("X509", "BC");
// Read the Private Key
KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
ks.load( new FileInputStream(_privateKeyPath), _keyPass.toCharArray() );
String keyAlias = null;
Enumeration aliases = ks.aliases();
while (aliases.hasMoreElements()) {
keyAlias = (String) aliases.nextElement();
}
PrivateKey privateKey = (PrivateKey) ks.getKey( keyAlias, _keyPass.toCharArray() );
// Read the Certificate
X509Certificate certificate = (X509Certificate) cf.generateCertificate( new FileInputStream(_certPath) );
// Read the PayPal Cert
X509Certificate payPalCert = (X509Certificate) cf.generateCertificate( new FileInputStream(_payPalCertPath) );
// Create the Data
byte[] data = _data.getBytes();
// Sign the Data with my signing only key pair
CMSSignedDataGenerator signedGenerator = new CMSSignedDataGenerator();
signedGenerator.addSigner( privateKey, certificate, CMSSignedDataGenerator.DIGEST_SHA1 );
ArrayList certList = new ArrayList();
certList.add(certificate);
CertStore certStore = CertStore.getInstance( "Collection", new CollectionCertStoreParameters(certList) );
signedGenerator.addCertificatesAndCRLs(certStore);
CMSProcessableByteArray cmsByteArray = new CMSProcessableByteArray(data);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cmsByteArray.write(baos);
System.out.println( "CMSProcessableByteArray contains [" + baos.toString() + "]" );
CMSSignedData signedData = signedGenerator.generate(cmsByteArray, true, "BC");
byte[] signed = signedData.getEncoded();
CMSEnvelopedDataGenerator envGenerator = new CMSEnvelopedDataGenerator();
envGenerator.addKeyTransRecipient(payPalCert);
CMSEnvelopedData envData = envGenerator.generate( new CMSProcessableByteArray(signed),
CMSEnvelopedDataGenerator.DES_EDE3_CBC, "BC" );
byte[] pkcs7Bytes = envData.getEncoded();
return new String( DERtoPEM(pkcs7Bytes, "PKCS7") );
}
public static byte[] DERtoPEM(byte[] bytes, String headfoot)
{
ByteArrayOutputStream pemStream = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(pemStream);
byte[] stringBytes = Base64.encode(bytes);
System.out.println("Converting " + stringBytes.length + " bytes");
String encoded = new String(stringBytes);
if (headfoot != null) {
writer.print("-----BEGIN " + headfoot + "-----\n");
}
// write 64 chars per line till done
int i = 0;
while ((i + 1) * 64 < encoded.length()) {
writer.print(encoded.substring(i * 64, (i + 1) * 64));
writer.print("\n");
i++;
}
if (encoded.length() % 64 != 0) {
writer.print(encoded.substring(i * 64)); // write remainder
writer.print("\n");
}
if (headfoot != null) {
writer.print("-----END " + headfoot + "-----\n");
}
writer.flush();
return pemStream.toByteArray();
}
}
ButtonEncryption class
package palmb.servlets.paypal;
//import com.paypal.crypto.sample.*;
import palmb.servlets.paypal.ClientSide;
import java.io.*;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertStoreException;
import java.security.cert.CertificateException;
import org.bouncycastle.cms.CMSException;
/**
*/
public class ButtonEncryption {
//path to public cert
private static String certPath = "C:/jakarta-tomcat/webapps/PlanB/Certs/public-cert.pem";
//path to private key in PKCS12 format
private static String keyPath = "C:/jakarta-tomcat/webapps/PlanB/Certs/my_pkcs12.p12";
//path to Paypal's public cert
private static String paypalCertPath = "C:/jakarta-tomcat/webapps/PlanB/Certs/paypal_cert_pem.txt";
//private key password
private static String keyPass = "password"; //will be replaced with actual password when compiled and executed
//the button command, properties/parameters
private static String cmdText = "cmd=_xclick\nbusiness=buyer#hotmail.com\nitem_name=vase\nitemprice=25.00"; //cmd=_xclick,business=sample#paypal.com,amount=1.00,currency_code=USD
//output file for form code
private static String output = "test.html";
public static void main(String[] args)
{
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
String stage = "sandbox";
try
{
ClientSide client_side = new ClientSide( keyPath, certPath, paypalCertPath, keyPass );
String result = client_side.getButtonEncryptionValue( cmdText, keyPath, certPath, paypalCertPath, keyPass );
File outputFile = new File( output );
if ( outputFile.exists() )
outputFile.delete();
if ( result != null && result != "")
{
try {
OutputStream fout= new FileOutputStream( output );
OutputStream bout= new BufferedOutputStream(fout);
OutputStreamWriter out = new OutputStreamWriter(bout, "US-ASCII");
out.write( "<form action=\"https://www." );
out.write( stage );
out.write( "paypal.com/cgi-bin/webscr\" method=\"post\">" );
out.write( "<input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\">" ); ;
out.write( "<input type=\"image\" src=\"https://www." );
out.write( stage );
out.write( "paypal.com/en_US/i/btn/x-click-but23.gif\" border=\"0\" name=\"submit\" " );
out.write( "alt=\"Make payments with PayPal - it's fast, free and secure!\">" );
out.write( "<input type=\"hidden\" name=\"encrypted\" value=\"" );
out.write( result );
out.write( "\">" );
out.write( "</form>");
out.flush(); // Don't forget to flush!
out.close();
}
catch (UnsupportedEncodingException e) {
System.out.println(
"This VM does not support the ASCII character set."
);
}
catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
catch (NoSuchAlgorithmException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (NoSuchProviderException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (CMSException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (CertificateException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (KeyStoreException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (UnrecoverableKeyException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (InvalidAlgorithmParameterException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (CertStoreException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Edited - Exception from running ButtonEncryption class
C:\jakarta-tomcat\webapps\PlanB\WEB-INF\classes>java palmb.servlets.paypal.ButtonEncryption
java.io.IOException: exception decrypting data - java.security.InvalidKeyException: Illegal key size
at org.bouncycastle.jce.provider.JDKPKCS12KeyStore.cryptData(Unknown Source)
at org.bouncycastle.jce.provider.JDKPKCS12KeyStore.engineLoad(Unknown Source)
at java.security.KeyStore.load(Unknown Source)
at palmb.servlets.paypal.ClientSide.getButtonEncryptionValue(ClientSide.
java:63)
at palmb.servlets.paypal.ButtonEncryption.main(ButtonEncryption.java:81)
You are getting the illegalKeySize error because you didn't install the JCE files in the correct location. You likely have multiple JREs on your system.
As for answering your question about the deprecated functions... I came up with the below replacement functions to PayPal's sample code which works great (based on bouncycastle javadoc):
private final static String getButtonEncryptionValue(String commandData, String keystorePath,
String keystorePassword, boolean sandbox) throws IOException, CertificateException, KeyStoreException,
UnrecoverableKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
NoSuchProviderException, CertStoreException, CMSException, OperatorCreationException {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
commandData = commandData.replace(',', '\n');
CertificateFactory cf = CertificateFactory.getInstance("X509", "BC");
// Read the Private Key
KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
ks.load(new FileInputStream(keystorePath), keystorePassword.toCharArray());
String keyAlias = null;
Enumeration<String> aliases = ks.aliases();
while (aliases.hasMoreElements()) {
keyAlias = (String) aliases.nextElement();
}
PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias, keystorePassword.toCharArray());
// Read the Certificate
X509Certificate certificate = (X509Certificate) cf.generateCertificate(ApplicationProxyService.class
.getResourceAsStream("/myCompanyPublicCert.pem.cer"));
// Read the PayPal Cert
X509Certificate payPalCert = (X509Certificate) cf.generateCertificate(ApplicationProxyService.class
.getResourceAsStream("/paypalPublicCert" + (sandbox ? "-sandbox" : "") + ".pem.cer"));
// Create the Data
// System.out.println(commandData);
byte[] data = commandData.getBytes();
// Sign the Data with my signing only key pair
CMSSignedDataGenerator signedGenerator = new CMSSignedDataGenerator();
List<X509Certificate> certList = new ArrayList<X509Certificate>();
certList.add(certificate);
//deprecated: Store certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList));
Store certStore = new JcaCertStore(certList);
// deprecated: signedGenerator.addCertificatesAndCRLs(certStore);
signedGenerator.addCertificates(certStore);
// deprecated: signedGenerator.addSigner(privateKey, certificate, CMSSignedDataGenerator.DIGEST_SHA1);
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privateKey);
signedGenerator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(sha1Signer, certificate));
CMSProcessableByteArray cmsByteArray = new CMSProcessableByteArray(data);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cmsByteArray.write(baos);
LOGGER.debug("CMSProcessableByteArray contains [" + baos.toString() + "]");
// deprecated: CMSSignedData signedData = signedGenerator.generate(cmsByteArray, true, "BC");
CMSSignedData signedData = signedGenerator.generate(cmsByteArray, true);
byte[] signed = signedData.getEncoded();
CMSEnvelopedDataGenerator envGenerator = new CMSEnvelopedDataGenerator();
// deprecated: envGenerator.addKeyTransRecipient(payPalCert);
envGenerator.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(payPalCert).setProvider("BC"));
// deprecated: CMSEnvelopedData envData = envGenerator.generate(new CMSProcessableByteArray(signed),
// CMSEnvelopedDataGenerator.DES_EDE3_CBC, "BC");
CMSEnvelopedData envData = envGenerator.generate(new CMSProcessableByteArray(signed),
new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider("BC").build());
byte[] pkcs7Bytes = envData.getEncoded();
return new String(DERtoPEM(pkcs7Bytes, "PKCS7"));
}
I would also like to note that the sample DERtoPEM() function had a defect in it that would truncate the last line of the encrypted value if it happened to be 64 characters long (0 % 64 == 0 AND 64 % 64 == 0). Below is the fix:
private static final byte[] DERtoPEM(byte[] bytes, String headfoot) {
byte[] stringBytes = Base64.encode(bytes);
// System.out.println("Converting " + stringBytes.length + " bytes");
StringBuilder sb = new StringBuilder();
sb.append("-----BEGIN " + headfoot + "-----\n");
String encoded = new String(stringBytes);
// write 64 chars per line till done
int i = 0;
while ((i + 1) * 64 < encoded.length()) {
sb.append(encoded.substring(i * 64, (i + 1) * 64));
sb.append("\n");
i++;
}
// if (encoded.length() % 64 != 0) { //FIXME (fixed via next line): this is a BUG that drops remaining data if data.length==64!
String remainder = encoded.substring(i * 64);
if (StringUtils.isNotEmpty(remainder)) {
sb.append(remainder); // write remainder
sb.append("\n");
}
sb.append("-----END " + headfoot + "-----\n");
return sb.toString().getBytes();
}
Couldn't get the classes from Paypal to work, so decided to give the Paypal Button API a try. This proved to be the best way to go. I could still use Java, and let Paypal take care of encrypting the buttons. It turned out to be a simple process once I got things coded correctly.
To view information about the Paypal Button API click here.
I need to verify a signed xml document. As a part of the verification I need to check whether the certificate passed with the signed certificate is a trusted certificate.
All the trusted certificates are added to a keystore called trusted.keystore.
How can I check whether the certificate passed is a valid certificate?
I've wrote the following KeySelector, but it is not working
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.Iterator;
import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class X509KeySelector extends KeySelector {
private static Log log = LogFactory.getLog(X509KeySelector.class);
private KeyStore trustedStore;
public void setTrustedStore(KeyStore trustedStore) {
this.trustedStore = trustedStore;
}
#SuppressWarnings("rawtypes")
public KeySelectorResult select(KeyInfo keyInfo,
KeySelector.Purpose purpose, AlgorithmMethod method,
XMLCryptoContext context) throws KeySelectorException {
if (log.isDebugEnabled()) {
log.debug("Selecting key for algorithm: " + method.getAlgorithm());
}
Iterator ki = keyInfo.getContent().iterator();
while (ki.hasNext()) {
XMLStructure info = (XMLStructure) ki.next();
if (log.isDebugEnabled()) {
log.debug("Found xml structure: " + info.toString());
}
if (!(info instanceof X509Data)) {
if (log.isTraceEnabled()) {
log.trace("Ignoring xml structure since it is not a X509Data.");
}
continue;
}
X509Data x509Data = (X509Data) info;
Iterator xi = x509Data.getContent().iterator();
if (log.isDebugEnabled()) {
log.debug("Iterating X509Data: Size: "
+ x509Data.getContent().size());
}
while (xi.hasNext()) {
Object o = xi.next();
if (log.isDebugEnabled()) {
log.debug("Found object: " + o);
}
if (!(o instanceof X509Certificate)) {
if (log.isTraceEnabled()) {
log.trace("Ignoring object since it is not a X509Certificate");
}
continue;
}
X509Certificate cert = (X509Certificate) o;
if (!isTrustedCertificate(cert)) {
log.warn("Ignoring certificate since it is not a valid certificate. Certificate: "
+ cert);
continue;
}
final PublicKey key = cert.getPublicKey();
// Make sure the algorithm is compatible
// with the method.
if (algEquals(method.getAlgorithm(), key.getAlgorithm())) {
KeySelectorResult keySelectorResult = new KeySelectorResult() {
public Key getKey() {
return key;
}
};
return keySelectorResult;
} else {
log.warn("Ignoring certificate since the algorithms "
+ method.getAlgorithm() + " and "
+ key.getAlgorithm() + " does not match.");
}
}
}
log.error("Unable to find a valid certificate.");
throw new KeySelectorException("No key found!");
}
private boolean isTrustedCertificate(X509Certificate cert) {
if (trustedStore == null) {
return true;
}
boolean trusted = false;
try {
Enumeration<String> aliases = trustedStore.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
Certificate[] certificates = this.trustedStore
.getCertificateChain(alias);
if (certificates == null) {
Certificate certificate = this.trustedStore
.getCertificate(alias);
if (certificate != null) {
certificates = new Certificate[] { certificate };
}
}
if (certificates != null) {
for (Certificate certificate : certificates) {
if (!(certificate instanceof X509Certificate)) {
continue;
}
if (cert.getSignature().equals(
((X509Certificate) certificate).getSignature())) {
trusted = true;
break;
}
}
if (trusted) {
break;
}
}
}
} catch (KeyStoreException e) {
log.error(e.toString(), e);
}
return trusted;
}
static boolean algEquals(String algURI, String algName) {
if ((algName.equalsIgnoreCase("DSA") && algURI
.equalsIgnoreCase(SignatureMethod.DSA_SHA1))
|| (algName.equalsIgnoreCase("RSA") && algURI
.equalsIgnoreCase(SignatureMethod.RSA_SHA1))) {
return true;
} else {
return false;
}
}
}
The problem lies in the method isTrustedCertificate. Where I'm iterating through all the aliases in the keystore and check where it is the same as the passed certificate.
As the class name suggests it deals with only X509 type certificates.
Thank you
There is a simpler way to check this using a method that might not be obvious to use at first. The KeyStore class has a method called getCertificateAlias(Certificate cert). If you pass in the certificate you are trying to check for and you do not get a null return, then that certificate is in the KeyStore.
Try something like this:
private boolean isTrustedCertificate(X509Certificate cert) {
if (trustedStore == null) {
return true;
}
boolean trusted = false;
try {
if (cert != null) {
// Only returns null if cert is NOT in keystore.
String alias = trustedStore.getCertificateAlias(cert);
if (alias != null) {
trusted = true;
}
}
} catch (KeyStoreException e) {
log.error(e.toString(), e);
}
return trusted;
}
I think I was going the wrong way,
I found the verify(PublicKey) method in Certificate object, which will java.security.SignatureException: Signature does not match. exception if the certificates does not match.