jwt_compact/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,
6    SEED_LENGTH, 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(
40    docsrs,
41    doc(cfg(any(
42        feature = "exonum-crypto",
43        feature = "ed25519-dalek",
44        feature = "ed25519-compact"
45    )))
46)]
47pub struct Ed25519;
48
49impl Ed25519 {
50    /// Creates an algorithm instance with the algorithm name specified as `Ed25519`.
51    /// This is a non-standard name, but it is used in some apps.
52    pub fn with_specific_name() -> Renamed<Self> {
53        Renamed::new(Self, "Ed25519")
54    }
55}
56
57impl Algorithm for Ed25519 {
58    type SigningKey = SecretKey;
59    type VerifyingKey = PublicKey;
60    type Signature = Signature;
61
62    fn name(&self) -> Cow<'static, str> {
63        Cow::Borrowed("EdDSA")
64    }
65
66    fn sign(&self, signing_key: &Self::SigningKey, message: &[u8]) -> Self::Signature {
67        sign(message, signing_key)
68    }
69
70    fn verify_signature(
71        &self,
72        signature: &Self::Signature,
73        verifying_key: &Self::VerifyingKey,
74        message: &[u8],
75    ) -> bool {
76        verify(signature, message, verifying_key)
77    }
78}
79
80impl VerifyingKey<Ed25519> for PublicKey {
81    fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
82        Self::from_slice(raw).ok_or_else(|| format_err!("Invalid public key length"))
83    }
84
85    fn as_bytes(&self) -> Cow<'_, [u8]> {
86        Cow::Borrowed(self.as_ref())
87    }
88}
89
90impl SigningKey<Ed25519> for SecretKey {
91    fn from_slice(raw: &[u8]) -> anyhow::Result<Self> {
92        Self::from_slice(raw).ok_or_else(|| format_err!("Invalid secret key bytes"))
93    }
94
95    fn to_verifying_key(&self) -> PublicKey {
96        // Slightly hacky. The backend does not expose functions for converting secret keys
97        // to public ones, and we don't want to use `KeyPair` instead of `SecretKey`
98        // for this single purpose.
99        PublicKey::from_slice(&self[SEED_LENGTH..]).unwrap()
100    }
101
102    fn as_bytes(&self) -> SecretBytes<'_> {
103        SecretBytes::borrowed(&self[..])
104    }
105}
106
107impl<'a> From<&'a PublicKey> for JsonWebKey<'a> {
108    fn from(key: &'a PublicKey) -> JsonWebKey<'a> {
109        JsonWebKey::KeyPair {
110            curve: Cow::Borrowed("Ed25519"),
111            x: Cow::Borrowed(key.as_ref()),
112            secret: None,
113        }
114    }
115}
116
117impl TryFrom<&JsonWebKey<'_>> for PublicKey {
118    type Error = JwkError;
119
120    fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
121        let JsonWebKey::KeyPair { curve, x, .. } = jwk else {
122            return Err(JwkError::key_type(jwk, KeyType::KeyPair));
123        };
124
125        JsonWebKey::ensure_curve(curve, "Ed25519")?;
126        JsonWebKey::ensure_len("x", x, PUBLIC_KEY_LENGTH)?;
127        Ok(PublicKey::from_slice(x).unwrap())
128        // ^ unlike some other impls, libsodium does not check public key validity on creation
129    }
130}
131
132impl<'a> From<&'a SecretKey> for JsonWebKey<'a> {
133    fn from(key: &'a SecretKey) -> JsonWebKey<'a> {
134        JsonWebKey::KeyPair {
135            curve: Cow::Borrowed("Ed25519"),
136            x: Cow::Borrowed(&key[SEED_LENGTH..]),
137            secret: Some(SecretBytes::borrowed(&key[..SEED_LENGTH])),
138        }
139    }
140}
141
142impl TryFrom<&JsonWebKey<'_>> for SecretKey {
143    type Error = JwkError;
144
145    fn try_from(jwk: &JsonWebKey<'_>) -> Result<Self, Self::Error> {
146        let JsonWebKey::KeyPair { secret, .. } = jwk else {
147            return Err(JwkError::key_type(jwk, KeyType::KeyPair));
148        };
149        let seed_bytes = secret.as_deref();
150        let seed_bytes = seed_bytes.ok_or_else(|| JwkError::NoField("d".to_owned()))?;
151
152        JsonWebKey::ensure_len("d", seed_bytes, SEED_LENGTH)?;
153        let seed = Seed::from_slice(seed_bytes).unwrap();
154        let (_, sk) = gen_keypair_from_seed(&seed);
155        jwk.ensure_key_match(sk)
156    }
157}