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}