Skip to main content

saml_rs/crypto/
provider.rs

1//! Bergshamra document-crypto provider initialization and attestation.
2
3use std::fmt;
4
5use crate::error::SamlError;
6
7/// Compile-time selected document-crypto provider.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9#[non_exhaustive]
10pub enum CryptoProvider {
11    /// RustCrypto ecosystem implementations.
12    RustCrypto,
13    /// AWS-LC through `aws-lc-rs`.
14    AwsLc,
15}
16
17impl fmt::Display for CryptoProvider {
18    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
19        formatter.write_str(match self {
20            Self::RustCrypto => "rustcrypto",
21            Self::AwsLc => "aws-lc",
22        })
23    }
24}
25
26/// Runtime FIPS attestation state for the selected provider.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28#[non_exhaustive]
29pub enum CryptoFipsStatus {
30    /// The build did not request FIPS enforcement.
31    Disabled,
32    /// FIPS enforcement was compiled in but provider initialization has not run.
33    Uninitialized,
34    /// The selected provider attested that FIPS mode is active.
35    Active,
36}
37
38/// Attested information about the selected document-crypto provider.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40#[non_exhaustive]
41pub struct CryptoProviderInfo {
42    provider: CryptoProvider,
43    fips: CryptoFipsStatus,
44}
45
46impl CryptoProviderInfo {
47    /// Return the compile-time selected provider.
48    #[must_use]
49    pub const fn provider(&self) -> CryptoProvider {
50        self.provider
51    }
52
53    /// Return the provider's runtime FIPS attestation state.
54    #[must_use]
55    pub const fn fips_status(&self) -> CryptoFipsStatus {
56        self.fips
57    }
58}
59
60fn provider_info(info: bergshamra::BackendInfo) -> CryptoProviderInfo {
61    let provider = match info.document {
62        bergshamra::BackendId::RustCrypto => CryptoProvider::RustCrypto,
63        bergshamra::BackendId::AwsLc => CryptoProvider::AwsLc,
64    };
65    let fips = match info.fips {
66        bergshamra::FipsStatus::Disabled => CryptoFipsStatus::Disabled,
67        bergshamra::FipsStatus::Uninitialized => CryptoFipsStatus::Uninitialized,
68        bergshamra::FipsStatus::Active => CryptoFipsStatus::Active,
69    };
70    CryptoProviderInfo { provider, fips }
71}
72
73fn provider_error(action: &str, error: impl fmt::Display) -> SamlError {
74    SamlError::Crypto(format!("crypto provider {action} failed: {error}"))
75}
76
77/// Inspect the selected provider without triggering initialization.
78///
79/// A `crypto-fips` build reports [`CryptoFipsStatus::Uninitialized`] until
80/// [`initialize_crypto_provider`] or another `saml-rs` crypto operation runs.
81///
82/// # Errors
83///
84/// Returns [`SamlError::Crypto`] if Bergshamra cannot report provider state.
85pub fn crypto_provider_info() -> Result<CryptoProviderInfo, SamlError> {
86    bergshamra::backend_info()
87        .map(provider_info)
88        .map_err(|error| provider_error("inspection", error))
89}
90
91/// Initialize and attest the selected document-crypto provider.
92///
93/// Initialization is idempotent and its first result is retained for the
94/// process lifetime. `saml-rs` calls this automatically before its first
95/// Bergshamra operation; applications may call it during startup to fail
96/// early and inspect FIPS attestation before accepting traffic.
97///
98/// # Errors
99///
100/// Returns [`SamlError::Crypto`] when provider initialization or FIPS
101/// attestation fails.
102pub fn initialize_crypto_provider() -> Result<CryptoProviderInfo, SamlError> {
103    bergshamra::initialize_backend()
104        .map(provider_info)
105        .map_err(|error| provider_error("initialization", error))
106}
107
108pub(crate) fn ensure_crypto_provider_initialized() -> Result<(), SamlError> {
109    initialize_crypto_provider().map(|_| ())
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn initialization_reports_selected_provider() -> Result<(), Box<dyn std::error::Error>> {
118        let info = initialize_crypto_provider()?;
119
120        #[cfg(feature = "crypto-rustcrypto")]
121        assert_eq!(info.provider(), CryptoProvider::RustCrypto);
122        #[cfg(any(feature = "crypto-aws-lc", feature = "crypto-fips"))]
123        assert_eq!(info.provider(), CryptoProvider::AwsLc);
124
125        Ok(())
126    }
127
128    #[cfg(not(feature = "crypto-fips"))]
129    #[test]
130    fn initialization_reports_fips_disabled_without_fips_feature(
131    ) -> Result<(), Box<dyn std::error::Error>> {
132        let info = initialize_crypto_provider()?;
133
134        assert_eq!(info.fips_status(), CryptoFipsStatus::Disabled);
135        Ok(())
136    }
137
138    #[cfg(feature = "crypto-fips")]
139    #[test]
140    fn initialization_attests_active_fips_mode() -> Result<(), Box<dyn std::error::Error>> {
141        let info = initialize_crypto_provider()?;
142
143        assert_eq!(info.fips_status(), CryptoFipsStatus::Active);
144        Ok(())
145    }
146}