Documentation
//! Generate a test key for `msop update-key`, which has an expired primary key
//!
//! Usage, e.g.:
//!
//! `$ cargo run --example testkey_generate | cargo run --bin msop update-key
//!    | cargo run --example status`

use std::io::stdout;

use minipgp6_armor::{DataType, write::Armorer};
use minipgp6_packet::{
    Serialize,
    body::{
        secret_key::{SecretKey, SecretKeyMaterial},
        signature::{
            SignatureType,
            Subpacket,
            subpacket_data::{
                Features,
                IssuerFingerprint,
                KeyExpirationTime,
                KeyFlags,
                PreferredAeadCiphersuites,
                SignatureCreationTime,
            },
        },
    },
    transferable::{SignedComponent, Tsk},
    types::{
        KeyRole,
        algorithm::PublicKeyAlgorithm,
        time::{Duration, Timestamp},
    },
    unlocked::UnlockedSoftwareKey,
};
use minipgp6_sign::component::{sign_direct_key, sign_key_binding};
use rand::thread_rng;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut rng = thread_rng();

    let create = Timestamp(1722466800); // July 2024

    // -- Primary
    let primary = minipgp6_sign::generate_key(
        &mut rng,
        PublicKeyAlgorithm::Ed25519,
        KeyRole::Primary,
        create,
    )?;

    let hashed_area = vec![
        Subpacket::regular(SignatureCreationTime(create))?,
        Subpacket::regular(KeyExpirationTime(Duration(365 * 24 * 60 * 60)))?,
        Subpacket::regular(IssuerFingerprint(primary.public().fingerprint()))?,
        // CS (FIXME: don't hardcode literal)
        Subpacket::regular(KeyFlags(Box::new([0x03])))?,
        // SEIPD v2 only (FIXME: don't hardcode literal)
        Subpacket::regular(Features(Box::new([0x08])))?,
        // AES128, Ocb
        Subpacket::regular(PreferredAeadCiphersuites(Box::new([0x07, 0x02])))?,
    ];

    let config = minipgp6_sign::new_signature(
        &mut rng,
        SignatureType::DirectKey,
        PublicKeyAlgorithm::Ed25519,
        PublicKeyAlgorithm::Ed25519.hash_algorithm()?,
        hashed_area,
        Vec::new(),
    )?;

    // -- Signer from primary
    let SecretKeyMaterial::Unprotected(plain) = &primary.secret_key_material else {
        unreachable!();
    };

    let signer = UnlockedSoftwareKey::new(plain.clone(), primary.public());

    let dks = sign_direct_key(config, &signer, primary.public())?;

    // -- Subkey
    let subkey = minipgp6_encrypt::generate_key(
        &mut rng,
        PublicKeyAlgorithm::X25519,
        create,
        KeyRole::Subkey,
    )?;

    let hashed_area = vec![
        Subpacket::regular(SignatureCreationTime(create))?,
        Subpacket::regular(KeyExpirationTime(Duration(365 * 24 * 60 * 60)))?,
        Subpacket::regular(IssuerFingerprint(primary.public().fingerprint()))?,
        Subpacket::regular(KeyFlags(Box::new([0x04 | 0x08])))?, // E
    ];

    let primary_pk = primary.public().public_key_algorithm();

    let config = minipgp6_sign::new_signature(
        &mut rng,
        SignatureType::SubkeyBinding,
        primary_pk,
        primary_pk.hash_algorithm()?,
        hashed_area,
        Vec::new(),
    )?;

    let binding = sign_key_binding(config, &signer, primary.public(), subkey.public())?;

    let sk = SignedComponent::<SecretKey> {
        component: subkey,
        revocations: Vec::new(),
        signatures: vec![binding],
    };

    // -- Assemble
    let tsk = Tsk {
        primary: SignedComponent::<SecretKey> {
            component: primary,
            revocations: Vec::new(),
            signatures: vec![dks],
        },
        subkeys: vec![sk],
        user_ids: Vec::new(),
    };

    // -- Print to stdout
    let mut out = Armorer::new(stdout(), DataType::Private)?;
    tsk.to_writer(&mut out)?;
    out.finalize()?;

    Ok(())
}