jwt_compact_frame/alg/
eddsa_sodium.rs

1//! `EdDSA` algorithm implementation using the `exonum-crypto` crate.
2
3use anyhow::format_err;
4use exonum_crypto::{
5	gen_keypair_from_seed, sign, verify, PublicKey, SecretKey, Seed, Signature, PUBLIC_KEY_LENGTH, SEED_LENGTH,
6	SIGNATURE_LENGTH,
7};
8
9use core::num::NonZeroUsize;
10
11use crate::{
12	alg::{SecretBytes, SigningKey, VerifyingKey},
13	alloc::Cow,
14	jwk::{JsonWebKey, JwkError, KeyType},
15	Algorithm, AlgorithmSignature, Renamed,
16};
17
18impl AlgorithmSignature for Signature {
19	const LENGTH: Option<NonZeroUsize> = NonZeroUsize::new(SIGNATURE_LENGTH);
20
21	fn try_from_slice(bytes: &[u8]) -> anyhow::Result<Self> {
22		// There are no checks other than by signature length in `from_slice`,
23		// so the `unwrap()` below is safe.
24		Ok(Self::from_slice(bytes).unwrap())
25	}
26
27	fn as_bytes(&self) -> Cow<'_, [u8]> {
28		Cow::Borrowed(self.as_ref())
29	}
30}
31
32/// Integrity algorithm using digital signatures on the Ed25519 elliptic curve.
33///
34/// The name of the algorithm is specified as `EdDSA` as per [IANA registry].
35/// Use `with_specific_name()` to switch to non-standard `Ed25519`.
36///
37/// [IANA registry]: https://www.iana.org/assignments/jose/jose.xhtml
38#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
39#[cfg_attr(docsrs, doc(cfg(any(feature = "exonum-crypto", feature = "ed25519-dalek", feature = "ed25519-compact"))))]
40pub struct Ed25519;
41
42impl Ed25519 {
43	/// Creates an algorithm instance with the algorithm name specified as `Ed25519`.
44	/// This is a non-standard name, but it is used in some apps.
45	pub fn with_specific_name() -> Renamed<Self> {
46		Renamed::new(Self, "Ed25519")
47	}
48}
49
50impl Algorithm for Ed25519 {
51	type Signature = Signature;
52	type SigningKey = SecretKey;
53	type VerifyingKey = PublicKey;
54
55	fn name(&self) -> Cow<'static, str> {
56		Cow::Borrowed("EdDSA")
57	}
58
59	fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
60		sign(message, signing_key)
61	}
62
63	fn verify_signature(
64		&self,
65		signature: &Self::Signature,
66		verifying_key: &Self::VerifyingKey,
67		message: &[u8],
68	) -> bool {
69		verify(signature, message, verifying_key)
70	}
71}
72
73impl VerifyingKey<Ed25519> for PublicKey {
74	fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
75		Self::from_slice(raw).ok_or_else(|| format_err!("Invalid public key length"))
76	}
77
78	fn as_bytes(&self) -> Cow<'_, [u8]> {
79		Cow::Borrowed(self.as_ref())
80	}
81}
82
83impl SigningKey<Ed25519> for SecretKey {
84	fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
85		Self::from_slice(raw).ok_or_else(|| format_err!("Invalid secret key bytes"))
86	}
87
88	fn to_verifying_key(&self) -> PublicKey {
89		// Slightly hacky. The backend does not expose functions for converting secret keys
90		// to public ones, and we don't want to use `KeyPair` instead of `SecretKey`
91		// for this single purpose.
92		PublicKey::from_slice(&self[SEED_LENGTH..]).unwrap()
93	}
94
95	fn as_bytes(&self) -> SecretBytes<'_> {
96		SecretBytes::borrowed(&self[..])
97	}
98}
99
100impl<'a> From<&'a PublicKey> for JsonWebKey<'a> {
101	fn from(key: &'a PublicKey) -> JsonWebKey<'a> {
102		JsonWebKey::KeyPair { curve: Cow::Borrowed("Ed25519"), x: Cow::Borrowed(key.as_ref()), secret: None }
103	}
104}
105
106impl TryFrom<&JsonWebKey<'_>> for PublicKey {
107	type Error = JwkError;
108
109	fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
110		let JsonWebKey::KeyPair { curve, x, .. } = jwk else {
111			return Err(JwkError::key_type(jwk, KeyType::KeyPair));
112		};
113
114		JsonWebKey::ensure_curve(curve, "Ed25519")?;
115		JsonWebKey::ensure_len("x", x, PUBLIC_KEY_LENGTH)?;
116		Ok(PublicKey::from_slice(x).unwrap())
117		// ^ unlike some other impls, libsodium does not check public key validity on creation
118	}
119}
120
121impl<'a> From<&'a SecretKey> for JsonWebKey<'a> {
122	fn from(key: &'a SecretKey) -> JsonWebKey<'a> {
123		JsonWebKey::KeyPair {
124			curve: Cow::Borrowed("Ed25519"),
125			x: Cow::Borrowed(&key[SEED_LENGTH..]),
126			secret: Some(SecretBytes::borrowed(&key[..SEED_LENGTH])),
127		}
128	}
129}
130
131impl TryFrom<&JsonWebKey<'_>> for SecretKey {
132	type Error = JwkError;
133
134	fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
135		let JsonWebKey::KeyPair { secret, .. } = jwk else {
136			return Err(JwkError::key_type(jwk, KeyType::KeyPair));
137		};
138		let seed_bytes = secret.as_deref();
139		let seed_bytes = seed_bytes.ok_or_else(|| JwkError::NoField("d".to_owned()))?;
140
141		JsonWebKey::ensure_len("d", seed_bytes, SEED_LENGTH)?;
142		let seed = Seed::from_slice(seed_bytes).unwrap();
143		let (_, sk) = gen_keypair_from_seed(&seed);
144		jwk.ensure_key_match(sk)
145	}
146}