diff --git a/docs/using_mtls.md b/docs/using_mtls.md new file mode 100644 index 0000000..bdb1e74 --- /dev/null +++ b/docs/using_mtls.md @@ -0,0 +1,280 @@ +# 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 . +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 +``` + + diff --git a/mTLS/certs/ca.cert.pem b/mTLS/certs/ca.cert.pem new file mode 100644 index 0000000..7e812c7 --- /dev/null +++ b/mTLS/certs/ca.cert.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- diff --git a/mTLS/certs/wstunnel-client-1.cert.pem b/mTLS/certs/wstunnel-client-1.cert.pem new file mode 100644 index 0000000..057d950 --- /dev/null +++ b/mTLS/certs/wstunnel-client-1.cert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFDTCCAvWgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCR0Ix +EDAOBgNVBAgMB0VuZ2xhbmQxHTAbBgNVBAoMFHdzdHVubmVsIGRldmVsb3BtZW50 +MSUwIwYDVQQDDBx3c3R1bm5lbCBEZXZlbG9wbWVudCBSb290IENBMB4XDTI0MDQx +OTA2NDAwMVoXDTI1MDQyOTA2NDAwMVowZjELMAkGA1UEBhMCR0IxEDAOBgNVBAgM +B0VuZ2xhbmQxHTAbBgNVBAoMFHdzdHVubmVsIGRldmVsb3BtZW50MSYwJAYDVQQD +DB13c3R1bm5lbCBEZXZlbG9wbWVudCBDbGllbnQgMTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMI87ijKk6jMWPcJtkcK0AKQ5rbrkLMACkCliB4v11gi +kMbqMIT8eQHEfKmTu5uuisutMNrPqZQXCXgoYbUk0IuRuzXtmuZSMAzA5X/xWAU4 +fpbXwS5vd21impE6UZkXGQunngD5WNEn4sm7t4g/ioCTqMdv+svBMTaWXUIwpgEO +KtMsaC9FVQxYe9zyJPN0QaSU6NMOM2LwQNzh51vtJyVT9lCixTQWM1Q5bTbQrVrz +MdRZHEX6ogEg/WPUTaO1y+InBAVn6DzcLwslLPV7s4/6l6hmNEkZWBqz77KFc5H1 +Ol7PRInYLt8BlACb1MIlE81RYLIW1THMN7G4eVRrgq8CAwEAAaOBxTCBwjAJBgNV +HRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAzBglghkgBhvhCAQ0EJhYkT3BlblNT +TCBHZW5lcmF0ZWQgQ2xpZW50IENlcnRpZmljYXRlMB0GA1UdDgQWBBS3UAwJmyci +WunqYPI4DSNTMjpBIjAfBgNVHSMEGDAWgBTtbfEPPuXC2O2AOHxRPWgCoo07wzAO +BgNVHQ8BAf8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMA0G +CSqGSIb3DQEBCwUAA4ICAQCyUuIIIylwAEJ0x4cvwuuV65MnsDiNktpqllsih/wo +bfutcvGy//FOMk/04YYJZbLAOfvNLakp3A+oEGOuYaMJst9e2zGXRI2AqPZzxxkY +P1GAfnxiLtjHltUMlpMrx+thGWqKYYAXim8TY3O3hjXkrpE8vOTG5vpQmMMmo2/y +Po++eiVbd/+cXDdMwbum31rexpN0JC9SBF9GmrHoKekPpQ+gGJe6HAVAEWV2UMId +MEtKzk8RrwySfn6oErNkOD+4DF9eJogl2cjlfl5y+DHeadAtYDcXS8lEvgJ6LwsE +ftDMhtY1FBrq5nCxip2Fk6uB08wypcHXC/+sXP71tCBuQiaHCW/GTaFlWQijfepq +yh/zx9narIxpTFCVfX1hiy6uYkehvWiBsds0LNwVzSUEC+wfxuwGlVWRtalf/K9e ++1CHraMP0VjsSO+U5CWKuNdPffq/V/v2nyKzkbakssZex3Xoy0sWZ/i0tl2AD+K8 +p9SRRuWShv2q3qZUfbKRgGnIlsCJNK3xUlak6HTPpL/woV3QfakO81E06x/Mlgvm +9PXA9t0yp+hyk0S9mgwAiL5/s6pm+A0WsSjJ6ohl2jsktvAYgFy7S/rFL7ZC7kN1 +TH+dlY/0Cj5yBlA0G0B2VEoW0nrnUGfA6xxnl1Tv096bhxCUKg9YC+/VDRgIVyX4 +uw== +-----END CERTIFICATE----- diff --git a/mTLS/certs/wstunnel-server.cert.pem b/mTLS/certs/wstunnel-server.cert.pem new file mode 100644 index 0000000..a326254 --- /dev/null +++ b/mTLS/certs/wstunnel-server.cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFhzCCA2+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCR0Ix +EDAOBgNVBAgMB0VuZ2xhbmQxHTAbBgNVBAoMFHdzdHVubmVsIGRldmVsb3BtZW50 +MSUwIwYDVQQDDBx3c3R1bm5lbCBEZXZlbG9wbWVudCBSb290IENBMB4XDTI0MDQx +OTA2MzkwMloXDTI1MDQyOTA2MzkwMlowZDELMAkGA1UEBhMCR0IxEDAOBgNVBAgM +B0VuZ2xhbmQxHTAbBgNVBAoMFHdzdHVubmVsIGRldmVsb3BtZW50MSQwIgYDVQQD +DBt3c3R1bm5lbCBEZXZlbG9wbWVudCBTZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCukQ8xc1LHj+NxSGK714s3jMBJdzuaPsUh5tVj5I+2SMNf +t6SmLxRm14meUChoiyIaZ5K3Nd8F8+PzF2CmdH6dTY+XzZv2b0Ap544qNJ2trHwi +J9AR8zYOoCnHnlc4p7NatQsSJ8HozhVuPSBA46xtTUsHUFSVZt0kfGBDDXDtsnU1 +e3BWqvjyndvqSF/TF1YX1CakuY+rNKKyVUOYHE/dncMdlBA+G09NiDYhrSybiadW +OU3D++oJmOFC32Mz2r/sMTqmHgb/NoNA83br5I8AUmhFcZTNE5pbjLgbukQrlA1s +noTCNCHK39YvuC/DGFONWiwQdMCITn3S7z9k4/ObAgMBAAGjggFAMIIBPDAJBgNV +HRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDAzBglghkgBhvhCAQ0EJhYkT3BlblNT +TCBHZW5lcmF0ZWQgU2VydmVyIENlcnRpZmljYXRlMB0GA1UdDgQWBBTevpkP7cSW +XnmhdtxQej4tBVMd1DCBogYDVR0jBIGaMIGXgBTtbfEPPuXC2O2AOHxRPWgCoo07 +w6FppGcwZTELMAkGA1UEBhMCR0IxEDAOBgNVBAgMB0VuZ2xhbmQxHTAbBgNVBAoM +FHdzdHVubmVsIGRldmVsb3BtZW50MSUwIwYDVQQDDBx3c3R1bm5lbCBEZXZlbG9w +bWVudCBSb290IENBghRNOEar2B6EMORpy15L+8cSdEUR2zAOBgNVHQ8BAf8EBAMC +BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggIBABztA4Vm +ROMZmtMmcjxV5hsNXs1iB50MBjjMvsLS2bVSfqRiENDnSrwHrGtvvcaX2sLrKtld +Xv+zgIMU7n4UxAIPiJa+X47+glyQZXpK7MNpzQgsCtNCUyI6flqIADm/RPCwVbbM +tQOW3rsoPj01/lQ1oiAndVjDe83DXcA25fbXTFY7qtDqSbNGo/WIyR9npPSGrOdb +vgAxvapJ41XdmJNXcJ9Q/Pdi2EV8FFRUPxjq48NQMuoDwcyPmGZ/OfuHN2ln/+8Q +PBuzi/nEP1iA/kF3XWwEHPbrJDCmWXRBt5JDy41StlK3IP+1BJHfSq4+Z1lffhS+ +HmXDaGbejUc6AjC6P26GehwAwY9q2e/43Dgz9lsgatn+BujvnJAB5UfWDCXWIMqo ++TndKEPEdnNiNqdm015Tgx55bRtedzGAk9ezjgiopCp/CmyUfI4P88cKEcOUilFi +3GKjppaloUd66sgWHSNTKUqN17xkbfZMjEn90Bo+5fj1KlkeJBcesQadS9SHv9hA +isquaduRsrRikDwqX3pil1/zWS7cYIw0VomBk0QQ78u0UMwofs5Ra0VOTLJPRl+p +u1PUhwi76IKFkVsLaxaaAwE5wYm+VPQP+dScOSkJz6swEkDhe5A7Q61Lu3GxHM/Y +La4WMAlE3IDOA7Piao6jI/TU5Jg65SmS7gX2 +-----END CERTIFICATE----- diff --git a/mTLS/csr/wstunnel-client-1.csr.pem b/mTLS/csr/wstunnel-client-1.csr.pem new file mode 100644 index 0000000..641902e --- /dev/null +++ b/mTLS/csr/wstunnel-client-1.csr.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICqzCCAZMCAQAwZjELMAkGA1UEBhMCR0IxEDAOBgNVBAgMB0VuZ2xhbmQxHTAb +BgNVBAoMFHdzdHVubmVsIGRldmVsb3BtZW50MSYwJAYDVQQDDB13c3R1bm5lbCBE +ZXZlbG9wbWVudCBDbGllbnQgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMI87ijKk6jMWPcJtkcK0AKQ5rbrkLMACkCliB4v11gikMbqMIT8eQHEfKmT +u5uuisutMNrPqZQXCXgoYbUk0IuRuzXtmuZSMAzA5X/xWAU4fpbXwS5vd21impE6 +UZkXGQunngD5WNEn4sm7t4g/ioCTqMdv+svBMTaWXUIwpgEOKtMsaC9FVQxYe9zy +JPN0QaSU6NMOM2LwQNzh51vtJyVT9lCixTQWM1Q5bTbQrVrzMdRZHEX6ogEg/WPU +TaO1y+InBAVn6DzcLwslLPV7s4/6l6hmNEkZWBqz77KFc5H1Ol7PRInYLt8BlACb +1MIlE81RYLIW1THMN7G4eVRrgq8CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQAW +OnKLj8cAGS568iF30bcVZHXdM99pwtXXwu/KCNCEH2XzVQAq1LmT1mpxOui+HO4e +k8PcOqSfw14V4sDxgGuDugPysb68Dpml4zwa99MyZOr4PqJWsRLpWhXk4sRFTlQo +yk6EE8faRg3vN/ZCR4um869Vn7pgrCky/q6e/RSsFgjRX2tNEQkHYHaSM6adA3f4 +wzKj9gHKZcE1mVmTfzfGLnPi0Y4YzqA0Zbd9rtSHVg4SX6wvJrO+bPw8CQaBh4TK +26c11vB6S1snboi9AVN8rliYLSblZESX5txvTmjHiGV6vHnpBVceMJklgeLH2oP3 +6L9oLWpe8YewnB+d0ufb +-----END CERTIFICATE REQUEST----- diff --git a/mTLS/csr/wstunnel-server.csr.pem b/mTLS/csr/wstunnel-server.csr.pem new file mode 100644 index 0000000..ad3a57e --- /dev/null +++ b/mTLS/csr/wstunnel-server.csr.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICqTCCAZECAQAwZDELMAkGA1UEBhMCR0IxEDAOBgNVBAgMB0VuZ2xhbmQxHTAb +BgNVBAoMFHdzdHVubmVsIGRldmVsb3BtZW50MSQwIgYDVQQDDBt3c3R1bm5lbCBE +ZXZlbG9wbWVudCBTZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCukQ8xc1LHj+NxSGK714s3jMBJdzuaPsUh5tVj5I+2SMNft6SmLxRm14meUCho +iyIaZ5K3Nd8F8+PzF2CmdH6dTY+XzZv2b0Ap544qNJ2trHwiJ9AR8zYOoCnHnlc4 +p7NatQsSJ8HozhVuPSBA46xtTUsHUFSVZt0kfGBDDXDtsnU1e3BWqvjyndvqSF/T +F1YX1CakuY+rNKKyVUOYHE/dncMdlBA+G09NiDYhrSybiadWOU3D++oJmOFC32Mz +2r/sMTqmHgb/NoNA83br5I8AUmhFcZTNE5pbjLgbukQrlA1snoTCNCHK39YvuC/D +GFONWiwQdMCITn3S7z9k4/ObAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAdSKi +gdQSKSl9rXQH5FqpQgjHc9iOjQCf4JQ2cm/w+JyhS1aMtGFPaCcRc/D5rAc/3lkO +jL+i4fS3TSo+2bfO8geU+WUNnHU18QYf9CwENsSi9lCKV1d+mTSn2H1oA+ThJKjR +DnqzPA8AGUd9dM0dAAxPZL5A8w/IzzcK4LVEGEmiIzcVEPWjBPvSY4DQOyA2y4h/ +WL2nsG/2AD7CIFLI03bvSaTmnbQ2KSWTaSXaj5uFobJwajRLMcuCjshgdhD0C4aT ++vlzm1gc+VbSwsyKxbmWjCLagM+BBxySLJfq9GsoXRl9QCuyd+h94rrWMeXdleZU +fPs6uUYg2nqbyr7Vuw== +-----END CERTIFICATE REQUEST----- diff --git a/mTLS/index.txt b/mTLS/index.txt new file mode 100644 index 0000000..78a73b7 --- /dev/null +++ b/mTLS/index.txt @@ -0,0 +1,2 @@ +V 250429063902Z 1000 unknown /C=GB/ST=England/O=wstunnel development/CN=wstunnel Development Server +V 250429064001Z 1001 unknown /C=GB/ST=England/O=wstunnel development/CN=wstunnel Development Client 1 diff --git a/mTLS/index.txt.attr b/mTLS/index.txt.attr new file mode 100644 index 0000000..8f7e63a --- /dev/null +++ b/mTLS/index.txt.attr @@ -0,0 +1 @@ +unique_subject = yes diff --git a/mTLS/newcerts/1000.pem b/mTLS/newcerts/1000.pem new file mode 100644 index 0000000..a326254 --- /dev/null +++ b/mTLS/newcerts/1000.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFhzCCA2+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCR0Ix +EDAOBgNVBAgMB0VuZ2xhbmQxHTAbBgNVBAoMFHdzdHVubmVsIGRldmVsb3BtZW50 +MSUwIwYDVQQDDBx3c3R1bm5lbCBEZXZlbG9wbWVudCBSb290IENBMB4XDTI0MDQx +OTA2MzkwMloXDTI1MDQyOTA2MzkwMlowZDELMAkGA1UEBhMCR0IxEDAOBgNVBAgM +B0VuZ2xhbmQxHTAbBgNVBAoMFHdzdHVubmVsIGRldmVsb3BtZW50MSQwIgYDVQQD +DBt3c3R1bm5lbCBEZXZlbG9wbWVudCBTZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCukQ8xc1LHj+NxSGK714s3jMBJdzuaPsUh5tVj5I+2SMNf +t6SmLxRm14meUChoiyIaZ5K3Nd8F8+PzF2CmdH6dTY+XzZv2b0Ap544qNJ2trHwi +J9AR8zYOoCnHnlc4p7NatQsSJ8HozhVuPSBA46xtTUsHUFSVZt0kfGBDDXDtsnU1 +e3BWqvjyndvqSF/TF1YX1CakuY+rNKKyVUOYHE/dncMdlBA+G09NiDYhrSybiadW +OU3D++oJmOFC32Mz2r/sMTqmHgb/NoNA83br5I8AUmhFcZTNE5pbjLgbukQrlA1s +noTCNCHK39YvuC/DGFONWiwQdMCITn3S7z9k4/ObAgMBAAGjggFAMIIBPDAJBgNV +HRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDAzBglghkgBhvhCAQ0EJhYkT3BlblNT +TCBHZW5lcmF0ZWQgU2VydmVyIENlcnRpZmljYXRlMB0GA1UdDgQWBBTevpkP7cSW +XnmhdtxQej4tBVMd1DCBogYDVR0jBIGaMIGXgBTtbfEPPuXC2O2AOHxRPWgCoo07 +w6FppGcwZTELMAkGA1UEBhMCR0IxEDAOBgNVBAgMB0VuZ2xhbmQxHTAbBgNVBAoM +FHdzdHVubmVsIGRldmVsb3BtZW50MSUwIwYDVQQDDBx3c3R1bm5lbCBEZXZlbG9w +bWVudCBSb290IENBghRNOEar2B6EMORpy15L+8cSdEUR2zAOBgNVHQ8BAf8EBAMC +BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggIBABztA4Vm +ROMZmtMmcjxV5hsNXs1iB50MBjjMvsLS2bVSfqRiENDnSrwHrGtvvcaX2sLrKtld +Xv+zgIMU7n4UxAIPiJa+X47+glyQZXpK7MNpzQgsCtNCUyI6flqIADm/RPCwVbbM +tQOW3rsoPj01/lQ1oiAndVjDe83DXcA25fbXTFY7qtDqSbNGo/WIyR9npPSGrOdb +vgAxvapJ41XdmJNXcJ9Q/Pdi2EV8FFRUPxjq48NQMuoDwcyPmGZ/OfuHN2ln/+8Q +PBuzi/nEP1iA/kF3XWwEHPbrJDCmWXRBt5JDy41StlK3IP+1BJHfSq4+Z1lffhS+ +HmXDaGbejUc6AjC6P26GehwAwY9q2e/43Dgz9lsgatn+BujvnJAB5UfWDCXWIMqo ++TndKEPEdnNiNqdm015Tgx55bRtedzGAk9ezjgiopCp/CmyUfI4P88cKEcOUilFi +3GKjppaloUd66sgWHSNTKUqN17xkbfZMjEn90Bo+5fj1KlkeJBcesQadS9SHv9hA +isquaduRsrRikDwqX3pil1/zWS7cYIw0VomBk0QQ78u0UMwofs5Ra0VOTLJPRl+p +u1PUhwi76IKFkVsLaxaaAwE5wYm+VPQP+dScOSkJz6swEkDhe5A7Q61Lu3GxHM/Y +La4WMAlE3IDOA7Piao6jI/TU5Jg65SmS7gX2 +-----END CERTIFICATE----- diff --git a/mTLS/newcerts/1001.pem b/mTLS/newcerts/1001.pem new file mode 100644 index 0000000..057d950 --- /dev/null +++ b/mTLS/newcerts/1001.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFDTCCAvWgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCR0Ix +EDAOBgNVBAgMB0VuZ2xhbmQxHTAbBgNVBAoMFHdzdHVubmVsIGRldmVsb3BtZW50 +MSUwIwYDVQQDDBx3c3R1bm5lbCBEZXZlbG9wbWVudCBSb290IENBMB4XDTI0MDQx +OTA2NDAwMVoXDTI1MDQyOTA2NDAwMVowZjELMAkGA1UEBhMCR0IxEDAOBgNVBAgM +B0VuZ2xhbmQxHTAbBgNVBAoMFHdzdHVubmVsIGRldmVsb3BtZW50MSYwJAYDVQQD +DB13c3R1bm5lbCBEZXZlbG9wbWVudCBDbGllbnQgMTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMI87ijKk6jMWPcJtkcK0AKQ5rbrkLMACkCliB4v11gi +kMbqMIT8eQHEfKmTu5uuisutMNrPqZQXCXgoYbUk0IuRuzXtmuZSMAzA5X/xWAU4 +fpbXwS5vd21impE6UZkXGQunngD5WNEn4sm7t4g/ioCTqMdv+svBMTaWXUIwpgEO +KtMsaC9FVQxYe9zyJPN0QaSU6NMOM2LwQNzh51vtJyVT9lCixTQWM1Q5bTbQrVrz +MdRZHEX6ogEg/WPUTaO1y+InBAVn6DzcLwslLPV7s4/6l6hmNEkZWBqz77KFc5H1 +Ol7PRInYLt8BlACb1MIlE81RYLIW1THMN7G4eVRrgq8CAwEAAaOBxTCBwjAJBgNV +HRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAzBglghkgBhvhCAQ0EJhYkT3BlblNT +TCBHZW5lcmF0ZWQgQ2xpZW50IENlcnRpZmljYXRlMB0GA1UdDgQWBBS3UAwJmyci +WunqYPI4DSNTMjpBIjAfBgNVHSMEGDAWgBTtbfEPPuXC2O2AOHxRPWgCoo07wzAO +BgNVHQ8BAf8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMA0G +CSqGSIb3DQEBCwUAA4ICAQCyUuIIIylwAEJ0x4cvwuuV65MnsDiNktpqllsih/wo +bfutcvGy//FOMk/04YYJZbLAOfvNLakp3A+oEGOuYaMJst9e2zGXRI2AqPZzxxkY +P1GAfnxiLtjHltUMlpMrx+thGWqKYYAXim8TY3O3hjXkrpE8vOTG5vpQmMMmo2/y +Po++eiVbd/+cXDdMwbum31rexpN0JC9SBF9GmrHoKekPpQ+gGJe6HAVAEWV2UMId +MEtKzk8RrwySfn6oErNkOD+4DF9eJogl2cjlfl5y+DHeadAtYDcXS8lEvgJ6LwsE +ftDMhtY1FBrq5nCxip2Fk6uB08wypcHXC/+sXP71tCBuQiaHCW/GTaFlWQijfepq +yh/zx9narIxpTFCVfX1hiy6uYkehvWiBsds0LNwVzSUEC+wfxuwGlVWRtalf/K9e ++1CHraMP0VjsSO+U5CWKuNdPffq/V/v2nyKzkbakssZex3Xoy0sWZ/i0tl2AD+K8 +p9SRRuWShv2q3qZUfbKRgGnIlsCJNK3xUlak6HTPpL/woV3QfakO81E06x/Mlgvm +9PXA9t0yp+hyk0S9mgwAiL5/s6pm+A0WsSjJ6ohl2jsktvAYgFy7S/rFL7ZC7kN1 +TH+dlY/0Cj5yBlA0G0B2VEoW0nrnUGfA6xxnl1Tv096bhxCUKg9YC+/VDRgIVyX4 +uw== +-----END CERTIFICATE----- diff --git a/mTLS/openssl.cnf b/mTLS/openssl.cnf new file mode 100644 index 0000000..b6c30d5 --- /dev/null +++ b/mTLS/openssl.cnf @@ -0,0 +1,99 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +# Directory and file locations. +dir = /home/erebe/progs/wstunnel/certs/mTLS +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 = supplied +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 . +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 diff --git a/mTLS/private/ca.key.pem b/mTLS/private/ca.key.pem new file mode 100644 index 0000000..371370d --- /dev/null +++ b/mTLS/private/ca.key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +d9HqBI2AwjW8MYtrV4UaM2bM6n7aow== +-----END PRIVATE KEY----- diff --git a/mTLS/private/wstunnel-client-1.pem b/mTLS/private/wstunnel-client-1.pem new file mode 100644 index 0000000..4ac12d2 --- /dev/null +++ b/mTLS/private/wstunnel-client-1.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCPO4oypOozFj3 +CbZHCtACkOa265CzAApApYgeL9dYIpDG6jCE/HkBxHypk7ubrorLrTDaz6mUFwl4 +KGG1JNCLkbs17ZrmUjAMwOV/8VgFOH6W18Eub3dtYpqROlGZFxkLp54A+VjRJ+LJ +u7eIP4qAk6jHb/rLwTE2ll1CMKYBDirTLGgvRVUMWHvc8iTzdEGklOjTDjNi8EDc +4edb7SclU/ZQosU0FjNUOW020K1a8zHUWRxF+qIBIP1j1E2jtcviJwQFZ+g83C8L +JSz1e7OP+peoZjRJGVgas++yhXOR9Tpez0SJ2C7fAZQAm9TCJRPNUWCyFtUxzDex +uHlUa4KvAgMBAAECggEAPWP3nAHm6IdpqO6zY0HKG72DhhXu/nxJQURwQKY7SDpo +is4S9r07W7En+4rbVWm1qYk8MzRGMy2SyxzsQM35cdtmEbXe6uPYFvfSsXzspn3E +GNXpU01csEBlfPgzOREhU9su57zncvfJyJvhdpkqo9fHlP1SBZsyfD/LCvQIS7WL +IsjQeRYGeGI+T+HiJyRGqkkk0YWZ09OL8QCAwTyhT58TH+7HzoWRBCCHGQaIS/Wm +kMcPJsYLSNtwrj2qvG7dC4nZcl3ZHoRHVGnD4B+6y0esS8cHvu67FX09FviIrz6G +l7EnU/uWnJ8iKDwl7Jwqeu/mk06OecI6KlYokbbeZQKBgQDgC0yqdPyIU7TZtihq +cnet9yqm20yATSODIeOc2SPchMvF/EqLgBikSOBMTbl7OsZqfeTbPLbcKXbuJKa7 +QGu+5NJM6OKWW3h+aCwaB0YSQQnNtiqEOJoMPErm9Bj4jLEdVMP1J66i9ra+gxka +plL/cUsNxCZaUhieTHq70zlQWwKBgQDd8UvMtUdqLvAQ1oT8oX+C7hlGAdYjHXIH +PeBz+pWZWfNdH/lijWUEKBtynzmTJpvE52M6849MamUSFk7ypTe0NZJ/oPgxC0Nl +yAr49yZmByJ6RiKOJTexmykWVhSi6x++HmWE0oz6+2vRIQ+Wdj0VWmUP+LVsvMD0 +QjrkhIinPQKBgQCf7NDr+BfvRDkbEyEkYtM1NfKXKoEgMHACAeXUp1cm6RAAIogf +re9pDbA2J2EYKqtJhtYe/ObWny6K7VSq42BF4laPmclsZJzNNpUMe1a0XwKdecQ9 +n52u0DbzRxiwCtW+xywdyhapswxdT31S/ZjPSFK33+U0odd638LYYf1OcwKBgQDR +bsh7dLjeP0q0aOn3RyJ/V9UrlcIPQtL+eGpcpyMSIaqfvvNjB1BCmuJDyHLZI/6r +0Tl3QKyBjIixh7GaEUQ+XqtOmoR6K0m/OwT3qKlob+UeAx7Kid5DT8p21GYG0t8S +VbawnssAb85u+sat0geUJcfmSWhSIs/l7rWKPHKDdQKBgDqVTFHDncc3p4luCRgi +63cjz1fLSn+wiiICMsYTCJ9lHDwbXOKWAqyHDP0qTI8s3b13/LITOdj/Q0R33i0Y +38qJWBag5K2u7iyMQr/oxipuBhyhHU1mRXGXHl4/N3Oa5tkVhapz/e6PN0mMgTJQ +/x19K09p5hc4sLnrk6BCOr3q +-----END PRIVATE KEY----- diff --git a/mTLS/private/wstunnel-server.pem b/mTLS/private/wstunnel-server.pem new file mode 100644 index 0000000..da262c2 --- /dev/null +++ b/mTLS/private/wstunnel-server.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCukQ8xc1LHj+Nx +SGK714s3jMBJdzuaPsUh5tVj5I+2SMNft6SmLxRm14meUChoiyIaZ5K3Nd8F8+Pz +F2CmdH6dTY+XzZv2b0Ap544qNJ2trHwiJ9AR8zYOoCnHnlc4p7NatQsSJ8HozhVu +PSBA46xtTUsHUFSVZt0kfGBDDXDtsnU1e3BWqvjyndvqSF/TF1YX1CakuY+rNKKy +VUOYHE/dncMdlBA+G09NiDYhrSybiadWOU3D++oJmOFC32Mz2r/sMTqmHgb/NoNA +83br5I8AUmhFcZTNE5pbjLgbukQrlA1snoTCNCHK39YvuC/DGFONWiwQdMCITn3S +7z9k4/ObAgMBAAECggEAQgKcaiifrtLcQKQModdZz4Gr3Jv3r9X4mV8+Ze2x4k4V +gwZgfm7jGhh686CABzhFhxKPSjRWx1t4YR3/8DGxBy6jE9YuGbvr2Wy0N4V58oh8 +0DWZ2o/LazBpXBCmDshra+t16kGac7wqImt+3Mq7EwHdU0CvG2ewS/G0PObCQz9O +UCIitSoO/TCLBmVOVND9u9tR6VX+nmqv7lbs5s311g2OXWbFQQDaWj9ftx6nZ0w+ +k1MhXVYpKw1iM6fiaq5HB8AFF+H92bZyBGNxE3L9/peynzBnrwGb/n+w5cDSlmWI +NsIDMslAgAJktttNXanwwWPiZTUj9/RJ0aE7Md3QEQKBgQDbsYumFBVuV3khr4pL +fHRnAcDHRIGyWG5H/VZbNzeEZ1VVDGP1Xym0K3QtNKfM00gBIPIqnFf/gUuReY67 +H9HKg8i8kjSFH0G1HtR/dPVPqSc/DVeoEmy9hwgRH01ZYebL/fT9kRhn0bBLtoVh +bYUSFDYxkJIT9uhZDysDbKR8JQKBgQDLalrSWG9G+PIGdKuPeMDdZHMNWSdGYECe +Fv+oyhDQoH9228UJihIcbwT+ocsl/w0CK8Gvri8jxOnHW0yAzCaxCwi5VQWRCt1G +rTs8O6jtDi3wBc3IdLl3ktfA8OEbOShiQh3YYMRCPBg/21A5SCyHdMEEwaF/842A +6OxeUPPEvwKBgBwVtF6EzsCOWiPeRvWjcVYBuV0/+ryL5X06e6Gpi2VXuGbo8JZb +lf88Vtu4kYLzt469YXflCLLXGov8WCy/wpf7BNxmbGRgPIwk5tFsaDfIzgWXdQ89 +W71W18cok0DL7S9CxeDsfYw4GCt1p9NupsZK4yqu6p22wLkx4TPM3bIpAoGAO+nE +fGYNyIK0jpA4o9Z2P/9BH/Jdbg4Vmjq97JIvp7NON8z9WRTwxq0wdGtlMXjQ9Q28 +S6lrOwbZsJ1EiD8ZOlY8qJHRROpFSHbnlpMf60qc3zBmbx9qLTz0DWElfGY2bdJ5 +hezigXu/zLclBuoqK2+JFoSNs+khiZGRZSpE0nMCgYEAzs0oC0doQhulvZz1nPzb +AgthFZS4gIq8GsTW0GfXRYPRHEtHtDoYROEYM2aX7FcFRAKO/9iinW19rtMj6cpS +37tBMiEAGqInmPYGVfUalZpZKq14f85lVOg9HjTf/MxPs7RHgKj4WD1r4aeAhxPz +d1+I0rsmh2lceuhHer/sS4M= +-----END PRIVATE KEY----- diff --git a/mTLS/serial b/mTLS/serial new file mode 100644 index 0000000..7d802a3 --- /dev/null +++ b/mTLS/serial @@ -0,0 +1 @@ +1002 diff --git a/src/main.rs b/src/main.rs index 5bdf0d0..79abcc1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ use hyper::header::HOST; use hyper::http::{HeaderName, HeaderValue}; use log::{debug, warn}; use once_cell::sync::Lazy; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; use std::fmt::{Debug, Formatter}; @@ -40,6 +40,7 @@ use tokio_rustls::TlsConnector; use tracing::{error, info}; use crate::dns::DnsResolver; +use crate::tunnel::tls_reloader::TlsReloader; use crate::tunnel::{to_host_port, RemoteAddr, TransportAddr, TransportScheme}; use crate::udp::MyUdpSocket; use tracing_subscriber::filter::Directive; @@ -60,7 +61,7 @@ struct Wstunnel { /// *WARNING* The flag does nothing, you need to set the env variable *WARNING* /// Control the number of threads that will be used. - /// By default it is equal the number of cpus + /// By default, it is equal the number of cpus #[arg( long, global = true, @@ -132,7 +133,7 @@ struct Client { #[arg(short = 'c', long, value_name = "INT", default_value = "0", verbatim_doc_comment)] connection_min_idle: u32, - /// Domain name that will be use as SNI during TLS handshake + /// Domain name that will be used as SNI during TLS handshake /// Warning: If you are behind a CDN (i.e: Cloudflare) you must set this domain also in the http HOST header. /// or it will be flagged as fishy and your request rejected #[arg(long, value_name = "DOMAIN_NAME", value_parser = parse_sni_override, verbatim_doc_comment)] @@ -144,7 +145,7 @@ struct Client { tls_sni_disable: bool, /// Enable TLS certificate verification. - /// Disabled by default. The client will happily connect to any server with self signed certificate. + /// Disabled by default. The client will happily connect to any server with self-signed certificate. #[arg(long, verbatim_doc_comment)] tls_verify_certificate: bool, @@ -192,7 +193,7 @@ struct Client { websocket_ping_frequency_sec: Option, /// Enable the masking of websocket frames. Default is false - /// Enable this option only if you use unsecure (non TLS) websocket server and you see some issues. Otherwise, it is just overhead. + /// Enable this option only if you use unsecure (non TLS) websocket server, and you see some issues. Otherwise, it is just overhead. #[arg(long, default_value = "false", verbatim_doc_comment)] websocket_mask_frame: bool, @@ -203,7 +204,7 @@ struct Client { /// Send custom headers in the upgrade request reading them from a file. /// It overrides http_headers specified from command line. - /// File is read everytime and file format must contains lines with `HEADER_NAME: HEADER_VALUE` + /// File is read everytime and file format must contain lines with `HEADER_NAME: HEADER_VALUE` #[arg(long, value_name = "FILE_PATH", verbatim_doc_comment)] http_headers_file: Option, @@ -220,6 +221,17 @@ struct Client { /// The only way to make it works with http2 is to have wstunnel directly exposed to the internet without any reverse proxy in front of it #[arg(value_name = "ws[s]|http[s]://wstunnel.server.com[:port]", value_parser = parse_server_url, verbatim_doc_comment)] remote_addr: Url, + + /// [Optional] Certificate (pem) to present to the server when connecting over TLS (HTTPS). + /// Used when the server requires clients to authenticate themselves with a certificate (i.e. mTLS). + /// The certificate will be automatically reloaded if it changes + #[arg(long, value_name = "FILE_PATH", verbatim_doc_comment)] + tls_certificate: Option, + + /// [Optional] The private key for the corresponding certificate used with mTLS. + /// The certificate will be automatically reloaded if it changes + #[arg(long, value_name = "FILE_PATH", verbatim_doc_comment)] + tls_private_key: Option, } #[derive(clap::Args, Debug)] @@ -241,7 +253,7 @@ struct Server { websocket_ping_frequency_sec: Option, /// Enable the masking of websocket frames. Default is false - /// Enable this option only if you use unsecure (non TLS) websocket server and you see some issues. Otherwise, it is just overhead. + /// Enable this option only if you use unsecure (non TLS) websocket server, and you see some issues. Otherwise, it is just overhead. #[arg(long, default_value = "false", verbatim_doc_comment)] websocket_mask_frame: bool, @@ -264,7 +276,7 @@ struct Server { dns_resolver: Option>, /// Server will only accept connection from if this specific path prefix is used during websocket upgrade. - /// Useful if you specify in the client a custom path prefix and you want the server to only allow this one. + /// Useful if you specify in the client a custom path prefix, and you want the server to only allow this one. /// The path prefix act as a secret to authenticate clients /// Disabled by default. Accept all path prefix. Can be specified multiple time #[arg( @@ -275,7 +287,7 @@ struct Server { )] restrict_http_upgrade_path_prefix: Option>, - /// [Optional] Use custom certificate (pem) instead of the default embedded self signed certificate. + /// [Optional] Use custom certificate (pem) instead of the default embedded self-signed certificate. /// The certificate will be automatically reloaded if it changes #[arg(long, value_name = "FILE_PATH", verbatim_doc_comment)] tls_certificate: Option, @@ -284,6 +296,12 @@ struct Server { /// The private key will be automatically reloaded if it changes #[arg(long, value_name = "FILE_PATH", verbatim_doc_comment)] tls_private_key: Option, + + /// [Optional] Enables mTLS (client authentication with certificate). Argument must be PEM file + /// containing one or more certificates of CA's of which the certificate of clients needs to be signed with. + /// The ca will be automatically reloaded if it changes + #[arg(long, value_name = "FILE_PATH", verbatim_doc_comment)] + tls_client_ca_certs: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -565,15 +583,25 @@ pub struct TlsClientConfig { pub tls_sni_disabled: bool, pub tls_sni_override: Option, pub tls_verify_certificate: bool, - pub tls_connector: TlsConnector, + tls_connector: Arc>, + pub tls_certificate_path: Option, + pub tls_key_path: Option, +} + +impl TlsClientConfig { + pub fn tls_connector(&self) -> TlsConnector { + self.tls_connector.read().clone() + } } #[derive(Debug)] pub struct TlsServerConfig { pub tls_certificate: Mutex>, pub tls_key: Mutex, + pub tls_client_ca_certificates: Option>>, pub tls_certificate_path: Option, pub tls_key_path: Option, + pub tls_client_ca_certs_path: Option, } pub struct WsServerConfig { @@ -599,6 +627,14 @@ impl Debug for WsServerConfig { .field("timeout_connect", &self.timeout_connect) .field("websocket_mask_frame", &self.websocket_mask_frame) .field("tls", &self.tls.is_some()) + .field( + "mTLS", + &self + .tls + .as_ref() + .map(|x| x.tls_client_ca_certificates.is_some()) + .unwrap_or(false), + ) .finish() } } @@ -617,6 +653,7 @@ pub struct WsClientConfig { pub websocket_mask_frame: bool, pub http_proxy: Option, cnx_pool: Option>, + tls_reloader: Option>, pub dns_resolver: DnsResolver, } @@ -682,30 +719,54 @@ async fn main() { match args.commands { Commands::Client(args) => { - let tls = match TransportScheme::from_str(args.remote_addr.scheme()).expect("invalid scheme in server url") + let (tls_certificate, tls_key) = if let (Some(cert), Some(key)) = + (args.tls_certificate.as_ref(), args.tls_private_key.as_ref()) { + let tls_certificate = + tls::load_certificates_from_pem(cert).expect("Cannot load client TLS certificate (mTLS)"); + let tls_key = tls::load_private_key_from_file(key).expect("Cannot load client TLS private key (mTLS)"); + (Some(tls_certificate), Some(tls_key)) + } else { + (None, None) + }; + + let transport_scheme = + TransportScheme::from_str(args.remote_addr.scheme()).expect("invalid scheme in server url"); + let tls = match transport_scheme { TransportScheme::Ws | TransportScheme::Http => None, TransportScheme::Wss => Some(TlsClientConfig { - tls_connector: tls::tls_connector( - args.tls_verify_certificate, - Some(vec![b"http/1.1".to_vec()]), - !args.tls_sni_disable, - ) - .expect("Cannot create tls connector"), + tls_connector: Arc::new(RwLock::new( + tls::tls_connector( + args.tls_verify_certificate, + transport_scheme.alpn_protocols(), + !args.tls_sni_disable, + tls_certificate, + tls_key, + ) + .expect("Cannot create tls connector"), + )), tls_sni_override: args.tls_sni_override, tls_verify_certificate: args.tls_verify_certificate, tls_sni_disabled: args.tls_sni_disable, + tls_certificate_path: args.tls_certificate.clone(), + tls_key_path: args.tls_private_key.clone(), }), TransportScheme::Https => Some(TlsClientConfig { - tls_connector: tls::tls_connector( - args.tls_verify_certificate, - Some(vec![b"h2".to_vec()]), - !args.tls_sni_disable, - ) - .expect("Cannot create tls connector"), + tls_connector: Arc::new(RwLock::new( + tls::tls_connector( + args.tls_verify_certificate, + transport_scheme.alpn_protocols(), + !args.tls_sni_disable, + tls_certificate, + tls_key, + ) + .expect("Cannot create tls connector"), + )), tls_sni_override: args.tls_sni_override, tls_verify_certificate: args.tls_verify_certificate, tls_sni_disabled: args.tls_sni_disable, + tls_certificate_path: args.tls_certificate.clone(), + tls_key_path: args.tls_private_key.clone(), }), }; @@ -761,6 +822,7 @@ async fn main() { None }, cnx_pool: None, + tls_reloader: None, dns_resolver: if let Ok(resolver) = hickory_resolver::AsyncResolver::tokio_from_system_conf() { DnsResolver::TrustDns(resolver) } else { @@ -769,6 +831,9 @@ async fn main() { }, }; + let tls_reloader = + TlsReloader::new_for_client(Arc::new(client_config.clone())).expect("Cannot create tls reloader"); + client_config.tls_reloader = Some(Arc::new(tls_reloader)); let pool = bb8::Pool::builder() .max_size(1000) .min_idle(Some(args.connection_min_idle)) @@ -1120,11 +1185,20 @@ async fn main() { embedded_certificate::TLS_PRIVATE_KEY.clone() }; + let tls_client_ca_certificates = args.tls_client_ca_certs.as_ref().map(|tls_client_ca| { + Mutex::new( + tls::load_certificates_from_pem(tls_client_ca) + .expect("Cannot load client CA certificate (mTLS)"), + ) + }); + Some(TlsServerConfig { tls_certificate: Mutex::new(tls_certificate), tls_key: Mutex::new(tls_key), + tls_client_ca_certificates, tls_certificate_path: args.tls_certificate, tls_key_path: args.tls_private_key, + tls_client_ca_certs_path: args.tls_client_ca_certs, }) } else { None diff --git a/src/tls.rs b/src/tls.rs index 54fd304..2c99fd7 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -12,6 +12,7 @@ use tokio_rustls::client::TlsStream; use tokio_rustls::rustls::client::{ServerCertVerified, ServerCertVerifier}; use crate::tunnel::TransportAddr; +use tokio_rustls::rustls::server::{AllowAnyAuthenticatedClient, NoClientAuth}; use tokio_rustls::rustls::{Certificate, ClientConfig, KeyLogFile, PrivateKey, ServerName}; use tokio_rustls::{rustls, TlsAcceptor, TlsConnector}; use tracing::info; @@ -65,8 +66,10 @@ pub fn load_private_key_from_file(path: &Path) -> anyhow::Result { pub fn tls_connector( tls_verify_certificate: bool, - alpn_protocols: Option>>, + alpn_protocols: Vec>, enable_sni: bool, + tls_client_certificate: Option>, + tls_client_key: Option, ) -> anyhow::Result { let mut root_store = rustls::RootCertStore::empty(); @@ -79,10 +82,16 @@ pub fn tls_connector( } } - let mut config = ClientConfig::builder() + let config_builder = ClientConfig::builder() .with_safe_defaults() - .with_root_certificates(root_store) - .with_no_client_auth(); + .with_root_certificates(root_store); + + let mut config = match (tls_client_certificate, tls_client_key) { + (Some(tls_client_certificate), Some(tls_client_key)) => config_builder + .with_client_auth_cert(tls_client_certificate, tls_client_key) + .with_context(|| "Error setting up mTLS")?, + _ => config_builder.with_no_client_auth(), + }; config.enable_sni = enable_sni; config.key_log = Arc::new(KeyLogFile::new()); @@ -92,17 +101,28 @@ pub fn tls_connector( config.dangerous().set_certificate_verifier(Arc::new(NullVerifier)); } - if let Some(alpn_protocols) = alpn_protocols { - config.alpn_protocols = alpn_protocols; - } + config.alpn_protocols = alpn_protocols; let tls_connector = TlsConnector::from(Arc::new(config)); Ok(tls_connector) } pub fn tls_acceptor(tls_cfg: &TlsServerConfig, alpn_protocols: Option>>) -> anyhow::Result { + let client_cert_verifier = if let Some(tls_client_ca_certificates) = &tls_cfg.tls_client_ca_certificates { + let mut root_store = rustls::RootCertStore::empty(); + for tls_client_ca_certificate in tls_client_ca_certificates.lock().iter() { + root_store + .add(tls_client_ca_certificate) + .with_context(|| "Failed to add mTLS client CA certificate")?; + } + + Arc::new(AllowAnyAuthenticatedClient::new(root_store)) + } else { + NoClientAuth::boxed() + }; + let mut config = rustls::ServerConfig::builder() .with_safe_defaults() - .with_no_client_auth() + .with_client_cert_verifier(client_cert_verifier) .with_single_cert(tls_cfg.tls_certificate.lock().clone(), tls_cfg.tls_key.lock().clone()) .with_context(|| "invalid tls certificate or private key")?; @@ -116,8 +136,8 @@ pub fn tls_acceptor(tls_cfg: &TlsServerConfig, alpn_protocols: Option anyhow::Result> { let sni = client_cfg.tls_server_name(); let (tls_connector, sni_disabled) = match &client_cfg.remote_addr { - TransportAddr::Wss { tls, .. } => (&tls.tls_connector, tls.tls_sni_disabled), - TransportAddr::Https { tls, .. } => (&tls.tls_connector, tls.tls_sni_disabled), + TransportAddr::Wss { tls, .. } => (tls.tls_connector(), tls.tls_sni_disabled), + TransportAddr::Https { tls, .. } => (tls.tls_connector(), tls.tls_sni_disabled), TransportAddr::Http { .. } | TransportAddr::Ws { .. } => { return Err(anyhow!("Transport does not support TLS: {}", client_cfg.remote_addr.scheme())) } diff --git a/src/tunnel/mod.rs b/src/tunnel/mod.rs index 2564476..3192615 100644 --- a/src/tunnel/mod.rs +++ b/src/tunnel/mod.rs @@ -1,6 +1,6 @@ pub mod client; pub mod server; -mod tls_reloader; +pub mod tls_reloader; mod transport; use crate::{tcp, tls, LocalProtocol, TlsClientConfig, WsClientConfig}; @@ -104,6 +104,15 @@ impl TransportScheme { TransportScheme::Https => "https", } } + + pub fn alpn_protocols(&self) -> Vec> { + match self { + TransportScheme::Ws => vec![], + TransportScheme::Wss => vec![b"http/1.1".to_vec()], + TransportScheme::Http => vec![], + TransportScheme::Https => vec![b"h2".to_vec()], + } + } } impl FromStr for TransportScheme { type Err = (); diff --git a/src/tunnel/server.rs b/src/tunnel/server.rs index 158c46a..fe56ddd 100644 --- a/src/tunnel/server.rs +++ b/src/tunnel/server.rs @@ -597,7 +597,7 @@ pub async fn run_server(server_config: Arc) -> anyhow::Result<() let mut tls_context = if let Some(tls_config) = &server_config.tls { let tls_context = TlsContext { tls_acceptor: Arc::new(tls::tls_acceptor(tls_config, Some(vec![b"h2".to_vec(), b"http/1.1".to_vec()]))?), - tls_reloader: TlsReloader::new(server_config.clone())?, + tls_reloader: TlsReloader::new_for_server(server_config.clone())?, tls_config, }; Some(tls_context) diff --git a/src/tunnel/tls_reloader.rs b/src/tunnel/tls_reloader.rs index 37c9581..ffc6c67 100644 --- a/src/tunnel/tls_reloader.rs +++ b/src/tunnel/tls_reloader.rs @@ -1,4 +1,5 @@ -use crate::{tls, WsServerConfig}; +use crate::tunnel::tls_reloader::TlsReloaderState::{Client, Server}; +use crate::{tls, WsClientConfig, WsServerConfig}; use anyhow::Context; use log::trace; use notify::{EventKind, RecommendedWatcher, Watcher}; @@ -10,41 +11,108 @@ use std::thread; use std::time::Duration; use tracing::{error, info, warn}; -struct TlsReloaderState { +struct TlsReloaderServerState { fs_watcher: Mutex, tls_reload_certificate: AtomicBool, server_config: Arc, cert_path: PathBuf, key_path: PathBuf, + client_ca_path: Option, } + +struct TlsReloaderClientState { + fs_watcher: Mutex, + tls_reload_certificate: AtomicBool, + client_config: Arc, + cert_path: PathBuf, + key_path: PathBuf, +} + +enum TlsReloaderState { + Empty, + Server(Arc), + Client(Arc), +} + +impl TlsReloaderState { + fn fs_watcher(&self) -> &Mutex { + match self { + TlsReloaderState::Empty => unreachable!(), + Server(this) => &this.fs_watcher, + Client(this) => &this.fs_watcher, + } + } +} + pub struct TlsReloader { - state: Option>, + state: TlsReloaderState, } impl TlsReloader { - pub fn new(server_config: Arc) -> anyhow::Result { + pub fn new_for_server(server_config: Arc) -> anyhow::Result { // If there is no custom certificate and private key, there is nothing to watch - let Some((Some(cert_path), Some(key_path))) = server_config + let Some((Some(cert_path), Some(key_path), client_ca_certs)) = server_config .tls .as_ref() - .map(|t| (&t.tls_certificate_path, &t.tls_key_path)) + .map(|t| (&t.tls_certificate_path, &t.tls_key_path, &t.tls_client_ca_certs_path)) else { - return Ok(Self { state: None }); + return Ok(Self { + state: TlsReloaderState::Empty, + }); }; - let this = Arc::new(TlsReloaderState { + let this = Arc::new(TlsReloaderServerState { fs_watcher: Mutex::new(notify::recommended_watcher(|_| {})?), tls_reload_certificate: AtomicBool::new(false), cert_path: cert_path.to_path_buf(), key_path: key_path.to_path_buf(), + client_ca_path: client_ca_certs.as_ref().map(|x| x.to_path_buf()), server_config, }); - info!("Starting to watch tls certificate and private key for changes to reload them"); + info!("Starting to watch tls certificates and private key for changes to reload them"); let mut watcher = notify::recommended_watcher({ - let this = this.clone(); + let this = Server(this.clone()); - move |event: notify::Result| Self::handle_fs_event(&this, event) + move |event: notify::Result| Self::handle_server_fs_event(&this, event) + }) + .with_context(|| "Cannot create tls certificate watcher")?; + + watcher.watch(&this.cert_path, notify::RecursiveMode::NonRecursive)?; + watcher.watch(&this.key_path, notify::RecursiveMode::NonRecursive)?; + if let Some(client_ca_path) = &this.client_ca_path { + watcher.watch(client_ca_path, notify::RecursiveMode::NonRecursive)?; + } + *this.fs_watcher.lock() = watcher; + + Ok(Self { state: Server(this) }) + } + + pub fn new_for_client(client_config: Arc) -> anyhow::Result { + // If there is no custom certificate and private key, there is nothing to watch + let Some((Some(cert_path), Some(key_path))) = client_config + .remote_addr + .tls() + .map(|t| (&t.tls_certificate_path, &t.tls_key_path)) + else { + return Ok(Self { + state: TlsReloaderState::Empty, + }); + }; + + let this = Arc::new(TlsReloaderClientState { + fs_watcher: Mutex::new(notify::recommended_watcher(|_| {})?), + tls_reload_certificate: AtomicBool::new(false), + cert_path: cert_path.to_path_buf(), + key_path: key_path.to_path_buf(), + client_config, + }); + + info!("Starting to watch tls certificates and private key for changes to reload them"); + let mut watcher = notify::recommended_watcher({ + let this = Client(this.clone()); + + move |event: notify::Result| Self::handle_client_fs_event(&this, event) }) .with_context(|| "Cannot create tls certificate watcher")?; @@ -52,24 +120,25 @@ impl TlsReloader { watcher.watch(&this.key_path, notify::RecursiveMode::NonRecursive)?; *this.fs_watcher.lock() = watcher; - Ok(Self { state: Some(this) }) + Ok(Self { state: Client(this) }) } #[inline] pub fn should_reload_certificate(&self) -> bool { match &self.state { - None => false, - Some(this) => this.tls_reload_certificate.swap(false, Ordering::Relaxed), + TlsReloaderState::Empty => false, + Server(this) => this.tls_reload_certificate.swap(false, Ordering::Relaxed), + Client(this) => this.tls_reload_certificate.swap(false, Ordering::Relaxed), } } - fn try_rewatch_certificate(this: Arc, path: PathBuf) { + fn try_rewatch_certificate(this: TlsReloaderState, path: PathBuf) { thread::spawn(move || { while !path.exists() { warn!("TLS file {:?} does not exist anymore, waiting for it to be created", path); thread::sleep(Duration::from_secs(10)); } - let mut watcher = this.fs_watcher.lock(); + let mut watcher = this.fs_watcher().lock(); let _ = watcher.unwatch(&path); let Ok(_) = watcher .watch(&path, notify::RecursiveMode::NonRecursive) @@ -88,11 +157,21 @@ impl TlsReloader { paths: vec![path], attrs: Default::default(), }; - Self::handle_fs_event(&this, Ok(event)); + + match &this { + Server(_) => Self::handle_server_fs_event(&this, Ok(event)), + Client(_) => Self::handle_client_fs_event(&this, Ok(event)), + TlsReloaderState::Empty => {} + } }); } - fn handle_fs_event(this: &Arc, event: notify::Result) { + fn handle_server_fs_event(this: &TlsReloaderState, event: notify::Result) { + let this = match this { + TlsReloaderState::Empty | Client(_) => return, + Server(st) => st, + }; + let event = match event { Ok(event) => event, Err(err) => { @@ -115,12 +194,12 @@ impl TlsReloader { } Err(err) => { warn!("Error while loading TLS certificate {:?}", err); - Self::try_rewatch_certificate(this.clone(), path.to_path_buf()); + Self::try_rewatch_certificate(Server(this.clone()), path.to_path_buf()); } }, EventKind::Remove(_) => { warn!("TLS certificate file has been removed, trying to re-set a watch for it"); - Self::try_rewatch_certificate(this.clone(), path.to_path_buf()); + Self::try_rewatch_certificate(Server(this.clone()), path.to_path_buf()); } EventKind::Access(_) | EventKind::Other | EventKind::Any => { trace!("Ignoring event {:?}", event); @@ -137,12 +216,142 @@ impl TlsReloader { } Err(err) => { warn!("Error while loading TLS private key {:?}", err); - Self::try_rewatch_certificate(this.clone(), path.to_path_buf()); + Self::try_rewatch_certificate(Server(this.clone()), path.to_path_buf()); } }, EventKind::Remove(_) => { warn!("TLS private key file has been removed, trying to re-set a watch for it"); - Self::try_rewatch_certificate(this.clone(), path.to_path_buf()); + Self::try_rewatch_certificate(Server(this.clone()), path.to_path_buf()); + } + EventKind::Access(_) | EventKind::Other | EventKind::Any => { + trace!("Ignoring event {:?}", event); + } + } + } + + if let Some(client_ca_path) = &this.client_ca_path { + if let Some(path) = event.paths.iter().find(|p| p.ends_with(client_ca_path)) { + match event.kind { + EventKind::Create(_) | EventKind::Modify(_) => { + match tls::load_certificates_from_pem(client_ca_path) { + Ok(tls_certs) => { + if let Some(client_certs) = &tls.tls_client_ca_certificates { + *client_certs.lock() = tls_certs; + this.tls_reload_certificate.store(true, Ordering::Relaxed); + } + } + Err(err) => { + warn!("Error while loading TLS client certificate {:?}", err); + Self::try_rewatch_certificate(Server(this.clone()), path.to_path_buf()); + } + } + } + EventKind::Remove(_) => { + warn!("TLS client certificate has been removed, trying to re-set a watch for it"); + Self::try_rewatch_certificate(Server(this.clone()), path.to_path_buf()); + } + EventKind::Access(_) | EventKind::Other | EventKind::Any => { + trace!("Ignoring event {:?}", event); + } + } + } + } + } + + fn handle_client_fs_event(this: &TlsReloaderState, event: notify::Result) { + let this = match this { + TlsReloaderState::Empty | Server(_) => return, + Client(st) => st, + }; + + let event = match event { + Ok(event) => event, + Err(err) => { + error!("Error while watching tls certificate and private key for changes {:?}", err); + return; + } + }; + + if event.kind.is_access() { + return; + } + + let tls = this.client_config.remote_addr.tls().unwrap(); + if let Some(path) = event.paths.iter().find(|p| p.ends_with(&this.cert_path)) { + match event.kind { + EventKind::Create(_) | EventKind::Modify(_) => match ( + tls::load_certificates_from_pem(&this.cert_path), + tls::load_private_key_from_file(&this.key_path), + ) { + (Ok(tls_certs), Ok(tls_key)) => { + let tls_connector = tls::tls_connector( + tls.tls_verify_certificate, + this.client_config.remote_addr.scheme().alpn_protocols(), + !tls.tls_sni_disabled, + Some(tls_certs), + Some(tls_key), + ); + let tls_connector = match tls_connector { + Ok(cn) => cn, + Err(err) => { + error!("Error while creating TLS connector {:?}", err); + return; + } + }; + *tls.tls_connector.write() = tls_connector; + this.tls_reload_certificate.store(true, Ordering::Relaxed); + } + (Err(err), _) | (_, Err(err)) => { + warn!("Error while loading TLS certificate {:?}", err); + Self::try_rewatch_certificate(Client(this.clone()), path.to_path_buf()); + } + }, + EventKind::Remove(_) => { + warn!("TLS certificate file has been removed, trying to re-set a watch for it"); + Self::try_rewatch_certificate(Client(this.clone()), path.to_path_buf()); + } + EventKind::Access(_) | EventKind::Other | EventKind::Any => { + trace!("Ignoring event {:?}", event); + } + } + } + + if let Some(path) = event + .paths + .iter() + .find(|p| p.ends_with(tls.tls_key_path.as_ref().unwrap())) + { + match event.kind { + EventKind::Create(_) | EventKind::Modify(_) => match ( + tls::load_certificates_from_pem(&this.cert_path), + tls::load_private_key_from_file(&this.key_path), + ) { + (Ok(tls_certs), Ok(tls_key)) => { + let tls_connector = tls::tls_connector( + tls.tls_verify_certificate, + this.client_config.remote_addr.scheme().alpn_protocols(), + !tls.tls_sni_disabled, + Some(tls_certs), + Some(tls_key), + ); + let tls_connector = match tls_connector { + Ok(cn) => cn, + Err(err) => { + error!("Error while creating TLS connector {:?}", err); + return; + } + }; + *tls.tls_connector.write() = tls_connector; + this.tls_reload_certificate.store(true, Ordering::Relaxed); + } + (Err(err), _) | (_, Err(err)) => { + warn!("Error while loading TLS private key {:?}", err); + Self::try_rewatch_certificate(Client(this.clone()), path.to_path_buf()); + } + }, + EventKind::Remove(_) => { + warn!("TLS private key file has been removed, trying to re-set a watch for it"); + Self::try_rewatch_certificate(Client(this.clone()), path.to_path_buf()); } EventKind::Access(_) | EventKind::Other | EventKind::Any => { trace!("Ignoring event {:?}", event);