use crate::cipher::Cipher;
use crate::error::*;
use crate::format::ossh_privkey::*;
use crate::format::ossh_pubkey::*;
use crate::format::parse_keystr;
use crate::format::pem::*;
use crate::format::pkcs8::*;
use digest::{Digest, FixedOutputReset};
use md5::Md5;
use openssl::pkey::{Id, PKey, PKeyRef, Private, Public};
use sha2::{Sha256, Sha512};
use std::fmt;
pub mod dsa;
pub mod ecdsa;
pub mod ed25519;
pub mod rsa;
pub const MD5_NAME: &str = "MD5";
pub const SHA256_NAME: &str = "SHA256";
pub const SHA512_NAME: &str = "SHA512";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FingerprintHash {
MD5,
SHA256,
SHA512,
}
impl FingerprintHash {
fn hash(self, data: &[u8]) -> Vec<u8> {
fn digest_hash<D>(hasher: &mut D, data: &[u8]) -> Vec<u8>
where
D: Digest + FixedOutputReset,
{
Digest::update(hasher, data);
hasher.finalize_reset().to_vec()
}
match self {
FingerprintHash::MD5 => digest_hash(&mut Md5::default(), data),
FingerprintHash::SHA256 => digest_hash(&mut Sha256::default(), data),
FingerprintHash::SHA512 => digest_hash(&mut Sha512::default(), data),
}
}
fn name(self) -> &'static str {
match self {
FingerprintHash::MD5 => MD5_NAME,
FingerprintHash::SHA256 => SHA256_NAME,
FingerprintHash::SHA512 => SHA512_NAME,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KeyType {
RSA,
DSA,
ECDSA,
ED25519,
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, PartialEq)]
pub(crate) enum PublicKeyType {
RSA(rsa::RsaPublicKey),
DSA(dsa::DsaPublicKey),
ECDSA(ecdsa::EcDsaPublicKey),
ED25519(ed25519::Ed25519PublicKey),
}
#[allow(clippy::upper_case_acronyms)]
pub(crate) enum KeyPairType {
RSA(rsa::RsaKeyPair),
DSA(dsa::DsaKeyPair),
ECDSA(ecdsa::EcDsaKeyPair),
ED25519(ed25519::Ed25519KeyPair),
}
pub struct PublicKey {
pub(crate) key: PublicKeyType,
comment: String,
}
impl PublicKey {
pub(crate) fn from_ossl_pkey(pkey: &PKeyRef<Public>) -> OsshResult<Self> {
match pkey.id() {
Id::RSA => {
Ok(rsa::RsaPublicKey::from_ossl_rsa(pkey.rsa()?, rsa::RsaSignature::SHA1)?.into())
}
Id::DSA => Ok(dsa::DsaPublicKey::from_ossl_dsa(pkey.dsa()?).into()),
Id::EC => Ok(ecdsa::EcDsaPublicKey::from_ossl_ec(pkey.ec_key()?)?.into()),
Id::ED25519 => {
Ok(ed25519::Ed25519PublicKey::from_ossl_ed25519(&pkey.raw_public_key()?)?.into())
}
_ => Err(ErrorKind::UnsupportType.into()),
}
}
pub fn from_keystr(keystr: &str) -> OsshResult<Self> {
if keystr.trim().starts_with("-----BEGIN") {
Ok(parse_pem_pubkey(keystr.as_bytes())?)
} else {
Ok(parse_ossh_pubkey(keystr)?)
}
}
pub fn keytype(&self) -> KeyType {
match &self.key {
PublicKeyType::RSA(_) => KeyType::RSA,
PublicKeyType::DSA(_) => KeyType::DSA,
PublicKeyType::ECDSA(_) => KeyType::ECDSA,
PublicKeyType::ED25519(_) => KeyType::ED25519,
}
}
pub fn comment(&self) -> &str {
&self.comment
}
pub fn comment_mut(&mut self) -> &mut String {
&mut self.comment
}
pub fn serialize(&self) -> OsshResult<String> {
serialize_ossh_pubkey(self, &self.comment)
}
pub fn serialize_pem(&self) -> OsshResult<String> {
stringify_pem_pubkey(self)
}
fn inner_key(&self) -> &dyn PublicParts {
match &self.key {
PublicKeyType::RSA(key) => key,
PublicKeyType::DSA(key) => key,
PublicKeyType::ECDSA(key) => key,
PublicKeyType::ED25519(key) => key,
}
}
}
impl Key for PublicKey {
fn size(&self) -> usize {
self.inner_key().size()
}
fn keyname(&self) -> &'static str {
self.inner_key().keyname()
}
fn short_keyname(&self) -> &'static str {
self.inner_key().short_keyname()
}
}
impl PublicParts for PublicKey {
fn blob(&self) -> Result<Vec<u8>, Error> {
self.inner_key().blob()
}
fn fingerprint(&self, hash: FingerprintHash) -> Result<Vec<u8>, Error> {
self.inner_key().fingerprint(hash)
}
fn verify(&self, data: &[u8], sig: &[u8]) -> Result<bool, Error> {
self.inner_key().verify(data, sig)
}
}
impl fmt::Display for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.serialize().unwrap())
}
}
impl From<rsa::RsaPublicKey> for PublicKey {
fn from(inner: rsa::RsaPublicKey) -> PublicKey {
PublicKey {
key: PublicKeyType::RSA(inner),
comment: String::new(),
}
}
}
impl From<dsa::DsaPublicKey> for PublicKey {
fn from(inner: dsa::DsaPublicKey) -> PublicKey {
PublicKey {
key: PublicKeyType::DSA(inner),
comment: String::new(),
}
}
}
impl From<ecdsa::EcDsaPublicKey> for PublicKey {
fn from(inner: ecdsa::EcDsaPublicKey) -> PublicKey {
PublicKey {
key: PublicKeyType::ECDSA(inner),
comment: String::new(),
}
}
}
impl From<ed25519::Ed25519PublicKey> for PublicKey {
fn from(inner: ed25519::Ed25519PublicKey) -> PublicKey {
PublicKey {
key: PublicKeyType::ED25519(inner),
comment: String::new(),
}
}
}
pub struct KeyPair {
pub(crate) key: KeyPairType,
comment: String,
}
impl KeyPair {
pub(crate) fn from_ossl_pkey(pkey: &PKeyRef<Private>) -> OsshResult<Self> {
match pkey.id() {
Id::RSA => {
Ok(rsa::RsaKeyPair::from_ossl_rsa(pkey.rsa()?, rsa::RsaSignature::SHA1)?.into())
}
Id::DSA => Ok(dsa::DsaKeyPair::from_ossl_dsa(pkey.dsa()?).into()),
Id::EC => Ok(ecdsa::EcDsaKeyPair::from_ossl_ec(pkey.ec_key()?)?.into()),
Id::ED25519 => {
Ok(ed25519::Ed25519KeyPair::from_ossl_ed25519(&pkey.raw_private_key()?)?.into())
}
_ => Err(ErrorKind::UnsupportType.into()),
}
}
pub(crate) fn ossl_pkey(&self) -> OsshResult<PKey<Private>> {
match &self.key {
KeyPairType::RSA(key) => Ok(PKey::from_rsa(key.ossl_rsa().to_owned())?),
KeyPairType::DSA(key) => Ok(PKey::from_dsa(key.ossl_dsa().to_owned())?),
KeyPairType::ECDSA(key) => Ok(PKey::from_ec_key(key.ossl_ec().to_owned())?),
KeyPairType::ED25519(key) => Ok(key.ossl_pkey()?),
}
}
pub fn from_keystr(pem: &str, passphrase: Option<&str>) -> OsshResult<Self> {
parse_keystr(pem.as_bytes(), passphrase)
}
pub fn generate(keytype: KeyType, bits: usize) -> OsshResult<Self> {
Ok(match keytype {
KeyType::RSA => rsa::RsaKeyPair::generate(bits)?.into(),
KeyType::DSA => dsa::DsaKeyPair::generate(bits)?.into(),
KeyType::ECDSA => ecdsa::EcDsaKeyPair::generate(bits)?.into(),
KeyType::ED25519 => ed25519::Ed25519KeyPair::generate(bits)?.into(),
})
}
pub fn keytype(&self) -> KeyType {
match &self.key {
KeyPairType::RSA(_) => KeyType::RSA,
KeyPairType::DSA(_) => KeyType::DSA,
KeyPairType::ECDSA(_) => KeyType::ECDSA,
KeyPairType::ED25519(_) => KeyType::ED25519,
}
}
pub fn serialize_pem(&self, passphrase: Option<&str>) -> OsshResult<String> {
stringify_pem_privkey(self, passphrase)
}
pub fn serialize_pkcs8(&self, passphrase: Option<&str>) -> OsshResult<String> {
serialize_pkcs8_privkey(self, passphrase)
}
pub fn serialize_openssh(
&self,
passphrase: Option<&str>,
cipher: Cipher,
) -> OsshResult<String> {
if let Some(passphrase) = passphrase {
Ok(serialize_ossh_privkey(self, passphrase, cipher, 0)?)
} else {
Ok(serialize_ossh_privkey(self, "", Cipher::Null, 0)?)
}
}
pub fn comment(&self) -> &str {
&self.comment
}
pub fn comment_mut(&mut self) -> &mut String {
&mut self.comment
}
pub fn serialize_publickey(&self) -> OsshResult<String> {
serialize_ossh_pubkey(self, &self.comment)
}
pub fn clone_public_key(&self) -> Result<PublicKey, Error> {
let key = match &self.key {
KeyPairType::RSA(key) => PublicKeyType::RSA(key.clone_public_key()?),
KeyPairType::DSA(key) => PublicKeyType::DSA(key.clone_public_key()?),
KeyPairType::ECDSA(key) => PublicKeyType::ECDSA(key.clone_public_key()?),
KeyPairType::ED25519(key) => PublicKeyType::ED25519(key.clone_public_key()?),
};
Ok(PublicKey {
key,
comment: self.comment.clone(),
})
}
fn inner_key(&self) -> &dyn PrivateParts {
match &self.key {
KeyPairType::RSA(key) => key,
KeyPairType::DSA(key) => key,
KeyPairType::ECDSA(key) => key,
KeyPairType::ED25519(key) => key,
}
}
fn inner_key_pub(&self) -> &dyn PublicParts {
match &self.key {
KeyPairType::RSA(key) => key,
KeyPairType::DSA(key) => key,
KeyPairType::ECDSA(key) => key,
KeyPairType::ED25519(key) => key,
}
}
}
impl Key for KeyPair {
fn size(&self) -> usize {
self.inner_key().size()
}
fn keyname(&self) -> &'static str {
self.inner_key().keyname()
}
fn short_keyname(&self) -> &'static str {
self.inner_key().short_keyname()
}
}
impl PublicParts for KeyPair {
fn verify(&self, data: &[u8], sig: &[u8]) -> Result<bool, Error> {
self.inner_key_pub().verify(data, sig)
}
fn blob(&self) -> Result<Vec<u8>, Error> {
self.inner_key_pub().blob()
}
}
impl PrivateParts for KeyPair {
fn sign(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
self.inner_key().sign(data)
}
}
impl From<rsa::RsaKeyPair> for KeyPair {
fn from(inner: rsa::RsaKeyPair) -> KeyPair {
KeyPair {
key: KeyPairType::RSA(inner),
comment: String::new(),
}
}
}
impl From<dsa::DsaKeyPair> for KeyPair {
fn from(inner: dsa::DsaKeyPair) -> KeyPair {
KeyPair {
key: KeyPairType::DSA(inner),
comment: String::new(),
}
}
}
impl From<ecdsa::EcDsaKeyPair> for KeyPair {
fn from(inner: ecdsa::EcDsaKeyPair) -> KeyPair {
KeyPair {
key: KeyPairType::ECDSA(inner),
comment: String::new(),
}
}
}
impl From<ed25519::Ed25519KeyPair> for KeyPair {
fn from(inner: ed25519::Ed25519KeyPair) -> KeyPair {
KeyPair {
key: KeyPairType::ED25519(inner),
comment: String::new(),
}
}
}
pub trait Key {
fn size(&self) -> usize;
fn keyname(&self) -> &'static str;
fn short_keyname(&self) -> &'static str;
}
pub trait PublicParts: Key {
fn verify(&self, data: &[u8], sig: &[u8]) -> OsshResult<bool>;
fn blob(&self) -> OsshResult<Vec<u8>>;
fn fingerprint(&self, hash: FingerprintHash) -> OsshResult<Vec<u8>> {
let b = self.blob()?;
Ok(hash.hash(&b))
}
fn fingerprint_randomart(&self, hash: FingerprintHash) -> OsshResult<String> {
const FLDBASE: usize = 8;
const FLDSIZE_Y: usize = FLDBASE + 1;
const FLDSIZE_X: usize = FLDBASE * 2 + 1;
const AUGMENTATION_CHARS: &[u8] = b" .o+=*BOX@%&#/^SE";
let len = AUGMENTATION_CHARS.len() - 1;
let mut art = String::with_capacity((FLDSIZE_X + 3) * (FLDSIZE_Y + 2));
let mut field = [[0; FLDSIZE_X]; FLDSIZE_Y];
let mut x = FLDSIZE_X / 2;
let mut y = FLDSIZE_Y / 2;
let dgst_raw = self.fingerprint(hash)?;
for mut input in dgst_raw.iter().copied() {
for _ in 0..4 {
x = if (input & 0x1) != 0 {
x + 1
} else {
x.saturating_sub(1)
};
y = if (input & 0x2) != 0 {
y + 1
} else {
y.saturating_sub(1)
};
x = x.min(FLDSIZE_X - 1);
y = y.min(FLDSIZE_Y - 1);
if field[y][x] < len as u8 - 2 {
field[y][x] += 1;
}
input >>= 2;
}
}
field[FLDSIZE_Y / 2][FLDSIZE_X / 2] = len as u8 - 1;
field[y][x] = len as u8;
let title = format!("[{} {}]", self.short_keyname(), self.size());
let title = if title.chars().count() > FLDSIZE_X {
format!("[{}]", self.short_keyname())
} else {
title
};
let hash = format!("[{}]", hash.name());
art += &format!("+{:-^width$}+\n", title, width = FLDSIZE_X);
#[allow(clippy::needless_range_loop)]
for y in 0..FLDSIZE_Y {
art.push('|');
art.extend(
field[y]
.iter()
.map(|&c| AUGMENTATION_CHARS[c as usize] as char),
);
art += "|\n";
}
art += &format!("+{:-^width$}+", hash, width = FLDSIZE_X);
Ok(art)
}
}
pub trait PrivateParts: Key {
fn sign(&self, data: &[u8]) -> OsshResult<Vec<u8>>;
}
#[test]
#[ignore]
fn test_size() {
use std::mem::size_of;
eprintln!("PublicKey: {} bytes", size_of::<PublicKey>());
eprintln!("\tRSA: {} bytes", size_of::<rsa::RsaPublicKey>());
eprintln!("\tDSA: {} bytes", size_of::<dsa::DsaPublicKey>());
eprintln!("\tECDSA: {} bytes", size_of::<ecdsa::EcDsaPublicKey>());
eprintln!(
"\tED25519: {} bytes",
size_of::<ed25519::Ed25519PublicKey>()
);
eprintln!("KeyPair: {} bytes", size_of::<KeyPair>());
eprintln!("\tRSA: {} bytes", size_of::<rsa::RsaKeyPair>());
eprintln!("\tDSA: {} bytes", size_of::<dsa::DsaKeyPair>());
eprintln!("\tECDSA: {} bytes", size_of::<ecdsa::EcDsaKeyPair>());
eprintln!("\tED25519: {} bytes", size_of::<ed25519::Ed25519KeyPair>());
}