bitwarden_crypto/traits/
key_id.rs

1use std::{fmt::Debug, hash::Hash};
2
3use zeroize::ZeroizeOnDrop;
4
5use crate::{AsymmetricCryptoKey, CryptoKey, SigningKey, SymmetricCryptoKey};
6
7/// Represents a key identifier that can be used to identify cryptographic keys in the
8/// key store. It is used to avoid exposing the key material directly in the public API.
9///
10/// This trait is user-implemented, and the recommended implementation is using enums with variants
11/// for each expected key purpose. We provide a macro ([crate::key_ids]) that simplifies the trait
12/// implementation
13///
14/// To implement it manually, note that you need a few types:
15/// - One implementing [KeyId<KeyValue = SymmetricCryptoKey>]
16/// - One implementing [KeyId<KeyValue = AsymmetricCryptoKey>]
17/// - One implementing [KeyIds]
18pub trait KeyId:
19    Debug + Clone + Copy + Hash + Eq + PartialEq + Ord + PartialOrd + Send + Sync + 'static
20{
21    #[allow(missing_docs)]
22    type KeyValue: CryptoKey + Send + Sync + ZeroizeOnDrop;
23
24    /// Returns whether the key is local to the current context or shared globally by the
25    /// key store. See [crate::store::KeyStoreContext] for more information.
26    fn is_local(&self) -> bool;
27
28    /// Creates a new unique local key identifier.
29    fn new_local(id: LocalId) -> Self;
30}
31
32/// Represents a set of all the key identifiers that need to be defined to use a key store.
33/// At the moment it's just symmetric and asymmetric keys.
34pub trait KeyIds {
35    #[allow(missing_docs)]
36    type Symmetric: KeyId<KeyValue = SymmetricCryptoKey>;
37    #[allow(missing_docs)]
38    type Asymmetric: KeyId<KeyValue = AsymmetricCryptoKey>;
39    /// Signing keys are used to create detached signatures and to sign objects.
40    type Signing: KeyId<KeyValue = SigningKey>;
41}
42
43/// An opaque identifier for a local key. Currently only contains a unique ID, but it can be
44/// extended to contain scope information to allow cleanup on scope exit.
45#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
46pub struct LocalId(pub(crate) uuid::Uuid);
47
48impl LocalId {
49    pub(crate) fn new() -> Self {
50        LocalId(uuid::Uuid::new_v4())
51    }
52}
53
54/// Just a small derive_like macro that can be used to generate the key identifier enums.
55/// Example usage:
56/// ```rust
57/// use bitwarden_crypto::key_ids;
58/// key_ids! {
59///     #[symmetric]
60///     pub enum SymmKeyId {
61///         User,
62///         Org(uuid::Uuid),
63///         #[local]
64///         Local(LocalId),
65///     }
66///
67///     #[asymmetric]
68///     pub enum AsymmKeyId {
69///         PrivateKey,
70///         #[local]
71///         Local(LocalId),
72///     }
73///
74///     #[signing]
75///     pub enum SigningKeyId {
76///        SigningKey,
77///        #[local]
78///        Local(LocalId),
79///     }
80///
81///     pub Ids => SymmKeyId, AsymmKeyId, SigningKeyId;
82/// }
83#[macro_export]
84macro_rules! key_ids {
85    ( $(
86        #[$meta_type:tt]
87        $vis:vis enum $name:ident {
88            $(
89                $( #[$variant_tag:tt] )?
90                $variant:ident $( ( $inner:ty ) )?
91            ),*
92            $(,)?
93        }
94    )+
95    $ids_vis:vis $ids_name:ident => $symm_name:ident, $asymm_name:ident, $signing_name:ident;
96    ) => {
97
98        use $crate::LocalId;
99
100        $(
101            #[derive(std::fmt::Debug, Clone, Copy, std::hash::Hash, Eq, PartialEq, Ord, PartialOrd)]
102            #[allow(missing_docs)]
103            $vis enum $name { $(
104                $variant  $( ($inner) )?,
105            )* }
106
107            impl $crate::KeyId for $name {
108                type KeyValue = key_ids!(@key_type $meta_type);
109
110                fn is_local(&self) -> bool {
111                    use $name::*;
112                    match self { $(
113                        key_ids!(@variant_match $variant $( ( $inner ) )?) =>
114                            key_ids!(@variant_value $( $variant_tag )? ),
115                    )* }
116                }
117
118                fn new_local(id: LocalId) -> Self {
119                    $(
120                        { key_ids!(@new_local $variant  id $( $variant_tag )? ) }
121                    )*
122                }
123            }
124        )+
125
126        #[allow(missing_docs)]
127        $ids_vis struct $ids_name;
128        impl $crate::KeyIds for $ids_name {
129            type Symmetric = $symm_name;
130            type Asymmetric = $asymm_name;
131            type Signing = $signing_name;
132        }
133    };
134
135    ( @key_type symmetric ) => { $crate::SymmetricCryptoKey };
136    ( @key_type asymmetric ) => { $crate::AsymmetricCryptoKey };
137    ( @key_type signing ) => { $crate::SigningKey };
138
139    ( @variant_match $variant:ident ( $inner:ty ) ) => { $variant (_) };
140    ( @variant_match $variant:ident ) => { $variant };
141
142    ( @variant_value local ) => { true };
143    ( @variant_value ) => { false };
144
145    ( @new_local $variant:ident $id:ident local  ) => { Self::$variant($id) };
146    ( @new_local $variant:ident $id:ident ) => {{}};
147}
148
149#[cfg(test)]
150pub(crate) mod tests {
151
152    use crate::{
153        KeyId, LocalId,
154        traits::tests::{TestAsymmKey, TestSigningKey, TestSymmKey},
155    };
156
157    #[test]
158    fn test_local() {
159        let local = LocalId::new();
160
161        assert!(!TestSymmKey::A(0).is_local());
162        assert!(!TestSymmKey::B((4, 10)).is_local());
163        assert!(TestSymmKey::C(local).is_local());
164
165        assert!(!TestAsymmKey::A(0).is_local());
166        assert!(!TestAsymmKey::B.is_local());
167        assert!(TestAsymmKey::C(local).is_local());
168
169        assert!(!TestSigningKey::A(0).is_local());
170        assert!(!TestSigningKey::B.is_local());
171        assert!(TestSigningKey::C(local).is_local());
172    }
173}