diff --git a/tools/src/bin/opgpcard/output/attest.rs b/tools/src/bin/opgpcard/output/attest.rs new file mode 100644 index 0000000..1849d78 --- /dev/null +++ b/tools/src/bin/opgpcard/output/attest.rs @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2022 Lars Wirzenius +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use serde::Serialize; + +use crate::output::OpgpCardError; +use crate::{OutputBuilder, OutputFormat, OutputVariant, OutputVersion}; + +#[derive(Debug, Default, Serialize)] +pub struct AttestationCert { + ident: String, + attestation_cert: String, +} + +impl AttestationCert { + pub fn ident(&mut self, ident: String) { + self.ident = ident; + } + + pub fn attestation_cert(&mut self, cert: String) { + self.attestation_cert = cert; + } + + fn text(&self) -> Result { + Ok(format!( + "OpenPGP card {}\n\n{}\n", + self.ident, self.attestation_cert, + )) + } + + fn v1(&self) -> Result { + Ok(AttestationCertV0 { + schema_version: AttestationCertV0::VERSION, + ident: self.ident.clone(), + attestation_cert: self.attestation_cert.clone(), + }) + } +} + +impl OutputBuilder for AttestationCert { + type Err = OpgpCardError; + + fn print(&self, format: OutputFormat, version: OutputVersion) -> Result { + match format { + OutputFormat::Json => { + let result = if AttestationCertV0::VERSION.is_acceptable_for(&version) { + self.v1()?.json() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeJson) + } + OutputFormat::Yaml => { + let result = if AttestationCertV0::VERSION.is_acceptable_for(&version) { + self.v1()?.yaml() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeYaml) + } + OutputFormat::Text => Ok(self.text()?), + } + } +} + +#[derive(Debug, Serialize)] +struct AttestationCertV0 { + schema_version: OutputVersion, + ident: String, + attestation_cert: String, +} + +impl OutputVariant for AttestationCertV0 { + const VERSION: OutputVersion = OutputVersion::new(0, 9, 0); +} diff --git a/tools/src/bin/opgpcard/output/generate.rs b/tools/src/bin/opgpcard/output/generate.rs new file mode 100644 index 0000000..33c3282 --- /dev/null +++ b/tools/src/bin/opgpcard/output/generate.rs @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2022 Lars Wirzenius +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use serde::Serialize; + +use crate::output::OpgpCardError; +use crate::{OutputBuilder, OutputFormat, OutputVariant, OutputVersion}; + +#[derive(Debug, Default, Serialize)] +pub struct AdminGenerate { + ident: String, + algorithm: String, + public_key: String, +} + +impl AdminGenerate { + pub fn ident(&mut self, ident: String) { + self.ident = ident; + } + + pub fn algorithm(&mut self, algorithm: String) { + self.algorithm = algorithm; + } + + pub fn public_key(&mut self, key: String) { + self.public_key = key; + } + + fn text(&self) -> Result { + Ok(format!( + "OpenPGP card {}\n\n{}\n", + self.ident, self.public_key, + )) + } + + fn v1(&self) -> Result { + Ok(AdminGenerateV0 { + schema_version: AdminGenerateV0::VERSION, + ident: self.ident.clone(), + algorithm: self.algorithm.clone(), + public_key: self.public_key.clone(), + }) + } +} + +impl OutputBuilder for AdminGenerate { + type Err = OpgpCardError; + + fn print(&self, format: OutputFormat, version: OutputVersion) -> Result { + match format { + OutputFormat::Json => { + let result = if AdminGenerateV0::VERSION.is_acceptable_for(&version) { + self.v1()?.json() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeJson) + } + OutputFormat::Yaml => { + let result = if AdminGenerateV0::VERSION.is_acceptable_for(&version) { + self.v1()?.yaml() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeYaml) + } + OutputFormat::Text => Ok(self.text()?), + } + } +} + +#[derive(Debug, Serialize)] +struct AdminGenerateV0 { + schema_version: OutputVersion, + ident: String, + algorithm: String, + public_key: String, +} + +impl OutputVariant for AdminGenerateV0 { + const VERSION: OutputVersion = OutputVersion::new(0, 9, 0); +} diff --git a/tools/src/bin/opgpcard/output/info.rs b/tools/src/bin/opgpcard/output/info.rs new file mode 100644 index 0000000..57f2b10 --- /dev/null +++ b/tools/src/bin/opgpcard/output/info.rs @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2022 Lars Wirzenius +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use serde::Serialize; + +use crate::output::OpgpCardError; +use crate::{OutputBuilder, OutputFormat, OutputVariant, OutputVersion}; + +#[derive(Debug, Default, Serialize)] +pub struct Info { + ident: String, + card_version: String, + application_id: String, + manufacturer_id: String, + manufacturer_name: String, + card_capabilities: Vec, + card_service_data: String, + extended_length_info: Vec, + extended_capabilities: Vec, + algorithms: Option>, + firmware_version: Option, +} + +impl Info { + pub fn ident(&mut self, ident: String) { + self.ident = ident; + } + + pub fn card_version(&mut self, version: String) { + self.card_version = version; + } + + pub fn application_id(&mut self, id: String) { + self.application_id = id; + } + + pub fn manufacturer_id(&mut self, id: String) { + self.manufacturer_id = id; + } + + pub fn manufacturer_name(&mut self, name: String) { + self.manufacturer_name = name; + } + + pub fn card_capability(&mut self, capability: String) { + self.card_capabilities.push(capability); + } + + pub fn card_service_data(&mut self, data: String) { + self.card_service_data = data; + } + + pub fn extended_length_info(&mut self, info: String) { + self.extended_length_info.push(info); + } + + pub fn extended_capability(&mut self, capability: String) { + self.extended_capabilities.push(capability); + } + + pub fn algorithm(&mut self, algorithm: String) { + if let Some(ref mut algos) = self.algorithms { + algos.push(algorithm); + } else { + self.algorithms = Some(vec![algorithm]); + } + } + + pub fn firmware_version(&mut self, version: String) { + self.firmware_version = Some(version); + } + + fn text(&self) -> Result { + let mut s = format!("OpenPGP card {}\n\n", self.ident); + + s.push_str(&format!( + "Application Identifier: {}\n", + self.application_id + )); + s.push_str(&format!( + "Manufacturer [{}]: {}\n\n", + self.manufacturer_id, self.manufacturer_name + )); + + if !self.card_capabilities.is_empty() { + s.push_str("Card Capabilities:\n"); + for c in self.card_capabilities.iter() { + s.push_str(&format!("- {}\n", c)); + } + s.push('\n'); + } + + if !self.card_service_data.is_empty() { + s.push_str(&format!("Card service data: {}\n", self.card_service_data)); + s.push('\n'); + } + + if !self.extended_length_info.is_empty() { + s.push_str("Extended Length Info:\n"); + for c in self.extended_length_info.iter() { + s.push_str(&format!("- {}\n", c)); + } + s.push('\n'); + } + + s.push_str("Extended Capabilities:\n"); + for c in self.extended_capabilities.iter() { + s.push_str(&format!("- {}\n", c)); + } + s.push('\n'); + + if let Some(algos) = &self.algorithms { + s.push_str("Supported algorithms:\n"); + for c in algos.iter() { + s.push_str(&format!("- {}\n", c)); + } + s.push('\n'); + } + + if let Some(v) = &self.firmware_version { + s.push_str(&format!("Firmware Version: {}\n", v)); + } + + Ok(s) + } + + fn v1(&self) -> Result { + Ok(InfoV0 { + schema_version: InfoV0::VERSION, + ident: self.ident.clone(), + card_version: self.card_version.clone(), + application_id: self.application_id.clone(), + manufacturer_id: self.manufacturer_id.clone(), + manufacturer_name: self.manufacturer_name.clone(), + card_capabilities: self.card_capabilities.clone(), + card_service_data: self.card_service_data.clone(), + extended_length_info: self.extended_length_info.clone(), + extended_capabilities: self.extended_capabilities.clone(), + algorithms: self.algorithms.clone(), + firmware_version: self.firmware_version.clone(), + }) + } +} + +impl OutputBuilder for Info { + type Err = OpgpCardError; + + fn print(&self, format: OutputFormat, version: OutputVersion) -> Result { + match format { + OutputFormat::Json => { + let result = if InfoV0::VERSION.is_acceptable_for(&version) { + self.v1()?.json() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeJson) + } + OutputFormat::Yaml => { + let result = if InfoV0::VERSION.is_acceptable_for(&version) { + self.v1()?.yaml() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeYaml) + } + OutputFormat::Text => Ok(self.text()?), + } + } +} + +#[derive(Debug, Serialize)] +struct InfoV0 { + schema_version: OutputVersion, + ident: String, + card_version: String, + application_id: String, + manufacturer_id: String, + manufacturer_name: String, + card_capabilities: Vec, + card_service_data: String, + extended_length_info: Vec, + extended_capabilities: Vec, + algorithms: Option>, + firmware_version: Option, +} + +impl OutputVariant for InfoV0 { + const VERSION: OutputVersion = OutputVersion::new(0, 9, 0); +} diff --git a/tools/src/bin/opgpcard/output/list.rs b/tools/src/bin/opgpcard/output/list.rs new file mode 100644 index 0000000..eb2d010 --- /dev/null +++ b/tools/src/bin/opgpcard/output/list.rs @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2022 Lars Wirzenius +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use serde::Serialize; + +use crate::output::OpgpCardError; +use crate::{OutputBuilder, OutputFormat, OutputVariant, OutputVersion}; + +#[derive(Default, Debug, Serialize)] +pub struct List { + idents: Vec, +} + +impl List { + pub fn push(&mut self, idnet: String) { + self.idents.push(idnet); + } + + fn text(&self) -> Result { + let s = if self.idents.is_empty() { + "No OpenPGP cards found.".into() + } else { + let mut s = "Available OpenPGP cards:\n".to_string(); + for id in self.idents.iter() { + s.push_str(&format!(" {}\n", id)); + } + s + }; + Ok(s) + } + + fn v1(&self) -> Result { + Ok(ListV0 { + schema_version: ListV0::VERSION, + idents: self.idents.clone(), + }) + } +} + +impl OutputBuilder for List { + type Err = OpgpCardError; + + fn print(&self, format: OutputFormat, version: OutputVersion) -> Result { + match format { + OutputFormat::Json => { + let result = if ListV0::VERSION.is_acceptable_for(&version) { + self.v1()?.json() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeJson) + } + OutputFormat::Yaml => { + let result = if ListV0::VERSION.is_acceptable_for(&version) { + self.v1()?.yaml() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeYaml) + } + OutputFormat::Text => Ok(self.text()?), + } + } +} + +#[derive(Debug, Serialize)] +struct ListV0 { + schema_version: OutputVersion, + idents: Vec, +} + +impl OutputVariant for ListV0 { + const VERSION: OutputVersion = OutputVersion::new(0, 9, 0); +} diff --git a/tools/src/bin/opgpcard/output/mod.rs b/tools/src/bin/opgpcard/output/mod.rs new file mode 100644 index 0000000..1534851 --- /dev/null +++ b/tools/src/bin/opgpcard/output/mod.rs @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2022 Lars Wirzenius +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::OutputVersion; + +#[derive(Debug, thiserror::Error)] +pub enum OpgpCardError { + #[error("unknown output version {0}")] + UnknownVersion(OutputVersion), + + #[error("failed to serialize JSON output with serde_json")] + SerdeJson(#[source] serde_json::Error), + + #[error("failed to serialize YAML output with serde_yaml")] + SerdeYaml(#[source] serde_yaml::Error), +} + +mod list; +pub use list::List; + +mod status; +pub use status::{KeySlotInfo, Status}; + +mod info; +pub use info::Info; + +mod ssh; +pub use ssh::Ssh; + +mod pubkey; +pub use pubkey::PublicKey; + +mod generate; +pub use generate::AdminGenerate; + +mod attest; +pub use attest::AttestationCert; diff --git a/tools/src/bin/opgpcard/output/pubkey.rs b/tools/src/bin/opgpcard/output/pubkey.rs new file mode 100644 index 0000000..0c9ddbd --- /dev/null +++ b/tools/src/bin/opgpcard/output/pubkey.rs @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2022 Lars Wirzenius +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use serde::Serialize; + +use crate::output::OpgpCardError; +use crate::{OutputBuilder, OutputFormat, OutputVariant, OutputVersion}; + +#[derive(Debug, Default, Serialize)] +pub struct PublicKey { + ident: String, + public_key: String, +} + +impl PublicKey { + pub fn ident(&mut self, ident: String) { + self.ident = ident; + } + + pub fn public_key(&mut self, key: String) { + self.public_key = key; + } + + fn text(&self) -> Result { + Ok(format!( + "OpenPGP card {}\n\n{}\n", + self.ident, self.public_key + )) + } + + fn v1(&self) -> Result { + Ok(PublicKeyV0 { + schema_version: PublicKeyV0::VERSION, + ident: self.ident.clone(), + public_key: self.public_key.clone(), + }) + } +} + +impl OutputBuilder for PublicKey { + type Err = OpgpCardError; + + fn print(&self, format: OutputFormat, version: OutputVersion) -> Result { + match format { + OutputFormat::Json => { + let result = if PublicKeyV0::VERSION.is_acceptable_for(&version) { + self.v1()?.json() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeJson) + } + OutputFormat::Yaml => { + let result = if PublicKeyV0::VERSION.is_acceptable_for(&version) { + self.v1()?.yaml() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeYaml) + } + OutputFormat::Text => Ok(self.text()?), + } + } +} + +#[derive(Debug, Serialize)] +struct PublicKeyV0 { + schema_version: OutputVersion, + ident: String, + public_key: String, +} + +impl OutputVariant for PublicKeyV0 { + const VERSION: OutputVersion = OutputVersion::new(0, 9, 0); +} diff --git a/tools/src/bin/opgpcard/output/ssh.rs b/tools/src/bin/opgpcard/output/ssh.rs new file mode 100644 index 0000000..7d73288 --- /dev/null +++ b/tools/src/bin/opgpcard/output/ssh.rs @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2022 Lars Wirzenius +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use serde::Serialize; + +use crate::output::OpgpCardError; +use crate::{OutputBuilder, OutputFormat, OutputVariant, OutputVersion}; + +#[derive(Debug, Default, Serialize)] +pub struct Ssh { + ident: String, + authentication_key_fingerprint: Option, + ssh_public_key: Option, +} + +impl Ssh { + pub fn ident(&mut self, ident: String) { + self.ident = ident; + } + + pub fn authentication_key_fingerprint(&mut self, fp: String) { + self.authentication_key_fingerprint = Some(fp); + } + + pub fn ssh_public_key(&mut self, key: String) { + self.ssh_public_key = Some(key); + } + + fn text(&self) -> Result { + let mut s = format!("OpenPGP card {}\n", self.ident); + + if let Some(fp) = &self.authentication_key_fingerprint { + s.push_str(&format!("Authentication key fingerprint:\n{}\n", fp)); + } + if let Some(key) = &self.ssh_public_key { + s.push_str(&format!("SSH public key:\n{}\n", key)); + } + + Ok(s) + } + + fn v1(&self) -> Result { + Ok(SshV0 { + schema_version: SshV0::VERSION, + ident: self.ident.clone(), + authentication_key_fingerprint: self.authentication_key_fingerprint.clone(), + ssh_public_key: self.ssh_public_key.clone(), + }) + } +} + +impl OutputBuilder for Ssh { + type Err = OpgpCardError; + + fn print(&self, format: OutputFormat, version: OutputVersion) -> Result { + match format { + OutputFormat::Json => { + let result = if SshV0::VERSION.is_acceptable_for(&version) { + self.v1()?.json() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeJson) + } + OutputFormat::Yaml => { + let result = if SshV0::VERSION.is_acceptable_for(&version) { + self.v1()?.yaml() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeYaml) + } + OutputFormat::Text => Ok(self.text()?), + } + } +} + +#[derive(Debug, Serialize)] +struct SshV0 { + schema_version: OutputVersion, + ident: String, + authentication_key_fingerprint: Option, + ssh_public_key: Option, +} + +impl OutputVariant for SshV0 { + const VERSION: OutputVersion = OutputVersion::new(0, 9, 0); +} diff --git a/tools/src/bin/opgpcard/output/status.rs b/tools/src/bin/opgpcard/output/status.rs new file mode 100644 index 0000000..4e462ad --- /dev/null +++ b/tools/src/bin/opgpcard/output/status.rs @@ -0,0 +1,327 @@ +// SPDX-FileCopyrightText: 2022 Lars Wirzenius +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use serde::Serialize; + +use crate::output::OpgpCardError; +use crate::{OutputBuilder, OutputFormat, OutputVariant, OutputVersion}; + +#[derive(Debug, Default, Serialize)] +pub struct Status { + verbose: bool, + ident: String, + card_version: String, + card_holder: Option, + url: Option, + language_preferences: Vec, + signature_key: KeySlotInfo, + signature_count: u32, + decryption_key: KeySlotInfo, + authentication_key: KeySlotInfo, + user_pin_remaining_attempts: u8, + admin_pin_remaining_attempts: u8, + reset_code_remaining_attempts: u8, + card_touch_policy: String, + card_touch_features: String, + key_statuses: Vec<(u8, String)>, + ca_fingerprints: Vec, +} + +impl Status { + pub fn verbose(&mut self, verbose: bool) { + self.verbose = verbose; + } + + pub fn ident(&mut self, ident: String) { + self.ident = ident; + } + + pub fn card_version(&mut self, card_version: String) { + self.card_version = card_version; + } + + pub fn card_holder(&mut self, card_holder: String) { + self.card_holder = Some(card_holder); + } + + pub fn url(&mut self, url: String) { + self.url = Some(url); + } + + pub fn language_preference(&mut self, pref: String) { + self.language_preferences.push(pref); + } + + pub fn signature_key(&mut self, key: KeySlotInfo) { + self.signature_key = key; + } + + pub fn signature_count(&mut self, count: u32) { + self.signature_count = count; + } + + pub fn decryption_key(&mut self, key: KeySlotInfo) { + self.decryption_key = key; + } + + pub fn authentication_key(&mut self, key: KeySlotInfo) { + self.authentication_key = key; + } + + pub fn user_pin_remaining_attempts(&mut self, count: u8) { + self.user_pin_remaining_attempts = count; + } + + pub fn admin_pin_remaining_attempts(&mut self, count: u8) { + self.admin_pin_remaining_attempts = count; + } + + pub fn reset_code_remaining_attempts(&mut self, count: u8) { + self.reset_code_remaining_attempts = count; + } + + pub fn card_touch_policy(&mut self, policy: String) { + self.card_touch_policy = policy; + } + + pub fn card_touch_features(&mut self, features: String) { + self.card_touch_features = features; + } + + pub fn key_status(&mut self, keyref: u8, status: String) { + self.key_statuses.push((keyref, status)); + } + + pub fn ca_fingerprint(&mut self, fingerprint: String) { + self.ca_fingerprints.push(fingerprint); + } + + fn text(&self) -> Result { + let mut s = String::new(); + + s.push_str(&format!( + "OpenPGP card {} (card version {})\n\n", + self.ident, self.card_version + )); + + let mut nl = false; + if let Some(name) = &self.card_holder { + if !name.is_empty() { + s.push_str(&format!("Cardholder: {}\n", name)); + nl = true; + } + } + + if let Some(url) = &self.url { + if !url.is_empty() { + s.push_str(&format!("URL: {}\n", url)); + nl = true; + } + } + + if !self.language_preferences.is_empty() { + let prefs = self.language_preferences.to_vec().join(", "); + if !prefs.is_empty() { + s.push_str(&format!("Language preferences: '{}'\n", prefs)); + nl = true; + } + } + + if nl { + s.push('\n'); + } + + s.push_str("Signature key:\n"); + for line in self.signature_key.format(self.verbose) { + s.push_str(&format!(" {}\n", line)); + } + s.push_str(&format!(" Signatures made: {}\n", self.signature_count)); + s.push('\n'); + + s.push_str("Decryption key:\n"); + for line in self.decryption_key.format(self.verbose) { + s.push_str(&format!(" {}\n", line)); + } + s.push('\n'); + + s.push_str("Authentication key:\n"); + for line in self.authentication_key.format(self.verbose) { + s.push_str(&format!(" {}\n", line)); + } + s.push('\n'); + + s.push_str(&format!( + "Remaining PIN attempts: User: {}, Admin: {}, Reset Code: {}\n", + self.user_pin_remaining_attempts, + self.admin_pin_remaining_attempts, + self.reset_code_remaining_attempts + )); + + if self.verbose { + s.push_str(&format!( + "Touch policy attestation: {}\n", + self.card_touch_policy + )); + + for (keyref, status) in self.key_statuses.iter() { + s.push_str(&format!("Key status (#{}): {}\n", keyref, status)); + } + } + + Ok(s) + } + + fn v1(&self) -> Result { + Ok(StatusV0 { + schema_version: StatusV0::VERSION, + ident: self.ident.clone(), + card_version: self.card_version.clone(), + card_holder: self.card_holder.clone(), + url: self.url.clone(), + language_preferences: self.language_preferences.clone(), + signature_key: self.signature_key.clone(), + signature_count: self.signature_count, + decryption_key: self.decryption_key.clone(), + authentication_key: self.authentication_key.clone(), + user_pin_remaining_attempts: self.user_pin_remaining_attempts, + admin_pin_remaining_attempts: self.admin_pin_remaining_attempts, + reset_code_remaining_attempts: self.reset_code_remaining_attempts, + card_touch_policy: self.card_touch_policy.clone(), + card_touch_features: self.card_touch_features.clone(), + key_statuses: self.key_statuses.clone(), + ca_fingerprints: self.ca_fingerprints.clone(), + }) + } +} + +impl OutputBuilder for Status { + type Err = OpgpCardError; + + fn print(&self, format: OutputFormat, version: OutputVersion) -> Result { + match format { + OutputFormat::Json => { + let result = if StatusV0::VERSION.is_acceptable_for(&version) { + self.v1()?.json() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeJson) + } + OutputFormat::Yaml => { + let result = if StatusV0::VERSION.is_acceptable_for(&version) { + self.v1()?.yaml() + } else { + return Err(Self::Err::UnknownVersion(version)); + }; + result.map_err(Self::Err::SerdeYaml) + } + OutputFormat::Text => Ok(self.text()?), + } + } +} + +#[derive(Debug, Serialize)] +pub struct StatusV0 { + schema_version: OutputVersion, + ident: String, + card_version: String, + card_holder: Option, + url: Option, + language_preferences: Vec, + signature_key: KeySlotInfo, + signature_count: u32, + decryption_key: KeySlotInfo, + authentication_key: KeySlotInfo, + user_pin_remaining_attempts: u8, + admin_pin_remaining_attempts: u8, + reset_code_remaining_attempts: u8, + card_touch_policy: String, + card_touch_features: String, + key_statuses: Vec<(u8, String)>, + ca_fingerprints: Vec, +} + +impl OutputVariant for StatusV0 { + const VERSION: OutputVersion = OutputVersion::new(0, 9, 0); +} + +#[derive(Debug, Default, Clone, Serialize)] +pub struct KeySlotInfo { + fingerprint: Option, + algorithm: Option, + created: Option, + touch_policy: Option, + touch_features: Option, + status: Option, + pin_valid_once: bool, + public_key_material: Option, +} + +impl KeySlotInfo { + pub fn fingerprint(&mut self, fingerprint: String) { + self.fingerprint = Some(fingerprint); + } + + pub fn algorithm(&mut self, algorithm: String) { + self.algorithm = Some(algorithm); + } + + pub fn created(&mut self, created: String) { + self.created = Some(created); + } + + pub fn touch_policy(&mut self, policy: String) { + self.touch_policy = Some(policy); + } + + pub fn touch_features(&mut self, features: String) { + self.touch_features = Some(features); + } + + pub fn status(&mut self, status: String) { + self.status = Some(status); + } + + pub fn pin_valid_once(&mut self) { + self.pin_valid_once = true; + } + + pub fn public_key_material(&mut self, material: String) { + self.public_key_material = Some(material); + } + + fn format(&self, verbose: bool) -> Vec { + let mut lines = vec![]; + + if let Some(fp) = &self.fingerprint { + lines.push(format!("Fingerprint: {}", fp)); + } + if let Some(a) = &self.algorithm { + lines.push(format!("Algorithm: {}", a)); + } + if let Some(ts) = &self.created { + lines.push(format!("Created: {}", ts)); + } + + if verbose { + if let Some(policy) = &self.touch_policy { + if let Some(features) = &self.touch_features { + lines.push(format!("Touch policy: {} (features: {})", policy, features)); + } + } + if let Some(status) = &self.status { + lines.push(format!("Key Status: {}", status)); + } + if self.pin_valid_once { + lines.push("User PIN presentation valid for one signature".into()); + } else { + lines.push("User PIN presentation valid for unlimited signatures".into()); + } + } + if let Some(material) = &self.public_key_material { + lines.push(format!("Public key material: {}", material)); + } + + lines + } +}