use ring::{
digest,
signature::{
RsaPublicKeyComponents, UnparsedPublicKey, ECDSA_P256_SHA256_FIXED,
ECDSA_P384_SHA384_FIXED, ED25519, RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY,
RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_2048_8192_SHA512,
},
};
use crate::{error::Error, ssh::Writer, PrivateKey, PublicKey, Result};
use super::{KeyType, PublicKeyKind, Reader, SSHCertificateSigner};
#[derive(Debug)]
pub enum HashAlgorithm {
Sha256,
Sha512,
}
impl HashAlgorithm {
pub fn as_str(&self) -> &'static str {
match self {
HashAlgorithm::Sha256 => "sha256",
HashAlgorithm::Sha512 => "sha512",
}
}
}
#[derive(Debug)]
pub struct SshSignature {
pub pubkey: PublicKey,
pub namespace: String,
pub hash_algorithm: HashAlgorithm,
pub signature: Vec<u8>,
}
impl std::fmt::Display for SshSignature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut writer = Writer::new();
writer.write_raw_bytes(&[0x53, 0x53, 0x48, 0x53, 0x49, 0x47]);
writer.write_u32(1);
writer.write_pub_key(&self.pubkey);
writer.write_string(&self.namespace);
writer.write_bytes(&[]);
writer.write_string(self.hash_algorithm.as_str());
writer.write_bytes(&self.signature);
let encoded = base64::encode(&writer.into_bytes());
let lines: Vec<_> = encoded
.chars()
.collect::<Vec<char>>()
.chunks(76)
.map(|chunk| chunk.into_iter().collect::<String>())
.collect();
write!(
f,
"-----BEGIN SSH SIGNATURE-----\n{}\n-----END SSH SIGNATURE-----",
lines.join("\n")
)
}
}
#[derive(Debug)]
pub struct VerifiedSshSignature {
pub signature: SshSignature,
pub message: Vec<u8>,
}
impl std::fmt::Display for VerifiedSshSignature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.signature)
}
}
impl SshSignature {
pub fn from_armored_string(contents: &str) -> Result<Self> {
let mut iter = contents.lines();
let header = iter.next().unwrap_or("");
if header != "-----BEGIN SSH SIGNATURE-----" {
return Err(Error::InvalidFormat);
}
let mut encoded_signature = String::new();
loop {
let part = match iter.next() {
Some(p) => p,
None => return Err(Error::InvalidFormat),
};
if part == "-----END SSH SIGNATURE-----" {
break;
}
encoded_signature.push_str(part);
}
let decoded = base64::decode(encoded_signature)?;
let mut reader = Reader::new(&decoded);
let k = Self::from_reader(&mut reader)?;
Ok(k)
}
pub(crate) fn from_reader(reader: &mut Reader<'_>) -> Result<Self> {
let preamble = reader.read_raw_bytes(6)?;
if preamble != [0x53, 0x53, 0x48, 0x53, 0x49, 0x47] {
return Err(Error::InvalidFormat);
}
let version = reader.read_u32()?;
if version != 1 {
return Err(Error::Unsupported);
}
let pubkey = reader
.read_bytes()
.and_then(|v| PublicKey::from_bytes(&v))?;
let namespace = reader.read_string()?;
if namespace.is_empty() {
return Err(Error::InvalidFormat);
}
let _reserved = reader.read_string()?;
let hash_algorithm = match reader.read_string()?.as_str() {
"sha256" => HashAlgorithm::Sha256,
"sha512" => HashAlgorithm::Sha512,
_ => return Err(Error::Unsupported),
};
let signature = reader.read_bytes()?;
Ok(Self {
pubkey,
namespace,
hash_algorithm,
signature,
})
}
fn to_signed_format(&self, message: &[u8]) -> Vec<u8> {
let mut signed_message = Writer::new();
signed_message.write_raw_bytes(&[0x53, 0x53, 0x48, 0x53, 0x49, 0x47]);
signed_message.write_string(&self.namespace);
signed_message.write_bytes(&[]);
signed_message.write_string(self.hash_algorithm.as_str());
let algorithm = match self.hash_algorithm {
HashAlgorithm::Sha256 => &digest::SHA256,
HashAlgorithm::Sha512 => &digest::SHA512,
};
signed_message.write_bytes(digest::digest(algorithm, message).as_ref());
signed_message.into_bytes()
}
}
impl VerifiedSshSignature {
pub fn from_ssh_signature(
message: &[u8],
ssh_signature: SshSignature,
namespace: &str,
pub_key: Option<PublicKey>,
) -> Result<Self> {
if let Some(pub_key) = pub_key {
if ssh_signature.pubkey != pub_key {
return Err(Error::InvalidSignature);
}
}
if namespace != ssh_signature.namespace {
return Err(Error::InvalidSignature);
}
match verify_signature(
&ssh_signature.signature,
&ssh_signature.to_signed_format(message),
&ssh_signature.pubkey,
) {
Ok(_) => Ok(Self {
signature: ssh_signature,
message: message.to_vec(),
}),
Err(_) => Err(Error::InvalidSignature),
}
}
pub fn new_with_private_key(
message: &[u8],
namespace: &str,
private_key: PrivateKey,
hash_algorithm: Option<HashAlgorithm>,
) -> Result<Self> {
let hash_algorithm = hash_algorithm.unwrap_or(HashAlgorithm::Sha512);
let mut ssh_signature = SshSignature {
pubkey: private_key.pubkey.clone(),
namespace: namespace.to_string(),
hash_algorithm,
signature: vec![],
};
let tbs_message = ssh_signature.to_signed_format(message);
let signature = private_key.sign(&tbs_message).ok_or(Error::SigningError)?;
ssh_signature.signature = signature;
VerifiedSshSignature::from_ssh_signature(
message,
ssh_signature,
namespace,
Some(private_key.pubkey.clone()),
)
}
}
pub(crate) fn verify_signature(
signature_buf: &[u8],
signed_bytes: &[u8],
public_key: &PublicKey,
) -> Result<Vec<u8>> {
let mut reader = Reader::new(&signature_buf);
let sig_type = reader.read_string().and_then(|v| KeyType::from_name(&v))?;
if public_key.key_type.kind != sig_type.kind {
return Err(Error::KeyTypeMismatch);
}
match &public_key.kind {
PublicKeyKind::Ecdsa(key) => {
let sig_reader = reader.read_bytes()?;
let mut sig_reader = Reader::new(&sig_reader);
let (alg, len) = match sig_type.name {
"ecdsa-sha2-nistp256" | "sk-ecdsa-sha2-nistp256@openssh.com" => {
(&ECDSA_P256_SHA256_FIXED, 32)
}
"ecdsa-sha2-nistp384" => (&ECDSA_P384_SHA384_FIXED, 48),
_ => return Err(Error::KeyTypeMismatch),
};
let r_bytes = sig_reader.read_positive_mpint()?;
let s_bytes = sig_reader.read_positive_mpint()?;
if r_bytes.len() > len || s_bytes.len() > len {
return Err(Error::InvalidFormat);
}
let mut r = vec![0; len - r_bytes.len()];
let mut s = vec![0; len - s_bytes.len()];
r.extend(r_bytes);
s.extend(s_bytes);
let mut sig = r;
sig.extend(s);
if let Some(sk_application) = &key.sk_application {
let flags = reader.read_raw_bytes(1)?[0];
let signature_counter = reader.read_u32()?;
let mut app_hash = digest::digest(&digest::SHA256, sk_application.as_bytes())
.as_ref()
.to_vec();
let mut data_hash = digest::digest(&digest::SHA256, signed_bytes)
.as_ref()
.to_vec();
app_hash.push(flags);
app_hash.extend_from_slice(&signature_counter.to_be_bytes());
app_hash.append(&mut data_hash);
UnparsedPublicKey::new(alg, &key.key).verify(&app_hash, &sig)?;
} else {
UnparsedPublicKey::new(alg, &key.key).verify(signed_bytes, &sig)?;
}
Ok(signature_buf.to_vec())
}
PublicKeyKind::Rsa(key) => {
let alg = match sig_type.name {
"rsa-sha2-256" => &RSA_PKCS1_2048_8192_SHA256,
"rsa-sha2-512" => &RSA_PKCS1_2048_8192_SHA512,
"ssh-rsa" => &RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY,
_ => return Err(Error::KeyTypeMismatch),
};
let signature = reader.read_bytes()?;
let public_key = RsaPublicKeyComponents {
n: &key.n,
e: &key.e,
};
public_key.verify(alg, signed_bytes, &signature)?;
Ok(signature_buf.to_vec())
}
PublicKeyKind::Ed25519(key) => {
match sig_type.name {
"ssh-ed25519" => (),
"sk-ssh-ed25519@openssh.com" => (),
_ => return Err(Error::KeyTypeMismatch),
};
let alg = &ED25519;
let signature = reader.read_bytes()?;
let peer_public_key = UnparsedPublicKey::new(alg, &key.key);
if let Some(sk_application) = &key.sk_application {
let flags = reader.read_raw_bytes(1)?[0];
let signature_counter = reader.read_u32()?;
let mut app_hash = digest::digest(&digest::SHA256, sk_application.as_bytes())
.as_ref()
.to_vec();
let mut data_hash = digest::digest(&digest::SHA256, signed_bytes)
.as_ref()
.to_vec();
app_hash.push(flags);
app_hash.extend_from_slice(&signature_counter.to_be_bytes());
app_hash.append(&mut data_hash);
peer_public_key.verify(&app_hash, &signature)?;
} else {
peer_public_key.verify(signed_bytes, &signature)?;
}
Ok(signature_buf.to_vec())
}
}
}