iota_tls/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5mod acceptor;
6mod certgen;
7mod verifier;
8
9pub const IOTA_VALIDATOR_SERVER_NAME: &str = "iota";
10
11pub use acceptor::{TlsAcceptor, TlsConnectionInfo};
12pub use certgen::SelfSignedCertificate;
13pub use rustls;
14pub use verifier::{
15    AllowAll, AllowPublicKeys, Allower, ClientCertVerifier, ServerCertVerifier,
16    public_key_from_certificate,
17};
18
19#[cfg(test)]
20mod tests {
21    use std::collections::BTreeSet;
22
23    use fastcrypto::{ed25519::Ed25519KeyPair, traits::KeyPair};
24    use rustls::{
25        client::danger::ServerCertVerifier as _,
26        pki_types::{ServerName, UnixTime},
27        server::danger::ClientCertVerifier as _,
28    };
29
30    use super::*;
31
32    #[test]
33    fn verify_allowall() {
34        let mut rng = rand::thread_rng();
35        let allowed = Ed25519KeyPair::generate(&mut rng);
36        let disallowed = Ed25519KeyPair::generate(&mut rng);
37        let random_cert_bob =
38            SelfSignedCertificate::new(allowed.private(), IOTA_VALIDATOR_SERVER_NAME);
39        let random_cert_alice =
40            SelfSignedCertificate::new(disallowed.private(), IOTA_VALIDATOR_SERVER_NAME);
41
42        let verifier = ClientCertVerifier::new(AllowAll, IOTA_VALIDATOR_SERVER_NAME.to_string());
43
44        // The bob passes validation
45        verifier
46            .verify_client_cert(&random_cert_bob.rustls_certificate(), &[], UnixTime::now())
47            .unwrap();
48
49        // The alice passes validation
50        verifier
51            .verify_client_cert(
52                &random_cert_alice.rustls_certificate(),
53                &[],
54                UnixTime::now(),
55            )
56            .unwrap();
57    }
58
59    #[test]
60    fn verify_server_cert() {
61        let mut rng = rand::thread_rng();
62        let allowed = Ed25519KeyPair::generate(&mut rng);
63        let disallowed = Ed25519KeyPair::generate(&mut rng);
64        let allowed_public_key = allowed.public().to_owned();
65        let random_cert_bob =
66            SelfSignedCertificate::new(allowed.private(), IOTA_VALIDATOR_SERVER_NAME);
67        let random_cert_alice =
68            SelfSignedCertificate::new(disallowed.private(), IOTA_VALIDATOR_SERVER_NAME);
69
70        let verifier =
71            ServerCertVerifier::new(allowed_public_key, IOTA_VALIDATOR_SERVER_NAME.to_string());
72
73        // The bob passes validation
74        verifier
75            .verify_server_cert(
76                &random_cert_bob.rustls_certificate(),
77                &[],
78                &ServerName::try_from("example.com").unwrap(),
79                &[],
80                UnixTime::now(),
81            )
82            .unwrap();
83
84        // The alice does not pass validation
85        let err = verifier
86            .verify_server_cert(
87                &random_cert_alice.rustls_certificate(),
88                &[],
89                &ServerName::try_from("example.com").unwrap(),
90                &[],
91                UnixTime::now(),
92            )
93            .unwrap_err();
94        assert!(
95            matches!(err, rustls::Error::General(_)),
96            "Actual error: {err:?}"
97        );
98    }
99
100    #[test]
101    fn verify_hashset() {
102        let mut rng = rand::thread_rng();
103        let allowed = Ed25519KeyPair::generate(&mut rng);
104        let disallowed = Ed25519KeyPair::generate(&mut rng);
105
106        let allowed_public_keys = BTreeSet::from([allowed.public().to_owned()]);
107        let allowed_cert =
108            SelfSignedCertificate::new(allowed.private(), IOTA_VALIDATOR_SERVER_NAME);
109
110        let disallowed_cert =
111            SelfSignedCertificate::new(disallowed.private(), IOTA_VALIDATOR_SERVER_NAME);
112
113        let allowlist = AllowPublicKeys::new(allowed_public_keys);
114        let verifier =
115            ClientCertVerifier::new(allowlist.clone(), IOTA_VALIDATOR_SERVER_NAME.to_string());
116
117        // The allowed cert passes validation
118        verifier
119            .verify_client_cert(&allowed_cert.rustls_certificate(), &[], UnixTime::now())
120            .unwrap();
121
122        // The disallowed cert fails validation
123        let err = verifier
124            .verify_client_cert(&disallowed_cert.rustls_certificate(), &[], UnixTime::now())
125            .unwrap_err();
126        assert!(
127            matches!(err, rustls::Error::General(_)),
128            "Actual error: {err:?}"
129        );
130
131        // After removing the allowed public key from the set it now fails validation
132        allowlist.update(BTreeSet::new());
133        let err = verifier
134            .verify_client_cert(&allowed_cert.rustls_certificate(), &[], UnixTime::now())
135            .unwrap_err();
136        assert!(
137            matches!(err, rustls::Error::General(_)),
138            "Actual error: {err:?}"
139        );
140    }
141
142    #[test]
143    fn invalid_server_name() {
144        let mut rng = rand::thread_rng();
145        let keypair = Ed25519KeyPair::generate(&mut rng);
146        let public_key = keypair.public().to_owned();
147        let cert = SelfSignedCertificate::new(keypair.private(), "not-iota");
148
149        let allowlist = AllowPublicKeys::new(BTreeSet::from([public_key.clone()]));
150        let client_verifier =
151            ClientCertVerifier::new(allowlist.clone(), IOTA_VALIDATOR_SERVER_NAME.to_string());
152
153        // Allowed public key but the server-name in the cert is not the required "iota"
154        let err = client_verifier
155            .verify_client_cert(&cert.rustls_certificate(), &[], UnixTime::now())
156            .unwrap_err();
157        assert_eq!(
158            err,
159            rustls::Error::InvalidCertificate(rustls::CertificateError::NotValidForName),
160            "Actual error: {err:?}"
161        );
162
163        let server_verifier =
164            ServerCertVerifier::new(public_key, IOTA_VALIDATOR_SERVER_NAME.to_string());
165
166        // Allowed public key but the server-name in the cert is not the required "iota"
167        let err = server_verifier
168            .verify_server_cert(
169                &cert.rustls_certificate(),
170                &[],
171                &ServerName::try_from("example.com").unwrap(),
172                &[],
173                UnixTime::now(),
174            )
175            .unwrap_err();
176        assert_eq!(
177            err,
178            rustls::Error::InvalidCertificate(rustls::CertificateError::NotValidForName),
179            "Actual error: {err:?}"
180        );
181    }
182
183    #[tokio::test]
184    async fn axum_acceptor() {
185        use fastcrypto::{ed25519::Ed25519KeyPair, traits::KeyPair};
186
187        let mut rng = rand::thread_rng();
188        let client_keypair = Ed25519KeyPair::generate(&mut rng);
189        let client_public_key = client_keypair.public().to_owned();
190        let client_certificate =
191            SelfSignedCertificate::new(client_keypair.private(), IOTA_VALIDATOR_SERVER_NAME);
192        let server_keypair = Ed25519KeyPair::generate(&mut rng);
193        let server_certificate = SelfSignedCertificate::new(server_keypair.private(), "localhost");
194
195        let client = reqwest::Client::builder()
196            .add_root_certificate(server_certificate.reqwest_certificate())
197            .identity(client_certificate.reqwest_identity())
198            .https_only(true)
199            .build()
200            .unwrap();
201
202        let allowlist = AllowPublicKeys::new(BTreeSet::new());
203        let tls_config =
204            ClientCertVerifier::new(allowlist.clone(), IOTA_VALIDATOR_SERVER_NAME.to_string())
205                .rustls_server_config(
206                    vec![server_certificate.rustls_certificate()],
207                    server_certificate.rustls_private_key(),
208                )
209                .unwrap();
210
211        async fn handler(tls_info: axum::Extension<TlsConnectionInfo>) -> String {
212            tls_info.public_key().unwrap().to_string()
213        }
214
215        let app = axum::Router::new().route("/", axum::routing::get(handler));
216        let listener = std::net::TcpListener::bind("localhost:0").unwrap();
217        let server_address = listener.local_addr().unwrap();
218        let acceptor = TlsAcceptor::new(tls_config);
219        let _server = tokio::spawn(async move {
220            axum_server::Server::from_tcp(listener)
221                .acceptor(acceptor)
222                .serve(app.into_make_service())
223                .await
224                .unwrap()
225        });
226
227        let server_url = format!("https://localhost:{}", server_address.port());
228        // Client request is rejected because it isn't in the allowlist
229        client.get(&server_url).send().await.unwrap_err();
230
231        // Insert the client's public key into the allowlist and verify the request is
232        // successful
233        allowlist.update(BTreeSet::from([client_public_key.clone()]));
234
235        let res = client.get(&server_url).send().await.unwrap();
236        let body = res.text().await.unwrap();
237        assert_eq!(client_public_key.to_string(), body);
238    }
239}