├── .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 |
--------------------------------------------------------------------------------