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}