Подключение к серверу Parse на VPS с использованием https (самостоятельный сертификат для SSL)

По некоторым причинам пользователи Parse должны перенести свою среду Parse на VPS (это касается моего вопроса) или Heroku, AWS (эти платформы не нужны) и т. д. Существует новый Parse SDK для Android (1.13.0) который позволяет инициализировать соединение с использованием нового интерфейса Parse следующим образом:

Parse.initialize(new Parse.Configuration.Builder(this)
                .applicationId("myAppId")
                .clientKey(null) 
                .addNetworkInterceptor(new ParseLogInterceptor())
                .server("https://VPS_STATIC_IP_ADDRESS/parse/").build());

Этот тип запроса выполняется с использованием порта 443. Соответствующий файл коннектора .js (nodejs) уже отредактирован, так что порт 443 локально подключен к порту 1337 (прослушиватель портов) и работает при доступе к серверу Parse в браузере (удаленно, конечно: из-за пределов VPS), где можно применить самоподписанный сертификат и пойти дальше. Но когда Android-приложение (лаунчер) пытается его подключить, оно не может из-за самозаверяющего сертификата. Есть ли возможность из Parse SDK применить самозаверяющий сертификат?

P.S. Правда ли, что есть ошибка, связанная с этой проблемой, и что это причина, по которой была выпущена версия 1.13.1 Parse? Если да, то где можно взять jar-библиотеку этой версии?

Благодарю вас!


person LowLevel    schedule 18.06.2016    source источник
comment
Наверное, надо исправить опечатку в названии...   -  person Croc    schedule 12.07.2016


Ответы (1)


Я только что решил это - Parse SDK для Android не поставляется с готовой поддержкой самоподписанных сертификатов. Вам нужно изменить код самостоятельно.

Первый шаг. Соответствующий фрагмент кода находится в ParseHttpClient.

  public static ParseHttpClient createClient(int socketOperationTimeout,
      SSLSessionCache sslSessionCache) {
    String httpClientLibraryName;
    ParseHttpClient httpClient;
    if (hasOkHttpOnClasspath()) {
      httpClientLibraryName = OKHTTP_NAME;
      httpClient =  new ParseOkHttpClient(socketOperationTimeout, sslSessionCache);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      httpClientLibraryName = URLCONNECTION_NAME;
      httpClient =  new ParseURLConnectionHttpClient(socketOperationTimeout, sslSessionCache);
    } else {
      httpClientLibraryName = APACHE_HTTPCLIENT_NAME;
      httpClient =  new ParseApacheHttpClient(socketOperationTimeout, sslSessionCache);
    }
    PLog.i(TAG, "Using " + httpClientLibraryName + " library for networking communication.");
    return httpClient;   }

Если ваша целевая поддержка предназначена для более продвинутой версии, чем KITKAT, вам нужно добавить в конструктор ParseURLConnectionHttpClient:

    HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){

            public boolean verify(String hostname, SSLSession session) {
                if(hostname.equals("YOUR TARGET SERVER")) {
                    return true;
                }
                return false;
            }});

В остальных случаях (более старые версии) код будет падать на апач, работать с ним у меня не получилось - поэтому сделал следующее: добавил в приложение библиотеку okhttp (возьмем версию 2.4 - тот же самый парс указывает в файле build , самый последний имеет другое имя пакета), а затем код перейдет к первому условию, так как он найдет okhttp в пути. Вероятно, вам следует заменить порядок условий if, чтобы это происходило только в версиях меньше, чем KITKAT.

В ParseOkHttpClient добавьте следующий код самоподписанного сертификата:

   public void initCert() {
    try {
        Log.i("PARSE","initCert");

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        String yairCert = "-----BEGIN CERTIFICATE-----\n" +
                YOUR CERTIFICATE HERE
                "-----END CERTIFICATE-----\n";
        InputStream caInput = new ByteArrayInputStream(yairCert.getBytes());
        Certificate ca = null;
        try {
            ca = cf.generateCertificate(caInput);
            System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
        } catch (CertificateException e) {
            e.printStackTrace();
        } finally {
            try {
                caInput.close();
            } catch (IOException e) {
                Log.e("PARSE_BUG","Failure on Cert installing",e);
                e.printStackTrace();
            }
        }

        // 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 context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);
        Log.i("PARSE","Initiating Self Signed cert");
        okHttpClient.setSslSocketFactory(context.getSocketFactory());
        try {
            cf = CertificateFactory.getInstance("X.509");
        } catch (CertificateException e) {
            Log.e("PARSE_BUG","Failure on Cert installing",e);
            e.printStackTrace();
        }
    } catch (IOException e) {
        Log.e("PARSE_BUG","Failure on Cert installing",e);
        e.printStackTrace();
    } catch (CertificateException e) {
        Log.e("PARSE_BUG","Failure on Cert installing",e);
        e.printStackTrace();

    } catch (NoSuchAlgorithmException e) {
        Log.e("PARSE_BUG","Failure on Cert installing",e);
        e.printStackTrace();
    } catch (KeyStoreException e) {
        Log.e("PARSE_BUG","Failure on Cert installing",e);
        e.printStackTrace();
    } catch (KeyManagementException e) {
        Log.e("PARSE_BUG","Failure on Cert installing",e);
        e.printStackTrace();
    }

И последняя часть — это вызов этого метода + проверка имени хоста, это тоже должно происходить в Конструкторе. инитцерт(); okHttpClient.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String s, SSLSession sslSession) { if(s.equals("ВАШ ЦЕЛЕВОЙ СЕРВЕР")) { return true; } return false; } });

Вот и все. Соберите PARSE локально и разверните его в своем приложении, и он будет работать как шарм.

Наслаждаться

person Croc    schedule 11.07.2016