use pgp::composed::{
Deserializable, DetachedSignature, Message, SignedPublicKey, SignedSecretKey,
VerificationResult,
};
use pgp::types::{Password, VerifyingKey};
use serde::{Deserialize, Serialize};
use super::errors::CryptoError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum VerificationStatus {
Ok,
NotSigned,
NoVerifier,
Failed,
}
impl VerificationStatus {
pub fn is_ok(self) -> bool {
matches!(self, VerificationStatus::Ok)
}
}
#[derive(Debug, Clone)]
pub struct PublicKey {
key: SignedPublicKey,
}
impl PublicKey {
pub fn from_armored(armored: &str) -> Result<Self, CryptoError> {
let (key, _headers) = SignedPublicKey::from_string(armored)
.map_err(|e| CryptoError::Parse(format!("public key: {e}")))?;
Ok(Self { key })
}
fn signed(&self) -> &SignedPublicKey {
&self.key
}
}
#[derive(Debug, Clone, Default)]
pub struct VerificationKeyRing {
keys: Vec<SignedPublicKey>,
}
impl VerificationKeyRing {
pub fn empty() -> Self {
Self::default()
}
pub fn from_public_keys(keys: &[PublicKey]) -> Self {
Self {
keys: keys.iter().map(|k| k.signed().clone()).collect(),
}
}
pub fn from_private(key: &super::PrivateKey) -> Self {
Self {
keys: vec![key.signed_public_key()],
}
}
pub fn from_private_and_public_keys(
key: &super::PrivateKey,
public_keys: &[PublicKey],
) -> Self {
let mut keys = Vec::with_capacity(public_keys.len() + 1);
keys.push(key.signed_public_key());
keys.extend(public_keys.iter().map(|k| k.signed().clone()));
Self { keys }
}
pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}
fn dyn_refs(&self) -> Vec<&dyn VerifyingKey> {
self.keys.iter().map(|k| k as &dyn VerifyingKey).collect()
}
}
pub fn verify_detached(
armored_sig: &str,
data: &[u8],
ring: &VerificationKeyRing,
) -> VerificationStatus {
let signature = match DetachedSignature::from_string(armored_sig) {
Ok((sig, _headers)) => sig,
Err(_) => return VerificationStatus::Failed,
};
if ring.is_empty() {
return VerificationStatus::NoVerifier;
}
for key in &ring.keys {
if signature.verify(key, data).is_ok() {
return VerificationStatus::Ok;
}
}
VerificationStatus::Failed
}
pub(crate) fn decrypt_and_verify(
armored: &str,
key: &SignedSecretKey,
key_pw: &Password,
ring: &VerificationKeyRing,
) -> Result<(Vec<u8>, VerificationStatus), CryptoError> {
let (message, _headers) =
Message::from_string(armored).map_err(|e| CryptoError::Parse(format!("message: {e}")))?;
let mut decrypted = message
.decrypt(key_pw, key)
.map_err(|e| CryptoError::Decrypt(e.to_string()))?;
while decrypted.is_compressed() {
decrypted = decrypted
.decompress()
.map_err(|e| CryptoError::Decrypt(format!("decompress: {e}")))?;
}
let data = decrypted
.as_data_vec()
.map_err(|e| CryptoError::Decrypt(format!("read literal: {e}")))?;
let status = verify_message(&decrypted, ring);
Ok((data, status))
}
fn verify_message(message: &Message<'_>, ring: &VerificationKeyRing) -> VerificationStatus {
if !message.is_signed() && !message.is_one_pass_signed() {
return VerificationStatus::NotSigned;
}
if ring.is_empty() {
return VerificationStatus::NoVerifier;
}
match message.verify_nested(&ring.dyn_refs()) {
Ok(results) => {
if results
.iter()
.any(|r| matches!(r, VerificationResult::Valid(_)))
{
VerificationStatus::Ok
} else {
VerificationStatus::Failed
}
}
Err(_) => VerificationStatus::Failed,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::generate_node_key;
#[test]
fn detached_signature_verifies_against_signer() {
let signer = generate_node_key().expect("generate signer key");
let data = b"manifest bytes";
let sig = signer.key.sign_detached(data).expect("sign");
let ring = VerificationKeyRing::from_private(&signer.key);
assert_eq!(verify_detached(&sig, data, &ring), VerificationStatus::Ok);
}
#[test]
fn detached_signature_fails_against_wrong_key() {
let signer = generate_node_key().expect("generate signer key");
let other = generate_node_key().expect("generate other key");
let data = b"manifest bytes";
let sig = signer.key.sign_detached(data).expect("sign");
let ring = VerificationKeyRing::from_private(&other.key);
assert_eq!(
verify_detached(&sig, data, &ring),
VerificationStatus::Failed
);
}
#[test]
fn detached_signature_fails_on_tampered_data() {
let signer = generate_node_key().expect("generate signer key");
let sig = signer.key.sign_detached(b"manifest bytes").expect("sign");
let ring = VerificationKeyRing::from_private(&signer.key);
assert_eq!(
verify_detached(&sig, b"tampered bytes", &ring),
VerificationStatus::Failed
);
}
#[test]
fn detached_signature_without_verifier_is_no_verifier() {
let signer = generate_node_key().expect("generate signer key");
let data = b"manifest bytes";
let sig = signer.key.sign_detached(data).expect("sign");
assert_eq!(
verify_detached(&sig, data, &VerificationKeyRing::empty()),
VerificationStatus::NoVerifier
);
}
#[test]
fn inline_signed_message_round_trips_with_status() {
let signer = generate_node_key().expect("generate signer key");
let armored = signer
.key
.encrypt_and_sign(&signer.key, b"hello world", false, true)
.expect("encrypt+sign");
let ring = VerificationKeyRing::from_private(&signer.key);
let (data, status) = signer
.key
.decrypt_armored_verify(&armored, &ring)
.expect("decrypt+verify");
assert_eq!(data, b"hello world");
assert_eq!(status, VerificationStatus::Ok);
}
#[test]
fn inline_signed_message_without_verifier_is_no_verifier() {
let signer = generate_node_key().expect("generate signer key");
let armored = signer
.key
.encrypt_and_sign(&signer.key, b"hello world", false, true)
.expect("encrypt+sign");
let (data, status) = signer
.key
.decrypt_armored_verify(&armored, &VerificationKeyRing::empty())
.expect("decrypt+verify");
assert_eq!(data, b"hello world");
assert_eq!(status, VerificationStatus::NoVerifier);
}
}