Skip to main content

signstar_download_key_certificates/
backend.rs

1//! The handling of support HSM backends.
2
3use std::fmt::Display;
4#[cfg(feature = "nethsm")]
5use std::str::FromStr;
6
7use log::warn;
8#[cfg(feature = "nethsm")]
9use nethsm::{FullCredentials, NetHsm, signer::OwnedNetHsmKey};
10use signstar_common::{
11    backend::BackendType,
12    request_api::{Certificate, CertificateType},
13};
14use signstar_config::config::{
15    Config,
16    NonAdminBackendUserIdFilter,
17    NonAdminBackendUserIdKind,
18    SystemUserId,
19    UserBackendConnection,
20    UserBackendConnectionFilter,
21};
22#[cfg(feature = "nethsm")]
23use signstar_config::nethsm::{NetHsmUserMapping, nethsm_export::UserId};
24#[cfg(feature = "yubihsm2")]
25use signstar_config::yubihsm2::YubiHsm2UserMapping;
26use signstar_crypto::{
27    key::{CryptographicKeyContext, SigningKeySetup},
28    signer::traits::RawSigningKey,
29};
30#[cfg(feature = "yubihsm2")]
31use signstar_yubihsm2::{Connection, Credentials, YubiHsm2SigningKey};
32
33use crate::error::Error;
34
35/// Retrieves the certificate of a single signing key from a backend.
36///
37/// # Errors
38///
39/// Returns an error if the certificate retrieval from the backend fails.
40fn get_backend_certificates(
41    key_setup: SigningKeySetup,
42    signing_key_id: impl Display,
43    signers: &[impl RawSigningKey],
44    backend_id: BackendType,
45) -> Result<Vec<Certificate>, Error> {
46    let certificate_type = if let CryptographicKeyContext::OpenPgp { .. } = key_setup.key_context()
47    {
48        CertificateType::OpenPgp
49    } else {
50        warn!("Unsupported key context for signing key {signing_key_id}");
51
52        return Ok(Vec::new());
53    };
54
55    let mut certificates = Vec::new();
56    for signer in signers.iter() {
57        if let Some(cert) = signer.certificate()? {
58            certificates.push(Certificate {
59                certificate: certificate_type.with_framing(&cert),
60                r#type: certificate_type,
61                backend_id,
62                signing_key_id: signing_key_id.to_string(),
63            });
64        }
65    }
66
67    Ok(certificates)
68}
69
70/// Loads a [`Certificate`] from the backend, for each signing key configured in the Signstar
71/// configuration.
72///
73/// # Note
74///
75/// Access to each signing key's certificate is established using backend users that lack permission
76/// to use the signing key for signing.
77///
78/// # Errors
79///
80/// Returns an error if
81/// - loading the Signstar configuration fails
82/// - getting the Unix user of the current process fails
83/// - no backend user(s) are mapped to the currently calling system user
84/// - no backend credentials can be loaded for the currently calling system user
85/// - loading encounters errors
86/// - certificate retrieval of a signing key fails
87pub fn load_certificates() -> Result<Vec<Certificate>, Error> {
88    let config = Config::from_system_path()?;
89
90    let current_system_user = SystemUserId::from_current_unix_user()?;
91
92    let Some(user_backend_connection) = config.user_backend_connection(&current_system_user) else {
93        return Err(Error::NoCredentials {
94            user: current_system_user.to_string(),
95        });
96    };
97
98    let Some(creds) = user_backend_connection.load_non_admin_backend_user_secrets(
99        NonAdminBackendUserIdFilter {
100            backend_user_id_kind: NonAdminBackendUserIdKind::Observer,
101        },
102    )?
103    else {
104        return Err(Error::NoCredentials {
105            user: current_system_user.to_string(),
106        });
107    };
108    if creds.is_empty() {
109        return Err(Error::NoCredentials {
110            user: current_system_user.to_string(),
111        });
112    }
113
114    let backend_type = match user_backend_connection {
115        #[cfg(feature = "nethsm")]
116        UserBackendConnection::NetHsm { .. } => BackendType::NetHsm,
117        #[cfg(feature = "yubihsm2")]
118        UserBackendConnection::YubiHsm2 { .. } => BackendType::YubiHsm2,
119    };
120
121    let mut certificates = Vec::new();
122
123    for user_backend_connection in config.user_backend_connections(&[
124        UserBackendConnectionFilter::Backend(backend_type),
125        UserBackendConnectionFilter::NonAdmin,
126    ]) {
127        match user_backend_connection {
128            #[cfg(feature = "nethsm")]
129            UserBackendConnection::NetHsm {
130                admin_secret_handling: _,
131                non_admin_secret_handling: _,
132                connections,
133                mapping:
134                    NetHsmUserMapping::Signing {
135                        backend_user,
136                        signing_key_id,
137                        key_setup,
138                        ..
139                    },
140            } => {
141                // NOTE: Currently we are only selecting the first connection found, but in the
142                // future we could select them randomly.
143                let connection = connections.first().cloned().ok_or(Error::NoConnection)?;
144
145                let signers = creds
146                    .iter()
147                    .map(|creds| {
148                        Ok(FullCredentials::new(
149                            UserId::from_str(&creds.user())?,
150                            creds.passphrase().clone(),
151                        ))
152                    })
153                    .collect::<Result<Vec<_>, <UserId as FromStr>::Err>>()
154                    .map_err(|e| Error::NetHsm(e.into()))?
155                    .into_iter()
156                    .filter(|cred| cred.name.namespace() == backend_user.namespace())
157                    .map(|creds| {
158                        OwnedNetHsmKey::new(
159                            NetHsm::new(connection.clone(), Some(creds.into()), None, None)?,
160                            signing_key_id.clone(),
161                        )
162                    })
163                    .collect::<Result<Vec<_>, nethsm::Error>>()?;
164
165                certificates.extend(get_backend_certificates(
166                    key_setup,
167                    signing_key_id,
168                    &signers,
169                    BackendType::NetHsm,
170                )?)
171            }
172
173            #[cfg(feature = "yubihsm2")]
174            UserBackendConnection::YubiHsm2 {
175                admin_secret_handling: _,
176                non_admin_secret_handling: _,
177                connections,
178                mapping:
179                    YubiHsm2UserMapping::Signing {
180                        key_setup,
181                        signing_key_id,
182                        ..
183                    },
184            } => {
185                // NOTE: Currently we are only selecting the first connection found, but in the
186                // future we could select them randomly.
187                let connection = connections.first().cloned().ok_or(Error::NoConnection)?;
188
189                let signers = creds
190                    .iter()
191                    .map(|creds| {
192                        let creds = Credentials::new(
193                            creds
194                                .user()
195                                .parse()
196                                .map_err(|_| Error::InvalidUser { user: creds.user() })?,
197                            creds.passphrase().clone(),
198                        );
199
200                        let signer = match connection {
201                            #[cfg(feature = "_yubihsm2-mockhsm")]
202                            Connection::Mock => YubiHsm2SigningKey::mock(signing_key_id, &creds)?,
203                            Connection::Usb { serial_number } => {
204                                YubiHsm2SigningKey::new_with_serial_number(
205                                    serial_number,
206                                    signing_key_id,
207                                    &creds,
208                                )?
209                            }
210                        };
211
212                        Ok(signer)
213                    })
214                    .collect::<Result<Vec<_>, Error>>()?;
215
216                certificates.extend(get_backend_certificates(
217                    key_setup,
218                    signing_key_id,
219                    &signers,
220                    BackendType::YubiHsm2,
221                )?)
222            }
223            _ => (),
224        }
225    }
226    Ok(certificates)
227}