keygen_openssh_test/
main.rs

1mod args;
2
3use args::{Args, KeyType};
4use tor_llcrypto::pk::ed25519::Ed25519PublicKey as _;
5use tor_llcrypto::util::rng::RngCompat;
6
7use std::fs;
8
9use clap::Parser;
10
11use ssh_key::private::{DsaKeypair, Ed25519Keypair, Ed25519PrivateKey, OpaqueKeypair};
12use ssh_key::public::{DsaPublicKey, Ed25519PublicKey, OpaquePublicKey};
13use ssh_key::{self, Algorithm, AlgorithmName, PrivateKey, PublicKey};
14use tor_basic_utils::test_rng::testing_rng;
15use tor_llcrypto::pk::{curve25519, ed25519};
16
17/// A helper for creating a ([`PrivateKey`], [`PublicKey`]) pair.
18macro_rules! make_openssh_key {
19    ($kind:tt, $args:expr, $keypair:expr, $public:expr) => {{
20        let comment = $args.comment.clone().unwrap_or("test-key".into());
21        let openssh_key = ssh_key::public::PublicKey::new(
22            ssh_key::public::KeyData::$kind($public),
23            comment.clone(),
24        );
25        let openssh_private = ssh_key::private::PrivateKey::new(
26            ssh_key::private::KeypairData::$kind($keypair),
27            comment,
28        )
29        .unwrap();
30
31        (openssh_private, openssh_key)
32    }};
33}
34
35/// Generate an ed25519-expanded ssh key.
36fn generate_expanded_ed25519(args: &Args) -> (PrivateKey, PublicKey) {
37    let algo = args
38        .algorithm
39        .clone()
40        .unwrap_or("ed25519-expanded@spec.torproject.org".into());
41    let algorithm_name = AlgorithmName::new(algo).unwrap();
42
43    let mut rng = testing_rng();
44    let ed25519_kp = ed25519::Keypair::generate(&mut rng);
45    let expanded_kp: ed25519::ExpandedKeypair = (&ed25519_kp).into();
46    let ssh_public = OpaquePublicKey::new(
47        expanded_kp.public().to_bytes().to_vec(),
48        Algorithm::Other(algorithm_name),
49    );
50    let keypair = OpaqueKeypair::new(
51        expanded_kp.to_secret_key_bytes().to_vec(),
52        ssh_public.clone(),
53    );
54
55    make_openssh_key!(Other, args, keypair, ssh_public)
56}
57
58/// Generate an ed25519-expanded ssh key.
59fn generate_ed25519(args: &Args) -> (PrivateKey, PublicKey) {
60    let mut rng = testing_rng();
61    let ed25519_kp = ed25519::Keypair::generate(&mut rng);
62    let public_key_bytes: [u8; 32] = ed25519_kp
63        .public_key()
64        .to_bytes()
65        .to_vec()
66        .try_into()
67        .unwrap();
68    let public = Ed25519PublicKey(public_key_bytes);
69    let secret_key_bytes: [u8; 32] = ed25519_kp.to_bytes().to_vec().try_into().unwrap();
70    let private = Ed25519PrivateKey::from_bytes(&secret_key_bytes);
71    let keypair = Ed25519Keypair { public, private };
72
73    make_openssh_key!(Ed25519, args, keypair, public)
74}
75
76/// Generate a DSA ssh key.
77fn generate_dsa(args: &Args) -> (PrivateKey, PublicKey) {
78    let mut rng = RngCompat::new(testing_rng());
79    let keypair = DsaKeypair::random(&mut rng).unwrap();
80    let public = DsaPublicKey::from(&keypair);
81
82    make_openssh_key!(Dsa, args, keypair, public)
83}
84
85/// Generate an x25519 ssh key.
86fn generate_x25519(args: &Args) -> (PrivateKey, PublicKey) {
87    let rng = testing_rng();
88    let x25519_sk = curve25519::StaticSecret::random_from_rng(rng);
89    let x25519_pk = curve25519::PublicKey::from(&x25519_sk);
90
91    let algo = args
92        .algorithm
93        .clone()
94        .unwrap_or("x25519@spec.torproject.org".into());
95    let algorithm_name = AlgorithmName::new(algo).unwrap();
96
97    let public = OpaquePublicKey::new(
98        x25519_pk.to_bytes().to_vec(),
99        Algorithm::Other(algorithm_name),
100    );
101    let keypair = OpaqueKeypair::new(x25519_sk.to_bytes().to_vec(), public.clone());
102
103    make_openssh_key!(Other, args, keypair, public)
104}
105
106fn main() {
107    let args = Args::parse();
108
109    // Figure out if we're generating a public key, a private key, or both.
110    let (gen_pub, gen_priv) = match (args.public, args.private) {
111        (false, false) => {
112            // If neither --public nor --private is specified, generate both.
113            (true, true)
114        }
115        (gen_pub, gen_priv) => (gen_pub, gen_priv),
116    };
117
118    let (openssh_private, openssh_public) = match &args.key_type {
119        KeyType::ExpandedEd25519 => generate_expanded_ed25519(&args),
120        KeyType::Ed25519 => generate_ed25519(&args),
121        KeyType::Dsa => generate_dsa(&args),
122        KeyType::X25519 => generate_x25519(&args),
123    };
124
125    let public = openssh_public.to_openssh().unwrap();
126    let private = openssh_private
127        .to_openssh(ssh_key::LineEnding::LF)
128        .unwrap()
129        .to_string();
130
131    let pub_file = format!("{}.public", args.name);
132    let priv_file = format!("{}.private", args.name);
133
134    if gen_pub {
135        fs::write(&pub_file, public).unwrap();
136        println!("created {pub_file}");
137    }
138
139    if gen_priv {
140        fs::write(&priv_file, private).unwrap();
141        println!("created {priv_file}");
142    }
143}