diff --git a/Cargo.toml b/Cargo.toml
index d9d8206a9b995fb09018e150c54bb4ba8d04b9e2..a918fc6b1594f0aa95d5db22774184d6a6cd44cf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -37,6 +37,7 @@ no-panic = "0.1"
 ascii = { version = "1.0", default-features = false }
 
 getrandom = "0.2"
+rand_core = { version = "0.6", default-features = false, features = ["getrandom"]}
 
 ctr = { version = "0.9", features = ["zeroize"] }
 aes = { version = "0.8", features = ["zeroize"] }
@@ -52,7 +53,9 @@ subtle = { version = "2.4", default-features = false }
 # ed25519/x25519
 # fork allows hashing by parts (sign/verify from sshwire), and zeroize
 salty = { version = "0.2", git = "https://github.com/mkj/salty", branch = "sunset" }
-ssh-key = { version = "0.5", default-features = false, optional = true }
+rsa = { version = "0.8", default-features = false, optional = true, features = ["sha2"] }
+# TODO: getrandom feature is a workaround for missing ssh-key dependency with rsa. fixed in pending 0.6
+ssh-key = { version = "0.5", default-features = false, optional = true, features = ["getrandom"] }
 
 embedded-io = { version = "0.4", optional = true }
 
@@ -60,9 +63,10 @@ embedded-io = { version = "0.4", optional = true }
 pretty-hex = { version = "0.3", default-features = false }
 
 [features]
-std = ["snafu/std", "snafu/backtraces" ]
+std = ["snafu/std", "snafu/backtraces", "rsa"]
+rsa = ["dep:rsa", "ssh-key/rsa"]
 # allows conversion to/from OpenSSH key formats
-openssh-key = ["dep:ssh-key"]
+openssh-key = ["ssh-key"]
 # implements embedded_io::Error for sunset::Error
 embedded-io = ["dep:embedded-io"]
 
diff --git a/async/Cargo.toml b/async/Cargo.toml
index 424553642f19f3bf411af3900ae76f169fabfec8..2eaa8c28bd3e3a066bd825bd74256ca991bfdd7d 100644
--- a/async/Cargo.toml
+++ b/async/Cargo.toml
@@ -36,6 +36,10 @@ heapless = "0.7.10"
 pretty-hex = "0.3"
 # snafu = { version = "0.7", default-features = true }
 
+[features]
+# rsa is implied by sunset/std
+# rsa = ["sunset/rsa"]
+
 [dev-dependencies]
 anyhow = { version = "1.0" }
 pretty-hex = "0.3"
diff --git a/async/src/agent.rs b/async/src/agent.rs
index 9fc0868520e371ee755b9ed950563658565e199f..d022a7c4a0fd1c73dc5fc88b005c4beaf08f3821 100644
--- a/async/src/agent.rs
+++ b/async/src/agent.rs
@@ -165,8 +165,11 @@ impl AgentClient {
     }
 
     pub async fn sign_auth(&mut self, key: &SignKey, msg: &AuthSigMsg<'_>) -> Result<OwnedSig> {
-        // TODO: rsa needs SSH_AGENT_FLAG_RSA_SHA2_256
-        let flags = 0u32;
+        let flags = match key {
+            SignKey::AgentRSA(_) => SSH_AGENT_FLAG_RSA_SHA2_256,
+            _ => 0,
+        };
+        trace!("flags {key:?}");
         let r = AgentRequest::SignRequest(AgentSignRequest {
             key_blob: Blob(key.pubkey()),
             msg: Blob(msg),
diff --git a/src/cliauth.rs b/src/cliauth.rs
index 61327c48ad35ebdd85c15067fce872c7827199d8..18b44402b5353b6543fe1d0b54c5adc8ef5fb2c1 100644
--- a/src/cliauth.rs
+++ b/src/cliauth.rs
@@ -144,19 +144,32 @@ impl CliAuth {
         &mut self,
         b: &mut impl CliBehaviour,
     ) -> Option<Req> {
-        let k = b.next_authkey().unwrap_or_else(|e| {
-            warn!("Error getting pubkey for auth");
-            None
-        });
-
-        match k {
-            Some(key) => {
-                Some(Req::PubKey { key })
-            }
-            None => {
-                trace!("stop iterating pubkeys");
-                self.try_pubkey = false;
+        loop {
+            let k = b.next_authkey().unwrap_or_else(|e| {
+                warn!("Error getting pubkey for auth");
                 None
+            });
+
+            match k {
+                Some(key) => {
+                    #[cfg(feature = "rsa")]
+                    match key {
+                        SignKey::RSA(_) | SignKey::AgentRSA(_) => {
+                            if !self.allow_rsa_sha2 {
+                                trace!("Skipping rsa key, no ext-info");
+                                continue
+                            }
+                        }
+                        _ => (),
+                    }
+
+                    break Some(Req::PubKey { key })
+                }
+                None => {
+                    trace!("stop iterating pubkeys");
+                    self.try_pubkey = false;
+                    break None
+                }
             }
         }
     }
@@ -297,6 +310,8 @@ impl CliAuth {
 
     pub fn handle_ext_info(&mut self, p: &packets::ExtInfo<'_>) {
         if let Some(ref algs) = p.server_sig_algs {
+            // we only worry about rsa-sha256, assuming other older key types are fine
+
             // OK unwrap: is a remote namelist
             self.allow_rsa_sha2 = algs.has_algo(SSH_NAME_RSA_SHA256).unwrap();
             trace!("setting allow_rsa_sha2 = {}", self.allow_rsa_sha2);
diff --git a/src/config.rs b/src/config.rs
index 659702ff290235cedb866079e1839ee23c3a3500..389dbc0e784b5727e99572fc6476fcd5a5760ae2 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -18,3 +18,5 @@ pub const MAX_TERM: usize = 32;
 
 pub const DEFAULT_TERM: &str = "xterm";
 
+pub const RSA_DEFAULT_KEYSIZE: usize = 2048;
+pub const RSA_MIN_KEYSIZE: usize = 1024;
diff --git a/src/error.rs b/src/error.rs
index 897b774090fa7bb518e9b074b2704f69b77bec28..c0417b09894fa3f7c6828aefd7eebf3edda340fb 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -47,6 +47,9 @@ pub enum Error {
     /// Error in received SSH protocol. Will disconnect.
     SSHProtoError,
 
+    /// Received a key with invalid structure, or too large.
+    BadKeyFormat,
+
     /// Remote peer isn't SSH
     NotSSH,
 
diff --git a/src/kex.rs b/src/kex.rs
index 540a8b7b2ee1baccf9b26e3f34ecea66f2dc69ab..4f1fd5f66e364397bf367190d02229375fffd029 100644
--- a/src/kex.rs
+++ b/src/kex.rs
@@ -40,7 +40,7 @@ const marker_only_kexs: &[&'static str] =
 
 const fixed_options_hostsig: &[&'static str] = &[
     SSH_NAME_ED25519,
-    #[cfg(rsa)]
+    #[cfg(feature = "rsa")]
     SSH_NAME_RSA_SHA256,
 ];
 
@@ -65,7 +65,7 @@ impl AlgoConfig {
         let mut kexs: LocalNames = fixed_options_kex.try_into().unwrap();
 
         // Only clients are interested in ext-info
-        // TODO perhaps it could go behind cfg(rsa)?
+        // TODO perhaps it could go behind cfg rsa?
         if is_client {
             // OK unwrap: static arrays are < MAX_LOCAL_NAMES+slack
             kexs.0.push(SSH_NAME_EXT_INFO_C).unwrap();
@@ -410,6 +410,7 @@ impl Kex {
             false => p.kex.has_algo(SSH_NAME_EXT_INFO_C).unwrap(),
         };
 
+        debug!("hostsig {:?}    vs   {:?}", p.hostsig, conf.hostsig);
         let hostsig_method = p
             .hostsig
             .first_match(is_client, &conf.hostsig)?
diff --git a/src/packets.rs b/src/packets.rs
index 087abb27f9ce7ceef1ae78ef8108bd51a441bae7..d24c615fe7703b4af449bea43532f154e64122fd 100644
--- a/src/packets.rs
+++ b/src/packets.rs
@@ -11,6 +11,7 @@ use {
 };
 
 use core::fmt;
+use core::fmt::{Debug, Display};
 
 use heapless::String;
 use pretty_hex::PrettyHex;
@@ -25,6 +26,9 @@ use sign::{SigType, OwnedSig};
 use sshwire::{SSHEncode, SSHDecode, SSHSource, SSHSink, WireResult, WireError};
 use sshwire::{SSHEncodeEnum, SSHDecodeEnum};
 
+#[cfg(feature = "rsa")]
+use rsa::PublicKeyParts;
+
 // Any `enum` needs to have special handling to select a variant when deserializing.
 // This is mostly done with `#[sshwire(...)]` attributes.
 
@@ -208,7 +212,7 @@ pub struct MethodPassword<'a> {
 }
 
 // Don't print password
-impl fmt::Debug for MethodPassword<'_>{
+impl Debug for MethodPassword<'_>{
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.debug_struct("MethodPassword")
             .field("change", &self.change)
@@ -295,8 +299,11 @@ pub struct UserauthBanner<'a> {
 pub enum PubKey<'a> {
     #[sshwire(variant = SSH_NAME_ED25519)]
     Ed25519(Ed25519PubKey<'a>),
+
+    #[cfg(feature = "rsa")]
     #[sshwire(variant = SSH_NAME_RSA)]
-    RSA(RSAPubKey<'a>),
+    RSA(RSAPubKey),
+
     #[sshwire(unknown)]
     Unknown(Unknown<'a>),
 }
@@ -306,6 +313,7 @@ impl PubKey<'_> {
     pub fn algorithm_name(&self) -> Result<&str, &Unknown<'_>> {
         match self {
             PubKey::Ed25519(_) => Ok(SSH_NAME_ED25519),
+            #[cfg(feature = "rsa")]
             PubKey::RSA(_) => Ok(SSH_NAME_RSA),
             PubKey::Unknown(u) => Err(u),
         }
@@ -329,16 +337,27 @@ impl PubKey<'_> {
     }
 }
 
+// ssh_key::PublicKey is used for known_hosts comparisons
 #[cfg(feature = "openssh-key")]
 impl TryFrom<&PubKey<'_>> for ssh_key::PublicKey {
     type Error = Error;
-    fn try_from(k: &PubKey<'_>) -> Result<Self> {
+    fn try_from(k: &PubKey) -> Result<Self> {
         match k {
             PubKey::Ed25519(e) => {
                 let eb: &[u8; 32] = e.key.0.try_into().map_err(|_| Error::BadKey)?;
                 Ok(ssh_key::public::Ed25519PublicKey(*eb).into())
             }
-            _ => Err(Error::msg("Unsupported OpenSSH key"))
+
+            #[cfg(feature = "rsa")]
+            PubKey::RSA(r) => {
+                let k = ssh_key::public::RsaPublicKey {
+                    n: r.key.n().try_into().map_err(|_| Error::BadKey)?,
+                    e: r.key.e().try_into().map_err(|_| Error::BadKey)?,
+                };
+                Ok(k.into())
+            }
+
+            u => Err(Error::msg("Unsupported {u} OpenSSH key"))
         }
     }
 }
@@ -350,41 +369,64 @@ pub struct Ed25519PubKey<'a> {
 
 impl TryFrom<&Ed25519PubKey<'_>> for salty::PublicKey {
     type Error = Error;
-    fn try_from(k: &Ed25519PubKey<'_>) -> Result<Self> {
+    fn try_from(k: &Ed25519PubKey) -> Result<Self> {
         let b: [u8; 32] = k.key.0.try_into().map_err(|_| Error::BadKey)?;
         (&b).try_into().map_err(|_| Error::BadKey)
     }
 }
 
+#[cfg(feature = "rsa")]
+#[derive(Clone, PartialEq)]
+pub struct RSAPubKey {
+    // mpint     e
+    // mpint     n
+    pub key: rsa::RsaPublicKey,
+}
 
-#[derive(Debug, Clone, PartialEq, SSHEncode, SSHDecode)]
-pub struct RSAPubKey<'a> {
-    pub e: BinString<'a>,
-    pub n: BinString<'a>,
-}
-
-// #[cfg(feature = "rsa")]
-// impl TryFrom<RsaPubKey<'_> for rsa::RsaPublicKey {
-//     fn try_from(value: RsaPubKey<'_>) -> Result<Self, Self::Error> {
-//         use rsa::BigUint;
-//         rsa::RsaPublickey::new(
-//             BigUint::from_bytes_be(n.0),
-//             BigUint::from_bytes_be(e.0),
-//             )
-//         .map_err(|e| {
-//             debug!("Bad RSA key: {e}");
-//             Error::BadKey
-//         })
-//     }
-// }
+#[cfg(feature = "rsa")]
+impl SSHEncode for RSAPubKey {
+    fn enc(&self, s: &mut dyn SSHSink) -> WireResult<()> {
+        use rsa::PublicKeyParts;
+        self.key.e().enc(s)?;
+        self.key.n().enc(s)?;
+        Ok(())
+    }
+}
+
+#[cfg(feature = "rsa")]
+impl<'de> SSHDecode<'de> for RSAPubKey {
+    fn dec<S>(s: &mut S) -> WireResult<Self> where S: SSHSource<'de> {
+        use rsa::PublicKeyParts;
+        let e = SSHDecode::dec(s)?;
+        let n = SSHDecode::dec(s)?;
+        let key = rsa::RsaPublicKey::new(n, e)
+        .map_err(|e| {
+            debug!("Invalid RSA public key: {e}");
+            WireError::BadKeyFormat
+        })?;
+        Ok(Self { key })
+    }
+}
+
+#[cfg(feature = "rsa")]
+impl Debug for RSAPubKey {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("RSAPubKey")
+            .field(".key bits", &(self.key.n().bits()))
+            .finish_non_exhaustive()
+    }
+}
 
 #[derive(Debug, SSHEncode,  SSHDecode)]
 #[sshwire(variant_prefix)]
 pub enum Signature<'a> {
     #[sshwire(variant = SSH_NAME_ED25519)]
     Ed25519(Ed25519Sig<'a>),
+
+    #[cfg(feature = "rsa")]
     #[sshwire(variant = SSH_NAME_RSA_SHA256)]
-    RSA256(RSA256Sig<'a>),
+    RSA(RSASig<'a>),
+
     #[sshwire(unknown)]
     Unknown(Unknown<'a>),
 }
@@ -394,7 +436,8 @@ impl<'a> Signature<'a> {
     pub fn algorithm_name(&self) -> Result<&'a str, &Unknown<'a>> {
         match self {
             Signature::Ed25519(_) => Ok(SSH_NAME_ED25519),
-            Signature::RSA256(_) => Ok(SSH_NAME_RSA_SHA256),
+            #[cfg(feature = "rsa")]
+            Signature::RSA(_) => Ok(SSH_NAME_RSA_SHA256),
             Signature::Unknown(u) => Err(u),
         }
     }
@@ -408,6 +451,7 @@ impl<'a> Signature<'a> {
     pub fn sig_name_for_pubkey(pubkey: &PubKey) -> Result<&'static str> {
         match pubkey {
             PubKey::Ed25519(_) => Ok(SSH_NAME_ED25519),
+            #[cfg(feature = "rsa")]
             PubKey::RSA(_) => Ok(SSH_NAME_RSA_SHA256),
             PubKey::Unknown(u) => {
                 warn!("Unknown key type \"{}\"", u);
@@ -419,7 +463,8 @@ impl<'a> Signature<'a> {
     pub fn sig_type(&self) -> Result<SigType> {
         match self {
             Signature::Ed25519(_) => Ok(SigType::Ed25519),
-            Signature::RSA256(_) => Ok(SigType::RSA256),
+            #[cfg(feature = "rsa")]
+            Signature::RSA(_) => Ok(SigType::RSA),
             Signature::Unknown(u) => {
                 warn!("Unknown signature type \"{}\"", u);
                 Err(Error::UnknownMethod {kind: "signature" })
@@ -431,8 +476,9 @@ impl<'a> Signature<'a> {
 impl <'a> From<&'a OwnedSig> for Signature<'a> {
     fn from(s: &'a OwnedSig) -> Self {
         match s {
-            OwnedSig::Ed25519(e) => Signature::Ed25519(Ed25519Sig { sig: BinString(e) }),
-            OwnedSig::_RSA256 => todo!("sig from rsa"),
+            OwnedSig::Ed25519(s) => Signature::Ed25519(Ed25519Sig { sig: BinString(s) }),
+            #[cfg(feature = "rsa")]
+            OwnedSig::RSA(s) => Signature::RSA(RSASig { sig: BinString(s.as_ref()) })
         }
     }
 }
@@ -443,8 +489,9 @@ pub struct Ed25519Sig<'a> {
     pub sig: BinString<'a>,
 }
 
+#[cfg(feature = "rsa")]
 #[derive(Debug, SSHEncode, SSHDecode)]
-pub struct RSA256Sig<'a> {
+pub struct RSASig<'a> {
     pub sig: BinString<'a>,
 }
 
@@ -714,7 +761,15 @@ pub struct DirectTcpip<'a> {
 #[derive(Clone, PartialEq)]
 pub struct Unknown<'a>(pub &'a [u8]);
 
-impl core::fmt::Display for Unknown<'_> {
+impl<'a> Unknown<'a> {
+    fn new(u: &'a [u8]) -> Self {
+        let u = Unknown(u);
+        trace!("saw unknown variant \"{u}\"");
+        u
+    }
+}
+
+impl Display for Unknown<'_> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         if let Ok(s) = sshwire::try_as_ascii_str(self.0) {
             f.write_str(s)
@@ -724,7 +779,7 @@ impl core::fmt::Display for Unknown<'_> {
     }
 }
 
-impl core::fmt::Debug for Unknown<'_> {
+impl Debug for Unknown<'_> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         write!(f, "{self}")
     }
diff --git a/src/sign.rs b/src/sign.rs
index c6f9e717ae1138d82059a4fdef78ad5fb54a4e58..0cf37d86c716fa7bdfe4d89674a3bb1e0f03cba0 100644
--- a/src/sign.rs
+++ b/src/sign.rs
@@ -5,7 +5,8 @@ use {
     log::{debug, error, info, log, trace, warn},
 };
 
-use salty::{SecretKey, PublicKey};
+use core::ops::Deref;
+
 use signature::Verifier;
 use zeroize::ZeroizeOnDrop;
 
@@ -19,12 +20,23 @@ use pretty_hex::PrettyHex;
 
 use core::mem::discriminant;
 
+use digest::Digest;
+
+#[cfg(feature = "rsa")]
+use rsa::signature::{DigestVerifier, DigestSigner};
+#[cfg(feature = "rsa")]
+use packets::RSAPubKey;
+
+// #[cfg(feature = "rsa")]
+// use rsa::{PublicKey, RsaPrivateKey, RsaPublicKey, PaddingScheme};
+
 // RSA requires alloc.
 
 #[derive(Debug, Clone, Copy)]
 pub enum SigType {
     Ed25519,
-    RSA256,
+    #[cfg(feature = "rsa")]
+    RSA,
     // Ecdsa
 }
 
@@ -33,7 +45,8 @@ impl SigType {
     pub fn from_name(name: &'static str) -> Result<Self> {
         match name {
             SSH_NAME_ED25519 => Ok(SigType::Ed25519),
-            SSH_NAME_RSA_SHA256 => Ok(SigType::RSA256),
+            #[cfg(feature = "rsa")]
+            SSH_NAME_RSA_SHA256 => Ok(SigType::RSA),
             _ => Err(Error::bug()),
         }
     }
@@ -42,7 +55,8 @@ impl SigType {
     pub fn algorithm_name(&self) -> &'static str {
         match self {
             SigType::Ed25519 => SSH_NAME_ED25519,
-            SigType::RSA256 => SSH_NAME_RSA_SHA256,
+            #[cfg(feature = "rsa")]
+            SigType::RSA => SSH_NAME_RSA_SHA256,
         }
     }
 
@@ -75,21 +89,19 @@ impl SigType {
                 .map_err(|_| Error::BadSig)
             }
 
-            (SigType::RSA256, PubKey::RSA(_k), Signature::RSA256(_s)) => {
-                // TODO
-                warn!("RSA256 is not implemented");
-                Err(Error::BadSig)
-                // // untested
-                // use rsa::{PublicKey, RsaPrivateKey, RsaPublicKey, PaddingScheme};
-                // let k: RsaPublicKey = k.try_into()?;
-                // let h = sha2::Sha256::digest(message);
-                // k.verify(rsa::padding::PaddingScheme::PKCS1v15Sign{ hash: rsa::hash::Hash::SHA2_256},
-                //     &h,
-                //     s.sig.0)
-                // .map_err(|e| {
-                //     trace!("RSA signature failed: {e}");
-                //     Error::BadSig
-                // })
+            #[cfg(feature = "rsa")]
+            (SigType::RSA, PubKey::RSA(k), Signature::RSA(s)) => {
+                let verifying_key = rsa::pkcs1v15::VerifyingKey::<sha2::Sha256>::new_with_prefix(k.key.clone());
+                let s: Box<[u8]> = s.sig.0.into();
+                let signature = s.into();
+
+                let mut h = sha2::Sha256::new();
+                sshwire::hash_ser(&mut h, msg, parse_ctx)?;
+                verifying_key.verify_digest(h, &signature)
+                .map_err(|e| {
+                    trace!("RSA signature failed: {e}");
+                    Error::BadSig
+                })
             }
 
             _ => {
@@ -107,7 +119,8 @@ pub enum OwnedSig {
     // salty::Signature doesn't let us borrow the inner bytes,
     // so we just store raw bytes here.
     Ed25519([u8; 64]),
-    _RSA256, // TODO
+    #[cfg(feature = "rsa")]
+    RSA(rsa::pkcs1v15::Signature),
 }
 
 impl From<salty::Signature> for OwnedSig {
@@ -116,6 +129,13 @@ impl From<salty::Signature> for OwnedSig {
     }
 }
 
+#[cfg(feature = "rsa")]
+impl From<rsa::pkcs1v15::Signature> for OwnedSig {
+    fn from(s: rsa::pkcs1v15::Signature) -> Self {
+        OwnedSig::RSA(s)
+    }
+}
+
 impl TryFrom<Signature<'_>> for OwnedSig {
     type Error = Error;
     fn try_from(s: Signature) -> Result<Self> {
@@ -124,9 +144,10 @@ impl TryFrom<Signature<'_>> for OwnedSig {
                 let s: [u8; 64] = s.sig.0.try_into().map_err(|_| Error::BadSig)?;
                 Ok(OwnedSig::Ed25519(s))
             }
-            Signature::RSA256(_s) => {
-                warn!("RSA256 is not implemented");
-                Err(Error::BadSig)
+            #[cfg(feature = "rsa")]
+            Signature::RSA(s) => {
+                let s: Box<[u8]> = s.sig.0.into();
+                Ok(OwnedSig::RSA(s.into()))
             }
             Signature::Unknown(u) => {
                 debug!("Unknown {u} signature");
@@ -139,6 +160,8 @@ impl TryFrom<Signature<'_>> for OwnedSig {
 #[derive(Debug, Clone, Copy)]
 pub enum KeyType {
     Ed25519,
+    #[cfg(feature = "rsa")]
+    RSA,
 }
 
 /// A SSH signing key. This may hold the private part locally
@@ -151,27 +174,63 @@ pub enum SignKey {
 
     #[zeroize(skip)]
     AgentEd25519(salty::PublicKey),
+
+    #[cfg(feature = "rsa")]
+    // TODO zeroize doesn't seem supported? though BigUint has Zeroize
+    #[zeroize(skip)]
+    RSA(rsa::RsaPrivateKey),
+
+    #[cfg(feature = "rsa")]
+    #[zeroize(skip)]
+    AgentRSA(rsa::RsaPublicKey),
 }
 
 impl SignKey {
-    pub fn generate(ty: KeyType) -> Result<Self> {
+    pub fn generate(ty: KeyType, bits: Option<usize>) -> Result<Self> {
         match ty {
             KeyType::Ed25519 => {
+                if bits.unwrap_or(256) != 256 {
+                    return Err(Error::msg("Bad key size"));
+                }
                 let mut seed = [0u8; 32];
                 random::fill_random(seed.as_mut_slice())?;
                 Ok(Self::Ed25519((&seed).into()))
             },
+
+            #[cfg(feature = "rsa")]
+            KeyType::RSA => {
+                let bits = bits.unwrap_or(config::RSA_DEFAULT_KEYSIZE);
+                if bits < config::RSA_MIN_KEYSIZE
+                    || bits > rsa::RsaPublicKey::MAX_SIZE
+                    || (bits % 8 != 0) {
+                    return Err(Error::msg("Bad key size"));
+                }
+
+                let k = rsa::RsaPrivateKey::new(&mut rand_core::OsRng, bits)
+                .map_err(|e| {
+                    debug!("RSA key generation error {e}");
+                    // RNG shouldn't fail, keysize has been checked
+                    Error::bug()
+                })?;
+                Ok(Self::RSA(k))
+            },
         }
     }
 
     pub fn pubkey(&self) -> PubKey {
         match self {
-            SignKey::Ed25519(k) => {PubKey::Ed25519(Ed25519PubKey
-                { key: BinString(k.public.as_bytes()) } )
-            }
-            SignKey::AgentEd25519(pk) => {PubKey::Ed25519(Ed25519PubKey
-                { key: BinString(pk.as_bytes()) } )
-            }
+            SignKey::Ed25519(k) => PubKey::Ed25519(Ed25519PubKey
+                { key: BinString(k.public.as_bytes()) } ),
+
+            SignKey::AgentEd25519(pk) => PubKey::Ed25519(Ed25519PubKey
+                { key: BinString(pk.as_bytes()) } ),
+
+
+            #[cfg(feature = "rsa")]
+            SignKey::RSA(k) => PubKey::RSA(RSAPubKey { key: k.deref().clone() }),
+
+            #[cfg(feature = "rsa")]
+            SignKey::AgentRSA(pk) => PubKey::RSA(RSAPubKey { key: pk.clone() }),
         }
     }
 
@@ -191,7 +250,11 @@ impl SignKey {
                 let k: salty::PublicKey = k.try_into().map_err(|_| Error::BadKey)?;
                 Ok(Self::AgentEd25519(k))
             },
-            _ => {
+
+            #[cfg (feature = "rsa")]
+            PubKey::RSA(k) => Ok(Self::AgentRSA(k.key.clone())),
+
+            PubKey::Unknown(_) => {
                 Err(Error::msg("Unsupported agent key"))
             }
         }
@@ -203,23 +266,42 @@ impl SignKey {
             | SignKey::Ed25519(_)
             | SignKey::AgentEd25519(_)
             => matches!(sig_type, SigType::Ed25519),
+
+            #[cfg(feature = "rsa")]
+            | SignKey::RSA(_)
+            | SignKey::AgentRSA(_)
+            => matches!(sig_type, SigType::RSA),
         }
     }
 
     pub(crate) fn sign(&self, msg: &impl SSHEncode, parse_ctx: Option<&ParseContext>) -> Result<OwnedSig> {
         let sig: OwnedSig = match self {
             SignKey::Ed25519(k) => {
-                k.sign_parts(|h| {
+                let sig = k.sign_parts(|h| {
                     sshwire::hash_ser(h, msg, parse_ctx).map_err(|_| salty::Error::ContextTooLong)
                 })
-                .trap()
-                .map(|s| s.into())
-            },
-            SignKey::AgentEd25519(_) => {
-                // callers should check for agent keys first
-                return Error::bug_msg("agent sign")
+                .trap()?;
+                sig.into()
+            }
+
+            #[cfg(feature = "rsa")]
+            SignKey::RSA(k) => {
+                let signing_key = rsa::pkcs1v15::SigningKey::<sha2::Sha256>::new_with_prefix(k.clone());
+                let mut h = sha2::Sha256::new();
+                sshwire::hash_ser(&mut h, msg, parse_ctx)?;
+                let sig = signing_key.try_sign_digest(h).map_err(|e| {
+                    trace!("RSA signing failed: {e:?}");
+                    Error::bug()
+                })?;
+                sig.into()
             }
-        }?;
+
+            // callers should check for agent keys first
+            | SignKey::AgentEd25519(_) => return Error::bug_msg("agent sign"),
+            #[cfg(feature = "rsa")]
+            | SignKey::AgentRSA(_) => return Error::bug_msg("agent sign"),
+
+        };
 
         {
             // Faults in signing can expose the private key. We verify the signature
@@ -236,15 +318,27 @@ impl SignKey {
     pub(crate) fn is_agent(&self) -> bool {
         match self {
             SignKey::Ed25519(_) => false,
+            #[cfg(feature = "rsa")]
+            SignKey::RSA(_) => false,
+
             SignKey::AgentEd25519(_) => true,
+            #[cfg(feature = "rsa")]
+            SignKey::AgentRSA(_) => true,
         }
     }
 }
 
 impl core::fmt::Debug for SignKey {
     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        f.debug_struct("SignKey")
-        .finish()
+        let s = match self {
+            Self::Ed25519(_) => "Ed25519",
+            Self::AgentEd25519(_) => "AgentEd25519",
+            #[cfg(feature = "rsa")]
+            Self::RSA(_) => "RSA",
+            #[cfg(feature = "rsa")]
+            Self::AgentRSA(_) => "AgentRSA",
+        };
+        write!(f, "SignKey::{s}")
     }
 }
 
@@ -260,6 +354,21 @@ impl TryFrom<ssh_key::PrivateKey> for SignKey {
                 };
                 Ok(SignKey::Ed25519(key))
             }
+
+            #[cfg(feature = "rsa")]
+            ssh_key::private::KeypairData::Rsa(k) => {
+                let primes = vec![
+                    (&k.private.p).try_into().map_err(|_| Error::BadKey)?,
+                    (&k.private.q).try_into().map_err(|_| Error::BadKey)?,
+                ];
+                let key = rsa::RsaPrivateKey::from_components(
+                    (&k.public.n).try_into().map_err(|_| Error::BadKey)?,
+                    (&k.public.e).try_into().map_err(|_| Error::BadKey)?,
+                    (&k.private.d).try_into().map_err(|_| Error::BadKey)?,
+                    primes,
+                ).map_err(|_| Error::BadKey)?;
+                Ok(SignKey::RSA(key))
+            }
             _ => Err(Error::NotAvailable { what: k.algorithm().as_str() })
         }
     }
diff --git a/src/sshwire.rs b/src/sshwire.rs
index 70b89b98c127fbb1b5d9c60f76d0953023a9f11c..ab57217fbf0ea4361d05dc2899373e65509166f4 100644
--- a/src/sshwire.rs
+++ b/src/sshwire.rs
@@ -91,6 +91,8 @@ pub enum WireError {
 
     SSHProtoError,
 
+    BadKeyFormat,
+
     UnknownPacket { number: u8 },
 }
 
@@ -103,6 +105,7 @@ impl From<WireError> for Error {
             WireError::BadName => Error::BadName,
             WireError::SSHProtoError => Error::SSHProtoError,
             WireError::PacketWrong => Error::PacketWrong,
+            WireError::BadKeyFormat => Error::BadKeyFormat,
             WireError::UnknownVariant => Error::bug_err_msg("Can't encode Unknown"),
             WireError::UnknownPacket { number } => Error::UnknownPacket { number },
         }
@@ -583,6 +586,41 @@ impl DigestUpdate for sha2::Sha256 {
     }
 }
 
+fn top_bit_set(b: &[u8]) -> bool {
+    b.first().unwrap_or(&0) & 0x80 != 0
+}
+
+#[cfg(feature = "rsa")]
+impl SSHEncode for rsa::BigUint {
+    fn enc(&self, s: &mut dyn SSHSink) -> WireResult<()> {
+        let b = self.to_bytes_be();
+        let b = b.as_slice();
+
+        // rfc4251 mpint, need a leading zero byte if top bit is set
+        let pad = top_bit_set(b);
+        let len = b.len() as u32 + pad as u32;
+        len.enc(s)?;
+
+        if pad {
+            0u8.enc(s)?;
+        }
+
+        b.enc(s)
+    }
+}
+
+#[cfg(feature = "rsa")]
+impl<'de> SSHDecode<'de> for rsa::BigUint {
+    fn dec<S>(s: &mut S) -> WireResult<Self>
+    where S: SSHSource<'de> {
+        let b = BinString::dec(s)?;
+        if top_bit_set(b.0) {
+            trace!("received negative mpint");
+            return Err(WireError::BadKeyFormat)
+        }
+        Ok(rsa::BigUint::from_bytes_be(b.0))
+    }
+}
 
 #[cfg(test)]
 pub(crate) mod tests {
diff --git a/sshwire-derive/src/lib.rs b/sshwire-derive/src/lib.rs
index db8039fd79b44fe071b53a17f8c87ab0362efa43..e509c8e43caca4d38a6b71d9376a68b6b2898c55 100644
--- a/sshwire-derive/src/lib.rs
+++ b/sshwire-derive/src/lib.rs
@@ -541,7 +541,7 @@ fn decode_enum_names(
                     if atts.iter().any(|a| matches!(a, FieldAtt::CaptureUnknown)) {
                         // create the Unknown fallthrough but it will be at the end of the match list
                         let mut m = StreamBuilder::new();
-                        m.push_parsed(format!("_ => {{ s.ctx().seen_unknown = true; Self::{}(Unknown(variant))}}", var.name))?;
+                        m.push_parsed(format!("_ => {{ s.ctx().seen_unknown = true; Self::{}(Unknown::new(variant))}}", var.name))?;
                         if unknown_arm.replace(m).is_some() {
                             return Err(Error::Custom { error: "only one variant can have #[sshwire(unknown)]".into(), span: None})
                         }