//! A library for using cryptoki (PKCS#11) tokens from Sequoia.
//!
//! This library supports the use of cryptographic mechanisms via a
//! [cryptoki (PKCS#11) interface] with Sequoia PGP.  The cryptoki
//! interface is usually implemented by a library that interfaces with
//! a hardware token.
//!
//!   [cryptoki (PKCS#11) interface]: https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.2/pkcs11-spec-v3.2.html
//!
//! This library is specifically not a generic interface to cryptoki
//! tokens.  Instead, it assumes that the user wants to use the tokens
//! in an OpenPGP context.  For instance, when generating or importing
//! a key, the key's key ID is constructed in such a way that the
//! OpenPGP parameters that cannot be inferred from the information
//! stored on the token are encoded in the key ID.  This allows
//! reconstructing the public key packet and consequently the key's
//! OpenPGP fingerprint using just data stored on the token.
//!
//! It is not currently possible to use a key whose key ID does not
//! include the OpenPGP parameters.  This means that you can't use a
//! generic tool to generate the key, and then use that key with this
//! library.  This is usually a reasonable restriction, as key
//! material should not be shared between different protocols.
//!
//! # Examples
//!
//! ## Iterate Over Keys
//!
//! The following code creates a default context and iterates over the
//! available keys.  The configuration file is read from the user's
//! Sequoia home directory and respects the `SEQUOIA_HOME` environment
//! variable.  The configuration indicates what cryptoki modules to
//! load.
//!
//! ```
//! use sequoia_cryptoki::Context;
//! # use sequoia_cryptoki::ContextBuilder;
//!
//! # fn main() -> sequoia_openpgp::Result<()> {
//! # if false {
//! let cryptoki = Context::new()?;
//! # }
//! # let cryptoki = ContextBuilder::new().disable_config_file().build()?;
//! for key in cryptoki.keys()? {
//!     eprintln!("Key: {}",
//!               key.key_id()
//!                   .map(|keyid| String::from_utf8_lossy(keyid))
//!                   .unwrap_or_else(|| "missing key ID".into()));
//!     if let Ok(key) = key.to_openpgp_key() {
//!         eprintln!("  Fingerprint: {}", key.fingerprint());
//!     } else {
//!         eprintln!("  Not for OpenPGP");
//!     }
//! }
//! # Ok(()) }
//! ```
//!
//! You can disable the use of the configuration file by using
//! [`ContextBuilder::disable_config_file`] and then using
//! [`ContextBuilder::add_module`] to specify what modules to load.
//!
//! ## Sign a Message
//!
//! The sample code signs a message using a specified key.  We get a
//! key descriptor (a [`Key`]), then use it to instantiate a
//! [`Signer`].  We can do a similar thing to decrypt a message, but
//! in that case we instantiate a [`Decryptor`].
//!
//! ```
//! use sequoia_openpgp as openpgp;
//! use openpgp::crypto::Password;
//! use openpgp::packet::signature::SignatureBuilder;
//! use openpgp::types::SignatureType;
//! # use openpgp::Fingerprint;
//!
//! use sequoia_cryptoki::Context;
//! use sequoia_cryptoki::Prompt;
//! use sequoia_cryptoki::Signer;
//! # use sequoia_cryptoki::ContextBuilder;
//!
//! # fn main() -> sequoia_openpgp::Result<()> {
//! # let fpr: Fingerprint = "EC710462524581C4C3A5224A573784508844731C".parse().expect("valid fingerprint");
//! #
//! # if false {
//! let cryptoki = Context::new()?;
//! # }
//! # let cryptoki = ContextBuilder::new().disable_config_file().build()?;
//!
//! // Get the first key with the specified fingerprint.  Note:
//! // there may be multiple keys with the same fingerprint, if the key
//! // was copied.
//! let mut key = None;
//! for k in cryptoki.keys()? {
//!     if k.fingerprint().as_ref() == Some(&fpr) {
//!         key = Some(k);
//!         break;
//!     }
//! }
//!
//! let Some(key) = key else {
//!     eprintln!("No token has a copy of {}.", fpr);
//! #   return Ok(());
//!     return Err(anyhow::anyhow!("Key not found"));
//! };
//!
//! // Now, sign a message.
//!
//! // How to prompt the user for a password.
//! struct Prompter;
//! impl Prompt for Prompter {
//!     fn prompt_password(&self, _prompt: &str) -> anyhow::Result<Password> {
//!         Err(anyhow::anyhow!("Password prompting disabled").into())
//!     }
//! }
//!
//! // Turn a `Key` into a `Signer`.
//! let mut signer = Signer::new(Prompter, &key, None)?;
//!
//! let msg = b"Hello, world!";
//! let mut sig = SignatureBuilder::new(SignatureType::Binary)
//!     .sign_message(&mut signer, msg)?;
//! # Ok(()) }
//! ```
//!
//! ## Find a Key
//!
//! This shows how to use a [`KeySelector`] to select a key by its key
//! ID:
//!
//! ```
//! use sequoia_cryptoki::Context;
//! use sequoia_cryptoki::KeySelector;
//! use sequoia_cryptoki::SelectorScope;
//!
//! # fn main() -> sequoia_openpgp::Result<()> {
//! let cryptoki = Context::new()?;
//! let mut selector = KeySelector::unconstrained();
//! selector.key_id = Some("pgp:v4:eddsa:20251118T090408Z:scrap-heat-mouse".into());
//! match cryptoki.select_one_key(&selector, SelectorScope::PublicKeys) {
//!     Ok(key) => {
//!         // ...
//!     }
//!     Err(err) => eprintln!("Key not found"),
//! }
//! # Ok(()) }
//! ```
//!
//! It is also possible to search by unique ID, label and URL.  If
//! multiple constraints are specified, then only keys that match all
//! of the constraints are returned.
//!
//! We can do something similar to search for tokens using
//! [`Context::select_one_token`] and [`TokenSelector`].
//!
//! ## Import a Key
//!
//! This example shows how to import a key.  We need to first get the
//! token, which we find using [`Context::select_one_token`] (see the
//! previous example).  We then open a read-write user session.  A
//! session is similar to a transaction: it guarantees that the
//! operations are atomic.  A read-write session allows modifying the
//! token, which is what we do when we generate or import a key.
//! Finally, we import the key.
//!
//! ```
//! use anyhow::Context as _;
//! #
//! # use sequoia_openpgp as openpgp;
//! # use openpgp::cert::prelude::*;
//!
//! use sequoia_cryptoki::Context;
//! use sequoia_cryptoki::TokenSelector;
//! use sequoia_cryptoki::KeyCapability;
//!
//! # fn main() -> sequoia_openpgp::Result<()> {
//! # let (cert, _) =
//! #     CertBuilder::general_purpose(Some("alice@example.org"))
//! #         .generate()?;
//! // Import the primary key.
//! let key = cert.primary_key().key()
//!     .parts_as_secret().context("No secret key material")?
//!     .role_as_unspecified();
//!
//! let cryptoki = Context::new()?;
//!
//! // Get the token that we want to import the key to.
//! let mut selector = TokenSelector::unconstrained();
//! selector.token_label = Some("some-initialized-token".into());
//! let token = match cryptoki.select_one_token(&selector) {
//!     Ok(token) => token,
//!     Err(err) => {
//!         eprintln!("Token not found");
//!         # return Ok(());
//!         return Err(err);
//!     }
//! };
//!
//! // Open a read/write user session, which is needed to generate or
//! // import a key.
//! let session = token.open_rw_user_session("1234")?;
//!
//! // Import the key.
//! cryptoki.import(&session,
//!                 token,
//!                 None, // Use a random label.
//!                 key,
//!                 KeyCapability::Signing)?;
//! # Ok(()) }
//! ```
//!
//! ## Generate a Key
//!
//! This code shows how to generate a key on a token.  Note: the key
//! cannot normally be exported and thus it is not generally possible
//! to make a backup.  As such, it is often better to generate a key
//! in software, save a copy, and import it.
//!
//! The return value is a bare OpenPGP public key packet.  You still
//! need to turn it into a certificate by creating a direct signature
//! (using e.g., [`SignatureBuilder::sign_direct_key`]) and adding a
//! user ID, or you need to attach it to an existing certificate with
//! a binding signature (using, e.g., [`CertBuilder::add_subkey`]).
//!
//!   [`SignatureBuilder::sign_direct_key`]: https://docs.rs/sequoia-openpgp/latest/sequoia_openpgp/packet/signature/struct.SignatureBuilder.html#method.sign_direct_key
//!   [`CertBuilder::add_subkey`]: https://docs.rs/sequoia-openpgp/latest/sequoia_openpgp/cert/struct.CertBuilder.html#method.add_subkey
//!
//! ```
//! use sequoia_openpgp as openpgp;
//! use openpgp::Packet;
//! use openpgp::serialize::Serialize;
//!
//! use sequoia_cryptoki::Context;
//! use sequoia_cryptoki::TokenSelector;
//!
//! # fn main() -> sequoia_openpgp::Result<()> {
//! let cryptoki = Context::new()?;
//!
//! // Get the token that we want to import the key to.
//! let mut selector = TokenSelector::unconstrained();
//! selector.token_label = Some("some-initialized-token".into());
//! let token = match cryptoki.select_one_token(&selector) {
//!     Ok(token) => token,
//!     Err(err) => {
//!         eprintln!("Token not found");
//!         # return Ok(());
//!         return Err(err);
//!     }
//! };
//!
//! // Open a read/write user session, which is needed to generate or
//! // import a key.
//! let session = token.open_rw_user_session("1234")?;
//!
//! // Import the key.
//! let key = cryptoki.generate_eddsa(
//!     &session,
//!     token,
//!     None, // Use the current time.
//!     None, // Use a random label.
//!     )?;
//! let openpgp_key = Packet::from(key.to_openpgp_key()?.role_into_primary());
//!
//! # let mut output = Vec::new();
//! openpgp_key.serialize(&mut output)?;
//! # Ok(()) }
//! ```
//!
//! # Configuration File
//!
//! By default, [`Context`] and [`ContextBuilder`] read a
//! configuration from the Sequoia home directory.  If not using an
//! alternate location, this is
//! `.config/sequoia/keystore/cryptoki/config.toml`.  When using an
//! alternate location, this is
//! `$SEQUOIA_HOME/config/keystore/cryptoki/config.toml`.
//!
//! This is a `toml` file, which looks like this:
//!
//! ```toml
//! [[modules]]
//! path = "/usr/local/lib/libkryoptic_pkcs11.so"
//!
//! [[modules]]
//! path = "/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so"
//! ```
//!
//! That is, there is an array of tables named `modules`, and each
//! table contains a single key name `path`, which takes a string and
//! is the path to a cryptoki module to load.
#![allow(non_camel_case_types)]

use std::sync::OnceLock;

use sequoia_openpgp as openpgp;
use openpgp::Result;

// Re-export any third-party crates that we use in our API.
pub use cryptoki;

#[macro_use] mod macros;
#[macro_use] mod log;

mod algo;
pub use algo::PublicKeyAlgorithm;
mod builder;
pub use builder::ContextBuilder;
mod capabilities;
mod check_password;
mod config;
pub use config::Config;
pub use config::ConfigFile;
pub use config::Error as ConfigError;
mod decrypt;
pub use decrypt::Decryptor;
pub mod dicewords;
mod export;
mod keyid;
mod key_info;
mod objects;
pub use objects::*;
mod oids;
mod password;
pub use password::Prompt;
mod selector;
pub use selector::*;
mod sign;
pub use sign::Signer;
mod import;
pub use import::KeyCapability;
// This comes near the end so that rustdoc puts all of the generate
// key methods at the end of `Context`'s documentation.
mod generate_key;
pub mod toml_edit_tree;
/// Re-export.
pub use toml_edit;

/// Configuration for the PKCS#11 interface.
pub struct Context {
    /// The configuration.
    config: Config,

    /// The loaded PKCS#11 modules.
    backends: OnceLock<Vec<Backend>>,
}

impl Context {
    /// Creates a new context.
    ///
    /// This reads the default configuration file.  To disable this or
    /// to read an alternate configuration file use [`ContextBuilder`]
    /// to create the context.
    pub fn new() -> Result<Self> {
        ContextBuilder::new().build()
    }

    /// Returns the configuration.
    pub fn config(&self) -> &Config {
        &self.config
    }
}
