aws_lc_rs/
fips.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0 OR ISC
3
4/// Retrieve the FIPS module service status.
5#[allow(dead_code)] // appease clippy
6#[cfg(all(feature = "fips", debug_assertions))]
7pub(crate) fn get_fips_service_status() -> FipsServiceStatus<()> {
8    if let Some(status) = indicator::get_status() {
9        if status {
10            FipsServiceStatus::Approved(())
11        } else {
12            FipsServiceStatus::NonApproved(())
13        }
14    } else {
15        FipsServiceStatus::Unset(())
16    }
17}
18
19#[inline]
20pub(crate) fn set_fips_service_status_unapproved() {
21    #[cfg(all(feature = "fips", debug_assertions))]
22    indicator::set_unapproved();
23}
24
25#[allow(dead_code)]
26#[cfg(all(feature = "fips", debug_assertions))]
27#[inline]
28pub(crate) fn clear_fips_service_status() {
29    indicator::clear();
30}
31
32#[cfg(all(feature = "fips", debug_assertions))]
33pub(crate) mod indicator {
34    use core::cell::Cell;
35
36    thread_local! {
37        static STATUS_INDICATOR: Cell<Option<bool>> = const { Cell::new(None) };
38    }
39
40    // Retrieves and returns the current indicator status while resetting the indicator
41    // for future calls.
42    pub fn get_status() -> Option<bool> {
43        STATUS_INDICATOR.with(|v| {
44            let swap = Cell::new(None);
45            v.swap(&swap);
46            swap.take()
47        })
48    }
49
50    pub fn set_approved() {
51        STATUS_INDICATOR.with(|v| v.set(Some(true)));
52    }
53
54    pub fn set_unapproved() {
55        STATUS_INDICATOR.with(|v| v.set(Some(false)));
56    }
57
58    pub fn clear() {
59        STATUS_INDICATOR.with(|v| v.set(None));
60    }
61}
62
63#[cfg(all(feature = "fips", debug_assertions))]
64#[inline]
65pub(crate) fn service_indicator_before_call() -> u64 {
66    unsafe { aws_lc::FIPS_service_indicator_before_call() }
67}
68
69#[cfg(all(feature = "fips", debug_assertions))]
70#[inline]
71pub(crate) fn service_indicator_after_call() -> u64 {
72    unsafe { aws_lc::FIPS_service_indicator_after_call() }
73}
74
75/// The FIPS Module Service Status
76#[allow(dead_code)] // appease clippy
77#[cfg(all(feature = "fips", debug_assertions))]
78#[allow(clippy::module_name_repetitions)]
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub(crate) enum FipsServiceStatus<R> {
81    /// Indicates that the current thread is using approved FIPS cryptographic services.
82    Approved(R),
83
84    /// Indicates that the current thread has used non-approved FIPS cryptographic services.
85    /// The service indicator status can be reset using `reset_fips_service_status`.
86    /// `reset_fips_service_status` will return `NonApprovedMode` if the service used a non-approved
87    /// service, and automatically resets the service status for you.
88    NonApproved(R),
89
90    /// Indicates that the service indicator is not set
91    Unset(R),
92}
93
94#[cfg(all(feature = "fips", debug_assertions))]
95impl<R> FipsServiceStatus<R> {
96    /// Maps a `ServiceStatus<R>` to a `ServiceStatus<S>` by applying a function to a contained value.
97    #[allow(dead_code)]
98    pub fn map<S, F>(self, op: F) -> FipsServiceStatus<S>
99    where
100        F: FnOnce(R) -> S,
101    {
102        match self {
103            FipsServiceStatus::Approved(v) => FipsServiceStatus::Approved(op(v)),
104            FipsServiceStatus::NonApproved(v) => FipsServiceStatus::NonApproved(op(v)),
105            FipsServiceStatus::Unset(v) => FipsServiceStatus::Unset(op(v)),
106        }
107    }
108}
109
110macro_rules! indicator_check {
111    ($function:expr) => {{
112        #[cfg(all(feature = "fips", debug_assertions))]
113        {
114            use crate::fips::{service_indicator_after_call, service_indicator_before_call};
115            let before = service_indicator_before_call();
116            let result = $function;
117            let after = service_indicator_after_call();
118            if before == after {
119                crate::fips::indicator::set_unapproved();
120                result
121            } else {
122                crate::fips::indicator::set_approved();
123                result
124            }
125        }
126        #[cfg(any(not(feature = "fips"), not(debug_assertions)))]
127        {
128            $function
129        }
130    }};
131}
132
133pub(crate) use indicator_check;
134
135#[allow(unused_macros)]
136#[cfg(all(feature = "fips", debug_assertions))]
137macro_rules! check_fips_service_status {
138    ($function:expr) => {{
139        // Clear the current indicator status first by retrieving it
140        use $crate::fips::{clear_fips_service_status, get_fips_service_status};
141        clear_fips_service_status();
142        // do the expression
143        let result = $function;
144        // Check indicator after expression
145        get_fips_service_status().map(|()| result)
146    }};
147}
148
149#[allow(unused_imports)]
150#[cfg(all(feature = "fips", debug_assertions))]
151pub(crate) use check_fips_service_status;
152
153#[allow(unused_macros)]
154#[cfg(all(feature = "fips", debug_assertions))]
155macro_rules! assert_fips_status_indicator {
156    ($function:expr, $expect:path) => {
157        assert_fips_status_indicator!($function, $expect, "unexpected service indicator")
158    };
159    ($function:expr, $expect:path, $message:literal) => {{
160        match crate::fips::check_fips_service_status!($function) {
161            $expect(v) => v,
162            _ => panic!($message),
163        }
164    }};
165}
166
167#[allow(unused_imports)]
168#[cfg(all(feature = "fips", debug_assertions))]
169pub(crate) use assert_fips_status_indicator;
170
171#[cfg(test)]
172mod tests {
173
174    #[cfg(all(feature = "fips", debug_assertions))]
175    #[test]
176    fn test_service_status() {
177        use crate::fips::FipsServiceStatus;
178
179        assert_eq!(
180            FipsServiceStatus::Approved(true),
181            FipsServiceStatus::Approved(()).map(|()| true)
182        );
183        assert_eq!(
184            FipsServiceStatus::NonApproved(true),
185            FipsServiceStatus::NonApproved(()).map(|()| true)
186        );
187        assert_eq!(
188            FipsServiceStatus::Unset(true),
189            FipsServiceStatus::Unset(()).map(|()| true)
190        );
191    }
192}