1use ssh_key::{
4 private::KeypairData, public::KeyData, Algorithm, LineEnding, PrivateKey, PublicKey,
5};
6use tor_error::{internal, into_internal};
7use tor_llcrypto::pk::{curve25519, ed25519};
8
9use crate::{ErasedKey, Error, KeyType, Result};
10
11pub(crate) const X25519_ALGORITHM_NAME: &str = "x25519@spec.torproject.org";
15
16pub(crate) const ED25519_EXPANDED_ALGORITHM_NAME: &str = "ed25519-expanded@spec.torproject.org";
20
21#[derive(Clone, Debug, PartialEq, derive_more::Display)]
26#[non_exhaustive]
27pub enum SshKeyAlgorithm {
28 Dsa,
30 Ecdsa,
32 Ed25519,
34 Ed25519Expanded,
36 X25519,
38 Rsa,
40 SkEcdsaSha2NistP256,
42 SkEd25519,
44 Unknown(ssh_key::Algorithm),
46}
47
48impl From<Algorithm> for SshKeyAlgorithm {
49 fn from(algo: Algorithm) -> SshKeyAlgorithm {
50 match &algo {
51 Algorithm::Dsa => SshKeyAlgorithm::Dsa,
52 Algorithm::Ecdsa { .. } => SshKeyAlgorithm::Ecdsa,
53 Algorithm::Ed25519 => SshKeyAlgorithm::Ed25519,
54 Algorithm::Rsa { .. } => SshKeyAlgorithm::Rsa,
55 Algorithm::SkEcdsaSha2NistP256 => SshKeyAlgorithm::SkEcdsaSha2NistP256,
56 Algorithm::SkEd25519 => SshKeyAlgorithm::SkEd25519,
57 Algorithm::Other(name) => match name.as_str() {
58 X25519_ALGORITHM_NAME => SshKeyAlgorithm::X25519,
59 ED25519_EXPANDED_ALGORITHM_NAME => SshKeyAlgorithm::Ed25519Expanded,
60 _ => SshKeyAlgorithm::Unknown(algo),
61 },
62 _ => SshKeyAlgorithm::Unknown(algo),
64 }
65 }
66}
67
68macro_rules! ssh_to_internal_erased {
70 (PRIVATE $key:expr, $algo:expr) => {{
71 ssh_to_internal_erased!(
72 $key,
73 $algo,
74 convert_ed25519_kp,
75 convert_expanded_ed25519_kp,
76 convert_x25519_kp,
77 KeypairData
78 )
79 }};
80
81 (PUBLIC $key:expr, $algo:expr) => {{
82 ssh_to_internal_erased!(
83 $key,
84 $algo,
85 convert_ed25519_pk,
86 convert_expanded_ed25519_pk,
87 convert_x25519_pk,
88 KeyData
89 )
90 }};
91
92 ($key:expr, $algo:expr, $ed25519_fn:path, $expanded_ed25519_fn:path, $x25519_fn:path, $key_data_ty:tt) => {{
93 let key = $key;
94 let algo = SshKeyAlgorithm::from($algo);
95
96 match key {
99 $key_data_ty::Ed25519(key) => Ok($ed25519_fn(&key).map(Box::new)?),
100 $key_data_ty::Other(other) => match algo {
101 SshKeyAlgorithm::X25519 => Ok($x25519_fn(&other).map(Box::new)?),
102 SshKeyAlgorithm::Ed25519Expanded => Ok($expanded_ed25519_fn(&other).map(Box::new)?),
103 _ => Err(Error::UnsupportedKeyAlgorithm(algo)),
104 },
105 _ => Err(Error::UnsupportedKeyAlgorithm(algo)),
106 }
107 }};
108}
109
110#[allow(clippy::unnecessary_fallible_conversions)]
116fn convert_ed25519_kp(key: &ssh_key::private::Ed25519Keypair) -> Result<ed25519::Keypair> {
117 Ok(ed25519::Keypair::try_from(&key.private.to_bytes())
118 .map_err(|_| internal!("bad ed25519 keypair"))?)
119}
120
121fn convert_x25519_kp(key: &ssh_key::private::OpaqueKeypair) -> Result<curve25519::StaticKeypair> {
123 let public: [u8; 32] = key
124 .public
125 .as_ref()
126 .try_into()
127 .map_err(|_| internal!("bad x25519 public key length"))?;
128
129 let secret: [u8; 32] = key
130 .private
131 .as_ref()
132 .try_into()
133 .map_err(|_| internal!("bad x25519 secret key length"))?;
134
135 Ok(curve25519::StaticKeypair {
136 public: public.into(),
137 secret: secret.into(),
138 })
139}
140
141fn convert_expanded_ed25519_kp(
143 key: &ssh_key::private::OpaqueKeypair,
144) -> Result<ed25519::ExpandedKeypair> {
145 let public = ed25519::PublicKey::try_from(key.public.as_ref())
146 .map_err(|_| internal!("bad expanded ed25519 public key "))?;
147
148 let keypair = ed25519::ExpandedKeypair::from_secret_key_bytes(
149 key.private
150 .as_ref()
151 .try_into()
152 .map_err(|_| internal!("bad length on expanded ed25519 secret key ",))?,
153 )
154 .ok_or_else(|| internal!("bad expanded ed25519 secret key "))?;
155
156 if &public != keypair.public() {
157 return Err(internal!("mismatched ed25519 keypair",).into());
158 }
159
160 Ok(keypair)
161}
162
163fn convert_ed25519_pk(key: &ssh_key::public::Ed25519PublicKey) -> Result<ed25519::PublicKey> {
165 Ok(ed25519::PublicKey::from_bytes(key.as_ref())
166 .map_err(|_| internal!("bad ed25519 public key "))?)
167}
168
169fn convert_expanded_ed25519_pk(
175 _key: &ssh_key::public::OpaquePublicKey,
176) -> Result<ed25519::PublicKey> {
177 Err(internal!(
178 "invalid ed25519 public key (ed25519 public keys should be stored as ssh-ed25519)",
179 )
180 .into())
181}
182
183fn convert_x25519_pk(key: &ssh_key::public::OpaquePublicKey) -> Result<curve25519::PublicKey> {
185 let public: [u8; 32] = key
186 .as_ref()
187 .try_into()
188 .map_err(|_| internal!("bad x25519 public key length"))?;
189
190 Ok(curve25519::PublicKey::from(public))
191}
192
193#[derive(Clone, Debug)]
195#[non_exhaustive]
196pub struct SshKeyData(SshKeyDataInner);
197
198#[derive(Clone, Debug)]
200#[non_exhaustive]
201enum SshKeyDataInner {
202 Public(KeyData),
204 Private(KeypairData),
206}
207
208impl SshKeyData {
209 pub fn try_from_key_data(key: KeyData) -> Result<Self> {
213 let algo = SshKeyAlgorithm::from(key.algorithm());
214 let () = match key {
215 KeyData::Ed25519(_) => Ok(()),
216 KeyData::Other(_) => match algo {
217 SshKeyAlgorithm::X25519 => Ok(()),
218 _ => Err(Error::UnsupportedKeyAlgorithm(algo)),
219 },
220 _ => Err(Error::UnsupportedKeyAlgorithm(algo)),
221 }?;
222
223 Ok(Self(SshKeyDataInner::Public(key)))
224 }
225
226 pub fn try_from_keypair_data(key: KeypairData) -> Result<Self> {
230 let algo = SshKeyAlgorithm::from(
231 key.algorithm()
232 .map_err(into_internal!("encrypted keys are not yet supported"))?,
233 );
234 let () = match key {
235 KeypairData::Ed25519(_) => Ok(()),
236 KeypairData::Other(_) => match algo {
237 SshKeyAlgorithm::X25519 => Ok(()),
238 SshKeyAlgorithm::Ed25519Expanded => Ok(()),
239 _ => Err(Error::UnsupportedKeyAlgorithm(algo)),
240 },
241 _ => Err(Error::UnsupportedKeyAlgorithm(algo)),
242 }?;
243
244 Ok(Self(SshKeyDataInner::Private(key)))
245 }
246
247 pub fn to_openssh_string(&self, comment: &str) -> Result<String> {
249 let openssh_key = match &self.0 {
250 SshKeyDataInner::Public(key_data) => {
251 let openssh_key = PublicKey::new(key_data.clone(), comment);
252
253 openssh_key
254 .to_openssh()
255 .map_err(|_| tor_error::internal!("failed to encode SSH key"))?
256 }
257 SshKeyDataInner::Private(keypair) => {
258 let openssh_key = PrivateKey::new(keypair.clone(), comment)
259 .map_err(|_| tor_error::internal!("failed to create SSH private key"))?;
260
261 openssh_key
262 .to_openssh(LineEnding::LF)
263 .map_err(|_| tor_error::internal!("failed to encode SSH key"))?
264 .to_string()
265 }
266 };
267
268 Ok(openssh_key)
269 }
270
271 pub fn into_erased(self) -> Result<ErasedKey> {
276 match self.0 {
277 SshKeyDataInner::Private(key) => {
278 let algorithm = key
279 .algorithm()
280 .map_err(into_internal!("unsupported key type"))?;
281 ssh_to_internal_erased!(PRIVATE key, algorithm)
282 }
283 SshKeyDataInner::Public(key) => {
284 let algorithm = key.algorithm();
285 ssh_to_internal_erased!(PUBLIC key, algorithm)
286 }
287 }
288 }
289
290 pub fn key_type(&self) -> Result<KeyType> {
295 match &self.0 {
296 SshKeyDataInner::Public(k) => KeyType::try_from_key_data(k),
297 SshKeyDataInner::Private(k) => KeyType::try_from_keypair_data(k),
298 }
299 }
300}