Skip to main content

ruma_signatures/
ed25519.rs

1//! Types for the `ed25519` signing algorithm.
2
3use std::fmt;
4
5use ed25519_dalek::{
6    PUBLIC_KEY_LENGTH, SecretKey, Signer, SigningKey, Verifier as _,
7    VerifyingKey as Ed25519VerifyingKey, ed25519::Signature as Ed25519Signature,
8    pkcs8::ALGORITHM_OID,
9};
10use pkcs8::{
11    DecodePrivateKey, EncodePrivateKey, ObjectIdentifier, PrivateKeyInfoRef,
12    der::zeroize::Zeroizing,
13};
14use rand::TryCryptoRng;
15use ruma_common::{SigningKeyAlgorithm, SigningKeyId};
16use thiserror::Error;
17
18use crate::{KeyPair, Signature, verify::Verifier};
19
20#[cfg(feature = "ring-compat")]
21mod compat;
22
23/// An Ed25519 key pair.
24pub struct Ed25519KeyPair {
25    signing_key: SigningKey,
26    /// The specific name of the key pair.
27    version: String,
28}
29
30impl Ed25519KeyPair {
31    /// Create a key pair from its constituent parts.
32    pub fn new(
33        oid: ObjectIdentifier,
34        privkey: &[u8],
35        pubkey: Option<&[u8]>,
36        version: String,
37    ) -> Result<Self, Ed25519KeyPairParseError> {
38        if oid != ALGORITHM_OID {
39            return Err(Ed25519KeyPairParseError::InvalidOid {
40                expected: ALGORITHM_OID,
41                found: oid,
42            });
43        }
44
45        let secret_key = Self::correct_privkey_from_octolet(privkey)?;
46        let signing_key = SigningKey::from_bytes(secret_key);
47
48        if let Some(oak_key) = pubkey {
49            // If the document had a public key, we're verifying it.
50            let verifying_key = signing_key.verifying_key();
51
52            if oak_key != verifying_key.as_bytes() {
53                return Err(Ed25519KeyPairParseError::PublicKeyMismatch {
54                    derived: verifying_key.as_bytes().to_vec(),
55                    parsed: oak_key.to_owned(),
56                });
57            }
58        }
59
60        Ok(Self { signing_key, version })
61    }
62
63    /// Initializes a new key pair.
64    ///
65    /// # Parameters
66    ///
67    /// * `document`: PKCS#8 v1/v2 DER-formatted document containing the private (and optionally
68    ///   public) key.
69    /// * `version`: The "version" of the key used for this signature. Versions are used as an
70    ///   identifier to distinguish signatures generated from different keys but using the same
71    ///   algorithm on the same homeserver.
72    ///
73    /// # Errors
74    ///
75    /// Returns an error if the public and private keys provided are invalid for the implementing
76    /// algorithm.
77    ///
78    /// Returns an error when the PKCS#8 document had a public key, but it doesn't match the one
79    /// generated from the private key. This is a fallback and extra validation against
80    /// corruption or
81    pub fn from_der(document: &[u8], version: String) -> Result<Self, Ed25519KeyPairParseError> {
82        #[cfg(feature = "ring-compat")]
83        use self::compat::CompatibleDocument;
84
85        let signing_key;
86
87        #[cfg(feature = "ring-compat")]
88        {
89            signing_key = match CompatibleDocument::from_bytes(document) {
90                CompatibleDocument::WellFormed(bytes) => SigningKey::from_pkcs8_der(bytes)?,
91                CompatibleDocument::CleanedFromRing(vec) => SigningKey::from_pkcs8_der(&vec)?,
92            }
93        }
94        #[cfg(not(feature = "ring-compat"))]
95        {
96            signing_key = SigningKey::from_pkcs8_der(document)?;
97        }
98
99        Ok(Self { signing_key, version })
100    }
101
102    /// Constructs a key pair from [`pkcs8::PrivateKeyInfoRef`].
103    pub fn from_pkcs8(
104        oak: PrivateKeyInfoRef<'_>,
105        version: String,
106    ) -> Result<Self, Ed25519KeyPairParseError> {
107        Self::new(
108            oak.algorithm.oid,
109            oak.private_key.as_ref(),
110            oak.public_key.and_then(|key| key.as_bytes()),
111            version,
112        )
113    }
114
115    /// PKCS#8's "private key" is not yet actually the entire key,
116    /// so convert it if it is wrongly formatted.
117    ///
118    /// See [RFC 8310 10.3](https://datatracker.ietf.org/doc/html/rfc8410#section-10.3) for more details
119    fn correct_privkey_from_octolet(key: &[u8]) -> Result<&SecretKey, Ed25519KeyPairParseError> {
120        if key.len() == 34 && key[..2] == [0x04, 0x20] {
121            Ok(key[2..].try_into().unwrap())
122        } else {
123            key.try_into().map_err(|_| Ed25519KeyPairParseError::InvalidSecretKeyLength {
124                expected: ed25519_dalek::SECRET_KEY_LENGTH,
125                found: key.len(),
126            })
127        }
128    }
129
130    /// Generates a new key pair.
131    ///
132    /// # Returns
133    ///
134    /// Returns a `Vec<u8>` representing a DER-encoded PKCS#8 v2 document (with public key).
135    ///
136    /// # Panics
137    ///
138    /// Panics if the system RNG returns an error.
139    pub fn generate() -> Zeroizing<Vec<u8>> {
140        let signing_key = generate_signing_key(&mut rand::rngs::SysRng).unwrap();
141        signing_key.to_pkcs8_der().unwrap().to_bytes()
142    }
143
144    /// Returns the version string for this keypair.
145    pub fn version(&self) -> &str {
146        &self.version
147    }
148
149    /// Returns the public key.
150    pub fn public_key(&self) -> [u8; PUBLIC_KEY_LENGTH] {
151        self.signing_key.verifying_key().to_bytes()
152    }
153}
154
155// Copy of SigningKey::generate, updated to use `TryCryptoRng`
156// from current rand instead of the old `CryptoRngCore`.
157fn generate_signing_key<R: TryCryptoRng + ?Sized>(csprng: &mut R) -> Result<SigningKey, R::Error> {
158    let mut secret = SecretKey::default();
159    csprng.try_fill_bytes(&mut secret)?;
160    Ok(SigningKey::from_bytes(&secret))
161}
162
163impl KeyPair for Ed25519KeyPair {
164    fn sign(&self, message: &[u8]) -> Signature {
165        Signature {
166            key_id: SigningKeyId::from_parts(
167                SigningKeyAlgorithm::Ed25519,
168                self.version.as_str().into(),
169            ),
170            signature: self.signing_key.sign(message).to_bytes().to_vec(),
171        }
172    }
173}
174
175impl fmt::Debug for Ed25519KeyPair {
176    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
177        formatter
178            .debug_struct("Ed25519KeyPair")
179            .field("verifying_key", &self.signing_key.verifying_key().as_bytes())
180            .field("version", &self.version)
181            .finish()
182    }
183}
184
185/// An error encountered when constructing an [`Ed25519KeyPair`] from its constituent parts.
186#[derive(Debug, Error)]
187#[non_exhaustive]
188pub enum Ed25519KeyPairParseError {
189    /// The ASN.1 Object Identifier on a PKCS#8 document doesn't match the expected one.
190    ///
191    /// This can happen when the document describes a RSA key, while an ed25519 key was expected.
192    #[error("algorithm OID does not match ed25519 algorithm: expected {expected}, found {found}")]
193    InvalidOid {
194        /// The expected OID.
195        expected: ObjectIdentifier,
196
197        /// The OID that was found instead.
198        found: ObjectIdentifier,
199    },
200
201    /// The length of the ed25519 secret key is invalid.
202    #[error("invalid ed25519 secret key length: expected {expected}, found {found}")]
203    InvalidSecretKeyLength {
204        /// The expected length of the secret key.
205        expected: usize,
206
207        /// The actual size of the secret key.
208        found: usize,
209    },
210
211    /// The public key found in a PKCS#8 v2 document doesn't match the public key derived from its
212    /// private key.
213    #[error("PKCS#8 Document public key does not match public key derived from private key: derived {0:X?} (len {}), parsed {1:X?} (len {})", .derived.len(), .parsed.len())]
214    PublicKeyMismatch {
215        /// The key derived from the private key.
216        derived: Vec<u8>,
217
218        /// The key found in the document.
219        parsed: Vec<u8>,
220    },
221
222    /// An error occurred when parsing a PKCS#8 document.
223    #[error("invalid PKCS#8 document: {0}")]
224    Pkcs8(#[from] pkcs8::Error),
225}
226
227/// A verifier for Ed25519 digital signatures.
228#[derive(Debug, Default)]
229pub(crate) struct Ed25519Verifier;
230
231impl Verifier for Ed25519Verifier {
232    type Error = Ed25519VerificationError;
233
234    fn verify_json(
235        &self,
236        public_key: &[u8],
237        signature: &[u8],
238        message: &[u8],
239    ) -> Result<(), Self::Error> {
240        Ed25519VerifyingKey::try_from(public_key)
241            .map_err(Ed25519VerificationError::InvalidPublicKey)?
242            .verify(
243                message,
244                &Ed25519Signature::from_bytes(&signature.try_into().map_err(|_| {
245                    Ed25519VerificationError::InvalidSignatureLength {
246                        expected: Ed25519Signature::BYTE_SIZE,
247                        found: signature.len(),
248                    }
249                })?),
250            )
251            .map_err(Ed25519VerificationError::SignatureVerification)
252    }
253}
254
255/// Errors relating to the verification of ed25519 signatures.
256#[derive(Debug, Error)]
257#[non_exhaustive]
258pub enum Ed25519VerificationError {
259    /// The provided ed25519 public key is invalid.
260    #[error("Invalid ed25519 public key: {0}")]
261    InvalidPublicKey(#[source] ed25519_dalek::SignatureError),
262
263    /// The provided signature has an invalid length.
264    #[error("Invalid ed25519 signature length: expected {expected}, found {found}")]
265    InvalidSignatureLength {
266        /// The expected length of the signature.
267        expected: usize,
268
269        /// The actual length of the signature.
270        found: usize,
271    },
272
273    /// The signature verification failed.
274    #[error("ed25519 signature verification failed: {0}")]
275    SignatureVerification(#[source] ed25519_dalek::SignatureError),
276}
277
278#[cfg(test)]
279mod tests {
280    use super::Ed25519KeyPair;
281
282    const WELL_FORMED_DOC: &[u8] = &[
283        0x30, 0x72, 0x02, 0x01, 0x01, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x70, 0x04, 0x22, 0x04,
284        0x20, 0xD4, 0xEE, 0x72, 0xDB, 0xF9, 0x13, 0x58, 0x4A, 0xD5, 0xB6, 0xD8, 0xF1, 0xF7, 0x69,
285        0xF8, 0xAD, 0x3A, 0xFE, 0x7C, 0x28, 0xCB, 0xF1, 0xD4, 0xFB, 0xE0, 0x97, 0xA8, 0x8F, 0x44,
286        0x75, 0x58, 0x42, 0xA0, 0x1F, 0x30, 0x1D, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D,
287        0x01, 0x09, 0x09, 0x14, 0x31, 0x0F, 0x0C, 0x0D, 0x43, 0x75, 0x72, 0x64, 0x6C, 0x65, 0x20,
288        0x43, 0x68, 0x61, 0x69, 0x72, 0x73, 0x81, 0x21, 0x00, 0x19, 0xBF, 0x44, 0x09, 0x69, 0x84,
289        0xCD, 0xFE, 0x85, 0x41, 0xBA, 0xC1, 0x67, 0xDC, 0x3B, 0x96, 0xC8, 0x50, 0x86, 0xAA, 0x30,
290        0xB6, 0xB6, 0xCB, 0x0C, 0x5C, 0x38, 0xAD, 0x70, 0x31, 0x66, 0xE1,
291    ];
292
293    const WELL_FORMED_PUBKEY: &[u8] = &[
294        0x19, 0xBF, 0x44, 0x09, 0x69, 0x84, 0xCD, 0xFE, 0x85, 0x41, 0xBA, 0xC1, 0x67, 0xDC, 0x3B,
295        0x96, 0xC8, 0x50, 0x86, 0xAA, 0x30, 0xB6, 0xB6, 0xCB, 0x0C, 0x5C, 0x38, 0xAD, 0x70, 0x31,
296        0x66, 0xE1,
297    ];
298
299    #[test]
300    fn generate_key() {
301        // Should not panic.
302        Ed25519KeyPair::generate();
303    }
304
305    #[test]
306    fn well_formed_key() {
307        let keypair = Ed25519KeyPair::from_der(WELL_FORMED_DOC, "".to_owned()).unwrap();
308
309        assert_eq!(keypair.public_key(), WELL_FORMED_PUBKEY);
310    }
311
312    #[cfg(feature = "ring-compat")]
313    mod ring_compat {
314        use super::Ed25519KeyPair;
315
316        const RING_DOC: &[u8] = &[
317            0x30, 0x53, 0x02, 0x01, 0x01, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x70, 0x04, 0x22,
318            0x04, 0x20, 0x61, 0x9E, 0xD8, 0x25, 0xA6, 0x1D, 0x32, 0x29, 0xD7, 0xD8, 0x22, 0x03,
319            0xC6, 0x0E, 0x37, 0x48, 0xE9, 0xC9, 0x11, 0x96, 0x3B, 0x03, 0x15, 0x94, 0x19, 0x3A,
320            0x86, 0xEC, 0xE6, 0x2D, 0x73, 0xC0, 0xA1, 0x23, 0x03, 0x21, 0x00, 0x3D, 0xA6, 0xC8,
321            0xD1, 0x76, 0x2F, 0xD6, 0x49, 0xB8, 0x4F, 0xF6, 0xC6, 0x1D, 0x04, 0xEA, 0x4A, 0x70,
322            0xA8, 0xC9, 0xF0, 0x8F, 0x96, 0x7F, 0x6B, 0xD7, 0xDA, 0xE5, 0x2E, 0x88, 0x8D, 0xBA,
323            0x3E,
324        ];
325
326        const RING_PUBKEY: &[u8] = &[
327            0x3D, 0xA6, 0xC8, 0xD1, 0x76, 0x2F, 0xD6, 0x49, 0xB8, 0x4F, 0xF6, 0xC6, 0x1D, 0x04,
328            0xEA, 0x4A, 0x70, 0xA8, 0xC9, 0xF0, 0x8F, 0x96, 0x7F, 0x6B, 0xD7, 0xDA, 0xE5, 0x2E,
329            0x88, 0x8D, 0xBA, 0x3E,
330        ];
331
332        #[test]
333        fn ring_key() {
334            let keypair = Ed25519KeyPair::from_der(RING_DOC, "".to_owned()).unwrap();
335
336            assert_eq!(keypair.public_key(), RING_PUBKEY);
337        }
338    }
339}