sunset/
sign.rs

1#[allow(unused_imports)]
2use {
3    crate::error::*,
4    log::{debug, error, info, log, trace, warn},
5};
6
7use core::ops::Deref;
8
9use ed25519_dalek as dalek;
10use ed25519_dalek::{Signer, Verifier};
11use zeroize::ZeroizeOnDrop;
12
13use crate::*;
14use packets::{Ed25519PubKey, Ed25519Sig, PubKey, Signature};
15use sshnames::*;
16use sshwire::{BinString, Blob, SSHEncode};
17
18use pretty_hex::PrettyHex;
19
20use core::mem::discriminant;
21
22use digest::Digest;
23
24// TODO remove once we use byupdate.
25// signatures are for hostkey (32 byte sessiid) or pubkey (auth packet || sessid).
26// we assume a max 40 character username here.
27const MAX_SIG_MSG: usize = 1+4+40+4+14+4+9+1+4+SSH_NAME_CURVE25519_LIBSSH.len()+4+32+32;
28
29// RSA requires alloc.
30#[cfg(feature = "rsa")]
31use packets::RSAPubKey;
32#[cfg(feature = "rsa")]
33use rsa::signature::{DigestSigner, DigestVerifier};
34
35#[derive(Debug, Clone, Copy)]
36pub enum SigType {
37    Ed25519,
38    #[cfg(feature = "rsa")]
39    RSA,
40    // Ecdsa
41}
42
43impl SigType {
44    /// Must be a valid name
45    pub fn from_name(name: &'static str) -> Result<Self> {
46        match name {
47            SSH_NAME_ED25519 => Ok(SigType::Ed25519),
48            #[cfg(feature = "rsa")]
49            SSH_NAME_RSA_SHA256 => Ok(SigType::RSA),
50            _ => Err(Error::bug()),
51        }
52    }
53
54    /// Returns a valid name
55    pub fn algorithm_name(&self) -> &'static str {
56        match self {
57            SigType::Ed25519 => SSH_NAME_ED25519,
58            #[cfg(feature = "rsa")]
59            SigType::RSA => SSH_NAME_RSA_SHA256,
60        }
61    }
62
63    /// Returns `Ok(())` on success
64    pub fn verify(
65        &self,
66        pubkey: &PubKey,
67        msg: &dyn SSHEncode,
68        sig: &Signature,
69    ) -> Result<()> {
70        // Check that the signature type is known
71        let sig_type = sig.sig_type().map_err(|_| Error::BadSig)?;
72
73        // `self` is the expected signature type from kex/auth packet
74        // This would also get caught by SignatureMismatch below
75        // but that error message is intended for mismatch key vs sig.
76        if discriminant(&sig_type) != discriminant(self) {
77            warn!(
78                "Received {:?} signature, expecting {}",
79                sig.algorithm_name(),
80                self.algorithm_name()
81            );
82            return Err(Error::BadSig);
83        }
84
85        match (self, pubkey, sig) {
86            (SigType::Ed25519, PubKey::Ed25519(k), Signature::Ed25519(s)) => {
87                Self::verify_ed25519(k, msg, s)
88            }
89
90            #[cfg(feature = "rsa")]
91            (SigType::RSA, PubKey::RSA(k), Signature::RSA(s)) => {
92                Self::verify_rsa(k, msg, s)
93            }
94
95            _ => {
96                warn!(
97                    "Signature \"{:?}\" doesn't match key type \"{:?}\"",
98                    sig.algorithm_name(),
99                    pubkey.algorithm_name(),
100                );
101                Err(Error::BadSig)
102            }
103        }
104    }
105
106    fn verify_ed25519(
107        k: &Ed25519PubKey,
108        msg: &dyn SSHEncode,
109        s: &Ed25519Sig,
110    ) -> Result<()> {
111        let k: &[u8; 32] = &k.key.0;
112        let k = dalek::VerifyingKey::from_bytes(k).map_err(|_| Error::BadKey)?;
113
114        let s: &[u8; 64] = s.sig.0.try_into().map_err(|_| Error::BadSig)?;
115        let s: dalek::Signature = s.into();
116        // TODO: pending merge of https://github.com/dalek-cryptography/curve25519-dalek/pull/556
117        // In the interim we use a fixed buffer.
118        // dalek::hazmat::raw_verify_byupdate(
119        //     &k,
120        //     |h: &mut sha2::Sha512| {
121        //         sshwire::hash_ser(h, msg).map_err(|_| dalek::SignatureError::new())
122        //     },
123        //     &s,
124        // )
125        // .map_err(|_| Error::BadSig)
126        let mut buf = [0; MAX_SIG_MSG];
127        let l = sshwire::write_ssh(&mut buf, msg)?;
128        let buf = &buf[..l];
129        k.verify(buf, &s).map_err(|_| Error::BadSig)
130    }
131
132    #[cfg(feature = "rsa")]
133    fn verify_rsa(
134        k: &packets::RSAPubKey,
135        msg: &dyn SSHEncode,
136        s: &packets::RSASig,
137    ) -> Result<()> {
138        let verifying_key =
139            rsa::pkcs1v15::VerifyingKey::<sha2::Sha256>::new(
140                k.key.clone(),
141            );
142        let signature = s.sig.0.try_into().map_err(|e| {
143            trace!("RSA bad signature: {e}");
144            Error::BadSig
145        })?;
146
147        let mut h = sha2::Sha256::new();
148        sshwire::hash_ser(&mut h, msg)?;
149        verifying_key.verify_digest(h, &signature).map_err(|e| {
150            trace!("RSA verify failed: {e}");
151            Error::BadSig
152        })
153    }
154}
155
156pub enum OwnedSig {
157    // just store raw bytes here.
158    Ed25519([u8; 64]),
159    #[cfg(feature = "rsa")]
160    RSA(Box<[u8]>),
161}
162
163#[cfg(feature = "rsa")]
164impl From<rsa::pkcs1v15::Signature> for OwnedSig {
165    fn from(s: rsa::pkcs1v15::Signature) -> Self {
166        OwnedSig::RSA(s.into())
167    }
168}
169
170impl TryFrom<Signature<'_>> for OwnedSig {
171    type Error = Error;
172    fn try_from(s: Signature) -> Result<Self> {
173        match s {
174            Signature::Ed25519(s) => {
175                let s: [u8; 64] = s.sig.0.try_into().map_err(|_| Error::BadSig)?;
176                Ok(OwnedSig::Ed25519(s))
177            }
178            #[cfg(feature = "rsa")]
179            Signature::RSA(s) => {
180                let s = s.sig.0.try_into().map_err(|_| Error::BadSig)?;
181                Ok(OwnedSig::RSA(s))
182            }
183            Signature::Unknown(u) => {
184                debug!("Unknown {u} signature");
185                Err(Error::UnknownMethod { kind: "signature" })
186            }
187        }
188    }
189}
190/// Signing key types.
191#[derive(Debug, Clone, Copy)]
192pub enum KeyType {
193    Ed25519,
194    #[cfg(feature = "rsa")]
195    RSA,
196}
197
198/// A SSH signing key.
199///
200/// This may hold the private key part locally
201/// or potentially send the signing requests to an SSH agent or other entity.
202// #[derive(ZeroizeOnDrop, Clone, PartialEq)]
203#[derive(ZeroizeOnDrop, Clone, PartialEq)]
204pub enum SignKey {
205    // TODO: we could just have the 32 byte seed here to save memory, but
206    // computing the public part may be slow.
207    #[zeroize(skip)]
208    Ed25519(dalek::SigningKey),
209
210    #[zeroize(skip)]
211    AgentEd25519(dalek::VerifyingKey),
212
213    #[cfg(feature = "rsa")]
214    // TODO zeroize doesn't seem supported? though BigUint has Zeroize
215    #[zeroize(skip)]
216    RSA(rsa::RsaPrivateKey),
217
218    #[cfg(feature = "rsa")]
219    #[zeroize(skip)]
220    AgentRSA(rsa::RsaPublicKey),
221}
222
223impl SignKey {
224    pub fn generate(ty: KeyType, bits: Option<usize>) -> Result<Self> {
225        match ty {
226            KeyType::Ed25519 => {
227                if bits.unwrap_or(256) != 256 {
228                    return Err(Error::msg("Bad key size"));
229                }
230                let k = dalek::SigningKey::generate(&mut rand_core::OsRng);
231                Ok(Self::Ed25519(k))
232            }
233
234            #[cfg(feature = "rsa")]
235            KeyType::RSA => {
236                let bits = bits.unwrap_or(config::RSA_DEFAULT_KEYSIZE);
237                if bits < config::RSA_MIN_KEYSIZE
238                    || bits > rsa::RsaPublicKey::MAX_SIZE
239                    || (bits % 8 != 0)
240                {
241                    return Err(Error::msg("Bad key size"));
242                }
243
244                let k = rsa::RsaPrivateKey::new(&mut rand_core::OsRng, bits)
245                    .map_err(|e| {
246                        debug!("RSA key generation error {e}");
247                        // RNG shouldn't fail, keysize has been checked
248                        Error::bug()
249                    })?;
250                Ok(Self::RSA(k))
251            }
252        }
253    }
254
255    pub fn pubkey(&self) -> PubKey {
256        match self {
257            SignKey::Ed25519(k) => {
258                let pubk = k.verifying_key().to_bytes();
259                PubKey::Ed25519(Ed25519PubKey { key: Blob(pubk) })
260            }
261
262            SignKey::AgentEd25519(pk) => {
263                PubKey::Ed25519(Ed25519PubKey { key: Blob(pk.to_bytes()) })
264            }
265
266            #[cfg(feature = "rsa")]
267            SignKey::RSA(k) => PubKey::RSA(RSAPubKey { key: k.into() }),
268
269            #[cfg(feature = "rsa")]
270            SignKey::AgentRSA(pk) => PubKey::RSA(RSAPubKey { key: pk.clone() }),
271        }
272    }
273
274    #[cfg(feature = "openssh-key")]
275    pub fn from_openssh(k: impl AsRef<[u8]>) -> Result<Self> {
276        let k = ssh_key::PrivateKey::from_openssh(k)
277            .map_err(|_| Error::msg("Unsupported OpenSSH key"))?;
278
279        k.try_into()
280    }
281
282    pub fn from_agent_pubkey(pk: &PubKey) -> Result<Self> {
283        match pk {
284            PubKey::Ed25519(k) => {
285                let k: dalek::VerifyingKey =
286                    k.key.0.as_slice().try_into().map_err(|_| Error::BadKey)?;
287                Ok(Self::AgentEd25519(k))
288            }
289
290            #[cfg(feature = "rsa")]
291            PubKey::RSA(k) => Ok(Self::AgentRSA(k.key.clone())),
292
293            PubKey::Unknown(_) => Err(Error::msg("Unsupported agent key")),
294        }
295    }
296
297    /// Returns whether this `SignKey` can create a given signature type
298    pub(crate) fn can_sign(&self, sig_type: SigType) -> bool {
299        match self {
300            SignKey::Ed25519(_) | SignKey::AgentEd25519(_) => {
301                matches!(sig_type, SigType::Ed25519)
302            }
303
304            #[cfg(feature = "rsa")]
305            SignKey::RSA(_) | SignKey::AgentRSA(_) => {
306                matches!(sig_type, SigType::RSA)
307            }
308        }
309    }
310
311    pub(crate) fn sign(&self, msg: &impl SSHEncode) -> Result<OwnedSig> {
312        let sig: OwnedSig = match self {
313            SignKey::Ed25519(k) => {
314                // TODO: pending merge of https://github.com/dalek-cryptography/curve25519-dalek/pull/556
315                // let exk: dalek::hazmat::ExpandedSecretKey = (&k.to_bytes()).into();
316                // let sig = dalek::hazmat::raw_sign_byupdate(
317                //     &exk,
318                //     |h: &mut sha2::Sha512| {
319                //         sshwire::hash_ser(h, msg)
320                //             .map_err(|_| dalek::SignatureError::new())
321                //     },
322                //     &k.verifying_key(),
323                // )
324                // .trap()?;
325                let mut buf = [0; MAX_SIG_MSG];
326                let l = sshwire::write_ssh(&mut buf, msg)?;
327                let buf = &buf[..l];
328                let sig = k.sign(buf);
329
330                OwnedSig::Ed25519(sig.to_bytes())
331            }
332
333            #[cfg(feature = "rsa")]
334            SignKey::RSA(k) => {
335                let signing_key =
336                    rsa::pkcs1v15::SigningKey::<sha2::Sha256>::new(
337                        k.clone(),
338                    );
339                let mut h = sha2::Sha256::new();
340                sshwire::hash_ser(&mut h, msg)?;
341                let sig = signing_key.try_sign_digest(h).map_err(|e| {
342                    trace!("RSA signing failed: {e:?}");
343                    Error::bug()
344                })?;
345                OwnedSig::RSA(sig.into())
346            }
347
348            // callers should check for agent keys first
349            SignKey::AgentEd25519(_) => return Error::bug_msg("agent sign"),
350            #[cfg(feature = "rsa")]
351            SignKey::AgentRSA(_) => return Error::bug_msg("agent sign"),
352        };
353
354        // {
355        //     // Faults in signing can expose the private key. We verify the signature
356        //     // just created to avoid this problem.
357        //     // TODO: Maybe this needs to be configurable for slow platforms?
358        //     let vsig: Signature = (&sig).into();
359        //     let sig_type = vsig.sig_type().unwrap();
360        //     sig_type.verify(&self.pubkey(), msg, &vsig, parse_ctx)?;
361        // }
362
363        Ok(sig)
364    }
365
366    pub(crate) fn is_agent(&self) -> bool {
367        match self {
368            SignKey::Ed25519(_) => false,
369            #[cfg(feature = "rsa")]
370            SignKey::RSA(_) => false,
371
372            SignKey::AgentEd25519(_) => true,
373            #[cfg(feature = "rsa")]
374            SignKey::AgentRSA(_) => true,
375        }
376    }
377}
378
379impl core::fmt::Debug for SignKey {
380    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
381        let s = match self {
382            Self::Ed25519(_) => "Ed25519",
383            Self::AgentEd25519(_) => "AgentEd25519",
384            #[cfg(feature = "rsa")]
385            Self::RSA(_) => "RSA",
386            #[cfg(feature = "rsa")]
387            Self::AgentRSA(_) => "AgentRSA",
388        };
389        write!(f, "SignKey::{s}")
390    }
391}
392
393#[cfg(feature = "openssh-key")]
394impl TryFrom<ssh_key::PrivateKey> for SignKey {
395    type Error = Error;
396    fn try_from(k: ssh_key::PrivateKey) -> Result<Self> {
397        match k.key_data() {
398            ssh_key::private::KeypairData::Ed25519(k) => {
399                Ok(SignKey::Ed25519(k.private.to_bytes().into()))
400            }
401
402            #[cfg(feature = "rsa")]
403            ssh_key::private::KeypairData::Rsa(k) => {
404                let primes = vec![
405                    (&k.private.p).try_into().map_err(|_| Error::BadKey)?,
406                    (&k.private.q).try_into().map_err(|_| Error::BadKey)?,
407                ];
408                let key = rsa::RsaPrivateKey::from_components(
409                    (&k.public.n).try_into().map_err(|_| Error::BadKey)?,
410                    (&k.public.e).try_into().map_err(|_| Error::BadKey)?,
411                    (&k.private.d).try_into().map_err(|_| Error::BadKey)?,
412                    primes,
413                )
414                .map_err(|_| Error::BadKey)?;
415                Ok(SignKey::RSA(key))
416            }
417            _ => {
418                debug!("Unknown ssh-key algorithm {}", k.algorithm().as_str());
419                Err(Error::NotAvailable { what: "ssh key algorithm" })
420            },
421        }
422    }
423}
424
425#[cfg(test)]
426pub(crate) mod tests {
427
428    use crate::*;
429    use packets;
430    use sign::*;
431    use sshnames::SSH_NAME_ED25519;
432    use sunsetlog::init_test_log;
433
434    // TODO: tests for sign()/verify() and invalid signatures
435}