{"id":107,"date":"2024-12-17T06:55:33","date_gmt":"2024-12-17T06:55:33","guid":{"rendered":"https:\/\/gabriel.marcanobrady.family\/blog\/?p=107"},"modified":"2024-12-17T06:55:33","modified_gmt":"2024-12-17T06:55:33","slug":"managing-your-own-ca-elliptic-curve-edition","status":"publish","type":"post","link":"https:\/\/gabriel.marcanobrady.family\/blog\/2024\/12\/17\/managing-your-own-ca-elliptic-curve-edition\/","title":{"rendered":"Managing Your Own CA, Elliptic Curve Edition"},"content":{"rendered":"\n<p>Historically, I&#8217;ve used my main desktop to store system backups and provide other network services, but this is annoying because I dual-boot it to play video-games, so while I&#8217;m playing most services are offline. Additionally, electricity in San Diego is absurdly expensive (thanks SDGE), so the 180+W while idling adds up quite a bit. To fix both problems, I bought an HP Prodesk 600 G3 MT from <a href=\"https:\/\/store.human-i-t.org\/\">Human-I-T<\/a> for very reasonable prices, hoping that the idle power draw stays under 20 Watts. While setting up that server, I decided that I might as well figure out how to set up my own CA for all of the internal services that I&#8217;m setting up, since I was getting tired of getting warnings from Firefox about &#8220;unsecured&#8221; connections due to self-signed certificates.<\/p>\n\n\n\n<p>Most of the information I used to set up my own CA came from the OpenSSL CA documentation, which can be found here: <a href=\"https:\/\/openssl-ca.readthedocs.io\/en\/latest\/index.html\">https:\/\/openssl-ca.readthedocs.io\/en\/latest\/index.html<\/a>. However, it is missing the details on how to use Elliptic Curve keys. This post is an attempt to fill in that gap.<\/p>\n\n\n\n<p>The process can be summarized as follows:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Make a root CA key and certificate<\/li>\n\n\n\n<li>Make an intermediate CA key and certificate by requesting signing by root CA, and create an intermediate certificate chain<\/li>\n\n\n\n<li>Make individual server certificates by requesting signing by intermediate CA, and extend the certificate chain<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"safeguard_keys\">Safeguarding Keys<\/h2>\n\n\n\n<p>It is important to protect all private keys, as if they leak anyone with them can sign certificates that pass as valid when checked against your CA certificates. This matters a little less when all of your certs are meant for your local network, but meh, might as well try to do something about it.<\/p>\n\n\n\n<p>What I decided to do was to use LUKS to encrypt some filesystems on disk. I used one to store the CA and intermediate CA, and a second one to store client keys.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# Create the Root CA\/Intermediate CA file, and the file for server keys\ndd if=\/dev\/urandom of=ca bs=1M count=100 status=progress\ndd if=\/dev\/urandom of=server bs=1M count=100 status=progress\n\n# it will ask for a password-- remember it or store it somewhere secure\/safe\/encrypted\ncryptsetup luksFormat ca\ncryptsetup luksFormat server\n\n# now open the volume, and mount it for use. Opening it will ask for the volume password\ncryptsetup open ca ca-keys\ncryptsetup open server server-keys\nmkdir ca-keys\nmkdir server-keys\nmount \/dev\/mapper\/ca-keys ca-keys\nmount \/dev\/mapper\/server-keys server-keys\n\n<\/pre><\/div>\n\n\n<p>It&#8217;s probably a good idea to backup the LUKS headers, in case something happens to them:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ncryptsetup luksHeaderBackup ca --header-backup-file ca-keys.header.luks\ncryptsetup luksHeaderBackup server --header-backup-file server-keys.header.luks\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">Make Root CA and its Certificate<\/h2>\n\n\n\n<p>The general outline of the steps can be found here: <a href=\"https:\/\/openssl-ca.readthedocs.io\/en\/latest\/create-the-root-pair.html\">https:\/\/openssl-ca.readthedocs.io\/en\/latest\/create-the-root-pair.html<\/a><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ncd ca-keys\/\nmkdir root\/\ncd root\/\nmkdir certs crl newcerts private\nchmod 700 private\ntouch index.txt\necho 1000 &gt; serial\n<\/pre><\/div>\n\n\n<p>At this point a <code>openssl.cnf<\/code> file needs to be created that will include default settings. It can be copied from <a href=\"https:\/\/openssl-ca.readthedocs.io\/en\/latest\/root-configuration-file.html#root-configuration-file\">https:\/\/openssl-ca.readthedocs.io\/en\/latest\/root-configuration-file.html#root-configuration-file<\/a> and modified to meet needs. For example, on my local install, I tweaked the <code>dir<\/code> and <code>private_key<\/code> paths, and changed the defaults for many of the <code>*_default<\/code> fields (e.g. <code>countryName_default<\/code> to US). I also tweaked the <code>req<\/code> <code>default_bits<\/code> to 4096.<\/p>\n\n\n\n<p>With that in place, now it&#8217;s time to create the key in the <code>ca-keys\/root\/<\/code> directory:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nopenssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -pkeyopt ec_param_enc:named_curve -aes-256-cbc -out private\/root-ca.key.pem\n# This will now ask for a password, set one and keep it safe!\nchmod 400 private\/root-ca.key.pem\n<\/pre><\/div>\n\n\n<p>Wait, why is that so messy? Turns out that only some elliptic-curves are really used widely online (e.g. <a href=\"https:\/\/issues.chromium.org\/issues\/41168970\">Chrome doesn&#8217;t support secp521r1<\/a>). The two that seem to be the most supported are secp384r1 and secp256r1. So I just arbitrarily-ish picked secp384r1. Also, we are using the <code>-aes-256-cbc<\/code> option to enable password protection on the key. There are other options that may work (see the output of <code>openssl enc -list<\/code> for tentative options).<\/p>\n\n\n\n<p>OK, now we have the key. Now we need a root certificate (we&#8217;re setting the expiry date far into the future, as recommended):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nopenssl req -config openssl.cnf -new -sha256 -extensions v3_ca -days 7300 -key private\/root-ca.key.pem -out csr\/ca.csr.pem\n# This will request the password for the key, and then will ask for information about the certificate such as its common name, organization, etc.\nchmod 444 certs\/ca.cert.pem\n<\/pre><\/div>\n\n\n<p> To verify that the certificate looks ok:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nopenssl x509 -noout -text -in certs\/ca.cert.pem\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">Make Intermediate CA and its Certificate<\/h2>\n\n\n\n<p>OK, now we can work on making the intermediate CA, which is the one that will actually be used to sign all of the certificates for the websites, etc. This should look familiar as it&#8217;s similar to what we did with the root certificate:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ncd ..\/\nmkdir intermediate\ncd intermediate\nmkdir certs crl csr newcerts private\nchmod 700 private\ntouch index.txt\necho 1000 &gt; serial\necho 1000 &gt; crlnumber\n<\/pre><\/div>\n\n\n<p>Now, we can copy the base <code>openssl.cnf<\/code> for the intermediate certificate from <a href=\"https:\/\/openssl-ca.readthedocs.io\/en\/latest\/intermediate-configuration-file.html#intermediate-configuration-file\">https:\/\/openssl-ca.readthedocs.io\/en\/latest\/intermediate-configuration-file.html#intermediate-configuration-file<\/a>. Once saved, just as with the root <code>openssl.cnf<\/code>, variables can be tweaked (such as <code>dir<\/code>, <code>*_default<\/code>, <code>default_bits<\/code>).<\/p>\n\n\n\n<p>Now it is time to create the intermediate CA key:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nopenssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -pkeyopt ec_param_enc:named_curve -aes-256-cbc -out private\/intermediate.key.pem\n# This will now ask for a password, set one and keep it safe!\nchmod 400 private\/intermediate.key.pem\n<\/pre><\/div>\n\n\n<p>And now to create and sign the intermediate certificate:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nopenssl req -config openssl.cnf -new -sha256 -keyprivate\/intermediate.key.pem -out csr\/intermediate.csr.pem\n# This will ask for the intermediate key password, and other information to fill into the certificate\n\ncd ..\/root\nopenssl ca -config openssl.cnf -extensions v3_intermediate_ca -days 3650 -notext -md sha256 -in ..\/intermediate\/csr\/intermediate.csr.pem -out ..\/intermediate\/certs\/intermediate.cert.pem\n# This will ask for the root CA password\nchmod 444 ..\/intermediate\/certs\/intermediate.cert.pem\n<\/pre><\/div>\n\n\n<p>And to verify the newly created certificate:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nopenssl x509 -noout -text -in ..\/intermediate\/certs\/intermediate.cert.pem\n# and check it against the root CA\nopenssl verify -CAfile .\/certs\/ca.cert.pem ..\/intermediate\/certs\/intermediate.cert.pem\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">Create the Certificate Chain<\/h2>\n\n\n\n<p>This is straight-ish forward (the order matters!). We need to concatenate the certificates of the root and the intermediate together to form the start of the certificate chain (intermediate needs to go first, the root):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# Changing directory to the root of the certs encrypted partition\ncd ..\/\ncat .\/intermediate\/certs\/intermediate.cert.pem root\/certs\/ca.cert.pem &gt; .\/intermediate\/certs\/ca-chain.cert.pem\n<\/pre><\/div>\n\n\n<p>Great! At this point all of the initial setup is completed. From here on is a matter of creating site specific certificates, and installing the root certificate to all computers that need it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating a Certificate for an Internal Site<\/h2>\n\n\n\n<p>Now we need to go into the <a href=\"#safeguard_keys\" data-type=\"internal\" data-id=\"#safeguard_keys\"><code>server-certs<\/code> encrypted folder we created earlier<\/a>, as this is where we will store the certificates and keys for the individual sites as we create them (arguably, the better approach is to have each server create its own private keys and just provide the CSR to the signing server):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ncd server-keys\nmkdir site1.my.example\n# Now create the key, no password\n# If password is desired, add `-aes256 -pass stdin`\nopenssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -pkeyopt ec_param_enc:named_curve -out site1.my.example\/site1.key.pem\nchmod 400 site1.my.example\/site1.key.pem\n<\/pre><\/div>\n\n\n<p>With the private key in place (which should probably securely copied to the server for this domain), we now need to prepare the certificate signing request (CSR). We need the <code>openssl.cnf<\/code> file from <a href=\"https:\/\/openssl-ca.readthedocs.io\/en\/latest\/intermediate-configuration-file.html#intermediate-configuration-file\">here<\/a> again (the intermediate CA). <code>subjectAltName<\/code> are now effectively required by modern browsers if the certificate is to authenticate a website. Multiple names can be provided, separated by commas (e.g. <code>DNS:site1.my.example,DNS:site1.local<\/code>).<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nopenssl req -config openssl.cnf -new -sha256 -key site1.my.example\/site1.key.pem -out site1.my.example\/site1.csr.pem -addext &quot;subjectAltName = DNS:site1.my.example&quot;\n<\/pre><\/div>\n\n\n<p>With the CSR in hand, we now need to go back to the intermediate CA to create the certificate:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ncd ..\/ca-keys\/intermediate\/\nopenssl ca -config openssl.cnf -extensions server_cert -days 365 -notext -md sha256 -in ..\/..\/server-keys\/site1.my.example\/site1.csr.pem -out ..\/..\/server-keys\/site1.my.example\/site1.cert.pem\n<\/pre><\/div>\n\n\n<p>And with that we should now have a signed server certificate! The last thing to do is to create the certificate chain with this new certificate:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ncd ..\/..\/server-keys\/site1.my.example\/\ncat site1.cert.pem ..\/..\/ca-keys\/intermediate\/certs\/ca-chain.cert.pem &gt; site1.chain.pem\n<\/pre><\/div>\n\n\n<p>And finally, with that we should have all of the certificates and chains needed to set up properly signed certificates at home or other locations. The only thing really left to do is to install the root CA certificate on any machines and browsers that should recognize any certificates signed by the intermediate CA as authentic.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Safeguarding Keys, Again: Unmount and Lock<\/h2>\n\n\n\n<p>After being done signing and copying certificates and chains, remember to unmount and lock the encrypted storages!<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# cd out of server-keys and ca-keys\numount server-keys\numount ca-keys\ncryptsetup close ca\ncryptsetup close server\n<\/pre><\/div>\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Historically, I&#8217;ve used my main desktop to store system backups and provide other network services, but this is annoying because I dual-boot it to play video-games, so while I&#8217;m playing most services are offline. Additionally, electricity in San Diego is absurdly expensive (thanks SDGE), so the 180+W while idling adds up quite a bit. To [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,4],"tags":[29,31,30],"class_list":["post-107","post","type-post","status-publish","format-standard","hentry","category-engineering","category-linux","tag-certificate-authority","tag-certificates","tag-elliptic-curve"],"_links":{"self":[{"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/posts\/107","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/comments?post=107"}],"version-history":[{"count":8,"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/posts\/107\/revisions"}],"predecessor-version":[{"id":183,"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/posts\/107\/revisions\/183"}],"wp:attachment":[{"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/media?parent=107"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/categories?post=107"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/gabriel.marcanobrady.family\/blog\/wp-json\/wp\/v2\/tags?post=107"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}