authly_client/
identity.rs

1//! Client identity, in the TLS sense.
2
3use std::{borrow::Cow, str::FromStr};
4
5use authly_common::id::ServiceId;
6use pem::{EncodeConfig, Pem};
7
8use crate::Error;
9
10/// Client identitity.
11///
12/// All authly clients identifies themselves using mutual TLS.
13#[derive(Clone)]
14pub struct Identity {
15    pub(crate) cert_pem: Vec<u8>,
16    pub(crate) key_pem: Vec<u8>,
17}
18
19impl Identity {
20    /// Load identity from PEM file containing a certificate and private key.
21    pub fn from_pem(pem: impl AsRef<[u8]>) -> Result<Self, Error> {
22        use rustls_pemfile::Item;
23        use std::io::Cursor;
24
25        let mut pem = Cursor::new(pem);
26        let mut certs = Vec::<rustls_pki_types::CertificateDer>::new();
27        let mut keys = Vec::<rustls_pki_types::PrivateKeyDer>::new();
28
29        for result in rustls_pemfile::read_all(&mut pem) {
30            match result {
31                Ok(Item::X509Certificate(cert)) => certs.push(cert),
32                Ok(Item::Pkcs1Key(key)) => keys.push(key.into()),
33                Ok(Item::Pkcs8Key(key)) => keys.push(key.into()),
34                Ok(Item::Sec1Key(key)) => keys.push(key.into()),
35                Ok(_) => {
36                    return Err(Error::Identity("No valid certificate was found"));
37                }
38                Err(_) => {
39                    return Err(Error::Identity("Invalid identity PEM file"));
40                }
41            }
42        }
43
44        let Some(cert) = certs.into_iter().next() else {
45            return Err(Error::Identity("Certificate not found"));
46        };
47        let Some(key) = keys.into_iter().next() else {
48            return Err(Error::Identity("Private key not found"));
49        };
50
51        Ok(Self {
52            cert_pem: pem::encode_config(
53                &Pem::new("CERTIFICATE", cert.to_vec()),
54                EncodeConfig::new().set_line_ending(pem::LineEnding::LF),
55            )
56            .into_bytes(),
57            key_pem: pem::encode_config(
58                &Pem::new("PRIVATE KEY", key.secret_der()),
59                EncodeConfig::new().set_line_ending(pem::LineEnding::LF),
60            )
61            .into_bytes(),
62        })
63    }
64
65    /// Get the PEM encoded certificate.
66    pub fn cert_pem(&self) -> Cow<[u8]> {
67        self.cert_pem.as_slice().into()
68    }
69
70    /// Get the PEM encoded private key.
71    pub fn key_pem(&self) -> Cow<[u8]> {
72        self.key_pem.as_slice().into()
73    }
74
75    /// Get a PEM containing both the certificate and the private key.
76    pub fn pem(&self) -> Result<Cow<[u8]>, Error> {
77        let mut identity_pem = self.cert_pem.clone();
78        identity_pem.extend(&self.key_pem);
79        Ok(Cow::Owned(identity_pem))
80    }
81}
82
83#[derive(Clone)]
84pub(crate) struct IdentityData {
85    pub entity_id: ServiceId,
86}
87
88pub(crate) fn parse_identity_data(cert: &[u8]) -> Result<IdentityData, Error> {
89    let pem = pem::parse(cert).map_err(|_| Error::AuthlyCA("invalid authly certificate"))?;
90
91    let (_, x509_cert) = x509_parser::parse_x509_certificate(pem.contents())
92        .map_err(|_| Error::AuthlyCA("invalid authly certificate"))?;
93
94    let mut entity_id: Option<ServiceId> = None;
95
96    for subject_attr in x509_cert.subject().iter_attributes() {
97        if let Some(oid_iter) = subject_attr.attr_type().iter() {
98            if oid_iter.eq(authly_common::certificate::oid::ENTITY_UNIQUE_IDENTIFIER
99                .iter()
100                .copied())
101            {
102                let value = subject_attr
103                    .attr_value()
104                    .as_str()
105                    .map_err(|_| Error::Identity("Entity Id value encoding"))?;
106                entity_id = Some(
107                    ServiceId::from_str(value)
108                        .map_err(|_| Error::Identity("Entity Id value encoding"))?,
109                );
110            }
111        }
112    }
113
114    let entity_id = entity_id.ok_or_else(|| Error::Identity("Entity Id is missing"))?;
115
116    // Assume that EC is always used
117    Ok(IdentityData { entity_id })
118}