actpub_httpsig/key/
mod.rs1mod ed25519;
9mod multikey;
10mod pem;
11mod rsa;
12
13use std::fmt;
14
15pub use self::ed25519::{Ed25519PublicKey, Ed25519SigningKey};
16pub use self::multikey::Multikey;
17use self::pem::{
18 ed25519_public_key_from_pem, ed25519_public_key_to_pem, ed25519_signing_key_from_pem,
19 ed25519_signing_key_to_pem, rsa_public_key_from_pem, rsa_public_key_to_pem,
20 rsa_signing_key_from_pem, rsa_signing_key_to_pem,
21};
22pub use self::rsa::{RsaBits, RsaPublicKey, RsaSigningKey};
23use crate::error::Error;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30#[non_exhaustive]
31pub enum Algorithm {
32 RsaSha256,
34 Ed25519,
36}
37
38impl Algorithm {
39 #[must_use]
41 pub const fn name(self) -> &'static str {
42 match self {
43 Self::RsaSha256 => "rsa-sha256",
44 Self::Ed25519 => "ed25519",
45 }
46 }
47
48 pub fn parse(name: &str) -> Result<Option<Self>, Error> {
62 match name {
63 "rsa-sha256" | "rsa-v1_5-sha256" => Ok(Some(Self::RsaSha256)),
64 "ed25519" | "ed25519-sha512" => Ok(Some(Self::Ed25519)),
65 "hs2019" => Ok(None),
66 other => Err(Error::UnsupportedAlgorithm(other.to_owned())),
67 }
68 }
69}
70
71#[non_exhaustive]
73pub enum SigningKey {
74 Ed25519(Ed25519SigningKey),
76 Rsa(RsaSigningKey),
78}
79
80impl SigningKey {
81 #[must_use]
91 pub fn generate_ed25519() -> Self {
92 #[allow(
93 clippy::expect_used,
94 reason = "the system RNG is a hard dependency of every supported platform; a failure here indicates a broken host and is unrecoverable"
95 )]
96 let key = Ed25519SigningKey::generate().expect("system RNG must be available for Ed25519");
97 Self::Ed25519(key)
98 }
99
100 pub fn generate_rsa(bits: RsaBits) -> Result<Self, Error> {
106 RsaSigningKey::generate(bits).map(Self::Rsa)
107 }
108
109 pub fn from_pem(pem_text: &str) -> Result<Self, Error> {
117 match ed25519_signing_key_from_pem(pem_text) {
121 Ok(k) => Ok(Self::Ed25519(k)),
122 Err(Error::UnsupportedAlgorithm(_) | Error::UnexpectedPemLabel(_, _)) => {
123 rsa_signing_key_from_pem(pem_text).map(Self::Rsa)
124 }
125 Err(e) => Err(e),
126 }
127 }
128
129 #[must_use]
131 pub fn to_pem(&self) -> String {
132 match self {
133 Self::Ed25519(k) => ed25519_signing_key_to_pem(k),
134 Self::Rsa(k) => rsa_signing_key_to_pem(k),
135 }
136 }
137
138 #[must_use]
140 pub const fn algorithm(&self) -> Algorithm {
141 match self {
142 Self::Ed25519(_) => Algorithm::Ed25519,
143 Self::Rsa(_) => Algorithm::RsaSha256,
144 }
145 }
146
147 #[must_use]
149 pub fn verifying_key(&self) -> VerifyingKey {
150 match self {
151 Self::Ed25519(k) => VerifyingKey::Ed25519(k.public_key()),
152 Self::Rsa(k) => VerifyingKey::Rsa(k.public_key()),
153 }
154 }
155
156 pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, Error> {
162 match self {
163 Self::Ed25519(k) => Ok(k.sign(message)),
164 Self::Rsa(k) => k.sign(message),
165 }
166 }
167}
168
169impl fmt::Debug for SigningKey {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 f.debug_tuple("SigningKey")
172 .field(&self.algorithm())
173 .finish()
174 }
175}
176
177#[derive(Debug, Clone, PartialEq, Eq)]
179#[non_exhaustive]
180pub enum VerifyingKey {
181 Ed25519(Ed25519PublicKey),
183 Rsa(RsaPublicKey),
185}
186
187impl VerifyingKey {
188 pub fn from_pem(pem_text: &str) -> Result<Self, Error> {
195 match ed25519_public_key_from_pem(pem_text) {
196 Ok(k) => Ok(Self::Ed25519(k)),
197 Err(Error::UnsupportedAlgorithm(_) | Error::UnexpectedPemLabel(_, _)) => {
198 rsa_public_key_from_pem(pem_text).map(Self::Rsa)
199 }
200 Err(e) => Err(e),
201 }
202 }
203
204 #[must_use]
206 pub fn to_pem(&self) -> String {
207 match self {
208 Self::Ed25519(k) => ed25519_public_key_to_pem(k),
209 Self::Rsa(k) => rsa_public_key_to_pem(k),
210 }
211 }
212
213 #[must_use]
215 pub const fn algorithm(&self) -> Algorithm {
216 match self {
217 Self::Ed25519(_) => Algorithm::Ed25519,
218 Self::Rsa(_) => Algorithm::RsaSha256,
219 }
220 }
221
222 pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), Error> {
229 match self {
230 Self::Ed25519(k) => k.verify(message, signature),
231 Self::Rsa(k) => k.verify(message, signature),
232 }
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use pretty_assertions::assert_eq;
239
240 use super::*;
241
242 #[test]
243 fn ed25519_pem_roundtrip_through_top_level_enum() {
244 let key = SigningKey::generate_ed25519();
245 let pem = key.to_pem();
246 let reloaded = SigningKey::from_pem(&pem).expect("reload");
247 assert_eq!(reloaded.algorithm(), Algorithm::Ed25519);
248 assert_eq!(
249 key.verifying_key(),
250 reloaded.verifying_key(),
251 "verifying keys must match after PEM roundtrip",
252 );
253 }
254
255 #[test]
256 fn rsa_pem_roundtrip_through_top_level_enum() {
257 let key = SigningKey::generate_rsa(RsaBits::Rsa2048).expect("rng");
258 let pem = key.to_pem();
259 let reloaded = SigningKey::from_pem(&pem).expect("reload");
260 assert_eq!(reloaded.algorithm(), Algorithm::RsaSha256);
261 }
262
263 #[test]
264 fn sign_and_verify_through_enum_dispatch() {
265 for key in [
266 SigningKey::generate_ed25519(),
267 SigningKey::generate_rsa(RsaBits::Rsa2048).expect("rng"),
268 ] {
269 let msg = b"payload";
270 let sig = key.sign(msg).expect("sign");
271 key.verifying_key()
272 .verify(msg, &sig)
273 .expect("verify must succeed for the matching key");
274 }
275 }
276
277 #[test]
278 fn algorithm_parse_handles_known_names() {
279 assert_eq!(
280 Algorithm::parse("rsa-sha256").expect("parse"),
281 Some(Algorithm::RsaSha256),
282 );
283 assert_eq!(
285 Algorithm::parse("rsa-v1_5-sha256").expect("parse"),
286 Some(Algorithm::RsaSha256),
287 );
288 assert_eq!(
289 Algorithm::parse("ed25519").expect("parse"),
290 Some(Algorithm::Ed25519),
291 );
292 assert_eq!(
293 Algorithm::parse("ed25519-sha512").expect("parse"),
294 Some(Algorithm::Ed25519),
295 );
296 assert_eq!(Algorithm::parse("hs2019").expect("parse"), None);
298 }
299
300 #[test]
301 fn algorithm_parse_rejects_unknown_names() {
302 let err = Algorithm::parse("hmac-sha256").expect_err("unknown algorithm");
303 assert!(matches!(err, Error::UnsupportedAlgorithm(_)));
304 }
305}