rocket_community/mtls/certificate.rs
1use ref_cast::RefCast;
2
3use crate::http::Status;
4use crate::mtls::{bigint, oid, x509, Error, Name, Result};
5use crate::request::{FromRequest, Outcome, Request};
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_community as 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_community as 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
118 .connection
119 .peer_certs
120 .as_ref()
121 .or_forward(Status::Unauthorized);
122
123 let chain = try_outcome!(certs);
124 Certificate::parse(chain.inner()).or_error(Status::Unauthorized)
125 }
126}
127
128impl<'a> Certificate<'a> {
129 /// PRIVATE: For internal Rocket use only!
130 fn parse<'r>(chain: &'r [CertificateDer<'r>]) -> Result<Certificate<'r>> {
131 let data = chain.first().ok_or(Error::Empty)?;
132 let x509 = Certificate::parse_one(data)?;
133 Ok(Certificate { x509, data })
134 }
135
136 fn parse_one(raw: &[u8]) -> Result<x509::X509Certificate<'_>> {
137 use oid::OID_X509_EXT_SUBJECT_ALT_NAME as SUBJECT_ALT_NAME;
138 use x509::FromDer;
139
140 let (left, x509) = x509::X509Certificate::from_der(raw)?;
141 if !left.is_empty() {
142 return Err(Error::Trailing(left.len()));
143 }
144
145 // Ensure we have a subject or a subjectAlt.
146 if x509.subject().as_raw().is_empty() {
147 if let Some(ext) = x509.extensions().iter().find(|e| e.oid == SUBJECT_ALT_NAME) {
148 if let x509::ParsedExtension::SubjectAlternativeName(..) = ext.parsed_extension() {
149 return Err(Error::NoSubject);
150 } else if !ext.critical {
151 return Err(Error::NonCriticalSubjectAlt);
152 }
153 } else {
154 return Err(Error::NoSubject);
155 }
156 }
157
158 Ok(x509)
159 }
160
161 #[inline(always)]
162 fn inner(&self) -> &x509::TbsCertificate<'a> {
163 &self.x509.tbs_certificate
164 }
165
166 /// Returns the serial number of the X.509 certificate.
167 ///
168 /// # Example
169 ///
170 /// ```rust
171 /// # extern crate rocket_community as rocket;
172 /// # use rocket::get;
173 /// use rocket::mtls::Certificate;
174 ///
175 /// #[get("/auth")]
176 /// fn auth(cert: Certificate<'_>) {
177 /// let cert = cert.serial();
178 /// }
179 /// ```
180 pub fn serial(&self) -> &bigint::BigUint {
181 &self.inner().serial
182 }
183
184 /// Returns the version of the X.509 certificate.
185 ///
186 /// # Example
187 ///
188 /// ```rust
189 /// # extern crate rocket_community as rocket;
190 /// # use rocket::get;
191 /// use rocket::mtls::Certificate;
192 ///
193 /// #[get("/auth")]
194 /// fn auth(cert: Certificate<'_>) {
195 /// let cert = cert.version();
196 /// }
197 /// ```
198 pub fn version(&self) -> u32 {
199 self.inner().version.0
200 }
201
202 /// Returns the subject (a "DN" or "Distinguished Name") of the X.509
203 /// certificate.
204 ///
205 /// # Example
206 ///
207 /// ```rust
208 /// # extern crate rocket_community as rocket;
209 /// # use rocket::get;
210 /// use rocket::mtls::Certificate;
211 ///
212 /// #[get("/auth")]
213 /// fn auth(cert: Certificate<'_>) {
214 /// if let Some(name) = cert.subject().common_name() {
215 /// println!("Hello, {}!", name);
216 /// }
217 /// }
218 /// ```
219 pub fn subject(&self) -> &Name<'a> {
220 Name::ref_cast(&self.inner().subject)
221 }
222
223 /// Returns the issuer (a "DN" or "Distinguished Name") of the X.509
224 /// certificate.
225 ///
226 /// # Example
227 ///
228 /// ```rust
229 /// # extern crate rocket_community as rocket;
230 /// # use rocket::get;
231 /// use rocket::mtls::Certificate;
232 ///
233 /// #[get("/auth")]
234 /// fn auth(cert: Certificate<'_>) {
235 /// if let Some(name) = cert.issuer().common_name() {
236 /// println!("Issued by: {}", name);
237 /// }
238 /// }
239 /// ```
240 pub fn issuer(&self) -> &Name<'a> {
241 Name::ref_cast(&self.inner().issuer)
242 }
243
244 /// Returns a slice of the extensions in the X.509 certificate.
245 ///
246 /// # Example
247 ///
248 /// ```rust
249 /// # extern crate rocket_community as rocket;
250 /// # use rocket::get;
251 /// use rocket::mtls::{oid, x509, Certificate};
252 ///
253 /// #[get("/auth")]
254 /// fn auth(cert: Certificate<'_>) {
255 /// let subject_alt = cert.extensions().iter()
256 /// .find(|e| e.oid == oid::OID_X509_EXT_SUBJECT_ALT_NAME)
257 /// .and_then(|e| match e.parsed_extension() {
258 /// x509::ParsedExtension::SubjectAlternativeName(s) => Some(s),
259 /// _ => None
260 /// });
261 ///
262 /// if let Some(subject_alt) = subject_alt {
263 /// for name in &subject_alt.general_names {
264 /// if let x509::GeneralName::RFC822Name(name) = name {
265 /// println!("An email, perhaps? {}", name);
266 /// }
267 /// }
268 /// }
269 /// }
270 /// ```
271 pub fn extensions(&self) -> &[x509::X509Extension<'a>] {
272 self.inner().extensions()
273 }
274
275 /// Checks if the certificate has the serial number `number`.
276 ///
277 /// If `number` is not a valid unsigned integer in base 10, returns `None`.
278 ///
279 /// Otherwise, returns `Some(true)` if it does and `Some(false)` if it does
280 /// not.
281 ///
282 /// ```rust
283 /// # extern crate rocket_community as rocket;
284 /// # use rocket::get;
285 /// use rocket::mtls::Certificate;
286 ///
287 /// const SERIAL: &str = "65828378108300243895479600452308786010218223563";
288 ///
289 /// #[get("/auth")]
290 /// fn auth(cert: Certificate<'_>) {
291 /// if cert.has_serial(SERIAL).unwrap_or(false) {
292 /// println!("certificate has the expected serial number");
293 /// }
294 /// }
295 /// ```
296 pub fn has_serial(&self, number: &str) -> Option<bool> {
297 let uint: bigint::BigUint = number.parse().ok()?;
298 Some(&uint == self.serial())
299 }
300
301 /// Returns the raw, unmodified, DER-encoded X.509 certificate data bytes.
302 ///
303 /// # Example
304 ///
305 /// ```rust
306 /// # extern crate rocket_community as rocket;
307 /// # use rocket::get;
308 /// use rocket::mtls::Certificate;
309 ///
310 /// const SHA256_FINGERPRINT: &str =
311 /// "CE C2 4E 01 00 FF F7 78 CB A4 AA CB D2 49 DD 09 \
312 /// 02 EF 0E 9B DA 89 2A E4 0D F4 09 83 97 C1 97 0D";
313 ///
314 /// #[get("/auth")]
315 /// fn auth(cert: Certificate<'_>) {
316 /// # fn sha256_fingerprint(bytes: &[u8]) -> String { todo!() }
317 /// if sha256_fingerprint(cert.as_bytes()) == SHA256_FINGERPRINT {
318 /// println!("certificate fingerprint matched");
319 /// }
320 /// }
321 /// ```
322 pub fn as_bytes(&self) -> &'a [u8] {
323 self.data
324 }
325}
326
327impl<'a> std::ops::Deref for Certificate<'a> {
328 type Target = x509::TbsCertificate<'a>;
329
330 fn deref(&self) -> &Self::Target {
331 self.inner()
332 }
333}