1use 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
12pub 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 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 pub fn from_bytes(seed: &[u8; 32]) -> Self {
46 Self::new(ed25519_dalek::SigningKey::from_bytes(seed))
47 }
48
49 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 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 pub fn thumbprint(&self) -> &str {
82 &self.thumbprint
83 }
84
85 pub fn algorithm(&self) -> Algorithm {
87 Algorithm::Ed25519
88 }
89
90 pub fn public_jwk(&self) -> Jwk {
92 self.jwk.clone()
93 }
94
95 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 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}