// SPDX-FileCopyrightText: Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Handling of component keys. A component key is an individual "primary key" or "subkey."
//!
//! NOTE: This module is particularly experimental, the API may change drastically.
//! (This current implementation does a lot of cloning and is limited to read-only operations.)

use std::fmt::{Debug, Formatter};
use std::io::Write;

use chrono::{DateTime, Utc};
use pgp::crypto::hash::HashAlgorithm;
use pgp::crypto::public_key::PublicKeyAlgorithm;
use pgp::crypto::sym::SymmetricKeyAlgorithm;
use pgp::decrypt_session_key;
use pgp::packet::{KeyFlags, PublicKeyEncryptedSessionKey, SubpacketData};
use pgp::types::{
    EskType, Fingerprint, KeyId, KeyVersion, PkeskBytes, PkeskVersion, PublicKeyTrait,
    PublicParams, SignatureBytes,
};
use pgp::{
    packet, Message, Signature, SignedPublicKey, SignedPublicSubKey, SignedSecretKey,
    SignedSecretSubKey,
};
use rand::{thread_rng, Rng};
use rand_core::CryptoRng;

use crate::certificate::primary_user_id_binding_at;
use crate::signature::SigStack;
use crate::{certificate, signature, util, Error};

/// Any kind of component key: public or secret, primary or subkey.
/// This enum serves as a convenience wrapper around all four cases.
///
/// The component key is stored combined with the context of its binding signature(s)
pub enum SignedComponentKey {
    Pub(SignedComponentKeyPub),
    Sec(SignedComponentKeySec),
}

impl Debug for SignedComponentKey {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            SignedComponentKey::Pub(p) => p.fmt(f),
            SignedComponentKey::Sec(s) => s.fmt(f),
        }
    }
}

impl PublicKeyTrait for SignedComponentKey {
    fn version(&self) -> KeyVersion {
        match self {
            SignedComponentKey::Pub(p) => p.version(),
            SignedComponentKey::Sec(s) => s.version(),
        }
    }

    fn fingerprint(&self) -> Fingerprint {
        match self {
            SignedComponentKey::Pub(p) => p.fingerprint(),
            SignedComponentKey::Sec(s) => s.fingerprint(),
        }
    }

    fn key_id(&self) -> KeyId {
        match self {
            SignedComponentKey::Pub(p) => p.key_id(),
            SignedComponentKey::Sec(s) => s.key_id(),
        }
    }

    fn algorithm(&self) -> PublicKeyAlgorithm {
        match self {
            SignedComponentKey::Pub(p) => p.algorithm(),
            SignedComponentKey::Sec(s) => s.algorithm(),
        }
    }

    fn created_at(&self) -> &DateTime<Utc> {
        match self {
            SignedComponentKey::Pub(p) => p.created_at(),
            SignedComponentKey::Sec(s) => s.created_at(),
        }
    }

    fn expiration(&self) -> Option<u16> {
        match self {
            SignedComponentKey::Pub(p) => p.expiration(),
            SignedComponentKey::Sec(s) => s.expiration(),
        }
    }

    fn verify_signature(
        &self,
        hash: HashAlgorithm,
        data: &[u8],
        sig: &SignatureBytes,
    ) -> pgp::errors::Result<()> {
        match self {
            SignedComponentKey::Pub(p) => p.verify_signature(hash, data, sig),
            SignedComponentKey::Sec(s) => s.verify_signature(hash, data, sig),
        }
    }

    fn encrypt<R: CryptoRng + Rng>(
        &self,
        rng: R,
        plain: &[u8],
        typ: EskType,
    ) -> pgp::errors::Result<PkeskBytes> {
        match self {
            SignedComponentKey::Pub(p) => p.encrypt(rng, plain, typ),
            SignedComponentKey::Sec(s) => s.encrypt(rng, plain, typ),
        }
    }

    fn serialize_for_hashing(&self, writer: &mut impl Write) -> pgp::errors::Result<()> {
        match self {
            SignedComponentKey::Pub(p) => p.serialize_for_hashing(writer),
            SignedComponentKey::Sec(s) => s.serialize_for_hashing(writer),
        }
    }

    fn public_params(&self) -> &PublicParams {
        match self {
            SignedComponentKey::Pub(p) => p.public_params(),
            SignedComponentKey::Sec(s) => s.public_params(),
        }
    }
}

enum SignedComponentPrimary {
    Pub(SignedPublicKey),
    Sec(SignedSecretKey),
}

impl SignedComponentPrimary {
    fn direct_key_signature_at(&self, reference: &DateTime<Utc>) -> Option<&Signature> {
        match self {
            SignedComponentPrimary::Pub(spk) => {
                SigStack::from_iter(spk.details.direct_signatures.iter()).active_at(Some(reference))
            }
            SignedComponentPrimary::Sec(ssk) => {
                SigStack::from_iter(ssk.details.direct_signatures.iter()).active_at(Some(reference))
            }
        }
    }

    fn primary_user_id_binding_at<'a>(
        &'a self,
        reference: &'a DateTime<Utc>,
    ) -> Option<&'a Signature> {
        match self {
            SignedComponentPrimary::Pub(p) => {
                certificate::primary_user_id_binding_at(&p.details, reference)
            }
            SignedComponentPrimary::Sec(s) => {
                certificate::primary_user_id_binding_at(&s.details, reference)
            }
        }
    }
}

impl SignedComponentSubkey {
    // FIXME: should probably also keep track of primary user id binding signature
    fn direct_key_signature(&self, reference: Option<&DateTime<Utc>>) -> Option<&Signature> {
        match self {
            SignedComponentSubkey::Pub((_, dks)) => SigStack::from_iter(dks).active_at(reference),
            SignedComponentSubkey::Sec((_, dks)) => dks.as_ref(),
        }
    }

    fn subkey_binding_at(&self, reference: Option<&DateTime<Utc>>) -> Option<&Signature> {
        match self {
            SignedComponentSubkey::Pub((spsk, _)) => {
                SigStack::from_iter(spsk.signatures.iter()).active_at(reference)
            }
            SignedComponentSubkey::Sec((sssk, _)) => {
                SigStack::from_iter(sssk.signatures.iter()).active_at(reference)
            }
        }
    }
}

#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)] // FIXME
enum SignedComponentSubkey {
    Pub((SignedPublicSubKey, Vec<Signature>)),    // key, dks
    Sec((SignedSecretSubKey, Option<Signature>)), // key, dks
}

/// Signed component key, either a primary or a subkey
enum PrimaryOrSubkey {
    Primary(SignedComponentPrimary),
    Subkey(SignedComponentSubkey),
}

impl From<&SignedComponentKey> for PrimaryOrSubkey {
    fn from(value: &SignedComponentKey) -> Self {
        // FIXME: don't clone
        match value {
            SignedComponentKey::Pub(SignedComponentKeyPub::Primary((k, _))) => {
                PrimaryOrSubkey::Primary(SignedComponentPrimary::Pub(k.clone()))
            }
            SignedComponentKey::Pub(SignedComponentKeyPub::Subkey(pair)) => {
                PrimaryOrSubkey::Subkey(SignedComponentSubkey::Pub(pair.clone()))
            }
            SignedComponentKey::Sec(SignedComponentKeySec::Primary(k)) => {
                PrimaryOrSubkey::Primary(SignedComponentPrimary::Sec(k.clone()))
            }
            SignedComponentKey::Sec(SignedComponentKeySec::Subkey(pair)) => {
                PrimaryOrSubkey::Subkey(SignedComponentSubkey::Sec(pair.clone()))
            }
        }
    }
}

impl SignedComponentKey {
    #[allow(dead_code)]
    pub(crate) fn sigs(&self) -> SigStack {
        match self {
            SignedComponentKey::Pub(SignedComponentKeyPub::Primary((_spk, sigs))) => {
                SigStack::from_iter(sigs.iter())
            }
            SignedComponentKey::Pub(SignedComponentKeyPub::Subkey((spsk, _))) => {
                SigStack::from_iter(spsk.signatures.iter())
            }
            SignedComponentKey::Sec(SignedComponentKeySec::Primary(_ssk)) => {
                // FIXME: we don't want to support this kind of call at all (we're not handling DKS here)
                // TSKs should probably be represented as Certificate + "secret key packets"

                unimplemented!("unsupported case [primary secret]")
            }
            SignedComponentKey::Sec(SignedComponentKeySec::Subkey((sssk, _))) => {
                SigStack::from_iter(sssk.signatures.iter())
            }
        }
    }

    /// Find KeyFlags subpacket in hashed area, return first occurrence (if any)
    fn key_flag_subpacket(sig: &Signature) -> Option<KeyFlags> {
        sig.config
            .hashed_subpackets
            .iter()
            .find_map(|p| match &p.data {
                SubpacketData::KeyFlags(d) => Some(d[..].into()),
                _ => None,
            })
    }

    /// Get key flags.
    ///
    /// This convenience fn takes into consideration the different places that key flags can be stored in:
    /// - For primary component keys: dks or primary user id binding
    /// - For component subkeys: subkey binding or dks
    pub(crate) fn key_flags_at(&self, reference: &DateTime<Utc>) -> Option<KeyFlags> {
        match PrimaryOrSubkey::from(self) {
            PrimaryOrSubkey::Primary(scp) => {
                // We favor the dsk for the primary
                if let Some(dks) = &scp.direct_key_signature_at(reference) {
                    if let Some(kf) = Self::key_flag_subpacket(dks) {
                        return Some(kf);
                    }
                } else if let Some(prim_bind) = scp.primary_user_id_binding_at(reference) {
                    if let Some(kf) = Self::key_flag_subpacket(prim_bind) {
                        return Some(kf);
                    }
                }

                Some(KeyFlags::default())
            }

            PrimaryOrSubkey::Subkey(scsk) => {
                let Some(binding) = scsk.subkey_binding_at(Some(reference)) else {
                    eprintln!("no subkey binding");
                    return None;
                };

                // We favor the subkey binding for subkeys
                if let Some(kf) = Self::key_flag_subpacket(binding) {
                    return Some(kf);
                } else if let Some(dks) = scsk.direct_key_signature(Some(reference)) {
                    // FIXME: should we also consider the primary user id binding here?
                    // (or should we just find the primary key's flags and use those?!)

                    if let Some(kf) = Self::key_flag_subpacket(dks) {
                        return Some(kf);
                    }
                }

                Some(KeyFlags::default())
            }
        }
    }

    /// Check if *this* component key is revoked at `reference`.
    /// (Note that for subkeys, the revocation status of the primary is not considered!)
    pub fn revoked(&self, reference: &DateTime<Utc>) -> bool {
        let sigs = match self {
            SignedComponentKey::Pub(SignedComponentKeyPub::Primary((k, _))) => {
                // FIXME: check for primary user id based revocations
                k.details.revocation_signatures.as_slice()
            }
            SignedComponentKey::Pub(SignedComponentKeyPub::Subkey((spsk, _))) => {
                spsk.signatures.as_slice()
            }
            SignedComponentKey::Sec(SignedComponentKeySec::Primary(k)) => {
                // FIXME: check for primary user id based revocations
                k.details.revocation_signatures.as_slice()
            }
            SignedComponentKey::Sec(SignedComponentKeySec::Subkey((sssk, _))) => {
                sssk.signatures.as_slice()
            }
        };

        SigStack::from_iter(sigs.iter()).revoked_at(reference)
    }

    pub(crate) fn is_encryption_capable(&self, reference: &DateTime<Utc>) -> bool {
        let Some(kf) = self.key_flags_at(reference) else {
            return false;
        };

        kf.encrypt_comms() || kf.encrypt_storage()
    }

    pub(crate) fn is_signing_capable(&self, reference: &DateTime<Utc>) -> bool {
        log::debug!(
            "is_signing_capable {:?} at {:?}?",
            self.fingerprint(),
            reference
        );

        let Some(kf) = self.key_flags_at(reference) else {
            return false;
        };

        kf.sign()
    }

    pub(crate) fn is_authentication_capable(&self, reference: &DateTime<Utc>) -> bool {
        let Some(kf) = self.key_flags_at(reference) else {
            return false;
        };

        kf.authentication()
    }
}

/// A public component key (either a public primary key, or a public subkey).
///
/// NOTE: This component key representation consists of only the raw public key packet data,
/// without the context of binding signatures
#[derive(Debug, Clone)]
pub enum ComponentKeyPub {
    Primary(packet::PublicKey),
    Subkey(packet::PublicSubkey),
}

#[derive(Debug, Clone)]
pub enum SignedComponentKeyPub {
    // We store a copy of all primary-related signatures, to have an owned copy that can be borrowed as a SigStack.
    // FIXME: do this better?
    Primary((SignedPublicKey, Vec<Signature>)),

    // key, dks stack
    Subkey((SignedPublicSubKey, Vec<Signature>)),
}

impl PublicKeyTrait for SignedComponentKeyPub {
    fn version(&self) -> KeyVersion {
        match self {
            SignedComponentKeyPub::Primary((p, _)) => p.version(),
            SignedComponentKeyPub::Subkey((s, _)) => s.version(),
        }
    }

    fn fingerprint(&self) -> Fingerprint {
        match self {
            SignedComponentKeyPub::Primary((p, _)) => p.fingerprint(),
            SignedComponentKeyPub::Subkey((s, _)) => s.fingerprint(),
        }
    }

    fn key_id(&self) -> KeyId {
        match self {
            SignedComponentKeyPub::Primary((p, _)) => p.key_id(),
            SignedComponentKeyPub::Subkey((s, _)) => s.key_id(),
        }
    }

    fn algorithm(&self) -> PublicKeyAlgorithm {
        match self {
            SignedComponentKeyPub::Primary((p, _)) => p.algorithm(),
            SignedComponentKeyPub::Subkey((s, _)) => s.algorithm(),
        }
    }

    fn created_at(&self) -> &DateTime<Utc> {
        match self {
            SignedComponentKeyPub::Primary((p, _)) => p.created_at(),
            SignedComponentKeyPub::Subkey((s, _)) => s.created_at(),
        }
    }

    fn expiration(&self) -> Option<u16> {
        match self {
            SignedComponentKeyPub::Primary((p, _)) => p.expiration(),
            SignedComponentKeyPub::Subkey((s, _)) => s.expiration(),
        }
    }

    fn verify_signature(
        &self,
        hash: HashAlgorithm,
        data: &[u8],
        sig: &SignatureBytes,
    ) -> pgp::errors::Result<()> {
        match self {
            SignedComponentKeyPub::Primary((p, _)) => p.verify_signature(hash, data, sig),
            SignedComponentKeyPub::Subkey((s, _)) => s.verify_signature(hash, data, sig),
        }
    }

    fn encrypt<R: CryptoRng + Rng>(
        &self,
        rng: R,
        plain: &[u8],
        typ: EskType,
    ) -> pgp::errors::Result<PkeskBytes> {
        match self {
            SignedComponentKeyPub::Primary((p, _)) => p.encrypt(rng, plain, typ),
            SignedComponentKeyPub::Subkey((s, _)) => s.encrypt(rng, plain, typ),
        }
    }

    fn serialize_for_hashing(&self, writer: &mut impl Write) -> pgp::errors::Result<()> {
        match self {
            SignedComponentKeyPub::Primary((p, _)) => p.serialize_for_hashing(writer),
            SignedComponentKeyPub::Subkey((s, _)) => s.serialize_for_hashing(writer),
        }
    }

    fn public_params(&self) -> &PublicParams {
        match self {
            SignedComponentKeyPub::Primary((p, _)) => p.public_params(),
            SignedComponentKeyPub::Subkey((s, _)) => s.public_params(),
        }
    }
}

impl From<SignedComponentKeyPub> for ComponentKeyPub {
    fn from(value: SignedComponentKeyPub) -> ComponentKeyPub {
        match value {
            SignedComponentKeyPub::Subkey(sk) => ComponentKeyPub::Subkey(sk.0.key),
            SignedComponentKeyPub::Primary((pk, _)) => {
                ComponentKeyPub::Primary(pk.primary_key.clone())
            }
        }
    }
}

#[allow(dead_code)]
pub enum ComponentKeySec {
    Primary(packet::SecretKey),
    Subkey(packet::SecretSubkey),
}

impl ComponentKeySec {
    pub fn sign_msg<F>(
        &self,
        msg: Message,
        key_pw: F,
        hash_algo: HashAlgorithm,
    ) -> Result<Message, Error>
    where
        F: FnOnce() -> String,
    {
        match self {
            ComponentKeySec::Primary(sk) => {
                Ok(msg.sign(&mut thread_rng(), &sk, key_pw, hash_algo)?)
            }
            ComponentKeySec::Subkey(ssk) => {
                Ok(msg.sign(&mut thread_rng(), &ssk, key_pw, hash_algo)?)
            }
        }
    }

    pub fn key_id(&self) -> KeyId {
        match self {
            ComponentKeySec::Primary(pri) => pri.key_id(),
            ComponentKeySec::Subkey(sub) => sub.key_id(),
        }
    }

    pub fn fingerprint(&self) -> Fingerprint {
        match self {
            ComponentKeySec::Primary(pri) => pri.fingerprint(),
            ComponentKeySec::Subkey(sub) => sub.fingerprint(),
        }
    }

    pub fn created_at(&self) -> &DateTime<Utc> {
        match self {
            ComponentKeySec::Primary(pri) => pri.created_at(),
            ComponentKeySec::Subkey(sub) => sub.created_at(),
        }
    }

    pub fn algorithm(&self) -> PublicKeyAlgorithm {
        match self {
            ComponentKeySec::Primary(pri) => pri.algorithm(),
            ComponentKeySec::Subkey(sub) => sub.algorithm(),
        }
    }

    pub fn public_params(&self) -> &PublicParams {
        match self {
            ComponentKeySec::Primary(pri) => pri.public_params(),
            ComponentKeySec::Subkey(sub) => sub.public_params(),
        }
    }
}

/// Either a secret primary key, or a secret subkey.
/// This enum serves as a convenience wrapper around both cases.
///
/// The component key is stored combined with the context of its binding signature(s)
pub enum SignedComponentKeySec {
    Primary(SignedSecretKey),
    Subkey((SignedSecretSubKey, Option<Signature>)), // key, dks
}

impl Debug for SignedComponentKeySec {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            SignedComponentKeySec::Primary(p) => p.fmt(f),
            SignedComponentKeySec::Subkey(s) => s.fmt(f),
        }
    }
}

impl PublicKeyTrait for SignedComponentKeySec {
    fn version(&self) -> KeyVersion {
        match self {
            SignedComponentKeySec::Primary(p) => p.version(),
            SignedComponentKeySec::Subkey((s, _)) => s.version(),
        }
    }

    fn fingerprint(&self) -> Fingerprint {
        match self {
            SignedComponentKeySec::Primary(p) => p.fingerprint(),
            SignedComponentKeySec::Subkey((s, _)) => s.fingerprint(),
        }
    }

    fn key_id(&self) -> KeyId {
        match self {
            SignedComponentKeySec::Primary(p) => p.key_id(),
            SignedComponentKeySec::Subkey((s, _)) => s.key_id(),
        }
    }

    fn algorithm(&self) -> PublicKeyAlgorithm {
        match self {
            SignedComponentKeySec::Primary(p) => p.algorithm(),
            SignedComponentKeySec::Subkey((s, _)) => s.algorithm(),
        }
    }

    fn created_at(&self) -> &DateTime<Utc> {
        match self {
            SignedComponentKeySec::Primary(p) => p.created_at(),
            SignedComponentKeySec::Subkey((s, _)) => s.created_at(),
        }
    }

    fn expiration(&self) -> Option<u16> {
        match self {
            SignedComponentKeySec::Primary(p) => p.expiration(),
            SignedComponentKeySec::Subkey((s, _)) => s.expiration(),
        }
    }

    fn verify_signature(
        &self,
        hash: HashAlgorithm,
        data: &[u8],
        sig: &SignatureBytes,
    ) -> pgp::errors::Result<()> {
        match self {
            SignedComponentKeySec::Primary(p) => p.verify_signature(hash, data, sig),
            SignedComponentKeySec::Subkey((s, _)) => s.verify_signature(hash, data, sig),
        }
    }

    fn encrypt<R: CryptoRng + Rng>(
        &self,
        rng: R,
        plain: &[u8],
        typ: EskType,
    ) -> pgp::errors::Result<PkeskBytes> {
        match self {
            SignedComponentKeySec::Primary(p) => p.encrypt(rng, plain, typ),
            SignedComponentKeySec::Subkey((s, _)) => s.encrypt(rng, plain, typ),
        }
    }

    fn serialize_for_hashing(&self, writer: &mut impl Write) -> pgp::errors::Result<()> {
        match self {
            SignedComponentKeySec::Primary(p) => p.serialize_for_hashing(writer),
            SignedComponentKeySec::Subkey((s, _)) => s.serialize_for_hashing(writer),
        }
    }

    fn public_params(&self) -> &PublicParams {
        match self {
            SignedComponentKeySec::Primary(p) => p.public_params(),
            SignedComponentKeySec::Subkey((s, _)) => s.public_params(),
        }
    }
}

pub(crate) enum KeyFlagMatch {
    Sign,
    Enc,
    Auth,
}

impl SignedComponentKeySec {
    pub fn key_id(&self) -> KeyId {
        match self {
            SignedComponentKeySec::Primary(ssk) => ssk.key_id(),
            SignedComponentKeySec::Subkey((sssk, _)) => sssk.key_id(),
        }
    }

    fn match_flag(sig: &Signature, check: KeyFlagMatch) -> bool {
        let flags = sig.key_flags();
        match check {
            KeyFlagMatch::Sign => flags.sign(),
            KeyFlagMatch::Auth => flags.authentication(),
            KeyFlagMatch::Enc => flags.encrypt_comms() | flags.encrypt_storage(),
        }
    }

    pub(crate) fn has_key_flag(&self, check: KeyFlagMatch) -> bool {
        match self {
            SignedComponentKeySec::Primary(ssk) => {
                let x = &ssk.details;

                // FIXME: also consider direct signatures
                if let Some(binding) = primary_user_id_binding_at(x, &Utc::now()) {
                    Self::match_flag(binding, check)
                } else {
                    false
                }
            }
            SignedComponentKeySec::Subkey((sssk, _)) => {
                let stack = SigStack::from_iter(sssk.signatures.iter());
                if let Some(sig) = stack.active_at(Some(&Utc::now())) {
                    Self::match_flag(sig, check)
                } else {
                    false
                }
            }
        }
    }

    pub fn decrypt_session_key<F>(
        &self,
        pkesk: &PublicKeyEncryptedSessionKey,
        key_pw: F,
    ) -> Result<pgp::PlainSessionKey, Error>
    where
        F: FnOnce() -> String,
    {
        let typ = match pkesk.version() {
            PkeskVersion::V3 => pgp::types::EskType::V3_4,
            PkeskVersion::V6 => pgp::types::EskType::V6,
            v => {
                return Err(Error::Rpgp(pgp::errors::Error::Message(format!(
                    "Unsupported PKESK version {:?}",
                    v
                ))))
            }
        };

        match self {
            SignedComponentKeySec::Primary(ssk) => {
                Ok(decrypt_session_key(&ssk, key_pw, pkesk.values()?, typ)?)
            }
            SignedComponentKeySec::Subkey((sssk, _)) => {
                Ok(decrypt_session_key(&sssk, key_pw, pkesk.values()?, typ)?)
            }
        }
    }
}

impl From<&SignedComponentKeySec> for ComponentKeySec {
    fn from(value: &SignedComponentKeySec) -> Self {
        match value {
            SignedComponentKeySec::Primary(ssk) => {
                ComponentKeySec::Primary(ssk.primary_key.clone())
            }
            SignedComponentKeySec::Subkey((sssk, _)) => ComponentKeySec::Subkey(sssk.key.clone()),
        }
    }
}

impl SignedComponentKeyPub {
    pub fn algorithm_name(&self) -> String {
        match &self {
            SignedComponentKeyPub::Primary((spk, _)) => {
                util::algo_name(spk.primary_key.public_params())
            }
            SignedComponentKeyPub::Subkey((spsk, _)) => util::algo_name(spsk.key.public_params()),
        }
    }

    pub(crate) fn key_creation_time(&self) -> &DateTime<Utc> {
        match &self {
            SignedComponentKeyPub::Primary((spk, _)) => spk.primary_key.created_at(),
            SignedComponentKeyPub::Subkey((spsk, _)) => spsk.key.created_at(),
        }
    }

    /// This fn is intended for use on signing-capable component keys.
    /// It returns "false" is a subkey is not validly "backward-bound" to the primary.
    pub(crate) fn has_valid_backsig_at(&self, reference: &DateTime<Utc>) -> bool {
        if let SignedComponentKeyPub::Subkey(sssk) = self {
            // If a subkey binding signature has no embedded back signature,
            // the subkey is not reasonably bound to the certificate for signing

            let key_creation: &DateTime<Utc> = self.key_creation_time();

            let binding = SigStack::from_iter(sssk.0.signatures.iter()).active_at(Some(reference));

            if let Some(binding) = binding {
                binding.embedded_signature().iter().any(|backsig| {
                    signature::is_signature_valid_at(backsig, key_creation, reference)
                })
            } else {
                // we don't even have a binding at the reference time
                false
            }
        } else {
            // A primary doesn't need a backsig to be "validly bound"

            true
        }
    }

    /// This fn presupposes that the primary is valid at the reference time
    pub(crate) fn is_component_subkey_valid_at(&self, reference: &DateTime<Utc>) -> bool {
        match &self {
            SignedComponentKeyPub::Subkey(sk) => Self::is_subkey_valid_at(&sk.0, reference),
            SignedComponentKeyPub::Primary(_) => true,
        }
    }

    /// takes into account the semantics of hard and soft revocation
    fn subkey_revoked_at(sk: &SignedPublicSubKey, reference: &DateTime<Utc>) -> bool {
        SigStack::from_iter(sk.signatures.iter()).revoked_at(reference)
    }

    fn is_subkey_valid_at(spsk: &SignedPublicSubKey, reference: &DateTime<Utc>) -> bool {
        // subkey is revoked
        if Self::subkey_revoked_at(spsk, reference) {
            return false;
        }
        let key_creation = spsk.key.created_at();

        let stack = SigStack::from_iter(spsk.signatures.iter());
        if !stack.has_valid_binding_at(reference, key_creation) {
            return false;
        }

        true
    }

    /// Are we willing to encrypt to the algorithm/ECDH parameters of self?
    pub(crate) fn valid_encryption_algo(&self) -> bool {
        // FIXME: where should this go?

        let pp = self.public_params();

        crate::policy::accept_for_encryption(pp)
    }

    pub(crate) fn public_params(&self) -> &PublicParams {
        match &self {
            SignedComponentKeyPub::Primary((pk, _)) => pk.primary_key.public_params(),
            SignedComponentKeyPub::Subkey((sk, _)) => sk.key.public_params(),
        }
    }

    pub fn revoked(&self, reference: &DateTime<Utc>) -> bool {
        let sck = SignedComponentKey::Pub(self.clone());

        sck.revoked(reference)
    }

    /// Checks if this component key by itself is valid.
    ///
    /// For a subkey this fn does *not* check if the certificate itself is valid!
    ///
    /// CAUTION: this currently is *not* implemented for primary component keys and will panic!
    pub fn is_valid_at(&self, reference: &DateTime<Utc>) -> bool {
        match self {
            SignedComponentKeyPub::Primary(_) => unimplemented!("FIXME"),
            SignedComponentKeyPub::Subkey((spsk, _dks)) => {
                Self::is_subkey_valid_at(spsk, reference)
            }
        }
    }
}

impl ComponentKeyPub {
    pub fn pkesk_from_session_key_v3<R: CryptoRng + Rng>(
        &self,
        rng: &mut R,
        session_key: &[u8],
        alg: SymmetricKeyAlgorithm,
    ) -> Result<PublicKeyEncryptedSessionKey, Error> {
        match &self {
            Self::Primary(pk) => Ok(PublicKeyEncryptedSessionKey::from_session_key_v3(
                rng,
                session_key,
                alg,
                pk,
            )?),
            Self::Subkey(psk) => Ok(PublicKeyEncryptedSessionKey::from_session_key_v3(
                rng,
                session_key,
                alg,
                psk,
            )?),
        }
    }

    pub fn pkesk_from_session_key_v6<R: CryptoRng + Rng>(
        &self,
        rng: &mut R,
        session_key: &[u8],
    ) -> Result<PublicKeyEncryptedSessionKey, Error> {
        match &self {
            Self::Primary(pk) => Ok(PublicKeyEncryptedSessionKey::from_session_key_v6(
                rng,
                session_key,
                pk,
            )?),
            Self::Subkey(psk) => Ok(PublicKeyEncryptedSessionKey::from_session_key_v6(
                rng,
                session_key,
                psk,
            )?),
        }
    }

    pub fn verify(&self, s: &Signature, payload: &[u8]) -> Result<(), Error> {
        // policy check on s
        if !signature::signature_acceptable(s) {
            return Err(Error::Message(
                "Signature doesn't satisfy our policy".to_string(),
            ));
        }

        match self {
            ComponentKeyPub::Primary(pri) => Ok(s.verify(pri, payload)?),
            ComponentKeyPub::Subkey(sub) => Ok(s.verify(sub, payload)?),
        }
    }

    pub fn created_at(&self) -> &DateTime<Utc> {
        match self {
            ComponentKeyPub::Primary(pri) => pri.created_at(),
            ComponentKeyPub::Subkey(sub) => sub.created_at(),
        }
    }

    pub fn algorithm(&self) -> PublicKeyAlgorithm {
        match self {
            ComponentKeyPub::Primary(pri) => pri.algorithm(),
            ComponentKeyPub::Subkey(sub) => sub.algorithm(),
        }
    }

    pub fn key_id(&self) -> KeyId {
        match self {
            ComponentKeyPub::Primary(pri) => pri.key_id(),
            ComponentKeyPub::Subkey(sub) => sub.key_id(),
        }
    }

    pub fn fingerprint(&self) -> Fingerprint {
        match self {
            ComponentKeyPub::Primary(pri) => pri.fingerprint(),
            ComponentKeyPub::Subkey(sub) => sub.fingerprint(),
        }
    }

    pub fn public_params(&self) -> &PublicParams {
        match self {
            ComponentKeyPub::Primary(pri) => pri.public_params(),
            ComponentKeyPub::Subkey(sub) => sub.public_params(),
        }
    }
}
