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