OpenBSD ``httpd`` and local ``https`` ========================================== This tutorial configures a local OpenBSD web server for: - Hostname: ``minifishpc01.ronverbs.dev`` - Document root: ``/var/www/htdocs/chronicle-my-brain`` - Access: the OpenBSD host itself - Web server: OpenBSD ``httpd`` - Certificate authority: a private, locally trusted CA The result will be available at: .. code:: text https://minifishpc01.ronverbs.dev/ Because this is a local-only hostname, Let's Encrypt's HTTP-01 validation is not appropriate. A private CA lets the browser verify the server certificate without making the development site publicly accessible. The ``.dev`` top-level domain is treated as HTTPS-only by modern browsers. A certificate warning may therefore be difficult or impossible to bypass. The correct solution is to import the private CA certificate as a trusted authority. .. _1-configure-local-hostname-resolution: 1. Configure local hostname resolution -------------------------------------- Since the website will be opened only on this OpenBSD machine, ``/etc/hosts`` is sufficient. Add: .. code:: text 127.0.0.1 minifishpc01.ronverbs.dev Edit the file: .. code:: sh doas vi /etc/hosts Test the name: .. code:: sh ping -c 1 minifishpc01.ronverbs.dev It should resolve to ``127.0.0.1``. .. _2-create-a-private-certificate-authority: 2. Create a private certificate authority ----------------------------------------- If an existing private CA is already installed and trusted, reuse it and skip to the server-certificate section. Do not create another CA unless a separate trust hierarchy is actually wanted. Create ``/etc/ssl/ronverbs-local-ca.cnf``: .. code:: ini [ req ] distinguished_name = dn x509_extensions = v3_ca prompt = no [ dn ] CN = Ronverbs Local Development CA [ v3_ca ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:true keyUsage = critical, keyCertSign, cRLSign Generate the CA private key: .. code:: sh doas openssl genrsa \ -out /etc/ssl/private/ronverbs-local-ca.key 4096 Protect it: .. code:: sh doas chmod 600 /etc/ssl/private/ronverbs-local-ca.key Generate a CA certificate valid for ten years: .. code:: sh doas openssl req -x509 -new -sha256 -days 3650 \ -key /etc/ssl/private/ronverbs-local-ca.key \ -out /etc/ssl/ronverbs-local-ca.crt \ -config /etc/ssl/ronverbs-local-ca.cnf The private key must remain private: .. code:: text /etc/ssl/private/ronverbs-local-ca.key Only the public CA certificate should be imported into browsers: .. code:: text /etc/ssl/ronverbs-local-ca.crt .. _3-create-the-server-certificate: 3. Create the server certificate -------------------------------- Modern browsers verify the Subject Alternative Name rather than relying solely on the certificate's Common Name. Create ``/etc/ssl/minifishpc01.ronverbs.dev.cnf``: .. code:: ini [ v3_server ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer basicConstraints = critical, CA:false keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth subjectAltName = @alt_names [ alt_names ] DNS.1 = minifishpc01.ronverbs.dev Generate the server private key: .. code:: sh doas openssl genrsa \ -out /etc/ssl/private/minifishpc01.ronverbs.dev.key 2048 Protect it: .. code:: sh doas chmod 600 \ /etc/ssl/private/minifishpc01.ronverbs.dev.key Create a certificate-signing request: .. code:: sh doas openssl req -new -sha256 \ -key /etc/ssl/private/minifishpc01.ronverbs.dev.key \ -out /tmp/minifishpc01.ronverbs.dev.csr \ -subj "/CN=minifishpc01.ronverbs.dev" Sign the request with the private CA: .. code:: sh doas openssl x509 -req -sha256 -days 365 \ -in /tmp/minifishpc01.ronverbs.dev.csr \ -CA /etc/ssl/ronverbs-local-ca.crt \ -CAkey /etc/ssl/private/ronverbs-local-ca.key \ -CAcreateserial \ -out /etc/ssl/minifishpc01.ronverbs.dev.crt \ -extfile /etc/ssl/minifishpc01.ronverbs.dev.cnf \ -extensions v3_server Remove the temporary request: .. code:: sh rm /tmp/minifishpc01.ronverbs.dev.csr Inspect the certificate: .. code:: sh openssl x509 \ -in /etc/ssl/minifishpc01.ronverbs.dev.crt \ -noout -subject -issuer -dates -ext subjectAltName Confirm that the CA signature is valid: .. code:: sh openssl verify \ -CAfile /etc/ssl/ronverbs-local-ca.crt \ /etc/ssl/minifishpc01.ronverbs.dev.crt The result should be: .. code:: text /etc/ssl/minifishpc01.ronverbs.dev.crt: OK .. _4-configure-openbsd-httpd: 4. Configure OpenBSD ``httpd`` ------------------------------ Add these server blocks to ``/etc/httpd.conf``: .. code:: text server "minifishpc01.ronverbs.dev" { listen on 127.0.0.1 port 80 block return 301 \ "https://minifishpc01.ronverbs.dev$REQUEST_URI" } server "minifishpc01.ronverbs.dev" { listen on 127.0.0.1 tls port 443 tls { certificate "/etc/ssl/minifishpc01.ronverbs.dev.crt" key "/etc/ssl/private/minifishpc01.ronverbs.dev.key" } root "/htdocs/chronicle-my-brain" directory index "index.html" } OpenBSD ``httpd`` is chrooted under ``/var/www``. Consequently, this configuration path: .. code:: text /htdocs/chronicle-my-brain refers to this actual filesystem directory: .. code:: text /var/www/htdocs/chronicle-my-brain The server's private key stays outside the chroot under ``/etc/ssl/private``. .. _5-validate-and-start-httpd: 5. Validate and start ``httpd`` ------------------------------- Check the configuration before restarting anything: .. code:: sh doas httpd -n The expected result is: .. code:: text configuration OK Enable and restart the service: .. code:: sh doas rcctl enable httpd doas rcctl restart httpd Confirm that it is running: .. code:: sh doas rcctl check httpd If troubleshooting is required, confirm that ports 80 and 443 are listening: .. code:: sh netstat -an -f inet | grep LISTEN .. _6-import-the-ca-into-firefox: 6. Import the CA into Firefox ----------------------------- Copy the public CA certificate into Downloads so the browser's restricted file picker can see it: .. code:: sh cp /etc/ssl/ronverbs-local-ca.crt "$HOME/Downloads/" In Firefox: 1. Open **Settings**. 2. Select **Privacy & Security**. 3. Find **Certificates**. 4. Select **View Certificates**. 5. Open **Authorities**. 6. Select **Import**. 7. Choose ``ronverbs-local-ca.crt``. 8. Enable trust for identifying websites. 9. Restart Firefox. Import only the ``.crt`` file. Never import or copy the CA's private ``.key`` file. .. _7-import-the-ca-into-chrome-or-chromium: 7. Import the CA into Chrome or Chromium ---------------------------------------- The CA certificate should already be in Downloads: .. code:: text $HOME/Downloads/ronverbs-local-ca.crt In Chrome or Chromium: 1. Open ``chrome://certificate-manager``. 2. Select **Authorities**. 3. Select **Import**. 4. Choose ``ronverbs-local-ca.crt``. 5. Enable **Trust this certificate for identifying websites**. 6. Completely close and restart the browser. NSS command-line alternative ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If the browser does not provide an Import button, install the NSS utilities: .. code:: sh doas pkg_add nss Close Chrome or Chromium completely. Create its NSS database if one does not already exist: .. code:: sh mkdir -p "$HOME/.pki/nssdb" certutil -N --empty-password -d "sql:$HOME/.pki/nssdb" If the database already exists, do not recreate it. Import the CA: .. code:: sh certutil -A \ -d "sql:$HOME/.pki/nssdb" \ -n "Ronverbs Local Development CA" \ -t "C,," \ -i "$HOME/Downloads/ronverbs-local-ca.crt" Confirm the import: .. code:: sh certutil -L -d "sql:$HOME/.pki/nssdb" The certificate list should contain: .. code:: text Ronverbs Local Development CA Restart the browser. .. _8-test-the-finished-site: 8. Test the finished site ------------------------- Test the TLS handshake independently of the browser: .. code:: sh openssl s_client \ -connect minifishpc01.ronverbs.dev:443 \ -servername minifishpc01.ronverbs.dev \ -CAfile /etc/ssl/ronverbs-local-ca.crt