Quickly create a self signed certificate for a custom domain
Tags: openssl, batch
Published: 2020-04-13 20:03:55

I often find myself needing a simple self signed certificate that is either for a wildcard domain or that cover multiple domain names. And then, even when I have openssl somewhere the questions are "do I have the config file?", "do I really need to fill in all those distinguished name values?", "where do I put additional domains", etc.

So to make my life simpler I made a small batch script that takes domain names as arguments, creates configuration file and instructs openssl to create a self signed certificate for those domains.

## Example usage

Requesting certificates like:

```
E:\Temp> self-signed-cert mydomain.local *.mydomain.local
```

will generate one certificate with 2 domain names (`mydomain.local` and `*.mydomain.local`) and will produce the following files:

1. mydomain.local.conf - configuration file for openssl
2. mydomain.local.key - private key
3. mydomain.local.crt - certificate
4. mydomain.local.pfx - a pkcs#12 file with both certificate and private key

Content of the generated `mydomain.local.conf` file:

```ini
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = req_distinguished_name
x509_extensions = v3_req

[req_distinguished_name]
CN = mydomain.local

[v3_req]
subjectAltName = @san

[san]
DNS.1 = mydomain.local
DNS.2 = *.mydomain.local
```

And the generated certificate looks like this:

```
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            fc:55:a1:88:a5:68:67:0c
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = mydomain.local
        Validity
            Not Before: Apr 13 12:12:57 2020 GMT
            Not After : Apr 11 12:12:57 2030 GMT
        Subject: CN = mydomain.local
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (4096 bit)
                Modulus:
                    00:cb:d9:5c:bb:df:23:1e:69:5d:b2:45:06:d8:1f:
                    d9:d1:4a:37:07:ab:c7:86:77:e0:2f:88:36:dc:f4:
                                      <...>
                    7d:70:b6:23:aa:9a:bc:57:83:c9:f8:13:3f:9f:b4:
                    01:54:71
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Alternative Name: 
                DNS:mydomain.local, DNS:*.mydomain.local
    Signature Algorithm: sha256WithRSAEncryption
         b0:7b:4c:2c:f6:5e:40:2b:9d:9d:71:b1:d3:f8:0b:44:84:50:
         d1:ea:b0:91:fb:61:7d:d8:f8:d9:ca:5d:c8:b2:bf:d4:5d:75:
                               <...>
         5c:73:66:d1:52:10:11:2b:68:85:da:8a:16:3d:82:4f:0c:c5:
         50:a5:85:81:4f:06:27:33
```



## 



## Batch file explained

```batch
if [%1]==[] (
  echo Usage: %0 ^<domain_name^> [additional_domain] [additional_domain] ...
  exit /b 1
)
```

Just making sure that there is at least one argument



```batch
if [%2]==[] (set addsan=) else (set addsan=1)
```

Checking if it is a single domain name or multiple domain name



```batch
set fn=%~1
if [%fn:~0,2%] == [*.] set fn=_wildcard.%fn:~2%
```

Setting a file name for configuration and certificates. If the (first) domain name in the list is a wildcard domain, replace the asterisk character with "wildcard" word.  Otherwise just take the domain name. So requesting a `*.mydomain.local` certificates will generate `wildcard.mydomain.local` files, while requesting `web1.mydomain.local`  certificate will generate `web1.mydomain.local` files.

`%~1` - removing surrounding quotes around first argument. E.g. if the first argument is `"something with spaces"`, the `%~1` will remove the quotes.
`%fn:~0,2%` - substring, first two characters of the `fn` variable
`%fn:~2%` - substring, everything after 2nd character



```batch
echo [req]>%fn%.conf
echo default_bits = 2048>>%fn%.conf
echo prompt = no>>%fn%.conf
echo default_md = sha256>>%fn%.conf
echo distinguished_name = req_distinguished_name>>%fn%.conf
if [%addsan%] == [1] echo x509_extensions = v3_req>>%fn%.conf
echo.>>%fn%.conf
echo [req_distinguished_name]>>%fn%.conf
echo CN = %~1>>%fn%.conf
echo.>>%fn%.conf
```

Making the openssl configuration, named `%fn%.conf`.



```batch
if not [%addsan%] == [1] goto :makecert
```

Skip additional configuration if there is only one domain



```batch
echo [v3_req]>>%fn%.conf
echo subjectAltName = @san>>%fn%.conf
echo.>>%fn%.conf
echo [san]>>%fn%.conf
set /a sanid = 0
```

Additional configuration sections for multiple domain names. 



```batch
:sanloop
set /a sanid+=1
echo DNS.%sanid% = %~1>>%fn%.conf
shift
if [%~1]==[] goto :makecert
goto :sanloop
```

Looping through the arguments. 
`set /a sanid+=1` - increment counter so that each DNS entry has it's own number
`shift` - shift arguments so that %2 becomes %1, %3 becomes %2, etc
`if [%~1]==[] goto :makecert` - once there are no more arguments, break the loop 



```batch
:makecert
openssl req -new -x509 -nodes -days 3650 -sha256 -newkey rsa:4096 -keyout %fn%.key -out %fn%.crt -config %fn%.conf

openssl pkcs12 -export -out %fn%.pfx -inkey %fn%.key -in %fn%.crt -name %friendly% -passout pass:
```

Finally, the openssl call. Frist create a new self signed certificate which is valid for 10 years, and then export it to pkcs#12  pfx file with an empty password.

Make sure that openssl is somewhere in PATH.



## self-signed-cert.cmd


{% gist https://gist.github.com/milolav/1d38f92dd51c06837f333e4731ef7810 %}