Keamanan dengan HTTPS dan SSL

Secure Socket Layer (SSL)—sekarang secara teknis dikenal sebagai Transport Layer Security (TLS)—adalah blok pembangun umum untuk komunikasi terenkripsi antara klien dan server. Ada kemungkinan bahwa aplikasi menggunakan SSL dengan tidak benar sehingga benda berbahaya mungkin mencegat data aplikasi melalui jaringan. Untuk membantu Anda memastikan bahwa hal ini tidak terjadi untuk aplikasi Anda, artikel ini menyoroti perangkap umum ketika menggunakan protokol jaringan yang aman dan mengatasi beberapa kekhawatiran yang lebih besar tentang penggunaan Public-Key Infrastructure (PKI).

Anda juga harus membaca Ringkasan Keamanan Android serta Ringkasan Izin.

Konsep

Dalam skenario penggunaan SSL umum, server dikonfigurasi dengan sertifikat yang berisi kunci publik serta kunci pribadi yang cocok. Sebagai bagian dari handshake antara klien dan server SSL, server membuktikan kepemilikan kunci pribadi dengan menandatangani sertifikat dengan kriptografi kunci publik.

Namun, siapa pun dapat menghasilkan sertifikat dan kunci pribadi sendiri, sehingga handshake yang sederhana tidak membuktikan apa pun tentang server selain bahwa server mengetahui kunci pribadi yang cocok dengan kunci publik sertifikat. Salah satu cara untuk memecahkan masalah ini adalah meminta klien memiliki kumpulan satu atau beberapa sertifikat yang dipercaya. Jika sertifikat tidak ada dalam kumpulan tersebut, server tidak bisa dipercaya.

Ada beberapa kelemahan dari pendekatan sederhana ini. Server harus dapat mengupgrade ke kunci yang lebih kuat dari waktu ke waktu ("rotasi kunci"), yang menggantikan kunci publik dalam sertifikat dengan yang baru. Sayangnya, sekarang aplikasi klien harus diupdate karena perubahan konfigurasi server. Hal ini akan bermasalah jika server tidak di bawah kontrol developer aplikasi, misalnya jika server adalah layanan web pihak ketiga. Pendekatan ini juga memiliki masalah jika aplikasi harus berkomunikasi ke server bebas seperti browser web atau aplikasi email.

Untuk mengatasi kekurangan ini, server biasanya dikonfigurasi dengan sertifikat dari penerbit terkenal yang disebut Certificate Authorities (CA). Platform host biasanya berisi daftar CA terkenal yang dipercaya. Hingga Android 4.2 (Jelly Bean), Android berisi lebih dari 100 CA yang diupdate dalam setiap rilis. Seperti halnya pada server, CA memiliki sertifikat dan kunci pribadi. Saat menerbitkan sertifikat untuk server, CA menandatangani sertifikat server menggunakan kunci pribadinya. Klien kemudian dapat memverifikasi bahwa server memiliki sertifikat yang diterbitkan oleh CA yang dikenal oleh platform.

Namun, meskipun memecahkan beberapa masalah, penggunaan CA memunculkan masalah lain. Karena CA menerbitkan sertifikat untuk banyak server, Anda masih memerlukan beberapa cara untuk memastikan bahwa Anda berkomunikasi dengan server yang diinginkan. Untuk mengatasi hal ini, sertifikat yang dikeluarkan oleh CA mengidentifikasi server dengan nama spesifik seperti gmail.com atau kumpulan karakter pengganti dari host seperti *.google.com.

Contoh berikut akan membuat konsep ini sedikit lebih konkret. Dalam cuplikan di bawah ini dari baris perintah, perintah s_client fitur openssl melihat informasi sertifikat server Wikipedia. Ini menentukan port 443 karena merupakan default untuk HTTPS. Perintah mengirimkan output dari openssl s_client ke openssl x509, yang memformat informasi tentang sertifikat sesuai dengan standar X.509. Secara khusus, perintah meminta subjek, yang berisi informasi nama server, dan penerbit, yang mengidentifikasi CA.

    $ openssl s_client -connect wikipedia.org:443 | openssl x509 -noout -subject -issuer
    subject= /serialNumber=sOrr2rKpMVP70Z6E9BT5reY008SJEdYv/C=US/O=*.wikipedia.org/OU=GT03314600/OU=See www.rapidssl.com/resources/cps (c)11/OU=Domain Control Validated - RapidSSL(R)/CN=*.wikipedia.org
    issuer= /C=US/O=GeoTrust, Inc./CN=RapidSSL CA
    

Anda dapat melihat bahwa sertifikat diterbitkan oleh RapidSSL CA untuk server yang cocok dengan *.wikipedia.org.

Contoh HTTPS

Dengan anggapan bahwa Anda memiliki sebuah server web dengan sertifikat yang diterbitkan oleh CA terkenal, Anda dapat membuat permintaan aman dengan kode semudah ini:

Kotlin

    val url = URL("https://wikipedia.org")
    val urlConnection: URLConnection = url.openConnection()
    val inputStream: InputStream = urlConnection.getInputStream()
    copyInputStreamToOutputStream(inputStream, System.out)
    

Java

    URL url = new URL("https://wikipedia.org");
    URLConnection urlConnection = url.openConnection();
    InputStream in = urlConnection.getInputStream();
    copyInputStreamToOutputStream(in, System.out);
    

Ya, benar-benar bisa semudah itu. Jika ingin menyesuaikan permintaan HTTP, Anda dapat melakukan transmisi ke HttpURLConnection. Dokumentasi Android untuk HttpURLConnection memiliki contoh lebih lanjut tentang bagaimana menangani header permintaan dan respons, memposting konten, mengelola cookie, menggunakan proxy, men-cache respons, dan sebagainya. Namun untuk cara mendetail dalam memverifikasi sertifikat dan hostname, framework Android menanganinya untuk Anda melalui API ini. Ini adalah tempat Anda ingin berada jika memungkinkan. Di bawah ini adalah beberapa pertimbangan lainnya.

Masalah umum dalam memverifikasi sertifikat server

Misalnya bukan menerima konten dari getInputStream(), tetapi melontarkan pengecualian:

    javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
            at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
            at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
            at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
            at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
            at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
            at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
            at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
            at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
    

Hal ini dapat terjadi karena beberapa alasan, seperti:

  1. CA yang mengeluarkan sertifikat server tidak dikenal
  2. Sertifikat server tidak ditandatangani oleh CA, namun ditandatangani sendiri
  3. CA perantara tidak ditemukan pada konfigurasi server

Bagian berikut membahas cara mengatasi masalah ini sambil menjaga koneksi Anda ke server tetap aman.

Otoritas sertifikat tidak dikenal

Dalam kasus ini, SSLHandshakeException terjadi karena Anda memiliki CA yang tidak dipercaya oleh sistem. Bisa jadi karena Anda memiliki sertifikat dari CA baru yang belum dipercaya oleh Android atau aplikasi Anda berjalan pada versi lama tanpa CA. Sering kali CA tidak dikenal karena bukan CA publik, tetapi CA privat yang dikeluarkan oleh organisasi seperti pemerintah, perusahaan, atau lembaga pendidikan untuk penggunaan mereka sendiri.

Untungnya, Anda dapat melatih HttpsURLConnection agar memercayai kumpulan CA tertentu. Prosedurnya bisa sedikit rumit. Di bawah ini adalah contoh yang mengambil CA tertentu dari InputStream, menggunakannya untuk membuat KeyStore, yang kemudian digunakan untuk membuat dan melakukan inisialiasi TrustManager. TrustManager adalah yang digunakan sistem untuk memvalidasi sertifikat dari server dan, dengan membuat satu dari KeyStore dengan satu atau beberapa CA, itu akan menjadi satu-satunya CA yang dipercaya oleh TrustManager.

Dengan pertimbangan TrustManager baru, contoh menginisialisasi SSLContext baru yang menyediakan SSLSocketFactory yang dapat Anda gunakan untuk mengganti SSLSocketFactory default dari HttpsURLConnection. Dengan cara ini koneksi akan menggunakan CA untuk validasi sertifikat.

Berikut adalah contoh penuh menggunakan CA organisasi dari University of Washington:

Kotlin

    // Load CAs from an InputStream
    // (could be from a resource or ByteArrayInputStream or ...)
    val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
    // From https://www.washington.edu/itconnect/security/ca/load-der.crt
    val caInput: InputStream = BufferedInputStream(FileInputStream("load-der.crt"))
    val ca: X509Certificate = caInput.use {
        cf.generateCertificate(it) as X509Certificate
    }
    System.out.println("ca=" + ca.subjectDN)

    // Create a KeyStore containing our trusted CAs
    val keyStoreType = KeyStore.getDefaultType()
    val keyStore = KeyStore.getInstance(keyStoreType).apply {
        load(null, null)
        setCertificateEntry("ca", ca)
    }

    // Create a TrustManager that trusts the CAs inputStream our KeyStore
    val tmfAlgorithm: String = TrustManagerFactory.getDefaultAlgorithm()
    val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
        init(keyStore)
    }

    // Create an SSLContext that uses our TrustManager
    val context: SSLContext = SSLContext.getInstance("TLS").apply {
        init(null, tmf.trustManagers, null)
    }

    // Tell the URLConnection to use a SocketFactory from our SSLContext
    val url = URL("https://certs.cac.washington.edu/CAtest/")
    val urlConnection = url.openConnection() as HttpsURLConnection
    urlConnection.sslSocketFactory = context.socketFactory
    val inputStream: InputStream = urlConnection.inputStream
    copyInputStreamToOutputStream(inputStream, System.out)
    

Java

    // Load CAs from an InputStream
    // (could be from a resource or ByteArrayInputStream or ...)
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    // From https://www.washington.edu/itconnect/security/ca/load-der.crt
    InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.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 context = SSLContext.getInstance("TLS");
    context.init(null, tmf.getTrustManagers(), null);

    // Tell the URLConnection to use a SocketFactory from our SSLContext
    URL url = new URL("https://certs.cac.washington.edu/CAtest/");
    HttpsURLConnection urlConnection =
        (HttpsURLConnection)url.openConnection();
    urlConnection.setSSLSocketFactory(context.getSocketFactory());
    InputStream in = urlConnection.getInputStream();
    copyInputStreamToOutputStream(in, System.out);
    

Dengan TrustManager kustom yang mengetahui CA Anda, sistem dapat memvalidasi bahwa sertifikat server Anda berasal dari penerbit tepercaya.

Perhatian: Banyak situs web menjelaskan tentang sebuah solusi alternatif buruk yang diinstal TrustManager tetapi tidak ada gunanya. Jika melakukannya, Anda mungkin tidak mengenkripsi komunikasi Anda, karena siapa pun dapat menyerang pengguna Anda di hotspot Wi-Fi publik menggunakan trik DNS untuk mengirimkan traffic ke pengguna melalui proxy mereka sendiri yang berpura-pura menjadi server Anda. Penyerang kemudian dapat merekam sandi dan data pribadi lainnya. Cara ini dapat dilakukan karena penyerang bisa membuat sertifikat dan, tanpa TrustManager yang benar-benar melakukan validasi bahwa sertifikat tersebut berasal dari sumber terpercaya, aplikasi Anda bisa saja berkomunikasi dengan siapa pun. Jadi jangan lakukan hal ini, bahkan untuk sementara sekalipun. Anda selalu dapat membuat aplikasi memercayai penerbit sertifikat server, jadi lakukan saja cara ini.

Sertifikat server ditandatangani-sendiri

Kasus kedua SSLHandshakeException adalah karena sertifikat yang ditandatangani sendiri, yang berarti server berperilaku sebagai CA-nya sendiri. Ini mirip dengan otoritas sertifikat yang tidak dikenal, sehingga Anda dapat menggunakan pendekatan yang sama dari bagian sebelumnya.

Anda dapat membuat TrustManager Anda sendiri, saat ini dengan memercayai sertifikat server secara langsung. Ini memiliki semua kekurangan yang sebelumnya telah dibahas karena mengikat aplikasi secara langsung ke sertifikat, tetapi dapat dilakukan dengan aman. Namun, Anda harus berhati-hati untuk memastikan sertifikat yang ditandatangani-sendiri memiliki kunci yang cukup kuat. Sampai 2012, tanda tangan RSA 2048-bit dengan eksponen 65537 yang berakhir tahunan dapat diterima. Ketika merotasi kunci, Anda sebaiknya memeriksa rekomendasi dari otoritas (seperti NIST) tentang apa yang dapat diterima.

Kehilangan otorisasi intermediate certificate

Kasus ketiga SSLHandshakeException terjadi karena CA perantara hilang. Sebagian besar CA publik tidak menandatangani sertifikat server secara langsung. Sebagai gantinya, mereka menggunakan Sertifikat CA utama, disebut sebagai CA root, untuk menandatangani CA perantara. Mereka melakukan ini sehingga CA root dapat disimpan secara offline untuk mengurangi risiko disusupi. Namun, sistem operasi seperti Android biasanya hanya memercayai CA root secara langsung, yang menyisakan sedikit celah kepercayaan antara sertifikat server,yang ditandatangani CA perantara, dan pemverifikasi sertifikat, yang mengetahui CA root. Untuk mengatasi ini, server tidak hanya mengirimkan sertifikatnya kepada klien selama handshake SSL, tetapi rantai sertifikat dari CA server melalui perantara diperlukan untuk mencapai CA root terpercaya.

Untuk melihat bagaimana praktiknya, berikut rantai sertifikat mail.google.com seperti yang dilihat oleh perintah openssl s_client:

    $ openssl s_client -connect mail.google.com:443
    ---
    Certificate chain
     0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=mail.google.com
       i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
     1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
       i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
    ---
    

Ini memperlihatkan bahwa server akan mengirimkan sertifikat untuk mail.google.com yang diterbitkan oleh Thawte SGC CA, yang merupakan CA perantara dan sertifikat kedua untuk Thawte SGC CA yang diterbitkan oleh Verisign CA, yang merupakan CA utama yang dipercaya oleh Android.

Namun, tidak jarang dilakukan konfigurasi server dengan tidak menyertakan CA perantara yang diperlukan. Misalnya, berikut adalah server yang dapat menyebabkan error dalam browser Android dan pengecualian dalam aplikasi Android:

    $ openssl s_client -connect egov.uscis.gov:443
    ---
    Certificate chain
     0 s:/C=US/ST=District Of Columbia/L=Washington/O=U.S. Department of Homeland Security/OU=United States Citizenship and Immigration Services/OU=Terms of use at www.verisign.com/rpa (c)05/CN=egov.uscis.gov
       i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 International Server CA - G3
    ---
    

Yang menarik untuk dicatat di sini adalah bahwa mengunjungi server ini di sebagian besar browser desktop tidak menyebabkan error seperti yang ditimbulkan oleh CA yang tidak dikenal sama sekali atau sertifikat server yang ditandatangani sendiri. Ini dikarenakan sebagian besar cache browser desktop memercayai CA perantara dari waktu ke waktu. Setelah mengunjungi dan mempelajari CA perantara dari satu situs, browser tidak perlu lagi memasukkan CA perantara dalam rantai sertifikat di waktu mendatang.

Beberapa situs melakukan ini dengan sengaja untuk server web sekunder yang digunakan untuk menyediakan resource. Misalnya, mungkin halaman HTML utama mereka disalurkan oleh server dengan rantai sertifikat penuh, tetapi menggunakan server untuk resource seperti gambar, CSS, atau JavaScript tanpa CA, yang tujuannya mungkin untuk menghemat bandwidth. Sayangnya, terkadang server ini memberikan layanan web yang Anda coba panggil dari aplikasi Android, yang berdampak negatif.

Ada dua pendekatan untuk memecahkan masalah ini:

  • Mengonfigurasi server untuk menyertakan CA perantara dalam rantai server. Sebagian besar CA memberikan dokumentasi tentang cara melakukan hal ini untuk semua server web umum. Ini adalah satu-satunya pendekatan jika Anda menginginkan situs bekerja dengan browser Android default setidaknya melalui Android 4.2.
  • Atau, memperlakukan CA perantara seperti CA tidak dikenal lainnya, dan membuat TrustManager untuk memercayainya secara langsung, seperti yang dilakukan di dua bagian sebelumnya.

Masalah umum dengan verifikasi hostname

Seperti yang disebutkan di awal artikel ini, ada dua bagian kunci untuk memverifikasi koneksi SSL. Yang pertama adalah memverifikasi sertifikat tersebut dari sumber tepercaya, yang merupakan fokus dari bagian sebelumnya. Yang menjadi fokus bagian ini adalah bagian kedua: memastikan server yang Anda hubungi menyajikan sertifikat yang benar. Jika tidak benar, Anda biasanya akan melihat error seperti ini:

    java.io.IOException: Hostname 'example.com' was not verified
            at libcore.net.http.HttpConnection.verifySecureSocketHostname(HttpConnection.java:223)
            at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:446)
            at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
            at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
            at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
            at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
            at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
    

Salah satu alasan mengapa ini bisa terjadi adalah karena error konfigurasi server. Server dikonfigurasi dengan sertifikat yang tidak memiliki bidang subjek atau bidang nama alternatif subjek yang cocok dengan server yang sedang Anda coba hubungi. Dimungkinkan untuk memiliki satu sertifikat yang digunakan dengan banyak server yang berbeda. Misalnya, dengan melihat sertifikat google.com dengan openssl s_client -connect google.com:443 | openssl x509 -text, Anda dapat melihat subjek yang mendukung *.google.com beserta nama alternatif subjek untuk *.youtube.com, *.android.com, dan lainnya. Error ini terjadi hanya jika nama server yang ingin Anda hubungi tidak terdaftar oleh sertifikat sebagai dapat diterima.

Sayangnya, ini juga bisa terjadi karena alasan lain: hosting virtual. Ketika berbagi server dengan lebih dari satu hostname dengan HTTP, server web dapat membedakannya dari permintaan HTTP/1.1 yang menargetkan hostname yang dicari klien. Sayangnya ini menjadi rumit dengan HTTPS, karena server harus mengetahui sertifikat mana yang dikembalikan sebelum melihat permintaan HTTP. Untuk mengatasinya, versi SSL yang lebih baru, khususnya TLSv.1.0 dan setelahnya, mendukung Server Name Indication (SNI), yang memungkinkan klien SSL menentukan hostname yang dimaksudkan ke server sehingga sertifikat yang tepat dapat dikembalikan.

Untungnya, HttpsURLConnection mendukung SNI sejak Android 2.3. Salah satu solusi jika Anda perlu mendukung Android 2.2 (dan yang lebih lama) adalah dengan menyiapkan sebuah host virtual alternatif pada port unik sehingga jelas sertifikat server mana yang dikembalikan.

Alternatif yang lebih jelas lagi adalah mengganti HostnameVerifier dengan yang tidak menggunakan hostname dari host virtual Anda, tetapi yang dikembalikan oleh server secara default.

Perhatian: Mengganti HostnameVerifier dapat sangat berbahaya jika host virtual lain tidak berada di bawah kendali Anda, karena serangan man-in-the-middle dapat mengarahkan traffic langsung ke server lain tanpa sepengetahuan Anda.

Jika Anda masih yakin ingin mengganti verifikasi hostname, berikut adalah contoh yang menggantikan pemverifikasi untuk URLConnection tunggal dengan yang masih memverifikasi bahwa hostname setidaknya sesuai yang diharapkan oleh aplikasi:

Kotlin

    // Create an HostnameVerifier that hardwires the expected hostname.
    // Note that is different than the URL's hostname:
    // example.com versus example.org
    val hostnameVerifier = HostnameVerifier { _, session ->
        HttpsURLConnection.getDefaultHostnameVerifier().run {
            verify("example.com", session)
        }
    }

    // Tell the URLConnection to use our HostnameVerifier
    val url = URL("https://example.org/")
    val urlConnection = url.openConnection() as HttpsURLConnection
    urlConnection.hostnameVerifier = hostnameVerifier
    val inputStream: InputStream = urlConnection.inputStream
    copyInputStreamToOutputStream(inputStream, System.out)
    

Java

    // Create an HostnameVerifier that hardwires the expected hostname.
    // Note that is different than the URL's hostname:
    // example.com versus example.org
    HostnameVerifier hostnameVerifier = new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            HostnameVerifier hv =
                HttpsURLConnection.getDefaultHostnameVerifier();
            return hv.verify("example.com", session);
        }
    };

    // Tell the URLConnection to use our HostnameVerifier
    URL url = new URL("https://example.org/");
    HttpsURLConnection urlConnection =
        (HttpsURLConnection)url.openConnection();
    urlConnection.setHostnameVerifier(hostnameVerifier);
    InputStream in = urlConnection.getInputStream();
    copyInputStreamToOutputStream(in, System.out);
    

Namun ingat, jika Anda memutuskan untuk mengganti verifikasi hostname, terutama karena hosting virtual, hal itu tetap sangat berbahaya jika host virtual lain tidak di bawah kendali Anda dan Anda sebaiknya menemukan pengaturan hosting alternatif sehingga dapat terhindar dari masalah ini.

Peringatan tentang penggunaan SSLSocket secara langsung

Sejauh ini, contoh berfokus pada HTTPS menggunakan HttpsURLConnection. Terkadang aplikasi perlu menggunakan SSL yang terpisah dari HTTP. Misalnya, aplikasi email mungkin menggunakan varian SSL SMTP, POP3, atau IMAP. Dalam kasus tersebut, aplikasi ingin menggunakan SSLSocket secara langsung, seperti yang dilakukan HttpsURLConnection secara internal.

Teknik yang dijelaskan sejauh ini untuk menangani masalah verifikasi sertifikat juga berlaku untuk SSLSocket. Bahkan, ketika menggunakan TrustManager kustom, apa yang akan diteruskan ke HttpsURLConnection adalah SSLSocketFactory. Jadi, jika Anda perlu menggunakan TrustManager kustom dengan SSLSocket, ikuti langkah-langkah yang sama dan gunakan SSLSocketFactory untuk membuat SSLSocket Anda.

Perhatian: SSLSocket tidak melakukan verifikasi hostname. Cara melakukan verifikasi hostname sendiri tergantung aplikasi, lebih baik dengan memanggil getDefaultHostnameVerifier() dengan hostname yang diharapkan. Selanjutnya, berhati-hatilah bahwa HostnameVerifier.verify() tidak melontarkan pengecualian pada error melainkan mengembalikan hasil boolean yang harus diperiksa secara eksplisit.

Berikut adalah contoh yang menunjukkan bagaimana cara melakukannya. Contoh ini menunjukkan bahwa saat menghubungkan ke gmail.com port 443 tanpa dukungan SNI, Anda akan menerima sertifikat untuk mail.google.com. Hal ini memang sudah diperkirakan, jadi periksa untuk memastikan bahwa sertifikat tersebut memang untuk mail.google.com:

Kotlin

    // Open SSLSocket directly to gmail.com
    val socket: SSLSocket = SSLSocketFactory.getDefault().run {
        createSocket("gmail.com", 443) as SSLSocket
    }
    val session = socket.session

    // Verify that the certicate hostname is for mail.google.com
    // This is due to lack of SNI support in the current SSLSocket.
    HttpsURLConnection.getDefaultHostnameVerifier().run {
        if (!verify("mail.google.com", session)) {
            throw SSLHandshakeException("Expected mail.google.com, found ${session.peerPrincipal} ")
        }
    }

    // At this point SSLSocket performed certificate verification and
    // we have performed hostname verification, so it is safe to proceed.

    // ... use socket ...
    socket.close()
    

Java

    // Open SSLSocket directly to gmail.com
    SocketFactory sf = SSLSocketFactory.getDefault();
    SSLSocket socket = (SSLSocket) sf.createSocket("gmail.com", 443);
    HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
    SSLSession s = socket.getSession();

    // Verify that the certicate hostname is for mail.google.com
    // This is due to lack of SNI support in the current SSLSocket.
    if (!hv.verify("mail.google.com", s)) {
        throw new SSLHandshakeException("Expected mail.google.com, "
                                        "found " + s.getPeerPrincipal());
    }

    // At this point SSLSocket performed certificate verification and
    // we have performed hostname verification, so it is safe to proceed.

    // ... use socket ...
    socket.close();
    

Daftar yang tidak diizinkan

SSL sangat bergantung pada CA untuk menerbitkan sertifikat hanya bagi pemilik server dan domain yang sudah diverifikasi dengan benar. Dalam kasus yang jarang terjadi, CA dicurangi atau, dalam kasus Comodo atau DigiNotar, dilanggar, sehingga sertifikat untuk hostname diterbitkan untuk orang lain selain pemilik server atau domain.

Untuk mengurangi risiko ini, Android memiliki kemampuan untuk membuat daftar pemblokiran sertifikat tertentu atau bahkan seluruh CA. Meskipun daftar ini secara historis dibuat ke dalam sistem operasi, mulai Android 4.2, daftar ini dapat diperbarui dari jarak jauh untuk menangani penyusupan di masa mendatang.

Memasang pin

Aplikasi dapat lebih melindungi dirinya dari sertifikat palsu yang dikeluarkan dari teknik yang dikenal sebagai pemasangan pin. Ini pada dasarnya menggunakan contoh yang diberikan dalam kasus CA tidak dikenal di atas untuk membatasi CA yang dipercaya aplikasi dalam kelompok kecil yang dikenal untuk digunakan oleh server aplikasi. Hal ini mencegah penyusupan satu CA dari lebih dari 100 CA lainnya dalam sistem yang dapat mengakibatkan penerobosan saluran aman aplikasi.

Sertifikat klien

Artikel ini berfokus pada pengguna SSL untuk mengamankan komunikasi dengan server. SSL juga mendukung konsep sertifikat klien yang memungkinkan server memvalidasi identitas klien. Meskipun di luar cakupan artikel ini, teknik yang terlibat mirip dengan penetapan TrustManager kustom. Lihat diskusi tentang membuat KeyManager kustom dalam dokumentasi untuk HttpsURLConnection.

Nogotofail: Fitur pengujian keamanan traffic jaringan

Nogotofail adalah fitur yang memberi Anda kemudahan untuk mengonfirmasi bahwa aplikasi Anda aman terhadap kerentanan dan kesalahan konfigurasi TLS/SSL yang dikenal. Inilah fitur yang otomatis, kuat, dan skalabel untuk pengujian masalah keamanan jaringan pada perangkat apa pun yang dapat dilalui traffic jaringan.

Nogotofail bermanfaat untuk tiga kasus penggunaan utama:

  • Menemukan bug dan kerentanan.
  • Memverifikasi perbaikan dan memperhatikan regresi.
  • Memahami traffic apa yang akan dihasilkan aplikasi dan perangkat.

Nogotofail dapat digunakan untuk Android, iOS, Linux, Windows, Chrome OS, OSX, bahkan semua perangkat yang Anda gunakan untuk terhubung ke Internet. Terdapat klien yang mudah digunakan untuk mengonfigurasi setelan dan mendapatkan notifikasi di Android dan Linux, serta mesin serangan yang dapat diterapkan sebagai router, server VPN, atau proxy.

Anda dapat mengakses fitur ini di project open source Nogotofail.