├── .gitignore ├── AndroidManifest.xml ├── LICENSE.txt ├── README.md ├── ant.properties ├── assets └── master-cacert.pem ├── build.xml ├── gen └── com │ └── chariotsolutions │ └── example │ ├── BuildConfig.java │ ├── Manifest.java │ └── R.java ├── local.properties ├── pom.xml ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── drawable-xxhdpi │ └── ic_launcher.png ├── layout │ └── main.xml └── values │ └── strings.xml └── src └── main └── java └── com └── chariotsolutions └── example ├── ExampleActivity.java ├── http ├── Api.java ├── AuthenticationParameters.java ├── CustomTrustManager.java └── SSLContextFactory.java └── util └── IOUtil.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | *.class 4 | 5 | # Package Files # 6 | *.jar 7 | *.war 8 | *.ear 9 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Rich Freedman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | android-ssl 2 | =========== 3 | 4 | An example Android project using HTTPS/SSL with client certificates and self-signed server certificate. 5 | 6 | Note: To successfully run this application, you will need to do the following things: 7 | 8 | 1. Have or set up an HTTPS server with a self-signed SSL certificate 9 | 10 | 2. Generate a client certificate (.p12 file) and sign it with the server's private (CA) key 11 | 12 | 3. Export the server's certificate chain as a .pem file, and replace this project's /assets/master-cacert.pem with that file. 13 | Be sure to keep the name the same. 14 | 15 | 4. Copy the client certificate to the root directory of your phone or emulator's sdcard as client-cert.p12 16 | 17 | 5. Update this project's /res/values/strings.xml file - change the value of example_url to point to your server. 18 | The default value is https://www.google.com - this will work, but doesn't really test this code, as Google's SSL 19 | cert is trusted, and Google doesn't require a client certificate. 20 | -------------------------------------------------------------------------------- /ant.properties: -------------------------------------------------------------------------------- 1 | # This file is used to override default values used by the Ant build system. 2 | # 3 | # This file must be checked into Version Control Systems, as it is 4 | # integral to the build system of your project. 5 | 6 | # This file is only used by the Ant script. 7 | 8 | # You can use this to override default values such as 9 | # 'source.dir' for the location of your java source folder and 10 | # 'out.dir' for the location of your output folder. 11 | 12 | # You can also use it define how the release builds are signed by declaring 13 | # the following properties: 14 | # 'key.store' for the location of your keystore and 15 | # 'key.alias' for the name of the key to use. 16 | # The password will be asked during the build when you use the 'release' target. 17 | 18 | -------------------------------------------------------------------------------- /assets/master-cacert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEezCCA+SgAwIBAgIJANyFQsPppCWzMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD 3 | VQQGEwJVUzEVMBMGA1UECBMMUGVubnN5bHZhbmlhMRgwFgYDVQQHEw9Gb3J0IFdh 4 | c2hpbmd0b24xGjAYBgNVBAoTEUNoYXJpb3QgU29sdXRpb25zMQswCQYDVQQLEwJJ 5 | VDEaMBgGA1UEAxMRQ2hhcmlvdCBNYXN0ZXIgQ0ExKTAnBgkqhkiG9w0BCQEWGmNl 6 | cnRzQGNoYXJpb3Rzb2x1dGlvbnMuY29tMB4XDTA1MDIxMTIwNTkxMFoXDTE1MDIw 7 | OTIwNTkxMFowga4xCzAJBgNVBAYTAlVTMRUwEwYDVQQIEwxQZW5uc3lsdmFuaWEx 8 | GDAWBgNVBAcTD0ZvcnQgV2FzaGluZ3RvbjEaMBgGA1UEChMRQ2hhcmlvdCBTb2x1 9 | dGlvbnMxCzAJBgNVBAsTAklUMRowGAYDVQQDExFDaGFyaW90IE1hc3RlciBDQTEp 10 | MCcGCSqGSIb3DQEJARYaY2VydHNAY2hhcmlvdHNvbHV0aW9ucy5jb20wgZ8wDQYJ 11 | KoZIhvcNAQEBBQADgY0AMIGJAoGBALiPtgaPU2gZRw5M+q7/xA1L9Ft+3JIdDLka 12 | oMCxRnI/0yg841ziXc02R8PtRx6Z7XzvJWoR8RgyfR5SQEpXHoOkJdTsbAhl/dcE 13 | AjZi9NEytY6I+J7pj0cfZLhbiE6zOuAME7ceocHae2UeMwlOTahlxwv2kMVYBrD8 14 | 3GA1QrSHAgMBAAGjggGdMIIBmTAdBgNVHQ4EFgQUx++vRku+KxuJhcL+uzMlQDhz 15 | V3UwgeMGA1UdIwSB2zCB2IAUx++vRku+KxuJhcL+uzMlQDhzV3WhgbSkgbEwga4x 16 | CzAJBgNVBAYTAlVTMRUwEwYDVQQIEwxQZW5uc3lsdmFuaWExGDAWBgNVBAcTD0Zv 17 | cnQgV2FzaGluZ3RvbjEaMBgGA1UEChMRQ2hhcmlvdCBTb2x1dGlvbnMxCzAJBgNV 18 | BAsTAklUMRowGAYDVQQDExFDaGFyaW90IE1hc3RlciBDQTEpMCcGCSqGSIb3DQEJ 19 | ARYaY2VydHNAY2hhcmlvdHNvbHV0aW9ucy5jb22CCQDchULD6aQlszAPBgNVHRMB 20 | Af8EBTADAQH/MBEGCWCGSAGG+EIBAQQEAwIABzAJBgNVHRIEAjAAMCwGCWCGSAGG 21 | +EIBDQQfFh1DaGFyaW90IE1hc3RlciBDQSBDZXJ0aWZpY2F0ZTAlBgNVHREEHjAc 22 | gRpjZXJ0c0BjaGFyaW90c29sdXRpb25zLmNvbTAOBgNVHQ8BAf8EBAMCAQYwDQYJ 23 | KoZIhvcNAQEFBQADgYEAHzF0FG8XQbZMjaO5H3isi9JhoABnhVtTGvWLiLTKfzE/ 24 | UxCU1SL7BPPcVUIHYv6r9DAzOXA5K1mB+rChlu/nctWjKvFwWXkb/a7S7VPzEbRS 25 | Sk2M02x4siNnxDQm0KWaImXZd1CUjnOVxJqPZjyt1Y1kvul/Wyw8vrAg43Dj+6A= 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 49 | 50 | 51 | 52 | 56 | 57 | 69 | 70 | 71 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /gen/com/chariotsolutions/example/BuildConfig.java: -------------------------------------------------------------------------------- 1 | /** Automatically generated file. DO NOT MODIFY */ 2 | package com.chariotsolutions.example; 3 | 4 | public final class BuildConfig { 5 | public final static boolean DEBUG = true; 6 | } -------------------------------------------------------------------------------- /gen/com/chariotsolutions/example/Manifest.java: -------------------------------------------------------------------------------- 1 | package com.chariotsolutions.example; 2 | 3 | /* This stub is for using by IDE only. It is NOT the Manifest class actually packed into APK */ 4 | public final class Manifest { 5 | } -------------------------------------------------------------------------------- /gen/com/chariotsolutions/example/R.java: -------------------------------------------------------------------------------- 1 | package com.chariotsolutions.example; 2 | 3 | /* This stub is for using by IDE only. It is NOT the R class actually packed into APK */ 4 | public final class R { 5 | } -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must *NOT* be checked into Version Control Systems, 5 | # as it contains information specific to your local configuration. 6 | 7 | # location of the SDK. This is only used by Ant 8 | # For customization when using a Version Control System, please read the 9 | # header note. 10 | sdk.dir=/Users/rfreedman/android-sdk-macosx 11 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 4.0.0 8 | com.chariotsolutions 9 | example 10 | 1.0.0-SNAPSHOT 11 | apk 12 | Android SSL Example 13 | 14 | 15 | 16 | com.google.android 17 | android 18 | 2.2.1 19 | provided 20 | 21 | 22 | 23 | org.roboguice 24 | roboguice 25 | 2.0 26 | 27 | 28 | 29 | 30 | 31 | com.pivotallabs 32 | robolectric 33 | 1.1 34 | test 35 | 36 | 37 | 38 | junit 39 | junit 40 | 4.8.2 41 | test 42 | 43 | 44 | 45 | com.google.android 46 | android-test 47 | 2.2.1 48 | provided 49 | 50 | 51 | 52 | 53 | ${project.artifactId} 54 | src/main/java 55 | src/test/java 56 | 57 | 58 | 59 | com.jayway.maven.plugins.android.generation2 60 | android-maven-plugin 61 | 3.0.0 62 | 63 | 64 | 10 65 | 66 | 67 | true 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-10 15 | -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rfreedman/android-ssl/87c606d65233242480f0d20ade0ba110ffe1d990/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rfreedman/android-ssl/87c606d65233242480f0d20ade0ba110ffe1d990/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rfreedman/android-ssl/87c606d65233242480f0d20ade0ba110ffe1d990/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rfreedman/android-ssl/87c606d65233242480f0d20ade0ba110ffe1d990/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 16 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SSL Example 4 | https://www.google.com 5 | master-cacert.pem 6 | client-cert.p12 7 | chariot 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/com/chariotsolutions/example/ExampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.chariotsolutions.example; 2 | 3 | import android.content.res.AssetManager; 4 | import android.os.AsyncTask; 5 | import android.os.Bundle; 6 | import android.os.Environment; 7 | import android.util.Log; 8 | import android.widget.ScrollView; 9 | import android.widget.TextView; 10 | import com.chariotsolutions.example.http.Api; 11 | import com.chariotsolutions.example.http.AuthenticationParameters; 12 | import com.chariotsolutions.example.util.IOUtil; 13 | import roboguice.activity.RoboActivity; 14 | import roboguice.inject.ContentView; 15 | import roboguice.inject.InjectResource; 16 | import roboguice.inject.InjectView; 17 | 18 | import java.io.ByteArrayOutputStream; 19 | import java.io.File; 20 | import java.io.InputStream; 21 | import java.io.PrintWriter; 22 | 23 | @ContentView(R.layout.main) 24 | public class ExampleActivity extends RoboActivity { 25 | private static final String TAG = ExampleActivity.class.getSimpleName(); 26 | 27 | private Api exampleApi; 28 | 29 | @InjectView(R.id.mainTextView) 30 | TextView mainTextView; 31 | 32 | @InjectView(R.id.mainTextScroller) 33 | ScrollView mainTextScroller; 34 | 35 | @InjectResource(R.string.server_cert_asset_name) 36 | String caCertificateName; 37 | 38 | @InjectResource(R.string.client_cert_file_name) 39 | String clientCertificateName; 40 | 41 | @InjectResource(R.string.client_cert_password) 42 | String clientCertificatePassword; 43 | 44 | @InjectResource(R.string.example_url) 45 | String exampleUrl; 46 | 47 | @Override 48 | public void onCreate(Bundle savedInstanceState) { 49 | super.onCreate(savedInstanceState); 50 | } 51 | 52 | 53 | @Override 54 | protected void onResume() { 55 | super.onResume(); 56 | 57 | doRequest(); 58 | } 59 | 60 | private void updateOutput(String text) { 61 | mainTextView.setText(mainTextView.getText() + "\n\n" + text); 62 | } 63 | 64 | private void doRequest() { 65 | 66 | try { 67 | AuthenticationParameters authParams = new AuthenticationParameters(); 68 | authParams.setClientCertificate(getClientCertFile()); 69 | authParams.setClientCertificatePassword(clientCertificatePassword); 70 | authParams.setCaCertificate(readCaCert()); 71 | 72 | exampleApi = new Api(authParams); 73 | updateOutput("Connecting to " + exampleUrl); 74 | 75 | new AsyncTask() { 76 | @Override 77 | protected Object doInBackground(Object... objects) { 78 | 79 | try { 80 | String result = exampleApi.doGet(exampleUrl); 81 | int responseCode = exampleApi.getLastResponseCode(); 82 | if (responseCode == 200) { 83 | publishProgress(result); 84 | } else { 85 | publishProgress("HTTP Response Code: " + responseCode); 86 | } 87 | 88 | } catch (Throwable ex) { 89 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 90 | PrintWriter writer = new PrintWriter(baos); 91 | ex.printStackTrace(writer); 92 | writer.flush(); 93 | writer.close(); 94 | publishProgress(ex.toString() + " : " + baos.toString()); 95 | } 96 | 97 | return null; 98 | } 99 | 100 | @Override 101 | protected void onProgressUpdate(final Object... values) { 102 | StringBuilder buf = new StringBuilder(); 103 | for (final Object value : values) { 104 | buf.append(value.toString()); 105 | } 106 | updateOutput(buf.toString()); 107 | } 108 | 109 | @Override 110 | protected void onPostExecute(final Object result) { 111 | updateOutput("Done!"); 112 | } 113 | }.execute(); 114 | 115 | } catch (Exception ex) { 116 | Log.e(TAG, "failed to create timeApi", ex); 117 | updateOutput(ex.toString()); 118 | } 119 | } 120 | 121 | private File getClientCertFile() { 122 | File externalStorageDir = Environment.getExternalStorageDirectory(); 123 | return new File(externalStorageDir, clientCertificateName); 124 | } 125 | 126 | private String readCaCert() throws Exception { 127 | AssetManager assetManager = getAssets(); 128 | InputStream inputStream = assetManager.open(caCertificateName); 129 | return IOUtil.readFully(inputStream); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/chariotsolutions/example/http/Api.java: -------------------------------------------------------------------------------- 1 | package com.chariotsolutions.example.http; 2 | 3 | import com.chariotsolutions.example.util.IOUtil; 4 | 5 | import javax.net.ssl.HttpsURLConnection; 6 | import javax.net.ssl.SSLContext; 7 | import java.io.File; 8 | import java.net.CookieHandler; 9 | import java.net.CookieManager; 10 | import java.net.HttpURLConnection; 11 | import java.net.URL; 12 | 13 | 14 | /** 15 | * client-side interface to the back-end application. 16 | */ 17 | public class Api { 18 | 19 | private SSLContext sslContext; 20 | private int lastResponseCode; 21 | 22 | public int getLastResponseCode() { 23 | return lastResponseCode; 24 | } 25 | 26 | public Api(AuthenticationParameters authParams) throws Exception { 27 | 28 | File clientCertFile = authParams.getClientCertificate(); 29 | 30 | sslContext = SSLContextFactory.getInstance().makeContext(clientCertFile, authParams.getClientCertificatePassword(), authParams.getCaCertificate()); 31 | 32 | CookieHandler.setDefault(new CookieManager()); 33 | } 34 | 35 | 36 | public String doGet(String url) throws Exception { 37 | String result = null; 38 | 39 | HttpURLConnection urlConnection = null; 40 | try { 41 | URL requestedUrl = new URL(url); 42 | urlConnection = (HttpURLConnection) requestedUrl.openConnection(); 43 | if(urlConnection instanceof HttpsURLConnection) { 44 | ((HttpsURLConnection)urlConnection).setSSLSocketFactory(sslContext.getSocketFactory()); 45 | } 46 | urlConnection.setRequestMethod("GET"); 47 | urlConnection.setConnectTimeout(1500); 48 | urlConnection.setReadTimeout(1500); 49 | 50 | lastResponseCode = urlConnection.getResponseCode(); 51 | result = IOUtil.readFully(urlConnection.getInputStream()); 52 | 53 | } catch(Exception ex) { 54 | result = ex.toString(); 55 | } finally { 56 | if(urlConnection != null) { 57 | urlConnection.disconnect(); 58 | } 59 | } 60 | return result; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/chariotsolutions/example/http/AuthenticationParameters.java: -------------------------------------------------------------------------------- 1 | package com.chariotsolutions.example.http; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Authentication parameters, including client cert, server cert, user name, and password. 7 | */ 8 | public class AuthenticationParameters { 9 | private File clientCertificate = null; 10 | private String clientCertificatePassword = null; 11 | private String caCertificate = null; 12 | 13 | public File getClientCertificate() { 14 | return clientCertificate; 15 | } 16 | 17 | public void setClientCertificate(File clientCertificate) { 18 | this.clientCertificate = clientCertificate; 19 | } 20 | 21 | public String getClientCertificatePassword() { 22 | return clientCertificatePassword; 23 | } 24 | 25 | public void setClientCertificatePassword(String clientCertificatePassword) { 26 | this.clientCertificatePassword = clientCertificatePassword; 27 | } 28 | 29 | public String getCaCertificate() { 30 | return caCertificate; 31 | } 32 | 33 | public void setCaCertificate(String caCertificate) { 34 | this.caCertificate = caCertificate; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/chariotsolutions/example/http/CustomTrustManager.java: -------------------------------------------------------------------------------- 1 | package com.chariotsolutions.example.http; 2 | 3 | import javax.net.ssl.TrustManager; 4 | import javax.net.ssl.TrustManagerFactory; 5 | import javax.net.ssl.X509TrustManager; 6 | import java.security.KeyStore; 7 | import java.security.KeyStoreException; 8 | import java.security.NoSuchAlgorithmException; 9 | import java.security.Principal; 10 | import java.security.cert.*; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | /** 15 | * A custom X509TrustManager implementation that trusts a specified server certificate in addition 16 | * to those that are in the system TrustStore. 17 | * Also handles an out-of-order certificate chain, as is often produced by Apache's mod_ssl 18 | */ 19 | public class CustomTrustManager implements X509TrustManager { 20 | 21 | private final X509TrustManager originalX509TrustManager; 22 | private final KeyStore trustStore; 23 | 24 | /** 25 | * @param trustStore A KeyStore containing the server certificate that should be trusted 26 | * @throws NoSuchAlgorithmException 27 | * @throws KeyStoreException 28 | */ 29 | public CustomTrustManager(KeyStore trustStore) throws NoSuchAlgorithmException, KeyStoreException { 30 | this.trustStore = trustStore; 31 | 32 | TrustManagerFactory originalTrustManagerFactory = TrustManagerFactory.getInstance("X509"); 33 | originalTrustManagerFactory.init((KeyStore) null); 34 | 35 | TrustManager[] originalTrustManagers = originalTrustManagerFactory.getTrustManagers(); 36 | originalX509TrustManager = (X509TrustManager) originalTrustManagers[0]; 37 | } 38 | 39 | /** 40 | * No-op. Never invoked by client, only used in server-side implementations 41 | * @return 42 | */ 43 | public X509Certificate[] getAcceptedIssuers() { 44 | return new X509Certificate[0]; 45 | } 46 | 47 | /** 48 | * No-op. Never invoked by client, only used in server-side implementations 49 | * @return 50 | */ 51 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws java.security.cert.CertificateException { 52 | } 53 | 54 | 55 | /** 56 | * Given the partial or complete certificate chain provided by the peer, 57 | * build a certificate path to a trusted root and return if it can be validated and is trusted 58 | * for client SSL authentication based on the authentication type. The authentication type is 59 | * determined by the actual certificate used. For instance, if RSAPublicKey is used, the authType should be "RSA". 60 | * Checking is case-sensitive. 61 | * Defers to the default trust manager first, checks the cert supplied in the ctor if that fails. 62 | * @param chain the server's certificate chain 63 | * @param authType the authentication type based on the client certificate 64 | * @throws java.security.cert.CertificateException 65 | */ 66 | public void checkServerTrusted(X509Certificate[] chain, String authType) throws java.security.cert.CertificateException { 67 | try { 68 | originalX509TrustManager.checkServerTrusted(chain, authType); 69 | } catch(CertificateException originalException) { 70 | try { 71 | X509Certificate[] reorderedChain = reorderCertificateChain(chain); 72 | CertPathValidator validator = CertPathValidator.getInstance("PKIX"); 73 | CertificateFactory factory = CertificateFactory.getInstance("X509"); 74 | CertPath certPath = factory.generateCertPath(Arrays.asList(reorderedChain)); 75 | PKIXParameters params = new PKIXParameters(trustStore); 76 | params.setRevocationEnabled(false); 77 | validator.validate(certPath, params); 78 | } catch(Exception ex) { 79 | throw originalException; 80 | } 81 | } 82 | 83 | } 84 | 85 | /** 86 | * Puts the certificate chain in the proper order, to deal with out-of-order 87 | * certificate chains as are sometimes produced by Apache's mod_ssl 88 | * @param chain the certificate chain, possibly with bad ordering 89 | * @return the re-ordered certificate chain 90 | */ 91 | private X509Certificate[] reorderCertificateChain(X509Certificate[] chain) { 92 | 93 | X509Certificate[] reorderedChain = new X509Certificate[chain.length]; 94 | List certificates = Arrays.asList(chain); 95 | 96 | int position = chain.length - 1; 97 | X509Certificate rootCert = findRootCert(certificates); 98 | reorderedChain[position] = rootCert; 99 | 100 | X509Certificate cert = rootCert; 101 | while((cert = findSignedCert(cert, certificates)) != null && position > 0) { 102 | reorderedChain[--position] = cert; 103 | } 104 | 105 | return reorderedChain; 106 | } 107 | 108 | /** 109 | * A helper method for certificate re-ordering. 110 | * Finds the root certificate in a possibly out-of-order certificate chain. 111 | * @param certificates the certificate change, possibly out-of-order 112 | * @return the root certificate, if any, that was found in the list of certificates 113 | */ 114 | private X509Certificate findRootCert(List certificates) { 115 | X509Certificate rootCert = null; 116 | 117 | for(X509Certificate cert : certificates) { 118 | X509Certificate signer = findSigner(cert, certificates); 119 | if(signer == null || signer.equals(cert)) { // no signer present, or self-signed 120 | rootCert = cert; 121 | break; 122 | } 123 | } 124 | 125 | return rootCert; 126 | } 127 | 128 | /** 129 | * A helper method for certificate re-ordering. 130 | * Finds the first certificate in the list of certificates that is signed by the sigingCert. 131 | */ 132 | private X509Certificate findSignedCert(X509Certificate signingCert, List certificates) { 133 | X509Certificate signed = null; 134 | 135 | for(X509Certificate cert : certificates) { 136 | Principal signingCertSubjectDN = signingCert.getSubjectDN(); 137 | Principal certIssuerDN = cert.getIssuerDN(); 138 | if(certIssuerDN.equals(signingCertSubjectDN) && !cert.equals(signingCert)) { 139 | signed = cert; 140 | break; 141 | } 142 | } 143 | 144 | return signed; 145 | } 146 | 147 | /** 148 | * A helper method for certificate re-ordering. 149 | * Finds the certificate in the list of certificates that signed the signedCert. 150 | */ 151 | private X509Certificate findSigner(X509Certificate signedCert, List certificates) { 152 | X509Certificate signer = null; 153 | 154 | for(X509Certificate cert : certificates) { 155 | Principal certSubjectDN = cert.getSubjectDN(); 156 | Principal issuerDN = signedCert.getIssuerDN(); 157 | if(certSubjectDN.equals(issuerDN)) { 158 | signer = cert; 159 | break; 160 | } 161 | } 162 | 163 | return signer; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/com/chariotsolutions/example/http/SSLContextFactory.java: -------------------------------------------------------------------------------- 1 | package com.chariotsolutions.example.http; 2 | 3 | import android.util.Base64; 4 | import javax.net.ssl.KeyManager; 5 | import javax.net.ssl.KeyManagerFactory; 6 | import javax.net.ssl.SSLContext; 7 | import javax.net.ssl.TrustManager; 8 | import java.io.*; 9 | import java.security.KeyStore; 10 | import java.security.cert.CertificateFactory; 11 | import java.security.cert.X509Certificate; 12 | 13 | /** 14 | * A factory for SSLContexts. 15 | * Builds an SSLContext with custom KeyStore and TrustStore, to work with a client cert signed by a self-signed CA cert. 16 | */ 17 | public class SSLContextFactory { 18 | 19 | private static SSLContextFactory theInstance = null; 20 | 21 | private SSLContextFactory() { 22 | } 23 | 24 | public static SSLContextFactory getInstance() { 25 | if(theInstance == null) { 26 | theInstance = new SSLContextFactory(); 27 | } 28 | return theInstance; 29 | } 30 | 31 | /** 32 | * Creates an SSLContext with the client and server certificates 33 | * @param clientCertFile A File containing the client certificate 34 | * @param clientCertPassword Password for the client certificate 35 | * @param caCertString A String containing the server certificate 36 | * @return An initialized SSLContext 37 | * @throws Exception 38 | */ 39 | public SSLContext makeContext(File clientCertFile, String clientCertPassword, String caCertString) throws Exception { 40 | final KeyStore keyStore = loadPKCS12KeyStore(clientCertFile, clientCertPassword); 41 | KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509"); 42 | kmf.init(keyStore, clientCertPassword.toCharArray()); 43 | KeyManager[] keyManagers = kmf.getKeyManagers(); 44 | 45 | final KeyStore trustStore = loadPEMTrustStore(caCertString); 46 | TrustManager[] trustManagers = {new CustomTrustManager(trustStore)}; 47 | 48 | SSLContext sslContext = SSLContext.getInstance("TLS"); 49 | sslContext.init(keyManagers, trustManagers, null); 50 | 51 | return sslContext; 52 | } 53 | 54 | /** 55 | * Produces a KeyStore from a String containing a PEM certificate (typically, the server's CA certificate) 56 | * @param certificateString A String containing the PEM-encoded certificate 57 | * @return a KeyStore (to be used as a trust store) that contains the certificate 58 | * @throws Exception 59 | */ 60 | private KeyStore loadPEMTrustStore(String certificateString) throws Exception { 61 | 62 | byte[] der = loadPemCertificate(new ByteArrayInputStream(certificateString.getBytes())); 63 | ByteArrayInputStream derInputStream = new ByteArrayInputStream(der); 64 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 65 | X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream); 66 | String alias = cert.getSubjectX500Principal().getName(); 67 | 68 | KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); 69 | trustStore.load(null); 70 | trustStore.setCertificateEntry(alias, cert); 71 | 72 | return trustStore; 73 | } 74 | 75 | /** 76 | * Produces a KeyStore from a PKCS12 (.p12) certificate file, typically the client certificate 77 | * @param certificateFile A file containing the client certificate 78 | * @param clientCertPassword Password for the certificate 79 | * @return A KeyStore containing the certificate from the certificateFile 80 | * @throws Exception 81 | */ 82 | private KeyStore loadPKCS12KeyStore(File certificateFile, String clientCertPassword) throws Exception { 83 | KeyStore keyStore = null; 84 | FileInputStream fis = null; 85 | try { 86 | keyStore = KeyStore.getInstance("PKCS12"); 87 | fis = new FileInputStream(certificateFile); 88 | keyStore.load(fis, clientCertPassword.toCharArray()); 89 | } finally { 90 | try { 91 | if(fis != null) { 92 | fis.close(); 93 | } 94 | } catch(IOException ex) { 95 | // ignore 96 | } 97 | } 98 | return keyStore; 99 | } 100 | 101 | /** 102 | * Reads and decodes a base-64 encoded DER certificate (a .pem certificate), typically the server's CA cert. 103 | * @param certificateStream an InputStream from which to read the cert 104 | * @return a byte[] containing the decoded certificate 105 | * @throws IOException 106 | */ 107 | byte[] loadPemCertificate(InputStream certificateStream) throws IOException { 108 | 109 | byte[] der = null; 110 | BufferedReader br = null; 111 | 112 | try { 113 | StringBuilder buf = new StringBuilder(); 114 | br = new BufferedReader(new InputStreamReader(certificateStream)); 115 | 116 | String line = br.readLine(); 117 | while(line != null) { 118 | if(!line.startsWith("--")){ 119 | buf.append(line); 120 | } 121 | line = br.readLine(); 122 | } 123 | 124 | String pem = buf.toString(); 125 | der = Base64.decode(pem, Base64.DEFAULT); 126 | 127 | } finally { 128 | if(br != null) { 129 | br.close(); 130 | } 131 | } 132 | 133 | return der; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/com/chariotsolutions/example/util/IOUtil.java: -------------------------------------------------------------------------------- 1 | package com.chariotsolutions.example.util; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | 8 | public class IOUtil { 9 | public static String readFully(InputStream inputStream) throws IOException { 10 | 11 | if(inputStream == null) { 12 | return ""; 13 | } 14 | 15 | BufferedInputStream bufferedInputStream = null; 16 | ByteArrayOutputStream byteArrayOutputStream = null; 17 | 18 | try { 19 | bufferedInputStream = new BufferedInputStream(inputStream); 20 | byteArrayOutputStream = new ByteArrayOutputStream(); 21 | 22 | final byte[] buffer = new byte[1024]; 23 | int available = 0; 24 | 25 | while ((available = bufferedInputStream.read(buffer)) >= 0) { 26 | byteArrayOutputStream.write(buffer, 0, available); 27 | } 28 | 29 | return byteArrayOutputStream.toString(); 30 | 31 | } finally { 32 | if(bufferedInputStream != null) { 33 | bufferedInputStream.close(); 34 | } 35 | } 36 | } 37 | } 38 | --------------------------------------------------------------------------------