1mod 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 verifier
46 .verify_client_cert(&random_cert_bob.rustls_certificate(), &[], UnixTime::now())
47 .unwrap();
48
49 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 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 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 verifier
119 .verify_client_cert(&allowed_cert.rustls_certificate(), &[], UnixTime::now())
120 .unwrap();
121
122 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 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 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 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.get(&server_url).send().await.unwrap_err();
230
231 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}