tor_key_forge/
key_type.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! This module defines the key types that can be written to a [`Keystore`](tor-keymgr::Keystore).

// @Diziet's notes regarding why we shouldn't be storing public keys in the key store:
//
// "Let me talk through, a bit, why we have public keys here.
//
// ISTM that primarily, a keystore is a store of secrets (ie, things we use to demonstrate to other
// people).  Ie it corresponds to our identities.  It's not a registry of public data (including
// instructions about who to trust for what).
//
// But we do need to store some ancillary data with some of our identities.  Where this data is
// small or convenient, we can put it into the keystore.  And when we do that we can use the same
// storage format as we use for private keys?  (That's not actually true: openssh private keys and
// openssh public keys are different formats.)
//
// Which public keys are you anticipating storing here?
//
// I would like to rule out, at this stage, using the Arti keystore to store the public keys of
// clients for our HS.  That is, the HS client discovery keys for a particular HS should be
// stored, for that HS, outside the keystore.  (IIRC C Tor does keep the client discovery public keys
// for an HS in its keystore, so we need to be compatible with that, but that doesn't necessary
// have to be done in Arti via the keystore API.  Perhaps the "C Tor keystore" object could
// implement both the keystore trait and an "HS client public keys" trait.)
//
// I should explain why I have this opinion:
// Basically, (private) keystores are awkward.  They have to handle private key material, deal with
// privsep (possibly including offline hosts); they have to be transparent and manipulable, but
// also secure.  They might need to be implemented by or associated with HSMs.  All of these things
// make the keystore's APIs (both the caller API and the visible filesystem interface) compromises
// with nontrivial downsides.
//
// Whereas data about who we should trust is totally different.  It can live in normal
// configuration land; it doesn't need to be associated with HSMs.  It doesn't want or need (the
// possibility of) privsep.  And the user might want to override/supplement it in totally different
// ways.  For example, it should be possible for an HS server to look up client discovery keys
// in a database.  But we don't need or want to write a generic "look up stuff in a database" API;
// that can be (at least for now) a bespoke API just for HS restricted discovery."

use ssh_key::private::KeypairData;
use ssh_key::public::KeyData;
use ssh_key::Algorithm;
use tor_error::internal;

use crate::ssh::{ED25519_EXPANDED_ALGORITHM_NAME, X25519_ALGORITHM_NAME};
use crate::Result;

/// Declare and implement the `KeyType` enum.
///
/// Each of the `variant`s is mapped to the specified `str_repr`.
///
/// `str_repr` is returned from [`KeyType::arti_extension`].
///
/// The `str_repr` is also used for implementing `From<&str>` for `KeyType`.
/// Note `KeyType` implements `From<&str>` rather than `FromStr`,
/// because the conversion from string is infallible
/// (unrecognized strings are mapped to `KeyType::Unknown`)
macro_rules! declare_key_type {
    {
        $(#[$enum_meta:meta])*
        $vis:vis enum KeyType {
            $(
                $(#[$meta:meta])*
                $variant:ident => $str_repr:expr,
            )*
        }
    } => {

        $(#[$enum_meta])*
        $vis enum KeyType {
            $(
                $(#[$meta])*
                $variant,
            )*

            /// An unrecognized key type.
            Unknown {
                /// The extension used for keys of this type in an Arti keystore.
                arti_extension: String,
            },
        }

        impl KeyType {
            /// The file extension for a key of this type.
            pub fn arti_extension(&self) -> String {
                use KeyType::*;

                match self {
                    $(
                        $variant => $str_repr.into(),
                    )*
                    Unknown { arti_extension } => arti_extension.clone(),
                }
            }
        }

        impl From<&str> for KeyType {
            fn from(key_type: &str) -> Self {
                use KeyType::*;

                match key_type {
                    $(
                        $str_repr => $variant,
                    )*
                    _ => Unknown {
                        arti_extension: key_type.into(),
                    },
                }
            }
        }
    }
}

impl KeyType {
    /// Return the `KeyType` of the specified [`KeyData`].
    ///
    /// Returns an error if the [`KeyData`] is of an unsupported type.
    pub(crate) fn try_from_key_data(key: &KeyData) -> Result<KeyType> {
        match key.algorithm() {
            Algorithm::Ed25519 => Ok(KeyType::Ed25519PublicKey),
            Algorithm::Other(algo) if algo.as_str() == X25519_ALGORITHM_NAME => {
                Ok(KeyType::X25519PublicKey)
            }
            _ => Err(internal!("invalid key data").into()),
        }
    }

    /// Return the `KeyType` of the specified [`KeypairData`].
    ///
    /// Returns an error if the [`KeypairData`] is of an unsupported type.
    pub(crate) fn try_from_keypair_data(key: &KeypairData) -> Result<KeyType> {
        let algo = key.algorithm().map_err(|e| internal!("invalid algr {e}"))?;
        match algo {
            Algorithm::Ed25519 => Ok(KeyType::Ed25519Keypair),
            Algorithm::Other(algo) if algo.as_str() == X25519_ALGORITHM_NAME => {
                Ok(KeyType::X25519StaticKeypair)
            }
            Algorithm::Other(algo) if algo.as_str() == ED25519_EXPANDED_ALGORITHM_NAME => {
                Ok(KeyType::Ed25519ExpandedKeypair)
            }
            _ => Err(internal!("invalid keypair data").into()),
        }
    }
}

declare_key_type! {
    /// A type of key stored in the key store.
    #[derive(Clone, Debug, PartialEq, Eq, Hash)]
    #[non_exhaustive]
    pub enum KeyType {
        /// An Ed25519 keypair.
        Ed25519Keypair => "ed25519_private",
        /// An Ed25519 public key.
        Ed25519PublicKey => "ed25519_public",
        /// A Curve25519 keypair.
        X25519StaticKeypair => "x25519_private",
        /// A Curve25519 public key.
        X25519PublicKey => "x25519_public",
        /// An expanded Ed25519 keypair.
        Ed25519ExpandedKeypair => "ed25519_expanded_private",
    }
}

#[cfg(test)]
mod tests {
    // @@ begin test lint list maintained by maint/add_warning @@
    #![allow(clippy::bool_assert_comparison)]
    #![allow(clippy::clone_on_copy)]
    #![allow(clippy::dbg_macro)]
    #![allow(clippy::mixed_attributes_style)]
    #![allow(clippy::print_stderr)]
    #![allow(clippy::print_stdout)]
    #![allow(clippy::single_char_pattern)]
    #![allow(clippy::unwrap_used)]
    #![allow(clippy::unchecked_duration_subtraction)]
    #![allow(clippy::useless_vec)]
    #![allow(clippy::needless_pass_by_value)]
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
    use super::*;

    #[test]
    fn unknown_key_types() {
        const UNKNOWN_KEY_TYPE: &str = "rsa";

        let unknown_key_ty = KeyType::from(UNKNOWN_KEY_TYPE);
        assert_eq!(
            unknown_key_ty,
            KeyType::Unknown {
                arti_extension: UNKNOWN_KEY_TYPE.into()
            }
        );
        assert_eq!(unknown_key_ty.arti_extension(), UNKNOWN_KEY_TYPE);
    }
}