All Products
Search
Document Center

Alibaba Cloud DNS:Connect an Android app to an IP address over HTTPS

Last Updated:Dec 27, 2023

This topic describes how to connect an Android app to an IP address over HTTPS. You can also specify the Server Name Indication (SNI) in HTTPS requests.

Overview

HTTPS is an extension of HTTP. It is used to implement secure communication over computer networks. Transport Layer Security (TLS), or formerly Secure Sockets Layer (SSL), is used to establish secure channels and encrypt data. HTTPS provides identity authentication for website servers. It also ensures the confidentiality and integrity of exchanged data. The SNI is an optional configuration for HTTPS requests. It is an extension to the TLS or SSL protocol and indicates the hostname to which a client attempts to connect. It allows a server to host multiple domain names under the same IP address.

  • Common HTTPS requests

Developers can easily connect an Android app to an IP address over HTTPS by replacing the Host header value in the URL of the HTTPS request with the IP address. However, you need to replace the IP address with the original Host header value for certificate verification. The original Host header value indicates the original domain name.

  • HTTPS requests with the SNI

When the SNI is specified, websites can use their own HTTPS certificates while still being hosted on a shared IP address. In this case, developers must use SSLSocketFactory of HttpsURLConnection to establish a connection to the required website. When you call the createSocket method, you need to use the API operations in Alibaba Cloud Public DNS SDK for Android to replace the domain name with an IP address. This IP address is resolved from the domain name of the required website. You also need to specify the SNI and the HostNameVerify configurations.

Important

OkHttp provides the Dns operation, which allows you to perform custom DNS resolution. Then, you can connect Android apps to the IP address. If you are an Android developer and use OkHttp, we recommend that you use OkHttp together with Alibaba Cloud Public DNS SDK for Android. For more information, see Best practices for integrating Alibaba Cloud Public DNS SDK on Android apps that use OkHttp.

Solutions

  • Common HTTPS requests

After you use an IP address in the URL of an HTTPS request, the website to which the IP address points may not pass the certificate verification. In this case, a hook can be used to implement certificate verification. You can replace the IP address with the original domain name, and then verify the website certificate.

HttpURLConnection is used in the following example.

try {
      String url = "https://124.239.XX.XX/?sprefer=sypc00";
      HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();

      connection.setRequestProperty("Host", "m.taobao.com");
      connection.setHostnameVerifier(new HostnameVerifier() {

      /*
       * Descriptions about HttpURLConnection from the Android official documentation:
       * This is an extended verification option that implementers can provide.
       * It is to be used during a handshake if the URL's hostname does not match the
       * peer's identification hostname.
       *
       * After you use Alibaba Cloud Public DNS SDK to perform the DNS resolution, the IP address in the new URL is not the hostname of a remote host that has passed the certificate verification. In the following example, the hostname of a certified remote host is used, which is m.taobao.com. 
       * HttpsURLConnection provides a callback that you can use to modify the hostname in the URL. 
       * You need to check whether the IP address returned by using Alibaba Cloud Public DNS SDK is the same as the IP address carried by the session. If they are the same, you can use this callback to replace the IP address with the original domain name before you verify the certificate. 
       *
       */

        @Override
        public boolean verify(String hostname, SSLSession session) {
             return HttpsURLConnection.getDefaultHostnameVerifier().verify("m.taobao.com", session);
        }
    }); 
    connection.connect();
     } catch (Exception e) {
           e.printStackTrace();
     } finally { 
   }
Important

When you implement this method and initiate a network request, an SSL verification error may be reported on Android, such as System.err: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found. In this case, you can check whether the SNI is specified.

  • HTTPS requests with the SNI

  1. For the complete code for calling HttpsURLConnection when the SNI is specified in HTTPS requests, see the source code for a demo project.

  2. You need to use SSLSocketFactory to perform custom DNS resolution and certificate verification. When you call the createSocket method, replace the domain name with the IP address that is resolved from the domain name, and specify the SNI and HostNameVerify configurations.

class TlsSniSocketFactory extends SSLSocketFactory {
    private final String TAG = TlsSniSocketFactory.class.getSimpleName();
    HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
    private HttpsURLConnection conn;

    public TlsSniSocketFactory(HttpsURLConnection conn) {
        this.conn = conn;
    }

    @Override
    public Socket createSocket() throws IOException {
        return null;
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        return null;
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
        return null;
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return null;
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return null;
    }

    // TLS layer
    @Override
    public String[] getDefaultCipherSuites() {
        return new String[0];
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return new String[0];
    }

    @Override
    public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException {
        String peerHost = this.conn.getRequestProperty("Host");
        if (peerHost == null{           
            peerHost = host;
        }

        Log.i(TAG, "customized createSocket. host: " + peerHost);
        InetAddress address = plainSocket.getInetAddress();

        if (autoClose) {
            // we don't need the plainSocket
            plainSocket.close();
        }
        // create and connect SSL socket, but don't do hostname/certificate verification yet
        SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);
        SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(address, port);

        // enable TLSv1.1/1.2 if available
        ssl.setEnabledProtocols(ssl.getSupportedProtocols());

       // set up SNI before the handshake
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            Log.i(TAG, "Setting SNI hostname");
            sslSocketFactory.setHostname(ssl, peerHost);
       } else {
            Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection");
            try {
                 java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
                 setHostnameMethod.invoke(ssl, peerHost);
            } catch (Exception e) {
                 Log.w(TAG, "SNI not useable", e);
            }
        }

        // verify hostname and certificate
        SSLSession session = ssl.getSession();

        if (!hostnameVerifier.verify(peerHost, session)){}
            throw new SSLPeerUnverifiedException("Cannot verify hostname: " + peerHost);
        }

       Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
                " using " + session.getCipherSuite());

        return ssl;
    }
}

The following code provides an example on how to specify the SNI to redirect requests.

public void recursiveRequest(String path, String reffer) {
    URL url = null;
    try {
          url = new URL(path);
          conn = (HttpsURLConnection) url.openConnection();
          String ip = dnsResolver.getIPV4ByHost(url.getHost();
          if (ip != null) {
               // Use Alibaba Cloud Public DNS SDK for Android to perform the DNS resolution and obtain the IP address. Replace the original domain name in the URL with the IP address, and configure the Host header.
               Log.d(TAG, "get IP: " + ip + " for host: " + url.getHost() + "from pdns resolver success!");
               String newUrl = path.replaceFirst(url.getHost(), ip);
               conn = (HttpsURLConnection) new URL(newUrl).openConnection();
               // Set the Host header of the HTTP request.
               conn.setRequestProperty("Host", url.getHost());
            }
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(30000);
            conn.setInstanceFollowRedirects(false);
            TlsSniSocketFactory sslSocketFactory = new TlsSniSocketFactory(conn);
            conn.setSSLSocketFactory(sslSocketFactory);
            conn.setHostnameVerifier(new HostnameVerifier() {
            /*
             * Descriptions from the Android official documentation:
             * This is an extended verification option that implementers can provide.
             * It is to be used during a handshake if the URL's hostname does not match the
             * peer's identification hostname.
             *
             * After you use Alibaba Cloud Public DNS SDK to perform the DNS resolution, the IP address in the new URL is not the hostname of a remote host that has passed the certificate verification. In the following example, the hostname of a certified remote host is used, which is m.taobao.com. 
             * HttpsURLConnection provides a callback that you can use to modify the hostname in the URL. 
             * You need to check whether the IP address returned by using Alibaba Cloud Public DNS SDK is the same as the IP address carried by the session. If they are the same, you can use this callback to replace the IP address with the original domain name before you verify the certificate. 
             *
             */
            @Override
            public boolean verify(String hostname, SSLSession session) {
                String host = conn.getRequestProperty("Host");
                if (null == host) {
                    host = conn.getURL().getHost();
                }
                return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
             }
         });
         int code = conn.getResponseCode();// Network block
         if (needRedirect(code)) {
             // Note that the Location key is case-sensitive. Different capitalization styles indicate different redirection modes. For example, the key may indicate that all subsequent requests are redirected to the specified IP address, or that only the current request is redirected to the specified IP address. The capitalization styles and their corresponding redirection modes are determined by the server.
             String location = conn.getHeaderField("Location");
             if (location == null) {
                 location = conn.getHeaderField("location");
             }
             if (!(location.startsWith("http://") || location.startsWith("https://"))) {
                 // In some cases, the server returns only the path of the new URL, and you need to complete the URL by replacing the original domain name with the IP address.
                 URL originalUrl = new URL(path);
                 location = originalUrl.getProtocol() + "://"+originalUrl.getHost() + location;
             }
             recursiveRequest(location, path);
         } else {
            // redirect finish.
            DataInputStream dis = new DataInputStream(conn.getInputStream());
            int len;
            byte[] buff = new byte[4096];
            StringBuilder response = new StringBuilder();
            while ((len = dis.read(buff)) != -1) {
                response.append(new String(buff, 0, len));
            }
            Log.d(TAG, "Response: " + response.toString());
         }
    } catch (MalformedURLException e) {
        Log.w(TAG, "recursiveRequest MalformedURLException");
    } catch (IOException e) {
        Log.w(TAG, "recursiveRequest IOException");
    } catch (Exception e) {
        Log.w(TAG, "unknow exception");
    } finally {
        if (conn != null) {
            conn.disconnect();
        }
    }
}
Important

  1. This topic provides references only for connecting an Android app to an IP address over HTTPS.

  2. For more information about how to use Alibaba Cloud Public DNS SDK for Android and the DNS resolution service, see SDK for Android developer guide.

  3. For the complete code for integrating Alibaba Cloud Public DNS SDK on Android when the SNI is specified in HTTPS requests, see the source code for a demo project.