rocket/mtls/certificate.rs
1use ref_cast::RefCast;
2
3use crate::mtls::{x509, oid, bigint, Name, Result, Error};
4use crate::request::{Request, FromRequest, Outcome};
5use crate::http::Status;
6
7/// A request guard for validated, verified client certificates.
8///
9/// This type is a wrapper over [`x509::TbsCertificate`] with convenient
10/// methods and complete documentation. Should the data exposed by the inherent
11/// methods not suffice, this type derefs to [`x509::TbsCertificate`].
12///
13/// # Request Guard
14///
15/// The request guard implementation succeeds if:
16///
17/// * MTLS is [configured](crate::mtls).
18/// * The client presents certificates.
19/// * The certificates are valid and not expired.
20/// * The client's certificate chain was signed by the CA identified by the
21/// configured `ca_certs` and with respect to SNI, if any. See [module level
22/// docs](crate::mtls) for configuration details.
23///
24/// If the client does not present certificates, the guard _forwards_ with a
25/// status of 401 Unauthorized.
26///
27/// If the certificate chain fails to validate or verify, the guard _fails_ with
28/// the respective [`Error`] a status of 401 Unauthorized.
29///
30/// # Wrapping
31///
32/// To implement roles, the `Certificate` guard can be wrapped with a more
33/// semantically meaningful type with extra validation. For example, if a
34/// certificate with a specific serial number is known to belong to an
35/// administrator, a `CertifiedAdmin` type can authorize as follow:
36///
37/// ```rust
38/// # #[macro_use] extern crate rocket;
39/// use rocket::mtls::{self, bigint::BigUint, Certificate};
40/// use rocket::request::{Request, FromRequest, Outcome};
41/// use rocket::outcome::try_outcome;
42/// use rocket::http::Status;
43///
44/// // The serial number for the certificate issued to the admin.
45/// const ADMIN_SERIAL: &str = "65828378108300243895479600452308786010218223563";
46///
47/// // A request guard that authenticates and authorizes an administrator.
48/// struct CertifiedAdmin<'r>(Certificate<'r>);
49///
50/// #[rocket::async_trait]
51/// impl<'r> FromRequest<'r> for CertifiedAdmin<'r> {
52/// type Error = mtls::Error;
53///
54/// async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
55/// let cert = try_outcome!(req.guard::<Certificate<'r>>().await);
56/// if let Some(true) = cert.has_serial(ADMIN_SERIAL) {
57/// Outcome::Success(CertifiedAdmin(cert))
58/// } else {
59/// Outcome::Forward(Status::Unauthorized)
60/// }
61/// }
62/// }
63///
64/// #[get("/admin")]
65/// fn admin(admin: CertifiedAdmin<'_>) {
66/// // This handler can only execute if an admin is authenticated.
67/// }
68///
69/// #[get("/admin", rank = 2)]
70/// fn unauthorized(user: Option<Certificate<'_>>) {
71/// // This handler always executes, whether there's a non-admin user that's
72/// // authenticated (user = Some()) or not (user = None).
73/// }
74/// ```
75///
76/// # Example
77///
78/// To retrieve certificate data in a route, use `Certificate` as a guard:
79///
80/// ```rust
81/// # extern crate rocket;
82/// # use rocket::get;
83/// use rocket::mtls::{self, Certificate};
84///
85/// #[get("/auth")]
86/// fn auth(cert: Certificate<'_>) {
87/// // This handler only runs when a valid certificate was presented.
88/// }
89///
90/// #[get("/maybe")]
91/// fn maybe_auth(cert: Option<Certificate<'_>>) {
92/// // This handler runs even if no certificate was presented or an invalid
93/// // certificate was presented.
94/// }
95///
96/// #[get("/ok")]
97/// fn ok_auth(cert: mtls::Result<Certificate<'_>>) {
98/// // This handler does not run if a certificate was not presented but
99/// // _does_ run if a valid (Ok) or invalid (Err) one was presented.
100/// }
101/// ```
102#[derive(Debug, PartialEq)]
103pub struct Certificate<'a> {
104 x509: x509::X509Certificate<'a>,
105 data: &'a CertificateDer<'a>,
106}
107
108pub use rustls::pki_types::CertificateDer;
109
110#[crate::async_trait]
111impl<'r> FromRequest<'r> for Certificate<'r> {
112 type Error = Error;
113
114 async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
115 use crate::outcome::{try_outcome, IntoOutcome};
116
117 let certs = req.connection
118 .peer_certs
119 .as_ref()
120 .or_forward(Status::Unauthorized);
121
122 let chain = try_outcome!(certs);
123 Certificate::parse(chain.inner()).or_error(Status::Unauthorized)
124 }
125}
126
127impl<'a> Certificate<'a> {
128 /// PRIVATE: For internal Rocket use only!
129 fn parse<'r>(chain: &'r [CertificateDer<'r>]) -> Result<Certificate<'r>> {
130 let data = chain.first().ok_or(Error::Empty)?;
131 let x509 = Certificate::parse_one(data)?;
132 Ok(Certificate { x509, data })
133 }
134
135 fn parse_one(raw: &[u8]) -> Result<x509::X509Certificate<'_>> {
136 use oid::OID_X509_EXT_SUBJECT_ALT_NAME as SUBJECT_ALT_NAME;
137 use x509::FromDer;
138
139 let (left, x509) = x509::X509Certificate::from_der(raw)?;
140 if !left.is_empty() {
141 return Err(Error::Trailing(left.len()));
142 }
143
144 // Ensure we have a subject or a subjectAlt.
145 if x509.subject().as_raw().is_empty() {
146 if let Some(ext) = x509.extensions().iter().find(|e| e.oid == SUBJECT_ALT_NAME) {
147 if let x509::ParsedExtension::SubjectAlternativeName(..) = ext.parsed_extension() {
148 return Err(Error::NoSubject);
149 } else if !ext.critical {
150 return Err(Error::NonCriticalSubjectAlt);
151 }
152 } else {
153 return Err(Error::NoSubject);
154 }
155 }
156
157 Ok(x509)
158 }
159
160 #[inline(always)]
161 fn inner(&self) -> &x509::TbsCertificate<'a> {
162 &self.x509.tbs_certificate
163 }
164
165 /// Returns the serial number of the X.509 certificate.
166 ///
167 /// # Example
168 ///
169 /// ```rust
170 /// # extern crate rocket;
171 /// # use rocket::get;
172 /// use rocket::mtls::Certificate;
173 ///
174 /// #[get("/auth")]
175 /// fn auth(cert: Certificate<'_>) {
176 /// let cert = cert.serial();
177 /// }
178 /// ```
179 pub fn serial(&self) -> &bigint::BigUint {
180 &self.inner().serial
181 }
182
183 /// Returns the version of the X.509 certificate.
184 ///
185 /// # Example
186 ///
187 /// ```rust
188 /// # extern crate rocket;
189 /// # use rocket::get;
190 /// use rocket::mtls::Certificate;
191 ///
192 /// #[get("/auth")]
193 /// fn auth(cert: Certificate<'_>) {
194 /// let cert = cert.version();
195 /// }
196 /// ```
197 pub fn version(&self) -> u32 {
198 self.inner().version.0
199 }
200
201 /// Returns the subject (a "DN" or "Distinguished Name") of the X.509
202 /// certificate.
203 ///
204 /// # Example
205 ///
206 /// ```rust
207 /// # extern crate rocket;
208 /// # use rocket::get;
209 /// use rocket::mtls::Certificate;
210 ///
211 /// #[get("/auth")]
212 /// fn auth(cert: Certificate<'_>) {
213 /// if let Some(name) = cert.subject().common_name() {
214 /// println!("Hello, {}!", name);
215 /// }
216 /// }
217 /// ```
218 pub fn subject(&self) -> &Name<'a> {
219 Name::ref_cast(&self.inner().subject)
220 }
221
222 /// Returns the issuer (a "DN" or "Distinguished Name") of the X.509
223 /// certificate.
224 ///
225 /// # Example
226 ///
227 /// ```rust
228 /// # extern crate rocket;
229 /// # use rocket::get;
230 /// use rocket::mtls::Certificate;
231 ///
232 /// #[get("/auth")]
233 /// fn auth(cert: Certificate<'_>) {
234 /// if let Some(name) = cert.issuer().common_name() {
235 /// println!("Issued by: {}", name);
236 /// }
237 /// }
238 /// ```
239 pub fn issuer(&self) -> &Name<'a> {
240 Name::ref_cast(&self.inner().issuer)
241 }
242
243 /// Returns a slice of the extensions in the X.509 certificate.
244 ///
245 /// # Example
246 ///
247 /// ```rust
248 /// # extern crate rocket;
249 /// # use rocket::get;
250 /// use rocket::mtls::{oid, x509, Certificate};
251 ///
252 /// #[get("/auth")]
253 /// fn auth(cert: Certificate<'_>) {
254 /// let subject_alt = cert.extensions().iter()
255 /// .find(|e| e.oid == oid::OID_X509_EXT_SUBJECT_ALT_NAME)
256 /// .and_then(|e| match e.parsed_extension() {
257 /// x509::ParsedExtension::SubjectAlternativeName(s) => Some(s),
258 /// _ => None
259 /// });
260 ///
261 /// if let Some(subject_alt) = subject_alt {
262 /// for name in &subject_alt.general_names {
263 /// if let x509::GeneralName::RFC822Name(name) = name {
264 /// println!("An email, perhaps? {}", name);
265 /// }
266 /// }
267 /// }
268 /// }
269 /// ```
270 pub fn extensions(&self) -> &[x509::X509Extension<'a>] {
271 self.inner().extensions()
272 }
273
274 /// Checks if the certificate has the serial number `number`.
275 ///
276 /// If `number` is not a valid unsigned integer in base 10, returns `None`.
277 ///
278 /// Otherwise, returns `Some(true)` if it does and `Some(false)` if it does
279 /// not.
280 ///
281 /// ```rust
282 /// # extern crate rocket;
283 /// # use rocket::get;
284 /// use rocket::mtls::Certificate;
285 ///
286 /// const SERIAL: &str = "65828378108300243895479600452308786010218223563";
287 ///
288 /// #[get("/auth")]
289 /// fn auth(cert: Certificate<'_>) {
290 /// if cert.has_serial(SERIAL).unwrap_or(false) {
291 /// println!("certificate has the expected serial number");
292 /// }
293 /// }
294 /// ```
295 pub fn has_serial(&self, number: &str) -> Option<bool> {
296 let uint: bigint::BigUint = number.parse().ok()?;
297 Some(&uint == self.serial())
298 }
299
300 /// Returns the raw, unmodified, DER-encoded X.509 certificate data bytes.
301 ///
302 /// # Example
303 ///
304 /// ```rust
305 /// # extern crate rocket;
306 /// # use rocket::get;
307 /// use rocket::mtls::Certificate;
308 ///
309 /// const SHA256_FINGERPRINT: &str =
310 /// "CE C2 4E 01 00 FF F7 78 CB A4 AA CB D2 49 DD 09 \
311 /// 02 EF 0E 9B DA 89 2A E4 0D F4 09 83 97 C1 97 0D";
312 ///
313 /// #[get("/auth")]
314 /// fn auth(cert: Certificate<'_>) {
315 /// # fn sha256_fingerprint(bytes: &[u8]) -> String { todo!() }
316 /// if sha256_fingerprint(cert.as_bytes()) == SHA256_FINGERPRINT {
317 /// println!("certificate fingerprint matched");
318 /// }
319 /// }
320 /// ```
321 pub fn as_bytes(&self) -> &'a [u8] {
322 self.data
323 }
324}
325
326impl<'a> std::ops::Deref for Certificate<'a> {
327 type Target = x509::TbsCertificate<'a>;
328
329 fn deref(&self) -> &Self::Target {
330 self.inner()
331 }
332}