use bytes::Bytes;
use derivative::Derivative;
use std::fmt;
use crate::codec::{PacketDecode, PacketEncode};
use crate::error::{Result, Error};
use crate::util::base64_encode;
pub use self::ecdsa::{ECDSA_SHA2_NISTP256, ECDSA_SHA2_NISTP384, EcdsaPubkey, EcdsaPrivkey};
pub use self::ed25519::{SSH_ED25519, Ed25519Pubkey, Ed25519Privkey};
pub use self::rsa::{SSH_RSA_SHA1, RSA_SHA2_256, RSA_SHA2_512, RsaPubkey, RsaPrivkey};
mod ecdsa;
mod ed25519;
mod rsa;
#[derive(Derivative)]
#[derivative(Debug)]
pub struct PubkeyAlgo {
pub name: &'static str,
#[derivative(Debug = "ignore")]
pub(crate) verify: fn(pubkey: &Pubkey, message: &[u8], signature: Bytes) -> Result<SignatureVerified>,
#[derivative(Debug = "ignore")]
pub(crate) sign: fn(privkey: &Privkey, message: &[u8]) -> Result<Bytes>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Pubkey {
Ed25519(Ed25519Pubkey),
Rsa(RsaPubkey),
EcdsaP256(EcdsaPubkey<p256::NistP256>),
EcdsaP384(EcdsaPubkey<p384::NistP384>),
}
impl Pubkey {
pub fn algos(&self) -> &'static [&'static PubkeyAlgo] {
static ED25519: &[&PubkeyAlgo] = &[&SSH_ED25519];
static RSA: &[&PubkeyAlgo] = &[&RSA_SHA2_256, &RSA_SHA2_512, &SSH_RSA_SHA1];
static ECDSA_P256: &[&PubkeyAlgo] = &[&ECDSA_SHA2_NISTP256];
static ECDSA_P384: &[&PubkeyAlgo] = &[&ECDSA_SHA2_NISTP384];
match self {
Pubkey::Ed25519(_) => ED25519,
Pubkey::Rsa(_) => RSA,
Pubkey::EcdsaP256(_) => ECDSA_P256,
Pubkey::EcdsaP384(_) => ECDSA_P384,
}
}
#[deprecated(since = "0.2.1", note = "Disabling public key algorithms for authentication _on the client_ \
does not increase security, the older, less secure algorithms must be disabled on the server. \
Please use `Pubkey::algos()` instead.")]
pub fn algos_secure(&self) -> &'static [&'static PubkeyAlgo] {
static RSA: &[&PubkeyAlgo] = &[&RSA_SHA2_256, &RSA_SHA2_512];
match self {
Pubkey::Rsa(_) => RSA,
_ => self.algos(),
}
}
#[deprecated(since = "0.2.1", note = "Please use `Pubkey::algos()` instead.")]
pub fn algos_compatible_less_secure(&self) -> &'static [&'static PubkeyAlgo] {
self.algos()
}
pub fn decode(blob: Bytes) -> Result<Self> {
decode_pubkey(blob)
}
pub fn encode(&self) -> Bytes {
encode_pubkey(self)
}
pub fn type_str(&self) -> String {
pubkey_type_str(self).into()
}
pub fn fingerprint(&self) -> String {
use sha2::Digest;
let digest = sha2::Sha256::digest(self.encode());
format!("SHA256:{}", base64_encode(&digest))
}
}
impl fmt::Display for Pubkey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Pubkey::Ed25519(pubkey) => fmt::Display::fmt(pubkey, f),
Pubkey::Rsa(pubkey) => fmt::Display::fmt(pubkey, f),
Pubkey::EcdsaP256(pubkey) => fmt::Display::fmt(pubkey, f),
Pubkey::EcdsaP384(pubkey) => fmt::Display::fmt(pubkey, f),
}
}
}
#[derive(Debug)]
pub(crate) struct SignatureVerified(());
impl SignatureVerified {
fn assertion() -> Self { Self(()) }
}
#[derive(Clone, PartialEq, Eq)]
#[non_exhaustive]
#[cfg_attr(feature = "debug-less-secure", derive(Debug))]
pub enum Privkey {
Ed25519(Ed25519Privkey),
Rsa(RsaPrivkey),
EcdsaP256(EcdsaPrivkey<p256::NistP256>),
EcdsaP384(EcdsaPrivkey<p384::NistP384>),
}
impl Privkey {
pub fn pubkey(&self) -> Pubkey {
match self {
Privkey::Ed25519(privkey) => Pubkey::Ed25519(privkey.pubkey()),
Privkey::Rsa(privkey) => Pubkey::Rsa(privkey.pubkey()),
Privkey::EcdsaP256(privkey) => Pubkey::EcdsaP256(privkey.pubkey()),
Privkey::EcdsaP384(privkey) => Pubkey::EcdsaP384(privkey.pubkey()),
}
}
pub(crate) fn decode(blob: &mut PacketDecode) -> Result<Privkey> {
decode_privkey(blob)
}
}
fn decode_pubkey(blob: Bytes) -> Result<Pubkey> {
let mut blob = PacketDecode::new(blob);
let format = blob.get_string()?;
match format.as_str() {
"ssh-ed25519" => ed25519::decode_pubkey(&mut blob).map(Pubkey::Ed25519),
"ssh-rsa" => rsa::decode_pubkey(&mut blob).map(Pubkey::Rsa),
"ecdsa-sha2-nistp256" => ecdsa::decode_pubkey::<p256::NistP256>(&mut blob).map(Pubkey::EcdsaP256),
"ecdsa-sha2-nistp384" => ecdsa::decode_pubkey::<p384::NistP384>(&mut blob).map(Pubkey::EcdsaP384),
_ => {
log::debug!("unknown pubkey format {:?}", format);
Err(Error::Decode("unknown public key format"))
},
}
}
fn pubkey_type_str(pubkey: &Pubkey) -> &'static str {
match pubkey {
Pubkey::Ed25519(_) => "ssh-ed25519",
Pubkey::Rsa(_) => "ssh-rsa",
Pubkey::EcdsaP256(_) => "ecdsa-sha2-nistp256",
Pubkey::EcdsaP384(_) => "ecdsa-sha2-nistp384",
}
}
fn encode_pubkey(pubkey: &Pubkey) -> Bytes {
let mut blob = PacketEncode::new();
match pubkey {
Pubkey::Ed25519(pubkey) => ed25519::encode_pubkey(&mut blob, pubkey),
Pubkey::Rsa(pubkey) => rsa::encode_pubkey(&mut blob, pubkey),
Pubkey::EcdsaP256(pubkey) => ecdsa::encode_pubkey(&mut blob, pubkey),
Pubkey::EcdsaP384(pubkey) => ecdsa::encode_pubkey(&mut blob, pubkey),
}
blob.finish()
}
fn decode_privkey(blob: &mut PacketDecode) -> Result<Privkey> {
let format = blob.get_string()?;
match format.as_str() {
"ssh-ed25519" => ed25519::decode_privkey(blob).map(Privkey::Ed25519),
"ssh-rsa" => rsa::decode_privkey(blob).map(Privkey::Rsa),
"ecdsa-sha2-nistp256" => ecdsa::decode_privkey::<p256::NistP256>(blob).map(Privkey::EcdsaP256),
"ecdsa-sha2-nistp384" => ecdsa::decode_privkey::<p384::NistP384>(blob).map(Privkey::EcdsaP384),
_ => {
log::debug!("unknown privkey format {:?}", format);
Err(Error::Decode("unknown private key format"))
},
}
}