#[cfg(feature = "alloc")]
mod dsa;
#[cfg(feature = "ecdsa")]
mod ecdsa;
mod ed25519;
mod key_data;
mod openssh;
#[cfg(feature = "alloc")]
mod rsa;
mod sk;
pub use self::{ed25519::Ed25519PublicKey, key_data::KeyData, sk::SkEd25519};
#[cfg(feature = "alloc")]
pub use self::{dsa::DsaPublicKey, rsa::RsaPublicKey};
#[cfg(feature = "ecdsa")]
pub use self::{ecdsa::EcdsaPublicKey, sk::SkEcdsaSha2NistP256};
pub(crate) use self::openssh::Encapsulation;
use crate::{
decode::Decode,
encode::Encode,
reader::{Base64Reader, Reader},
Algorithm, Error, Result,
};
use core::str::FromStr;
#[cfg(feature = "alloc")]
use {
crate::{checked::CheckedSum, writer::base64_len},
alloc::{
borrow::ToOwned,
string::{String, ToString},
vec::Vec,
},
};
#[cfg(feature = "fingerprint")]
use crate::{Fingerprint, HashAlg};
#[cfg(all(feature = "alloc", feature = "serde"))]
use serde::{de, ser, Deserialize, Serialize};
#[cfg(feature = "std")]
use std::{fs, path::Path};
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct PublicKey {
pub(crate) key_data: KeyData,
#[cfg(feature = "alloc")]
pub(crate) comment: String,
}
impl PublicKey {
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn new(key_data: KeyData, comment: impl Into<String>) -> Self {
Self {
key_data,
comment: comment.into(),
}
}
pub fn from_openssh(public_key: &str) -> Result<Self> {
let encapsulation = Encapsulation::decode(public_key.trim_end().as_bytes())?;
let mut reader = Base64Reader::new(encapsulation.base64_data)?;
let key_data = KeyData::decode(&mut reader)?;
if encapsulation.algorithm_id != key_data.algorithm().as_str() {
return Err(Error::Algorithm);
}
let public_key = Self {
key_data,
#[cfg(feature = "alloc")]
comment: encapsulation.comment.to_owned(),
};
reader.finish(public_key)
}
pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
let reader = &mut bytes;
let key_data = KeyData::decode(reader)?;
reader.finish(key_data.into())
}
pub fn encode_openssh<'o>(&self, out: &'o mut [u8]) -> Result<&'o str> {
Encapsulation::encode(out, self.algorithm().as_str(), self.comment(), |writer| {
self.key_data.encode(writer)
})
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn to_openssh(&self) -> Result<String> {
let encoded_len = [
2, self.algorithm().as_str().len(),
base64_len(self.key_data.encoded_len()?),
self.comment.len(),
]
.checked_sum()?;
let mut buf = vec![0u8; encoded_len];
let actual_len = self.encode_openssh(&mut buf)?.len();
buf.truncate(actual_len);
Ok(String::from_utf8(buf)?)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut public_key_bytes = Vec::new();
self.key_data.encode(&mut public_key_bytes)?;
Ok(public_key_bytes)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn read_openssh_file(path: &Path) -> Result<Self> {
let input = fs::read_to_string(path)?;
Self::from_openssh(&*input)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn write_openssh_file(&self, path: &Path) -> Result<()> {
let encoded = self.to_openssh()?;
fs::write(path, encoded.as_bytes())?;
Ok(())
}
pub fn algorithm(&self) -> Algorithm {
self.key_data.algorithm()
}
#[cfg(not(feature = "alloc"))]
pub fn comment(&self) -> &str {
""
}
#[cfg(feature = "alloc")]
pub fn comment(&self) -> &str {
&self.comment
}
pub fn key_data(&self) -> &KeyData {
&self.key_data
}
#[cfg(feature = "fingerprint")]
#[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))]
pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
self.key_data.fingerprint(hash_alg)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn set_comment(&mut self, comment: impl Into<String>) {
self.comment = comment.into();
}
#[cfg(not(feature = "alloc"))]
pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> {
reader.drain_prefixed()?;
Ok(())
}
#[cfg(feature = "alloc")]
pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> {
self.comment = String::decode(reader)?;
Ok(())
}
}
impl From<KeyData> for PublicKey {
fn from(key_data: KeyData) -> PublicKey {
PublicKey {
key_data,
#[cfg(feature = "alloc")]
comment: String::new(),
}
}
}
impl From<PublicKey> for KeyData {
fn from(public_key: PublicKey) -> KeyData {
public_key.key_data
}
}
impl From<&PublicKey> for KeyData {
fn from(public_key: &PublicKey) -> KeyData {
public_key.key_data.clone()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl From<DsaPublicKey> for PublicKey {
fn from(public_key: DsaPublicKey) -> PublicKey {
KeyData::from(public_key).into()
}
}
#[cfg(feature = "ecdsa")]
#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
impl From<EcdsaPublicKey> for PublicKey {
fn from(public_key: EcdsaPublicKey) -> PublicKey {
KeyData::from(public_key).into()
}
}
impl From<Ed25519PublicKey> for PublicKey {
fn from(public_key: Ed25519PublicKey) -> PublicKey {
KeyData::from(public_key).into()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl From<RsaPublicKey> for PublicKey {
fn from(public_key: RsaPublicKey) -> PublicKey {
KeyData::from(public_key).into()
}
}
#[cfg(feature = "ecdsa")]
#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
impl From<SkEcdsaSha2NistP256> for PublicKey {
fn from(public_key: SkEcdsaSha2NistP256) -> PublicKey {
KeyData::from(public_key).into()
}
}
impl From<SkEd25519> for PublicKey {
fn from(public_key: SkEd25519) -> PublicKey {
KeyData::from(public_key).into()
}
}
impl FromStr for PublicKey {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_openssh(s)
}
}
#[cfg(feature = "alloc")]
impl ToString for PublicKey {
fn to_string(&self) -> String {
self.to_openssh().expect("SSH public key encoding error")
}
}
#[cfg(all(feature = "alloc", feature = "serde"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "serde"))))]
impl<'de> Deserialize<'de> for PublicKey {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let string = String::deserialize(deserializer)?;
Self::from_openssh(&string).map_err(de::Error::custom)
} else {
let bytes = Vec::<u8>::deserialize(deserializer)?;
Self::from_bytes(&bytes).map_err(de::Error::custom)
}
}
}
#[cfg(all(feature = "alloc", feature = "serde"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "serde"))))]
impl Serialize for PublicKey {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
if serializer.is_human_readable() {
self.to_openssh()
.map_err(ser::Error::custom)?
.serialize(serializer)
} else {
self.to_bytes()
.map_err(ser::Error::custom)?
.serialize(serializer)
}
}
}