Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use an SSL client certificate with Apache HttpClient?

In Python I was using requests like this:

requests.put(               webdavURL,                auth=(tUsername, tPassword),                data=webdavFpb,                verify=False,                cert=("/path/to/file.pem", "/path/to/file.key")) 

Easy as pie.

Now I need to implement the same thing in Java using Apache HttpClient. How can I pass a client certificate when making requests using HttpClient?

like image 971
daniels Avatar asked Jan 19 '14 21:01

daniels


People also ask

How do I pass a certificate in HTTP request?

You need to send the client certificate during the TLS handshake before anything HTTP (methods, headers, URLs, request bodies) is available to be influenced. The server will not accept a client certificate sent later.


1 Answers

I think the main difference is that in java, you usually put the key and the certificate to a key store and use it from there. Like you mention often people do want to use a separate library for it, like mentioned httpcomponents client (just like you're using requests library in your python example).

Here's an example of using a client certificate from a key store, using the previously mentioned library:

import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils; import org.junit.Test;  import javax.net.ssl.SSLContext; import java.io.InputStream; import java.security.KeyStore;  import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull;  public class MyClientCertTest {      private static final String KEYSTOREPATH = "/clientkeystore.jks"; // or .p12     private static final String KEYSTOREPASS = "keystorepass";     private static final String KEYPASS = "keypass";      KeyStore readStore() throws Exception {         try (InputStream keyStoreStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {             KeyStore keyStore = KeyStore.getInstance("JKS"); // or "PKCS12"             keyStore.load(keyStoreStream, KEYSTOREPASS.toCharArray());             return keyStore;         }     }     @Test     public void readKeyStore() throws Exception {         assertNotNull(readStore());     }     @Test     public void performClientRequest() throws Exception {         SSLContext sslContext = SSLContexts.custom()                 .loadKeyMaterial(readStore(), KEYPASS.toCharArray()) // use null as second param if you don't have a separate key password                 .build();          HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();         HttpResponse response = httpClient.execute(new HttpGet("https://slsh.iki.fi/client-certificate/protected/"));         assertEquals(200, response.getStatusLine().getStatusCode());         HttpEntity entity = response.getEntity();          System.out.println("----------------------------------------");         System.out.println(response.getStatusLine());         EntityUtils.consume(entity);     } } 

Maven pom for dependency versions:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>      <groupId>com.acme</groupId>     <artifactId>httptests</artifactId>     <version>0.0.1-SNAPSHOT</version>     <properties>         <maven.compiler.source>1.8</maven.compiler.source>         <maven.compiler.target>1.8</maven.compiler.target>     </properties>     <dependencies>         <dependency>             <groupId>org.apache.httpcomponents</groupId>             <artifactId>httpclient</artifactId>             <version>4.5.3</version>         </dependency>         <dependency>             <groupId>junit</groupId>             <artifactId>junit</artifactId>             <version>4.12</version>             <scope>test</scope>         </dependency>     </dependencies>     <build>         <plugins>             <plugin>                 <groupId>org.apache.maven.plugins</groupId>                 <artifactId>maven-surefire-plugin</artifactId>                 <version>2.9</version>                 <!-- this is not needed, but useful if you want to debug what's going                      on with your connection -->                 <configuration>                     <argLine>-Djavax.net.debug=all</argLine>                 </configuration>             </plugin>         </plugins>     </build> </project> 

I've also published a simple test page for testing a client certificate.


Just to demonstrate that it can be done, below is an example of using client certificate just using standard java api, without extra libraries.

import org.junit.Test;  import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.security.KeyStore;  public class PlainJavaHTTPS2Test {      @Test     public void testJKSKeyStore() throws Exception {         final String KEYSTOREPATH = "clientkeystore.jks";         final char[] KEYSTOREPASS = "keystorepass".toCharArray();         final char[] KEYPASS = "keypass".toCharArray();          try (InputStream storeStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {             setSSLFactories(storeStream, "JKS", KEYSTOREPASS, KEYPASS);         }         testPlainJavaHTTPS();     }     @Test     public void testP12KeyStore() throws Exception {         final String KEYSTOREPATH = "clientkeystore.p12";         final char[] KEYSTOREPASS = "keystorepass".toCharArray();         final char[] KEYPASS = "keypass".toCharArray();          try (InputStream storeStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {             setSSLFactories(storeStream, "PKCS12", KEYSTOREPASS, KEYPASS);         }         testPlainJavaHTTPS();     }     private static void setSSLFactories(InputStream keyStream, String keystoreType, char[] keyStorePassword, char[] keyPassword) throws Exception     {         KeyStore keyStore = KeyStore.getInstance(keystoreType);          keyStore.load(keyStream, keyStorePassword);          KeyManagerFactory keyFactory =                 KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());          keyFactory.init(keyStore, keyPassword);          KeyManager[] keyManagers = keyFactory.getKeyManagers();          SSLContext sslContext = SSLContext.getInstance("SSL");         sslContext.init(keyManagers, null, null);         SSLContext.setDefault(sslContext);     }      public void testPlainJavaHTTPS() throws Exception {         String httpsURL = "https://slsh.iki.fi/client-certificate/protected/";         URL myUrl = new URL(httpsURL);         HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection();         try (InputStream is = conn.getInputStream()) {             InputStreamReader isr = new InputStreamReader(is);             BufferedReader br = new BufferedReader(isr);              String inputLine;              while ((inputLine = br.readLine()) != null) {                 System.out.println(inputLine);             }         }     } } 

And here's a third version with least amount of code, but which relies on the fact that a) keystore is a file on disk, not within jar, and b) key password must be identical to keystore password.

import org.junit.BeforeClass; import org.junit.Test;  import java.net.URL; import java.io.*; import javax.net.ssl.HttpsURLConnection;  public class PlainJavaHTTPSTest {      @BeforeClass     public static void setUp() {         System.setProperty("javax.net.ssl.keyStore", "/full/path/to/clientkeystore-samepassword.jks");         System.setProperty("javax.net.ssl.keyStorePassword", "keystorepass");     }      @Test     public void testPlainJavaHTTPS() throws Exception {         String httpsURL = "https://slsh.iki.fi/client-certificate/protected/";         URL myUrl = new URL(httpsURL);         HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection();         try (InputStream is = conn.getInputStream()) {             InputStreamReader isr = new InputStreamReader(is);             BufferedReader br = new BufferedReader(isr);              String inputLine;              while ((inputLine = br.readLine()) != null) {                 System.out.println(inputLine);             }         }     } } 

The properties set above in code can of course be also given as startup parameters, -Djavax.net.ssl.keyStore=/full/path/to/clientkeystore-samepassword.jks and -Djavax.net.ssl.keyStorePassword=keystorepass.

like image 81
eis Avatar answered Sep 28 '22 21:09

eis