Skip to main content

crypto/
ed25519.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Ed25519 signature implementation.
3
4use ed25519_dalek::{Signature, Signer as EdSigner, SigningKey, Verifier, VerifyingKey};
5use rsa::rand_core::OsRng;
6
7use crate::{Signer, SignerError};
8
9/// Ed25519 signer.
10pub struct Ed25519Signer {
11    signing_key: SigningKey,
12}
13
14impl Ed25519Signer {
15    pub fn generate() -> Result<Self, SignerError> {
16        Ok(Self {
17            signing_key: SigningKey::generate(&mut OsRng),
18        })
19    }
20
21    pub fn from_pem(pem: &str) -> Result<Self, SignerError> {
22        if pem.contains("-----BEGIN PRIVATE KEY-----") {
23            return Self::from_pkcs8_pem(pem);
24        }
25        if pem.contains("-----BEGIN OPENSSH PRIVATE KEY-----") {
26            return Self::from_openssh_pem(pem);
27        }
28
29        let trimmed = pem.trim();
30        if let Ok(bytes) = hex::decode(trimmed)
31            && bytes.len() == 32
32        {
33            return Self::from_seed(&bytes);
34        }
35
36        if let Ok(bytes) = base64_decode(trimmed) {
37            if bytes.len() == 32 {
38                return Self::from_seed(&bytes);
39            }
40            if bytes.len() == 64 {
41                return Self::from_seed(&bytes[..32]);
42            }
43        }
44
45        Err(SignerError::UnknownKeyFormat)
46    }
47
48    pub fn from_seed(seed: &[u8]) -> Result<Self, SignerError> {
49        let seed_bytes: [u8; 32] = seed
50            .try_into()
51            .map_err(|_| SignerError::InvalidKey("seed must be 32 bytes".to_string()))?;
52        let signing_key = SigningKey::from_bytes(&seed_bytes);
53        Ok(Self { signing_key })
54    }
55
56    fn from_pkcs8_pem(pem: &str) -> Result<Self, SignerError> {
57        use pkcs8::DecodePrivateKey;
58
59        let signing_key = SigningKey::from_pkcs8_pem(pem)?;
60        Ok(Self { signing_key })
61    }
62
63    fn from_openssh_pem(_pem: &str) -> Result<Self, SignerError> {
64        Err(SignerError::Pem(
65            "OpenSSH Ed25519 private keys are not yet supported".to_string(),
66        ))
67    }
68
69    pub fn to_pem(&self) -> Result<String, SignerError> {
70        use pkcs8::EncodePrivateKey;
71
72        self.signing_key
73            .to_pkcs8_pem(pkcs8::LineEnding::LF)
74            .map(|pem| pem.to_string())
75            .map_err(|e| SignerError::Pkcs8(e.to_string()))
76    }
77
78    pub fn verify_with_public_key(
79        data: &[u8],
80        public_key: &[u8],
81        signature: &[u8],
82    ) -> Result<(), SignerError> {
83        let verifying_key = VerifyingKey::from_bytes(public_key.try_into().map_err(|_| {
84            SignerError::InvalidPublicKey("public key must be 32 bytes".to_string())
85        })?)?;
86        let signature = Signature::from_slice(signature)
87            .map_err(|_| SignerError::InvalidSignature("signature must be 64 bytes".to_string()))?;
88        verifying_key
89            .verify(data, &signature)
90            .map_err(|_| SignerError::VerificationFailed)
91    }
92}
93
94impl Signer for Ed25519Signer {
95    fn algorithm(&self) -> &'static str {
96        "ed25519"
97    }
98
99    fn public_key(&self) -> Vec<u8> {
100        self.signing_key.verifying_key().to_bytes().to_vec()
101    }
102
103    fn sign(&self, data: &[u8]) -> Result<Vec<u8>, SignerError> {
104        let signature = self.signing_key.sign(data);
105        Ok(signature.to_bytes().to_vec())
106    }
107
108    fn verify(&self, data: &[u8], signature: &[u8]) -> Result<(), SignerError> {
109        Self::verify_with_public_key(data, &self.public_key(), signature)
110    }
111}
112
113fn base64_decode(input: &str) -> Result<Vec<u8>, SignerError> {
114    use base64::Engine;
115    base64::engine::general_purpose::STANDARD
116        .decode(input)
117        .map_err(|e| SignerError::Pem(e.to_string()))
118}