1use ssh_key::{
4 Algorithm, LineEnding, PrivateKey, PublicKey, private::KeypairData, public::KeyData,
5};
6use tor_error::{internal, into_internal};
7use tor_llcrypto::pk::{curve25519, ed25519, rsa};
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 convert_rsa_kp,
78 KeypairData
79 )
80 }};
81
82 (PUBLIC $key:expr, $algo:expr) => {{
83 ssh_to_internal_erased!(
84 $key,
85 $algo,
86 convert_ed25519_pk,
87 convert_expanded_ed25519_pk,
88 convert_x25519_pk,
89 convert_rsa_pk,
90 KeyData
91 )
92 }};
93
94 ($key:expr, $algo:expr, $ed25519_fn:path, $expanded_ed25519_fn:path, $x25519_fn:path, $rsa_fn:path, $key_data_ty:tt) => {{
95 let key = $key;
96 let algo = SshKeyAlgorithm::from($algo);
97
98 match key {
101 $key_data_ty::Ed25519(key) => Ok($ed25519_fn(&key).map(Box::new)?),
102 $key_data_ty::Rsa(key) => Ok($rsa_fn(&key).map(Box::new)?),
103 $key_data_ty::Other(other) => match algo {
104 SshKeyAlgorithm::X25519 => Ok($x25519_fn(&other).map(Box::new)?),
105 SshKeyAlgorithm::Ed25519Expanded => Ok($expanded_ed25519_fn(&other).map(Box::new)?),
106 _ => Err(Error::UnsupportedKeyAlgorithm(algo)),
107 },
108 _ => Err(Error::UnsupportedKeyAlgorithm(algo)),
109 }
110 }};
111}
112
113#[allow(clippy::unnecessary_fallible_conversions)]
119fn convert_ed25519_kp(key: &ssh_key::private::Ed25519Keypair) -> Result<ed25519::Keypair> {
120 Ok(ed25519::Keypair::try_from(&key.private.to_bytes())
121 .map_err(|_| internal!("bad ed25519 keypair"))?)
122}
123
124fn convert_x25519_kp(key: &ssh_key::private::OpaqueKeypair) -> Result<curve25519::StaticKeypair> {
126 let public: [u8; 32] = key
127 .public
128 .as_ref()
129 .try_into()
130 .map_err(|_| internal!("bad x25519 public key length"))?;
131
132 let secret: [u8; 32] = key
133 .private
134 .as_ref()
135 .try_into()
136 .map_err(|_| internal!("bad x25519 secret key length"))?;
137
138 Ok(curve25519::StaticKeypair {
139 public: public.into(),
140 secret: secret.into(),
141 })
142}
143
144fn convert_expanded_ed25519_kp(
146 key: &ssh_key::private::OpaqueKeypair,
147) -> Result<ed25519::ExpandedKeypair> {
148 let public = ed25519::PublicKey::try_from(key.public.as_ref())
149 .map_err(|_| internal!("bad expanded ed25519 public key "))?;
150
151 let keypair = ed25519::ExpandedKeypair::from_secret_key_bytes(
152 key.private
153 .as_ref()
154 .try_into()
155 .map_err(|_| internal!("bad length on expanded ed25519 secret key ",))?,
156 )
157 .ok_or_else(|| internal!("bad expanded ed25519 secret key "))?;
158
159 if &public != keypair.public() {
160 return Err(internal!("mismatched ed25519 keypair",).into());
161 }
162
163 Ok(keypair)
164}
165
166fn convert_rsa_kp(key: &ssh_key::private::RsaKeypair) -> Result<rsa::KeyPair> {
168 Ok(TryInto::<::rsa::RsaPrivateKey>::try_into(key)
176 .map_err(|_| internal!("bad RSA keypair"))?
177 .into())
178}
179
180fn convert_ed25519_pk(key: &ssh_key::public::Ed25519PublicKey) -> Result<ed25519::PublicKey> {
182 Ok(ed25519::PublicKey::from_bytes(key.as_ref())
183 .map_err(|_| internal!("bad ed25519 public key "))?)
184}
185
186fn convert_expanded_ed25519_pk(
192 _key: &ssh_key::public::OpaquePublicKey,
193) -> Result<ed25519::PublicKey> {
194 Err(internal!(
195 "invalid ed25519 public key (ed25519 public keys should be stored as ssh-ed25519)",
196 )
197 .into())
198}
199
200fn convert_x25519_pk(key: &ssh_key::public::OpaquePublicKey) -> Result<curve25519::PublicKey> {
202 let public: [u8; 32] = key
203 .as_ref()
204 .try_into()
205 .map_err(|_| internal!("bad x25519 public key length"))?;
206
207 Ok(curve25519::PublicKey::from(public))
208}
209
210fn convert_rsa_pk(key: &ssh_key::public::RsaPublicKey) -> Result<rsa::PublicKey> {
212 Ok(TryInto::<::rsa::RsaPublicKey>::try_into(key)
213 .map_err(|_| internal!("bad RSA keypair"))?
214 .into())
215}
216
217#[derive(Clone, Debug)]
219#[non_exhaustive]
220pub struct SshKeyData(SshKeyDataInner);
221
222#[derive(Clone, Debug)]
224#[non_exhaustive]
225enum SshKeyDataInner {
226 Public(KeyData),
228 Private(KeypairData),
230}
231
232impl SshKeyData {
233 pub fn try_from_key_data(key: KeyData) -> Result<Self> {
237 let algo = SshKeyAlgorithm::from(key.algorithm());
238 let () = match key {
239 KeyData::Ed25519(_) => Ok(()),
240 KeyData::Rsa(_) => Ok(()),
241 KeyData::Other(_) => match algo {
242 SshKeyAlgorithm::X25519 => Ok(()),
243 _ => Err(Error::UnsupportedKeyAlgorithm(algo)),
244 },
245 _ => Err(Error::UnsupportedKeyAlgorithm(algo)),
246 }?;
247
248 Ok(Self(SshKeyDataInner::Public(key)))
249 }
250
251 pub fn try_from_keypair_data(key: KeypairData) -> Result<Self> {
255 let algo = SshKeyAlgorithm::from(
256 key.algorithm()
257 .map_err(into_internal!("encrypted keys are not yet supported"))?,
258 );
259 let () = match key {
260 KeypairData::Ed25519(_) => Ok(()),
261 KeypairData::Rsa(_) => Ok(()),
262 KeypairData::Other(_) => match algo {
263 SshKeyAlgorithm::X25519 => Ok(()),
264 SshKeyAlgorithm::Ed25519Expanded => Ok(()),
265 _ => Err(Error::UnsupportedKeyAlgorithm(algo)),
266 },
267 _ => Err(Error::UnsupportedKeyAlgorithm(algo)),
268 }?;
269
270 Ok(Self(SshKeyDataInner::Private(key)))
271 }
272
273 pub fn to_openssh_string(&self, comment: &str) -> Result<String> {
275 let openssh_key = match &self.0 {
276 SshKeyDataInner::Public(key_data) => {
277 let openssh_key = PublicKey::new(key_data.clone(), comment);
278
279 openssh_key
280 .to_openssh()
281 .map_err(|_| tor_error::internal!("failed to encode SSH key"))?
282 }
283 SshKeyDataInner::Private(keypair) => {
284 let openssh_key = PrivateKey::new(keypair.clone(), comment)
285 .map_err(|_| tor_error::internal!("failed to create SSH private key"))?;
286
287 openssh_key
288 .to_openssh(LineEnding::LF)
289 .map_err(|_| tor_error::internal!("failed to encode SSH key"))?
290 .to_string()
291 }
292 };
293
294 Ok(openssh_key)
295 }
296
297 pub fn into_erased(self) -> Result<ErasedKey> {
302 match self.0 {
303 SshKeyDataInner::Private(key) => {
304 let algorithm = key
305 .algorithm()
306 .map_err(into_internal!("unsupported key type"))?;
307 ssh_to_internal_erased!(PRIVATE key, algorithm)
308 }
309 SshKeyDataInner::Public(key) => {
310 let algorithm = key.algorithm();
311 ssh_to_internal_erased!(PUBLIC key, algorithm)
312 }
313 }
314 }
315
316 pub fn key_type(&self) -> Result<KeyType> {
321 match &self.0 {
322 SshKeyDataInner::Public(k) => KeyType::try_from_key_data(k),
323 SshKeyDataInner::Private(k) => KeyType::try_from_keypair_data(k),
324 }
325 }
326}