{"id":443,"date":"2018-06-11T18:28:26","date_gmt":"2018-06-11T07:28:26","guid":{"rendered":"http:\/\/scipilot.org\/blog\/?p=443"},"modified":"2018-06-28T19:32:25","modified_gmt":"2018-06-28T08:32:25","slug":"local-development-certificates-done-properly","status":"publish","type":"post","link":"https:\/\/scipilot.org\/blog\/2018\/06\/11\/local-development-certificates-done-properly\/","title":{"rendered":"Local development certificates done properly"},"content":{"rendered":"<p>Yesterday I finally lost my patience with the developer&#8217;s eternal problem of having to skip the untrusted self-signed certificate interstitial warning screens, and so I decided to solve it <em>properly<\/em>. The problem has got worse recently due to a combination of changes in the world and my projects:<\/p>\n<ol>\n<li>Containerisation &#8211; in Docker containers &#8220;localhost&#8221; is no longer useful as a service hostname as every service has their own localhost! So I have migrated towards using local host names, .local or .dev patterns etc.<\/li>\n<li>Chrome has long made special exceptions to &#8220;localhost&#8221; to allows invalid certificates which ease development pains. This is now lost due to 1).<\/li>\n<li>Chrome doesn&#8217;t resolve .localhost domain extensions which are spoofed in a container&#8217;s \/etc\/host file. I recently hit this problem at work, while migrating Selenium tests to Docker. So again we had to migrate our test domains to something.local instead of .localhost.<\/li>\n<li>Chrome since about v60-ish now requires a subject alternative name (SAN) in a certificate and won&#8217;t respect a fallback to a local canonical name.<\/li>\n<li>Whenever my local Node.js application crashes with an exception, Chrome forgets the fact I&#8217;ve whitelisted the certificate. It took me a while to realise this was happening, but I now see it&#8217;s 100% correlated. This has led to a gradual fear of crashes (probably a good thing).<\/li>\n<\/ol>\n<p>Being RSI conscious and generally lazy, having to click three more things each dev-cycle is a major pain. Also the fact that Browsersync stops working every time too makes it even more work to resume &#8211; involving double-clicking the failed GET in the network tab, and accepting the same warning message in a temporary browser window.<\/p>\n<p>After reading <a href=\"https:\/\/www.chromium.org\/Home\/chromium-security\/deprecating-powerful-features-on-insecure-origins#TOC-Testing-Powerful-Features\">the Chrome team&#8217;s official vision<\/a>, and with my recent experience in setting up a personal CA for a business to allow them to use client-certificates for authenticating remote staff, I decided the best, most permanent solution is to use a personal CA added to the trust root of the local host, which signs the local cert &#8211; instead of self-signing.<\/p>\n<p>While the process is actually easy to perform, I found as usual the historical complexities of the OpenSSL command line options and configuration files, plus the lack of a single article on this specific approach, meant I had to fiddle around for a while to get it right.<\/p>\n<p>Things that made it tricky:<\/p>\n<ul>\n<li>You cannot add a SAN on the openssl command line, so you must use a config file.<\/li>\n<li>The config files overlap between &#8220;req&#8221; and &#8220;x509&#8221; are not immediately obvious.<\/li>\n<li>People helpfully offer solutions using &#8220;openssl req -x509&#8221; which can do everything in one pass but it can\u00a0<span style=\"text-decoration: underline;\">only<\/span> self-sign certificates.<\/li>\n<li>The config file section names are themselves configurable, so there&#8217;s differences across the examples and tutorials<\/li>\n<\/ul>\n<p>I started from <a href=\"https:\/\/gist.github.com\/fntlnz\/cf14feb5a46b2eda428e000157447309\">this simplified gist<\/a>\u00a0and my knowledge of the fabulously professional and detailed OpenSSL DIY-CA guide by <a href=\"https:\/\/jamielinux.com\/docs\/openssl-certificate-authority\/index.html\">Jamie Nguyen<\/a>\u00a0(aimed at production quality so the setup is a bit over-complex for local dev).<\/p>\n<p>The configuration file\u00a0<span style=\"font-weight: 400;\">mysite.local.conf<\/span>, which contains sections for both the &#8220;req -config&#8221; option and the &#8220;x509 -extfile&#8221; option is as follows.<\/p>\n<p>The important part for generating a CSR with SANs with &#8220;req&#8221; command and then making the certificate signed with the CA with &#8220;x509&#8221; command,\u00a0 is for the &#8220;req&#8221; command the\u00a0config line &#8220;req_extensions = v3_ca&#8221; tells it to find the extensions section with the SAN, but for &#8220;x509&#8221; it&#8217;s the &#8220;<span style=\"font-weight: 400;\">-extensions v3_ca<\/span>&#8221; option which points to the same section in the &#8220;<span style=\"font-weight: 400;\">-extfile<\/span>&#8221; file. This took me a while to get right &#8211; although it seems obvious now!<\/p>\n<pre>[req]\r\ndefault_bits = 2048\r\nprompt = no\r\ndefault_md = sha256\r\ndistinguished_name = req_distinguished_name\r\nreq_extensions = v3_ca\r\n\r\n[req_distinguished_name]\r\nC = AU\r\nST = SYDNEY\r\nL = NSW\r\nO = MyOrganisation\r\nOU = MyOrganisation Unit\r\nCN = *.local\r\n\r\n[v3_ca]\r\nbasicConstraints = CA:FALSE\r\nkeyUsage = digitalSignature, keyEncipherment\r\nsubjectAltName = @alternate_names\r\n\r\n# I added localhost wildcards for general compatibility with older projects.\r\n[alternate_names]\r\nDNS.1 = localhost\r\nDNS.2 = *.localhost\r\nDNS.2 = *.local\r\nDNS.3 = mysite.local<\/pre>\n<p>The process &#8211; to create a CA and create a certificate for the local site with the above SANs:<\/p>\n<p><span style=\"font-weight: 400;\">1. Make root CA Key<\/span><\/p>\n<pre style=\"padding-left: 30px;\"><span style=\"font-weight: 400;\">openssl genrsa -des3 -out rootCA.key 4096\r\n&gt; secret\r\n<\/span><\/pre>\n<p><span style=\"font-weight: 400;\">2. Create and self-sign the CA Root Certificate<\/span><\/p>\n<pre><span style=\"font-weight: 400;\">openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt -config mysite.local.conf<\/span><\/pre>\n<p>Once that&#8217;s done, this CA can be used to issue many site certificates.<\/p>\n<p><span style=\"font-weight: 400;\">3. Make local site key<\/span><\/p>\n<pre><span style=\"font-weight: 400;\">openssl genrsa -out mysite.local.key 2048<\/span><\/pre>\n<p><span style=\"font-weight: 400;\">4. Make certificate request (CSR)<\/span><\/p>\n<pre><span style=\"font-weight: 400;\">openssl req -new -key mysite.local.key -out mysite.local.csr -config mysite.local.conf<\/span><\/pre>\n<p><span style=\"font-weight: 400;\">5. Generate and sign the site certificate.<\/span><\/p>\n<pre><span style=\"font-weight: 400;\">openssl x509 -req -in mysite.local.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial \\\r\n  -out mysite.local.crt -days 500 -sha256 -extfile mysite.local.conf -extensions v3_ca\r\n<\/span><span style=\"font-weight: 400;\">&gt; secret<\/span><\/pre>\n<p>6. Install the certificates.<\/p>\n<p>The critical step which makes all the difference to this approach is getting your host to trust the personal CA. This is slightly different for Windows, Mac and Unix, or iOS, but the principle is the same &#8211; you add it to the list of &#8220;trusted roots&#8221;.<\/p>\n<p>In Mac, you can open a .crt file which will add it into your Keychain Access, under Login Keychain &gt; Certificates Category.<\/p>\n<p>Then you must open the &#8220;Trust&#8221; section and set it to &#8220;Always Trust&#8221;.<\/p>\n<p><a href=\"https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.42.37-pm.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-444\" src=\"https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.42.37-pm.png\" alt=\"\" width=\"451\" height=\"321\" srcset=\"https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.42.37-pm.png 1030w, https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.42.37-pm-300x214.png 300w, https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.42.37-pm-768x547.png 768w, https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.42.37-pm-1024x730.png 1024w\" sizes=\"auto, (max-width: 451px) 100vw, 451px\" \/><\/a><\/p>\n<p>Once you have done this any site certificate signed by this fake CA will be trusted by your <em>entire machine<\/em> &#8211; in theory all applications, all browsers and the O\/S itself. This is important to developers using various tools &#8211; IDEs, Postman, CURL or other API clients. It&#8217;s not just a specific solution for Chrome (like startup flags).<\/p>\n<p>After copying the site certificate and key into place in my ExpressJS app, and restarting finally Chrome shows the valid &#8220;Secure&#8221; icon! No more interstitials&#8230;<\/p>\n<p>If you inspect the certificate in Chrome, you should see both the site cert and the CA cert are both valid and trusted.<\/p>\n<p>The site (the SANs are under Details):<\/p>\n<p><a href=\"https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.41.53-pm.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-445\" src=\"https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.41.53-pm.png\" alt=\"\" width=\"461\" height=\"260\" srcset=\"https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.41.53-pm.png 964w, https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.41.53-pm-300x169.png 300w, https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.41.53-pm-768x433.png 768w\" sizes=\"auto, (max-width: 461px) 100vw, 461px\" \/><\/a><\/p>\n<p>The CA:<\/p>\n<p><a href=\"https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.41.25-pm.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-446\" src=\"https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.41.25-pm.png\" alt=\"\" width=\"462\" height=\"261\" srcset=\"https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.41.25-pm.png 970w, https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.41.25-pm-300x169.png 300w, https:\/\/scipilot.org\/blog\/wp-content\/uploads\/2018\/06\/Screen-Shot-2018-06-11-at-4.41.25-pm-768x434.png 768w\" sizes=\"auto, (max-width: 462px) 100vw, 462px\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<p><span style=\"font-weight: 400;\">I also had to add it to <\/span><a href=\"https:\/\/browsersync.io\/docs\/options\"><span style=\"font-weight: 400;\">Browsersync<\/span><\/a>\u00a0configuration<span style=\"font-weight: 400;\">:<\/span><\/p>\n<pre><span style=\"font-weight: 400;\">browserSync.init({<\/span>\r\n <span style=\"font-weight: 400;\">https: {<\/span>\r\n <span style=\"font-weight: 400;\">key: \"..\/ssl\/mysite.local.key\",<\/span>\r\n <span style=\"font-weight: 400;\">cert: \"..\/ssl\/mysite.local.crt\"<\/span>\r\n <span style=\"font-weight: 400;\">},<\/span>\r\n<span style=\"font-weight: 400;\">});<\/span><\/pre>\n<p><span style=\"font-weight: 400;\">Which also started working again on Gulp restart.<\/span><\/p>\n<p>Now this is working and stable, it really has made a difference to my workflow &#8211; just one less level of friction and annoyance in my daily grind. I&#8217;ll have to find something else to get annoyed about now.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Yesterday I finally lost my patience with the developer&#8217;s eternal problem of having to skip the untrusted self-signed certificate interstitial warning screens, and so I decided to solve it properly. The problem has got worse recently due to a combination &hellip; <a href=\"https:\/\/scipilot.org\/blog\/2018\/06\/11\/local-development-certificates-done-properly\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[17],"tags":[36,71],"class_list":["post-443","post","type-post","status-publish","format-standard","hentry","category-articles","tag-programming","tag-security"],"_links":{"self":[{"href":"https:\/\/scipilot.org\/blog\/wp-json\/wp\/v2\/posts\/443","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/scipilot.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/scipilot.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/scipilot.org\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/scipilot.org\/blog\/wp-json\/wp\/v2\/comments?post=443"}],"version-history":[{"count":6,"href":"https:\/\/scipilot.org\/blog\/wp-json\/wp\/v2\/posts\/443\/revisions"}],"predecessor-version":[{"id":454,"href":"https:\/\/scipilot.org\/blog\/wp-json\/wp\/v2\/posts\/443\/revisions\/454"}],"wp:attachment":[{"href":"https:\/\/scipilot.org\/blog\/wp-json\/wp\/v2\/media?parent=443"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/scipilot.org\/blog\/wp-json\/wp\/v2\/categories?post=443"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/scipilot.org\/blog\/wp-json\/wp\/v2\/tags?post=443"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}