{"id":1298,"date":"2023-01-22T17:21:02","date_gmt":"2023-01-22T23:21:02","guid":{"rendered":"https:\/\/www.nathanhunstad.com\/blog\/?p=1298"},"modified":"2023-01-22T17:24:08","modified_gmt":"2023-01-22T23:24:08","slug":"adding-a-san-to-a-certificate-using-openssl","status":"publish","type":"post","link":"https:\/\/www.nathanhunstad.com\/blog\/2023\/01\/adding-a-san-to-a-certificate-using-openssl\/","title":{"rendered":"Adding a SAN to a certificate using OpenSSL"},"content":{"rendered":"\n<p>A long time ago, <a href=\"https:\/\/www.nathanhunstad.com\/blog\/2016\/09\/pki-revisited\/\" target=\"_blank\" rel=\"noopener\" title=\"I set up an internal PKI\">I set up an internal PKI<\/a> so I could create my own TLS certificates to add to internal devices and HTTPS servers. I used this primarily for my EdgeOS router since that was the main device I would log into that would give me a warning about untrusted certificates.<\/p>\n\n\n\n<p>That was all the way back in 2016. Since then, I&#8217;ve kind of kept on top of things, renewing the certificate when it expired a few years ago, but it had expired yet again so I was faced with that dreaded warning when connecting to my router. This time, though, creating a new cert using the old commands wasn&#8217;t good enough, because it was missing a Subject Alternate Name (SAN), and that prompted a slightly different warning with the same result: no trust in the browser.<\/p>\n\n\n\n<p>What happened? Using a SAN instead of the Common Name for certificate validation has been required by browsers for a while (<a href=\"https:\/\/developer.chrome.com\/blog\/chrome-58-deprecations\/#remove_support_for_commonname_matching_in_certificates\" target=\"_blank\" rel=\"noopener\" title=\"since 2017 in Chrome\">since 2017 in Chrome<\/a> for example). This is because the Common Name is ambiguous, whereas the SAN can specify a domain, IP, or URI; if you want the gnarly details, see <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc2818\" target=\"_blank\" rel=\"noopener\" title=\"RFC2818\">RFC2818<\/a> and <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc6125\" target=\"_blank\" rel=\"noopener\" title=\"RFC6125\">RFC6125<\/a>.<\/p>\n\n\n\n<p>This wasn&#8217;t a problem in 2016 since browsers weren&#8217;t requiring this, but it&#8217;s been an issue for a while now. Unfortunately, it&#8217;s a bit harder to create a certificate-signing request (CSR) and sign a certificate in such a way that a SAN is included. There are ways you can do this using OpenSSL configuration and extension files, but I was looking for one-liners like I could do before.<\/p>\n\n\n\n<p>Eventually, thanks to some searching, I was able to find a way to add a DNS SAN to a certificate and still make it a one-liner for the most part. To start by generating the CSR, I ran the following command (replaced FQDN with the FQDN of the target server):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>openssl req -new -sha256 -subj \"\/C=US\/ST=Minnesota\/L=Minneapolis\/O=MyOrg\/CN=FQDN\" -addext \"subjectAltName = DNS:FQDN\" -key private.key -out req.csr<\/code><\/pre>\n\n\n\n<p>That generated a CSR with the right extension, as you can see when looking at the text dump via OpenSSL:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> Attributes:\n            Requested Extensions:\n                X509v3 Subject Alternative Name:\n                    DNS:FQDN\n<\/code><\/pre>\n\n\n\n<p>I thought that was it and signing it the old way would be all that I&#8217;d need, but nope, it didn&#8217;t include the SAN (a fact that a user <a href=\"https:\/\/security.stackexchange.com\/questions\/74345\/provide-subjectaltname-to-openssl-directly-on-the-command-line\" target=\"_blank\" rel=\"noopener\" title=\"calls out explicitly\">calls out explicitly<\/a> in the page where I found this. So I needed to find out how to sign it with the extension as well. After a bit more digging, I found that this would do the trick<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>openssl x509 -sha256 -req  -days 1096 -in req.csr -CA ia.crt -CAkey ia.key -extensions SAN -extfile &lt;(cat \/etc\/ssl\/openssl.cnf &lt;(printf \"\\n&#91;SAN]\\nsubjectAltName=DNS:FQDN\")) -set_serial 4 -out cert.pem<\/code><\/pre>\n\n\n\n<p>This essentially creates the proper configuration on the fly, and as a result the certificate that is generated has the proper SAN. After adding to the router and restarting the lighttpd daemon, we are trusted again!<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.nathanhunstad.com\/blog\/wp-content\/uploads\/2023\/01\/image.png\"><img loading=\"lazy\" decoding=\"async\" width=\"382\" height=\"48\" src=\"https:\/\/www.nathanhunstad.com\/blog\/wp-content\/uploads\/2023\/01\/image.png\" alt=\"\" class=\"wp-image-1299\" srcset=\"https:\/\/www.nathanhunstad.com\/blog\/wp-content\/uploads\/2023\/01\/image.png 382w, https:\/\/www.nathanhunstad.com\/blog\/wp-content\/uploads\/2023\/01\/image-300x38.png 300w\" sizes=\"auto, (max-width: 382px) 100vw, 382px\" \/><\/a><figcaption class=\"wp-element-caption\">Success!<\/figcaption><\/figure>\n\n\n\n<p>If there was a need to add another SAN such as an IP address, the same method could be used.<\/p>\n\n\n\n<p>That&#8217;s it!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A long time ago, I set up an internal PKI so I could create my own TLS certificates to add to internal devices and HTTPS servers. I used this primarily for my EdgeOS router since that was the main device I would log into that would give me a warning about untrusted certificates. That was&hellip; <a class=\"more-link\" href=\"https:\/\/www.nathanhunstad.com\/blog\/2023\/01\/adding-a-san-to-a-certificate-using-openssl\/\">Continue reading <span class=\"screen-reader-text\">Adding a SAN to a certificate using OpenSSL<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[1],"tags":[286,285,287],"class_list":["post-1298","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-openssl","tag-security","tag-tls","entry"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/posts\/1298","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/comments?post=1298"}],"version-history":[{"count":2,"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/posts\/1298\/revisions"}],"predecessor-version":[{"id":1301,"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/posts\/1298\/revisions\/1301"}],"wp:attachment":[{"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/media?parent=1298"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/categories?post=1298"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.nathanhunstad.com\/blog\/wp-json\/wp\/v2\/tags?post=1298"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}