# Using mTLS with wstunnel

## Generating keys and certificates

WARNING: The following instructions are intended for using in a development / testing environment. They are **not**
intended for setting up a production environment. In a production environment you could use a solution such
as [OpenBao](https://openbao.org/) (opensource fork of Hashicorp Vault), [EJBCA](https://www.ejbca.org/)
or [Dogtag PKI](https://www.dogtagpki.org/) for example.

These steps are based on: https://jamielinux.com/docs/openssl-certificate-authority/

In order to setup wstunnel to authenticate clients with certificates (mTLS) one must have a certificate authority for
signing client certificates. In this example we will create a certificate authority using OpenSSL.

Run these commands from a directory which we will use to store the CA's files. For example under `~/wstunnel/client_ca`

```shell
$ mkdir -p $HOME/wstunnel/ca/{certs,csr,crl,newcerts,private}
$ cd $HOME/wstunnel/ca/
$ echo 1000 > serial
$ touch index.txt
```

Create the OpenSSL CA configuration. Beware some entries are escaped so they can be easily written out with `cat`:

```shell
$ cat > ./openssl.cnf << END_OF_FILE
[ ca ]
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = $HOME/wstunnel/ca
certs             = \$dir/certs
crl_dir           = \$dir/crl
new_certs_dir     = \$dir/newcerts
database          = \$dir/index.txt
serial            = \$dir/serial
RANDFILE          = \$dir/private/.rand

# The root key and root certificate.
private_key       = \$dir/private/ca.key.pem
certificate       = \$dir/certs/ca.cert.pem

# For certificate revocation lists.
crlnumber         = \$dir/crlnumber
crl               = \$dir/crl/ca.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_loose

[ policy_loose ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = optional
emailAddress            = optional

[ req ]
# Configuration for a certificate signing request.
default_bits        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only
# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256
# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = GB
stateOrProvinceName_default     = England
localityName_default            =
0.organizationName_default      = wstunnel development
#organizationalUnitName_default =
#emailAddress_default           =

[ v3_ca ]
# Configuration for a certificate authority.
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ client_cert ]
# Configuration for client certificates.
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Configuration for server certificates.
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ crl_ext ]
# Configuration for CRLs.
authorityKeyIdentifier=keyid:always
END_OF_FILE
```

Generate the private key of the certificate authority. Normally you would encrypt it and set a passphrase on it but for
development purposes we will leave it unencrypted.

```shell
$ cd $HOME/wstunnel/ca/
$ openssl genrsa -out private/ca.key.pem 4096
```

The certificate of the root certificate authority is self-signed (since it is the root of trust):

```shell
$ openssl req -config openssl.cnf \
      -key private/ca.key.pem \
      -new -x509 -days 7300 -sha256 -extensions v3_ca \
      -out certs/ca.cert.pem
---8<------
Country Name (2 letter code) [GB]:
State or Province Name [England]:
Locality Name []:
Organization Name [Alice Ltd]:
Organizational Unit Name []:
Common Name []:wstunnel Development Root CA
Email Address []:
```

Generate a key for the wstunnel server, generate a certificate signing request (CSR) and create a certificate with our
CA for the CSR:

```shell
$ openssl genrsa -out private/wstunnel-server.pem 2048
$ openssl req -config openssl.cnf \
      -key private/wstunnel-server.pem \
      -new -sha256 -out csr/wstunnel-server.csr.pem
---8<------
Country Name (2 letter code) [GB]:
State or Province Name [England]:
Locality Name []:
Organization Name [Alice Ltd]:
Organizational Unit Name []:
Common Name []:wstunnel Development Server
Email Address []:

$ openssl ca -config openssl.cnf \
      -extensions server_cert -days 375 -notext -md sha256 \
      -in csr/wstunnel-server.csr.pem \
      -out certs/wstunnel-server.cert.pem
---8<------
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
```

Next we do the same thing (generate key, create request, sign request) but then for a wstunnel client:

```shell
$ openssl genrsa -out private/wstunnel-client-1.pem 2048
$ openssl req -config openssl.cnf \
      -key private/wstunnel-client-1.pem \
      -new -sha256 -out csr/wstunnel-client-1.csr.pem
---8<------
Country Name (2 letter code) [GB]:
State or Province Name [England]:
Locality Name []:
Organization Name [Alice Ltd]:
Organizational Unit Name []:
Common Name []:wstunnel Development Client 1
Email Address []:

$ openssl ca -config openssl.cnf \
      -extensions client_cert -days 375 -notext -md sha256 \
      -in csr/wstunnel-client-1.csr.pem \
      -out certs/wstunnel-client-1.cert.pem
---8<------
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
```

## Using mTLS on the wstunnel server side

This section assumes you have generated the certificate authority, keys, certificates, etc. as outlined in the "
Generating keys and certificates" section.

Start a `wstunnel` server and make it use the server key pair certificate (`--tls-certificate` and `--tls-private-key`)
and configure it to authenticate clients via mTLS (`--tls-client-ca-certs`):

```shell
$ wstunnel server \
   --tls-certificate ./certs/wstunnel-server.cert.pem \
   --tls-private-key ./private/wstunnel-server.pem \
   --tls-client-ca-certs ./certs/ca.cert.pem \
   wss://0.0.0.0:8443
```

### Testing

You can use `openssl` to test connecting with the client certificate to the wstunnel server:

```shell
$ openssl s_client -connect 127.0.0.1:8443 \
   -key ./private/wstunnel-client-1.pem \
   -cert ./certs/wstunnel-client-1.cert.pem \
   -cert_chain ./certs/ca.cert.pem \
   -state -debug
---8<-----
Acceptable client certificate CA names
C = GB, ST = England, O = Alice Ltd, CN = wstunnel Development Root CA
---8<-----
```

Similarly, you can use `openssl` to test what happens if you try to connect with a client certificate which is not
signed by our CA by generating a self-signed certificate:

```shell
$ openssl req -nodes -x509 -sha256 -newkey rsa:4096 \
  -keyout faux.key.pem \
  -out faux.crt.pem \
  -days 356 \
  -subj "/C=GB/ST=England/L=London/O=ACME Corp/OU=IT Dept/CN=Development Faux Client"
$ openssl s_client -connect 127.0.0.1:8443 \
   -key faux.key.pem \
   -cert faux.crt.pem \
   -cert_chain ./certs/ca.cert.pem \
   -state -debug
----8<----
SSL3 alert read:fatal:certificate unknown
---8<-----
```

Trying to connect without the client presenting any certificate at all will also fail (the `--cacert` flag only
tells `curl` which CA certificate to use to verify the certificate of the **server** with):

```shell
$ curl -vvv --cacert ./certs/ca.cert.pem https://127.0.0.1:8443
```

## Using mTLS on the wstunnel client side

This section assumes you have generated the certificate authority, keys, certificates, etc. as outlined in the "
Generating keys and certificates" section. It also assumes you have a running wstunnel server with mTLS configured. For
example as setup in the `Using mTLS on the wstunnel server side` section.

```shell
$ wstunnel client \
   --tls-certificate ./certs/wstunnel-client-1.cert.pem \
   --tls-private-key ./private/wstunnel-client-1.pem \
   -L tcp://1212:localhost:1313 \
   wss://127.0.0.1:8443
   
 $ nc 127.0.0.1 1212
```

