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