git-ents.gitmain
⌘K
foforge
keys.rs79 lines · 2.4 KB · rusthistorycomment on this file
1//! Deterministic SSH signing keys for fixtures.
2
3use ssh_key::private::{Ed25519Keypair, KeypairData};
4use ssh_key::{HashAlg, LineEnding, PrivateKey};
5
6/// The SSHSIG namespace git uses when signing commits with an SSH key.
7pub const GIT_SIGN_NAMESPACE: &str = "git";
8
9/// A deterministic ed25519 keypair for signing fixture commits.
10///
11/// Deterministic (seeded) rather than random so property tests and
12/// build-the-fixture-twice "clone" tests reproduce byte-identical objects.
13///
14/// # Examples
15///
16/// ```
17/// use ents_testutil::Keypair;
18///
19/// let a = Keypair::from_seed(1);
20/// let b = Keypair::from_seed(1);
21/// assert_eq!(a.public_openssh(), b.public_openssh());
22/// assert_ne!(a.public_openssh(), Keypair::from_seed(2).public_openssh());
23/// ```
24#[derive(Debug)]
25pub struct Keypair {
26 private: PrivateKey,
27}
28
29impl Keypair {
30 /// Derive a keypair from a one-byte seed (repeated to fill the ed25519
31 /// seed), so tests can name keys `1`, `2`, ... and always get the same
32 /// key back.
33 #[must_use]
34 pub fn from_seed(seed: u8) -> Self {
35 let pair = Ed25519Keypair::from_seed(&[seed; 32]);
36 let private = PrivateKey::new(KeypairData::from(pair), "ents-testutil")
37 .expect("a well-formed ed25519 keypair is always accepted");
38 Self { private }
39 }
40
41 /// The public half in OpenSSH single-line format — exactly what a
42 /// [`ents_model::Member`]'s `key` field carries.
43 ///
44 /// # Examples
45 ///
46 /// ```
47 /// use ents_testutil::Keypair;
48 ///
49 /// let key = Keypair::from_seed(1).public_openssh();
50 /// assert!(key.starts_with("ssh-ed25519 "));
51 /// ```
52 #[must_use]
53 pub fn public_openssh(&self) -> String {
54 self.private
55 .public_key()
56 .to_openssh()
57 .expect("an ed25519 public key always renders")
58 }
59
60 /// Sign `payload` in git's SSHSIG namespace, returning the armored
61 /// signature block git would store in a commit's `gpgsig` header.
62 ///
63 /// # Examples
64 ///
65 /// ```
66 /// use ents_testutil::Keypair;
67 ///
68 /// let pem = Keypair::from_seed(1).sign(b"payload");
69 /// assert!(pem.starts_with("-----BEGIN SSH SIGNATURE-----"));
70 /// ```
71 #[must_use]
72 pub fn sign(&self, payload: &[u8]) -> String {
73 self.private
74 .sign(GIT_SIGN_NAMESPACE, HashAlg::Sha512, payload)
75 .expect("ed25519 signing is infallible for any payload")
76 .to_pem(LineEnding::LF)
77 .expect("an SSHSIG always renders as PEM")
78 }
79}