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 (opensource fork of Hashicorp Vault), EJBCA or Dogtag PKI 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
$ 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
:
$ 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.
$ 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):
$ 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:
$ 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:
$ 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
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
):
$ 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
You can use openssl
to test connecting with the client certificate to the wstunnel server:
$ 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:
$ 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):
$ curl -vvv --cacert ./certs/ca.cert.pem https://127.0.0.1:8443
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.
$ 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