My Java client application needs to do REST calls. I was instructed to use Play's WS implementation. Currently, I have this:
AsyncHttpClientConfig.Builder builder = new com.ning.http.client.AsyncHttpClientConfig.Builder();
NingWSClient wsc = new play.libs.ws.ning.NingWSClient(builder.build());
WSRequestHolder holder = wsc.url("http://www.simpleweb.org/");
This works. However, my application needs to access a secure web service that uses SSL. I have a PKCS12 cert for my client. How can I get WS to use this certificate to establish a secure connection?
To be clear, this isn't a Play application.
Its not possible directly with WS. Play docs says : "WS does not support client certificates (aka mutual TLS / MTLS / client authentication). You should set the SSLContext directly in an instance of AsyncHttpClientConfig and set up the appropriate KeyStore and TrustStore."
You could do something like this maybe:
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory
.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance("pkcs12");
InputStream inputStream = new FileInputStream("YOUR.p12");
keyStore.load(inputStream, "Your password as char[]");
keyManagerFactory.init(keyStore, "Your password as char[]");
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(keyManagerFactory.getKeyManagers(), null,new SecureRandom());
AsyncHttpClientConfig httpClientConfig = new AsyncHttpClientConfig.Builder().setSSLContext(sslContext).build();
AsyncHttpClient httpClient = new AsyncHttpClient(httpClientConfig);
You want to use the parser. See https://www.playframework.com/documentation/2.3.x/KeyStores for details about the configuration.
val config = play.api.Configuration(ConfigFactory.parseString("""
|trustManager = {
| stores = [
| { type: "pkcs12", path: "/path/to/pkcs12/file", password: "foo" }
| ]
|}
""".stripMargin))
val parser = new DefaultSSLConfigParser(config, app.classloader)
val sslConfig = parser.parse()
val clientConfig = new DefaultWSClientConfig(sslConfig = sslConfig)
val secureDefaults = new NingAsyncHttpClientConfigBuilder(clientConfig).build()
val builder = new AsyncHttpClientConfig.Builder(secureDefaults)
val wsc = new play.libs.ws.ning.NingWSClient(builder.build());
val holder = wsc.url("http://www.simpleweb.org/");
Make sure you have added your Certificate to your trust-store like
this:
keytool -import -trustcacerts -keystore
{JAVA_HOME}/jre/lib/security/cacerts -noprompt -alias
-file {CORRECT_PATH}/what_ever.crt
If still the problem exists, set the path directly by setting java parameters in your execution command line like this:
-Djavax.net.ssl.trustStore={JAVA_HOME}/jre/lib/security/cacerts
Related
I have an ssl connection(2 way handshake) and I am unable to understand the why the following code procedures 400(openJdk 11, p12 file & password provided by the server , cer file provided by the server) ,
I have created the jks file from the cer file via the following command:
keytool -importcert -file example-api.cer -keystore example-api.jks
The code
File keyFile = new File(Objects.requireNonNull(exampleController.class.getClassLoader().
getResource("example-client-api1.p12")).getFile());
File trustFile = new File(Objects.requireNonNull(exampleController.class.getClassLoader().
getResource("example-api.jks")).getFile());
KeyStore keyStore = KeyStore.getInstance("PKCS12");
try(FileInputStream inStream = new FileInputStream(keyFile)) {
keyStore.load(inStream, "password".toCharArray());
}
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(trustFile, "password".toCharArray() ,new TrustAllStrategy()).
loadKeyMaterial(keyStore , "password".toCharArray()).build();
HostnameVerifier hostnameVerifier = new NoopHostnameVerifier();
SSLConnectionSocketFactory socketFactory =
new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLHostnameVerifier(hostnameVerifier)
.setSSLSocketFactory(socketFactory)
.useSystemProperties()
.build();
HttpGet httpget = new HttpGet("https://example-api/link?token=#Secret_Token#");
System.out.println("executing request" + httpget.getRequestLine());
return httpclient.execute(httpget);
The code above always returns 400 (No required SSL certificate was sent).
but the following curl works(on IOS):
curl https://example-api/link?token=#secret_token# --cacert ./example-api-ca.crt --cert ./example-client-api1.p12:password
Any help would be greatly appreciated
OK So after Some frustrating days:
I do not know the reason why but The problem was with the hyphen char ('-') in the hostName of the URL, removing hyphen sign fix the issue , not sure why , but posting it anyway, maybe someone could explain this phenomenon.
Example(using the code above):
example-api -> not working
example.api -> Works OK
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'm trying to load a comodo Positive SSL Multi-Site cert into Java's HttpsServer. I'm not getting any errors from the code, but when I try and access the URL in a browser it tells me there is an SSL error. Neither Chrome nor FireFox give any additional information. This cert is working fine in Apache.
Below is the code I am using. I've made it fairly verbose. Does anything stand out as incorrect? I've converted the private key to pkcs8 for importing. The certificate and bundle I'm loading are PEM encoded.
serverHttps = HttpsServer.create(new InetSocketAddress(ports[port_selector]), 0);
SSLContext sslContext = SSLContext.getInstance("TLS");
String alias = "alias";
// Load Certificates
InputStream stream = MyClass.class.getResourceAsStream("/certs/mycert.crt");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(stream);
stream.close();
stream = MyClass.class.getResourceAsStream("/certs/bundle.crt");
cf = CertificateFactory.getInstance("X.509");
Collection bundle = cf.generateCertificates(stream);
stream.close();
// Build cert chain
java.security.cert.Certificate[] chain = new Certificate[bundle.size()+1];
Iterator i = bundle.iterator();
int pos = 0;
while (i.hasNext()) {
chain[pos] = (Certificate)i.next();
pos++;
}
chain[chain.length-1] = cert;
// Load private key
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
stream = MyClass.class.getResourceAsStream("/certs/pkcs8_my_key");
PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(IOUtils.toByteArray(stream));
RSAPrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8);
stream.close();
stream = null;
KeyStore ks = KeyStore.getInstance("JKS");
char[] ksPassword = "mypass".toCharArray();
ks.load(null, ksPassword);
ks.setKeyEntry(alias, privKey, ksPassword, chain);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, ksPassword);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
// serverHttps.setHttpsConfigurator(new HttpsConfigurator(sslContext));
serverHttps.setHttpsConfigurator ( new HttpsConfigurator( sslContext )
{
#Override
public void configure ( HttpsParameters params )
{
try
{
// initialise the SSL context
SSLContext c = SSLContext.getDefault ();
SSLEngine engine = c.createSSLEngine ();
params.setNeedClientAuth ( false );
params.setCipherSuites ( engine.getEnabledCipherSuites () );
params.setProtocols ( engine.getEnabledProtocols () );
// get the default parameters
SSLParameters defaultSSLParameters = c.getDefaultSSLParameters ();
params.setSSLParameters ( defaultSSLParameters );
}
catch ( Exception ex )
{
System.out.println( "Failed to configure HTTPS server: "+ex.getMessage() );
System.exit(100);
}
}
} );
Your server cert must be chain[0] in the keystore entry.
The remaining certs should be in upward order i.e. root last -- and when you use keytool it puts them in that order -- because JSSE server sends them in the keystore order and SSL/TLS protocol says they should be sent in upward order. However, in my experience (most?) browsers/clients will tolerate the rest of the chain being out of order as long as the server cert is first.
PS: I think everything in your configure overrride is unnecessary. You haven't done anything to make the parameters of your SSLContext different from the default one, and the SSLParameters of the default context are (and override) the CipherSuites and Protocols you just set individually. But I can't easily test.
I'm trying to sign my files with my smart card. I initialize my keystore like this:
String pkcs11config = "name = CertumSmartCard \n" + "library = "
+ new File(".").getAbsolutePath() + "/cryptoCertum3PKCS.dll";
Provider pkcs11Provider = new SunPKCS11(new ByteArrayInputStream(
pkcs11config.getBytes()));
Security.addProvider(pkcs11Provider);
KeyStore keyStore = KeyStore.getInstance("PKCS11", pkcs11Provider);
keyStore.load(null, pin.toCharArray());
and then I try to read certificate chain using:
Enumeration<String> aliasesEnum = keyStore.aliases();
String alias = null;
while (aliasesEnum.hasMoreElements()) {
alias = aliasesEnum.nextElement();
Certificate[] certChain = keyStore.getCertificateChain(alias);
(...)
}
but unfortunately I get only one certificate in my chain (certificate of the owner of this card). I don't have any trusted root certificates so during validation I get an error that file was signed using untrusted certicicate.
Do you have any idea? Should I use SunPKSC11 class? It doesn't work with java 7 (I use java 6), looks like it's deprecated. Are there any other libraries to get into the card's guts?
Actually this card contained only one certificate so my code worked properly. I added missing certificates manually and connected them into chain. With that chain I could sign my file. I got missing certificates by saving them from another application (proCertum Smart Card) oficially used for singning file with this type of certificate.
I think Your problem was inside the while loop :
while (aliasesEnum.hasMoreElements()) {
alias = aliasesEnum.nextElement();
**Certificate[] certChain = keyStore.getCertificateChain(alias);**
(...)
}
in this case you will get only one certificate with the last alias .
so , I sugget you to change the code to be like this :
Certificate[] certChain = new Certificate[NumnberOfYourCertificates];
int count = 0 ;
while (aliasesEnum.hasMoreElements()) {
alias = aliasesEnum.nextElement();
certChain[count++] = keyStore.getCertificateChain(alias);
(...)
}
I think it will work .
I am in the process of learning SSL and in this process I am attempting to create an SSL connection between a .NET server with a Java client. I use a self signed certificate for this purpose. I don't want to use the standard keystore in Java so I create a custom keystore and load that instead.
I use the following steps to generate the certificate and the pfx file to use at the .NET server end.
Generated a cerficiate using the following command on windows.
makecert -r -pe -sr "localhost" -$ individual -n "CN=localhost" -sv
.pvk -r localhost.cer
Converted this to a .pfx so that I can load this certificate on the .NET server app.
Exported the .cer file as a .pem (Base64 format).
Took the .cer file (the public component of the above mentioned certificate) and created a .jks file (JavaKeyStore) to use as the java client using the following command.
keytool \
-import \
-v \
-trustcacerts \
-alias 0 \
-file <(openssl x509 -in localhost.pem) \
-keystore mystore.jks\
-storetype JKS\
-storepass ez24get
Loaded this .jks in the Java client app and initiated the connection with the following code
FileInputStream fis = new FileInputStream("res/myjksstore.jks");
KeyStore trusted = KeyStore.getInstance("JKS");
trusted.load(fis, "ez24get".toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trusted);
SSLContext context = SSLContext.getInstance("SSL");
context.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
Socket socket = context.getSocketFactory().createSocket("localhost", 443);
String str = "abc123";
socket.getOutputStream().write(GeneralUtil.toByta(str.length()));
socket.getOutputStream().write(str.getBytes());
socket.setKeepAlive(true);
But when I try to write data to the socket, I get the following error at the server end
System.ComponentModel.Win32Exception: The client and server cannot communicate, because they do not possess a common algorithm
My server code looks as follows...
X509Certificate cert = new X509Certificate("localhost.pfx", "abc123");
TcpListener listener = new TcpListener(IPAddress.Loopback, 443);
listener.Start();
while (true) {
try { TcpClient tcpClient = listener.AcceptTcpClient();
NetworkStream networkStream = tcpClient.GetStream();
SslStream sslStream = new SslStream(networkStream);
sslStream.AuthenticateAsServer(cert, false, SslProtocols.Default,
false);
byte[] data1 = new byte[4];
sslStream.Read(data1, 0, data1.Length);
int len = BitConverter.ToInt32(data1, 0);
String message = "Length of incoming data " +
BitConverter.ToInt32(data1, 0);
byte[] data2 = new byte[len];
sslStream.Read(data2, 0, data2.Length);
message += " Message: " + ASCIIEncoding.ASCII.GetString(data2);
Thread.Sleep(1000);
} catch (Exception ex)
{
}
}
The exception occurs at the line
sslStream.AuthenticateAsServer(cert, false, SslProtocols.Default, false);
What could be the reason for this and how can I fix it ?
Any help would be highly appreciated.
SSLEngine (obtained from SSLContext) must be set to client mode (setUseClientMode)