rpgpie 0.9.1

Experimental high level API for rPGP
Documentation
// SPDX-FileCopyrightText: Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0

use pgp::{
    composed::SignedPublicKey,
    crypto::ecc_curve::ECCCurve,
    packet::{PublicKey, Signature},
    types::{EcdsaPublicParams, EddsaLegacyPublicParams, PublicParams},
};

use crate::{
    certificate::Certificate,
    model::info::{print::print_cert_info, transform::cert_to_info},
    signature::{merge_unhashed, signature_bytes_eq},
};

/// Pick the signature with the newer creation time, if creation time differs
pub(crate) fn newer<'a>(a: &'a Signature, b: &'a Signature) -> &'a Signature {
    if a.created() > b.created() { a } else { b }
}

/// Produce a human-readable name for the public key algorithm in a PublicParams
pub(crate) fn algo_name(pp: &PublicParams) -> String {
    match pp {
        PublicParams::RSA(key) => {
            use rsa::traits::PublicKeyParts;
            format!("RSA({})", key.key.n().to_bytes_be().len() * 8)
        }
        PublicParams::DSA(key) => {
            format!("DSA({})", key.key.y().to_bytes_be().len() * 8)
        }
        PublicParams::Elgamal(_) => "Elgamal".to_string(),

        PublicParams::ECDH(params) => {
            format!("ECDH/{}", params.curve().name())
        }

        PublicParams::ECDSA(params) => {
            let curve = match params {
                EcdsaPublicParams::P256 { .. } => ECCCurve::P256,
                EcdsaPublicParams::P384 { .. } => ECCCurve::P384,
                EcdsaPublicParams::P521 { .. } => ECCCurve::P521,
                EcdsaPublicParams::Secp256k1 { .. } => ECCCurve::Secp256k1,
                EcdsaPublicParams::Unsupported { curve, .. } => curve.clone(),
            };

            format!("ECDSA/{}", curve.name())
        }

        PublicParams::EdDSALegacy(EddsaLegacyPublicParams::Ed25519 { .. }) => {
            "EdDSA/Curve25519".to_string()
        }

        PublicParams::Ed25519(_) => "Ed25519".to_string(),
        PublicParams::X25519(_) => "X25519".to_string(),
        PublicParams::Ed448(_) => "Ed448".to_string(),
        PublicParams::X448(_) => "X448".to_string(),

        #[cfg(feature = "draft-pqc")]
        PublicParams::MlKem768X25519(_) => "ML-KEM-768+X25519".to_string(),
        #[cfg(feature = "draft-pqc")]
        PublicParams::MlKem1024X448(_) => "ML-KEM-1024+X448".to_string(),
        #[cfg(feature = "draft-pqc")]
        PublicParams::MlDsa65Ed25519(_) => "ML-DSA-65+Ed25519".to_string(),
        #[cfg(feature = "draft-pqc")]
        PublicParams::MlDsa87Ed448(_) => "ML-DSA-87+Ed448".to_string(),
        #[cfg(feature = "draft-pqc")]
        PublicParams::SlhDsaShake128s(_) => "SLH-DSA-SHAKE-128s".to_string(),
        #[cfg(feature = "draft-pqc")]
        PublicParams::SlhDsaShake128f(_) => "SLH-DSA-SHAKE-128f".to_string(),
        #[cfg(feature = "draft-pqc")]
        PublicParams::SlhDsaShake256s(_) => "SLH-DSA-SHAKE-256s".to_string(),

        _ => format!("{pp:?}"),
    }
}

/// Print human-readable information about a Certificate
///
/// FIXME: This will probably not remain in the public API in this form.
pub fn print_cert(cert: &Certificate, verbose: bool, ordered: bool) {
    let ci = cert_to_info(cert, ordered);
    print_cert_info(&ci, verbose, ordered);
}

/// Check if `sig` is a valid direct/revocation signature over `public`
pub(crate) fn verify_signature(sig: &Signature, public: &PublicKey) -> bool {
    sig.verify_key(public).is_ok()
}

/// Flatten duplicates in a set of signatures:
/// Remove duplicate instances of the same signature, but merge
/// unhashed subpackets into the unified signature.
fn dedup(sigs: &mut Vec<Signature>) {
    /// Returns the pos of a signature that can be removed as a duplicate.
    /// None, if no duplicate is found.
    fn dedup_inner(sigs: &mut [Signature]) -> Option<usize> {
        for pos in 0..sigs.len() {
            for check in pos + 1..sigs.len() {
                if signature_bytes_eq(&sigs[pos], &sigs[check]) {
                    // merge unhashed packets from check into pos
                    let chk = sigs[check].clone();
                    merge_unhashed(&mut sigs[pos], &chk);

                    return Some(check);
                }
            }
        }
        None
    }

    while let Some(pos) = dedup_inner(sigs) {
        sigs.remove(pos);
    }
}

/// Drop/unify duplicate signatures
pub fn canonicalize(spk: &mut SignedPublicKey) {
    // - drop duplicate signatures
    dedup(&mut spk.details.revocation_signatures);
    dedup(&mut spk.details.direct_signatures);
    for sk in &mut spk.public_subkeys {
        dedup(&mut sk.signatures);
    }
    for user in &mut spk.details.users {
        dedup(&mut user.signatures);
    }
    for ua in &mut spk.details.user_attributes {
        dedup(&mut ua.signatures);
    }

    // TODO?
    // - check that non-revocation signatures satisfy our policy?
    // - verify self-signatures?
}