diff --git a/src/identity.rs b/src/identity.rs index 1e950bd..550ab1b 100644 --- a/src/identity.rs +++ b/src/identity.rs @@ -35,7 +35,7 @@ use crate::{InvalidSig, SsiPub, SsiSecret, SsiSig}; #[display(doc_comments)] pub enum UidParseError { #[from] - /// non-UTF-8 UID. {0} + /// non-UTF-8 UID - {0} Utf8(Utf8Error), /// UID '{0}' without identity part NoId(String), @@ -137,16 +137,16 @@ pub enum SsiParseError { InvalidUid(UidParseError), #[from] - /// SSI contains signature not matching the provided data. {0} + /// SSI contains signature not matching the provided data - {0} WrongSig(InvalidSig), #[from] - /// SSI contains non-parsable expiration date. {0} + /// SSI contains non-parsable expiration date - {0} WrongExpiry(chrono::ParseError), - /// SSI contains non-parsable public key. {0} + /// SSI contains non-parsable public key - {0} InvalidPub(Baid64ParseError), - /// SSI contains non-parsable signature. {0} + /// SSI contains non-parsable signature - {0} InvalidSig(Baid64ParseError), } diff --git a/src/lib.rs b/src/lib.rs index 282d332..05f7abb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,8 +36,8 @@ pub use bip340::Bip340Secret; pub use ed25519::Ed25519Secret; pub use identity::{Ssi, SsiParseError, Uid}; pub use public::{ - Algo, Chain, Fingerprint, InvalidPubkey, InvalidSig, SsiCert, SsiPub, SsiQuery, SsiSig, - UnknownAlgo, UnknownChain, + Algo, CertParseError, Chain, Fingerprint, InvalidPubkey, InvalidSig, SsiCert, SsiPub, SsiQuery, + SsiSig, UnknownAlgo, UnknownChain, }; pub use runtime::{Error, SsiRuntime, SSI_DIR}; pub use secret::{SecretParseError, SsiPair, SsiSecret}; diff --git a/src/main.rs b/src/main.rs index 133e6a8..9064e66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,7 @@ use std::str::FromStr; use chrono::{DateTime, Utc}; use clap::Parser; -use ssi::{Algo, Chain, InvalidSig, Ssi, SsiQuery, SsiRuntime, SsiSecret, Uid}; +use ssi::{Algo, Chain, InvalidSig, Ssi, SsiCert, SsiQuery, SsiRuntime, SsiSecret, Uid}; #[derive(Parser, Clone, Debug)] pub struct Args { @@ -72,6 +72,7 @@ pub enum Command { expiry: Option, }, + /// List known identitites List { /// List only signing identities #[clap(short, long)] @@ -91,12 +92,11 @@ pub enum Command { /// Identity to use for the signature ssi: SsiQuery, }, - /* + Verify { /// Signature certificate to verify signature: SsiCert, }, - */ } fn main() { @@ -176,19 +176,19 @@ fn main() { } Command::Sign { text, file, ssi } => { - eprintln!("Signing with {ssi:?}"); + eprintln!("Signing with {ssi} ..."); let passwd = rpassword::prompt_password("Password for private key encryption: ") .expect("unable to read password"); let msg = match (text, file) { - (Some(t), None) => t, - (None, Some(f)) => fs::read_to_string(f).expect("unable to read the file"), + (Some(t), None) => t.into_bytes(), + (None, Some(f)) => fs::read(f).expect("unable to read the file"), (None, None) => { let mut s = String::new(); stdin() .read_to_string(&mut s) .expect("unable to read standard input"); - s + s.into_bytes() } _ => unreachable!(), }; @@ -199,5 +199,17 @@ fn main() { let cert = signer.sign(msg); println!("{cert}"); } + + Command::Verify { signature } => { + eprint!("Verifying signature for message digest {} ... ", signature.msg); + let ssi = runtime + .find_identity(signature.fp) + .expect("unknown signing identity"); + match ssi.pk.verify(signature.msg.to_byte_array(), signature.sig) { + Ok(_) => eprintln!("valid"), + Err(err) => eprintln!("invalid: {err}"), + } + println!(); + } } } diff --git a/src/public.rs b/src/public.rs index 9ee3ef0..5f7f1e2 100644 --- a/src/public.rs +++ b/src/public.rs @@ -23,7 +23,7 @@ use std::fmt::{self, Display, Formatter}; use std::hash::Hash; use std::str::FromStr; -use amplify::{Bytes, Bytes32, Display}; +use amplify::{hex, Bytes, Bytes32, Display}; use crate::baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str}; @@ -195,7 +195,7 @@ impl FromStr for SsiPub { pub struct SsiSig(pub(crate) [u8; 64]); impl DisplayBaid64<64> for SsiSig { - const HRI: &'static str = "ssi:sig"; + const HRI: &'static str = ""; const CHUNKING: bool = false; const PREFIX: bool = false; const EMBED_CHECKSUM: bool = false; @@ -293,6 +293,41 @@ pub struct SsiCert { pub sig: SsiSig, } +#[derive(Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum CertParseError { + /// SSI URI lacks signature or message information. + DataMissed, + /// invalid fingerprint data in private key - {0}. + InvalidFingerprint(Baid64ParseError), + /// invalid message digest - {0}. + #[from] + InvalidMessage(hex::Error), + #[from] + /// invalid signature data - {0} + InvalidSig(Baid64ParseError), +} + +impl FromStr for SsiCert { + type Err = CertParseError; + + fn from_str(s: &str) -> Result { + let (fp, rest) = s + .trim_start_matches("ssi:") + .split_once('?') + .ok_or(CertParseError::DataMissed)?; + let (msg, rest) = rest + .trim_start_matches("msg=") + .split_once('&') + .ok_or(CertParseError::DataMissed)?; + let sig = rest.trim_start_matches("sig="); + let fp = Fingerprint::from_str(fp).map_err(CertParseError::InvalidFingerprint)?; + let msg = Bytes32::from_str(msg)?; + let sig = SsiSig::from_str(sig)?; + Ok(SsiCert { fp, msg, sig }) + } +} + impl Display for SsiCert { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "ssi:{fp}?msg={msg}&sig={sig}", fp = self.fp, msg = self.msg, sig = self.sig) diff --git a/src/secret.rs b/src/secret.rs index 6e59c95..7a37091 100644 --- a/src/secret.rs +++ b/src/secret.rs @@ -44,10 +44,10 @@ pub enum SsiSecret { pub enum SecretParseError { /// incomplete private key data. Incomplete, - /// invalid fingerprint data in private key. {0}. + /// invalid fingerprint data in private key - {0}. InvalidFingerprint(Baid64ParseError), #[from] - /// invalid secret key data. {0} + /// invalid secret key data - {0} InvalidSecret(Baid64ParseError), }