Skip to content

Commit

Permalink
Improve TLS connection resilience
Browse files Browse the repository at this point in the history
Until now, it was possible to procure MitM attack by acting
as malicious fake server.
This commit removes that possibility by never trusting pre-tls
addresses provided by the NATS server.

For now it has to be based of tokio-rustls fork, as ruslts support
for IP addresses is not yet released.

This points tokio-rustls to rustls main branch. No custom code added.

Signed-off-by: Tomasz Pietrek <tomasz@nats.io>
  • Loading branch information
Jarema committed Mar 20, 2023
1 parent f335dd1 commit 817a7b9
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 30 deletions.
4 changes: 2 additions & 2 deletions async-nats/Cargo.toml
Expand Up @@ -28,12 +28,12 @@ http = "0.2.9"
tokio = { version = "1.25.0", features = ["macros", "rt", "fs", "net", "sync", "time", "io-util"] }
itoa = "1"
url = "2"
tokio-rustls = "0.23"
tokio-rustls = { git = "https://github.com/Jarema/tls", rev = "935b459"}
rustls-pemfile = "1.0.2"
nuid = "0.3.2"
serde_nanos = "0.1.3"
time = { version = "0.3.20", features = ["parsing", "formatting", "serde", "serde-well-known"] }
rustls-native-certs = "0.6.2"
rustls-native-certs = "0.6"
tracing = "0.1"
thiserror = "1.0"
base64 = "0.13"
Expand Down
18 changes: 2 additions & 16 deletions async-nats/src/connector.rs
Expand Up @@ -300,22 +300,8 @@ impl Connector {
})
.map_err(|err| ConnectError::with_source(crate::ConnectErrorKind::Tls, err))?;

// Use the server-advertised hostname to validate if given as a hostname, not an IP address
let domain = if let Ok(server_hostname @ rustls::ServerName::DnsName(_)) =
rustls::ServerName::try_from(info.host.as_str())
{
server_hostname
} else if let Ok(tls_hostname @ rustls::ServerName::DnsName(_)) =
rustls::ServerName::try_from(tls_host)
{
tls_hostname
} else {
return Err(io::Error::new(
ErrorKind::InvalidInput,
"cannot determine hostname for TLS connection",
))
.map_err(|err| ConnectError::with_source(crate::ConnectErrorKind::Tls, err));
};
let domain = rustls::ServerName::try_from(tls_host)
.map_err(|err| ConnectError::with_source(crate::ConnectErrorKind::Tls, err))?;

connection = Connection {
stream: Box::new(tls_connector.connect(domain, connection.stream).await?),
Expand Down
22 changes: 10 additions & 12 deletions async-nats/src/tls.rs
Expand Up @@ -63,24 +63,22 @@ pub(crate) async fn load_key(path: PathBuf) -> io::Result<PrivateKey> {
}

pub(crate) async fn config_tls(options: &ConnectorOptions) -> io::Result<rustls::ClientConfig> {
let mut root_store = rustls::RootCertStore::empty();
let mut root_store = tokio_rustls::rustls::RootCertStore::empty();
// load native system certs only if user did not specify them.
if options.tls_client_config.is_some() || options.certificates.is_empty() {
for cert in rustls_native_certs::load_native_certs().map_err(|err| {
io::Error::new(
ErrorKind::Other,
format!("could not load platform certs: {err}"),
)
})? {
root_store
.add(&rustls::Certificate(cert.0))
root_store.add_parsable_certificates(
rustls_native_certs::load_native_certs()
.map_err(|err| {
io::Error::new(
ErrorKind::Other,
format!("failed to read root certificates: {err}"),
format!("could not load platform certs: {err}"),
)
})?;
}
})?
.into_iter()
.map(|cert| cert.0)
.collect::<Vec<Vec<u8>>>()
.as_ref(),
);
}

// use provided ClientConfig or built it from options.
Expand Down
27 changes: 27 additions & 0 deletions async-nats/tests/configs/certs/ip-ca.pem
@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEkDCCA3igAwIBAgIUSZwW7btc9EUbrMWtjHpbM0C2bSEwDQYJKoZIhvcNAQEL
BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM
B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl
IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw
MjMwMlowcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV
BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmlj
YXRlIEF1dGhvcml0eSAyMDIyLTA4LTI3MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAqilVqyY8rmCpTwAsLF7DEtWEq37KbljBWVjmlp2Wo6TgMd3b537t
6iO8+SbI8KH75i63RcxV3Uzt1/L9Yb6enDXF52A/U5ugmDhaa+Vsoo2HBTbCczmp
qndp7znllQqn7wNLv6aGSvaeIUeYS5Dmlh3kt7Vqbn4YRANkOUTDYGSpMv7jYKSu
1ee05Rco3H674zdwToYto8L8V7nVMrky42qZnGrJTaze+Cm9tmaIyHCwUq362CxS
dkmaEuWx11MOIFZvL80n7ci6pveDxe5MIfwMC3/oGn7mbsSqidPMcTtjw6ey5NEu
Z0UrC/2lL1FtF4gnVMKUSaEhU2oKjj0ZAQIDAQABo4IBHjCCARowHQYDVR0OBBYE
FP7Pfz4u7sSt6ltviEVsx4hIFIs6MIGuBgNVHSMEgaYwgaOAFP7Pfz4u7sSt6ltv
iEVsx4hIFIs6oXWkczBxMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5p
YTEQMA4GA1UECgwHU3luYWRpYTEQMA4GA1UECwwHbmF0cy5pbzEpMCcGA1UEAwwg
Q2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMjItMDgtMjeCFEmcFu27XPRFG6zFrYx6
WzNAtm0hMAwGA1UdEwQFMAMBAf8wOgYJYIZIAYb4QgENBC0WK25hdHMuaW8gbmF0
cy1zZXJ2ZXIgdGVzdC1zdWl0ZSB0cmFuc2llbnQgQ0EwDQYJKoZIhvcNAQELBQAD
ggEBAHDCHLQklYZlnzHDaSwxgGSiPUrCf2zhk2DNIYSDyBgdzrIapmaVYQRrCBtA
j/4jVFesgw5WDoe4TKsyha0QeVwJDIN8qg2pvpbmD8nOtLApfl0P966vcucxDwqO
dQWrIgNsaUdHdwdo0OfvAlTfG0v/y2X0kbL7h/el5W9kWpxM/rfbX4IHseZL2sLq
FH69SN3FhMbdIm1ldrcLBQVz8vJAGI+6B9hSSFQWljssE0JfAX+8VW/foJgMSx7A
vBTq58rLkAko56Jlzqh/4QT+ckayg9I73v1Q5/44jP1mHw35s5ZrzpDQt2sVv4l5
lwRPJFXMwe64flUs9sM+/vqJaIY=
-----END CERTIFICATE-----
99 changes: 99 additions & 0 deletions async-nats/tests/configs/certs/ip-cert.pem
@@ -0,0 +1,99 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
1d:d9:1f:06:dd:fd:90:26:4e:27:ea:2e:01:4b:31:e6:d2:49:31:1f
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, ST=California, O=Synadia, OU=nats.io, CN=Certificate Authority 2022-08-27
Validity
Not Before: Aug 27 20:23:02 2022 GMT
Not After : Aug 24 20:23:02 2032 GMT
Subject: C=US, ST=California, O=Synadia, OU=nats.io, CN=localhost
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:e6:fb:47:65:cd:c9:a2:2d:af:8b:cd:d5:6a:79:
54:3c:07:5f:eb:5a:71:2b:2b:e5:6f:be:31:fb:16:
65:68:76:0e:59:e7:e4:57:ca:88:e9:77:d6:41:ad:
57:7a:42:b2:d2:54:c4:0f:7c:5b:c1:bc:61:97:e3:
22:3a:3e:1e:4a:5d:47:9f:6b:7d:6f:34:e3:8c:86:
9d:85:19:29:9a:11:58:44:4c:a1:90:d3:14:61:e1:
57:da:01:ea:ce:3f:90:ae:9e:5d:13:6d:2c:89:ca:
39:15:6b:b6:9e:32:d7:2a:4c:48:85:2f:b0:1e:d8:
4b:62:32:14:eb:32:b6:29:04:34:3c:af:39:b6:8b:
52:32:4d:bf:43:5f:9b:fb:0d:43:a6:ad:2c:a7:41:
29:55:c9:70:b3:b5:15:46:34:bf:e4:1e:52:2d:a4:
49:2e:d5:21:ed:fc:00:f7:a2:0b:bc:12:0a:90:64:
50:7c:c5:14:70:f5:fb:9b:62:08:78:43:49:31:f3:
47:b8:93:d4:2d:4c:a9:dc:17:70:76:34:66:ff:65:
c1:39:67:e9:a6:1c:80:6a:f0:9d:b3:28:c8:a3:3a:
b7:5d:de:6e:53:6d:09:b3:0d:b1:13:10:e8:ec:e0:
bd:5e:a1:94:4b:70:bf:dc:bd:8b:b9:82:65:dd:af:
81:7b
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
nats.io nats-server test-suite certificate
X509v3 Subject Key Identifier:
2B:8C:A3:8B:DB:DB:5C:CE:18:DB:F6:A8:31:4E:C2:3E:EE:D3:40:7E
X509v3 Authority Key Identifier:
keyid:FE:CF:7F:3E:2E:EE:C4:AD:EA:5B:6F:88:45:6C:C7:88:48:14:8B:3A
DirName:/C=US/ST=California/O=Synadia/OU=nats.io/CN=Certificate Authority 2022-08-27
serial:49:9C:16:ED:BB:5C:F4:45:1B:AC:C5:AD:8C:7A:5B:33:40:B6:6D:21

X509v3 Subject Alternative Name:
DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
Netscape Cert Type:
SSL Client, SSL Server
X509v3 Key Usage:
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication, Netscape Server Gated Crypto, Microsoft Server Gated Crypto, TLS Web Client Authentication
Signature Algorithm: sha256WithRSAEncryption
54:49:34:2b:38:d1:aa:3b:43:60:4c:3f:6a:f8:74:ca:49:53:
a1:af:12:d3:a8:17:90:7b:9d:a3:69:13:6e:da:2c:b7:61:31:
ac:eb:00:93:92:fc:0c:10:d4:18:a0:16:61:94:4b:42:cb:eb:
7a:f6:80:c6:45:c0:9c:09:aa:a9:48:e8:36:e3:c5:be:36:e0:
e9:78:2a:bb:ab:64:9b:20:eb:e6:0f:63:2b:59:c3:58:0b:3a:
84:15:04:c1:7e:12:03:1b:09:25:8d:4c:03:e8:18:26:c0:6c:
b7:90:b1:fd:bc:f1:cf:d0:d5:4a:03:15:71:0c:7d:c1:76:87:
92:f1:3e:bc:75:51:5a:c4:36:a4:ff:91:98:df:33:5d:a7:38:
de:50:29:fd:0f:c8:55:e6:8f:24:c2:2e:98:ab:d9:5d:65:2f:
50:cc:25:f6:84:f2:21:2e:5e:76:d0:86:1e:69:8b:cb:8a:3a:
2d:79:21:5e:e7:f7:2d:06:18:a1:13:cb:01:c3:46:91:2a:de:
b4:82:d7:c3:62:6f:08:a1:d5:90:19:30:9d:64:8e:e4:f8:ba:
4f:2f:ba:13:b4:a3:9f:d1:d5:77:64:8a:3e:eb:53:c5:47:ac:
ab:3e:0e:7a:9b:a6:f4:48:25:66:eb:c7:4c:f9:50:24:eb:71:
e0:75:ae:e6
-----BEGIN CERTIFICATE-----
MIIE+TCCA+GgAwIBAgIUHdkfBt39kCZOJ+ouAUsx5tJJMR8wDQYJKoZIhvcNAQEL
BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM
B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl
IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw
MjMwMlowWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV
BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xEjAQBgNVBAMMCWxvY2FsaG9z
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOb7R2XNyaItr4vN1Wp5
VDwHX+tacSsr5W++MfsWZWh2Dlnn5FfKiOl31kGtV3pCstJUxA98W8G8YZfjIjo+
HkpdR59rfW8044yGnYUZKZoRWERMoZDTFGHhV9oB6s4/kK6eXRNtLInKORVrtp4y
1ypMSIUvsB7YS2IyFOsytikENDyvObaLUjJNv0Nfm/sNQ6atLKdBKVXJcLO1FUY0
v+QeUi2kSS7VIe38APeiC7wSCpBkUHzFFHD1+5tiCHhDSTHzR7iT1C1MqdwXcHY0
Zv9lwTln6aYcgGrwnbMoyKM6t13eblNtCbMNsRMQ6OzgvV6hlEtwv9y9i7mCZd2v
gXsCAwEAAaOCAZ4wggGaMAkGA1UdEwQCMAAwOQYJYIZIAYb4QgENBCwWKm5hdHMu
aW8gbmF0cy1zZXJ2ZXIgdGVzdC1zdWl0ZSBjZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU
K4yji9vbXM4Y2/aoMU7CPu7TQH4wga4GA1UdIwSBpjCBo4AU/s9/Pi7uxK3qW2+I
RWzHiEgUizqhdaRzMHExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh
MRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQLDAduYXRzLmlvMSkwJwYDVQQDDCBD
ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAyMi0wOC0yN4IUSZwW7btc9EUbrMWtjHpb
M0C2bSEwLAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAA
AAABMBEGCWCGSAGG+EIBAQQEAwIGwDALBgNVHQ8EBAMCBaAwNAYDVR0lBC0wKwYI
KwYBBQUHAwEGCWCGSAGG+EIEAQYKKwYBBAGCNwoDAwYIKwYBBQUHAwIwDQYJKoZI
hvcNAQELBQADggEBAFRJNCs40ao7Q2BMP2r4dMpJU6GvEtOoF5B7naNpE27aLLdh
MazrAJOS/AwQ1BigFmGUS0LL63r2gMZFwJwJqqlI6Dbjxb424Ol4KrurZJsg6+YP
YytZw1gLOoQVBMF+EgMbCSWNTAPoGCbAbLeQsf288c/Q1UoDFXEMfcF2h5LxPrx1
UVrENqT/kZjfM12nON5QKf0PyFXmjyTCLpir2V1lL1DMJfaE8iEuXnbQhh5pi8uK
Oi15IV7n9y0GGKETywHDRpEq3rSC18Nibwih1ZAZMJ1kjuT4uk8vuhO0o5/R1Xdk
ij7rU8VHrKs+DnqbpvRIJWbrx0z5UCTrceB1ruY=
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions async-nats/tests/configs/certs/ip-key.pem
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDm+0dlzcmiLa+L
zdVqeVQ8B1/rWnErK+VvvjH7FmVodg5Z5+RXyojpd9ZBrVd6QrLSVMQPfFvBvGGX
4yI6Ph5KXUefa31vNOOMhp2FGSmaEVhETKGQ0xRh4VfaAerOP5Cunl0TbSyJyjkV
a7aeMtcqTEiFL7Ae2EtiMhTrMrYpBDQ8rzm2i1IyTb9DX5v7DUOmrSynQSlVyXCz
tRVGNL/kHlItpEku1SHt/AD3ogu8EgqQZFB8xRRw9fubYgh4Q0kx80e4k9QtTKnc
F3B2NGb/ZcE5Z+mmHIBq8J2zKMijOrdd3m5TbQmzDbETEOjs4L1eoZRLcL/cvYu5
gmXdr4F7AgMBAAECggEBAK4sr3MiEbjcsHJAvXyzjwRRH1Bu+8VtLW7swe2vvrpd
w4aiKXrV/BXpSsRtvPgxkXyvdMSkpuBZeFI7cVTwAJFc86RQPt77x9bwr5ltFwTZ
rXCbRH3b3ZPNhByds3zhS+2Q92itu5cPyanQdn2mor9/lHPyOOGZgobCcynELL6R
wRElkeDyf5ODuWEd7ADC5IFyZuwb3azNVexIK+0yqnMmv+QzEW3hsycFmFGAeB7v
MIMjb2BhLrRr6Y5Nh+k58yM5DCf9h/OJhDpeXwLkxyK4BFg+aZffEbUX0wHDMR7f
/nMv1g6cKvDWiLU8xLzez4t2qNIBNdxw5ZSLyQRRolECgYEA+ySTKrBAqI0Uwn8H
sUFH95WhWUXryeRyGyQsnWAjZGF1+d67sSY2un2W6gfZrxRgiNLWEFq9AaUs0MuH
6syF4Xwx/aZgU/gvsGtkgzuKw1bgvekT9pS/+opmHRCZyQAFEHj0IEpzyB6rW1u/
LdlR3ShEENnmXilFv/uF/uXP5tMCgYEA63LiT0w46aGPA/E+aLRWU10c1eZ7KdhR
c3En6zfgIxgFs8J38oLdkOR0CF6T53DSuvGR/OprVKdlnUhhDxBgT1oQjK2GlhPx
JV5uMvarJDJxAwsF+7T4H2QtZ00BtEfpyp790+TlypSG1jo/BnSMmX2uEbV722lY
hzINLY49obkCgYBEpN2YyG4T4+PtuXznxRkfogVk+kiVeVx68KtFJLbnw//UGT4i
EHjbBmLOevDT+vTb0QzzkWmh3nzeYRM4aUiatjCPzP79VJPsW54whIDMHZ32KpPr
TQMgPt3kSdpO5zN7KiRIAzGcXE2n/e7GYGUQ1uWr2XMu/4byD5SzdCscQwJ/Ymii
LoKtRvk/zWYHr7uwWSeR5dVvpQ3E/XtONAImrIRd3cRqXfJUqTrTRKxDJXkCmyBc
5FkWg0t0LUkTSDiQCJqcUDA3EINFR1kwthxja72pfpwc5Be/nV9BmuuUysVD8myB
qw8A/KsXsHKn5QrRuVXOa5hvLEXbuqYw29mX6QKBgDGDzIzpR9uPtBCqzWJmc+IJ
z4m/1NFlEz0N0QNwZ/TlhyT60ytJNcmW8qkgOSTHG7RDueEIzjQ8LKJYH7kXjfcF
6AJczUG5PQo9cdJKo9JP3e1037P/58JpLcLe8xxQ4ce03zZpzhsxR2G/tz8DstJs
b8jpnLyqfGrcV2feUtIZ
-----END PRIVATE KEY-----
17 changes: 17 additions & 0 deletions async-nats/tests/configs/ip-tls.conf
@@ -0,0 +1,17 @@
# Simple TLS config file

port: 4443
net: localhost # net interface

tls {
cert_file: "./tests/configs/certs/ip-cert.pem"
key_file: "./tests/configs/certs/ip-key.pem"
ca_file: "./tests/configs/certs/ip-ca.pem"
timeout: 2
}

authorization {
user: derek
password: porkchop
timeout: 1
}
28 changes: 28 additions & 0 deletions async-nats/tests/tls_tests.rs
Expand Up @@ -14,11 +14,22 @@
mod client {
use std::path::PathBuf;

use futures::StreamExt;

#[tokio::test]
async fn basic_tls() {
let server = nats_server::run_server("tests/configs/tls.conf");

// Should fail without certs.
assert!(async_nats::connect(&server.client_url()).await.is_err());

// Should fail with IP (cert doesn't have proper SAN entry)
assert!(
async_nats::connect(format!("tls://127.0.0.1:{}", server.client_port()))
.await
.is_err()
);

let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));

async_nats::ConnectOptions::with_user_and_password("derek".into(), "porkchop".into())
Expand All @@ -33,6 +44,19 @@ mod client {
.unwrap();
}

#[tokio::test]
async fn ip_basic_tls() {
let server = nats_server::run_server("tests/configs/ip-tls.conf");

let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));

async_nats::ConnectOptions::with_user_and_password("derek".into(), "porkchop".into())
.add_root_certificates(path.join("tests/configs/certs/ip-ca.pem"))
.require_tls(true)
.connect(format!("tls://127.0.0.1:{}", server.client_port()))
.await
.unwrap();
}
#[tokio::test]
async fn unknown_server_ca() {
let server = nats_server::run_server("tests/configs/tls.conf");
Expand Down Expand Up @@ -75,9 +99,13 @@ mod client {
.await
.unwrap();

let mut subscription = client.subscribe("subject".into()).await.unwrap();
client
.publish("subject".into(), "data".into())
.await
.unwrap();

client.flush().await.unwrap();
assert!(subscription.next().await.is_some());
}
}

0 comments on commit 817a7b9

Please sign in to comment.