Android: соединение HTTPS (SSL) с использованием HttpsURLConnection

У меня есть 2 приложения, один - сервер Servlet/Tomcat, а другой - приложение для Android.

Я хочу использовать HttpURLConnection для отправки и получения XML между ними.

код:

    private String sendPostRequest(String requeststring) {

    DataInputStream dis = null;
    StringBuffer messagebuffer = new StringBuffer();

    HttpURLConnection urlConnection = null;

    try {
        URL url = new URL(this.getServerURL());

        urlConnection = (HttpURLConnection) url.openConnection();           

        urlConnection.setDoOutput(true);

        urlConnection.setRequestMethod("POST");

        OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());

        out.write(requeststring.getBytes());

        out.flush();

        InputStream in = new BufferedInputStream(urlConnection.getInputStream());

        dis = new DataInputStream(in);

        int ch;

        long len = urlConnection.getContentLength();

        if (len != -1) {

            for (int i = 0; i < len; i++)

                if ((ch = dis.read()) != -1) {

                    messagebuffer.append((char) ch);
                }
        } else {

            while ((ch = dis.read()) != -1)
                messagebuffer.append((char) ch);
        }

        dis.close();

    } catch (Exception e) {

        e.printStackTrace();

    } finally {

        urlConnection.disconnect();
    }

    return messagebuffer.toString();
}

Теперь мне нужно использовать SSL для отправки XML для безопасности.

Во-первых, я использую Java Keytool для генерации файла .keystore.

Keytool  -keygen -alias tomcat -keyalg RSA

Затем я поместил XML-код в файл server.xml Tomcat для использования SSL

<Connector 
port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
keystoreFile="c:/Documents and Settings/MyUser/.keystore"
keystorePass="password"
clientAuth="false" sslProtocol="TLS" 
/>

Затем я изменяю его HttpURLConnection для HttpsURLConnection

    private String sendPostRequest(String requeststring) {

    DataInputStream dis = null;
    StringBuffer messagebuffer = new StringBuffer();

    HttpURLConnection urlConnection = null;

    //Conexion por HTTPS
    HttpsURLConnection urlHttpsConnection = null;

    try {
        URL url = new URL(this.getServerURL());

        //urlConnection = (HttpURLConnection) url.openConnection();         

        //Si necesito usar HTTPS
        if (url.getProtocol().toLowerCase().equals("https")) {

            trustAllHosts();

            //Creo la Conexion
            urlHttpsConnection = (HttpsURLConnection) url.openConnection();

            //Seteo la verificacion para que NO verifique nada!!
            urlHttpsConnection.setHostnameVerifier(DO_NOT_VERIFY);

            //Asigno a la otra variable para usar simpre la mism
            urlConnection = urlHttpsConnection;

        } else {

            urlConnection = (HttpURLConnection) url.openConnection();
        }

//Do the same like up

и добавьте метод trustAllHosts для проверки каждого сервера (не проверяйте какой-либо сертификат)

private static void trustAllHosts() {

    X509TrustManager easyTrustManager = new X509TrustManager() {

        public void checkClientTrusted(
                X509Certificate[] chain,
                String authType) throws CertificateException {
            // Oh, I am easy!
        }

        public void checkServerTrusted(
                X509Certificate[] chain,
                String authType) throws CertificateException {
            // Oh, I am easy!
        }

        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

    };

    // Create a trust manager that does not validate certificate chains
    TrustManager[] trustAllCerts = new TrustManager[] {easyTrustManager};

    // Install the all-trusting trust manager
    try {
        SSLContext sc = SSLContext.getInstance("TLS");

        sc.init(null, trustAllCerts, new java.security.SecureRandom());

        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

    } catch (Exception e) {
            e.printStackTrace();
    }
}

Эти изменения работали очень хорошо, но я не хочу доверять каждому серверу. Я хочу использовать файл хранилища ключей для проверки соединения и правильного использования SSL. Я много читал в Интернете и проводил много тестов, но я не могу понять, что мне нужно делать и как это сделать.

Может кто-нибудь мне помочь?

Большое спасибо

Извините за мой плохой английский

------------------------- UPDATE 2011/08/24 ---------------- ---------------------------------

Хорошо, я все еще работаю над этим. Я сделал новый метод для установки KeyStore, InputStream и т.д.

Метод выглядит следующим образом:

private static void trustIFNetServer() {

    try {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

        KeyStore ks = KeyStore.getInstance("BKS");

        InputStream in = context.getResources().openRawResource(R.raw.mykeystore);

        String keyPassword = "password"; 

        ks.load(in, keyPassword.toCharArray());

        in.close();

        tmf.init(ks);

        TrustManager[] tms = tmf.getTrustManagers();    

        SSLContext sc = SSLContext.getInstance("TLS");

    sc.init(null, tms, new java.security.SecureRandom());

    } catch (Exception e) {
    e.printStackTrace();
    }
}

Сначала у меня было много проблем с ключом и сертификатом, но теперь он работает (я так думаю)

Моя проблема прямо сейчас - это исключение TimeOut. Я не знаю, почему это происходит. Я думаю, что что-то с данными пишут, но я пока не могу решить.

Любая идея?

Ответы

Ответ 1

Вам нужно создать файл хранилища доверия для вашего самозаверяющего сертификата, как описано здесь. Используйте его на стороне клиента для подключения к вашему серверу. На самом деле не имеет значения, используете ли вы JKS или другой формат, я буду считать JKS на данный момент.

Чтобы выполнить то, что вы имеете в виду, вам нужно другое TrustManager, очевидно. Вы можете использовать TrustManagerFactory и перенести свои настройки доверия с помощью только что созданного хранилища доверия.

TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream in = new FileInputStream("<path to your key store>");
ks.load(in, "password".toCharArray());
in.close();
tmf.init(ks);
TrustManager[] tms = tmf.getTrustManagers();

Используйте tms для инициализации SSLContext вместо новых параметров доверия, которые будут использоваться для вашего соединения SSL/TLS.

Также вы должны убедиться, что CN часть сертификата сервера TLS равна FQDN (полное доменное имя) вашего сервера, например. если ваш базовый URL-адрес сервера " https://www.example.com ', тогда CN сертификата должен быть" www.example.com ". Это необходимо для проверки имени узла, функции, которая предотвращает атаки" человек-в-середине". Вы можете отключить это, но только при использовании этого соединение будет действительно безопасным.

Ответ 2

Создайте свой магазин доверия, сохраните его как актив и используйте его для инициализации SocketFactory. Затем используйте factory вместо своего собственного "доверять всем".