From 0470009cfc1ec678ff381179c87f976725a665fb Mon Sep 17 00:00:00 2001 From: Mitchell Grenier Date: Thu, 18 Jul 2024 02:15:08 -0700 Subject: [PATCH] Add new metadata functionality for SK keys --- Cargo.toml | 4 ++ examples/parse-ssh-signature.rs | 33 ++++++++++++ src/ssh/signature.rs | 96 ++++++++++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 examples/parse-ssh-signature.rs diff --git a/Cargo.toml b/Cargo.toml index 6cc0653..99fde9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,6 +126,10 @@ required-features = ["x509-support"] name = "new-fido-sshkey" required-features = ["fido-support-mozilla"] +[[example]] +name = "parse-ssh-signature" +required-features = ["fido-support-mozilla"] + [[test]] name = "privkey-encrypted" path = "tests/privkey_encrypted.rs" diff --git a/examples/parse-ssh-signature.rs b/examples/parse-ssh-signature.rs new file mode 100644 index 0000000..ad5ae1c --- /dev/null +++ b/examples/parse-ssh-signature.rs @@ -0,0 +1,33 @@ +use clap::{Arg, Command}; +use std::io; + +use sshcerts::{ssh::SshSignature, *}; + +fn main() { + let input = io::stdin() + .lines() + .collect::, _>>() + .unwrap() + .join("\n") + .to_string(); + + let signature = SshSignature::from_armored_string(&input).unwrap(); + + if let Ok(Some(meta)) = signature.metadata() { + println!("Application:\t\t{}", meta.application().unwrap_or_default()); + println!( + "User Present:\t\t{}", + meta.user_presence().unwrap_or_default() + ); + println!( + "User Verification:\t{}", + meta.user_verification().unwrap_or_default() + ); + println!( + "Counter:\t\t{}", + meta.signature_counter().unwrap_or_default() + ); + } else { + println!("Couldn't parse metadata."); + } +} diff --git a/src/ssh/signature.rs b/src/ssh/signature.rs index 8dd3c18..9ff05b7 100644 --- a/src/ssh/signature.rs +++ b/src/ssh/signature.rs @@ -82,6 +82,53 @@ pub struct VerifiedSshSignature { pub message: Vec, } +/// Metadata about a signature. This is generally only available for SK +/// signatures. +#[derive(Debug)] +pub enum SignatureMetadata { + /// Metadata for an SK signature + SkMetadata { + /// Key application. + application: String, + /// Raw flags as part of the signature + flags: u8, + /// The signature counter + signature_counter: u32, + }, +} + +impl SignatureMetadata { + /// Returns the application as an Option + pub fn application(&self) -> Option { + match self { + SignatureMetadata::SkMetadata { application, .. } => Some(application.clone()), + } + } + + /// Returns true if the signature had user presence when created + pub fn user_presence(&self) -> Option { + match self { + SignatureMetadata::SkMetadata { flags, .. } => Some(flags & 0x1 > 0x00), + } + } + + /// Returns true if the signature had user verification when created + pub fn user_verification(&self) -> Option { + match self { + SignatureMetadata::SkMetadata { flags, .. } => Some(flags & 0x4 > 0x0), + } + } + + /// Returns how many signatures the authenticator has made + pub fn signature_counter(&self) -> Option { + match self { + SignatureMetadata::SkMetadata { + signature_counter, .. + } => Some(*signature_counter), + } + } +} + impl std::fmt::Display for VerifiedSshSignature { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", &self.signature) @@ -117,9 +164,53 @@ impl SshSignature { let decoded = base64::decode(encoded_signature)?; let mut reader = Reader::new(&decoded); // Construct a new `SshSignature` - let k = Self::from_reader(&mut reader)?; + Ok(Self::from_reader(&mut reader)?) + } - Ok(k) + /// Return signature metadata. This is generally only available + /// for SK signatures. + pub fn metadata(&self) -> Result> { + let mut reader = Reader::new(&self.signature); + reader.read_string().and_then(|v| KeyType::from_name(&v))?; // Skip the key type + + match &self.pubkey.kind { + PublicKeyKind::Ecdsa(key) => { + if let Some(application) = key.sk_application.clone() { + // Skip the mpints which make up the signature itself + reader.read_positive_mpint()?; + reader.read_positive_mpint()?; + + let flags = reader.read_raw_bytes(1)?[0]; + let signature_counter = reader.read_u32()?; + + Ok(Some(SignatureMetadata::SkMetadata { + application, + flags, + signature_counter, + })) + } else { + Ok(None) + } + } + PublicKeyKind::Ed25519(key) => { + if let Some(application) = key.sk_application.clone() { + // Skip bytes which make up the signature + reader.read_bytes()?; + + let flags = reader.read_raw_bytes(1)?[0]; + let signature_counter = reader.read_u32()?; + + Ok(Some(SignatureMetadata::SkMetadata { + application, + flags, + signature_counter, + })) + } else { + Ok(None) + } + } + _ => Ok(None), + } } pub(crate) fn from_reader(reader: &mut Reader<'_>) -> Result { @@ -377,6 +468,7 @@ pub(crate) fn verify_signature( 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())