仅信任在Android 6上由特定CA签名的证书

时间:2015-10-31 03:21:00

标签: java android security ssl android-6.0-marshmallow

亲爱的SO社区,

我正在建立一个处理敏感信息的安全应用。该应用程序通过SSL与我自己的RESTful API进行通信。我不想将应用限制为我发布的特定证书,而是仅信任我的提供商颁发的证书,例如科摩多。这样我就可以扩展并重新颁发证书,而无需发布应用更新。

我找到了获取this done here的绝佳资源,但Android 6已弃用HttpClient并已切换为HttpsURLConnection。 Google已their own approach posted here。然而,在实施时,我注意到,而不是抛出一个不被信任的"对于不同的证书,它只是强制使用本地CA证书,这不是我想要的行为。

是否有人使用HttpsURLConnection

仅引用信任特定CA.

1 个答案:

答案 0 :(得分:0)

好的我解决了,我想我会发布解决方案以防其他人遇到同样的问题。以下是使用HttpsUrlConnection获取JSON文件的代码:

(...)
public static class GetJsonTask extends AsyncTask<Void, Integer, AsyncResponse> {

    protected String jsonData;

    protected IGetJsonListener listener;
    protected Context context = null;
    protected String strUrl;

    public GetJsonTask(Context c, IGetJsonListener l, String strUrl) {
        super();
        listener = l;
        context = c;
        this.strUrl = strUrl;
    }

    @Override
    protected AsyncResponse doInBackground(Void... Void) {

        JsonObject jsonObjectResult = new JsonObject();
        APIStatus status;

        if (isConnected(context)) {
            HttpsURLConnection httpsURLConnection=null;
            try {
                //THIS IS KEY: context contains only our CA cert
                SSLContext sslContext = getSSLContext(context);
                if (sslContext != null) {
                    //for HTTP BASIC AUTH if your server implements this
                    //String encoded = Base64.encodeToString(
                    //        ("your_user_name" + ":" + "your_pwd").getBytes(),
                    //        Base64.DEFAULT);
                    URL url = new URL(strUrl);
                    httpsURLConnection = (HttpsURLConnection) url.openConnection();
                    httpsURLConnection.setRequestMethod("GET");
                    httpsURLConnection.setRequestProperty("Content-length", "0");
                    httpsURLConnection.setUseCaches(false);
                    httpsURLConnection.setAllowUserInteraction(false);
                    //FOR HTTP BASIC AUTH
                    //httpsURLConnection.setRequestProperty("Authorization", "Basic " + encoded);
                    //THIS IS KEY: Set connection to use custom socket factory
                    httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
                    //httpsURLConnection.setConnectTimeout(timeout);
                    //httpsURLConnection.setReadTimeout(timeout);
                    httpsURLConnection.connect();
                    status = getStatusFromCode(httpsURLConnection.getResponseCode());


                    listener.getJsonShowProgress(90);

                    if (status == APIStatus.OK) {

                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpsURLConnection.getInputStream()));
                        StringBuilder stringBuilder = new StringBuilder();

                        String line;
                        while ((line = bufferedReader.readLine()) != null) {
                            stringBuilder.append(line);
                        }
                        bufferedReader.close();
                        JsonParser parser = new JsonParser();
                        String s = stringBuilder.toString();
                        jsonObjectResult = (JsonObject) parser.parse(s);
                    }
                } else
                    status = APIStatus.AUTH_ERROR;
                listener.getJsonShowProgress(99);
            //THIS IS KEY: this exception is thrown if the certificate
            //is signed by a CA that is not our CA
            } catch (SSLHandshakeException e) {
                status = APIStatus.AUTH_ERROR;
                //React to what is probably a man-in-the-middle attack
            } catch (IOException e) {
                status = APIStatus.NET_ERROR;
            } catch (JsonParseException e) {
                status = APIStatus.JSON_ERROR;
            } catch (Exception e) {
                status = APIStatus.UNKNOWN_ERROR;
            } finally {
                if (httpsURLConnection != null)
                    httpsURLConnection.disconnect();
            }
        } else {
            status = APIStatus.NET_ERROR;
        }
        // if not successful issue another call for the next hour.
        AsyncResponse response = new AsyncResponse();
        response.jsonData = jsonObjectResult;
        response.opStatus = status;

        return response;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        if (listener != null)
            listener.getJsonStartProgress();
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        listener.getJsonShowProgress(progress[0]);
    }

    @Override
    protected void onPostExecute(AsyncResponse result) {
        listener.getJsonFinished(result.jsonData, result.opStatus);
    }

    public  interface IGetJsonListener {
        void getJsonStartProgress();
        void getJsonShowProgress(int percent);
        void getJsonFinished(JsonObject resJson, APIStatus status);
    }
}
private static SSLContext getSSLContext(Context context){
    //Mostly taken from the Google code link in the question.
    try {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        AssetManager am = context.getAssets();
        //THIS IS KEY: Your CA's cert stored in /assets/
        InputStream caInput = new BufferedInputStream(am.open("RootCA.crt"));
        Certificate ca;
        try {
            ca = cf.generateCertificate(caInput);
            //System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
        } finally {
            caInput.close();
        }

        // Create a KeyStore containing our trusted CAs
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);

        // Create a TrustManager that trusts the CAs in our KeyStore
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);

        // Create an SSLContext that uses our TrustManager
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);
        return sslContext;
    } catch (Exception e){
        return null;
    }

}

public enum APIStatus {
    OK("OK.", 200), //all went well
    JSON_ERROR("Error parsing response.", 1),
    NET_ERROR("Network error.", 2), //we couldn't reach the server
    UNKNOWN_ERROR("Unknown error.", 3), //some sh*t went down

    AUTH_ERROR("Authentication error.", 401), //credentials where wrong
    SERVER_ERROR("Internal server error.", 500), //server code crashed
    TIMEOUT("Operation timed out.", 408); //network too slow or server overloaded

    private String stringValue;
    private int intValue;

    private APIStatus(String toString, int value) {
        stringValue = toString;
        intValue = value;
    }

    @Override
    public String toString() {
        return stringValue;
    }
}

private static APIStatus getStatusFromCode(int code) {

    if (code==200 || code==201) {
        return APIStatus.OK;
    }else if (code == 401) {
        return APIStatus.AUTH_ERROR;
    } else if (code == 500) {
        return APIStatus.SERVER_ERROR;
    } else if (code == 408) {
        return APIStatus.TIMEOUT;
    } else {
        return APIStatus.UNKNOWN_ERROR;
    }

}

private static class AsyncResponse {
    public APIStatus opStatus;
    public JsonObject jsonData;
}
(...)

用法相当简单:

public class MyClass implements IGetJsonListener {
     (...)
     new GetJsonTask(context, this, "https://your.url.com/").execute();

     @Override
     public void getJsonFinished(JsonObject resJson, APIStatus status) {
        //Handle JSON content from web here
        (...)
     }
     (...)
}

我很想听到你有任何改进。