{"id":918,"date":"2014-07-21T23:17:29","date_gmt":"2014-07-22T04:17:29","guid":{"rendered":"http:\/\/littlesvr.ca\/grumble\/?p=918"},"modified":"2019-09-20T13:36:16","modified_gmt":"2019-09-20T18:36:16","slug":"android-programming-connect-to-an-https-server-with-self-signed-certificate","status":"publish","type":"post","link":"http:\/\/littlesvr.ca\/grumble\/2014\/07\/21\/android-programming-connect-to-an-https-server-with-self-signed-certificate\/","title":{"rendered":"Android programming: connect to an HTTPS server with self-signed certificate"},"content":{"rendered":"<p>In a <a href=\"http:\/\/littlesvr.ca\/grumble\/2014\/07\/19\/why-hate-self-signed-public-key-certificates\/\">previous post<\/a> I described my frustration with the fact that it&#8217;s so difficult to find documentation about how to connect to a server using HTTPS if the certificate for that server is self-signed (not from a paid-for certificate authority).<\/p>\n<p>After a while I found that someone at Google noticed that because of their lack of documentation the common solution is to disable certificate checking altogether, which of course nullifies any possible advantage of using HTTPS. So they posted some documentation, and after struggling with it for a while I finally got it to work.<\/p>\n<p>You don&#8217;t need to use <span class=\"hiddenSpellError\">BouncyCastle<\/span>, just the stock Android APIs, you should be able to easily customise this code for your own uses:<\/p>\n<pre>    \/**\n     * Set up a connection to littlesvr.ca using HTTPS. An entire function\n     * is needed to do this because littlesvr.ca has a self-signed certificate.\n     * \n     * The caller of the function would do something like:\n     * <span class=\"hiddenSpellError\">HttpsURLConnection<\/span> <span class=\"hiddenSpellError\">urlConnection<\/span> = <span class=\"hiddenSpellError\">setUpHttpsConnection<\/span>(\"https:\/\/littlesvr.ca\");\n     * <span class=\"hiddenSpellError\">InputStream<\/span> in = <span class=\"hiddenSpellError\">urlConnection<\/span>.getInputStream();\n     * And read from that \"in\" as usual in Java\n     * \n     * Based on code from:\n     * https:\/\/developer.android.com\/training\/articles\/security-ssl.html#<span class=\"hiddenSpellError\">SelfSigned<\/span>\n     *\/\n    @<span class=\"hiddenSpellError\">SuppressLint<\/span>(\"<span class=\"hiddenSpellError\">SdCardPath<\/span>\")\n    public static <span class=\"hiddenSpellError\">HttpsURLConnection<\/span> <span class=\"hiddenSpellError\">setUpHttpsConnection<\/span>(String <span class=\"hiddenSpellError\">urlString<\/span>)\n    {\n        try\n        {\n            \/\/ Load <span class=\"hiddenSpellError\">CAs<\/span> from an <span class=\"hiddenSpellError\">InputStream<\/span>\n            \/\/ (could be from a resource <span class=\"hiddenSpellError\">or<\/span> <span class=\"hiddenSpellError\">ByteArrayInputStream<\/span> or ...)\n            <span class=\"hiddenSpellError\">CertificateFactory<\/span> cf = CertificateFactory.getInstance(\"X.509\");\n            \n            \/\/ My CRT file that I put in the assets folder\n            \/\/ I got this file by following these steps:\n            \/\/ * Go to https:\/\/littlesvr.ca using Firefox\n            \/\/ * Click the padlock\/More\/Security\/View Certificate\/Details\/Export\n            \/\/ * Saved the file as littlesvr.crt (type X.509 Certificate (PEM))\n            \/\/ The MainActivity.context is declared as:\n            \/\/ public static Context context;\n            \/\/ And initialized in MainActivity.onCreate() as:\n            \/\/ MainActivity.context = getApplicationContext();\n            <span class=\"hiddenSpellError\">InputStream<\/span> <span class=\"hiddenSpellError\">caInput<\/span> = new <span class=\"hiddenSpellError\">BufferedInputStream<\/span>(MainActivity.context.getAssets().open(\"littlesvr.crt\"));\n            Certificate ca = cf.generateCertificate(<span class=\"hiddenSpellError\">caInput<\/span>);\n            System.out.println(\"ca=\" + ((<span class=\"hiddenSpellError\">X509Certificate<\/span>) ca).<span class=\"hiddenSpellError\">getSubjectDN<\/span>());\n            \n            \/\/ Create a <span class=\"hiddenSpellError\">KeyStore<\/span> containing our trusted <span class=\"hiddenSpellError\">CAs<\/span>\n            String <span class=\"hiddenSpellError\">keyStoreType<\/span> = <span class=\"hiddenSpellError\">KeyStore<\/span>.getDefaultType();\n            <span class=\"hiddenSpellError\">KeyStore<\/span> <span class=\"hiddenSpellError\">keyStore<\/span> = KeyStore.getInstance(<span class=\"hiddenSpellError\">keyStoreType<\/span>);\n            <span class=\"hiddenSpellError\">keyStore<\/span>.load(null, null);\n            keyStore.setCertificateEntry(\"ca\", ca);\n            \n            \/\/ Create a <span class=\"hiddenSpellError\">TrustManager<\/span> that trusts the <span class=\"hiddenSpellError\">CAs<\/span> in our KeyStore\n            String <span class=\"hiddenSpellError\">tmfAlgorithm<\/span> = <span class=\"hiddenSpellError\">TrustManagerFactory<\/span>.getDefaultAlgorithm();\n            TrustManagerFactory <span class=\"hiddenSpellError\">tmf<\/span> = TrustManagerFactory.getInstance(<span class=\"hiddenSpellError\">tmfAlgorithm<\/span>);\n            tmf.init(keyStore);\n            \n            \/\/ Create an <span class=\"hiddenSpellError\">SSLContext<\/span> that uses our <span class=\"hiddenSpellError\">TrustManager<\/span>\n            <span class=\"hiddenSpellError\">SSLContext<\/span> context = <span class=\"hiddenSpellError\">SSLContext<\/span>.getInstance(\"TLS\");\n            context.init(null, tmf.getTrustManagers(), null);\n            \n            \/\/ Tell the <span class=\"hiddenSpellError\">URLConnection<\/span> to use a <span class=\"hiddenSpellError\">SocketFactory<\/span> from our SSLContext\n            URL <span class=\"hiddenSpellError\">url<\/span> = new URL(<span class=\"hiddenSpellError\">urlString<\/span>);\n            <span class=\"hiddenSpellError\">HttpsURLConnection<\/span> <span class=\"hiddenSpellError\">urlConnection<\/span> = (<span class=\"hiddenSpellError\">HttpsURLConnection<\/span>)url.openConnection();\n            urlConnection.setSSLSocketFactory(context.getSocketFactory());\n            \n            return urlConnection;\n        }\n        catch (Exception ex)\n        {\n            Log.e(TAG, \"Failed to establish SSL connection to server: \" + ex.toString());\n            return null;\n        }\n    }\n<\/pre>\n<p>Good luck! And you&#8217;re welcome.<\/p>\n<p>P.S. 20 september 2019: For some reason the solution above is insufficient on Android 28 and 29 (Pie and Q). On the emulators I tried it required an extra bit of code just before the <code>return urlConnection<\/code>:<\/p>\n<pre>            \/\/ 20 september 2019:\n            \/\/ For some reason this call to setHostnameVerifier() has become necessary in\n            \/\/ Android 28 (also doesn't work in 29).\n            \/\/ In older Androids everything works fine without this.\n            urlConnection.setHostnameVerifier(new HostnameVerifier() {\n                @Override\n                public boolean verify(String hostname, SSLSession session) {\n                    if (hostname.equalsIgnoreCase(\"littlesvr.ca\")) {\n                        return true;\n                    } else {\n                        return false;\n                    }\n                }\n            });<\/pre>\n<p>I don&#8217;t know why that extra requirement. My certificate is for only one host, and it&#8217;s the host I&#8217;m connecting to. It even has reverse DNS set up.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In a previous post I described my frustration with the fact that it&#8217;s so difficult to find documentation about how to connect to a server using HTTPS if the certificate for that server is self-signed (not from a paid-for certificate authority). After a while I found that someone at Google noticed that because of their &hellip; <\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,4],"tags":[],"class_list":{"0":"entry","1":"post","2":"publish","3":"author-andrew","4":"post-918","6":"format-standard","7":"category-opensource","8":"category-safeforseneca"},"_links":{"self":[{"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/posts\/918","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/comments?post=918"}],"version-history":[{"count":5,"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/posts\/918\/revisions"}],"predecessor-version":[{"id":1300,"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/posts\/918\/revisions\/1300"}],"wp:attachment":[{"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/media?parent=918"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/categories?post=918"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/littlesvr.ca\/grumble\/wp-json\/wp\/v2\/tags?post=918"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}