I've never found a good, single document that covers self-signed certificates well. Googling for guidance results in a lot of different ways to do it, and if you don't find one that does exactly what you want, trying to combine that one with a different one that does the things missing from the first might not work because they are using different approaches.

I did manage to glean enough to make a pair of simple scripts that seemed to work for my needs, but have no idea if they are actually right, or if putting together things from different guides messed things up.

Here's the script I use for my root certificate (makeroot.sh):

#!/bin/bash
NAME=${1:?Must specify name for root}
CN=${CN:-My Little Certificate Authority}
O=${O:-My Home Network}
C=${C:-US}
DAYS=${DAYS:-365}
PW=${PW:--aes256}
SUBJ="/CN=$CN/O=$O/C=$C"
openssl genrsa $PW -out $NAME.key 2048
openssl req -x509 -new -subj "$SUBJ" -nodes -key $NAME.key -sha256 -days $DAYS -out $NAME.pem

So suppose I'm setting up a private certificate authority for my iot devices. I would do:

$ ./makeroot.sh iot

That will make iot.key and iot.pem.

To make a certificate signed with the iot certificate I use this script (makecert.sh):

#!/bin/bash
NAME=${1:?Must specify name for cert}
ROOT=${2:?Must specify name for root}
CA="-CA $ROOT.pem -CAkey $ROOT.key -CAcreateserial"
O=${O:-My Home Network}
C=${C:-US}
DAYS=${DAYS:-365}
SUBJ="/CN=$NAME/O=$O/C=$C"
openssl genrsa -out $NAME.key 2048
openssl req -new -key $NAME.key -subj "$SUBJ" -out $NAME.csr
openssl x509 -req -in $NAME.csr $CA -out $NAME.crt -days $DAYS -sha256
rm $NAME.csr

E.g., to make a key for an iot doorbell:

$ ./makecert.sh doorbell iot

That makes doorbell.crt and doorbell.key.

That worked well until the need arose to make an SNI certificate. I had expected that it would just be a matter of slightly tweaking makecert.sh, add a few more arguments.

But all I could find in the way of examples took a different approach, where most of the information is passed in a config file, and some of the guides I read indicated that you cannot pass the names on the command line to openssl when doing SNI. Anyway, this is the script I came up with (makesni.sh):

#!/bin/bash
function makeconf {
CN=$1
DIR='$dir'
shift
cat > tmp.conf <<HERE
[ req ]
distinguished_name = dn
req_extensions = req_ext
unique_subject = no
prompt = no

[ ca ]
default_ca = tzs_ca

[ tzs_ca ]
dir = ca-files
private_key	= $DIR/iot.key
certificate = $DIR/iot.pem
new_certs_dir = $DIR
database	= $DIR/index.txt	# database index file.
unique_subject = no
default_md	= sha256	# use public key default MD
serial	= $DIR/iot.srl	# The current serial number
email_in_dn = tzs@mouse-potato.com
default_days = 365

[ dn ]
CN = $CN
O = My Home Network
C = US

[ policy_anything ]
countryName	= optional
stateOrProvinceName	= optional
localityName	= optional
organizationName	= optional
organizationalUnitName	= optional
commonName	= supplied
emailAddress	= optional

[req_ext]
HERE

if [ -n "$1" ]
then
cat >> tmp.conf <<HERE
subjectAltName = @alts

[ alts ]
DNS.1 = $1
HERE
shift
fi
POS=2
while [ -n "$1" ]
do
echo "DNS.$POS = $1" >> tmp.conf
shift
POS=$((POS + 1))
done
}

NAME=${1:?Must specify name for cert}
makeconf $*
openssl genrsa -out out/$NAME.key 2048
openssl req -new -key out/$NAME.key -config tmp.conf -out out/$NAME.csr
openssl ca -policy policy_anything -out out/$NAME.crt -config tmp.conf -extensions req_ext -infiles out/$NAME.csr
rm out/$NAME.csr tmp.conf

This assumes a directory, ca_files, that contains iot.key and iot.pem, and also an empty file named index.txt. Also it assumes in out directory.

To make an SNI certificate for a device that has three hosts, named fridge, freezer, and icemaker:

$ ./makesni.sh fridge freezer icemaker

That will make fridge.crt and fridge.key in the out directory, and that will have fridge as the CN and freezer and icemaker as subject alternate names.

OK, so now I can put doorbell.crt and doorbell.key on the doorbell device, and fridge.crt and fridge.key on the device that has the fridge, freezer, and icemaker sites, and all is well, right?

Well...I also need to give my browsers iot.pem so they will recognize those other certificates. I'd also like curl and Perl and Python to recognize them, so that I can write scripts that do fancy iot things.

I didn't find any good guide to installing the CA certificate. From a bunch of Googling I came up with:

• On Debian Linux, copy iot.pem to /usr/local/share/ca-certificates, and change the extension to .crt.

Run update-ca-certificates.

That should make it available to Perl, Python, and curl.

For Chrome and Firefox, find their certificate management dialogs and use them to add the certificate.

• On OS X, double-click iot.pem. That should open Keychain Access. Let it import the certificate. Set it to be trusted for SSL. That will make it work in Chrome and Safari.

For Firefox, same as Linux.

For Perl, do one of:

1. Set environment variable PERL_LWP_SSL_CA_PATH or HTTPS_CA_DIR to point to a directory containing iot.pem, or

2. Set the environment variable PERL_LWP_SSL_CA_FILE of HTTPS_CA_FILE to point to iot.pem, or

The first requires that the directory contain symbolic links of the form hash.N where hash is a hash of the certificate and N is a sequence number. You need two of these, because there apparently is an old hash and a new hash. 'openssl x509 -subject_hash -noout -in iot.pem' to get the new hash, and 'openssl x509 -subject_hash_old -noout -in iot.pem' to get the old hash. Or just go to the cert directory and run 'c_rehash .' and 'c_rehash -old .' and those will make all the symlinks for you.

For Python3, similar to Perl but with SSL_CERT_DIR and SSL_CERT_FILE as the environment variable. Same hash symlinks as Perl (although I think you only need the new hash).

For curl, "--cacert iot.pem" or "--capath <dir>" where <dir> is a directory with the certificate. Same symlink considerations as Python3. Or set CURL_CA_BUNDLE env variable to point to a bundle that contains iot.pem. A bundle is just a bunch of certificate fills concatenated.

• Windows, I have no idea.