Skip to content

Commit 8d21b37

Browse files
committed
download: statically bundle relevant trust anchors
1 parent fcd02c0 commit 8d21b37

File tree

6 files changed

+189
-6
lines changed

6 files changed

+189
-6
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ http-body-util = "0.1.0"
140140
hyper = { version = "1.0", default-features = false, features = ["server", "http1"] }
141141
hyper-util = { version = "0.1.1", features = ["tokio"] }
142142
proptest = "1.1.0"
143+
rustls-webpki = { version = "0.103.3" }
144+
tokio-rustls = "0.26.4"
145+
webpki-root-certs = "1"
143146

144147
[build-dependencies]
145148
platforms = "3.4"

src/download/mod.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -546,12 +546,14 @@ mod reqwest_be {
546546
use std::sync::{Arc, OnceLock};
547547
use std::time::Duration;
548548

549+
#[cfg(feature = "reqwest-rustls-tls")]
550+
use crate::anchors::RUSTUP_TRUST_ANCHORS;
549551
use anyhow::{Context, anyhow};
550552
use reqwest::{Client, ClientBuilder, Proxy, Response, header};
551553
#[cfg(feature = "reqwest-rustls-tls")]
552554
use rustls::crypto::aws_lc_rs;
553555
#[cfg(feature = "reqwest-rustls-tls")]
554-
use rustls_platform_verifier::BuilderVerifierExt;
556+
use rustls_platform_verifier::Verifier;
555557
use tokio_stream::StreamExt;
556558
use url::Url;
557559

@@ -607,14 +609,19 @@ mod reqwest_be {
607609
return Ok(client);
608610
}
609611

612+
let provider = Arc::new(aws_lc_rs::default_provider());
613+
let verifier =
614+
Verifier::new_with_extra_roots(RUSTUP_TRUST_ANCHORS.iter().cloned(), provider.clone())
615+
.map_err(|err| {
616+
DownloadError::Message(format!("failed to initialize platform verifier: {err}"))
617+
})?;
618+
610619
let mut tls_config =
611-
rustls::ClientConfig::builder_with_provider(Arc::new(aws_lc_rs::default_provider()))
620+
rustls::ClientConfig::builder_with_provider(provider)
612621
.with_safe_default_protocol_versions()
613622
.unwrap()
614-
.with_platform_verifier()
615-
.map_err(|err| {
616-
DownloadError::Message(format!("failed to initialize platform verifier: {err}"))
617-
})?
623+
.dangerous() // We're using a rustls verifier, so it's okay
624+
.with_custom_certificate_verifier(Arc::new(verifier))
618625
.with_no_client_auth();
619626
tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
620627

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ mod settings;
8585
pub mod test;
8686
mod toolchain;
8787
pub mod utils;
88+
#[cfg(feature = "reqwest-rustls-tls")]
89+
mod anchors;
8890

8991
#[cfg(test)]
9092
mod tests {

tests/suite/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ mod cli_v1;
1010
mod cli_v2;
1111
mod dist_install;
1212
mod known_triples;
13+
mod static_roots;

tests/suite/static_roots.rs

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
//! Connects to a known set of `HOSTS`, captures the root certificates for the
2+
//! certificate chain presented, and writes them to `src/anchors.rs` in a way
3+
//! that is easy to consume for use as `extra_roots` in rustls-platform-verifier.
4+
5+
use std::{
6+
fs, process::Command, sync::{Arc, Mutex}
7+
};
8+
9+
use rustls::{
10+
DigitallySignedStruct, Error, RootCertStore, SignatureScheme,
11+
client::{
12+
WebPkiServerVerifier,
13+
danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
14+
},
15+
crypto::{CryptoProvider, aws_lc_rs},
16+
pki_types::{CertificateDer, ServerName, TrustAnchor, UnixTime},
17+
};
18+
use tempfile::NamedTempFile;
19+
use tokio::net::TcpStream;
20+
use tokio_rustls::TlsConnector;
21+
use webpki::{EndEntityCert, anchor_from_trusted_cert};
22+
use webpki_root_certs::TLS_SERVER_ROOT_CERTS;
23+
24+
#[tokio::test]
25+
async fn store_static_roots() {
26+
let provider = Arc::new(aws_lc_rs::default_provider());
27+
let mut root_store = RootCertStore::empty();
28+
let mut roots = Vec::with_capacity(TLS_SERVER_ROOT_CERTS.len());
29+
for cert_der in TLS_SERVER_ROOT_CERTS {
30+
let ta = anchor_from_trusted_cert(cert_der).unwrap();
31+
roots.push((cert_der, ta.clone()));
32+
root_store.roots.push(ta);
33+
}
34+
35+
let root_store = Arc::new(root_store);
36+
let inner = WebPkiServerVerifier::builder_with_provider(root_store.clone(), provider.clone())
37+
.build()
38+
.unwrap();
39+
40+
let verifier = Arc::new(TrackRootVerifier {
41+
root: Mutex::default(),
42+
roots: root_store,
43+
inner,
44+
provider: provider.clone(),
45+
});
46+
47+
let config = Arc::new(
48+
rustls::ClientConfig::builder_with_provider(provider)
49+
.with_safe_default_protocol_versions()
50+
.unwrap()
51+
.dangerous()
52+
.with_custom_certificate_verifier(verifier.clone())
53+
.with_no_client_auth(),
54+
);
55+
56+
let mut code = "// @generated by tests/static_roots.rs\n".to_string();
57+
code.push_str("use rustls::pki_types::CertificateDer;\n\n");
58+
code.push_str("pub(crate) const RUSTUP_TRUST_ANCHORS: &[CertificateDer<'static>] = &[\n");
59+
let connector = TlsConnector::from(config);
60+
for &host in HOSTS {
61+
connector
62+
.connect(
63+
ServerName::try_from(host).unwrap(),
64+
TcpStream::connect((host, 443)).await.unwrap(),
65+
)
66+
.await
67+
.unwrap();
68+
69+
let root = verifier.root.lock().unwrap().take().unwrap();
70+
let root_cert = roots
71+
.iter()
72+
.find_map(|(cert_der, ta)| match ta == &root {
73+
true => Some(cert_der),
74+
false => None,
75+
})
76+
.unwrap();
77+
78+
code.push_str(&format!(" CertificateDer::from_slice(&{:?}),\n", root_cert.as_ref()));
79+
}
80+
code.push_str("];\n");
81+
82+
let tmp = NamedTempFile::new().unwrap();
83+
fs::write(&tmp, &code).unwrap();
84+
Command::new("rustfmt").arg(tmp.path()).status().unwrap();
85+
let new = fs::read_to_string(&tmp).unwrap();
86+
87+
let old = fs::read_to_string(PATH).unwrap();
88+
if old != new {
89+
fs::rename(tmp.path(), PATH).unwrap();
90+
panic!("anchors.rs is outdated; updated it");
91+
}
92+
}
93+
94+
const PATH: &str = "src/anchors.rs";
95+
96+
#[derive(Debug)]
97+
struct TrackRootVerifier {
98+
root: Mutex<Option<TrustAnchor<'static>>>,
99+
inner: Arc<WebPkiServerVerifier>,
100+
roots: Arc<RootCertStore>,
101+
provider: Arc<CryptoProvider>,
102+
}
103+
104+
impl ServerCertVerifier for TrackRootVerifier {
105+
fn verify_server_cert(
106+
&self,
107+
end_entity: &CertificateDer<'_>,
108+
intermediates: &[CertificateDer<'_>],
109+
server_name: &ServerName<'_>,
110+
ocsp_response: &[u8],
111+
now: UnixTime,
112+
) -> Result<ServerCertVerified, Error> {
113+
let verified = self.inner.verify_server_cert(
114+
end_entity,
115+
intermediates,
116+
server_name,
117+
ocsp_response,
118+
now,
119+
)?;
120+
121+
let cert = EndEntityCert::try_from(end_entity)
122+
.map_err(|e| Error::General(format!("invalid end entity certificate: {e}")))?;
123+
124+
let path = cert
125+
.verify_for_usage(
126+
&self.provider.signature_verification_algorithms.all,
127+
&self.roots.roots,
128+
intermediates,
129+
now,
130+
webpki::KeyUsage::server_auth(),
131+
None,
132+
None,
133+
)
134+
.unwrap();
135+
136+
let mut root = self.root.lock().unwrap();
137+
*root = Some(path.anchor().to_owned());
138+
Ok(verified)
139+
}
140+
141+
fn verify_tls12_signature(
142+
&self,
143+
message: &[u8],
144+
cert: &CertificateDer<'_>,
145+
dss: &DigitallySignedStruct,
146+
) -> Result<HandshakeSignatureValid, Error> {
147+
self.inner.verify_tls12_signature(message, cert, dss)
148+
}
149+
150+
fn verify_tls13_signature(
151+
&self,
152+
message: &[u8],
153+
cert: &CertificateDer<'_>,
154+
dss: &DigitallySignedStruct,
155+
) -> Result<HandshakeSignatureValid, Error> {
156+
self.inner.verify_tls13_signature(message, cert, dss)
157+
}
158+
159+
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
160+
self.inner.supported_verify_schemes()
161+
}
162+
}
163+
164+
const HOSTS: &[&str] = &[
165+
"fastly-static.rust-lang.org",
166+
"cloudfront-static.rust-lang.org",
167+
];

0 commit comments

Comments
 (0)