Skip to main content

lzc_sdk/
peer.rs

1use tonic::Request;
2use tonic::transport::server::{TcpConnectInfo, TlsConnectInfo};
3use x509_parser::oid_registry::Oid;
4use x509_parser::parse_x509_certificate;
5use x509_parser::x509::X509Name;
6
7use crate::Error;
8
9/// Identity encoded in a `LazyCat` application client certificate.
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub struct Application {
12    /// Application ID from the subject serial-number attribute.
13    pub app_id: String,
14    /// Box ID from the issuer serial-number attribute.
15    pub box_id: String,
16    /// Application domain from the subject common name.
17    pub app_domain: String,
18}
19
20/// Extract the authenticated `LazyCat` application identity from a server request.
21///
22/// # Errors
23///
24/// Returns an error unless the request carries exactly one valid TLS peer
25/// certificate.
26pub fn peer_application<T>(request: &Request<T>) -> Result<Application, Error> {
27    let certificates = request
28        .extensions()
29        .get::<TlsConnectInfo<TcpConnectInfo>>()
30        .and_then(TlsConnectInfo::peer_certs);
31    #[cfg(unix)]
32    let certificates = certificates.or_else(|| {
33        request
34            .extensions()
35            .get::<TlsConnectInfo<tonic::transport::server::UdsConnectInfo>>()
36            .and_then(TlsConnectInfo::peer_certs)
37    });
38    let certificates = certificates.ok_or(Error::UnauthenticatedPeer)?;
39    application_from_certificates(certificates.as_ref())
40}
41
42fn application_from_certificates(
43    certificates: &[rustls::pki_types::CertificateDer<'static>],
44) -> Result<Application, Error> {
45    if certificates.len() != 1 {
46        return Err(Error::InvalidPeerCertificateCount {
47            count: certificates.len(),
48        });
49    }
50    let (_, certificate) = parse_x509_certificate(certificates[0].as_ref())
51        .map_err(|_| Error::InvalidPeerCertificate)?;
52    Ok(Application {
53        app_id: serial_number(certificate.subject()),
54        box_id: serial_number(certificate.issuer()),
55        app_domain: certificate
56            .subject()
57            .iter_common_name()
58            .next()
59            .and_then(|name| name.as_str().ok())
60            .unwrap_or_default()
61            .to_owned(),
62    })
63}
64
65fn serial_number(name: &X509Name<'_>) -> String {
66    let oid = Oid::from(&[2, 5, 4, 5]).expect("serialNumber OID is valid");
67    name.iter_by_oid(&oid)
68        .next()
69        .and_then(|value| value.as_str().ok())
70        .unwrap_or_default()
71        .to_owned()
72}
73
74#[cfg(test)]
75mod tests {
76    use rustls::pki_types::CertificateDer;
77
78    use crate::Error;
79
80    use super::application_from_certificates;
81
82    #[test]
83    fn rejects_zero_or_multiple_peer_certificates() {
84        let zero = application_from_certificates(&[]).expect_err("zero certificates");
85        assert!(matches!(
86            zero,
87            Error::InvalidPeerCertificateCount { count: 0 }
88        ));
89
90        let invalid = CertificateDer::from(vec![0_u8]);
91        let multiple = application_from_certificates(&[invalid.clone(), invalid])
92            .expect_err("multiple certificates");
93        assert!(matches!(
94            multiple,
95            Error::InvalidPeerCertificateCount { count: 2 }
96        ));
97    }
98}