Skip to main content

aauth_protocol/
keys.rs

1//! The Ed25519 keys this crate signs with: an agent's session key, which its
2//! agent token's `cnf.jwk` binds, and a server's token-signing key.
3
4use base64::engine::general_purpose::URL_SAFE_NO_PAD;
5use base64::Engine;
6use ed25519_dalek::pkcs8::{DecodePrivateKey, EncodePrivateKey};
7use ed25519_dalek::Signer;
8
9use crate::error::{Error, Result};
10use crate::jwk::{Algorithm, Jwk, Jwks};
11
12/// An Ed25519 private key, with its public JWK and thumbprint.
13///
14/// ```
15/// use aauth_protocol::SigningKey;
16///
17/// let key = SigningKey::generate();
18/// let again = SigningKey::from_pkcs8_pem(&key.to_pkcs8_pem()?)?;
19/// assert_eq!(key.thumbprint(), again.thumbprint());
20/// # Ok::<(), aauth_protocol::Error>(())
21/// ```
22pub struct SigningKey {
23    key: ed25519_dalek::SigningKey,
24    jwk: Jwk,
25    thumbprint: String,
26}
27
28impl std::fmt::Debug for SigningKey {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        f.debug_struct("SigningKey")
31            .field("thumbprint", &self.thumbprint)
32            .finish_non_exhaustive()
33    }
34}
35
36impl SigningKey {
37    /// A fresh key from the operating system's randomness.
38    pub fn generate() -> Self {
39        let mut seed = [0u8; 32];
40        getrandom::fill(&mut seed).expect("the operating system provides randomness");
41        Self::from_bytes(&seed)
42    }
43
44    /// The key a 32-byte seed derives.
45    pub fn from_bytes(seed: &[u8; 32]) -> Self {
46        Self::new(ed25519_dalek::SigningKey::from_bytes(seed))
47    }
48
49    /// A PKCS#8 PEM (`-----BEGIN PRIVATE KEY-----`).
50    pub fn from_pkcs8_pem(pem: &str) -> Result<Self> {
51        ed25519_dalek::SigningKey::from_pkcs8_pem(pem)
52            .map(Self::new)
53            .map_err(|e| Error::Key(e.to_string()))
54    }
55
56    /// The key as a PKCS#8 PEM. It is secret: store it like a password.
57    pub fn to_pkcs8_pem(&self) -> Result<String> {
58        self.key
59            .to_pkcs8_pem(Default::default())
60            .map(|pem| pem.to_string())
61            .map_err(|e| Error::Key(e.to_string()))
62    }
63
64    fn new(key: ed25519_dalek::SigningKey) -> Self {
65        let jwk = Jwk {
66            kty: "OKP".into(),
67            crv: Some("Ed25519".into()),
68            x: Some(URL_SAFE_NO_PAD.encode(key.verifying_key().as_bytes())),
69            alg: Some(Algorithm::Ed25519.as_str().into()),
70            ..Default::default()
71        };
72        let thumbprint = jwk.thumbprint().expect("an OKP JWK has a thumbprint");
73        Self {
74            key,
75            jwk,
76            thumbprint,
77        }
78    }
79
80    /// RFC 7638 thumbprint of the public key.
81    pub fn thumbprint(&self) -> &str {
82        &self.thumbprint
83    }
84
85    /// The algorithm it signs with: always [`Algorithm::Ed25519`].
86    pub fn algorithm(&self) -> Algorithm {
87        Algorithm::Ed25519
88    }
89
90    /// The public key, with `alg`, as a `cnf.jwk` carries it.
91    pub fn public_jwk(&self) -> Jwk {
92        self.jwk.clone()
93    }
94
95    /// The public key as a one-key JWKS, its `kid` the thumbprint.
96    pub fn jwks(&self) -> Jwks {
97        Jwks {
98            keys: vec![Jwk {
99                kid: Some(self.thumbprint.clone()),
100                use_: Some("sig".into()),
101                ..self.jwk.clone()
102            }],
103        }
104    }
105
106    /// Sign `message`; the 64-byte signature.
107    pub fn sign(&self, message: &[u8]) -> Vec<u8> {
108        self.key.sign(message).to_bytes().to_vec()
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn pem_round_trips() {
118        let key = SigningKey::generate();
119        let again = SigningKey::from_pkcs8_pem(&key.to_pkcs8_pem().unwrap()).unwrap();
120        assert_eq!(key.thumbprint(), again.thumbprint());
121    }
122
123    #[test]
124    fn a_signature_verifies_under_the_public_jwk() {
125        let key = SigningKey::generate();
126        let signature = key.sign(b"hello");
127        assert!(key
128            .public_jwk()
129            .verify(Algorithm::Ed25519, b"hello", &signature));
130        assert!(!key
131            .public_jwk()
132            .verify(Algorithm::Ed25519, b"hellO", &signature));
133    }
134}