1use 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
21pub struct Ed25519KeyPair {
23 signing_key: SigningKey,
24 version: String,
26}
27
28impl Ed25519KeyPair {
29 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 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 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 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 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 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 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 pub fn version(&self) -> &str {
147 &self.version
148 }
149
150 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#[derive(Debug, Error)]
180#[non_exhaustive]
181pub enum Ed25519KeyPairParseError {
182 #[error("algorithm OID does not match ed25519 algorithm: expected {expected}, found {found}")]
186 InvalidOid {
187 expected: ObjectIdentifier,
189
190 found: ObjectIdentifier,
192 },
193
194 #[error("invalid ed25519 secret key length: expected {expected}, found {found}")]
196 InvalidSecretKeyLength {
197 expected: usize,
199
200 found: usize,
202 },
203
204 #[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 derived: Vec<u8>,
210
211 parsed: Vec<u8>,
213 },
214
215 #[error("invalid PKCS#8 document: {0}")]
217 Pkcs8(#[from] pkcs8::Error),
218}
219
220#[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#[derive(Debug, Error)]
250#[non_exhaustive]
251pub enum Ed25519VerificationError {
252 #[error("Invalid ed25519 public key: {0}")]
254 InvalidPublicKey(#[source] ed25519_dalek::SignatureError),
255
256 #[error("Invalid ed25519 signature length: expected {expected}, found {found}")]
258 InvalidSignatureLength {
259 expected: usize,
261
262 found: usize,
264 },
265
266 #[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}