sunset/
sign.rs

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