Skip to main content

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