From c4609049254b133addc264d18a8724eedc959f10 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 13 Mar 2023 17:50:36 +0100 Subject: [PATCH 001/115] opgpcard: Add a parameter '--key-only' to the ssh command. This outputs only one line, containing the ssh public key string, which is useful in scripts (e.g. in CI). --- tools/src/commands/ssh.rs | 5 +++++ tools/src/output/ssh.rs | 25 +++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/tools/src/commands/ssh.rs b/tools/src/commands/ssh.rs index 7999579..3fe49ee 100644 --- a/tools/src/commands/ssh.rs +++ b/tools/src/commands/ssh.rs @@ -22,6 +22,9 @@ pub struct SshCommand { help = "Identifier of the card to use" )] pub ident: Option, + + #[clap(long, help = "Only print the ssh public key")] + pub key_only: bool, } pub fn print_ssh( @@ -31,6 +34,8 @@ pub fn print_ssh( ) -> Result<()> { let mut output = output::Ssh::default(); + output.key_only(command.key_only); + let ident = command.ident; let backend = pick_card_for_reading(ident)?; diff --git a/tools/src/output/ssh.rs b/tools/src/output/ssh.rs index 5ba0467..da28d28 100644 --- a/tools/src/output/ssh.rs +++ b/tools/src/output/ssh.rs @@ -8,12 +8,17 @@ use crate::{OutputBuilder, OutputFormat, OutputVariant, OutputVersion}; #[derive(Debug, Default, Serialize)] pub struct Ssh { + key_only: bool, // only print ssh public key, in text mode ident: String, authentication_key_fingerprint: Option, ssh_public_key: Option, } impl Ssh { + pub fn key_only(&mut self, ssh_key_only: bool) { + self.key_only = ssh_key_only; + } + pub fn ident(&mut self, ident: String) { self.ident = ident; } @@ -27,16 +32,20 @@ impl Ssh { } fn text(&self) -> Result { - let mut s = format!("OpenPGP card {}\n\n", self.ident); + if !self.key_only { + let mut s = format!("OpenPGP card {}\n\n", self.ident); - if let Some(fp) = &self.authentication_key_fingerprint { - s.push_str(&format!("Authentication key fingerprint:\n{fp}\n\n")); - } - if let Some(key) = &self.ssh_public_key { - s.push_str(&format!("SSH public key:\n{key}\n")); - } + if let Some(fp) = &self.authentication_key_fingerprint { + s.push_str(&format!("Authentication key fingerprint:\n{fp}\n\n")); + } + if let Some(key) = &self.ssh_public_key { + s.push_str(&format!("SSH public key:\n{key}\n")); + } - Ok(s) + Ok(s) + } else { + Ok(self.ssh_public_key.clone().unwrap_or("".to_string())) + } } fn v1(&self) -> Result { From 971a19b4f2a61f38e1d55c58fe692ae34ce19705 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 13 Mar 2023 17:50:51 +0100 Subject: [PATCH 002/115] Release openpgp-card 0.3.4, openpgp-card-tools 0.9.2 --- openpgp-card/Cargo.toml | 2 +- tools/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpgp-card/Cargo.toml b/openpgp-card/Cargo.toml index 1d46391..7881416 100644 --- a/openpgp-card/Cargo.toml +++ b/openpgp-card/Cargo.toml @@ -5,7 +5,7 @@ name = "openpgp-card" description = "A client implementation for the OpenPGP card specification" license = "MIT OR Apache-2.0" -version = "0.3.3" +version = "0.3.4" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" diff --git a/tools/Cargo.toml b/tools/Cargo.toml index fb0c3ec..df30617 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +# SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer # SPDX-FileCopyrightText: 2022 Nora Widdecke # SPDX-License-Identifier: MIT OR Apache-2.0 @@ -6,7 +6,7 @@ name = "openpgp-card-tools" description = "CLI tools for OpenPGP cards" license = "MIT OR Apache-2.0" -version = "0.9.1" +version = "0.9.2" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" From 7a28d36e9329a48effd8373d70d2ac4d43836133 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 17 Mar 2023 13:55:42 +0100 Subject: [PATCH 003/115] openpgp-card-tools moved to https://codeberg.org/openpgp-card/openpgp-card-tools --- .gitlab-ci.yml | 45 +- .reuse/dep5 | 4 - Cargo.toml | 1 - tools/Cargo.toml | 51 -- tools/README.md | 736 ---------------------------- tools/build.rs | 31 -- tools/cargo-test-in-docker | 26 - tools/debian/build-deb | 9 - tools/debian/cargo-checksum.json | 0 tools/debian/changelog | 6 - tools/debian/compat | 2 - tools/debian/control | 23 - tools/debian/copyright | 3 - tools/debian/docs | 1 - tools/debian/rules | 23 - tools/debian/source/format | 1 - tools/prepare-card.py | 149 ------ tools/scripting.md | 61 --- tools/src/cli.rs | 111 ----- tools/src/commands/admin.rs | 568 --------------------- tools/src/commands/attestation.rs | 195 -------- tools/src/commands/decrypt.rs | 69 --- tools/src/commands/factory_reset.rs | 29 -- tools/src/commands/info.rs | 98 ---- tools/src/commands/mod.rs | 15 - tools/src/commands/pin.rs | 364 -------------- tools/src/commands/pubkey.rs | 110 ----- tools/src/commands/set_identity.rs | 53 -- tools/src/commands/sign.rs | 92 ---- tools/src/commands/ssh.rs | 64 --- tools/src/commands/status.rs | 220 --------- tools/src/opgpcard.rs | 148 ------ tools/src/output/attest.rs | 75 --- tools/src/output/generate.rs | 80 --- tools/src/output/info.rs | 192 -------- tools/src/output/list.rs | 74 --- tools/src/output/mod.rs | 37 -- tools/src/output/pubkey.rs | 75 --- tools/src/output/ssh.rs | 97 ---- tools/src/output/status.rs | 340 ------------- tools/src/util.rs | 253 ---------- tools/src/versioned_output.rs | 82 ---- tools/subplot/opgpcard.md | 238 --------- tools/subplot/opgpcard.rs | 99 ---- tools/subplot/opgpcard.subplot | 15 - tools/subplot/opgpcard.yaml | 12 - tools/subplot/test-in-docker.sh | 29 -- tools/tests/opgpcard.rs | 6 - 48 files changed, 9 insertions(+), 5003 deletions(-) delete mode 100644 tools/Cargo.toml delete mode 100644 tools/README.md delete mode 100644 tools/build.rs delete mode 100755 tools/cargo-test-in-docker delete mode 100755 tools/debian/build-deb delete mode 100644 tools/debian/cargo-checksum.json delete mode 100644 tools/debian/changelog delete mode 100644 tools/debian/compat delete mode 100644 tools/debian/control delete mode 100644 tools/debian/copyright delete mode 100644 tools/debian/docs delete mode 100755 tools/debian/rules delete mode 100644 tools/debian/source/format delete mode 100755 tools/prepare-card.py delete mode 100644 tools/scripting.md delete mode 100644 tools/src/cli.rs delete mode 100644 tools/src/commands/admin.rs delete mode 100644 tools/src/commands/attestation.rs delete mode 100644 tools/src/commands/decrypt.rs delete mode 100644 tools/src/commands/factory_reset.rs delete mode 100644 tools/src/commands/info.rs delete mode 100644 tools/src/commands/mod.rs delete mode 100644 tools/src/commands/pin.rs delete mode 100644 tools/src/commands/pubkey.rs delete mode 100644 tools/src/commands/set_identity.rs delete mode 100644 tools/src/commands/sign.rs delete mode 100644 tools/src/commands/ssh.rs delete mode 100644 tools/src/commands/status.rs delete mode 100644 tools/src/opgpcard.rs delete mode 100644 tools/src/output/attest.rs delete mode 100644 tools/src/output/generate.rs delete mode 100644 tools/src/output/info.rs delete mode 100644 tools/src/output/list.rs delete mode 100644 tools/src/output/mod.rs delete mode 100644 tools/src/output/pubkey.rs delete mode 100644 tools/src/output/ssh.rs delete mode 100644 tools/src/output/status.rs delete mode 100644 tools/src/util.rs delete mode 100644 tools/src/versioned_output.rs delete mode 100644 tools/subplot/opgpcard.md delete mode 100644 tools/subplot/opgpcard.rs delete mode 100644 tools/subplot/opgpcard.subplot delete mode 100644 tools/subplot/opgpcard.yaml delete mode 100755 tools/subplot/test-in-docker.sh delete mode 100644 tools/tests/opgpcard.rs diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7d4e1f3..2657bb4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +# SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer # SPDX-FileCopyrightText: 2021-2022 Nora Widdecke # SPDX-License-Identifier: CC0-1.0 @@ -131,33 +131,6 @@ cargo-test-debian-bookworm: # override the key key: "bookworm" -subplot: - stage: virtual-test - image: registry.gitlab.com/openpgp-card/virtual-cards/smartpgp-builddeps - before_script: - - mkdir -p /run/user/$UID - - apt update -y -qq - - > - apt install -y -qq --no-install-recommends - git clang make pkg-config nettle-dev libssl-dev capnproto ca-certificates - libpcsclite-dev - sq - - apt clean - - /etc/init.d/pcscd start - - su - -c "sh /home/jcardsim/run-card.sh >/dev/null" jcardsim - - *report-rust - script: - # make sure a virtual card is available, so that the subplot tests are - # generated - - CARD_BASED_TESTS=true cargo test -- --test-threads 1 - cache: - # inherit all general cache settings - <<: *general_cache_config - # subplot uses tests/virtual-card-available to indicate that tests which use - # virtual cards should be created. The cache with this file should not be - # shared. - key: "subplot" - run_cardtest_smartpgp: stage: virtual-test image: registry.gitlab.com/openpgp-card/virtual-cards/smartpgp-builddeps @@ -165,8 +138,8 @@ run_cardtest_smartpgp: - *report-rust script: - sh /start.sh - - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- status - - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- info +# - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- status +# - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- info - RUST_BACKTRACE=1 cargo run -p openpgp-card-tests --bin import -- $CONFIG - RUST_BACKTRACE=1 cargo run -p openpgp-card-tests --bin keygen -- $CONFIG variables: @@ -185,8 +158,8 @@ run_cardtest_opcard_rs: - *report-rust script: - sh /start.sh - - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- status - - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- info +# - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- status +# - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- info - RUST_BACKTRACE=1 cargo run -p openpgp-card-tests --bin import -- $CONFIG - RUST_BACKTRACE=1 cargo run -p openpgp-card-tests --bin keygen -- $CONFIG variables: @@ -205,8 +178,8 @@ run_cardtest_ykneo: - *report-rust script: - sh /start.sh - - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- status - - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- info +# - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- status +# - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- info - RUST_BACKTRACE=1 cargo run -p openpgp-card-tests --bin import -- $CONFIG - RUST_BACKTRACE=1 cargo run -p openpgp-card-tests --bin keygen -- $CONFIG variables: @@ -225,8 +198,8 @@ run_cardtest_fluffypgp: - *report-rust script: - sh /start.sh - - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- status - - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- info +# - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- status +# - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- info - RUST_BACKTRACE=1 cargo run -p openpgp-card-tests --bin import -- $CONFIG - RUST_BACKTRACE=1 cargo run -p openpgp-card-tests --bin keygen -- $CONFIG variables: diff --git a/.reuse/dep5 b/.reuse/dep5 index a762701..fda4287 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -10,7 +10,3 @@ License: CC0-1.0 Files: card-functionality/data/* Copyright: 2021 Heiko Schaefer License: CC0-1.0 - -Files: tools/debian/* -Copyright: 2022 Lars Wirzenius -License: CC0-1.0 diff --git a/Cargo.toml b/Cargo.toml index dd33793..32c40c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,5 @@ members = [ "pcsc", "scdc", "openpgp-card-examples", - "tools", "card-functionality", ] diff --git a/tools/Cargo.toml b/tools/Cargo.toml deleted file mode 100644 index df30617..0000000 --- a/tools/Cargo.toml +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer -# SPDX-FileCopyrightText: 2022 Nora Widdecke -# SPDX-License-Identifier: MIT OR Apache-2.0 - -[package] -name = "openpgp-card-tools" -description = "CLI tools for OpenPGP cards" -license = "MIT OR Apache-2.0" -version = "0.9.2" -authors = ["Heiko Schaefer "] -edition = "2018" -repository = "https://gitlab.com/openpgp-card/openpgp-card" -documentation = "https://docs.rs/crate/openpgp-card-tools" - -[[bin]] -name = "opgpcard" -path = "src/opgpcard.rs" - -[dependencies] -sequoia-openpgp = { version = "1.3", default-features = false } -openpgp-card-pcsc = { path = "../pcsc", version = "0.3" } -openpgp-card-sequoia = { path = "../openpgp-card-sequoia", version = "0.1.1", default-features = false } -sshkeys = "0.3.2" -pem = "1" -rpassword = "6" -anyhow = "1" -clap = { version = "3", features = ["derive", "wrap_help"] } -env_logger = "0.9" -log = "0.4" -serde_json = "1.0.86" -serde = { version = "1.0.145", features = ["derive"] } -semver = "1.0.14" -serde_yaml = "0.9.13" -thiserror = "1.0.37" -indoc = "1" - -[build-dependencies] -subplot-build = "0.5.0" - -[dev-dependencies] -fehler = "1.0.0" -subplotlib = "0.5.0" - -[profile.release] -codegen-units = 1 - -[features] -default = ["sequoia-openpgp/default"] - -[package.metadata.cargo-udeps.ignore] -development = ["fehler", "subplotlib"] diff --git a/tools/README.md b/tools/README.md deleted file mode 100644 index 7eb126a..0000000 --- a/tools/README.md +++ /dev/null @@ -1,736 +0,0 @@ - - -# OpenPGP card tools - -This crate contains the `opgpcard` tool for inspecting, configuring and using OpenPGP -cards. - -# Install - -One easy way to install this crate is via the "cargo" tool. - -The following build dependencies are needed for current Debian: - -``` -# apt install rustc cargo clang pkg-config nettle-dev libpcsclite-dev -``` - -And for current Fedora: - -``` -# dnf install rustc cargo clang nettle-devel pcsc-lite-devel -``` - -Afterwards, you can install this crate by running: - -``` -$ cargo install openpgp-card-tools -``` - -Finally, add `$HOME/.cargo/bin` to your PATH to be able to run the installed -binaries. - -`opgpcard` uses the PC/SC framework. So on Linux-based systems, you need to make sure the `pcscd` -service is running, to be able to access your OpenPGP cards. - -## opgpcard - -A tool to inspect, configure and use OpenPGP cards. - -This tool is designed to be equally convenient for regular interactive use, as well as from scripts. -To this end, all functionality of this tool is alternatively usable in a non-interactive manner (see below). - -When using the tool in interactive contexts, two methods of PIN entry are supported: -in most cases, PINs can (and must) be entered via the host computer. -When a pin pad is available on the smartcard reader, PIN entry will be requested via this pin pad. - -### List cards - -List idents of all currently connected cards: - -``` -$ opgpcard list -Available OpenPGP cards: - ABCD:01234567 - 0007:87654321 -``` - -### Inspect card status - -Print status information about the data on a card. -The card is implicitly selected (if exactly one card is connected): - -``` -$ opgpcard status -OpenPGP card ABCD:01234567 (card version 3.4) - -Cardholder: Alice Adams -Language preferences: 'en' - -Signature key - Fingerprint: 034B 348C EDA2 064C AA22 74E4 7563 E86F 5CAB C2A4 - Creation Time: 2022-05-21 13:15:19 UTC - Algorithm: Ed25519 (EdDSA) - Signatures made: 11 - -Decryption key - Fingerprint: 338B EE09 3950 D831 A76F 0EB9 13D6 2DF6 8C9E 5176 - Creation Time: 2022-05-21 13:15:19 UTC - Algorithm: Cv25519 (ECDH) - -Authentication key - Fingerprint: 4881 A22E 7EC6 26D1 1202 50B0 A7D7 F0D5 0C8D F719 - Creation Time: 2022-05-21 13:15:19 UTC - Algorithm: Ed25519 (EdDSA) - -Remaining PIN attempts: User: 3, Admin: 3, Reset Code: 0 -``` - -Explicitly print the status information for a specific card (this command syntax is needed, when more than one card -is plugged in): - -``` -$ opgpcard status --card ABCD:01234567 -``` - -Add `-v` for more verbose card status: - -``` -OpenPGP card ABCD:01234567 (card version 3.4) - -Cardholder: Alice Adams -Language preferences: 'en' - -Signature key - Fingerprint: 034B 348C EDA2 064C AA22 74E4 7563 E86F 5CAB C2A4 - Creation Time: 2022-05-21 13:15:19 UTC - Algorithm: Ed25519 (EdDSA) - Touch policy: Cached (features: Button) - Key Status: generated - User PIN presentation is valid for unlimited signatures - Signatures made: 11 - -Decryption key - Fingerprint: 338B EE09 3950 D831 A76F 0EB9 13D6 2DF6 8C9E 5176 - Creation Time: 2022-05-21 13:15:19 UTC - Algorithm: Cv25519 (ECDH) - Touch policy: Off (features: Button) - Key Status: generated - -Authentication key - Fingerprint: 4881 A22E 7EC6 26D1 1202 50B0 A7D7 F0D5 0C8D F719 - Creation Time: 2022-05-21 13:15:19 UTC - Algorithm: Ed25519 (EdDSA) - Touch policy: Off (features: Button) - Key Status: generated - -Attestation key: - Algorithm: RSA 2048 [e 17] - Touch policy: Cached (features: Button) - -Remaining PIN attempts: User: 3, Admin: 3, Reset Code: 0 -Key Status (#129): imported -``` - -The `--public-key-material` flag additionally outputs the raw public key data for each key slot. - -### Get an OpenPGP public key representation from a card - -This command returns an OpenPGP public key representation of the keys on a card. - -To bind the decryption and authentication subkeys (if any) to the signing key, the user pin needs to be provided. - -``` -$ opgpcard pubkey -OpenPGP card ABCD:01234567 -Enter User PIN: ------BEGIN PGP PUBLIC KEY BLOCK----- -Comment: F9C7 97CB 1AF2 1C68 AEEC 8D4D 1002 89F5 5EF6 B2D4 -Comment: Alice Adams - -xjMEYkOmahYJKwYBBAHaRw8BAQdADwHIuuSgboyzgcLci8Hc0Q15YHKfDP8/CZG4 -uumYosXNA2JhesLABgQTFgoAeAWCYkjTagWJAAAAAAkQEAKJ9V72stRHFAAAAAAA -HgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnifpLw5yhNlKffk7V+P9g -idnIM3j6l3k34+p7tMQmCPoCmwMWIQT5x5fLGvIcaK7sjU0QAon1Xvay1AAAhJkB -AIEhZTDuc9xARVK8ta51SOpX3mZs/UYA5a+UrB6vpmZ3AP4k14gFQ6q/cl/SOhPR -FpCAvYlqL8rb3gc2sFIZDfYUDM4zBGJDpmoWCSsGAQQB2kcPAQEHQDRodITykZoi -hIIPZcFZ2bMXvo20YEv+I1eg2kFQ2qSqwsAGBBgWCgB4BYJiSNNqBYkAAAAACRAQ -Aon1Xvay1EcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmcI -5rVHhWA5cGdYlyQJYRXv4osAyFlyznFiUOATnoT6LgKbIBYhBPnHl8sa8hxoruyN -TRACifVe9rLUAADpTwD/a+AlBGryfLsqFzIhdJRpGkoOl0H+xcgk3vcaPUQq0pcA -/3TtUmaJ5w60qb/Px7/Q+MTymHH54elRY4lvwIfbvkUIzjgEYkOmahIKKwYBBAGX -VQEFAQEHQO5KBZ7cMwwjsXGOWWMqgAkCyNdw7smcx/+jBEk0m38dAwEKCcLABgQY -FgoAeAWCYkjTagWJAAAAAAkQEAKJ9V72stRHFAAAAAAAHgAgc2FsdEBub3RhdGlv -bnMuc2VxdW9pYS1wZ3Aub3Jn9IwQkbcw9W0jfrduv1q4qNhsOgJWkGTMbVyvQCug -YpcCmwwWIQT5x5fLGvIcaK7sjU0QAon1Xvay1AAAfTwBAPSQq/hGcGjAWNePHoLH -5zA/ePu1vaY1nh2dPhqtUg8+AP0TDG96MJxlM8SJUQXtQsJCAEo4qT9GnGi7MyTU -nvraDw== -=es4l ------END PGP PUBLIC KEY BLOCK----- -``` - -You can query a specific card - -``` -$ opgpcard pubkey --card ABCD:01234567 -``` - -In the process of exporting the key material on a card as a certificate (public key), one or more User IDs can be -bound to the certificate: - -``` -$ opgpcard pubkey --userid "Alice Adams " -``` - - -#### Caution: the exported public key material isn't always what you want - -The result of exporting public key material from a card is only an approximation of the original public key, since -some metadata is not available on OpenPGP cards. This missing metadata includes expiration dates. - -Also, if your card only contains subkeys, but not the original primary key, then the exported certificate will use the -signing subkey from the card as the primary key for the exported certificate. - -One way to safely process this exported public key material from a card is via `sq key adopt`. - -You can use this approach when you have access to your private primary key material (in the following example, we -assume this key is available in `key.pgp`). Then you can bind the public key material from a card to your key: - -``` -opgpcard pubkey > public.key -sq key adopt key.pgp public.pgp -``` - -In that process, you will be able to manually set any relevant flags. - - -### Using a card for ssh auth - -To use an OpenPGP card for ssh login authentication, a PGP authentication key needs to exist on the card. - -`opgpcard ssh` then shows the ssh public key string representation of the PGP authentication -key on the card, like this: - -``` -$ opgpcard ssh -OpenPGP card ABCD:01234567 - -Authentication key fingerprint: -59A5CD3EA88F8707D887EAAE13545F404E11BE1C - -SSH public key: -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII2dcYBqMCamidT5MpE3Cl3MIKcYMBekGXbK2aaN6JaH opgpcard:ABCD:01234567 -``` - -To allow login to a remote machine, that ssh public key can be added to -`.ssh/authorized_keys` on that remote machine. - -In the example output above, this string is the ssh public key: - -`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII2dcYBqMCamidT5MpE3Cl3MIKcYMBekGXbK2aaN6JaH opgpcard:ABCD:01234567` - -### Show OpenPGP card metadata - -Print information about the capabilities of a card, including the list of supported algorithms (if the card returns -that list). - -Most of the output is probably not of interest to regular users. - -``` -$ opgpcard info -OpenPGP card FFFE:12345678 (card version 2.0) - -Application Identifier: D276000124 01 0200 FFFE 12345678 0000 -Manufacturer [FFFE]: Range reserved for randomly assigned serial numbers. - -Card Capabilities: -- command chaining - -Card service data: -- Application Selection by full DF name -- EF.DIR and EF.ATR/INFO access services by the GET DATA command (BER-TLV): 010 - -Extended Capabilities: -- get challenge -- key import -- PW Status changeable -- algorithm attributes changeable -- KDF-DO -- maximum length of challenge: 32 -- maximum length cardholder certificates: 2048 -- maximum command length: 255 -- maximum response length: 256 - -Supported algorithms: -- SIG: RSA 2048 [e 32] -- SIG: RSA 4096 [e 32] -- SIG: Secp256k1 (ECDSA) -- SIG: Ed25519 (EdDSA) -- SIG: Ed448 (EdDSA) -- DEC: RSA 2048 [e 32] -- DEC: RSA 4096 [e 32] -- DEC: Secp256k1 (ECDSA) -- DEC: Cv25519 (ECDH) -- DEC: X448 (ECDH) -- AUT: RSA 2048 [e 32] -- AUT: RSA 4096 [e 32] -- AUT: Secp256k1 (ECDSA) -- AUT: Ed25519 (EdDSA) -- AUT: Ed448 (EdDSA) -``` - -Or to query a specific card: - -``` -$ opgpcard info --card ABCD:01234567 -``` - -### Admin commands - -All `admin` commands need the Admin PIN. It can be provided as a file, with `-P `, -for non-interactive use (see below). - -By default, the PIN must be entered interactively on the host computer, or via a pin pad if the OpenPGP card is -used in a smart card reader that has a pin pad. - -#### Set touch policy - -Cards can require confirmation by the user before cryptographic operations are performed -(this confirmation feature is often implemented as a mechanical button on the card). - -However, not all cards implement this feature. - -Rationale: when a card requires touch confirmation, an attacker who gains control of the user's host computer -cannot perform cryptographic operations on the card at will - even after they learn the user's PINs. - -This feature is configured per key slot. The user can choose to require (or not require) touch confirmation separately -for signing, decryption, authentication and attestation operations. - -E.g., when the touch policy is set to `On` for the `SIG` key slot, then every signing operation requires a touch button -confirmation: - -``` -$ opgpcard admin --card ABCD:01234567 touch --key SIG --policy On -``` - -Valid values for the key slot are: `SIG`, `DEC`, `AUT`, `ATT` (some cards only support the first three). - -Available policies can include: `Off`, `On`, `Fixed`, `Cached`, `CachedFixed`. -Some cards only support a subset of these. - -- `Off` means that no touch confirmation is required. -- `On` means that each operation requires on touch confirmation. -- The `Fixed` policies are like `On`, but the policies cannot be changed without performing a factory reset on the card. -- With the `Cached` policies, a touch confirmation is valid for multiple operations within 15 seconds. - - -#### Set cardholder name - -Set the (informational) cardholder name: - -``` -$ opgpcard admin --card ABCD:01234567 name "Alice Adams" -``` - -#### Set certificate URL - -The URL field on OpenPGP cards is intended to point to the certificate (or "public key") the corresponds to the keys -that are present on the card. - -It can be set like this: - -``` -$ opgpcard admin --card ABCD:01234567 url "https://key.url.example" -``` - -##### Using `keys.openpgp.org` for the URL - -If you have uploaded (or plan to upload) your certificate (your public key) to the `keys.openpgp.org` keyserver, -you can point the URL field on your card there: - -If the fingerprint of your certificate is `0123456789ABCDEF0123456789ABCDEF01234567`, then you can set the URL -as follows: - -``` -$ opgpcard admin --card FFFE:12345678 url "https://keys.openpgp.org/vks/v1/by-fingerprint/0123456789ABCDEF0123456789ABCDEF01234567" -``` - -##### Other common options for certificate URLs - -You can use any URL that serves your certificate ("public key"), including links to: - -- gitlab (`https://gitlab.com/.gpg`) or github (`https://github.com/.gpg`) -- any other keyserver, such as https://keyserver.ubuntu.com/, -- a WKD server, -- a copy of your certificate on your personal website, ... - - -#### Import keys - -Import private key onto a card. This works if at most one (sub)key per role -(sign, decrypt, auth) exists in `key.priv`: - -``` -$ opgpcard admin --card ABCD:01234567 import key.priv -``` - -Import private key onto a card while explicitly selecting subkeys. Explicitly -specified fingerprints are necessary if more than one subkey exists -in `key.priv` for any role (spaces in fingerprints are ignored). - -``` -$ opgpcard admin --card ABCD:01234567 -P import key.priv \ - --sig-fp "F290 DBBF 21DB 8634 3C96 157B 87BE 15B7 F548 D97C" \ - --dec-fp "3C6E 08F6 7613 8935 8B8D 7666 73C7 F1A9 EEDA C360" \ - --auth-fp "D6AA 48EF 39A2 6F26 C42D 5BCB AAD2 14D5 5332 C838" -``` - -When fingerprints are only specified for a subset of the roles, no keys will -be imported for the other roles. - -If the private (sub)keys in the import file are password protected, the user will be prompted to enter -the password. If (sub)keys are encrypted with different passwords, the user will be prompted multiple times. -(Background: OpenPGP keys can be password protected when they are stored in files, but on an OpenPGP card -the keys always exist in unencrypted form. Therefore, they need to be decrypted for import.) - -(NOTE: There is currently no mechanism to non-interactively provide passwords to import password protected -OpenPGP keys) - -#### Generate Keys on the card - -This command generates new keys on an OpenPGP card. It creates the corresponding certificate ("public key") -representation in an output file. - -``` -$ opgpcard admin --card ABCD:01234567 generate --output cv25519 -``` - -Note that key generation needs both the Admin PIN and the User PIN (the User PIN is needed to export the new key as -a public key). - -Output will look like: - -``` -Enter Admin PIN: -Enter User PIN: - Generate subkey for Signing - Generate subkey for Decryption - Generate subkey for Authentication -``` - -The `` will contain the corresponding certificate ("public key"). - -As part of the process of generating key material on a card, one or more User IDs can be included with the exported -certificate: - -``` -$ opgpcard admin --card ABCD:01234567 generate --userid "Alice Adams " --output cv25519 -``` - - -### Signing - -For now, this tool only supports creating detached signatures, like this -(if no input file is set, stdin is read): - -``` -$ opgpcard sign --detached --card ABCD:01234567 -``` - -### Decrypting - -Decryption using a card (if no input file is set, stdin is read): - -``` -$ opgpcard decrypt --card ABCD:01234567 -``` - -### PIN management - -OpenPGP cards use PINs (numerical passwords) to verify that a user is allowed to perform an operation. - -To use cryptographic operations on a card (such as decryption or signing), the *User PIN* is required. - -To configure a card (for example to import OpenPGP key material into the card's key slots), the *Admin PIN* is needed. - -By default, on unconfigured (or factory reset) cards, the User PIN is typically set to `123456`, -and the Admin PIN is set to `12345678`. - -#### Blocked cards and resetting - -When a user has entered a wrong User PIN too often, the card goes into a blocked state, in which presenting the -User PIN successfully is not possible anymore. The purpose of this is to prevent attackers from trying all possible -PINs (e.g. after stealing a card). - -To be able to use the card again, the User PIN must be "reset". - -A User PIN reset can be performed by presenting the Admin PIN. - -#### The resetting code - -OpenPGP cards offer an additional, optional, *Resetting Code* mechanism. - -The resetting code may be configured on a card and used to reset the User PIN if it has been forgotten or blocked. -When unblocking a card with the Resetting Code, the Admin PIN is not needed. - -The Resetting Code mechanism is only useful in scenarios where a user doesn't have access to (or prefers not to use) -the Admin PIN (e.g. in some corporate settings, users might not be given the Admin PIN for -their cards. Instead, an admin may define a resetting code and give that code to the user). - -On un-configured (or factory reset) cards, the Resetting Code is typically unset. - - -#### Set a new User PIN - -Setting a new User PIN requires the Admin PIN: - -``` -$ opgpcard pin --card ABCD:01234567 set-user -``` - -#### Set new Admin PIN - -This requires the (previous) Admin PIN. - -``` -$ opgpcard pin --card ABCD:01234567 set-admin -``` - -#### Reset User PIN with Admin PIN - -The User PIN can be reset to a different (or the same) PIN by providing the Admin PIN. -This is possible at any time, including when a wrong User PIN has been entered too often, -and the card refuses to accept the User PIN anymore. - -``` -$ opgpcard pin --card ABCD:01234567 reset-user -``` - -#### Configuring the resetting code - -The resetting code is an alternative mechanism to recover from a lost or locked User PIN. - -You can set the resetting code after verifying the Admin PIN. Once a resetting code is configured on your card, -you can use that code to reset the User PIN without needing the Admin PIN. - -``` -$ opgpcard pin --card ABCD:01234567 set-reset -``` - -#### Reset User PIN with the resetting code - -If a resetting code is configured on a card, you can use that code to reset the User PIN: - -``` -$ opgpcard pin --card ABCD:01234567 reset-user-rc -Enter resetting code: -Enter new User PIN: -Repeat the new User PIN: - -User PIN has been set. -``` - -### Factory reset - -A factory reset erases all data on your card, including the private key material that the card stores. - -``` -$ opgpcard factory-reset --card ABCD:01234567 -``` - -NOTE: you do not need a PIN to reset a card! - -### Directly entering PINs on card readers with pin pad - -If your OpenPGP card is inserted in a card reader with a pin pad, this tool -offers you the option to use the pin pad to enter the User- or Admin PINs. -To do this, you can omit the `-p` and/or `-P` parameters. Then you will -be prompted to enter the user or Admin PINs where needed. - - -### Machine-readable Output (JSON, YAML) - -This tool is can optionally provide its output in JSON (or YAML) format. -The functionality is intended for scripting use. - -For all commands that return relevant output, the parameter `--output-format json` chooses JSON as the output format. - -For example, with the `status` command: - -``` -$ opgpcard --output-format json status -{ - "schema_version": "0.9.0", - "ident": "ABCD:01234567", - "card_version": "3.4", - "cardholder_name": "Alice Adams", - "language_preferences": [], - "certificate_url": "http://alice.example/alice.pgp", - "signature_key": { - "fingerprint": "A393 4505 BC51 1177 2E0B 845A 142C C9AB 7126 5C00", - "creation_time": "2022-10-31 13:45:35 UTC", - "algorithm": "Ed25519 (EdDSA)", - "touch_policy": "Off", - "touch_features": "Button", - "status": "generated", - "public_key_material": "ECC [Ed25519 (EdDSA)], data: 3A2B88EF788FA59575E3C4DB89EE367DBD0D9E93B6CE26B7686D32E94958F32A" - }, - "signature_count": 3, - "user_pin_valid_for_only_one_signature": false, - "decryption_key": { - "fingerprint": "0643 F2A9 6605 4158 CCFA B11F C7D2 0DBA DA64 84E0", - "creation_time": "2022-10-31 13:45:35 UTC", - "algorithm": "Cv25519 (ECDH)", - "touch_policy": "Off", - "touch_features": "Button", - "status": "generated", - "public_key_material": "ECC [Cv25519 (ECDH)], data: AF97CA49B2D89998605985AEDAA19097A0CE7E5CC681B1ABD1C8610933FDB320" - }, - "authentication_key": { - "fingerprint": "2BA3 3B42 90DE 337D 1DF8 54B3 2E20 E550 3ABC 57A9", - "creation_time": "2022-10-31 13:45:35 UTC", - "algorithm": "Ed25519 (EdDSA)", - "touch_policy": "Off", - "touch_features": "Button", - "status": "generated", - "public_key_material": "ECC [Ed25519 (EdDSA)], data: 80178ECE7F16ACDFDB0A645C81E72287761F03488CE3AE01F74279AA88A9018C" - }, - "attestation_key": { - "fingerprint": null, - "creation_time": null, - "algorithm": "RSA 2048 [e 17]", - "touch_policy": "Off", - "touch_features": "Button", - "status": null, - "public_key_material": null - }, - "user_pin_remaining_attempts": 3, - "admin_pin_remaining_attempts": 3, - "reset_code_remaining_attempts": 0 -} -``` - - -### Non-interactive use - -All commands that require PIN entry can be used non-interactively by providing PINs via files -(see the section "Using file-descriptors to provide PINs" for a variation on this). - -In almost all contexts, `-p` is used to provide the User PIN and `-P` to provide the Admin PIN -(the exception is when changing a PIN on the card, then a different parameter is used to provide the new PIN). - -**Examples of non-interactive use** - -- Setting the cardholder name: - -`$ opgpcard admin --card ABCD:01234567 -P name "Alice Adams"` - -- Importing a key to the card: - -`$ opgpcard admin --card ABCD:01234567 -P import key.priv` - -- Generating key material on the card: - -`$ opgpcard admin --card ABCD:01234567 -P generate -p --output cv25519` - -- Creating a detached signature: - -`$ opgpcard sign --detached --card ABCD:01234567 -p ` - -**Examples of non-interactive PIN management** - -- Setting a new User PIN: - -`$ opgpcard pin --card ABCD:01234567 set-user -p -q ` - -- Setting a new Admin PIN: - -`$ opgpcard pin --card ABCD:01234567 set-admin -P -Q ` - -- Setting a new User PIN based on the Admin PIN (and unblocking the card, if needed): - -`$ opgpcard pin --card ABCD:01234567 reset-user -P -p ` - -- Setting the resetting code: - -`$ opgpcard pin --card ABCD:01234567 set-reset -P -r ` - -- Setting a new User ID based on the resetting code (and unblocking the card, if needed): - -`$ opgpcard pin --card ABCD:01234567 reset-user-rc -r -p ` - -#### Using file-descriptors to provide PINs - -When using a shell like -[bash](https://www.gnu.org/software/bash/manual/html_node/Redirections.html#Here-Strings) -, you can pass User- and/or Admin PINs via file-descriptors (instead of from a file on disk): - -``` -$ opgpcard sign --detached --card ABCD:01234567 -p /dev/fd/3 3<<<123456 -``` - -``` -$ opgpcard admin --card ABCD:01234567 -P /dev/fd/3 generate -p /dev/fd/4 --output cv25519 3<<<12345678 4<<<123456 -``` - -### Attestation - -Yubico implements a [proprietary extension](https://developers.yubico.com/PGP/Attestation.html) to the OpenPGP card -standard to *"cryptographically certify that a certain asymmetric key has been generated on device, and not imported"*. - -This feature is available on YubiKey 5 devices with firmware version 5.2 or newer. - -#### Attestation key/certificate - -*"The YubiKey is preloaded with an attestation certificate and matching attestation key issued by the Yubico CA. -The template and key are replaceable, which permits an individual or organization to issue attestations verifiable -with their own CA if they prefer. If replaced, the Yubico template can never be restored."* - -This tool does not currently support replacing the attestation key on a YubiKey. -It only supports use of the Yubico-provided attestation key to generate "attestation statements". - -The attestation certificate on a card can be inspected as follows: - -``` -$ opgpcard attestation cert ------BEGIN CERTIFICATE----- -[...] ------END CERTIFICATE----- -``` - -#### Generating an attestation statement - -For any key slot on the card you can generate an attestation statement, -if the key material in that key slot has been generated on the card. - -It's not possible to generate attestation statements for key material that was imported to the card -(the attestation statement certifies that the key has been generated on the card). - -To generate an attestation statement, run: - -``` -$ opgpcard attestation generate --key SIG --card 0006:01234567 -``` - -Supported values for `--key` are `SIG`, `DEC` and `AUT`. - -Generation of an attestation requires the User PIN. By default, it also requires touch confirmation -(the touch policy configuration for the attestation key slot is set to `On` by default). - -#### Viewing an attestation statement - -When the YubiKey generates an attestation statement, it gets stored in a `cardholder certificate` data object on the card. - -After an attestation statement has been generated, it can be read from the card and viewed in pem-encoded format: - -``` -$ opgpcard attestation statement --key SIG ------BEGIN CERTIFICATE----- -[...] ------END CERTIFICATE----- -``` - -Supported values for `--key` are `SIG`, `DEC` and `AUT`. diff --git a/tools/build.rs b/tools/build.rs deleted file mode 100644 index 5185c03..0000000 --- a/tools/build.rs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lars Wirzenius -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use std::fs::File; -use std::path::Path; - -fn main() { - // Only generate test code from the subplot, if a virtual smart - // card is available. This is a kludge until Subplot can do - // conditional scenarios - // (https://gitlab.com/subplot/subplot/-/issues/20). - match option_env!("CARD_BASED_TESTS") { - Some(_) => { - subplot_build::codegen("subplot/opgpcard.subplot") - .expect("failed to generate code with Subplot"); - println!("cargo:warning=generated subplot tests"); - } - None => { - // If we're not generating code from the subplot, we should at - // least create an empty file so that the tests/opgpcard.rs - // file can include it. Otherwise the build will fail. - println!("cargo:warning=did not generate subplot tests"); - let out_dir = std::env::var("OUT_DIR").unwrap(); - let include = Path::new(&out_dir).join("opgpcard.rs"); - eprintln!("build.rs: include={}", include.display()); - if !include.exists() { - File::create(include).unwrap(); - } - } - } -} diff --git a/tools/cargo-test-in-docker b/tools/cargo-test-in-docker deleted file mode 100755 index b369ac8..0000000 --- a/tools/cargo-test-in-docker +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -# -# Run this to run opgpcard (tools directory) test suite inside a -# Docker container with a virtual smartcard running. The test suite -# contains tests that run the opgpcard binary and rely on a virtual -# smart card to be available. -# -# SPDX-FileCopyrightText: 2022 Lars Wirzenius -# SPDX-License-Identifier: MIT OR Apache-2.0 - -set -euo pipefail - -docker run --rm -it \ - -v root:/root \ - -v cargo:/cargo \ - -v $(pwd):/src \ - -e CARD_BASED_TESTS=true \ - registry.gitlab.com/openpgp-card/virtual-cards/smartpgp-builddeps sh -c ' -apt install sq && -sed -i "s/timeout=20/timeout=60/" /home/jcardsim/run-card.sh && -/etc/init.d/pcscd start && -su - -c "sh /home/jcardsim/run-card.sh >/dev/null" jcardsim && -cd /src/tools && -CARGO_TARGET_DIR=/cargo/ cargo update && -CARGO_TARGET_DIR=/cargo/ cargo build -vv && -CARGO_TARGET_DIR=/cargo/ cargo test -- --test-threads 1' diff --git a/tools/debian/build-deb b/tools/debian/build-deb deleted file mode 100755 index 11f8b1f..0000000 --- a/tools/debian/build-deb +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -S="$(dpkg-parsechangelog -SSource)" -V="$(dpkg-parsechangelog -SVersion | sed 's/-[^-]*$//')" - -git archive HEAD | gzip >"../${S}_${V}.orig.tar.gz" -dpkg-buildpackage -us -uc diff --git a/tools/debian/cargo-checksum.json b/tools/debian/cargo-checksum.json deleted file mode 100644 index e69de29..0000000 diff --git a/tools/debian/changelog b/tools/debian/changelog deleted file mode 100644 index 29b0f5e..0000000 --- a/tools/debian/changelog +++ /dev/null @@ -1,6 +0,0 @@ -openpgp-card-tool (0.0.11-1) unstable; urgency=medium - - * Initial packaging. This is not intended to be uploaded to Debian, so - not closing of an ITP bug. - - -- Lars Wirzenius Thu, 30 Sep 2021 09:51:32 +0300 diff --git a/tools/debian/compat b/tools/debian/compat deleted file mode 100644 index 021ea30..0000000 --- a/tools/debian/compat +++ /dev/null @@ -1,2 +0,0 @@ -10 - diff --git a/tools/debian/control b/tools/debian/control deleted file mode 100644 index e2c36a2..0000000 --- a/tools/debian/control +++ /dev/null @@ -1,23 +0,0 @@ -Source: openpgp-card-tool -Maintainer: Heiko Schaefer -Uploaders: Lars Wirzenius -Section: admin -Priority: optional -Standards-Version: 4.2.0 -Build-Depends: - debhelper (>= 10~), - dh-cargo, - libclang-dev, - libpcsclite-dev, - nettle-dev, - pkg-config, -Homepage: https://gitlab.com/openpgp-card/openpgp-card - -Package: openpgp-card-tool -Architecture: any -Depends: ${misc:Depends}, ${shlibs:Depends} -Built-Using: ${cargo:Built-Using} -Description: tool to manage OpenPGP hardware tokens - The opgpcard tool allows you to inspect, configure, administer, - factory reset, and generally manage OpenPGP cards (hardware tokens), - such as Gnuk, YubiKeys, Nitrokeys, and similar. diff --git a/tools/debian/copyright b/tools/debian/copyright deleted file mode 100644 index 40a5b06..0000000 --- a/tools/debian/copyright +++ /dev/null @@ -1,3 +0,0 @@ -Copyright 2021-2022 Heiko Schaefer - -# SPDX-License-Identifier: MIT OR Apache-2.0 diff --git a/tools/debian/docs b/tools/debian/docs deleted file mode 100644 index b43bf86..0000000 --- a/tools/debian/docs +++ /dev/null @@ -1 +0,0 @@ -README.md diff --git a/tools/debian/rules b/tools/debian/rules deleted file mode 100755 index bb6dbe1..0000000 --- a/tools/debian/rules +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/make -f - -%: - dh $@ --buildsystem cargo - -override_dh_auto_clean: - echo auto clean - -override_dh_auto_configure: - echo auto configure - -override_dh_auto_build: - cargo --version - rustc --version - cargo build --release - -override_dh_auto_test: - true - -override_dh_auto_install: - install -d debian/openpgp-card-tool/bin - cargo install --locked --path=. --root=debian/openpgp-card-tool - find debian -name ".crates*" -delete diff --git a/tools/debian/source/format b/tools/debian/source/format deleted file mode 100644 index 163aaf8..0000000 --- a/tools/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/tools/prepare-card.py b/tools/prepare-card.py deleted file mode 100755 index ee153e9..0000000 --- a/tools/prepare-card.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/python3 -# -# WARNING: This will wipe any information on a card. Do not use it unless -# you're very sure you don't mind. -# -# Prepare an OpenPGP card for use within a hypothetical organization: -# -# - factory reset the card -# - set card holder name, if desired -# - generate elliptic curve 25519 keys -# - write to stdout a JSON object with the card id, card holder, and -# key fingerprints -# -# Usage: run with --help. -# -# SPDX-FileCopyrightText: 2022 Lars Wirzenius -# SPDX-License-Identifier: MIT OR Apache-2.0 - - -import argparse -import json -import sys -from subprocess import run - - -tracing = False - - -def trace(msg): - if tracing: - sys.stderr.write(f"DEBUG: {msg}\n") - sys.stderr.flush() - - -def opgpcard_raw(args): - argv = ["opgpcard"] + args - trace(f"running {argv}") - p = run(argv, capture_output=True) - if p.returncode != 0: - raise Exception(f"opgpcard failed:\n{p.stderr}") - o = p.stdout - trace(f"opgpcard raw output: {o!r}") - return o - - -def opgpcard_json(args): - o = json.loads(opgpcard_raw(["--output-format=json"] + args)) - trace(f"opgpcard JSON output: {o}") - return o - - -def list_cards(): - return opgpcard_json(["list"])["idents"] - - -def pick_card(card): - cards = list_cards() - if card is None: - if not cards: - raise Exception("No cards found") - if len(cards) > 1: - raise Exception(f"Can't pick card automatically: found {len(cards)} cards") - return cards[0] - elif card in cards: - return card - else: - raise Exception(f"Can't find specified card {card}") - - -def factory_reset(card): - opgpcard_raw(["factory-reset", "--card", card]) - - -def set_card_holder(card, admin_pin, name): - trace(f"set card holder to {name!r}") - opgpcard_raw(["admin", "--card", card, "--admin-pin", admin_pin, "name", name]) - - -def generate_key(card, admin_pin, user_pin): - opgpcard_raw( - [ - "admin", - f"--card={card}", - f"--admin-pin={admin_pin}", - "generate", - f"--user-pin={user_pin}", - "--output=/dev/null", - "cv25519", - ] - ) - - -def status(card): - o = opgpcard_json(["status", f"--card={card}"]) - return { - "card_ident": o["ident"], - "cardholder_name": o["cardholder_name"], - "signature_key": o["signature_key"]["fingerprint"], - "decryption_key": o["signature_key"]["fingerprint"], - "authentication_key": o["signature_key"]["fingerprint"], - } - - -def card_is_empty(card): - o = status(card) - del o["card_ident"] - for key in o: - if o[key]: - return False - return True - - -def main(): - p = argparse.ArgumentParser() - p.add_argument("--force", action="store_true", help="prepare a card that has data") - p.add_argument( - "--verbose", action="store_true", help="produce debugging output to stderr" - ) - p.add_argument("--card", help="card identifier, default is to pick the only one") - p.add_argument("--card-holder", help="name of card holder", required=True) - p.add_argument( - "--admin-pin", action="store", help="set file with admin PIN", required=True - ) - p.add_argument( - "--user-pin", action="store", help="set file with user PIN", required=True - ) - args = p.parse_args() - - if args.verbose: - global tracing - tracing = True - - trace(f"args: {args}") - card = pick_card(args.card) - if not args.force and not card_is_empty(card): - raise Exception(f"card {card} has existing keys, not touching it") - factory_reset(card) - set_card_holder(card, args.admin_pin, args.card_holder) - key = generate_key(card, args.admin_pin, args.user_pin) - o = status(card) - print(json.dumps(o, indent=4)) - - -if __name__ == "__main__": - try: - main() - except Exception as e: - sys.stderr.write(f"ERROR: {e}\n") - sys.exit(1) diff --git a/tools/scripting.md b/tools/scripting.md deleted file mode 100644 index 8900a4e..0000000 --- a/tools/scripting.md +++ /dev/null @@ -1,61 +0,0 @@ - - -# Scripting around opgpcard - -The `opgpcard` tool can manipulate an OpenPGP smart card (also known -as hardware token). There are various commercial as well as Free Software-implementations of the standard. -Well known commercial products with OpenPGP card support include YubiKey and Nitrokey. This tool is meant to work with -any card that implements the OpenPGP smart card interface. - -`opgpcard` supports structured output as JSON and YAML. The default is -human-readable text. The structured output it meant to be consumed by -other programs, and is versioned. - -For example, to list all the OpenPGP cards connected to a system: - -~~~sh -$ opgpcard --output-format=json list -{ - "schema_version": "1.0.0", - "idents": [ - "ABCD:01234567" - ] -} -$ -~~~ - -The structured output is versioned (text output is not), using the -field name `schema_version`. The version numbering follows [semantic -versioning](https://semver.org/): - -* if a field is added, the minor level is incremented, and patch level - is set to zero -* if a field is removed, the major level is incremented, and minor and - patch level are set to zero -* if there are changed with no semantic impatc, the patch level is - incremented - -Each version of `opgpcard` supports only the latest minor version for -each major version. Consumers of the output have to be OK with added -fields. - -Thus, for example, if the `opgpcard list` command would add a new -field for when the command was run, the output might look like this: - -~~~sh -$ opgpcard --output-format=json list -{ - "schema_version": "1.1.0", - "date": "Tue, 18 Oct 2022 18:07:41 +0300", - "idents": [ - "ABCD:01234567" - ] -} -$ -~~~ - -A new field means the minor level in the schema version is -incremented. diff --git a/tools/src/cli.rs b/tools/src/cli.rs deleted file mode 100644 index 01e6221..0000000 --- a/tools/src/cli.rs +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use clap::{AppSettings, Parser}; - -use crate::commands; -use crate::{OutputFormat, OutputVersion}; - -pub const OUTPUT_VERSIONS: &[OutputVersion] = &[OutputVersion::new(0, 9, 0)]; -pub const DEFAULT_OUTPUT_VERSION: OutputVersion = OutputVersion::new(0, 9, 0); - -#[derive(Parser, Debug)] -#[clap( - name = "opgpcard", - author = "Heiko Schäfer ", - version, - global_setting(AppSettings::DeriveDisplayOrder), - about = "A tool for inspecting and configuring OpenPGP cards." -)] -pub struct Cli { - /// Produce output in the chosen format. - #[clap(long, value_enum, default_value_t = OutputFormat::Text)] - pub output_format: OutputFormat, - - /// Pick output version to use, for non-textual formats. - #[clap(long, default_value_t = DEFAULT_OUTPUT_VERSION)] - pub output_version: OutputVersion, - - #[clap(subcommand)] - pub cmd: Command, -} - -#[derive(Parser, Debug)] -pub enum Command { - /// Enumerate available OpenPGP cards - List {}, - - /// Show information about the data on a card - Status(commands::status::StatusCommand), - - /// Show technical details about a card - Info(commands::info::InfoCommand), - - /// Show a card's authentication key as an SSH public key - Ssh(commands::ssh::SshCommand), - - /// Export the key data on a card as an OpenPGP public key - Pubkey(commands::pubkey::PubkeyCommand), - - /// Administer data on a card (including keys and metadata) - Admin(commands::admin::AdminCommand), - - /// PIN management (change PINs, reset blocked PINs) - #[clap( - long_about = indoc::indoc! { " - PIN management (change PINs, reset blocked PINs) - - OpenPGP cards use PINs (numerical passwords) to verify that a user is allowed to \ - perform an operation. There are two PINs for regular operation, User PIN and Admin \ - PIN, and one optional Resetting Code. - - The User PIN is required to use cryptographic operations on a card (such as \ - decryption or signing). - The Admin PIN is needed to configure a card (for example to import an OpenPGP key \ - into the card) or to unblock the User PIN. - The Resetting Code only allows unblocking the User PIN. This is useful if the user \ - doesn't have access to the Admin PIN. - - By default, on unconfigured (or factory reset) cards, the User PIN is typically set to - 123456, and the Admin PIN is set to 12345678." - }, - )] - Pin(commands::pin::PinCommand), - - /// Decrypt data using a card - Decrypt(commands::decrypt::DecryptCommand), - - /// Sign data using a card - /// - /// Currently, only detached signatures are supported. - Sign(commands::sign::SignCommand), - - /// Attestation management (Yubico only) - /// - /// Yubico implements a proprietary extension to the OpenPGP card standard to - /// cryptographically certify that a certain asymmetric key has been generated on device, and - /// not imported. - /// - /// This feature is available on YubiKey 5 devices with firmware version 5.2 or newer. - Attestation(commands::attestation::AttestationCommand), - - /// Completely reset a card (deletes all data including keys!) - FactoryReset(commands::factory_reset::FactoryResetCommand), - - /// Change identity (Nitrokey Start only) - /// - /// A Nitrokey Start device contains three distinct virtual OpenPGP cards, select the identity - /// of the virtual card to activate. - SetIdentity(commands::set_identity::SetIdentityCommand), - - /// Show supported output format versions - #[clap( - long_about = indoc::indoc! { " - Show supported output format versions for JSON and YAML output. - - Mark the currently chosen one with a star." - } - )] - OutputVersions {}, -} diff --git a/tools/src/commands/admin.rs b/tools/src/commands/admin.rs deleted file mode 100644 index 8fb686d..0000000 --- a/tools/src/commands/admin.rs +++ /dev/null @@ -1,568 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Lars Wirzenius -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use std::path::PathBuf; - -use anyhow::{anyhow, Result}; -use clap::{Parser, ValueEnum}; -use openpgp_card_sequoia::state::{Admin, Open, Transaction}; -use openpgp_card_sequoia::types::AlgoSimple; -use openpgp_card_sequoia::util::public_key_material_to_key; -use openpgp_card_sequoia::{sq_util, PublicKey}; -use openpgp_card_sequoia::{types::KeyType, Card}; -use sequoia_openpgp::cert::prelude::ValidErasedKeyAmalgamation; -use sequoia_openpgp::packet::key::{SecretParts, UnspecifiedRole}; -use sequoia_openpgp::packet::Key; -use sequoia_openpgp::parse::Parse; -use sequoia_openpgp::policy::Policy; -use sequoia_openpgp::policy::StandardPolicy; -use sequoia_openpgp::serialize::SerializeInto; -use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm}; -use sequoia_openpgp::Cert; - -use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; -use crate::{output, util, ENTER_ADMIN_PIN, ENTER_USER_PIN}; - -#[derive(Parser, Debug)] -pub struct AdminCommand { - #[clap( - name = "card ident", - short = 'c', - long = "card", - help = "Identifier of the card to use" - )] - pub ident: String, - - #[clap( - name = "Admin PIN file", - short = 'P', - long = "admin-pin", - help = "Optionally, get Admin PIN from a file" - )] - pub admin_pin: Option, - - #[clap(subcommand)] - pub cmd: AdminSubCommand, -} - -#[derive(Parser, Debug)] -pub enum AdminSubCommand { - /// Set cardholder name - Name { - #[clap(help = "cardholder name to set on the card")] - name: String, - }, - - /// Set certificate URL - Url { - #[clap(help = "URL that provides the certificate for the key material on this card")] - url: String, - }, - - /// Import a Key onto the card. - /// - /// Most keys can be imported without specifying subkey fingerprints. However, if the key - /// contins more than one signing, decryption or authentication capable subkey, subkeys must be - /// explicitly selected. - /// - /// If any of the options is given, only the selected subkeys are imported into the selected - /// slots. - /// - /// Subkey capabilities must match the slot the key is imported into. The DEC slot can - /// only be used for encryption capable subkeys. The SIG and AUT slots can be used for signing, - /// certification and authentication capable subkeys. - Import { - #[clap(help = "File that contains the PGP private key")] - keyfile: PathBuf, - - /// Optionally, select the subkey to import in the SIG slot - #[clap(name = "SIG subkey fingerprint", short = 's', long = "sig-fp")] - sig_fp: Option, - - /// Optionally, select the subkey to import in the DEC slot - #[clap(name = "DEC subkey fingerprint", short = 'd', long = "dec-fp")] - dec_fp: Option, - - /// Optionally, select the subkey to import in the AUT slot - #[clap(name = "AUT subkey fingerprint", short = 'a', long = "aut-fp")] - aut_fp: Option, - }, - - /// Generate a Key on the card. - /// - /// A signing key is always created, decryption and authentication keys - /// are optional. - Generate(AdminGenerateCommand), - - /// Set the card's touch policy (if supported) - /// - /// A touch policy defines if cryptographic operations on the card require user interaction - /// with the card, for example by touching a button on the card. - /// - /// Only some cards support this feature at all, not all cards support all policies. - /// - /// Caution: Setting the ATT slot to Fixed or Cached-Fixed is permanent. Even a factory reset does - /// not undo this setting. - Touch { - /// Key slot to set the touch policy for - #[clap(name = "Key slot", short = 'k', long = "key", value_enum)] - key: BasePlusAttKeySlot, - - /// Touch policy to set on this key slot - #[clap( - name = "Policy", - short = 'p', - long = "policy", - value_enum, - long_help = "Touch policy to set on this key slot - -Off: No touch confirmation required. -On: Touch confirmation required for each operation. -Fixed: Like 'On', but the policy can only be changed by a reset. -Cached: Like 'On', but touch confirmation is valid for 15 seconds. -Cached-Fixed: Combines 'Cached' and 'Fixed'." - )] - policy: TouchPolicy, - }, -} - -#[derive(Parser, Debug)] -pub struct AdminGenerateCommand { - /// Output file - #[clap(name = "output", long = "output", short = 'o')] - output_file: PathBuf, - - /// Do not create a key in the DEC slot - #[clap(long = "no-dec", action = clap::ArgAction::SetFalse)] - decrypt: bool, - - /// Do not create a key in the AUT slot - #[clap(long = "no-aut", action = clap::ArgAction::SetFalse)] - auth: bool, - - /// Choose the algorithm for the key material to generate on the card. - /// - /// If the parameter is not given, use the algorithm currently set on the card. - /// - /// Specific cards support a set of algorithms that can differ between models. On modern cards, - /// use 'opgpcard info' to see the list of supported algorithms. - #[clap(name = "algorithm", value_enum)] - algo: Option, - - /// User ID to add to the exported certificate representation - #[clap(name = "User ID", short = 'u', long = "userid")] - user_ids: Vec, - - #[clap( - name = "User PIN file", - short = 'p', - long = "user-pin", - help = "Optionally, get User PIN from a file" - )] - user_pin: Option, -} - -#[derive(ValueEnum, Debug, Clone)] -#[clap(rename_all = "UPPER")] -pub enum BasePlusAttKeySlot { - Sig, - Dec, - Aut, - Att, -} - -impl From for KeyType { - fn from(ks: BasePlusAttKeySlot) -> Self { - match ks { - BasePlusAttKeySlot::Sig => KeyType::Signing, - BasePlusAttKeySlot::Dec => KeyType::Decryption, - BasePlusAttKeySlot::Aut => KeyType::Authentication, - BasePlusAttKeySlot::Att => KeyType::Attestation, - } - } -} - -#[derive(ValueEnum, Debug, Clone)] -pub enum TouchPolicy { - #[clap(name = "Off")] - Off, - #[clap(name = "On")] - On, - #[clap(name = "Fixed")] - Fixed, - #[clap(name = "Cached")] - Cached, - #[clap(name = "Cached-Fixed")] - CachedFixed, -} - -impl From for openpgp_card_sequoia::types::TouchPolicy { - fn from(tp: TouchPolicy) -> Self { - use openpgp_card_sequoia::types::TouchPolicy as OCTouchPolicy; - match tp { - TouchPolicy::On => OCTouchPolicy::On, - TouchPolicy::Off => OCTouchPolicy::Off, - TouchPolicy::Fixed => OCTouchPolicy::Fixed, - TouchPolicy::Cached => OCTouchPolicy::Cached, - TouchPolicy::CachedFixed => OCTouchPolicy::CachedFixed, - } - } -} - -#[derive(ValueEnum, Debug, Clone)] -#[clap(rename_all = "lower")] -pub enum Algo { - Rsa2048, - Rsa3072, - Rsa4096, - Nistp256, - Nistp384, - Nistp521, - Cv25519, -} - -impl From for AlgoSimple { - fn from(a: Algo) -> Self { - match a { - Algo::Rsa2048 => AlgoSimple::RSA2k, - Algo::Rsa3072 => AlgoSimple::RSA3k, - Algo::Rsa4096 => AlgoSimple::RSA4k, - Algo::Nistp256 => AlgoSimple::NIST256, - Algo::Nistp384 => AlgoSimple::NIST384, - Algo::Nistp521 => AlgoSimple::NIST521, - Algo::Cv25519 => AlgoSimple::Curve25519, - } - } -} - -pub fn admin( - output_format: OutputFormat, - output_version: OutputVersion, - command: AdminCommand, -) -> Result<(), Box> { - let backend = util::open_card(&command.ident)?; - let mut open: Card = backend.into(); - let mut card = open.transaction()?; - - let admin_pin = util::get_pin(&mut card, command.admin_pin, ENTER_ADMIN_PIN)?; - - match command.cmd { - AdminSubCommand::Name { name } => { - name_command(&name, card, admin_pin.as_deref())?; - } - AdminSubCommand::Url { url } => { - url_command(&url, card, admin_pin.as_deref())?; - } - AdminSubCommand::Import { - keyfile, - sig_fp, - dec_fp, - aut_fp, - } => { - import_command(keyfile, sig_fp, dec_fp, aut_fp, card, admin_pin.as_deref())?; - } - AdminSubCommand::Generate(cmd) => { - generate_command( - output_format, - output_version, - card, - admin_pin.as_deref(), - cmd, - )?; - } - AdminSubCommand::Touch { key, policy } => { - touch_command(card, admin_pin.as_deref(), key, policy)?; - } - } - Ok(()) -} - -fn keys_pick_yolo<'a>( - key: &'a Cert, - policy: &'a dyn Policy, -) -> Result<[Option>; 3]> { - let key_by_type = |kt| sq_util::subkey_by_type(key, policy, kt); - - Ok([ - key_by_type(KeyType::Signing)?, - key_by_type(KeyType::Decryption)?, - key_by_type(KeyType::Authentication)?, - ]) -} - -fn keys_pick_explicit<'a>( - key: &'a Cert, - policy: &'a dyn Policy, - sig_fp: Option, - dec_fp: Option, - aut_fp: Option, -) -> Result<[Option>; 3]> { - let key_by_fp = |fp: Option| match fp { - Some(fp) => sq_util::private_subkey_by_fingerprint(key, policy, &fp), - None => Ok(None), - }; - - Ok([key_by_fp(sig_fp)?, key_by_fp(dec_fp)?, key_by_fp(aut_fp)?]) -} - -fn gen_subkeys( - admin: &mut Card, - decrypt: bool, - auth: bool, - algo: Option, -) -> Result<(PublicKey, Option, Option)> { - // We begin by generating the signing subkey, which is mandatory. - println!(" Generate subkey for Signing"); - let (pkm, ts) = admin.generate_key_simple(KeyType::Signing, algo)?; - let key_sig = public_key_material_to_key(&pkm, KeyType::Signing, &ts, None, None)?; - - // make decryption subkey (unless disabled), with the same algorithm as - // the sig key - let key_dec = if decrypt { - println!(" Generate subkey for Decryption"); - let (pkm, ts) = admin.generate_key_simple(KeyType::Decryption, algo)?; - Some(public_key_material_to_key( - &pkm, - KeyType::Decryption, - &ts, - Some(HashAlgorithm::SHA256), // FIXME - Some(SymmetricAlgorithm::AES128), // FIXME - )?) - } else { - None - }; - - // make authentication subkey (unless disabled), with the same - // algorithm as the sig key - let key_aut = if auth { - println!(" Generate subkey for Authentication"); - let (pkm, ts) = admin.generate_key_simple(KeyType::Authentication, algo)?; - - Some(public_key_material_to_key( - &pkm, - KeyType::Authentication, - &ts, - None, - None, - )?) - } else { - None - }; - - Ok((key_sig, key_dec, key_aut)) -} - -fn name_command( - name: &str, - mut card: Card, - admin_pin: Option<&[u8]>, -) -> Result<(), Box> { - let mut admin = util::verify_to_admin(&mut card, admin_pin)?; - - admin.set_name(name)?; - Ok(()) -} - -fn url_command( - url: &str, - mut card: Card, - admin_pin: Option<&[u8]>, -) -> Result<(), Box> { - let mut admin = util::verify_to_admin(&mut card, admin_pin)?; - - admin.set_url(url)?; - Ok(()) -} - -fn import_command( - keyfile: PathBuf, - sig_fp: Option, - dec_fp: Option, - aut_fp: Option, - mut card: Card, - admin_pin: Option<&[u8]>, -) -> Result<(), Box> { - let key = Cert::from_file(keyfile)?; - - let p = StandardPolicy::new(); - - // select the (sub)keys to upload - let [sig, dec, auth] = match (&sig_fp, &dec_fp, &aut_fp) { - // No fingerprint has been provided, try to autoselect keys - // (this fails if there is more than one (sub)key for any keytype). - (&None, &None, &None) => keys_pick_yolo(&key, &p)?, - - _ => keys_pick_explicit(&key, &p, sig_fp, dec_fp, aut_fp)?, - }; - - let mut pws: Vec = vec![]; - - // helper: true, if `pw` decrypts `key` - let pw_ok = |key: &Key, pw: &str| { - key.clone() - .decrypt_secret(&sequoia_openpgp::crypto::Password::from(pw)) - .is_ok() - }; - - // helper: if any password in `pws` decrypts `key`, return that password - let find_pw = |key: &Key, pws: &[String]| { - pws.iter().find(|pw| pw_ok(key, pw)).cloned() - }; - - // helper: check if we have the right password for `key` in `pws`, - // if so return it. otherwise ask the user for the password, - // add it to `pws` and return it. - let mut get_pw_for_key = |key: &Option>, - key_type: &str| - -> Result> { - if let Some(k) = key { - if !k.has_secret() { - // key has no secret key material, it can't be imported - return Err(anyhow!( - "(Sub)Key {} contains no private key material", - k.fingerprint() - )); - } - - if k.has_unencrypted_secret() { - // key is unencrypted, we need no password - return Ok(None); - } - - // key is encrypted, we need the password - - // do we already have the right password? - if let Some(pw) = find_pw(k, &pws) { - return Ok(Some(pw)); - } - - // no, we need to get the password from user - let pw = rpassword::prompt_password(format!( - "Enter password for {} (sub)key {}:", - key_type, - k.fingerprint() - ))?; - - if pw_ok(k, &pw) { - // remember pw for next subkeys - pws.push(pw.clone()); - - Ok(Some(pw)) - } else { - // this password doesn't work, error out - Err(anyhow!( - "Password not valid for (Sub)Key {}", - k.fingerprint() - )) - } - } else { - // we have no key for this slot, so we don't need a password - Ok(None) - } - }; - - // get passwords, if encrypted (try previous pw before asking for user input) - let sig_p = get_pw_for_key(&sig, "signing")?; - let dec_p = get_pw_for_key(&dec, "decryption")?; - let auth_p = get_pw_for_key(&auth, "authentication")?; - - // upload keys to card - let mut admin = util::verify_to_admin(&mut card, admin_pin)?; - - if let Some(sig) = sig { - println!("Uploading {} as signing key", sig.fingerprint()); - admin.upload_key(sig, KeyType::Signing, sig_p)?; - } - if let Some(dec) = dec { - println!("Uploading {} as decryption key", dec.fingerprint()); - admin.upload_key(dec, KeyType::Decryption, dec_p)?; - } - if let Some(auth) = auth { - println!("Uploading {} as authentication key", auth.fingerprint()); - admin.upload_key(auth, KeyType::Authentication, auth_p)?; - } - Ok(()) -} - -fn generate_command( - output_format: OutputFormat, - output_version: OutputVersion, - mut card: Card, - - admin_pin: Option<&[u8]>, - - cmd: AdminGenerateCommand, -) -> Result<()> { - let user_pin = util::get_pin(&mut card, cmd.user_pin, ENTER_USER_PIN)?; - - let mut output = output::AdminGenerate::default(); - output.ident(card.application_identifier()?.ident()); - - // 1) Interpret the user's choice of algorithm. - // - // Unset (None) means that the algorithm that is specified on the card - // should remain unchanged. - // - // For RSA, different cards use different exact algorithm - // specifications. In particular, the length of the value `e` differs - // between cards. Some devices use 32 bit length for e, others use 17 bit. - // In some cases, it's possible to get this information from the card, - // but I believe this information is not obtainable in all cases. - // Because of this, for generation of RSA keys, here we take the approach - // of first trying one variant, and then if that fails, try the other. - - let algo = cmd.algo.map(AlgoSimple::from); - log::info!(" Key generation will be attempted with algo: {:?}", algo); - output.algorithm(format!("{algo:?}")); - - // 2) Then, generate keys on the card. - // We need "admin" access to the card for this). - let (key_sig, key_dec, key_aut) = { - if let Ok(mut admin) = util::verify_to_admin(&mut card, admin_pin) { - gen_subkeys(&mut admin, cmd.decrypt, cmd.auth, algo)? - } else { - return Err(anyhow!("Failed to open card in admin mode.")); - } - }; - - // 3) Generate a Cert from the generated keys. For this, we - // need "signing" access to the card (to make binding signatures within - // the Cert). - let cert = crate::get_cert( - &mut card, - key_sig, - key_dec, - key_aut, - user_pin.as_deref(), - &cmd.user_ids, - &|| println!("Enter User PIN on card reader pinpad."), - )?; - - let armored = String::from_utf8(cert.armored().to_vec()?)?; - output.public_key(armored); - - // Write armored certificate to the output file - let mut handle = util::open_or_stdout(Some(&cmd.output_file))?; - handle.write_all(output.print(output_format, output_version)?.as_bytes())?; - let _ = handle.write(b"\n")?; - - Ok(()) -} - -fn touch_command( - mut card: Card, - admin_pin: Option<&[u8]>, - key: BasePlusAttKeySlot, - policy: TouchPolicy, -) -> Result<(), Box> { - let kt = KeyType::from(key); - - let pol = openpgp_card_sequoia::types::TouchPolicy::from(policy); - - let mut admin = util::verify_to_admin(&mut card, admin_pin)?; - - admin.set_uif(kt, pol)?; - Ok(()) -} diff --git a/tools/src/commands/attestation.rs b/tools/src/commands/attestation.rs deleted file mode 100644 index 40b0c20..0000000 --- a/tools/src/commands/attestation.rs +++ /dev/null @@ -1,195 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Lars Wirzenius -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use std::path::PathBuf; - -use anyhow::Result; -use clap::{Parser, ValueEnum}; -use openpgp_card_sequoia::types::KeyType; -use openpgp_card_sequoia::{state::Open, Card}; - -use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; -use crate::ENTER_USER_PIN; -use crate::{output, pick_card_for_reading, util}; - -#[derive(Parser, Debug)] -pub struct AttestationCommand { - #[clap(subcommand)] - pub cmd: AttSubCommand, -} - -#[derive(Parser, Debug)] -pub enum AttSubCommand { - /// Print the card's attestation certificate - /// - /// New YubiKeys are preloaded with an attestation certificate issued by the Yubico CA. - Cert { - #[clap( - name = "card ident", - short = 'c', - long = "card", - help = "Identifier of the card to use" - )] - ident: Option, - }, - - /// Generate attestation statement for one of the key slots on the card - /// - /// An attestation statement can only be generated for key slots that contain keys that were - /// generated by the card. See 'opgpcard admin generate' and 'opgpcard status -v'. - Generate { - #[clap( - name = "card ident", - short = 'c', - long = "card", - help = "Identifier of the card to use" - )] - ident: String, - - /// Key slot to use - #[clap(name = "Key slot", short = 'k', long = "key", value_enum)] - key: BaseKeySlot, - - #[clap( - name = "User PIN file", - short = 'p', - long = "user-pin", - help = "Optionally, get User PIN from a file" - )] - user_pin: Option, - }, - - /// Print the attestation statement for one of the key slots on the card - /// - /// An attestation statement can only be printed after generating it. See 'opgpcard attestation - /// generate'. - Statement { - #[clap( - name = "card ident", - short = 'c', - long = "card", - help = "Identifier of the card to reset" - )] - ident: Option, - - /// Key slot to use - #[clap(name = "Key slot", short = 'k', long = "key", value_enum)] - key: BaseKeySlot, - }, -} - -#[derive(ValueEnum, Debug, Clone)] -#[clap(rename_all = "UPPER")] -pub enum BaseKeySlot { - Sig, - Dec, - Aut, -} - -impl From for KeyType { - fn from(ks: BaseKeySlot) -> Self { - match ks { - BaseKeySlot::Sig => KeyType::Signing, - BaseKeySlot::Dec => KeyType::Decryption, - BaseKeySlot::Aut => KeyType::Authentication, - } - } -} - -pub fn attestation( - output_format: OutputFormat, - output_version: OutputVersion, - command: AttestationCommand, -) -> Result<(), Box> { - match command.cmd { - AttSubCommand::Cert { ident } => cert(output_format, output_version, ident), - AttSubCommand::Generate { - ident, - key, - user_pin, - } => generate(&ident, key, user_pin), - AttSubCommand::Statement { ident, key } => statement(ident, key), - } -} - -fn cert( - output_format: OutputFormat, - output_version: OutputVersion, - ident: Option, -) -> Result<(), Box> { - let mut output = output::AttestationCert::default(); - - let backend = pick_card_for_reading(ident)?; - let mut open: Card = backend.into(); - let mut card = open.transaction()?; - - output.ident(card.application_identifier()?.ident()); - - if let Ok(ac) = card.attestation_certificate() { - let pem = util::pem_encode(ac); - output.attestation_cert(pem); - } - - println!("{}", output.print(output_format, output_version)?); - Ok(()) -} - -fn generate( - ident: &str, - key: BaseKeySlot, - user_pin: Option, -) -> Result<(), Box> { - let backend = util::open_card(ident)?; - let mut open: Card = backend.into(); - let mut card = open.transaction()?; - - let user_pin = util::get_pin(&mut card, user_pin, ENTER_USER_PIN)?; - - let mut sign = util::verify_to_sign(&mut card, user_pin.as_deref())?; - - let kt = KeyType::from(key); - sign.generate_attestation(kt, &|| { - println!("Touch confirmation needed to generate an attestation") - })?; - Ok(()) -} - -fn statement(ident: Option, key: BaseKeySlot) -> Result<(), Box> { - let backend = pick_card_for_reading(ident)?; - let mut open: Card = backend.into(); - let mut card = open.transaction()?; - - // Get cardholder certificate from card. - - let mut select_data_workaround = false; - // Use "select data" workaround if the card reports a - // yk firmware version number >= 5 and <= 5.4.3 - if let Ok(version) = card.firmware_version() { - if version.len() == 3 - && version[0] == 5 - && (version[1] < 4 || (version[1] == 4 && version[2] <= 3)) - { - select_data_workaround = true; - } - } - - // Select cardholder certificate - match key { - BaseKeySlot::Aut => card.select_data(0, &[0x7F, 0x21], select_data_workaround)?, - BaseKeySlot::Dec => card.select_data(1, &[0x7F, 0x21], select_data_workaround)?, - BaseKeySlot::Sig => card.select_data(2, &[0x7F, 0x21], select_data_workaround)?, - }; - - // Get DO "cardholder certificate" (returns the slot that was previously selected) - let cert = card.cardholder_certificate()?; - - if !cert.is_empty() { - let pem = util::pem_encode(cert); - println!("{pem}"); - } else { - println!("Cardholder certificate slot is empty"); - } - Ok(()) -} diff --git a/tools/src/commands/decrypt.rs b/tools/src/commands/decrypt.rs deleted file mode 100644 index d153cc4..0000000 --- a/tools/src/commands/decrypt.rs +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use std::path::PathBuf; - -use anyhow::{anyhow, Result}; -use clap::Parser; -use openpgp_card_sequoia::{state::Open, Card}; -use sequoia_openpgp::{ - parse::{stream::DecryptorBuilder, Parse}, - policy::StandardPolicy, -}; - -use crate::util; - -#[derive(Parser, Debug)] -pub struct DecryptCommand { - #[clap( - name = "card ident", - short = 'c', - long = "card", - help = "Identifier of the card to use" - )] - ident: String, - - #[clap( - name = "User PIN file", - short = 'p', - long = "user-pin", - help = "Optionally, get User PIN from a file" - )] - pin_file: Option, - - /// Input file (stdin if unset) - #[clap(name = "input")] - input: Option, - - /// Output file (stdout if unset) - #[clap(name = "output", long = "output", short = 'o')] - pub output: Option, -} - -pub fn decrypt(command: DecryptCommand) -> Result<(), Box> { - let p = StandardPolicy::new(); - - let input = util::open_or_stdin(command.input.as_deref())?; - - let backend = util::open_card(&command.ident)?; - let mut open: Card = backend.into(); - let mut card = open.transaction()?; - - if card.fingerprints()?.decryption().is_none() { - return Err(anyhow!("Can't decrypt: this card has no key in the decryption slot.").into()); - } - - let user_pin = util::get_pin(&mut card, command.pin_file, crate::ENTER_USER_PIN)?; - - let mut user = util::verify_to_user(&mut card, user_pin.as_deref())?; - let d = user.decryptor(&|| println!("Touch confirmation needed for decryption"))?; - - let db = DecryptorBuilder::from_reader(input)?; - let mut decryptor = db.with_policy(&p, None, d)?; - - let mut sink = util::open_or_stdout(command.output.as_deref())?; - std::io::copy(&mut decryptor, &mut sink)?; - - Ok(()) -} diff --git a/tools/src/commands/factory_reset.rs b/tools/src/commands/factory_reset.rs deleted file mode 100644 index 9379d3b..0000000 --- a/tools/src/commands/factory_reset.rs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use anyhow::{anyhow, Result}; -use clap::Parser; -use openpgp_card_sequoia::{state::Open, Card}; - -use crate::util; - -#[derive(Parser, Debug)] -pub struct FactoryResetCommand { - #[clap( - name = "card ident", - short = 'c', - long = "card", - help = "Identifier of the card to use" - )] - ident: String, -} - -pub fn factory_reset(command: FactoryResetCommand) -> Result<()> { - println!("Resetting Card {}", command.ident); - let backend = util::open_card(&command.ident)?; - let mut open: Card = backend.into(); - - let mut card = open.transaction()?; - card.factory_reset().map_err(|e| anyhow!(e)) -} diff --git a/tools/src/commands/info.rs b/tools/src/commands/info.rs deleted file mode 100644 index 732a747..0000000 --- a/tools/src/commands/info.rs +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Lars Wirzenius -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use anyhow::Result; -use clap::Parser; -use openpgp_card_sequoia::{state::Open, Card}; - -use crate::output; -use crate::pick_card_for_reading; -use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; - -#[derive(Parser, Debug)] -pub struct InfoCommand { - #[clap( - name = "card ident", - short = 'c', - long = "card", - help = "Identifier of the card to use" - )] - pub ident: Option, -} - -/// print metadata information about a card -pub fn print_info( - format: OutputFormat, - output_version: OutputVersion, - command: InfoCommand, -) -> Result<()> { - let mut output = output::Info::default(); - - let backend = pick_card_for_reading(command.ident)?; - let mut open: Card = backend.into(); - let mut card = open.transaction()?; - - let ai = card.application_identifier()?; - - output.ident(ai.ident()); - - let version = ai.version().to_be_bytes(); - output.card_version(format!("{}.{}", version[0], version[1])); - - output.application_id(ai.to_string()); - output.manufacturer_id(format!("{:04X}", ai.manufacturer())); - output.manufacturer_name(ai.manufacturer_name().to_string()); - - if let Some(cc) = card.historical_bytes()?.card_capabilities() { - for line in cc.to_string().lines() { - let line = line.strip_prefix("- ").unwrap_or(line); - output.card_capability(line.to_string()); - } - } - if let Some(csd) = card.historical_bytes()?.card_service_data() { - for line in csd.to_string().lines() { - let line = line.strip_prefix("- ").unwrap_or(line); - output.card_service_data(line.to_string()); - } - } - - if let Some(eli) = card.extended_length_information()? { - for line in eli.to_string().lines() { - let line = line.strip_prefix("- ").unwrap_or(line); - output.extended_length_info(line.to_string()); - } - } - - let ec = card.extended_capabilities()?; - for line in ec.to_string().lines() { - let line = line.strip_prefix("- ").unwrap_or(line); - output.extended_capability(line.to_string()); - } - - // Algorithm information (list of supported algorithms) - // - // FIXME: this should be output in a more structured shape - // Algorithms should be grouped by key slot, and the format of the algorithm name should - // probably have a human readable, and an alternate machine readable format. - // Both formats should be output for machine readable formats. - if let Ok(Some(ai)) = card.algorithm_information() { - for line in ai.to_string().lines() { - let line = line.strip_prefix("- ").unwrap_or_else(|| line.trim()); - output.algorithm(line.to_string()); - } - } - - // FIXME: print KDF info - - // YubiKey specific (?) firmware version - if let Ok(ver) = card.firmware_version() { - let ver = ver.iter().map(u8::to_string).collect::>().join("."); - output.firmware_version(ver); - } - - println!("{}", output.print(format, output_version)?); - - Ok(()) -} diff --git a/tools/src/commands/mod.rs b/tools/src/commands/mod.rs deleted file mode 100644 index ba21c9a..0000000 --- a/tools/src/commands/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -pub mod admin; -pub mod attestation; -pub mod decrypt; -pub mod factory_reset; -pub mod info; -pub mod pin; -pub mod pubkey; -pub mod set_identity; -pub mod sign; -pub mod ssh; -pub mod status; diff --git a/tools/src/commands/pin.rs b/tools/src/commands/pin.rs deleted file mode 100644 index 2e7e5c8..0000000 --- a/tools/src/commands/pin.rs +++ /dev/null @@ -1,364 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use std::path::PathBuf; - -use anyhow::Result; -use clap::Parser; -use openpgp_card_sequoia::{state::Open, state::Transaction, Card}; - -use crate::util; -use crate::util::{load_pin, print_gnuk_note}; -use crate::{ENTER_ADMIN_PIN, ENTER_USER_PIN}; - -#[derive(Parser, Debug)] -pub struct PinCommand { - #[clap( - name = "card ident", - short = 'c', - long = "card", - help = "Identifier of the card to use" - )] - pub ident: String, - - #[clap(subcommand)] - pub cmd: PinSubCommand, -} - -#[derive(Parser, Debug)] -pub enum PinSubCommand { - /// Set User PIN - /// - /// Set a new User PIN by providing the current User PIN. - SetUser { - #[clap( - name = "User PIN file old", - short = 'p', - long = "user-pin-old", - help = "Optionally, get old User PIN from a file" - )] - user_pin_old: Option, - - #[clap( - name = "User PIN file new", - short = 'q', - long = "user-pin-new", - help = "Optionally, get new User PIN from a file" - )] - user_pin_new: Option, - }, - - /// Set Admin PIN - /// - /// Set a new Admin PIN by providing the current Admin PIN. - SetAdmin { - #[clap( - name = "Admin PIN file old", - short = 'P', - long = "admin-pin-old", - help = "Optionally, get old Admin PIN from a file" - )] - admin_pin_old: Option, - - #[clap( - name = "Admin PIN file new", - short = 'Q', - long = "admin-pin-new", - help = "Optionally, get new Admin PIN from a file" - )] - admin_pin_new: Option, - }, - - /// Reset User PIN with Admin PIN - /// - /// Set a new User PIN by providing the Admin PIN. This can also be used if the User PIN has - /// been blocked. - ResetUser { - #[clap( - name = "Admin PIN file", - short = 'P', - long = "admin-pin", - help = "Optionally, get Admin PIN from a file" - )] - admin_pin: Option, - - #[clap( - name = "User PIN file new", - short = 'p', - long = "user-pin-new", - help = "Optionally, get new User PIN from a file" - )] - user_pin_new: Option, - }, - - /// Reset User PIN with Resetting Code - /// - /// Set a new User PIN by providing the Resetting Code. This can also be used if the User PIN - /// has been blocked. - ResetUserRc { - #[clap( - name = "Resetting Code file", - short = 'r', - long = "reset-code", - help = "Optionally, get the Resetting Code from a file" - )] - reset_code: Option, - - #[clap( - name = "User PIN file new", - short = 'p', - long = "user-pin-new", - help = "Optionally, get new User PIN from a file" - )] - user_pin_new: Option, - }, - - /// Set Resetting Code - /// - /// Set a Resetting Code by providing the Admin PIN. - SetReset { - #[clap( - name = "Admin PIN file", - short = 'P', - long = "admin-pin", - help = "Optionally, get Admin PIN from a file" - )] - admin_pin: Option, - - #[clap( - name = "Resetting Code file", - short = 'r', - long = "reset-code", - help = "Optionally, get the Resetting Code from a file" - )] - reset_code: Option, - }, -} - -pub fn pin(ident: &str, cmd: PinSubCommand) -> Result<()> { - let backend = util::open_card(ident)?; - let mut open: Card = backend.into(); - let card = open.transaction()?; - - match cmd { - PinSubCommand::SetUser { - user_pin_old, - user_pin_new, - } => set_user(user_pin_old, user_pin_new, card), - - PinSubCommand::SetAdmin { - admin_pin_old, - admin_pin_new, - } => set_admin(admin_pin_old, admin_pin_new, card), - - PinSubCommand::ResetUser { - admin_pin, - user_pin_new, - } => reset_user(admin_pin, user_pin_new, card), - - PinSubCommand::SetReset { - admin_pin, - reset_code, - } => set_reset(admin_pin, reset_code, card), - - PinSubCommand::ResetUserRc { - reset_code, - user_pin_new, - } => reset_user_rc(reset_code, user_pin_new, card), - } -} - -fn set_user( - user_pin_old: Option, - user_pin_new: Option, - mut card: Card, -) -> Result<()> { - let pinpad_modify = card.feature_pinpad_modify(); - - let res = if !pinpad_modify { - // get current user pin - let user_pin1 = util::get_pin(&mut card, user_pin_old, ENTER_USER_PIN)? - .expect("this should never be None"); - - // verify pin - card.verify_user(&user_pin1)?; - println!("PIN was accepted by the card.\n"); - - let pin_new = match user_pin_new { - None => { - // ask user for new user pin - util::input_pin_twice("Enter new User PIN: ", "Repeat the new User PIN: ")? - } - Some(path) => load_pin(&path)?, - }; - - // set new user pin - card.change_user_pin(&user_pin1, &pin_new) - } else { - // set new user pin via pinpad - card.change_user_pin_pinpad(&|| { - println!("Enter old User PIN on card reader pinpad, then new User PIN (twice).") - }) - }; - - match res { - Err(err) => { - println!("\nFailed to change the User PIN!"); - println!("{err:?}"); - print_gnuk_note(err, &card)?; - } - Ok(_) => println!("\nUser PIN has been set."), - } - Ok(()) -} - -fn set_admin( - admin_pin_old: Option, - admin_pin_new: Option, - mut card: Card, -) -> Result<()> { - let pinpad_modify = card.feature_pinpad_modify(); - - if !pinpad_modify { - // get current admin pin - let admin_pin1 = util::get_pin(&mut card, admin_pin_old, ENTER_ADMIN_PIN)? - .expect("this should never be None"); - - // verify pin - card.verify_admin(&admin_pin1)?; - // (Verifying the PIN here fixes this class of problems: - // https://developers.yubico.com/PGP/PGP_PIN_Change_Behavior.html - // It is also just generally more user friendly than failing later) - println!("PIN was accepted by the card.\n"); - - let pin_new = match admin_pin_new { - None => { - // ask user for new admin pin - util::input_pin_twice("Enter new Admin PIN: ", "Repeat the new Admin PIN: ")? - } - Some(path) => load_pin(&path)?, - }; - - // set new admin pin - card.change_admin_pin(&admin_pin1, &pin_new)?; - } else { - // set new admin pin via pinpad - card.change_admin_pin_pinpad(&|| { - println!("Enter old Admin PIN on card reader pinpad, then new Admin PIN (twice).") - })?; - }; - - println!("\nAdmin PIN has been set."); - Ok(()) -} - -fn reset_user( - admin_pin: Option, - user_pin_new: Option, - mut card: Card, -) -> Result<()> { - // verify admin pin - match util::get_pin(&mut card, admin_pin, ENTER_ADMIN_PIN)? { - Some(admin_pin) => { - // verify pin - card.verify_admin(&admin_pin)?; - } - None => { - card.verify_admin_pinpad(&|| println!("Enter Admin PIN on pinpad."))?; - } - } - println!("PIN was accepted by the card.\n"); - - // ask user for new user pin - let pin = match user_pin_new { - None => util::input_pin_twice("Enter new User PIN: ", "Repeat the new User PIN: ")?, - Some(path) => load_pin(&path)?, - }; - - let res = if let Some(mut admin) = card.admin_card() { - admin.reset_user_pin(&pin) - } else { - return Err(anyhow::anyhow!("Failed to use card in admin-mode.")); - }; - - match res { - Err(err) => { - println!("\nFailed to change the User PIN!"); - print_gnuk_note(err, &card)?; - } - Ok(_) => println!("\nUser PIN has been set."), - } - Ok(()) -} - -fn set_reset( - admin_pin: Option, - reset_code: Option, - mut card: Card, -) -> Result<()> { - // verify admin pin - match util::get_pin(&mut card, admin_pin, ENTER_ADMIN_PIN)? { - Some(admin_pin) => { - // verify pin - card.verify_admin(&admin_pin)?; - } - None => { - card.verify_admin_pinpad(&|| println!("Enter Admin PIN on pinpad."))?; - } - } - println!("PIN was accepted by the card.\n"); - - // ask user for new resetting code - let code = match reset_code { - None => util::input_pin_twice( - "Enter new resetting code: ", - "Repeat the new resetting code: ", - )?, - Some(path) => load_pin(&path)?, - }; - - if let Some(mut admin) = card.admin_card() { - admin.set_resetting_code(&code)?; - println!("\nResetting code has been set."); - Ok(()) - } else { - Err(anyhow::anyhow!("Failed to use card in admin-mode.")) - } -} - -fn reset_user_rc( - reset_code: Option, - user_pin_new: Option, - mut card: Card, -) -> Result<()> { - // reset by presenting resetting code - - let rst = if let Some(path) = reset_code { - // load resetting code from file - load_pin(&path)? - } else { - // input resetting code - rpassword::prompt_password("Enter resetting code: ")? - .as_bytes() - .to_vec() - }; - - // ask user for new user pin - let pin = match user_pin_new { - None => util::input_pin_twice("Enter new User PIN: ", "Repeat the new User PIN: ")?, - Some(path) => load_pin(&path)?, - }; - - // reset to new user pin - match card.reset_user_pin(&rst, &pin) { - Err(err) => { - println!("\nFailed to change the User PIN!"); - print_gnuk_note(err, &card) - } - Ok(_) => { - println!("\nUser PIN has been set."); - Ok(()) - } - } -} diff --git a/tools/src/commands/pubkey.rs b/tools/src/commands/pubkey.rs deleted file mode 100644 index 19b2e7a..0000000 --- a/tools/src/commands/pubkey.rs +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Lars Wirzenius -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use std::path::PathBuf; - -use anyhow::Result; -use clap::Parser; -use openpgp_card_sequoia::types::KeyType; -use openpgp_card_sequoia::util::public_key_material_and_fp_to_key; -use openpgp_card_sequoia::{state::Open, Card}; -use sequoia_openpgp::serialize::SerializeInto; - -use crate::output; -use crate::pick_card_for_reading; -use crate::util; -use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; - -#[derive(Parser, Debug)] -pub struct PubkeyCommand { - #[clap( - name = "card ident", - short = 'c', - long = "card", - help = "Identifier of the card to use" - )] - ident: Option, - - #[clap( - name = "User PIN file", - short = 'p', - long = "user-pin", - help = "Optionally, get User PIN from a file" - )] - user_pin: Option, - - /// User ID to add to the exported certificate representation - #[clap(name = "User ID", short = 'u', long = "userid")] - user_ids: Vec, -} - -pub fn print_pubkey( - format: OutputFormat, - output_version: OutputVersion, - command: PubkeyCommand, -) -> Result<()> { - let mut output = output::PublicKey::default(); - - let backend = pick_card_for_reading(command.ident)?; - let mut open: Card = backend.into(); - let mut card = open.transaction()?; - - let ident = card.application_identifier()?.ident(); - output.ident(ident); - - let user_pin = util::get_pin(&mut card, command.user_pin, crate::ENTER_USER_PIN)?; - - let pkm = card.public_key_material(KeyType::Signing)?; - let times = card.key_generation_times()?; - let fps = card.fingerprints()?; - - let key_sig = public_key_material_and_fp_to_key( - &pkm, - KeyType::Signing, - times.signature().expect("Signature time is unset"), - fps.signature().expect("Signature fingerprint is unset"), - )?; - - let mut key_dec = None; - if let Ok(pkm) = card.public_key_material(KeyType::Decryption) { - if let Some(ts) = times.decryption() { - key_dec = Some(public_key_material_and_fp_to_key( - &pkm, - KeyType::Decryption, - ts, - fps.decryption().expect("Decryption fingerprint is unset"), - )?); - } - } - - let mut key_aut = None; - if let Ok(pkm) = card.public_key_material(KeyType::Authentication) { - if let Some(ts) = times.authentication() { - key_aut = Some(public_key_material_and_fp_to_key( - &pkm, - KeyType::Authentication, - ts, - fps.authentication() - .expect("Authentication fingerprint is unset"), - )?); - } - } - - let cert = crate::get_cert( - &mut card, - key_sig, - key_dec, - key_aut, - user_pin.as_deref(), - &command.user_ids, - &|| println!("Enter User PIN on card reader pinpad."), - )?; - - let armored = String::from_utf8(cert.armored().to_vec()?)?; - output.public_key(armored); - - println!("{}", output.print(format, output_version)?); - Ok(()) -} diff --git a/tools/src/commands/set_identity.rs b/tools/src/commands/set_identity.rs deleted file mode 100644 index 95f6544..0000000 --- a/tools/src/commands/set_identity.rs +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use anyhow::Result; -use clap::{Parser, ValueEnum}; -use openpgp_card_sequoia::{state::Open, Card}; - -use crate::util; - -#[derive(Parser, Debug)] -pub struct SetIdentityCommand { - #[clap( - name = "card ident", - short = 'c', - long = "card", - help = "Identifier of the card to use" - )] - ident: String, - - /// Identity of the virtual card to activate - #[clap(name = "identity", value_enum)] - id: SetIdentityId, -} - -#[derive(ValueEnum, Debug, Clone)] -pub enum SetIdentityId { - #[clap(name = "0")] - Zero, - #[clap(name = "1")] - One, - #[clap(name = "2")] - Two, -} - -impl From for u8 { - fn from(id: SetIdentityId) -> Self { - match id { - SetIdentityId::Zero => 0, - SetIdentityId::One => 1, - SetIdentityId::Two => 2, - } - } -} - -pub fn set_identity(command: SetIdentityCommand) -> Result<(), Box> { - let backend = util::open_card(&command.ident)?; - let mut open: Card = backend.into(); - let mut card = open.transaction()?; - - card.set_identity(u8::from(command.id))?; - Ok(()) -} diff --git a/tools/src/commands/sign.rs b/tools/src/commands/sign.rs deleted file mode 100644 index 6632ab8..0000000 --- a/tools/src/commands/sign.rs +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use std::path::{Path, PathBuf}; - -use anyhow::{anyhow, Result}; -use clap::Parser; -use openpgp_card_sequoia::{state::Open, Card}; -use sequoia_openpgp::serialize::stream::{Armorer, Message, Signer}; - -use crate::util; - -#[derive(Parser, Debug)] -pub struct SignCommand { - #[clap( - name = "card ident", - short = 'c', - long = "card", - help = "Identifier of the card to use" - )] - pub ident: String, - - #[clap( - name = "User PIN file", - short = 'p', - long = "user-pin", - help = "Optionally, get User PIN from a file" - )] - pub user_pin: Option, - - #[clap( - name = "detached", - short = 'd', - long = "detached", - help = "Create a detached signature" - )] - pub detached: bool, - - /// Input file (stdin if unset) - #[clap(name = "input")] - pub input: Option, - - /// Output file (stdout if unset) - #[clap(name = "output", long = "output", short = 'o')] - pub output: Option, -} - -pub fn sign(command: SignCommand) -> Result<(), Box> { - if command.detached { - sign_detached( - &command.ident, - command.user_pin, - command.input.as_deref(), - command.output.as_deref(), - ) - } else { - Err(anyhow::anyhow!("Only detached signatures are supported for now").into()) - } -} - -pub fn sign_detached( - ident: &str, - pin_file: Option, - input: Option<&Path>, - output: Option<&Path>, -) -> Result<(), Box> { - let mut input = util::open_or_stdin(input)?; - - let backend = util::open_card(ident)?; - let mut open: Card = backend.into(); - let mut card = open.transaction()?; - - if card.fingerprints()?.signature().is_none() { - return Err(anyhow!("Can't sign: this card has no key in the signing slot.").into()); - } - - let user_pin = util::get_pin(&mut card, pin_file, crate::ENTER_USER_PIN)?; - - let mut sign = util::verify_to_sign(&mut card, user_pin.as_deref())?; - let s = sign.signer(&|| println!("Touch confirmation needed for signing"))?; - - let sink = util::open_or_stdout(output)?; - - let message = Armorer::new(Message::new(sink)).build()?; - let mut signer = Signer::new(message, s).detached().build()?; - - std::io::copy(&mut input, &mut signer)?; - signer.finalize()?; - - Ok(()) -} diff --git a/tools/src/commands/ssh.rs b/tools/src/commands/ssh.rs deleted file mode 100644 index 3fe49ee..0000000 --- a/tools/src/commands/ssh.rs +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Lars Wirzenius -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use anyhow::Result; -use clap::Parser; -use openpgp_card_sequoia::types::KeyType; -use openpgp_card_sequoia::{state::Open, Card}; - -use crate::output; -use crate::pick_card_for_reading; -use crate::util; -use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; - -#[derive(Parser, Debug)] -pub struct SshCommand { - #[clap( - name = "card ident", - short = 'c', - long = "card", - help = "Identifier of the card to use" - )] - pub ident: Option, - - #[clap(long, help = "Only print the ssh public key")] - pub key_only: bool, -} - -pub fn print_ssh( - format: OutputFormat, - output_version: OutputVersion, - command: SshCommand, -) -> Result<()> { - let mut output = output::Ssh::default(); - - output.key_only(command.key_only); - - let ident = command.ident; - - let backend = pick_card_for_reading(ident)?; - let mut open: Card = backend.into(); - let mut card = open.transaction()?; - - let ident = card.application_identifier()?.ident(); - output.ident(ident.clone()); - - // Print fingerprint of authentication subkey - let fps = card.fingerprints()?; - - if let Some(fp) = fps.authentication() { - output.authentication_key_fingerprint(fp.to_string()); - } - - // Show authentication subkey as openssh public key string - if let Ok(pkm) = card.public_key_material(KeyType::Authentication) { - if let Ok(ssh) = util::get_ssh_pubkey_string(&pkm, ident) { - output.ssh_public_key(ssh); - } - } - - println!("{}", output.print(format, output_version)?); - Ok(()) -} diff --git a/tools/src/commands/status.rs b/tools/src/commands/status.rs deleted file mode 100644 index 280bcd5..0000000 --- a/tools/src/commands/status.rs +++ /dev/null @@ -1,220 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Lars Wirzenius -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use anyhow::Result; -use clap::Parser; -use openpgp_card_sequoia::types::KeyType; -use openpgp_card_sequoia::{state::Open, Card}; - -use crate::output; -use crate::pick_card_for_reading; -use crate::versioned_output::{OutputBuilder, OutputFormat, OutputVersion}; - -#[derive(Parser, Debug)] -pub struct StatusCommand { - #[clap( - name = "card ident", - short = 'c', - long = "card", - help = "Identifier of the card to use" - )] - pub ident: Option, - - #[clap( - name = "verbose", - short = 'v', - long = "verbose", - help = "Use verbose output" - )] - pub verbose: bool, - - /// Print public key material for each key slot - #[clap(name = "pkm", short = 'K', long = "public-key-material")] - pub pkm: bool, -} - -pub fn print_status( - format: OutputFormat, - output_version: OutputVersion, - command: StatusCommand, -) -> Result<()> { - let mut output = output::Status::default(); - output.verbose(command.verbose); - output.pkm(command.pkm); - - let backend = pick_card_for_reading(command.ident)?; - let mut open: Card = backend.into(); - let mut card = open.transaction()?; - - output.ident(card.application_identifier()?.ident()); - - let ai = card.application_identifier()?; - let version = ai.version().to_be_bytes(); - output.card_version(format!("{}.{}", version[0], version[1])); - - // Cardholder Name - if let Some(name) = card.cardholder_name()? { - output.cardholder_name(name); - } - - // We ignore the Cardholder "Sex" field, because it's silly and mostly unhelpful - - // Certificate URL - let url = card.url()?; - if !url.is_empty() { - output.certificate_url(url); - } - - // Language Preference - if let Some(lang) = card.cardholder_related_data()?.lang() { - for lang in lang { - output.language_preference(format!("{lang}")); - } - } - - // key information (imported vs. generated on card) - let ki = card.key_information().ok().flatten(); - - let pws = card.pw_status_bytes()?; - - // information about subkeys - - let fps = card.fingerprints()?; - let kgt = card.key_generation_times()?; - - let mut signature_key = output::KeySlotInfo::default(); - if let Some(fp) = fps.signature() { - signature_key.fingerprint(fp.to_spaced_hex()); - } - signature_key.algorithm(format!("{}", card.algorithm_attributes(KeyType::Signing)?)); - if let Some(kgt) = kgt.signature() { - signature_key.creation_time(format!("{}", kgt.to_datetime())); - } - if let Some(uif) = card.uif_signing()? { - signature_key.touch_policy(format!("{}", uif.touch_policy())); - signature_key.touch_features(format!("{}", uif.features())); - } - if let Some(ks) = ki.as_ref().map(|ki| ki.sig_status()) { - signature_key.status(format!("{ks}")); - } - - if let Ok(pkm) = card.public_key_material(KeyType::Signing) { - signature_key.public_key_material(pkm.to_string()); - } - - output.signature_key(signature_key); - - let sst = card.security_support_template()?; - output.signature_count(sst.signature_count()); - - let mut decryption_key = output::KeySlotInfo::default(); - if let Some(fp) = fps.decryption() { - decryption_key.fingerprint(fp.to_spaced_hex()); - } - decryption_key.algorithm(format!( - "{}", - card.algorithm_attributes(KeyType::Decryption)? - )); - if let Some(kgt) = kgt.decryption() { - decryption_key.creation_time(format!("{}", kgt.to_datetime())); - } - if let Some(uif) = card.uif_decryption()? { - decryption_key.touch_policy(format!("{}", uif.touch_policy())); - decryption_key.touch_features(format!("{}", uif.features())); - } - if let Some(ks) = ki.as_ref().map(|ki| ki.dec_status()) { - decryption_key.status(format!("{ks}")); - } - if let Ok(pkm) = card.public_key_material(KeyType::Decryption) { - decryption_key.public_key_material(pkm.to_string()); - } - output.decryption_key(decryption_key); - - let mut authentication_key = output::KeySlotInfo::default(); - if let Some(fp) = fps.authentication() { - authentication_key.fingerprint(fp.to_spaced_hex()); - } - authentication_key.algorithm(format!( - "{}", - card.algorithm_attributes(KeyType::Authentication)? - )); - if let Some(kgt) = kgt.authentication() { - authentication_key.creation_time(format!("{}", kgt.to_datetime())); - } - if let Some(uif) = card.uif_authentication()? { - authentication_key.touch_policy(format!("{}", uif.touch_policy())); - authentication_key.touch_features(format!("{}", uif.features())); - } - if let Some(ks) = ki.as_ref().map(|ki| ki.aut_status()) { - authentication_key.status(format!("{ks}")); - } - if let Ok(pkm) = card.public_key_material(KeyType::Authentication) { - authentication_key.public_key_material(pkm.to_string()); - } - output.authentication_key(authentication_key); - - let mut attestation_key = output::KeySlotInfo::default(); - if let Ok(Some(fp)) = card.attestation_key_fingerprint() { - attestation_key.fingerprint(fp.to_spaced_hex()); - } - if let Ok(Some(algo)) = card.attestation_key_algorithm_attributes() { - attestation_key.algorithm(format!("{algo}")); - } - if let Ok(Some(kgt)) = card.attestation_key_generation_time() { - attestation_key.creation_time(format!("{}", kgt.to_datetime())); - } - if let Some(uif) = card.uif_attestation()? { - attestation_key.touch_policy(format!("{}", uif.touch_policy())); - attestation_key.touch_features(format!("{}", uif.features())); - } - - // TODO: get public key data for the attestation key from the card - // if let Ok(pkm) = card.public_key(KeyType::Attestation) { - // attestation_key.public_key_material(pkm.to_string()); - // } - - // "Key-Ref = 0x81 is reserved for the Attestation key of Yubico" - // (see OpenPGP card spec 3.4.1 pg.43) - if let Some(ki) = ki.as_ref() { - if let Some(n) = (0..ki.num_additional()).find(|&n| ki.additional_ref(n) == 0x81) { - let ks = ki.additional_status(n); - attestation_key.status(format!("{ks}")); - } - }; - - output.attestation_key(attestation_key); - - // technical details about the card's state - output.user_pin_valid_for_only_one_signature(pws.pw1_cds_valid_once()); - - output.user_pin_remaining_attempts(pws.err_count_pw1()); - output.admin_pin_remaining_attempts(pws.err_count_pw3()); - output.reset_code_remaining_attempts(pws.err_count_rc()); - - if let Some(ki) = ki { - let num = ki.num_additional(); - for i in 0..num { - // 0x81 is the Yubico attestation key, it has already been used above -> skip here - if ki.additional_ref(i) != 0x81 { - output.additional_key_status( - ki.additional_ref(i), - ki.additional_status(i).to_string(), - ); - } - } - } - - if let Ok(fps) = card.ca_fingerprints() { - for fp in fps.iter().flatten() { - output.ca_fingerprint(fp.to_string()); - } - } - - // FIXME: print "Login Data" - - println!("{}", output.print(format, output_version)?); - - Ok(()) -} diff --git a/tools/src/opgpcard.rs b/tools/src/opgpcard.rs deleted file mode 100644 index 731f11a..0000000 --- a/tools/src/opgpcard.rs +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-FileCopyrightText: 2022 Lars Wirzenius -// SPDX-FileCopyrightText: 2022 Nora Widdecke -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use anyhow::Result; -use clap::Parser; -use openpgp_card_sequoia::types::CardBackend; -use openpgp_card_sequoia::util::make_cert; -use openpgp_card_sequoia::PublicKey; -use openpgp_card_sequoia::{state::Open, state::Transaction, Card}; -use sequoia_openpgp::Cert; - -mod cli; -mod commands; -mod output; -mod util; -mod versioned_output; - -use cli::OUTPUT_VERSIONS; -use versioned_output::{OutputBuilder, OutputFormat, OutputVariant, OutputVersion}; - -const ENTER_USER_PIN: &str = "Enter User PIN:"; -const ENTER_ADMIN_PIN: &str = "Enter Admin PIN:"; - -fn main() -> Result<(), Box> { - env_logger::init(); - - let cli = cli::Cli::parse(); - - match cli.cmd { - cli::Command::OutputVersions {} => { - output_versions(cli.output_version); - } - cli::Command::List {} => { - list_cards(cli.output_format, cli.output_version)?; - } - cli::Command::Status(cmd) => { - commands::status::print_status(cli.output_format, cli.output_version, cmd)?; - } - cli::Command::Info(cmd) => { - commands::info::print_info(cli.output_format, cli.output_version, cmd)?; - } - cli::Command::Ssh(cmd) => { - commands::ssh::print_ssh(cli.output_format, cli.output_version, cmd)?; - } - cli::Command::Pubkey(cmd) => { - commands::pubkey::print_pubkey(cli.output_format, cli.output_version, cmd)?; - } - cli::Command::SetIdentity(cmd) => { - commands::set_identity::set_identity(cmd)?; - } - cli::Command::Decrypt(cmd) => { - commands::decrypt::decrypt(cmd)?; - } - cli::Command::Sign(cmd) => { - commands::sign::sign(cmd)?; - } - cli::Command::Attestation(cmd) => { - commands::attestation::attestation(cli.output_format, cli.output_version, cmd)?; - } - cli::Command::FactoryReset(cmd) => { - commands::factory_reset::factory_reset(cmd)?; - } - cli::Command::Admin(cmd) => { - commands::admin::admin(cli.output_format, cli.output_version, cmd)?; - } - cli::Command::Pin(cmd) => { - commands::pin::pin(&cmd.ident, cmd.cmd)?; - } - } - - Ok(()) -} - -fn output_versions(chosen: OutputVersion) { - for v in OUTPUT_VERSIONS.iter() { - if v == &chosen { - println!("* {v}"); - } else { - println!(" {v}"); - } - } -} - -fn list_cards(format: OutputFormat, output_version: OutputVersion) -> Result<()> { - let cards = util::cards()?; - let mut output = output::List::default(); - if !cards.is_empty() { - for backend in cards { - let mut open: Card = backend.into(); - - output.push(open.transaction()?.application_identifier()?.ident()); - } - } - println!("{}", output.print(format, output_version)?); - Ok(()) -} - -/// Return a card for a read operation. If `ident` is None, and exactly one card -/// is plugged in, that card is returned. (We don't This -fn pick_card_for_reading(ident: Option) -> Result> { - if let Some(ident) = ident { - Ok(util::open_card(&ident)?) - } else { - let mut cards = util::cards()?; - if cards.len() == 1 { - Ok(cards.pop().unwrap()) - } else if cards.is_empty() { - Err(anyhow::anyhow!("No cards found")) - } else { - // The output version for OutputFormat::Text doesn't matter (it's ignored). - list_cards(OutputFormat::Text, OutputVersion::new(0, 0, 0))?; - - println!("Specify which card to use with '--card '\n"); - - Err(anyhow::anyhow!("Found more than one card")) - } - } -} - -fn get_cert( - card: &mut Card, - key_sig: PublicKey, - key_dec: Option, - key_aut: Option, - user_pin: Option<&[u8]>, - user_ids: &[String], - prompt: &dyn Fn(), -) -> Result { - if user_pin.is_none() && card.feature_pinpad_verify() { - println!( - "The public cert will now be generated.\n\n\ - You will need to enter your User PIN multiple times during this process.\n\n" - ); - } - - make_cert( - card, - key_sig, - key_dec, - key_aut, - user_pin, - prompt, - &|| println!("Touch confirmation needed for signing"), - user_ids, - ) -} diff --git a/tools/src/output/attest.rs b/tools/src/output/attest.rs deleted file mode 100644 index 1849d78..0000000 --- a/tools/src/output/attest.rs +++ /dev/null @@ -1,75 +0,0 @@ -// 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/output/generate.rs b/tools/src/output/generate.rs deleted file mode 100644 index cc92843..0000000 --- a/tools/src/output/generate.rs +++ /dev/null @@ -1,80 +0,0 @@ -// 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 { - // Do not print ident, as the file with the public_key must not contain anything else - Ok(self.public_key.to_string()) - } - - 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/output/info.rs b/tools/src/output/info.rs deleted file mode 100644 index 9a6cacb..0000000 --- a/tools/src/output/info.rs +++ /dev/null @@ -1,192 +0,0 @@ -// 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: Vec, - 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.push(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!("- {c}\n")); - } - s.push('\n'); - } - - if !self.card_service_data.is_empty() { - s.push_str("Card service data:\n"); - for c in self.card_service_data.iter() { - s.push_str(&format!("- {c}\n")); - } - 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!("- {c}\n")); - } - s.push('\n'); - } - - s.push_str("Extended Capabilities:\n"); - for c in self.extended_capabilities.iter() { - s.push_str(&format!("- {c}\n")); - } - s.push('\n'); - - if let Some(algos) = &self.algorithms { - s.push_str("Supported algorithms:\n"); - for c in algos.iter() { - s.push_str(&format!("- {c}\n")); - } - s.push('\n'); - } - - if let Some(v) = &self.firmware_version { - s.push_str(&format!("Firmware Version: {v}\n")); - } - - 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: Vec, - 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/output/list.rs b/tools/src/output/list.rs deleted file mode 100644 index 8483f59..0000000 --- a/tools/src/output/list.rs +++ /dev/null @@ -1,74 +0,0 @@ -// 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!(" {id}\n")); - } - 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/output/mod.rs b/tools/src/output/mod.rs deleted file mode 100644 index 1534851..0000000 --- a/tools/src/output/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -// 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/output/pubkey.rs b/tools/src/output/pubkey.rs deleted file mode 100644 index 0c9ddbd..0000000 --- a/tools/src/output/pubkey.rs +++ /dev/null @@ -1,75 +0,0 @@ -// 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/output/ssh.rs b/tools/src/output/ssh.rs deleted file mode 100644 index da28d28..0000000 --- a/tools/src/output/ssh.rs +++ /dev/null @@ -1,97 +0,0 @@ -// 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 { - key_only: bool, // only print ssh public key, in text mode - ident: String, - authentication_key_fingerprint: Option, - ssh_public_key: Option, -} - -impl Ssh { - pub fn key_only(&mut self, ssh_key_only: bool) { - self.key_only = ssh_key_only; - } - - 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 { - if !self.key_only { - let mut s = format!("OpenPGP card {}\n\n", self.ident); - - if let Some(fp) = &self.authentication_key_fingerprint { - s.push_str(&format!("Authentication key fingerprint:\n{fp}\n\n")); - } - if let Some(key) = &self.ssh_public_key { - s.push_str(&format!("SSH public key:\n{key}\n")); - } - - Ok(s) - } else { - Ok(self.ssh_public_key.clone().unwrap_or("".to_string())) - } - } - - 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/output/status.rs b/tools/src/output/status.rs deleted file mode 100644 index e65e01b..0000000 --- a/tools/src/output/status.rs +++ /dev/null @@ -1,340 +0,0 @@ -// 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, // show verbose text output? - pkm: bool, // include public key material in text output? - ident: String, - card_version: String, - cardholder_name: Option, - language_preferences: Vec, - certificate_url: Option, - signature_key: KeySlotInfo, - signature_count: u32, - user_pin_valid_for_only_one_signature: bool, - decryption_key: KeySlotInfo, - authentication_key: KeySlotInfo, - attestation_key: Option, - user_pin_remaining_attempts: u8, - admin_pin_remaining_attempts: u8, - reset_code_remaining_attempts: u8, - additional_key_statuses: Vec<(u8, String)>, - ca_fingerprints: Vec, -} - -impl Status { - pub fn verbose(&mut self, verbose: bool) { - self.verbose = verbose; - } - - pub fn pkm(&mut self, pkm: bool) { - self.pkm = pkm; - } - - 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 cardholder_name(&mut self, card_holder: String) { - self.cardholder_name = Some(card_holder); - } - - pub fn language_preference(&mut self, pref: String) { - self.language_preferences.push(pref); - } - - pub fn certificate_url(&mut self, url: String) { - self.certificate_url = Some(url); - } - - 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 user_pin_valid_for_only_one_signature(&mut self, sign_pin_valid_once: bool) { - self.user_pin_valid_for_only_one_signature = sign_pin_valid_once; - } - - 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 attestation_key(&mut self, key: KeySlotInfo) { - self.attestation_key = Some(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 additional_key_status(&mut self, keyref: u8, status: String) { - self.additional_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.cardholder_name { - if !name.is_empty() { - s.push_str(&format!("Cardholder: {name}\n")); - nl = true; - } - } - - if let Some(url) = &self.certificate_url { - if !url.is_empty() { - s.push_str(&format!("Certificate URL: {url}\n")); - 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: '{prefs}'\n")); - nl = true; - } - } - - if nl { - s.push('\n'); - } - - s.push_str("Signature key:\n"); - for line in self.signature_key.format(self.verbose, self.pkm) { - s.push_str(&format!(" {line}\n")); - } - if self.verbose { - if self.user_pin_valid_for_only_one_signature { - s.push_str(" User PIN presentation is valid for only one signature\n"); - } else { - s.push_str(" User PIN presentation is valid for unlimited signatures\n"); - } - } - 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, self.pkm) { - s.push_str(&format!(" {line}\n")); - } - s.push('\n'); - - s.push_str("Authentication key:\n"); - for line in self.authentication_key.format(self.verbose, self.pkm) { - s.push_str(&format!(" {line}\n")); - } - s.push('\n'); - - if self.verbose { - if let Some(attestation_key) = &self.attestation_key { - if attestation_key.touch_policy.is_some() || attestation_key.algorithm.is_some() { - s.push_str("Attestation key:\n"); - for line in attestation_key.format(self.verbose, self.pkm) { - s.push_str(&format!(" {line}\n")); - } - 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 { - for (keyref, status) in self.additional_key_statuses.iter() { - s.push_str(&format!("Additional key status (#{keyref}): {status}\n")); - } - } - - Ok(s) - } - - fn v1(&self) -> Result { - Ok(StatusV0 { - schema_version: StatusV0::VERSION, - ident: self.ident.clone(), - card_version: self.card_version.clone(), - cardholder_name: self.cardholder_name.clone(), - language_preferences: self.language_preferences.clone(), - certificate_url: self.certificate_url.clone(), - signature_key: self.signature_key.clone(), - signature_count: self.signature_count, - decryption_key: self.decryption_key.clone(), - authentication_key: self.authentication_key.clone(), - attestation_key: self.attestation_key.clone(), - user_pin_valid_for_only_one_signature: self.user_pin_valid_for_only_one_signature, - 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, - additional_key_statuses: self.additional_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, - cardholder_name: Option, - language_preferences: Vec, - certificate_url: Option, - signature_key: KeySlotInfo, - signature_count: u32, - user_pin_valid_for_only_one_signature: bool, - decryption_key: KeySlotInfo, - authentication_key: KeySlotInfo, - attestation_key: Option, - user_pin_remaining_attempts: u8, - admin_pin_remaining_attempts: u8, - reset_code_remaining_attempts: u8, - additional_key_statuses: Vec<(u8, String)>, - // ca_fingerprints: Vec, // TODO: add to JSON output after clarifying the content -} - -impl OutputVariant for StatusV0 { - const VERSION: OutputVersion = OutputVersion::new(0, 9, 0); -} - -#[derive(Debug, Default, Clone, Serialize)] -pub struct KeySlotInfo { - fingerprint: Option, - creation_time: Option, - algorithm: Option, - touch_policy: Option, - touch_features: Option, - status: Option, - 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 creation_time(&mut self, created: String) { - self.creation_time = 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 public_key_material(&mut self, material: String) { - self.public_key_material = Some(material); - } - - fn format(&self, verbose: bool, pkm: bool) -> Vec { - let mut lines = vec![]; - - if let Some(fp) = &self.fingerprint { - lines.push(format!("Fingerprint: {fp}")); - } else { - lines.push("Fingerprint: [unset]".to_string()); - } - if let Some(ts) = &self.creation_time { - lines.push(format!("Creation Time: {ts}")); - } - if let Some(a) = &self.algorithm { - lines.push(format!("Algorithm: {a}")); - } - - if verbose { - if let Some(policy) = &self.touch_policy { - if let Some(features) = &self.touch_features { - lines.push(format!("Touch policy: {policy} (features: {features})")); - } - } - if let Some(status) = &self.status { - lines.push(format!("Key Status: {status}")); - } - } - if pkm { - if let Some(material) = &self.public_key_material { - lines.push(format!("Public key material: {material}")); - } - } - - lines - } -} diff --git a/tools/src/util.rs b/tools/src/util.rs deleted file mode 100644 index a9aa949..0000000 --- a/tools/src/util.rs +++ /dev/null @@ -1,253 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use std::path::{Path, PathBuf}; - -use anyhow::{anyhow, Context, Result}; -use openpgp_card_pcsc::PcscBackend; -use openpgp_card_sequoia::state::{Admin, Sign, Transaction, User}; -use openpgp_card_sequoia::types::{ - Algo, CardBackend, Curve, EccType, Error, PublicKeyMaterial, StatusBytes, -}; -use openpgp_card_sequoia::Card; - -pub(crate) fn cards() -> Result>, Error> { - PcscBackend::cards(None).map(|cards| cards.into_iter().map(|c| c.into()).collect()) -} - -pub(crate) fn open_card(ident: &str) -> Result, Error> { - Ok(PcscBackend::open_by_ident(ident, None)?.into()) -} - -/// Get pin from file. Or via user input, if no file and no pinpad is available. -/// -/// If a pinpad is available, return Null (the pinpad will be used to get access to the card). -/// -/// `msg` is the message to show when asking the user to enter a PIN. -pub(crate) fn get_pin( - card: &mut Card>, - pin_file: Option, - msg: &str, -) -> Result>> { - if let Some(path) = pin_file { - // we have a pin file - Ok(Some(load_pin(&path).context(format!( - "Failed to read PIN file {}", - path.display() - ))?)) - } else if !card.feature_pinpad_verify() { - // we have no pin file and no pinpad - let pin = rpassword::prompt_password(msg).context("Failed to read PIN")?; - Ok(Some(pin.into_bytes())) - } else { - // we have a pinpad - Ok(None) - } -} - -/// Let the user input a PIN twice, return PIN if both entries match, error otherwise -pub(crate) fn input_pin_twice(msg1: &str, msg2: &str) -> Result> { - // get new user pin - let newpin1 = rpassword::prompt_password(msg1)?; - let newpin2 = rpassword::prompt_password(msg2)?; - - if newpin1 != newpin2 { - Err(anyhow::anyhow!("PINs do not match.")) - } else { - Ok(newpin1.as_bytes().to_vec()) - } -} - -pub(crate) fn verify_to_user<'app, 'open>( - card: &'open mut Card>, - pin: Option<&[u8]>, -) -> Result>, Box> { - if let Some(pin) = pin { - card.verify_user(pin)?; - } else { - if !card.feature_pinpad_verify() { - return Err(anyhow!("No user PIN file provided, and no pinpad found").into()); - }; - - card.verify_user_pinpad(&|| println!("Enter user PIN on card reader pinpad."))?; - } - - card.user_card() - .ok_or_else(|| anyhow!("Couldn't get user access").into()) -} - -pub(crate) fn verify_to_sign<'app, 'open>( - card: &'open mut Card>, - pin: Option<&[u8]>, -) -> Result>, Box> { - if let Some(pin) = pin { - card.verify_user_for_signing(pin)?; - } else { - if !card.feature_pinpad_verify() { - return Err(anyhow!("No user PIN file provided, and no pinpad found").into()); - } - card.verify_user_for_signing_pinpad(&|| println!("Enter user PIN on card reader pinpad."))?; - } - card.signing_card() - .ok_or_else(|| anyhow!("Couldn't get sign access").into()) -} - -pub(crate) fn verify_to_admin<'app, 'open>( - card: &'open mut Card>, - pin: Option<&[u8]>, -) -> Result>, Box> { - if let Some(pin) = pin { - card.verify_admin(pin)?; - } else { - if !card.feature_pinpad_verify() { - return Err(anyhow!("No admin PIN file provided, and no pinpad found").into()); - } - - card.verify_admin_pinpad(&|| println!("Enter admin PIN on card reader pinpad."))?; - } - card.admin_card() - .ok_or_else(|| anyhow!("Couldn't get admin access").into()) -} - -pub(crate) fn load_pin(pin_file: &Path) -> Result> { - let pin = std::fs::read_to_string(pin_file)?; - Ok(pin.trim().as_bytes().to_vec()) -} - -pub(crate) fn open_or_stdin(f: Option<&Path>) -> Result> { - match f { - Some(f) => Ok(Box::new( - std::fs::File::open(f).context("Failed to open input file")?, - )), - None => Ok(Box::new(std::io::stdin())), - } -} - -pub(crate) fn open_or_stdout(f: Option<&Path>) -> Result> { - match f { - Some(f) => Ok(Box::new( - std::fs::File::create(f).context("Failed to open input file")?, - )), - None => Ok(Box::new(std::io::stdout())), - } -} - -fn get_ssh_pubkey(pkm: &PublicKeyMaterial, ident: String) -> Result { - let cardname = format!("opgpcard:{ident}"); - - let (key_type, kind) = match pkm { - PublicKeyMaterial::R(rsa) => { - let key_type = sshkeys::KeyType::from_name("ssh-rsa")?; - - let kind = sshkeys::PublicKeyKind::Rsa(sshkeys::RsaPublicKey { - e: rsa.v().to_vec(), - n: rsa.n().to_vec(), - }); - - Ok((key_type, kind)) - } - PublicKeyMaterial::E(ecc) => { - if let Algo::Ecc(ecc_attrs) = ecc.algo() { - match ecc_attrs.ecc_type() { - EccType::EdDSA => { - let key_type = sshkeys::KeyType::from_name("ssh-ed25519")?; - - let kind = sshkeys::PublicKeyKind::Ed25519(sshkeys::Ed25519PublicKey { - key: ecc.data().to_vec(), - sk_application: None, - }); - - Ok((key_type, kind)) - } - EccType::ECDSA => { - let (curve, name) = match ecc_attrs.curve() { - Curve::NistP256r1 => Ok(( - sshkeys::Curve::from_identifier("nistp256")?, - "ecdsa-sha2-nistp256", - )), - Curve::NistP384r1 => Ok(( - sshkeys::Curve::from_identifier("nistp384")?, - "ecdsa-sha2-nistp384", - )), - Curve::NistP521r1 => Ok(( - sshkeys::Curve::from_identifier("nistp521")?, - "ecdsa-sha2-nistp521", - )), - _ => Err(anyhow!("Unexpected ECDSA curve {:?}", ecc_attrs.curve())), - }?; - - let key_type = sshkeys::KeyType::from_name(name)?; - - let kind = sshkeys::PublicKeyKind::Ecdsa(sshkeys::EcdsaPublicKey { - curve, - key: ecc.data().to_vec(), - sk_application: None, - }); - - Ok((key_type, kind)) - } - _ => Err(anyhow!("Unexpected EccType {:?}", ecc_attrs.ecc_type())), - } - } else { - Err(anyhow!("Unexpected Algo in EccPub {:?}", ecc)) - } - } - _ => Err(anyhow!("Unexpected PublicKeyMaterial type {:?}", pkm)), - }?; - - let pk = sshkeys::PublicKey { - key_type, - comment: Some(cardname), - kind, - }; - - Ok(pk) -} - -/// Return a String representation of an ssh public key, in a form like: -/// "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAuTuxILMTvzTIRvaRqqUM3aRDoEBgz/JAoWKsD1ECxy opgpcard:FFFE:43194240" -pub(crate) fn get_ssh_pubkey_string(pkm: &PublicKeyMaterial, ident: String) -> Result { - let pk = get_ssh_pubkey(pkm, ident)?; - - let mut v = vec![]; - pk.write(&mut v)?; - - let s = String::from_utf8_lossy(&v).to_string(); - - Ok(s.trim().into()) -} - -/// Gnuk doesn't allow the User password (pw1) to be changed while no -/// private key material exists on the card. -/// -/// This fn checks for Gnuk's Status code and the case that no keys exist -/// on the card, and prints a note to the user, pointing out that the -/// absence of keys on the card might be the reason for the error they get. -pub(crate) fn print_gnuk_note(err: Error, card: &Card) -> Result<()> { - if matches!( - err, - Error::CardStatus(StatusBytes::ConditionOfUseNotSatisfied) - ) { - // check if no keys exist on the card - let fps = card.fingerprints()?; - if fps.signature().is_none() && fps.decryption().is_none() && fps.authentication().is_none() - { - println!( - "\nNOTE: Some cards (e.g. Gnuk) don't allow \ - User PIN change while no keys exist on the card." - ); - } - } - Ok(()) -} - -pub(crate) fn pem_encode(data: Vec) -> String { - const PEM_TAG: &str = "CERTIFICATE"; - - let pem = pem::Pem { - tag: String::from(PEM_TAG), - contents: data, - }; - - pem::encode(&pem) -} diff --git a/tools/src/versioned_output.rs b/tools/src/versioned_output.rs deleted file mode 100644 index b0c3804..0000000 --- a/tools/src/versioned_output.rs +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lars Wirzenius -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use std::str::FromStr; - -use clap::ValueEnum; -use semver::Version; -use serde::{Serialize, Serializer}; - -#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)] -pub enum OutputFormat { - Json, - Text, - Yaml, -} - -#[derive(Debug, Clone)] -pub struct OutputVersion { - version: Version, -} - -impl OutputVersion { - pub const fn new(major: u64, minor: u64, patch: u64) -> Self { - Self { - version: Version::new(major, minor, patch), - } - } - - /// Does this version fulfill the needs of the version that is requested? - pub fn is_acceptable_for(&self, wanted: &Self) -> bool { - self.version.major == wanted.version.major - && (self.version.minor > wanted.version.minor - || (self.version.minor == wanted.version.minor - && self.version.patch >= wanted.version.patch)) - } -} - -impl std::fmt::Display for OutputVersion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.version) - } -} - -impl FromStr for OutputVersion { - type Err = semver::Error; - - fn from_str(s: &str) -> Result { - let v = Version::parse(s)?; - Ok(Self::new(v.major, v.minor, v.patch)) - } -} - -impl PartialEq for &OutputVersion { - fn eq(&self, other: &Self) -> bool { - self.version == other.version - } -} - -impl Serialize for OutputVersion { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -pub trait OutputBuilder { - type Err; - - fn print(&self, format: OutputFormat, version: OutputVersion) -> Result; -} - -pub trait OutputVariant: Serialize { - const VERSION: OutputVersion; - fn json(&self) -> Result { - serde_json::to_string_pretty(self) - } - fn yaml(&self) -> Result { - serde_yaml::to_string(self) - } -} diff --git a/tools/subplot/opgpcard.md b/tools/subplot/opgpcard.md deleted file mode 100644 index 9dedc5e..0000000 --- a/tools/subplot/opgpcard.md +++ /dev/null @@ -1,238 +0,0 @@ - - -# Introduction - -This document describes the requirements and acceptance criteria for -the `opgpcard` tool, and also how to verify that they are met. This -document is meant to be read and understood by all stakeholders, and -processed by the [Subplot](https://subplot.tech/) tool, which also -generates code to automatically perform the verification. - -## Note about running the tests described here - -The verification scenarios in this document assume the availability of -a virtual smart card. Specifically one described in -. The -`openpgp-card/tools` crate is set up to generate tests only if the -environment variable `CARD_BASED_TESTS` is set (to any value), -and the `openpgp-card` repository `.gitlab-ci.yml` file is set up to -set that environment variable when the repository is tested in GitLab CI. - -This means that if you run `cargo test`, no test code is normally -generated from this document. To run the tests locally, outside of -GitLab CI, use the script `tools/cargo-test-in-docker`. - -# Acceptance criteria - -These scenarios mainly test the JSON output format of the tool. That -format is meant for consumption by other tools, and it is thus more -important that it stays stable. The text output that is meant for -human consumption may change at will, so it's not worth testing. - -## Smoke test - -_Requirement: The tool can report its version._ - -Justification: This is useful mainly to make sure the tool can be run -at all. As such, it acts as a simple [smoke -test](https://en.wikipedia.org/wiki/Smoke_testing_(software)). -However, if this fails, then nothing else has a chance to work. - -Note that this is not in JSON format, as it is output by the `clap` -library, and `opgpcard` doesn't affect what it looks like. - -~~~scenario -given an installed opgpcard -when I run opgpcard --version -then stdout matches regex ^opgpcard \d+\.\d+\.\d+$ -~~~ - -## List cards: `opgpcard list` - -_Requirement: The tool lists available cards._ - -This is not at all a thorough test, but it exercises the simple happy -paths of the subcommand. - -~~~scenario -given an installed opgpcard -when I run opgpcard --output-format=json list -then stdout, as JSON, matches embedded file list.json -~~~ - -~~~{#list.json .file .json} -{ - "idents": ["AFAF:00001234"] -} -~~~ - -## Card status: `opgpcard status` - -_Requirement: The tool shows status of available cards._ - -This is not at all a thorough test, but it exercises the simple happy -paths of the subcommand. - -~~~scenario -given an installed opgpcard -when I run opgpcard --output-format=json status -then stdout, as JSON, matches embedded file status.json -~~~ - -~~~{#status.json .file .json} -{ - "card_version": "2.0", - "ident": "AFAF:00001234" -} -~~~ - -## Card information: `opgpcard info` - -_Requirement: The tool shows information about available cards._ - -This is not at all a thorough test, but it exercises the simple happy -paths of the subcommand. - -~~~scenario -given an installed opgpcard -when I run opgpcard --output-format=json info -then stdout, as JSON, matches embedded file info.json -~~~ - -~~~{#info.json .file .json} -{ - "card_version": "2.0", - "application_id": "D276000124 01 0200 AFAF 00001234 0000", - "manufacturer_id": "AFAF", - "manufacturer_name": "Unknown", - "card_service_data": [], - "ident": "AFAF:00001234" -} -~~~ - -## Key generation: `opgpcard generate` and `opgpcard decrypt` - -_Requirement: The tool is able to generate keys and use them for decryption._ - -This is not at all a thorough test, but it exercises the simple happy -paths of the subcommand. - -~~~scenario -given an installed opgpcard -given file admin.pin -given file user.pin -when I run opgpcard admin --card AFAF:00001234 --admin-pin admin.pin generate --user-pin user.pin --output certfile -then file certfile contains "-----BEGIN PGP PUBLIC KEY BLOCK-----" -then file certfile contains "-----END PGP PUBLIC KEY BLOCK-----" - -given file message -when I run sq encrypt message --recipient-cert certfile --output message.enc -and I run opgpcard decrypt --card AFAF:00001234 --user-pin user.pin message.enc --output message.dec -then files message and message.dec match -~~~ - -~~~{#admin.pin .file} -12345678 -~~~ - -~~~{#user.pin .file} -123456 -~~~ - -~~~{#message .file} -Hello World! -~~~ - -## Key generation: `opgpcard generate` and `opgpcard sign` - -_Requirement: The tool is able to generate keys and use them for signing._ - -This is not at all a thorough test, but it exercises the simple happy -paths of the subcommand. - -~~~scenario -given an installed opgpcard -given file admin.pin -given file user.pin -when I run opgpcard admin --card AFAF:00001234 --admin-pin admin.pin generate --user-pin user.pin --output certfile -then file certfile contains "-----BEGIN PGP PUBLIC KEY BLOCK-----" -then file certfile contains "-----END PGP PUBLIC KEY BLOCK-----" - -given file message -when I run opgpcard sign message --card AFAF:00001234 --user-pin user.pin --detached --output message.sig -when I run sq verify message --detached message.sig --signer-cert certfile -then stderr contains "1 good signature." -~~~ - -## Key import: `opgpcard import` and `opgpcard decrypt` - -_Requirement: The tool is able to import keys and use them for decryption._ - -This is not at all a thorough test, but it exercises the simple happy -paths of the subcommand. - -~~~scenario -given an installed opgpcard -given file admin.pin -given file user.pin -given file nist256key -when I run opgpcard admin --card AFAF:00001234 --admin-pin admin.pin import nist256key -then stdout contains "CCCFFFAAC77C9F9D3BB2D2CA3C93515DA813C03F" -then stdout contains "360EC3C59A7D8E51DCE9FA1171858B15EE7F4BCA" -then stdout contains "6D186AC7C6761FC22BE07557D2BE4918C44C74D9" - -given file message -when I run sq encrypt message --recipient-cert nist256key --output message.enc -and I run opgpcard decrypt --card AFAF:00001234 --user-pin user.pin message.enc --output message.dec -then files message and message.dec match -~~~ - -## Key import: `opgpcard import` and `opgpcard sign` - -_Requirement: The tool is able to import keys and use them for signing._ - -This is not at all a thorough test, but it exercises the simple happy -paths of the subcommand. - -~~~scenario -given an installed opgpcard -given file admin.pin -given file nist256key -when I run opgpcard admin --card AFAF:00001234 --admin-pin admin.pin import nist256key -then stdout contains "CCCFFFAAC77C9F9D3BB2D2CA3C93515DA813C03F" -then stdout contains "360EC3C59A7D8E51DCE9FA1171858B15EE7F4BCA" -then stdout contains "6D186AC7C6761FC22BE07557D2BE4918C44C74D9" - -given file user.pin -given file message -when I run opgpcard sign message --card AFAF:00001234 --user-pin user.pin --detached --output message.sig -when I run sq verify message --detached message.sig --signer-cert nist256key -then stderr contains "1 good signature." -~~~ - -~~~{#nist256key .file} ------BEGIN PGP PRIVATE KEY BLOCK----- - -lHcEYPP/JxMIKoZIzj0DAQcCAwT5a9JIM6BX1zxvFkNr2LMGLyTw72+iXsUZlA8X -w3Bn91jVRpSSIITjibHKliS2e2kZlaoHOZvlXmZ3nqOANjV+AAEAzCPG24MzHigZ -qyoaNr+7o6u/D8DndXHhsrERqm9cCgcOybQfTklTVCBQMjU2IDxuaXN0MjU2QGV4 -YW1wbGUub3JnPoiQBBMTCAA4FiEEzM//qsd8n507stLKPJNRXagTwD8FAmDz/ycC -GwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQPJNRXagTwD+bZAD/fu4NjabH -GKHB1dIpqX+opDt8E3RFes58P+p4wh8W+xEBAMcPs6HLYvcLLkqtpV06wKYngPY+ -Ln/wcpQOagwO+EgfnHsEYPP/JxIIKoZIzj0DAQcCAwTtyP4rOGNlU+Tzpa7UYv5h -jR/T9DzMVUntaFhb3Cm0ung7IEGNAOcbgpCx/fdm7BPL+9MJB+qwpsz8bQa4DfnE -AwEIBwABALvh9XLpqe1MqwPodYlWKgw4me/tR2FNKmLXPC1gl3g7EAeIeAQYEwgA -IBYhBMzP/6rHfJ+dO7LSyjyTUV2oE8A/BQJg8/8nAhsMAAoJEDyTUV2oE8A/SMMA -/3DuQU8hb+U9U2nX93bHwpTBQfAONsEn/vUeZ6u4NdX4AP9ABH//08SFfFttiWHm -TTAR9e57Rw0DhI/wb6qqWABIyZx3BGDz/zkTCCqGSM49AwEHAgMEJz+bbG6RHQag -BoULLuklPRUtQauVTxM9WZZG3PEAnIZuu4LKkHn/JPAN04iSV+K3lBWN+HALVZSV -kFweNSOX6gAA/RD5JKvdwS3CofhQY+czewkb8feXGLQIaPS9rIWP7QX4En2IeAQY -EwgAIBYhBMzP/6rHfJ+dO7LSyjyTUV2oE8A/BQJg8/85AhsgAAoJEDyTUV2oE8A/ -CSkA/2WnUoIwtv4ZBhuCpJY/GIFqRJEPgQ7DW1bXTrYsoTehAQD1wDkG0vD6Jnfu -QIPHexNllmYakW7WNqu1gobPuNEQyw== -=E2Hb ------END PGP PRIVATE KEY BLOCK----- -~~~ diff --git a/tools/subplot/opgpcard.rs b/tools/subplot/opgpcard.rs deleted file mode 100644 index 0297eb4..0000000 --- a/tools/subplot/opgpcard.rs +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lars Wirzenius -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use subplotlib::file::SubplotDataFile; -use subplotlib::steplibrary::runcmd::Runcmd; - -use serde_json::Value; -use std::path::Path; - -#[derive(Debug, Default)] -struct SubplotContext {} - -impl ContextElement for SubplotContext {} - -#[step] -#[context(SubplotContext)] -#[context(Runcmd)] -fn install_opgpcard(context: &ScenarioContext) { - let target_exe = env!("CARGO_BIN_EXE_opgpcard"); - let target_path = Path::new(target_exe); - let target_path = target_path.parent().ok_or("No parent?")?; - context.with_mut( - |context: &mut Runcmd| { - context.prepend_to_path(target_path); - Ok(()) - }, - false, - )?; -} - -#[step] -#[context(Runcmd)] -fn stdout_matches_json_file(context: &ScenarioContext, file: SubplotDataFile) { - let expected: Value = serde_json::from_slice(file.data())?; - println!("expecting JSON: {:#?}", expected); - - let stdout = context.with(|runcmd: &Runcmd| Ok(runcmd.stdout_as_string()), false)?; - let actual: Value = serde_json::from_str(&stdout)?; - println!("stdout JSON: {:#?}", actual); - - println!("fuzzy checking JSON values"); - assert!(json_eq(&actual, &expected)); -} - -// Fuzzy match JSON values. For objects, anything in expected must be -// in actual, but it's OK for there to be extra things. -fn json_eq(actual: &Value, expected: &Value) -> bool { - match actual { - Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => { - println!("simple value"); - println!("actual ={:?}", actual); - println!("expected={:?}", expected); - let eq = actual == expected; - println!("simple value eq={}", eq); - return eq; - } - Value::Array(a_values) => { - if let Value::Array(e_values) = expected { - println!("both actual and equal are arrays"); - for (a_value, e_value) in a_values.iter().zip(e_values.iter()) { - println!("comparing corresponding array elements"); - if !json_eq(a_value, e_value) { - println!("array elements differ"); - return false; - } - } - println!("arrays match"); - return true; - } else { - println!("actual is array, expected is not"); - return false; - } - } - Value::Object(a_obj) => { - if let Value::Object(e_obj) = expected { - println!("both actual and equal are objects"); - for key in e_obj.keys() { - println!("checking key {}", key); - if !a_obj.contains_key(key) { - println!("key {} is missing from actual", key); - return false; - } - let a_value = a_obj.get(key).unwrap(); - let e_value = e_obj.get(key).unwrap(); - let eq = json_eq(a_value, e_value); - println!("values for {} eq={}", key, eq); - if !eq { - return false; - } - } - println!("objects match"); - return true; - } else { - println!("actual is object, expected is not"); - return false; - } - } - } -} diff --git a/tools/subplot/opgpcard.subplot b/tools/subplot/opgpcard.subplot deleted file mode 100644 index 855d350..0000000 --- a/tools/subplot/opgpcard.subplot +++ /dev/null @@ -1,15 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Lars Wirzenius -# SPDX-License-Identifier: MIT OR Apache-2.0 - -title: "opgpcard acceptance tests" -markdowns: - - opgpcard.md -bindings: - - opgpcard.yaml - - lib/files.yaml - - lib/runcmd.yaml -impls: - rust: - - opgpcard.rs -classes: - - json diff --git a/tools/subplot/opgpcard.yaml b/tools/subplot/opgpcard.yaml deleted file mode 100644 index 1bdc70b..0000000 --- a/tools/subplot/opgpcard.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Lars Wirzenius -# SPDX-License-Identifier: MIT OR Apache-2.0 - -- given: an installed opgpcard - impl: - rust: - function: install_opgpcard - -- then: "stdout, as JSON, matches embedded file {file:file}" - impl: - rust: - function: stdout_matches_json_file diff --git a/tools/subplot/test-in-docker.sh b/tools/subplot/test-in-docker.sh deleted file mode 100755 index c24ae9a..0000000 --- a/tools/subplot/test-in-docker.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -# -# Run "cargo test" inside a Docker container with virtual cards -# installed and running. This will allow at least rudimentary testing -# of actual card functionality of opgpcard. -# -# SPDX-FileCopyrightText: 2022 Lars Wirzenius -# SPDX-License-Identifier: MIT OR Apache-2.0 - -set -euo pipefail - -image="registry.gitlab.com/openpgp-card/virtual-cards/smartpgp-builddeps" - -src="$(cd .. && pwd)" - -if ! [ -e Cargo.toml ] || ! grep '^name.*openpgp-card-tools' Cargo.toml; then - echo "MUST run this in the root of the openpgp-card-tool crate" 1>&2 - exit 1 -fi - -cargo build -docker run --rm -t \ - --volume "cargo:/cargo" \ - --volume "dotcargo:/root/.cargo" \ - --volume "$src:/opt" "$image" \ - bash -c ' -/etc/init.d/pcscd start && \ -su - -c "sh /home/jcardsim/run-card.sh >/dev/null" jcardsim && \ -cd /opt/tools && env CARGO_TARGET_DIR=/cargo cargo test' diff --git a/tools/tests/opgpcard.rs b/tools/tests/opgpcard.rs deleted file mode 100644 index 72f3554..0000000 --- a/tools/tests/opgpcard.rs +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lars Wirzenius -// SPDX-License-Identifier: MIT OR Apache-2.0 - -#![allow(clippy::needless_return)] - -include!(concat!(env!("OUT_DIR"), "/opgpcard.rs")); From 365670041fa013a815d7bf09532c492ea4923a50 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 8 Apr 2023 13:49:01 +0200 Subject: [PATCH 004/115] openpgp-card-sequoia: fix padding for Curve 25519 private key material. Importing 25519 keys with leading zero bytes led to failures on at least Gnuk and Nitrokey's opgpcard-rs implementation. Reported by Wiktor Kwapisiewicz, also see https://codeberg.org/wiktor/broken-nitro --- openpgp-card-sequoia/src/privkey.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card-sequoia/src/privkey.rs b/openpgp-card-sequoia/src/privkey.rs index 73a6d68..b4e8a73 100644 --- a/openpgp-card-sequoia/src/privkey.rs +++ b/openpgp-card-sequoia/src/privkey.rs @@ -234,11 +234,11 @@ impl EccKey for SqEccKey { } fn private(&self) -> Vec { - // FIXME: padding for 25519? match self.curve { Curve::NistP256 => self.private.value_padded(0x20).to_vec(), Curve::NistP384 => self.private.value_padded(0x30).to_vec(), Curve::NistP521 => self.private.value_padded(0x42).to_vec(), + Curve::Cv25519 | Curve::Ed25519 => self.private.value_padded(0x20).to_vec(), _ => self.private.value().to_vec(), } } From a85d3164d71a97b539793bb7ac131474c7c967dc Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 8 Apr 2023 13:52:54 +0200 Subject: [PATCH 005/115] openpgp-card-sequoia: Release 0.1.2 --- openpgp-card-sequoia/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index d19c008..2cac3ce 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -1,11 +1,11 @@ -# SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +# SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer # SPDX-License-Identifier: MIT OR Apache-2.0 [package] name = "openpgp-card-sequoia" description = "Wrapper of openpgp-card for use with Sequoia PGP" license = "MIT OR Apache-2.0" -version = "0.1.1" +version = "0.1.2" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" From 629eecd5107d37bd8d7e51f35222ec9518be374f Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 13 Apr 2023 18:07:02 +0200 Subject: [PATCH 006/115] Add a comment about the interaction between CardTransaction::init_card_caps and CardTransaction::initialize --- openpgp-card/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index a89a12a..ee0f362 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -70,7 +70,11 @@ pub trait CardTransaction { /// Set the card capabilities in the CardTransaction. /// /// Setting these capabilities is typically part of a bootstrapping - /// process: the information about the card's capabilities is typically + /// process (this fn is typically called from [CardTransaction::initialize]. + /// When implementing CardTransaction, you probably want to call + /// [CardTransaction::initialize] during setup). + /// + /// The information about the card's capabilities is typically /// requested from the card using the same CardTransaction instance, /// before the card's capabilities have been initialized. fn init_card_caps(&mut self, caps: CardCaps); From 4a042d703f3953fa021f17770055ff566eda34b4 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 13 Apr 2023 18:15:58 +0200 Subject: [PATCH 007/115] Don't panic if a long command is sent and the backend reports no support for extended length This case should not happen during normal operation with the pcsc backend. But the condition was triggered in tests with an alternate CardBackend implementation (see: https://gitlab.com/openpgp-card/openpgp-card/-/issues/69). --- openpgp-card/src/apdu/command.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/openpgp-card/src/apdu/command.rs b/openpgp-card/src/apdu/command.rs index cdb6497..1a7c447 100644 --- a/openpgp-card/src/apdu/command.rs +++ b/openpgp-card/src/apdu/command.rs @@ -81,7 +81,7 @@ impl Command { let nc = self.data.len() as u16; let mut buf = vec![self.cla, self.ins, self.p1, self.p2]; - buf.extend(Self::make_lc(nc, ext_len)); + buf.extend(Self::make_lc(nc, ext_len)?); buf.extend(&self.data); buf.extend(Self::make_le(nc, ext_len, expect_response)); @@ -89,21 +89,19 @@ impl Command { } /// Encode len for Lc field - fn make_lc(len: u16, ext_len: bool) -> Vec { - if !ext_len { - assert!( - len <= 0xff, - "{}", - "unexpected: len = {len:x?}, but ext says Short" - ); + fn make_lc(len: u16, ext_len: bool) -> Result, crate::Error> { + if !ext_len && len > 0xff { + return Err(crate::Error::InternalError(format!( + "Command len = {len:x?}, but extended length is unsupported by backend" + ))); } if len == 0 { - vec![] + Ok(vec![]) } else if !ext_len { - vec![len as u8] + Ok(vec![len as u8]) } else { - vec![0, (len >> 8) as u8, (len & 255) as u8] + Ok(vec![0, (len >> 8) as u8, (len & 255) as u8]) } } From 81ef12ffb2a7a6cb2dc3ca54ce4d14d40e3e75d7 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 1 May 2023 18:44:38 +0200 Subject: [PATCH 008/115] ci: set PATH for smartpgp-builddeps image --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2657bb4..9204d33 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -135,6 +135,7 @@ run_cardtest_smartpgp: stage: virtual-test image: registry.gitlab.com/openpgp-card/virtual-cards/smartpgp-builddeps before_script: + - export PATH="$HOME/.cargo/bin:$PATH" - *report-rust script: - sh /start.sh From 50e3d12f26731b43ffdf12d8283f21c04cdb70dc Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 1 May 2023 18:04:15 +0200 Subject: [PATCH 009/115] openpgp-card: Add activate_file() to CardTransaction --- openpgp-card/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index ee0f362..587b6c6 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -111,6 +111,13 @@ pub trait CardTransaction { apdu::send_command(self, select_openpgp, false)?.try_into() } + /// Activate file + fn activate_file(&mut self) -> Result, Error> { + log::info!("CardTransaction: activate_file"); + let activate_file = commands::activate_file(); + apdu::send_command(self, activate_file, false)?.try_into() + } + /// Get the "application related data" from the card. /// /// (This data should probably be cached in a higher layer. Some parts of From 806918b939804889a65751d3be0510215fbc8103 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 1 May 2023 18:04:25 +0200 Subject: [PATCH 010/115] openpgp-card: Release 0.3.5 --- openpgp-card/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card/Cargo.toml b/openpgp-card/Cargo.toml index 7881416..3d7a7fb 100644 --- a/openpgp-card/Cargo.toml +++ b/openpgp-card/Cargo.toml @@ -5,7 +5,7 @@ name = "openpgp-card" description = "A client implementation for the OpenPGP card specification" license = "MIT OR Apache-2.0" -version = "0.3.4" +version = "0.3.5" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" From cf5090bbc6377a29b0705a12f65642e58a17170a Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 1 May 2023 18:06:43 +0200 Subject: [PATCH 011/115] pcsc: add fn PcscBackend::activate_terminated_card() --- pcsc/Cargo.toml | 2 +- pcsc/src/lib.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/pcsc/Cargo.toml b/pcsc/Cargo.toml index 32677d7..cf69846 100644 --- a/pcsc/Cargo.toml +++ b/pcsc/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://gitlab.com/openpgp-card/openpgp-card" documentation = "https://docs.rs/crate/openpgp-card-pcsc" [dependencies] -openpgp-card = { path = "../openpgp-card", version = "0.3" } +openpgp-card = { path = "../openpgp-card", version = "0.3.5" } iso7816-tlv = "0.4" pcsc = "2.7" log = "0.4" diff --git a/pcsc/src/lib.rs b/pcsc/src/lib.rs index 98dedd6..b7f52f8 100644 --- a/pcsc/src/lib.rs +++ b/pcsc/src/lib.rs @@ -646,6 +646,38 @@ impl PcscBackend { fn reader_caps(&self) -> HashMap { self.reader_caps.clone() } + + /// This command will try to activate an OpenPGP card, if: + /// - exactly one card is connected to the system + /// - that card replies to SELECT with Status 6285 + /// + /// See OpenPGP card spec (version 3.4.1): 7.2.17 ACTIVATE FILE + pub fn activate_terminated_card() -> Result<(), Error> { + let mut cards = + Self::raw_pcsc_cards(pcsc::ShareMode::Exclusive).map_err(Error::Smartcard)?; + if cards.len() != 1 { + return Err(Error::InternalError(format!( + "This command is only allowed if exactly one card is connected, found {}.", + cards.len() + ))); + } + + let card = cards.pop().unwrap(); + + let mut backend = PcscBackend::new(card, pcsc::ShareMode::Exclusive); + let mut card_tx = Box::new(PcscTransaction::new(&mut backend, false)?); + + match ::select(&mut card_tx) { + Err(Error::CardStatus(openpgp_card::StatusBytes::TerminationState)) => { + let _ = ::activate_file(&mut card_tx)?; + Ok(()) + } + + _ => Err(Error::InternalError( + "Card doesn't appear to be terminated.".to_string(), + )), + } + } } impl CardBackend for PcscBackend { From 321258ec06b96f579daf8a983624ad0e9d041499 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 1 May 2023 18:09:09 +0200 Subject: [PATCH 012/115] pcsc: Release 0.3.1 --- pcsc/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcsc/Cargo.toml b/pcsc/Cargo.toml index cf69846..aed8160 100644 --- a/pcsc/Cargo.toml +++ b/pcsc/Cargo.toml @@ -6,7 +6,7 @@ name = "openpgp-card-pcsc" description = "PCSC OpenPGP card backend, for use with the openpgp-card crate" authors = ["Heiko Schaefer "] license = "MIT OR Apache-2.0" -version = "0.3.0" +version = "0.3.1" edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" documentation = "https://docs.rs/crate/openpgp-card-pcsc" From 5e96cb6f07a233b85a2ead82fbc46cba509bb04b Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 19 May 2023 15:01:33 +0200 Subject: [PATCH 013/115] ci: fix opgpcard-rs test image There is currently no opcard-rs-builddeps image, use opcard-rs-tools instead. --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9204d33..cb22e37 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -154,7 +154,7 @@ run_cardtest_smartpgp: run_cardtest_opcard_rs: stage: virtual-test - image: registry.gitlab.com/openpgp-card/virtual-cards/opcard-rs-builddeps + image: registry.gitlab.com/openpgp-card/virtual-cards/opcard-rs-tools before_script: - *report-rust script: @@ -169,8 +169,8 @@ run_cardtest_opcard_rs: # inherit all general cache settings <<: *general_cache_config # override the key - # (the base image of run_cardtest uses bookworm) - key: "bookworm" + # (the base image is separate from the other virtual cards) + key: "opcard_rs" run_cardtest_ykneo: stage: virtual-test From ff8554de3b5fd89399bcfa9eddc34cbdac430f6a Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 19 May 2023 15:57:55 +0200 Subject: [PATCH 014/115] ci: manually add "$HOME/.cargo/bin" to PATH --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cb22e37..1fb1e06 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -156,6 +156,7 @@ run_cardtest_opcard_rs: stage: virtual-test image: registry.gitlab.com/openpgp-card/virtual-cards/opcard-rs-tools before_script: + - export PATH="$HOME/.cargo/bin:$PATH" - *report-rust script: - sh /start.sh @@ -176,6 +177,7 @@ run_cardtest_ykneo: stage: virtual-test image: registry.gitlab.com/openpgp-card/virtual-cards/ykneo-builddeps before_script: + - export PATH="$HOME/.cargo/bin:$PATH" - *report-rust script: - sh /start.sh @@ -196,6 +198,7 @@ run_cardtest_fluffypgp: stage: virtual-test image: registry.gitlab.com/openpgp-card/virtual-cards/fluffypgp-builddeps before_script: + - export PATH="$HOME/.cargo/bin:$PATH" - *report-rust script: - sh /start.sh From 4ef4291b5398d9c000b980eeeb7a3bbb26a6d9e2 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 19 May 2023 14:15:31 +0200 Subject: [PATCH 015/115] ci: manually downgrade lalrpop on debian bookworm-based images This is a workaround to run tests on debian bookworm (with Rust 1.63). --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1fb1e06..c16633d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -121,6 +121,7 @@ cargo-test-debian-bookworm: - apt install -y -qq --no-install-recommends git rustc cargo clang make pkg-config nettle-dev libssl-dev capnproto ca-certificates libpcsclite-dev - apt clean - *report-rust + - cargo update -p lalrpop@0.20.0 --precise 0.19.12 # hack to work with Rust 1.63 script: # there is no virtual card in this image, so subplot does not generate tests # that would require one. @@ -179,6 +180,7 @@ run_cardtest_ykneo: before_script: - export PATH="$HOME/.cargo/bin:$PATH" - *report-rust + - cargo update -p lalrpop@0.20.0 --precise 0.19.12 # hack to work with Rust 1.63 script: - sh /start.sh # - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- status @@ -200,6 +202,7 @@ run_cardtest_fluffypgp: before_script: - export PATH="$HOME/.cargo/bin:$PATH" - *report-rust + - cargo update -p lalrpop@0.20.0 --precise 0.19.12 # hack to work with Rust 1.63 script: - sh /start.sh # - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- status From d0fc4b5725356dab0e55a790446d62f121c85399 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 19 May 2023 13:36:21 +0200 Subject: [PATCH 016/115] ci: Add tests against canokey virtual card (see https://github.com/canokeys/canokey-core) Fixes #4. --- .gitlab-ci.yml | 21 +++++++++++++++++++++ card-functionality/ci/virt-canokey.toml | 17 +++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 card-functionality/ci/virt-canokey.toml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c16633d..41eeaf3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -174,6 +174,27 @@ run_cardtest_opcard_rs: # (the base image is separate from the other virtual cards) key: "opcard_rs" +run_cardtest_canokey: + stage: virtual-test + image: registry.gitlab.com/openpgp-card/virtual-cards/canokey-builddeps + before_script: + - export PATH="$HOME/.cargo/bin:$PATH" + - *report-rust + script: + - sh /start.sh && sleep 10 + # - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- status + # - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- info + - RUST_BACKTRACE=1 cargo run -p openpgp-card-tests --bin import -- $CONFIG + - RUST_BACKTRACE=1 cargo run -p openpgp-card-tests --bin keygen -- $CONFIG + variables: + CONFIG: "card-functionality/ci/virt-canokey.toml" + cache: + # inherit all general cache settings + <<: *general_cache_config + # override the key + # (the base image of canokey-builddeps is distinct from the other virtual card images) + key: "canokey" + run_cardtest_ykneo: stage: virtual-test image: registry.gitlab.com/openpgp-card/virtual-cards/ykneo-builddeps diff --git a/card-functionality/ci/virt-canokey.toml b/card-functionality/ci/virt-canokey.toml new file mode 100644 index 0000000..2d26780 --- /dev/null +++ b/card-functionality/ci/virt-canokey.toml @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2021 Heiko Schaefer +# SPDX-License-Identifier: CC0-1.0 + +[card.smartpgp] +backend.pcsc = "F1D0:00000000" +config.keygen = [ + "RSA2k", "RSA3k", "RSA4k", + "NIST256", "NIST384", "Curve25519" +] +config.import = [ + "card-functionality/data/rsa2k.sec", + "card-functionality/data/rsa3k.sec", + "card-functionality/data/rsa4k.sec", + "card-functionality/data/nist256.sec", + "card-functionality/data/nist384.sec", + "card-functionality/data/25519.sec", +] From b852393039532d8e5751986fc7bbad535663a013 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 4 Aug 2023 14:20:43 +0200 Subject: [PATCH 017/115] openpgp-card: add OpenPgp::into_card() --- openpgp-card/src/openpgp.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpgp-card/src/openpgp.rs b/openpgp-card/src/openpgp.rs index c69374f..1b198b4 100644 --- a/openpgp-card/src/openpgp.rs +++ b/openpgp-card/src/openpgp.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use std::convert::{TryFrom, TryInto}; @@ -37,6 +37,14 @@ impl OpenPgp { } } + /// Get the internal `CardBackend`. + /// + /// This is useful to perform operations on the card with a different crate, + /// e.g. `yubikey-management`. + pub fn into_card(self) -> Box { + self.card + } + /// Get an OpenPgpTransaction object. This starts a transaction on the underlying /// CardBackend. /// From 6e20b9bde4cbbe53dbf1b7b7f8619354eea69688 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 4 Aug 2023 14:23:22 +0200 Subject: [PATCH 018/115] openpgp-card: release 0.3.6 --- openpgp-card/Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpgp-card/Cargo.toml b/openpgp-card/Cargo.toml index 3d7a7fb..bffa50f 100644 --- a/openpgp-card/Cargo.toml +++ b/openpgp-card/Cargo.toml @@ -1,18 +1,18 @@ -# SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +# SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer # SPDX-License-Identifier: MIT OR Apache-2.0 [package] name = "openpgp-card" description = "A client implementation for the OpenPGP card specification" license = "MIT OR Apache-2.0" -version = "0.3.5" +version = "0.3.6" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" documentation = "https://docs.rs/crate/openpgp-card" [dependencies] -blanket = "0.2.0" +blanket = "0.3" nom = "7" hex-slice = "0.1" thiserror = "1" @@ -20,4 +20,4 @@ log = "0.4" chrono = "0.4" [dev-dependencies] -hex-literal = "0.3" +hex-literal = "0.4" From 822b7f3b551e0bfa709d026411e8e1e1bf24129e Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 4 Aug 2023 14:39:55 +0200 Subject: [PATCH 019/115] clippy lint fix --- scdc/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index 2632d84..a8914d3 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -203,7 +203,7 @@ impl ScdBackend { return Err(Error::Smartcard(SmartcardError::Error(format!("{e:?}")))); } - if let Ok(..) = response { + if response.is_ok() { // drop remaining lines while let Some(_drop) = rt.block_on(self.agent.next()) { log::trace!(" drop: {:x?}", _drop); From 1be9cea119b12207ec8f453e0afc70519d237181 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 4 Aug 2023 14:37:48 +0200 Subject: [PATCH 020/115] ci: allow clippy job to fail --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 41eeaf3..7e590be 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -74,6 +74,7 @@ cargo-clippy: script: - rustup component add clippy - cargo clippy --verbose --tests -- -D warnings + allow_failure: true cache: # inherit all general cache settings <<: *general_cache_config From 06cb246a79e1cc634df5a3f8879296d425ad071b Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 4 Aug 2023 14:58:42 +0200 Subject: [PATCH 021/115] ci: allow failure for debian job --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7e590be..875406d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -127,6 +127,7 @@ cargo-test-debian-bookworm: # there is no virtual card in this image, so subplot does not generate tests # that would require one. - cargo test + allow_failure: true cache: # inherit all general cache settings <<: *general_cache_config From 7c42ec4d5e7e77742d389f71bb7920444d102432 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 4 Aug 2023 14:27:46 +0200 Subject: [PATCH 022/115] openpgp-card-sequoia: add Card::into_card() --- openpgp-card-sequoia/src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 6928b23..d67fd25 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! This crate offers ergonomic abstractions to use @@ -207,6 +207,14 @@ impl Card { Card::::new(opt) } + + /// Get the internal `CardBackend`. + /// + /// This is useful to perform operations on the card with a different crate, + /// e.g. `yubikey-management`. + pub fn into_card(self) -> Box { + self.state.pgp.into_card() + } } impl<'a> Card> { From da60687e252d230ef765bdda39fe0768b07999d3 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 4 Aug 2023 15:08:08 +0200 Subject: [PATCH 023/115] openpgp-card-sequoia: update dependency --- openpgp-card-sequoia/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index 2cac3ce..229179c 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -23,7 +23,7 @@ rsa = "0.8.1" [dev-dependencies] openpgp-card-pcsc = { path = "../pcsc", version = "0.3" } #openpgp-card-scdc = { path = "../scdc", version = "0.3" } -env_logger = "0.9" +env_logger = "0.10" testresult = "0.3.0" [features] From bf62541b486cd1194b8e00be9e890c07a66f86e1 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 4 Aug 2023 14:34:21 +0200 Subject: [PATCH 024/115] openpgp-card-sequoia: Release 0.1.3 --- openpgp-card-sequoia/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index 229179c..733a0fd 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -5,7 +5,7 @@ name = "openpgp-card-sequoia" description = "Wrapper of openpgp-card for use with Sequoia PGP" license = "MIT OR Apache-2.0" -version = "0.1.2" +version = "0.1.3" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" From 379a41f4ebbc3b2810c07aaceec7d5895125fa18 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 16 Aug 2023 09:55:20 +0200 Subject: [PATCH 025/115] openpgp-card-sequoia: fix dependency on openpgp-card openpgp-card 0.3.6 is needed for OpenPgp::into_card(). --- openpgp-card-sequoia/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index 733a0fd..91eb586 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -13,7 +13,7 @@ documentation = "https://docs.rs/crate/openpgp-card-sequoia" [dependencies] sequoia-openpgp = { version = "1.4", default-features = false } -openpgp-card = { path = "../openpgp-card", version = "0.3.3" } +openpgp-card = { path = "../openpgp-card", version = "0.3.6" } chrono = "0.4" anyhow = "1" thiserror = "1" From 5a77950f85a894773a8dc036e0235a7b0b49f6dd Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 16 Aug 2023 09:56:01 +0200 Subject: [PATCH 026/115] openpgp-card-sequoia: Release 0.1.4 --- openpgp-card-sequoia/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index 91eb586..f2d653f 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -5,7 +5,7 @@ name = "openpgp-card-sequoia" description = "Wrapper of openpgp-card for use with Sequoia PGP" license = "MIT OR Apache-2.0" -version = "0.1.3" +version = "0.1.4" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" From 61ab492d9c56fb7063195dab79f5aa27e1458925 Mon Sep 17 00:00:00 2001 From: Patryk Cisek Date: Wed, 16 Aug 2023 18:41:27 -0700 Subject: [PATCH 027/115] Added login data Added ability to read and set login data field. --- card-functionality/src/import.rs | 4 ++++ card-functionality/src/tests.rs | 16 ++++++++++++++++ openpgp-card/src/apdu/commands.rs | 10 ++++++++++ openpgp-card/src/openpgp.rs | 15 +++++++++++++++ 4 files changed, 45 insertions(+) diff --git a/card-functionality/src/import.rs b/card-functionality/src/import.rs index da2346e..b477117 100644 --- a/card-functionality/src/import.rs +++ b/card-functionality/src/import.rs @@ -40,6 +40,10 @@ fn main() -> Result<()> { } }; + println!("Set login data"); + let login_data_out = run_test(&mut card, test_set_login_data, &[])?; + println!(" {login_data_out:x?}"); + for key_file in &key_files { // upload keys print!("Upload key '{key_file}'"); diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index ca9f53b..8bac04a 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -352,6 +352,22 @@ pub fn test_set_user_data(pgp: &mut OpenPgp, _param: &[&str]) -> Result Result { + let mut pgpt = pgp.transaction()?; + + pgpt.verify_pw3(b"12345678")?; + + let test_login = b"someone@somewhere.com"; + pgpt.set_login(test_login)?; + + // Read the previously set login data + let read_login_data = pgpt.login_data()?; + + assert_eq!(read_login_data, test_login.to_vec()); + + Ok(vec![]) +} + pub fn test_private_data(pgp: &mut OpenPgp, _param: &[&str]) -> Result { let mut pgpt = pgp.transaction()?; diff --git a/openpgp-card/src/apdu/commands.rs b/openpgp-card/src/apdu/commands.rs index fc05677..c59fd99 100644 --- a/openpgp-card/src/apdu/commands.rs +++ b/openpgp-card/src/apdu/commands.rs @@ -47,6 +47,11 @@ pub(crate) fn url() -> Command { get_data(Tags::Url) } +/// GET DO "Login Data" +pub(crate) fn login_data() -> Command { + get_data(Tags::LoginData) +} + /// GET DO "Cardholder related data" pub(crate) fn cardholder_related_data() -> Command { get_data(Tags::CardholderRelatedData) @@ -131,6 +136,11 @@ pub(crate) fn put_private_use_do(num: u8, data: Vec) -> Command { } } +/// PUT DO Login Data +pub(crate) fn put_login_data(login_data: Vec) -> Command { + put_data(Tags::LoginData, login_data) +} + /// PUT DO Name pub(crate) fn put_name(name: Vec) -> Command { put_data(Tags::Name, name) diff --git a/openpgp-card/src/openpgp.rs b/openpgp-card/src/openpgp.rs index 1b198b4..1fcfd3f 100644 --- a/openpgp-card/src/openpgp.rs +++ b/openpgp-card/src/openpgp.rs @@ -110,6 +110,15 @@ impl<'a> OpenPgpTransaction<'a> { Ok(resp.data()?.to_vec()) } + /// Get Login Data (5e) + pub fn login_data(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: login_data"); + + let resp = apdu::send_command(self.tx(), commands::login_data(), true)?; + + Ok(resp.data()?.to_vec()) + } + /// Get cardholder related data (65) pub fn cardholder_related_data(&mut self) -> Result { log::info!("OpenPgpTransaction: cardholder_related_data"); @@ -717,6 +726,12 @@ impl<'a> OpenPgpTransaction<'a> { Ok(resp.data()?.to_vec()) } + pub fn set_login(&mut self, login: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_login"); + let put_login_data = commands::put_login_data(login.to_vec()); + apdu::send_command(self.tx(), put_login_data, false)?.try_into() + } + pub fn set_name(&mut self, name: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_name"); From 00345fff2cf0c520f1445f3520df97c610f60f63 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 17 Aug 2023 12:57:03 +0200 Subject: [PATCH 028/115] openpgp-card-sequoia: expose login_data getter/setter --- openpgp-card-sequoia/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index d67fd25..8b7b07c 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -435,6 +435,9 @@ impl<'a> Card> { // --- optional private DOs (0101 - 0104) --- // --- login data (5e) --- + pub fn login_data(&mut self) -> Result { + Ok(String::from_utf8_lossy(&self.state.opt.login_data()?).to_string()) + } // --- URL (5f50) --- @@ -775,6 +778,10 @@ impl Card> { self.card().set_sex(sex) } + pub fn set_login_data(&mut self, login_data: &str) -> Result<(), Error> { + self.card().set_login(login_data.as_bytes()) + } + /// Set "hardholder" URL on the card. /// /// "The URL should contain a link to a set of public keys in OpenPGP format, related to From b19d6ca305a40ca4dd7768e0a0a0ad6dfddcc47c Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 14 Aug 2023 18:16:00 +0200 Subject: [PATCH 029/115] card-functionality: also test RSA3k in opcard-rs New feature since opcard-rs v0.4.0: https://github.com/Nitrokey/opcard-rs/releases/tag/v0.4.0 --- card-functionality/ci/virt-opcard-rs.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/card-functionality/ci/virt-opcard-rs.toml b/card-functionality/ci/virt-opcard-rs.toml index bba5f72..5e12c0c 100644 --- a/card-functionality/ci/virt-opcard-rs.toml +++ b/card-functionality/ci/virt-opcard-rs.toml @@ -4,11 +4,12 @@ [card.opcard-rs] backend.pcsc = "0000:00000000" config.keygen = [ - "RSA2k", "RSA4k", + "RSA2k", "RSA3k", "RSA4k", "NIST256", "Curve25519" ] config.import = [ "card-functionality/data/rsa2k.sec", + "card-functionality/data/rsa3k.sec", "card-functionality/data/rsa4k.sec", "card-functionality/data/nist256.sec", "card-functionality/data/25519.sec", From 99ca7f4882a3cdae086ff7bafc0c431f81306d8a Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 17 Aug 2023 13:57:12 +0200 Subject: [PATCH 030/115] openpgp-card: adjust debug hex format for easier re-use of the output Thanks to @sosthene-nitrokey for the suggestion :) --- openpgp-card/src/apdu.rs | 12 ++++++------ openpgp-card/src/lib.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpgp-card/src/apdu.rs b/openpgp-card/src/apdu.rs index e14c4f0..41eb698 100644 --- a/openpgp-card/src/apdu.rs +++ b/openpgp-card/src/apdu.rs @@ -29,7 +29,7 @@ pub(crate) fn send_command( where C: CardTransaction + ?Sized, { - log::debug!(" -> full APDU command: {:x?}", cmd); + log::debug!(" -> full APDU command: {:02x?}", cmd); let mut resp = RawResponse::try_from(send_command_low_level( card_tx, @@ -69,7 +69,7 @@ where } log::debug!( - " <- APDU response [len {}]: {:x?}", + " <- APDU response [len {}]: {:02x?}", resp.raw_data().len(), resp ); @@ -158,11 +158,11 @@ where let serialized = partial.serialize(ext_len, expect_response)?; - log::trace!(" -> chained APDU command: {:x?}", &serialized); + log::trace!(" -> chained APDU command: {:02x?}", &serialized); let resp = card_tx.transmit(&serialized, buf_size)?; - log::trace!(" <- APDU response: {:x?}", &resp); + log::trace!(" <- APDU response: {:02x?}", &resp); if resp.len() < 2 { return Err(Error::ResponseLength(resp.len())); @@ -200,11 +200,11 @@ where return Err(Error::CommandTooLong(serialized.len())); } - log::trace!(" -> APDU command: {:x?}", &serialized); + log::trace!(" -> APDU command: {:02x?}", &serialized); let resp = card_tx.transmit(&serialized, buf_size)?; - log::trace!(" <- APDU response: {:x?}", resp); + log::trace!(" <- APDU response: {:02x?}", resp); Ok(resp) } diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 587b6c6..07e8146 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -128,7 +128,7 @@ pub trait CardTransaction { let resp = apdu::send_command(self, ad, true)?; let value = Value::from(resp.data()?, true)?; - log::trace!(" ARD value: {:x?}", value); + log::trace!(" ARD value: {:02x?}", value); Ok(ApplicationRelatedData(Tlv::new( Tags::ApplicationRelatedData, From bf41ab43161895706f3bc186af772756c44a99b8 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 19 Aug 2023 17:24:12 +0200 Subject: [PATCH 031/115] release openpgp-card 0.3.7, openpgp-card-sequoia 0.1.5 --- openpgp-card-sequoia/Cargo.toml | 4 ++-- openpgp-card/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index f2d653f..2d4970c 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -5,7 +5,7 @@ name = "openpgp-card-sequoia" description = "Wrapper of openpgp-card for use with Sequoia PGP" license = "MIT OR Apache-2.0" -version = "0.1.4" +version = "0.1.5" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" @@ -13,7 +13,7 @@ documentation = "https://docs.rs/crate/openpgp-card-sequoia" [dependencies] sequoia-openpgp = { version = "1.4", default-features = false } -openpgp-card = { path = "../openpgp-card", version = "0.3.6" } +openpgp-card = { path = "../openpgp-card", version = "0.3.7" } chrono = "0.4" anyhow = "1" thiserror = "1" diff --git a/openpgp-card/Cargo.toml b/openpgp-card/Cargo.toml index bffa50f..d6dcef6 100644 --- a/openpgp-card/Cargo.toml +++ b/openpgp-card/Cargo.toml @@ -5,7 +5,7 @@ name = "openpgp-card" description = "A client implementation for the OpenPGP card specification" license = "MIT OR Apache-2.0" -version = "0.3.6" +version = "0.3.7" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" From 0b5281b56a51fc6b1813e1b1be535ffcb29b2047 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 24 Aug 2023 06:30:15 +0200 Subject: [PATCH 032/115] ci: patch dependencies for rust 1.63-based jobs --- .gitlab-ci.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 875406d..27d3acf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -122,7 +122,8 @@ cargo-test-debian-bookworm: - apt install -y -qq --no-install-recommends git rustc cargo clang make pkg-config nettle-dev libssl-dev capnproto ca-certificates libpcsclite-dev - apt clean - *report-rust - - cargo update -p lalrpop@0.20.0 --precise 0.19.12 # hack to work with Rust 1.63 + - cargo update -p lalrpop --precise 0.19.12 # hack to work with Rust 1.63 + - cargo update -p petgraph --precise 0.6.3 # hack to work with Rust 1.63 script: # there is no virtual card in this image, so subplot does not generate tests # that would require one. @@ -203,7 +204,8 @@ run_cardtest_ykneo: before_script: - export PATH="$HOME/.cargo/bin:$PATH" - *report-rust - - cargo update -p lalrpop@0.20.0 --precise 0.19.12 # hack to work with Rust 1.63 + - cargo update -p lalrpop --precise 0.19.12 # hack to work with Rust 1.63 + - cargo update -p petgraph --precise 0.6.3 # hack to work with Rust 1.63 script: - sh /start.sh # - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- status @@ -225,7 +227,8 @@ run_cardtest_fluffypgp: before_script: - export PATH="$HOME/.cargo/bin:$PATH" - *report-rust - - cargo update -p lalrpop@0.20.0 --precise 0.19.12 # hack to work with Rust 1.63 + - cargo update -p lalrpop --precise 0.19.12 # hack to work with Rust 1.63 + - cargo update -p petgraph --precise 0.6.3 # hack to work with Rust 1.63 script: - sh /start.sh # - RUST_BACKTRACE=1 cargo run -p openpgp-card-tools --bin opgpcard -- status From ce1e0e0bc40f8c17a9a4d4eba782c667edf661ff Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 24 Aug 2023 18:06:03 +0200 Subject: [PATCH 033/115] ci: fix dependencies for rust 1.63-based jobs --- .gitlab-ci.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 27d3acf..a14bedf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -122,7 +122,8 @@ cargo-test-debian-bookworm: - apt install -y -qq --no-install-recommends git rustc cargo clang make pkg-config nettle-dev libssl-dev capnproto ca-certificates libpcsclite-dev - apt clean - *report-rust - - cargo update -p lalrpop --precise 0.19.12 # hack to work with Rust 1.63 + - cargo update + - cargo update -p lalrpop@0.20.0 --precise 0.19.12 # hack to work with Rust 1.63 - cargo update -p petgraph --precise 0.6.3 # hack to work with Rust 1.63 script: # there is no virtual card in this image, so subplot does not generate tests @@ -204,7 +205,8 @@ run_cardtest_ykneo: before_script: - export PATH="$HOME/.cargo/bin:$PATH" - *report-rust - - cargo update -p lalrpop --precise 0.19.12 # hack to work with Rust 1.63 + - cargo update + - cargo update -p lalrpop@0.20.0 --precise 0.19.12 # hack to work with Rust 1.63 - cargo update -p petgraph --precise 0.6.3 # hack to work with Rust 1.63 script: - sh /start.sh @@ -227,7 +229,8 @@ run_cardtest_fluffypgp: before_script: - export PATH="$HOME/.cargo/bin:$PATH" - *report-rust - - cargo update -p lalrpop --precise 0.19.12 # hack to work with Rust 1.63 + - cargo update + - cargo update -p lalrpop@0.20.0 --precise 0.19.12 # hack to work with Rust 1.63 - cargo update -p petgraph --precise 0.6.3 # hack to work with Rust 1.63 script: - sh /start.sh From c6518b91e2c4993bb9441fc5ab1a087be10249c5 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 24 Aug 2023 21:42:13 +0200 Subject: [PATCH 034/115] ci: disable hardware tests; run udeps independently udeps takes relatively long to run, and shouldn't block the other stages. --- .gitlab-ci.yml | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a14bedf..21ff91f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,7 +5,8 @@ stages: - lint - test - - virtual-test + - virtual-cards + - udeps - hw-builddeps - hw-import - hw-keygen @@ -82,7 +83,8 @@ cargo-clippy: key: "rust-latest" udeps: - stage: lint + stage: udeps + needs: [ ] image: rustlang/rust:nightly-slim before_script: - mkdir -p /run/user/$UID @@ -137,7 +139,7 @@ cargo-test-debian-bookworm: key: "bookworm" run_cardtest_smartpgp: - stage: virtual-test + stage: virtual-cards image: registry.gitlab.com/openpgp-card/virtual-cards/smartpgp-builddeps before_script: - export PATH="$HOME/.cargo/bin:$PATH" @@ -158,7 +160,7 @@ run_cardtest_smartpgp: key: "bookworm" run_cardtest_opcard_rs: - stage: virtual-test + stage: virtual-cards image: registry.gitlab.com/openpgp-card/virtual-cards/opcard-rs-tools before_script: - export PATH="$HOME/.cargo/bin:$PATH" @@ -179,7 +181,7 @@ run_cardtest_opcard_rs: key: "opcard_rs" run_cardtest_canokey: - stage: virtual-test + stage: virtual-cards image: registry.gitlab.com/openpgp-card/virtual-cards/canokey-builddeps before_script: - export PATH="$HOME/.cargo/bin:$PATH" @@ -200,7 +202,7 @@ run_cardtest_canokey: key: "canokey" run_cardtest_ykneo: - stage: virtual-test + stage: virtual-cards image: registry.gitlab.com/openpgp-card/virtual-cards/ykneo-builddeps before_script: - export PATH="$HOME/.cargo/bin:$PATH" @@ -224,7 +226,7 @@ run_cardtest_ykneo: key: "bookworm" run_cardtest_fluffypgp: - stage: virtual-test + stage: virtual-cards image: registry.gitlab.com/openpgp-card/virtual-cards/fluffypgp-builddeps before_script: - export PATH="$HOME/.cargo/bin:$PATH" @@ -247,7 +249,8 @@ run_cardtest_fluffypgp: # (the base image of run_cardtest uses bookworm) key: "bookworm" -hardware-builddeps: +# disabled for now +.hardware-builddeps: stage: hw-builddeps needs: [ ] image: docker:stable @@ -300,15 +303,16 @@ hardware-builddeps: # so use a different key for clarity key: "cookiejar" -import: +# disabled for now +.import: extends: .hw-test-template stage: hw-import variables: ARG: import -keygen: +# disabled for now +.keygen: extends: .hw-test-template stage: hw-keygen - timeout: 2h variables: ARG: keygen From a2232ebf865f8114e87df7bdae9577745ba2e8ae Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 26 Aug 2023 15:49:43 +0200 Subject: [PATCH 035/115] fix lint --- openpgp-card/src/card_do/algo_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card/src/card_do/algo_info.rs b/openpgp-card/src/card_do/algo_info.rs index 1c90724..dbebbd2 100644 --- a/openpgp-card/src/card_do/algo_info.rs +++ b/openpgp-card/src/card_do/algo_info.rs @@ -72,7 +72,7 @@ fn parse_tl_list(input: &[u8]) -> nom::IResult<&[u8], Vec<(KeyType, Algo)>> { Ok((input, list)) } -pub(self) fn parse(input: &[u8]) -> nom::IResult<&[u8], Vec<(KeyType, Algo)>> { +fn parse(input: &[u8]) -> nom::IResult<&[u8], Vec<(KeyType, Algo)>> { // Handle two variations of input format: // a) TLV format (e.g. YubiKey 5) // b) Plain list (e.g. Gnuk, FOSS-Store Smartcard 3.4) From 0e89c4baa6eabbc31880677a0e932d1c3b2be576 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 27 Aug 2023 22:20:16 +0200 Subject: [PATCH 036/115] Move backend traits to new card-backend crate - Move CardBackend, CardTransation traits to card-backend - Break SmartcardErrors out from openpgp-card to card-backend - CardCaps are (mostly) openpgp-card specific, move them (mostly) to openpgp-card - Rename pcsc and scdc backend crates: card-backend-pcsc, card-backend-scdc --- Cargo.toml | 9 +- card-backend/Cargo.toml | 15 ++ card-backend/README.md | 12 + card-backend/src/lib.rs | 192 ++++++++++++++ openpgp-card-sequoia/Cargo.toml | 2 +- openpgp-card/Cargo.toml | 4 +- openpgp-card/README.md | 6 +- openpgp-card/src/apdu.rs | 25 +- openpgp-card/src/apdu/commands.rs | 10 +- openpgp-card/src/errors.rs | 37 +-- openpgp-card/src/keys.rs | 8 +- openpgp-card/src/lib.rs | 238 +---------------- openpgp-card/src/openpgp.rs | 244 +++++++++++++----- pcsc/Cargo.toml | 12 +- pcsc/README.md | 10 +- pcsc/src/lib.rs | 412 ++++++++++-------------------- scdc/Cargo.toml | 12 +- scdc/README.md | 21 +- scdc/src/lib.rs | 214 ++++++++-------- 19 files changed, 710 insertions(+), 773 deletions(-) create mode 100644 card-backend/Cargo.toml create mode 100644 card-backend/README.md create mode 100644 card-backend/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 32c40c4..99dcdf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,13 @@ -# SPDX-FileCopyrightText: 2021 Heiko Schaefer +# SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer # SPDX-License-Identifier: MIT OR Apache-2.0 [workspace] members = [ "openpgp-card", - "openpgp-card-sequoia", +# "openpgp-card-sequoia", + "card-backend", "pcsc", "scdc", - "openpgp-card-examples", - "card-functionality", +# "openpgp-card-examples", +# "card-functionality", ] diff --git a/card-backend/Cargo.toml b/card-backend/Cargo.toml new file mode 100644 index 0000000..17f9266 --- /dev/null +++ b/card-backend/Cargo.toml @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2023 Heiko Schaefer +# SPDX-License-Identifier: MIT OR Apache-2.0 + +[package] +name = "card-backend" +description = "Card backend trait, for use with the openpgp-card crate" +authors = ["Heiko Schaefer "] +license = "MIT OR Apache-2.0" +version = "0.1.0" +edition = "2018" +repository = "https://gitlab.com/openpgp-card/openpgp-card" +documentation = "https://docs.rs/crate/card-backend" + +[dependencies] +thiserror = "1" \ No newline at end of file diff --git a/card-backend/README.md b/card-backend/README.md new file mode 100644 index 0000000..69a9591 --- /dev/null +++ b/card-backend/README.md @@ -0,0 +1,12 @@ + + +# Backend trait for Smart Card crates + +This crate defines the `CardBackend` and `CardTransactions` traits. + +The initial target for this abstraction layer was the +[openpgp-card](https://gitlab.com/openpgp-card/openpgp-card) set of client libraries +for OpenPGP card. This trait offers an implementation-agnostic means to access cards. diff --git a/card-backend/src/lib.rs b/card-backend/src/lib.rs new file mode 100644 index 0000000..dc90499 --- /dev/null +++ b/card-backend/src/lib.rs @@ -0,0 +1,192 @@ +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! A thin abstraction layer for accessing smart cards, including, but not +//! limited to, [openpgp-card](https://gitlab.com/openpgp-card/openpgp-card) +//! devices. + +/// This trait defines a connection with a smart card via a +/// backend implementation (e.g. via the pcsc backend in the crate +/// [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc)). +/// +/// A [CardBackend] is only used to get access to a [CardTransaction] object, +/// which supports transmitting commands to the card. +pub trait CardBackend { + fn transaction( + &mut self, + reselect_application: Option<&[u8]>, + ) -> Result, SmartcardError>; +} + +/// The CardTransaction trait defines communication with a smart card via a +/// backend implementation (e.g. the pcsc backend in the crate +/// [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc)), +/// after opening a transaction from a CardBackend. +pub trait CardTransaction { + /// Transmit the command data in `cmd` to the card. + /// + /// `buf_size` is a hint to the backend (the backend may ignore it) + /// indicating the expected maximum response size. + fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result, SmartcardError>; + + /// Select `application` on the card + fn select(&mut self, application: &[u8]) -> Result, SmartcardError> { + let mut cmd = vec![0x00, 0xa4, 0x04, 0x00]; // CLA, INS, P1, P2 + cmd.push(application.len() as u8); // Lc + cmd.extend_from_slice(application); // Data + cmd.push(0x00); // Le + + self.transmit(&cmd, 254) + } + + /// If a CardTransaction implementation introduces an additional, + /// backend-specific limit for maximum number of bytes per command, + /// this fn can indicate that limit by returning `Some(max_cmd_len)`. + fn max_cmd_len(&self) -> Option { + None + } + + /// Does the reader support FEATURE_VERIFY_PIN_DIRECT? + fn feature_pinpad_verify(&self) -> bool; + + /// Does the reader support FEATURE_MODIFY_PIN_DIRECT? + fn feature_pinpad_modify(&self) -> bool; + + /// Verify the PIN `pin` via the reader pinpad + fn pinpad_verify( + &mut self, + pin: PinType, + card_caps: &Option, + ) -> Result, SmartcardError>; + + /// Modify the PIN `pin` via the reader pinpad + fn pinpad_modify( + &mut self, + pin: PinType, + card_caps: &Option, + ) -> Result, SmartcardError>; +} + +/// Information about the capabilities of a card. +/// +/// CardCaps is used to signal capabilities (chaining, extended length support, max +/// command/response sizes, max PIN lengths) of the current card to backends. +/// +/// CardCaps is not intended for users of this library. +/// +/// (The information is gathered from the "Card Capabilities", "Extended length information" and +/// "PWStatus" DOs) +#[derive(Clone, Copy, Debug)] +pub struct CardCaps { + ext_support: bool, + chaining_support: bool, + max_cmd_bytes: u16, + max_rsp_bytes: u16, + pw1_max_len: u8, + pw3_max_len: u8, +} + +impl CardCaps { + pub fn new( + ext_support: bool, + chaining_support: bool, + max_cmd_bytes: u16, + max_rsp_bytes: u16, + pw1_max_len: u8, + pw3_max_len: u8, + ) -> Self { + Self { + ext_support, + chaining_support, + max_cmd_bytes, + max_rsp_bytes, + pw1_max_len, + pw3_max_len, + } + } + + /// Does the card support extended Lc and Le fields? + pub fn ext_support(&self) -> bool { + self.ext_support + } + + /// Does the card support command chaining? + pub fn chaining_support(&self) -> bool { + self.chaining_support + } + + /// Maximum number of bytes in a command APDU + pub fn max_cmd_bytes(&self) -> u16 { + self.max_cmd_bytes + } + + /// Maximum number of bytes in a response APDU + pub fn max_rsp_bytes(&self) -> u16 { + self.max_rsp_bytes + } + + /// Maximum length of PW1 + pub fn pw1_max_len(&self) -> u8 { + self.pw1_max_len + } + + /// Maximum length of PW3 + pub fn pw3_max_len(&self) -> u8 { + self.pw3_max_len + } +} + +/// Specify a PIN to *verify* (distinguishes between `Sign`, `User` and `Admin`). +/// +/// (Note that for PIN *management*, in particular changing a PIN, "signing and user" are +/// not distinguished. They always share the same PIN value `PW1`) +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum PinType { + /// Verify PW1 in mode P2=81 (for the PSO:CDS operation) + Sign, + + /// Verify PW1 in mode P2=82 (for all other User operations) + User, + + /// Verify PW3 (for Admin operations) + Admin, +} + +impl PinType { + pub fn id(&self) -> u8 { + match self { + PinType::Sign => 0x81, + PinType::User => 0x82, + PinType::Admin => 0x83, + } + } +} + +/// Errors on the smartcard/reader layer +#[derive(thiserror::Error, Debug)] +#[non_exhaustive] +pub enum SmartcardError { + #[error("Failed to create a pcsc smartcard context {0}")] + ContextError(String), + + #[error("Failed to list readers: {0}")] + ReaderError(String), + + #[error("No reader found.")] + NoReaderFoundError, + + #[error("The requested card '{0}' was not found.")] + CardNotFound(String), + + #[error("Failed to connect to the card: {0}")] + SmartCardConnectionError(String), + + #[error("Smart card status: [{0}, {1}]")] + SmartCardStatus(u8, u8), + + #[error("NotTransacted (SCARD_E_NOT_TRANSACTED)")] + NotTransacted, + + #[error("Generic SmartCard Error: {0}")] + Error(String), +} diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index 2d4970c..1936c47 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -13,7 +13,7 @@ documentation = "https://docs.rs/crate/openpgp-card-sequoia" [dependencies] sequoia-openpgp = { version = "1.4", default-features = false } -openpgp-card = { path = "../openpgp-card", version = "0.3.7" } +openpgp-card = { path = "../openpgp-card", version = "0.4" } chrono = "0.4" anyhow = "1" thiserror = "1" diff --git a/openpgp-card/Cargo.toml b/openpgp-card/Cargo.toml index d6dcef6..4b2a448 100644 --- a/openpgp-card/Cargo.toml +++ b/openpgp-card/Cargo.toml @@ -5,14 +5,14 @@ name = "openpgp-card" description = "A client implementation for the OpenPGP card specification" license = "MIT OR Apache-2.0" -version = "0.3.7" +version = "0.4.0" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" documentation = "https://docs.rs/crate/openpgp-card" [dependencies] -blanket = "0.3" +card-backend = { path = "../card-backend", version = "0.1" } nom = "7" hex-slice = "0.1" thiserror = "1" diff --git a/openpgp-card/README.md b/openpgp-card/README.md index 173946a..7e3a777 100644 --- a/openpgp-card/README.md +++ b/openpgp-card/README.md @@ -1,5 +1,5 @@ @@ -19,8 +19,8 @@ specification. This crate doesn't contain code to talk to cards. Implementations of the traits `CardBackend`/`CardTransaction` need to be provided for access to cards. -The crates [openpgp-card-pcsc](https://crates.io/crates/openpgp-card-pcsc) -and the experimental crate [openpgp-card-scdc](https://crates.io/crates/openpgp-card-scdc) +The crates [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc) +and the experimental crate [card-backend-scdc](https://crates.io/crates/card-backend-scdc) provide implementations of these traits for use with this crate. **Sequoia PGP wrapper** diff --git a/openpgp-card/src/apdu.rs b/openpgp-card/src/apdu.rs index 41eb698..6fc9d9d 100644 --- a/openpgp-card/src/apdu.rs +++ b/openpgp-card/src/apdu.rs @@ -10,9 +10,11 @@ pub mod response; use std::convert::TryFrom; +use card_backend::{CardCaps, CardTransaction}; + use crate::apdu::command::Expect; use crate::apdu::{command::Command, response::RawResponse}; -use crate::{CardTransaction, Error, StatusBytes}; +use crate::{Error, StatusBytes}; /// "Maximum amount of bytes in a short APDU command or response" (from pcsc) const MAX_BUFFER_SIZE: usize = 264; @@ -24,6 +26,7 @@ const MAX_BUFFER_SIZE: usize = 264; pub(crate) fn send_command( card_tx: &mut C, cmd: Command, + card_caps: Option, expect_reply: bool, ) -> Result where @@ -34,6 +37,7 @@ where let mut resp = RawResponse::try_from(send_command_low_level( card_tx, cmd.clone(), + card_caps, if expect_reply { Expect::Some } else { @@ -42,7 +46,12 @@ where )?)?; if let StatusBytes::UnknownStatus(0x6c, size) = resp.status() { - resp = RawResponse::try_from(send_command_low_level(card_tx, cmd, Expect::Short(size))?)?; + resp = RawResponse::try_from(send_command_low_level( + card_tx, + cmd, + card_caps, + Expect::Short(size), + )?)?; } while let StatusBytes::OkBytesAvailable(bytes) = resp.status() { @@ -53,6 +62,7 @@ where let next = RawResponse::try_from(send_command_low_level( card_tx, commands::get_response(), + card_caps, Expect::Short(bytes), )?)?; @@ -85,20 +95,21 @@ where fn send_command_low_level( card_tx: &mut C, cmd: Command, + card_caps: Option, expect_response: Expect, ) -> Result, Error> where C: CardTransaction + ?Sized, { let (ext_support, chaining_support, mut max_cmd_bytes, max_rsp_bytes) = - if let Some(caps) = card_tx.card_caps() { + if let Some(caps) = card_caps { log::trace!("found card caps data!"); ( - caps.ext_support, - caps.chaining_support, - caps.max_cmd_bytes as usize, - caps.max_rsp_bytes as usize, + caps.ext_support(), + caps.chaining_support(), + caps.max_cmd_bytes() as usize, + caps.max_rsp_bytes() as usize, ) } else { log::trace!("found NO card caps data!"); diff --git a/openpgp-card/src/apdu/commands.rs b/openpgp-card/src/apdu/commands.rs index c59fd99..cb20ccf 100644 --- a/openpgp-card/src/apdu/commands.rs +++ b/openpgp-card/src/apdu/commands.rs @@ -4,18 +4,12 @@ //! Pre-defined `Command` values for the OpenPGP card application use crate::apdu::command::Command; -use crate::{KeyType, ShortTag, Tags}; +use crate::{KeyType, ShortTag, Tags, OP_APP}; /// 7.2.1 SELECT /// (select the OpenPGP application on the card) pub(crate) fn select_openpgp() -> Command { - Command::new( - 0x00, - 0xA4, - 0x04, - 0x00, - vec![0xD2, 0x76, 0x00, 0x01, 0x24, 0x01], - ) + Command::new(0x00, 0xA4, 0x04, 0x00, OP_APP.to_vec()) } /// 7.2.6 GET DATA diff --git a/openpgp-card/src/errors.rs b/openpgp-card/src/errors.rs index 76c9c0f..02d93c4 100644 --- a/openpgp-card/src/errors.rs +++ b/openpgp-card/src/errors.rs @@ -10,6 +10,8 @@ //! - [`StatusBytes`], which models error statuses reported by the OpenPGP //! card application +use card_backend::SmartcardError; + /// Enum wrapper for the different error types of this crate #[derive(thiserror::Error, Debug)] #[non_exhaustive] @@ -49,6 +51,12 @@ impl From for Error { } } +impl From for Error { + fn from(sce: SmartcardError) -> Self { + Error::Smartcard(sce) + } +} + /// OpenPGP card "Status Bytes" (ok statuses and errors) #[derive(thiserror::Error, Debug, PartialEq, Eq, Copy, Clone)] #[non_exhaustive] @@ -161,32 +169,3 @@ impl From<(u8, u8)> for StatusBytes { } } } - -/// Errors on the smartcard/reader layer -#[derive(thiserror::Error, Debug)] -#[non_exhaustive] -pub enum SmartcardError { - #[error("Failed to create a pcsc smartcard context {0}")] - ContextError(String), - - #[error("Failed to list readers: {0}")] - ReaderError(String), - - #[error("No reader found.")] - NoReaderFoundError, - - #[error("The requested card '{0}' was not found.")] - CardNotFound(String), - - #[error("Couldn't select the OpenPGP card application")] - SelectOpenPGPCardFailed, - - #[error("Failed to connect to the card: {0}")] - SmartCardConnectionError(String), - - #[error("NotTransacted (SCARD_E_NOT_TRANSACTED)")] - NotTransacted, - - #[error("Generic SmartCard Error: {0}")] - Error(String), -} diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index b283428..9e77277 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -16,7 +16,7 @@ use crate::crypto_data::{ }; use crate::openpgp::OpenPgpTransaction; use crate::tlv::{length::tlv_encode_length, value::Value, Tlv}; -use crate::{apdu, Error, KeyType, Tag, Tags}; +use crate::{Error, KeyType, Tag, Tags}; /// Generate asymmetric key pair on the card. /// @@ -130,7 +130,7 @@ pub(crate) fn generate_asymmetric_key_pair( let crt = control_reference_template(key_type)?; let gen_key_cmd = commands::gen_key(crt.serialize().to_vec()); - let resp = apdu::send_command(card_tx.tx(), gen_key_cmd, true)?; + let resp = card_tx.send_command(gen_key_cmd, true)?; resp.check_ok()?; let tlv = Tlv::try_from(resp.data()?)?; @@ -158,7 +158,7 @@ pub(crate) fn public_key( let crt = control_reference_template(key_type)?; let get_pub_key_cmd = commands::get_pub_key(crt.serialize().to_vec()); - let resp = apdu::send_command(card_tx.tx(), get_pub_key_cmd, true)?; + let resp = card_tx.send_command(get_pub_key_cmd, true)?; resp.check_ok()?; let tlv = Tlv::try_from(resp.data()?)?; @@ -215,7 +215,7 @@ pub(crate) fn key_import( card_tx.set_algorithm_attributes(key_type, &algo)?; } - apdu::send_command(card_tx.tx(), key_cmd, false)?.check_ok()?; + card_tx.send_command(key_cmd, false)?.check_ok()?; card_tx.set_fingerprint(fp, key_type)?; card_tx.set_creation_time(key.timestamp(), key_type)?; diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 07e8146..18904fb 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -13,9 +13,9 @@ //! [OpenPGP implementation](https://www.openpgp.org/software/developer/). //! //! This library can't directly access cards by itself. Instead, users -//! need to supply a backend that implements the [`CardBackend`] -//! / [`CardTransaction`] traits. The companion crate -//! [openpgp-card-pcsc](https://crates.io/crates/openpgp-card-pcsc) +//! need to supply a backend that implements the [`card_backend::CardBackend`] +//! / [`card_backend::CardTransaction`] traits. The companion crate +//! [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc) //! offers a backend that uses [PC/SC](https://en.wikipedia.org/wiki/PC/SC) to //! communicate with Smart Cards. //! @@ -38,211 +38,11 @@ mod oid; mod openpgp; mod tlv; -use std::convert::TryInto; - -use crate::apdu::commands; -use crate::card_do::ApplicationRelatedData; -pub use crate::errors::{Error, SmartcardError, StatusBytes}; +pub use crate::errors::{Error, StatusBytes}; pub use crate::openpgp::{OpenPgp, OpenPgpTransaction}; -use crate::tlv::{tag::Tag, value::Value, Tlv}; +use crate::tlv::tag::Tag; -/// The CardBackend trait defines a connection with an OpenPGP card via a -/// backend implementation (e.g. via the pcsc backend in the crate -/// [openpgp-card-pcsc](https://crates.io/crates/openpgp-card-pcsc)), -/// A CardBackend is only used to get access to a `CardTransaction` object. -#[blanket::blanket(derive(Box))] -pub trait CardBackend { - fn transaction(&mut self) -> Result, Error>; -} - -/// The CardTransaction trait defines communication with an OpenPGP card via a -/// backend implementation (e.g. the pcsc backend in the crate -/// [openpgp-card-pcsc](https://crates.io/crates/openpgp-card-pcsc)), -/// after opening a transaction from a CardBackend. -#[blanket::blanket(derive(Box))] -pub trait CardTransaction { - /// Transmit the command data in `cmd` to the card. - /// - /// `buf_size` is a hint to the backend (the backend may ignore it) - /// indicating the expected maximum response size. - fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result, Error>; - - /// Set the card capabilities in the CardTransaction. - /// - /// Setting these capabilities is typically part of a bootstrapping - /// process (this fn is typically called from [CardTransaction::initialize]. - /// When implementing CardTransaction, you probably want to call - /// [CardTransaction::initialize] during setup). - /// - /// The information about the card's capabilities is typically - /// requested from the card using the same CardTransaction instance, - /// before the card's capabilities have been initialized. - fn init_card_caps(&mut self, caps: CardCaps); - - /// Request the card's capabilities - /// - /// (apdu serialization makes use of this information, e.g. to - /// determine if extended length can be used) - fn card_caps(&self) -> Option<&CardCaps>; - - /// If a CardTransaction implementation introduces an additional, - /// backend-specific limit for maximum number of bytes per command, - /// this fn can indicate that limit by returning `Some(max_cmd_len)`. - fn max_cmd_len(&self) -> Option { - None - } - - /// Does the reader support FEATURE_VERIFY_PIN_DIRECT? - fn feature_pinpad_verify(&self) -> bool; - - /// Does the reader support FEATURE_MODIFY_PIN_DIRECT? - fn feature_pinpad_modify(&self) -> bool; - - /// Verify the PIN `id` via the reader pinpad - fn pinpad_verify(&mut self, pin: PinType) -> Result, Error>; - - /// Modify the PIN `id` via the reader pinpad - fn pinpad_modify(&mut self, pin: PinType) -> Result, Error>; - - /// Select the OpenPGP card application - fn select(&mut self) -> Result, Error> { - log::info!("CardTransaction: select"); - let select_openpgp = commands::select_openpgp(); - apdu::send_command(self, select_openpgp, false)?.try_into() - } - - /// Activate file - fn activate_file(&mut self) -> Result, Error> { - log::info!("CardTransaction: activate_file"); - let activate_file = commands::activate_file(); - apdu::send_command(self, activate_file, false)?.try_into() - } - - /// Get the "application related data" from the card. - /// - /// (This data should probably be cached in a higher layer. Some parts of - /// it are needed regularly, and it does not usually change during - /// normal use of a card.) - fn application_related_data(&mut self) -> Result { - let ad = commands::application_related_data(); - let resp = apdu::send_command(self, ad, true)?; - let value = Value::from(resp.data()?, true)?; - - log::trace!(" ARD value: {:02x?}", value); - - Ok(ApplicationRelatedData(Tlv::new( - Tags::ApplicationRelatedData, - value, - ))) - } - - /// Get a CardApp based on a CardTransaction. - /// - /// It is expected that SELECT has already been performed on the card - /// beforehand. - /// - /// This fn initializes the CardCaps by requesting - /// application_related_data from the card, and setting the - /// capabilities accordingly. - fn initialize(&mut self) -> Result<(), Error> { - let ard = self.application_related_data()?; - - // Determine chaining/extended length support from card - // metadata and cache this information in the CardTransaction - // implementation (as a CardCaps) - let mut ext_support = false; - let mut chaining_support = false; - - if let Ok(hist) = ard.historical_bytes() { - if let Some(cc) = hist.card_capabilities() { - chaining_support = cc.command_chaining(); - ext_support = cc.extended_lc_le(); - } - } - - let ext_cap = ard.extended_capabilities()?; - - // Get max command/response byte sizes from card - let (max_cmd_bytes, max_rsp_bytes) = - if let Ok(Some(eli)) = ard.extended_length_information() { - // In card 3.x, max lengths come from ExtendedLengthInfo - (eli.max_command_bytes(), eli.max_response_bytes()) - } else if let (Some(cmd), Some(rsp)) = (ext_cap.max_cmd_len(), ext_cap.max_resp_len()) { - // In card 2.x, max lengths come from ExtendedCapabilities - (cmd, rsp) - } else { - // Fallback: use 255 if we have no information from the card - (255, 255) - }; - - let pw_status = ard.pw_status_bytes()?; - let pw1_max = pw_status.pw1_max_len(); - let pw3_max = pw_status.pw3_max_len(); - - let caps = CardCaps { - ext_support, - chaining_support, - max_cmd_bytes, - max_rsp_bytes, - pw1_max_len: pw1_max, - pw3_max_len: pw3_max, - }; - - log::trace!("init_card_caps to: {:x?}", caps); - - self.init_card_caps(caps); - - Ok(()) - } -} - -/// Information about the capabilities of a card. -/// -/// CardCaps is used to signal capabilities (chaining, extended length support, max -/// command/response sizes, max PIN lengths) of the current card to backends. -/// -/// CardCaps is not intended for users of this library. -/// -/// (The information is gathered from the "Card Capabilities", "Extended length information" and -/// "PWStatus" DOs) -#[derive(Clone, Copy, Debug)] -pub struct CardCaps { - /// Does the card support extended Lc and Le fields? - ext_support: bool, - - /// Command chaining support? - chaining_support: bool, - - /// Maximum number of bytes in a command APDU - max_cmd_bytes: u16, - - /// Maximum number of bytes in a response APDU - max_rsp_bytes: u16, - - /// Maximum length of PW1 - pw1_max_len: u8, - - /// Maximum length of PW3 - pw3_max_len: u8, -} - -impl CardCaps { - pub fn ext_support(&self) -> bool { - self.ext_support - } - - pub fn max_rsp_bytes(&self) -> u16 { - self.max_rsp_bytes - } - - pub fn pw1_max_len(&self) -> u8 { - self.pw1_max_len - } - - pub fn pw3_max_len(&self) -> u8 { - self.pw3_max_len - } -} +pub(crate) const OP_APP: &[u8] = &[0xD2, 0x76, 0x00, 0x01, 0x24, 0x01]; /// Tags, as specified and used in the OpenPGP card 3.4.1 spec. /// All tags in OpenPGP card are either 1 or 2 bytes long. @@ -497,32 +297,6 @@ impl From for Vec { } } -/// Specify a PIN to *verify* (distinguishes between `Sign`, `User` and `Admin`). -/// -/// (Note that for PIN *management*, in particular changing a PIN, "signing and user" are -/// not distinguished. They always share the same PIN value `PW1`) -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum PinType { - /// Verify PW1 in mode P2=81 (for the PSO:CDS operation) - Sign, - - /// Verify PW1 in mode P2=82 (for all other User operations) - User, - - /// Verify PW3 (for Admin operations) - Admin, -} - -impl PinType { - pub fn id(&self) -> u8 { - match self { - PinType::Sign => 0x81, - PinType::User => 0x82, - PinType::Admin => 0x83, - } - } -} - /// Identify a Key slot on an OpenPGP card #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[non_exhaustive] diff --git a/openpgp-card/src/openpgp.rs b/openpgp-card/src/openpgp.rs index 1fcfd3f..5cdf73c 100644 --- a/openpgp-card/src/openpgp.rs +++ b/openpgp-card/src/openpgp.rs @@ -3,7 +3,10 @@ use std::convert::{TryFrom, TryInto}; +use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError}; + use crate::algorithm::{Algo, AlgoInfo, AlgoSimple}; +use crate::apdu::command::Command; use crate::apdu::commands; use crate::apdu::response::RawResponse; use crate::card_do::{ @@ -12,29 +15,93 @@ use crate::card_do::{ }; use crate::crypto_data::{CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial}; use crate::tlv::{value::Value, Tlv}; -use crate::{ - apdu, keys, CardBackend, CardTransaction, Error, KeyType, PinType, SmartcardError, StatusBytes, - Tag, Tags, -}; +use crate::{apdu, keys, Error, KeyType, StatusBytes, Tag, Tags, OP_APP}; /// An OpenPGP card access object, backed by a CardBackend implementation. /// -/// Most users will probably want to use the `PcscCard` backend from the `openpgp-card-pcsc` crate. +/// Most users will probably want to use the `PcscCard` backend from the `card-backend-pcsc` crate. /// /// Users of this crate can keep a long lived OpenPgp object. All operations must be performed on /// a short lived `OpenPgpTransaction`. pub struct OpenPgp { card: Box, + card_caps: Option, } impl OpenPgp { - pub fn new(backend: B) -> Self + /// Turn a [card_backend::CardBackend] into an [OpenPgp] object: + /// + /// The OpenPGP application is `SELECT`ed, and the card capabilities + /// of the card are retrieved from the "Application Related Data". + pub fn new(backend: B) -> Result where B: Into>, { - Self { - card: backend.into(), - } + let card: Box = backend.into(); + + let mut op = Self { + card, + card_caps: None, + }; + + let caps = { + let mut tx = op.transaction()?; + tx.select()?; + + // Init card_caps + let ard = tx.application_related_data()?; + + // Determine chaining/extended length support from card + // metadata and cache this information in the CardTransaction + // implementation (as a CardCaps) + let mut ext_support = false; + let mut chaining_support = false; + + if let Ok(hist) = ard.historical_bytes() { + if let Some(cc) = hist.card_capabilities() { + chaining_support = cc.command_chaining(); + ext_support = cc.extended_lc_le(); + } + } + + let ext_cap = ard.extended_capabilities()?; + + // Get max command/response byte sizes from card + let (max_cmd_bytes, max_rsp_bytes) = if let Ok(Some(eli)) = + ard.extended_length_information() + { + // In card 3.x, max lengths come from ExtendedLengthInfo + (eli.max_command_bytes(), eli.max_response_bytes()) + } else if let (Some(cmd), Some(rsp)) = (ext_cap.max_cmd_len(), ext_cap.max_resp_len()) { + // In card 2.x, max lengths come from ExtendedCapabilities + (cmd, rsp) + } else { + // Fallback: use 255 if we have no information from the card + (255, 255) + }; + + let pw_status = ard.pw_status_bytes()?; + let pw1_max = pw_status.pw1_max_len(); + let pw3_max = pw_status.pw3_max_len(); + + let caps = CardCaps::new( + ext_support, + chaining_support, + max_cmd_bytes, + max_rsp_bytes, + pw1_max, + pw3_max, + ); + + drop(tx); + + caps + }; + + log::trace!("init_card_caps to: {:x?}", caps); + op.card_caps = Some(caps); + + Ok(op) } /// Get the internal `CardBackend`. @@ -50,10 +117,14 @@ impl OpenPgp { /// /// Note: transactions on the Card cannot be long running, they will be reset within seconds /// when idle. + /// + /// If the card has been reset, and `reselect_application` is set, then + /// that application will be `SELECT`ed after starting the transaction. pub fn transaction(&mut self) -> Result { - Ok(OpenPgpTransaction { - tx: self.card.transaction()?, - }) + let card_caps = &mut self.card_caps; + let tx = self.card.transaction(Some(OP_APP))?; + + Ok(OpenPgpTransaction { tx, card_caps }) } } @@ -67,6 +138,7 @@ impl OpenPgp { /// closed. pub struct OpenPgpTransaction<'a> { tx: Box, + card_caps: &'a mut Option, } impl<'a> OpenPgpTransaction<'a> { @@ -74,6 +146,24 @@ impl<'a> OpenPgpTransaction<'a> { self.tx.as_mut() } + pub(crate) fn send_command( + &mut self, + cmd: Command, + expect_reply: bool, + ) -> Result { + apdu::send_command(&mut *self.tx, cmd, *self.card_caps, expect_reply) + } + + // SELECT + + /// Select the OpenPGP card application + pub fn select(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: select"); + + self.send_command(commands::select_openpgp(), false)? + .try_into() + } + // --- pinpad --- /// Does the reader support FEATURE_VERIFY_PIN_DIRECT? @@ -96,7 +186,15 @@ impl<'a> OpenPgpTransaction<'a> { pub fn application_related_data(&mut self) -> Result { log::info!("OpenPgpTransaction: application_related_data"); - self.tx.application_related_data() + let resp = self.send_command(commands::application_related_data(), true)?; + let value = Value::from(resp.data()?, true)?; + + log::trace!(" ARD value: {:02x?}", value); + + Ok(ApplicationRelatedData(Tlv::new( + Tags::ApplicationRelatedData, + value, + ))) } // --- login data (5e) --- @@ -105,7 +203,7 @@ impl<'a> OpenPgpTransaction<'a> { pub fn url(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: url"); - let resp = apdu::send_command(self.tx(), commands::url(), true)?; + let resp = self.send_command(commands::url(), true)?; Ok(resp.data()?.to_vec()) } @@ -114,7 +212,7 @@ impl<'a> OpenPgpTransaction<'a> { pub fn login_data(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: login_data"); - let resp = apdu::send_command(self.tx(), commands::login_data(), true)?; + let resp = self.send_command(commands::login_data(), true)?; Ok(resp.data()?.to_vec()) } @@ -124,7 +222,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: cardholder_related_data"); let crd = commands::cardholder_related_data(); - let resp = apdu::send_command(self.tx(), crd, true)?; + let resp = self.send_command(crd, true)?; resp.check_ok()?; CardholderRelatedData::try_from(resp.data()?) @@ -135,7 +233,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: security_support_template"); let sst = commands::security_support_template(); - let resp = apdu::send_command(self.tx(), sst, true)?; + let resp = self.send_command(sst, true)?; resp.check_ok()?; let tlv = Tlv::try_from(resp.data()?)?; @@ -183,7 +281,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: cardholder_certificate"); let cmd = commands::cardholder_certificate(); - apdu::send_command(self.tx(), cmd, true)?.try_into() + self.send_command(cmd, true)?.try_into() } /// Call "GET NEXT DATA" for the DO cardholder certificate. @@ -194,14 +292,14 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: next_cardholder_certificate"); let cmd = commands::get_next_cardholder_certificate(); - apdu::send_command(self.tx(), cmd, true)?.try_into() + self.send_command(cmd, true)?.try_into() } /// Get "Algorithm Information" pub fn algorithm_information(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: algorithm_information"); - let resp = apdu::send_command(self.tx(), commands::algo_info(), true)?; + let resp = self.send_command(commands::algo_info(), true)?; resp.check_ok()?; let ai = AlgoInfo::try_from(resp.data()?)?; @@ -212,7 +310,7 @@ impl<'a> OpenPgpTransaction<'a> { pub fn attestation_certificate(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: attestation_certificate"); - let resp = apdu::send_command(self.tx(), commands::attestation_certificate(), true)?; + let resp = self.send_command(commands::attestation_certificate(), true)?; Ok(resp.data()?.into()) } @@ -221,7 +319,7 @@ impl<'a> OpenPgpTransaction<'a> { pub fn firmware_version(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: firmware_version"); - let resp = apdu::send_command(self.tx(), commands::firmware_version(), true)?; + let resp = self.send_command(commands::firmware_version(), true)?; Ok(resp.data()?.into()) } @@ -233,7 +331,7 @@ impl<'a> OpenPgpTransaction<'a> { pub fn set_identity(&mut self, id: u8) -> Result, Error> { log::info!("OpenPgpTransaction: set_identity"); - let resp = apdu::send_command(self.tx(), commands::set_identity(id), false); + let resp = self.send_command(commands::set_identity(id), false); // Apparently it's normal to get "NotTransacted" from pcsclite when // the identity switch was successful. @@ -291,7 +389,7 @@ impl<'a> OpenPgpTransaction<'a> { // Possible response data (Control Parameter = CP) don't need to be evaluated by the // application (See "7.2.5 SELECT DATA") - apdu::send_command(self.tx(), cmd, true)?.try_into()?; + self.send_command(cmd, true)?.try_into()?; Ok(()) } @@ -307,7 +405,7 @@ impl<'a> OpenPgpTransaction<'a> { assert!((1..=4).contains(&num)); let cmd = commands::private_use_do(num); - let resp = apdu::send_command(self.tx(), cmd, true)?; + let resp = self.send_command(cmd, true)?; Ok(resp.data()?.to_vec()) } @@ -337,7 +435,7 @@ impl<'a> OpenPgpTransaction<'a> { for _ in 0..4 { log::info!(" verify_pw1_81"); let verify = commands::verify_pw1_81([0x40; 8].to_vec()); - let resp = apdu::send_command(self.tx(), verify, false)?; + let resp = self.send_command(verify, false)?; if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied || resp.status() == StatusBytes::AuthenticationMethodBlocked || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) @@ -353,7 +451,7 @@ impl<'a> OpenPgpTransaction<'a> { for _ in 0..4 { log::info!(" verify_pw3"); let verify = commands::verify_pw3([0x40; 8].to_vec()); - let resp = apdu::send_command(self.tx(), verify, false)?; + let resp = self.send_command(verify, false)?; if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied || resp.status() == StatusBytes::AuthenticationMethodBlocked @@ -368,13 +466,13 @@ impl<'a> OpenPgpTransaction<'a> { // terminate_df [apdu 00 e6 00 00] log::info!(" terminate_df"); let term = commands::terminate_df(); - let resp = apdu::send_command(self.tx(), term, false)?; + let resp = self.send_command(term, false)?; resp.check_ok()?; // activate_file [apdu 00 44 00 00] log::info!(" activate_file"); let act = commands::activate_file(); - let resp = apdu::send_command(self.tx(), act, false)?; + let resp = self.send_command(act, false)?; resp.check_ok()?; Ok(()) @@ -391,7 +489,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: verify_pw1_sign"); let verify = commands::verify_pw1_81(pin.to_vec()); - apdu::send_command(self.tx(), verify, false)?.try_into() + self.send_command(verify, false)?.try_into() } /// Verify pw1 (user) for signing operation (mode 81) using a @@ -404,7 +502,9 @@ impl<'a> OpenPgpTransaction<'a> { pub fn verify_pw1_sign_pinpad(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: verify_pw1_sign_pinpad"); - let res = self.tx().pinpad_verify(PinType::Sign)?; + let cc = *self.card_caps; + + let res = self.tx().pinpad_verify(PinType::Sign, &cc)?; RawResponse::try_from(res)?.try_into() } @@ -420,7 +520,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: check_pw1_sign"); let verify = commands::verify_pw1_81(vec![]); - apdu::send_command(self.tx(), verify, false)?.try_into() + self.send_command(verify, false)?.try_into() } /// Verify PW1 (user). @@ -429,7 +529,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: verify_pw1_user"); let verify = commands::verify_pw1_82(pin.to_vec()); - apdu::send_command(self.tx(), verify, false)?.try_into() + self.send_command(verify, false)?.try_into() } /// Verify PW1 (user) for operations except signing (mode 82), @@ -439,7 +539,9 @@ impl<'a> OpenPgpTransaction<'a> { pub fn verify_pw1_user_pinpad(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: verify_pw1_user_pinpad"); - let res = self.tx().pinpad_verify(PinType::User)?; + let cc = *self.card_caps; + + let res = self.tx().pinpad_verify(PinType::User, &cc)?; RawResponse::try_from(res)?.try_into() } @@ -456,7 +558,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: check_pw1_user"); let verify = commands::verify_pw1_82(vec![]); - apdu::send_command(self.tx(), verify, false)?.try_into() + self.send_command(verify, false)?.try_into() } /// Verify PW3 (admin). @@ -464,7 +566,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: verify_pw3"); let verify = commands::verify_pw3(pin.to_vec()); - apdu::send_command(self.tx(), verify, false)?.try_into() + self.send_command(verify, false)?.try_into() } /// Verify PW3 (admin) using a pinpad on the card reader. If no usable @@ -472,7 +574,9 @@ impl<'a> OpenPgpTransaction<'a> { pub fn verify_pw3_pinpad(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: verify_pw3_pinpad"); - let res = self.tx().pinpad_verify(PinType::Admin)?; + let cc = *self.card_caps; + + let res = self.tx().pinpad_verify(PinType::Admin, &cc)?; RawResponse::try_from(res)?.try_into() } @@ -488,7 +592,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: check_pw3"); let verify = commands::verify_pw3(vec![]); - apdu::send_command(self.tx(), verify, false)?.try_into() + self.send_command(verify, false)?.try_into() } /// Change the value of PW1 (user password). @@ -502,7 +606,7 @@ impl<'a> OpenPgpTransaction<'a> { data.extend(new); let change = commands::change_pw1(data); - apdu::send_command(self.tx(), change, false)?.try_into() + self.send_command(change, false)?.try_into() } /// Change the value of PW1 (0x81) using a pinpad on the @@ -510,9 +614,11 @@ impl<'a> OpenPgpTransaction<'a> { pub fn change_pw1_pinpad(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: change_pw1_pinpad"); + let cc = *self.card_caps; + // Note: for change PW, only 0x81 and 0x83 are used! // 0x82 is implicitly the same as 0x81. - let res = self.tx().pinpad_modify(PinType::Sign)?; + let res = self.tx().pinpad_modify(PinType::Sign, &cc)?; RawResponse::try_from(res)?.try_into() } @@ -527,7 +633,7 @@ impl<'a> OpenPgpTransaction<'a> { data.extend(new); let change = commands::change_pw3(data); - apdu::send_command(self.tx(), change, false)?.try_into() + self.send_command(change, false)?.try_into() } /// Change the value of PW3 (admin password) using a pinpad on the @@ -535,7 +641,9 @@ impl<'a> OpenPgpTransaction<'a> { pub fn change_pw3_pinpad(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: change_pw3_pinpad"); - let res = self.tx().pinpad_modify(PinType::Admin)?; + let cc = *self.card_caps; + + let res = self.tx().pinpad_modify(PinType::Admin, &cc)?; RawResponse::try_from(res)?.try_into() } @@ -554,7 +662,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: reset_retry_counter_pw1"); let reset = commands::reset_retry_counter_pw1(resetting_code, new_pw1); - apdu::send_command(self.tx(), reset, false)?.try_into() + self.send_command(reset, false)?.try_into() } // --- decrypt --- @@ -604,7 +712,7 @@ impl<'a> OpenPgpTransaction<'a> { // The OpenPGP card is already connected and PW1 82 has been verified let dec_cmd = commands::decryption(data); - let resp = apdu::send_command(self.tx(), dec_cmd, true)?; + let resp = self.send_command(dec_cmd, true)?; resp.check_ok()?; Ok(resp.data().map(|d| d.to_vec())?) @@ -639,7 +747,7 @@ impl<'a> OpenPgpTransaction<'a> { } let cmd = commands::manage_security_environment(for_operation, key_ref); - let resp = apdu::send_command(self.tx(), cmd, false)?; + let resp = self.send_command(cmd, false)?; resp.check_ok()?; Ok(()) } @@ -671,7 +779,7 @@ impl<'a> OpenPgpTransaction<'a> { let cds_cmd = commands::signature(data); - let resp = apdu::send_command(self.tx(), cds_cmd, true)?; + let resp = self.send_command(cds_cmd, true)?; Ok(resp.data().map(|d| d.to_vec())?) } @@ -701,7 +809,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: internal_authenticate"); let ia_cmd = commands::internal_authenticate(data); - let resp = apdu::send_command(self.tx(), ia_cmd, true)?; + let resp = self.send_command(ia_cmd, true)?; Ok(resp.data().map(|d| d.to_vec())?) } @@ -721,7 +829,7 @@ impl<'a> OpenPgpTransaction<'a> { assert!((1..=4).contains(&num)); let cmd = commands::put_private_use_do(num, data); - let resp = apdu::send_command(self.tx(), cmd, true)?; + let resp = self.send_command(cmd, true)?; Ok(resp.data()?.to_vec()) } @@ -729,14 +837,14 @@ impl<'a> OpenPgpTransaction<'a> { pub fn set_login(&mut self, login: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_login"); let put_login_data = commands::put_login_data(login.to_vec()); - apdu::send_command(self.tx(), put_login_data, false)?.try_into() + self.send_command(put_login_data, false)?.try_into() } pub fn set_name(&mut self, name: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_name"); let put_name = commands::put_name(name.to_vec()); - apdu::send_command(self.tx(), put_name, false)?.try_into() + self.send_command(put_name, false)?.try_into() } pub fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> { @@ -748,21 +856,21 @@ impl<'a> OpenPgpTransaction<'a> { .collect(); let put_lang = commands::put_lang(bytes); - apdu::send_command(self.tx(), put_lang, false)?.try_into() + self.send_command(put_lang, false)?.try_into() } pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_sex"); let put_sex = commands::put_sex((&sex).into()); - apdu::send_command(self.tx(), put_sex, false)?.try_into() + self.send_command(put_sex, false)?.try_into() } pub fn set_url(&mut self, url: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_url"); let put_url = commands::put_url(url.to_vec()); - apdu::send_command(self.tx(), put_url, false)?.try_into() + self.send_command(put_url, false)?.try_into() } /// Set cardholder certificate (for AUT, DEC or SIG). @@ -773,7 +881,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: set_cardholder_certificate"); let cmd = commands::put_cardholder_certificate(data); - apdu::send_command(self.tx(), cmd, false)?.try_into() + self.send_command(cmd, false)?.try_into() } /// Set algorithm attributes @@ -788,7 +896,7 @@ impl<'a> OpenPgpTransaction<'a> { // Command to PUT the algorithm attributes let cmd = commands::put_data(key_type.algorithm_tag(), algo.to_data_object()?); - apdu::send_command(self.tx(), cmd, false)?.try_into() + self.send_command(cmd, false)?.try_into() } /// Set PW Status Bytes. @@ -812,7 +920,7 @@ impl<'a> OpenPgpTransaction<'a> { let data = pw_status.serialize_for_put(long); let cmd = commands::put_pw_status(data); - apdu::send_command(self.tx(), cmd, false)?.try_into() + self.send_command(cmd, false)?.try_into() } pub fn set_fingerprint(&mut self, fp: Fingerprint, key_type: KeyType) -> Result<(), Error> { @@ -820,28 +928,28 @@ impl<'a> OpenPgpTransaction<'a> { let fp_cmd = commands::put_data(key_type.fingerprint_put_tag(), fp.as_bytes().to_vec()); - apdu::send_command(self.tx(), fp_cmd, false)?.try_into() + self.send_command(fp_cmd, false)?.try_into() } pub fn set_ca_fingerprint_1(&mut self, fp: Fingerprint) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_ca_fingerprint_1"); let fp_cmd = commands::put_data(Tags::CaFingerprint1, fp.as_bytes().to_vec()); - apdu::send_command(self.tx(), fp_cmd, false)?.try_into() + self.send_command(fp_cmd, false)?.try_into() } pub fn set_ca_fingerprint_2(&mut self, fp: Fingerprint) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_ca_fingerprint_2"); let fp_cmd = commands::put_data(Tags::CaFingerprint2, fp.as_bytes().to_vec()); - apdu::send_command(self.tx(), fp_cmd, false)?.try_into() + self.send_command(fp_cmd, false)?.try_into() } pub fn set_ca_fingerprint_3(&mut self, fp: Fingerprint) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_ca_fingerprint_3"); let fp_cmd = commands::put_data(Tags::CaFingerprint3, fp.as_bytes().to_vec()); - apdu::send_command(self.tx(), fp_cmd, false)?.try_into() + self.send_command(fp_cmd, false)?.try_into() } pub fn set_creation_time( @@ -862,7 +970,7 @@ impl<'a> OpenPgpTransaction<'a> { let time_cmd = commands::put_data(key_type.timestamp_put_tag(), time_value); - apdu::send_command(self.tx(), time_cmd, false)?.try_into() + self.send_command(time_cmd, false)?.try_into() } // FIXME: optional DO SM-Key-ENC @@ -875,7 +983,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: set_resetting_code"); let cmd = commands::put_data(Tags::ResettingCode, resetting_code.to_vec()); - apdu::send_command(self.tx(), cmd, false)?.try_into() + self.send_command(cmd, false)?.try_into() } /// Set AES key for symmetric decryption/encryption operations. @@ -888,7 +996,7 @@ impl<'a> OpenPgpTransaction<'a> { let fp_cmd = commands::put_data(Tags::PsoEncDecKey, key.to_vec()); - apdu::send_command(self.tx(), fp_cmd, false)?.try_into() + self.send_command(fp_cmd, false)?.try_into() } // FIXME: optional DO for PSO:ENC/DEC with AES @@ -898,7 +1006,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: set_uif_pso_cds"); let cmd = commands::put_data(Tags::UifSig, uif.as_bytes().to_vec()); - apdu::send_command(self.tx(), cmd, false)?.try_into() + self.send_command(cmd, false)?.try_into() } /// Set UIF for PSO:DEC @@ -906,7 +1014,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: set_uif_pso_dec"); let cmd = commands::put_data(Tags::UifDec, uif.as_bytes().to_vec()); - apdu::send_command(self.tx(), cmd, false)?.try_into() + self.send_command(cmd, false)?.try_into() } /// Set UIF for PSO:AUT @@ -914,7 +1022,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: set_uif_pso_aut"); let cmd = commands::put_data(Tags::UifAuth, uif.as_bytes().to_vec()); - apdu::send_command(self.tx(), cmd, false)?.try_into() + self.send_command(cmd, false)?.try_into() } /// Set UIF for Attestation key @@ -922,7 +1030,7 @@ impl<'a> OpenPgpTransaction<'a> { log::info!("OpenPgpTransaction: set_uif_attestation"); let cmd = commands::put_data(Tags::UifAttestation, uif.as_bytes().to_vec()); - apdu::send_command(self.tx(), cmd, false)?.try_into() + self.send_command(cmd, false)?.try_into() } /// Generate Attestation (Yubico) @@ -937,7 +1045,7 @@ impl<'a> OpenPgpTransaction<'a> { }; let cmd = commands::generate_attestation(key); - apdu::send_command(self.tx(), cmd, false)?.try_into() + self.send_command(cmd, false)?.try_into() } // FIXME: Attestation key algo attr, FP, CA-FP, creation time diff --git a/pcsc/Cargo.toml b/pcsc/Cargo.toml index aed8160..e6a69c9 100644 --- a/pcsc/Cargo.toml +++ b/pcsc/Cargo.toml @@ -1,18 +1,18 @@ -# SPDX-FileCopyrightText: 2021 Heiko Schaefer +# SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer # SPDX-License-Identifier: MIT OR Apache-2.0 [package] -name = "openpgp-card-pcsc" -description = "PCSC OpenPGP card backend, for use with the openpgp-card crate" +name = "card-backend-pcsc" +description = "PCSC card backend, e.g. for use with the openpgp-card crate" authors = ["Heiko Schaefer "] license = "MIT OR Apache-2.0" -version = "0.3.1" +version = "0.4.0" edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" -documentation = "https://docs.rs/crate/openpgp-card-pcsc" +documentation = "https://docs.rs/crate/card-backend-pcsc" [dependencies] -openpgp-card = { path = "../openpgp-card", version = "0.3.5" } +card-backend = { path = "../card-backend", version = "0.1" } iso7816-tlv = "0.4" pcsc = "2.7" log = "0.4" diff --git a/pcsc/README.md b/pcsc/README.md index 436cc05..82e93bc 100644 --- a/pcsc/README.md +++ b/pcsc/README.md @@ -1,17 +1,19 @@ -# PC/SC client for the openpgp-card library +# PC/SC based smart card backend This crate provides `PcscBackend` and `PcscTransaction`, which are implementations of the -`CardBackend` and `CardTransactions` traits from the [`openpgp-card`](https://crates.io/crates/openpgp-card) crate. +`CardBackend` and `CardTransactions` traits from the [`card-backend`](https://crates.io/crates/card-backend) crate. This implementation uses the [pcsc](https://crates.io/crates/pcsc) Rust wrapper crate to access OpenPGP cards. -## Documentation +Mainly intended for use with the [openpgp-card](https://gitlab.com/openpgp-card/openpgp-card) library. + +## Documentation on PC/SC [PC/SC](https://en.wikipedia.org/wiki/PC/SC) is a standard for interaction with smartcards and readers. diff --git a/pcsc/src/lib.rs b/pcsc/src/lib.rs index b7f52f8..ace040a 100644 --- a/pcsc/src/lib.rs +++ b/pcsc/src/lib.rs @@ -1,38 +1,30 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 -//! This crate implements the `CardBackend`/`CardTransaction` backend for -//! `openpgp-card`. It uses the PCSC middleware to access the OpenPGP -//! application on smart cards. +//! This crate implements the traits [CardBackend] and [CardTransaction]. +//! It uses the PCSC middleware to access smart cards. +//! +//! This crate is mainly intended for use by the `openpgp-card` crate. use std::collections::HashMap; use std::convert::TryInto; +use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError}; use iso7816_tlv::simple::Tlv; -use openpgp_card::card_do::ApplicationRelatedData; -use openpgp_card::{CardBackend, CardCaps, CardTransaction, Error, PinType, SmartcardError}; +use pcsc::Disposition; const FEATURE_VERIFY_PIN_DIRECT: u8 = 0x06; const FEATURE_MODIFY_PIN_DIRECT: u8 = 0x07; -fn default_mode(mode: Option) -> pcsc::ShareMode { - if let Some(mode) = mode { - mode - } else { - pcsc::ShareMode::Shared - } -} - /// An opened PCSC Card (without open transaction). -/// The OpenPGP application on the card is `select`-ed while setting up a PcscCard object. +/// Note: No application is `select`-ed on the card while setting up a PcscCard object. /// /// This struct can be used to hold on to a Card, even while no operations /// are performed on the Card. To perform operations on the card, a -/// `TxClient` object needs to be obtained (via PcscCard::transaction()). +/// [PcscTransaction] object needs to be obtained (via [PcscBackend::transaction]). pub struct PcscBackend { card: pcsc::Card, mode: pcsc::ShareMode, - card_caps: Option, reader_caps: HashMap, } @@ -56,118 +48,99 @@ impl From for Box { /// ) pub struct PcscTransaction<'b> { tx: pcsc::Transaction<'b>, - card_caps: Option, // FIXME: manual copy from PcscCard - reader_caps: HashMap, // FIXME: manual copy from PcscCard + reader_caps: HashMap, // FIXME: gets manually cloned } impl<'b> PcscTransaction<'b> { /// Start a transaction on `card`. /// - /// `reselect` set to `false` is only used internally in this crate, - /// during initial setup of cards. Otherwise it must be `true`, to - /// cause a select() call on cards that have been reset. - fn new(card: &'b mut PcscBackend, reselect: bool) -> Result { - use pcsc::Disposition; + /// If `reselect_application` is set, the application is SELECTed, + /// if the card reports having been reset. + fn new( + card: &'b mut PcscBackend, + reselect_application: Option<&[u8]>, + ) -> Result { + log::trace!("Start a transaction"); let mut was_reset = false; - let card_caps = card.card_caps(); - let reader_caps = card.reader_caps(); - let mode = card.mode(); + let mode = card.mode; + let reader_caps = card.reader_caps.clone(); - let mut c = card.card(); + let mut c = &mut card.card; loop { match c.transaction2() { - Ok(mut tx) => { - // A transaction has been successfully started + Ok(tx) => { + // A pcsc transaction has been successfully started + + let mut pt = Self { tx, reader_caps }; if was_reset { - log::trace!("start_tx: card was reset, select!"); + log::trace!("Card was reset"); - let mut txc = Self { - tx, - card_caps, - reader_caps: reader_caps.clone(), - }; + // If the caller expects that an application on the + // card has been selected, re-select the application + // here. + // + // When initially opening a card, we don't do this + // (freshly opened cards don't have an application + // "SELECT"ed). + if let Some(app) = reselect_application { + log::trace!("Will re-select an application after card reset"); - // In contexts where the caller of this fn - // expects that the card has already been opened, - // re-open the card here. - // For initial card-opening, we don't do this, then - // the caller always expects a card that has not - // been "select"ed yet. - if reselect { - PcscTransaction::select(&mut txc)?; + let mut res = CardTransaction::select(&mut pt, app)?; + log::trace!("select res: {:0x?}", res); + + // Drop any bytes before the status code. + // + // e.g. SELECT on Basic Card 3.4 returns: + // [6f, 1d, + // 62, 15, 84, 10, d2, 76, 0, 1, 24, 1, 3, 4, 0, 5, 0, 0, a8, 35, 0, 0, 8a, 1, 5, 64, 4, 53, 2, c4, 41, + // 90, 0] + if res.len() > 2 { + res.drain(0..res.len() - 2); + } + + if res != [0x90, 0x00] { + break Err(SmartcardError::Error(format!( + "Error while attempting to (re-)select {:x?}, status code {:x?}", + app, res + ))); + } + + log::trace!("re-select ok"); } - - tx = txc.tx; } - let txc = Self { - tx, - card_caps, - reader_caps, - }; - - break Ok(txc); + break Ok(pt); } Err((c_, pcsc::Error::ResetCard)) => { // Card was reset, need to reconnect was_reset = true; - // drop(res); - c = c_; log::trace!("start_tx: do reconnect"); - { - c.reconnect(mode, pcsc::Protocols::ANY, Disposition::ResetCard) - .map_err(|e| { - Error::Smartcard(SmartcardError::Error(format!( - "Reconnect failed: {e:?}" - ))) - })?; - } + c.reconnect(mode, pcsc::Protocols::ANY, Disposition::ResetCard) + .map_err(|e| SmartcardError::Error(format!("Reconnect failed: {e:?}")))?; log::trace!("start_tx: reconnected."); - // -> try opening a transaction again + // -> try opening a transaction again, in the next loop run } Err((_, e)) => { log::trace!("start_tx: error {:?}", e); - break Err(Error::Smartcard(SmartcardError::Error(format!( - "Error: {e:?}" - )))); + break Err(SmartcardError::Error(format!("Error: {e:?}"))); } }; } } - /// Try to select the OpenPGP application on a card - fn select(card_tx: &mut PcscTransaction) -> Result<(), Error> { - if ::select(card_tx).is_ok() { - Ok(()) - } else { - Err(Error::Smartcard(SmartcardError::SelectOpenPGPCardFailed)) - } - } - - /// Get application_related_data from card - fn application_related_data( - card_tx: &mut PcscTransaction, - ) -> Result { - ::application_related_data(card_tx).map_err(|e| { - Error::Smartcard(SmartcardError::Error(format!( - "TxClient: failed to get application_related_data {e:x?}" - ))) - }) - } - /// GET_FEATURE_REQUEST /// (see http://pcscworkgroup.com/Download/Specifications/pcsc10_v2.02.09.pdf) - fn features(&mut self) -> Result, Error> { + fn features(&mut self) -> Result, SmartcardError> { let mut recv = vec![0; 1024]; let cm_ioctl_get_feature_request = pcsc::ctl_code(3400); @@ -175,9 +148,7 @@ impl<'b> PcscTransaction<'b> { .tx .control(cm_ioctl_get_feature_request, &[], &mut recv) .map_err(|e| { - Error::Smartcard(SmartcardError::Error(format!( - "GET_FEATURE_REQUEST control call failed: {e:?}" - ))) + SmartcardError::Error(format!("GET_FEATURE_REQUEST control call failed: {e:?}")) })?; Ok(Tlv::parse_all(res)) @@ -190,42 +161,39 @@ impl<'b> PcscTransaction<'b> { PinType::Admin => 8, } } + /// Get the maximum pin length for pin_id. - fn max_pin_len(&self, pin: PinType) -> Result { - if let Some(card_caps) = self.card_caps { + fn max_pin_len( + &self, + pin: PinType, + card_caps: &Option, + ) -> Result { + if let Some(card_caps) = card_caps { match pin { PinType::User | PinType::Sign => Ok(card_caps.pw1_max_len()), PinType::Admin => Ok(card_caps.pw3_max_len()), } } else { - Err(Error::InternalError("card_caps is None".into())) + Err(SmartcardError::Error("card_caps is None".into())) } } } impl CardTransaction for PcscTransaction<'_> { - fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result, Error> { + fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result, SmartcardError> { let mut resp_buffer = vec![0; buf_size]; let resp = self .tx .transmit(cmd, &mut resp_buffer) .map_err(|e| match e { - pcsc::Error::NotTransacted => Error::Smartcard(SmartcardError::NotTransacted), - _ => Error::Smartcard(SmartcardError::Error(format!("Transmit failed: {e:?}"))), + pcsc::Error::NotTransacted => SmartcardError::NotTransacted, + _ => SmartcardError::Error(format!("Transmit failed: {e:?}")), })?; Ok(resp.to_vec()) } - fn init_card_caps(&mut self, caps: CardCaps) { - self.card_caps = Some(caps); - } - - fn card_caps(&self) -> Option<&CardCaps> { - self.card_caps.as_ref() - } - fn feature_pinpad_verify(&self) -> bool { self.reader_caps.contains_key(&FEATURE_VERIFY_PIN_DIRECT) } @@ -234,9 +202,13 @@ impl CardTransaction for PcscTransaction<'_> { self.reader_caps.contains_key(&FEATURE_MODIFY_PIN_DIRECT) } - fn pinpad_verify(&mut self, pin: PinType) -> Result, Error> { + fn pinpad_verify( + &mut self, + pin: PinType, + card_caps: &Option, + ) -> Result, SmartcardError> { let pin_min_size = self.min_pin_len(pin); - let pin_max_size = self.max_pin_len(pin)?; + let pin_max_size = self.max_pin_len(pin, card_caps)?; // Default to varlen, for now. // (NOTE: Some readers don't support varlen, and need explicit length @@ -310,26 +282,28 @@ impl CardTransaction for PcscTransaction<'_> { let verify_ioctl: [u8; 4] = self .reader_caps .get(&FEATURE_VERIFY_PIN_DIRECT) - .ok_or_else(|| Error::Smartcard(SmartcardError::Error("no reader_capability".into())))? + .ok_or_else(|| SmartcardError::Error("no reader_capability".into()))? .value() .try_into() - .map_err(|e| Error::ParseError(format!("unexpected feature data: {e:?}")))?; + .map_err(|e| SmartcardError::Error(format!("unexpected feature data: {e:?}")))?; let res = self .tx .control(u32::from_be_bytes(verify_ioctl).into(), &send, &mut recv) - .map_err(|e: pcsc::Error| { - Error::Smartcard(SmartcardError::Error(format!("pcsc Error: {e:?}"))) - })?; + .map_err(|e: pcsc::Error| SmartcardError::Error(format!("pcsc Error: {e:?}")))?; log::trace!(" <- pcsc pinpad_verify result: {:x?}", res); Ok(res.to_vec()) } - fn pinpad_modify(&mut self, pin: PinType) -> Result, Error> { + fn pinpad_modify( + &mut self, + pin: PinType, + card_caps: &Option, + ) -> Result, SmartcardError> { let pin_min_size = self.min_pin_len(pin); - let pin_max_size = self.max_pin_len(pin)?; + let pin_max_size = self.max_pin_len(pin, card_caps)?; // Default to varlen, for now. // (NOTE: Some readers don't support varlen, and need explicit length @@ -413,17 +387,15 @@ impl CardTransaction for PcscTransaction<'_> { let modify_ioctl: [u8; 4] = self .reader_caps .get(&FEATURE_MODIFY_PIN_DIRECT) - .ok_or_else(|| Error::Smartcard(SmartcardError::Error("no reader_capability".into())))? + .ok_or_else(|| SmartcardError::Error("no reader_capability".into()))? .value() .try_into() - .map_err(|e| Error::ParseError(format!("unexpected feature data: {e:?}")))?; + .map_err(|e| SmartcardError::Error(format!("unexpected feature data: {e:?}")))?; let res = self .tx .control(u32::from_be_bytes(modify_ioctl).into(), &send, &mut recv) - .map_err(|e: pcsc::Error| { - Error::Smartcard(SmartcardError::Error(format!("pcsc Error: {e:?}"))) - })?; + .map_err(|e: pcsc::Error| SmartcardError::Error(format!("pcsc Error: {e:?}")))?; log::trace!(" <- pcsc pinpad_modify result: {:x?}", res); @@ -432,16 +404,7 @@ impl CardTransaction for PcscTransaction<'_> { } impl PcscBackend { - fn card(&mut self) -> &mut pcsc::Card { - &mut self.card - } - - fn mode(&self) -> pcsc::ShareMode { - self.mode - } - - /// A list of "raw" opened PCSC Cards (without selecting the OpenPGP card - /// application) + /// A list of "raw" opened PCSC Cards (without selecting any application) fn raw_pcsc_cards(mode: pcsc::ShareMode) -> Result, SmartcardError> { log::trace!("raw_pcsc_cards start"); @@ -505,116 +468,52 @@ impl PcscBackend { } } - /// Starts from a list of all pcsc cards, then compares their OpenPGP - /// application identity with `ident` (if `ident` is None, all Cards are - /// returned). Returns fully initialized PcscCard structs for all matching - /// cards. - fn cards_filter(ident: Option<&str>, mode: pcsc::ShareMode) -> Result, Error> { - let mut cards: Vec = vec![]; - - for mut card in Self::raw_pcsc_cards(mode).map_err(Error::Smartcard)? { - log::trace!("cards_filter: next card"); - log::trace!(" status: {:x?}", card.status2_owned()); - - let mut store_card = false; - { - // start transaction - let mut p = PcscBackend::new(card, mode); - let mut txc = PcscTransaction::new(&mut p, false)?; - - { - if let Err(e) = PcscTransaction::select(&mut txc) { - log::trace!(" select error: {:?}", e); - } else { - // successfully opened the OpenPGP application - log::trace!(" select ok, will read ARD"); - log::trace!(" status: {:x?}", txc.tx.status2_owned()); - - if let Some(ident) = ident { - if let Ok(ard) = PcscTransaction::application_related_data(&mut txc) { - let aid = ard.application_id()?; - - if aid.ident() == ident.to_ascii_uppercase() { - // FIXME: handle multiple cards with matching ident - log::info!(" found card: {:?} (will use)", ident); - - // we want to return this one card - store_card = true; - } else { - log::info!(" found card: {:?} (won't use)", aid.ident()); - } - } else { - // couldn't read ARD for this card. - // ignore and move on - continue; - } - } else { - // we want to return all cards - store_card = true; - } - } - } - - drop(txc); - card = p.card; - } - - if store_card { - let pcsc = PcscBackend::new(card, mode); - cards.push(pcsc.initialize_card()?); - } - } - - log::trace!("cards_filter: found {} cards", cards.len()); - - Ok(cards) - } - - /// Return all cards on which the OpenPGP application could be selected. + /// Returns an Iterator over Smart Cards that are accessible via PCSC. /// - /// Each card has the OpenPGP application selected, card_caps and reader_caps have been - /// initialized. - pub fn cards(mode: Option) -> Result, Error> { - Self::cards_filter(None, default_mode(mode)) + /// No application is SELECTed on the cards. + /// You can not assume that any particular application is available on the cards. + pub fn cards( + mode: Option, + ) -> Result>, SmartcardError> { + let mode = mode.unwrap_or(pcsc::ShareMode::Shared); + + let cards = Self::raw_pcsc_cards(mode)?; + + Ok(cards.into_iter().map(move |card| { + let backend = PcscBackend { + card, + mode, + reader_caps: Default::default(), + }; + + backend.initialize_card() + })) } - /// Returns the OpenPGP card that matches `ident`, if it is available. - /// A fully initialized PcscCard is returned: the OpenPGP application has - /// been selected, card_caps and reader_caps have been initialized. - pub fn open_by_ident(ident: &str, mode: Option) -> Result { - log::trace!("open_by_ident for {:?}", ident); + /// Returns an Iterator over Smart Cards that are accessible via PCSC. + /// Like [Self::cards], but returns the cards as [CardBackend]. + pub fn card_backends( + mode: Option, + ) -> Result< + impl Iterator, SmartcardError>>, + SmartcardError, + > { + let cards = PcscBackend::cards(mode)?; - let mut cards = Self::cards_filter(Some(ident), default_mode(mode))?; - - if !cards.is_empty() { - // FIXME: handle >1 cards found - - Ok(cards.pop().unwrap()) - } else { - Err(Error::Smartcard(SmartcardError::CardNotFound( - ident.to_string(), - ))) - } + Ok(cards.map(|c| match c { + Ok(c) => Ok(Box::new(c) as Box), + Err(e) => Err(e), + })) } - fn new(card: pcsc::Card, mode: pcsc::ShareMode) -> Self { - Self { - card, - mode, - card_caps: None, - reader_caps: HashMap::new(), - } - } - - /// Initialized a PcscCard: - /// - Obtain and store feature lists from reader (pinpad functionality). - /// - Get ARD from card, set CardCaps based on ARD. - fn initialize_card(mut self) -> Result { + /// Initialize this PcscBackend (obtains and stores feature lists from reader, + /// to determine if the reader offers PIN pad functionality). + fn initialize_card(mut self) -> Result { log::trace!("pcsc initialize_card"); let mut h: HashMap = HashMap::default(); - let mut txc = PcscTransaction::new(&mut self, true)?; + let mut txc = PcscTransaction::new(&mut self, None)?; // Get Features from reader (pinpad verify/modify) if let Ok(feat) = txc.features() { @@ -624,65 +523,22 @@ impl PcscBackend { } } - // Initialize CardTransaction (set CardCaps from ARD) - ::initialize(&mut txc)?; - - let cc = txc.card_caps().cloned(); - drop(txc); - self.card_caps = cc; - for (a, b) in h { self.reader_caps.insert(a, b); } Ok(self) } - - fn card_caps(&self) -> Option { - self.card_caps - } - fn reader_caps(&self) -> HashMap { - self.reader_caps.clone() - } - - /// This command will try to activate an OpenPGP card, if: - /// - exactly one card is connected to the system - /// - that card replies to SELECT with Status 6285 - /// - /// See OpenPGP card spec (version 3.4.1): 7.2.17 ACTIVATE FILE - pub fn activate_terminated_card() -> Result<(), Error> { - let mut cards = - Self::raw_pcsc_cards(pcsc::ShareMode::Exclusive).map_err(Error::Smartcard)?; - if cards.len() != 1 { - return Err(Error::InternalError(format!( - "This command is only allowed if exactly one card is connected, found {}.", - cards.len() - ))); - } - - let card = cards.pop().unwrap(); - - let mut backend = PcscBackend::new(card, pcsc::ShareMode::Exclusive); - let mut card_tx = Box::new(PcscTransaction::new(&mut backend, false)?); - - match ::select(&mut card_tx) { - Err(Error::CardStatus(openpgp_card::StatusBytes::TerminationState)) => { - let _ = ::activate_file(&mut card_tx)?; - Ok(()) - } - - _ => Err(Error::InternalError( - "Card doesn't appear to be terminated.".to_string(), - )), - } - } } impl CardBackend for PcscBackend { - /// Get a TxClient for this PcscCard (this starts a transaction) - fn transaction(&mut self) -> Result, Error> { - Ok(Box::new(PcscTransaction::new(self, true)?)) + /// Get a CardTransaction for this PcscBackend (this starts a transaction) + fn transaction( + &mut self, + reselect_application: Option<&[u8]>, + ) -> Result, SmartcardError> { + Ok(Box::new(PcscTransaction::new(self, reselect_application)?)) } } diff --git a/scdc/Cargo.toml b/scdc/Cargo.toml index c395a37..38b5ccd 100644 --- a/scdc/Cargo.toml +++ b/scdc/Cargo.toml @@ -1,19 +1,19 @@ -# SPDX-FileCopyrightText: 2021 Heiko Schaefer +# SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer # SPDX-License-Identifier: MIT OR Apache-2.0 [package] -name = "openpgp-card-scdc" -description = "Experimental SCDaemon Client, for use with the openpgp-card crate" +name = "card-backend-scdc" +description = "Experimental SCDaemon Client, e.g. for use with the openpgp-card crate" authors = ["Heiko Schaefer "] license = "MIT OR Apache-2.0" -version = "0.2.2" +version = "0.4.0" edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" documentation = "https://docs.rs/crate/openpgp-card-scdc" [dependencies] -openpgp-card = { path = "../openpgp-card", version = "0.3" } -sequoia-ipc = "0.27" +card-backend = { path = "../card-backend", version = "0.1" } +sequoia-ipc = "0.29" hex = "0.4" futures = "0.3" tokio = { version = "1.13.1", features = ["rt-multi-thread"] } diff --git a/scdc/README.md b/scdc/README.md index 420eea1..b204a7b 100644 --- a/scdc/README.md +++ b/scdc/README.md @@ -1,18 +1,19 @@ -**scdaemon client for the openpgp-card library** +# scdaemon based backend (e.g., for the openpgp-card library) -This crate provides `ScdBackend`/`ScdTransaction`, which is an implementation of the -`CardBackend`/`CardTransaction` traits that uses an instance of GnuPG's +This crate provides `ScdBackend`/`ScdTransaction`, which is an implementation +of the `CardBackend`/`CardTransaction` traits that uses an instance of GnuPG's [scdaemon](https://www.gnupg.org/documentation/manuals/gnupg/Invoking-SCDAEMON.html) to access OpenPGP cards. -Note that (unlike `openpgp-card-pcsc`), this backend doesn't implement transaction guarantees. +Note that (unlike `card-backend-pcsc`), this backend doesn't implement +transaction guarantees. -**Known limitations** +## Known limitations - Uploading RSA 4096 keys via `scdaemon` doesn't work with cards that don't support Command Chaining (e.g. the "Floss Shop OpenPGP Smart Card"). @@ -24,14 +25,14 @@ Note that (unlike `openpgp-card-pcsc`), this backend doesn't implement transacti OpenPGP card operations fit into this constraint). - When using `scdaemon` via pcsc (by configuring `scdaemon` with - `disable-ccid`), choosing a specific card of multiple plugged in OpenPGP + `disable-ccid`), choosing a specific card of multiple plugged-in OpenPGP cards seems to be broken. So you probably want to plug in only one OpenPGP card at a time when using `openpgp-card-scdc` combined with `disable-ccid`. - When using `scdaemon` via its default `ccid` driver, choosing a - specific one of multiple plugged in OpenPGP cards seems to only work up - to 4 plugged in cards. + specific one of multiple plugged-in OpenPGP cards seems to only work up + to 4 plugged-in cards. So you probably want to plug in at most four OpenPGP cards at a time when - using `openpgp-card-scdc` with its ccid driver. + using `card-backend-scdc` with its ccid driver. (This limit has been raised in GnuPG 2.3.x) diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index a8914d3..f728213 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -1,17 +1,17 @@ -// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! This crate implements the experimental `ScdBackend`/`ScdTransaction` backend for the //! `openpgp-card` crate. -//! It uses GnuPG's scdaemon (via GnuPG Agent) to access OpenPGP cards. +//! It uses GnuPG's scdaemon (via GnuPG Agent) to access smart cards (including OpenPGP cards). //! -//! Note that (unlike `openpgp-card-pcsc`), this backend doesn't implement transaction guarantees. +//! Note that (unlike `card-backend-pcsc`), this backend doesn't implement transaction guarantees. use std::sync::Mutex; +use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError}; use futures::StreamExt; use lazy_static::lazy_static; -use openpgp_card::{CardBackend, CardCaps, CardTransaction, Error, PinType, SmartcardError}; use sequoia_ipc::assuan::Response; use sequoia_ipc::gnupg::{Agent, Context}; use tokio::runtime::Runtime; @@ -33,36 +33,44 @@ lazy_static! { /// communication within GnuPG? Are \r\n added?) const ASSUAN_LINELENGTH: usize = 1000; -/// The maximum number of bytes for a command that we will send to +/// The maximum number of bytes for a command that we can send to /// scdaemon (via Assuan). /// /// Each command byte gets sent via Assuan as a two-character hex string. /// -/// 22 characters are used to send "SCD APDU --exlen=abcd " -/// (So, as a defensive limit, 25 characters are subtracted). +/// 17 characters are used to send "SCD APDU --exlen " /// /// In concrete terms, this limit means that some commands (with big /// parameters) cannot be sent to the card, when the card doesn't support /// command chaining (like the floss-shop "OpenPGP Smart Card 3.4"). /// /// In particular, uploading rsa4096 keys fails via scdaemon, with such cards. -const APDU_CMD_BYTES_MAX: usize = (ASSUAN_LINELENGTH - 25) / 2; +/// +/// The value of "36" was experimentally determined: +/// This value results in the biggest APDU_CMD_BYTES_MAX that still allows +/// uploading an RSA4k key onto a YubiKey 5. +const APDU_CMD_BYTES_MAX: usize = (ASSUAN_LINELENGTH - 36) / 2; /// An implementation of the CardBackend trait that uses GnuPG's scdaemon -/// (via GnuPG Agent) to access OpenPGP card devices. +/// (via GnuPG Agent) to access smart cards. pub struct ScdBackend { agent: Agent, - card_caps: Option, +} + +/// Boxing helper (for easier consumption of PcscBackend in openpgp_card and openpgp_card_sequoia) +impl From for Box { + fn from(backend: ScdBackend) -> Box { + Box::new(backend) + } } impl ScdBackend { - /// Open a CardApp that uses an scdaemon instance as its backend. - /// The specific card with AID `serial` is requested from scdaemon. - pub fn open_by_serial(agent: Option, serial: &str) -> Result { + /// Request card with AID `serial` from scdaemon, and return it as a ScdBackend. + /// + /// The client may provide a GnuPG `agent` to use. + pub fn open_by_serial(agent: Option, serial: &str) -> Result { let mut card = ScdBackend::new(agent, true)?; - card.select_card(serial)?; - - card.transaction()?.initialize()?; + card.select_by_serial(serial)?; Ok(card) } @@ -72,22 +80,21 @@ impl ScdBackend { /// If multiple cards are available, scdaemon implicitly selects one. /// /// (NOTE: implicitly picking an unspecified card might be a bad idea. - /// You might want to avoid using this function.) - pub fn open_yolo(agent: Option) -> Result { - let mut card = ScdBackend::new(agent, true)?; - - card.transaction()?.initialize()?; + /// You might want to avoid using this function, or check which card + /// you received.) + pub fn open_yolo(agent: Option) -> Result { + let card = ScdBackend::new(agent, true)?; Ok(card) } /// Helper fn that shuts down scdaemon via GnuPG Agent. /// This may be useful to obtain access to a Smard card via PCSC. - pub fn shutdown_scd(agent: Option) -> Result<(), Error> { + pub fn shutdown_scd(agent: Option) -> Result<(), SmartcardError> { let mut scdc = Self::new(agent, false)?; - scdc.send("SCD RESTART")?; - scdc.send("SCD BYE")?; + scdc.execute("SCD RESTART")?; + scdc.execute("SCD BYE")?; Ok(()) } @@ -97,26 +104,18 @@ impl ScdBackend { /// /// If `agent` is None, a Context with the default GnuPG home directory /// is used. - fn new(agent: Option, init: bool) -> Result { - let agent = if let Some(agent) = agent { - agent - } else { - // Create and use a new Agent based on a default Context - let ctx = Context::new().map_err(|e| { - Error::Smartcard(SmartcardError::Error(format!("Context::new failed {e}"))) - })?; - RT.lock() - .unwrap() - .block_on(Agent::connect(&ctx)) - .map_err(|e| { - Error::Smartcard(SmartcardError::Error(format!("Agent::connect failed {e}"))) - })? - }; + fn new(agent: Option, init: bool) -> Result { + let agent = agent.unwrap_or({ + let rt = RT.lock().unwrap(); - let mut scdc = Self { - agent, - card_caps: None, - }; + // Create and use a new Agent based on a default Context + let ctx = Context::new() + .map_err(|e| SmartcardError::Error(format!("Context::new failed {e}")))?; + rt.block_on(Agent::connect(&ctx)) + .map_err(|e| SmartcardError::Error(format!("Agent::connect failed {e}")))? + }); + + let mut scdc = Self { agent }; if init { scdc.serialno()?; @@ -125,24 +124,23 @@ impl ScdBackend { Ok(scdc) } - fn send2(&mut self, cmd: &str) -> Result<(), Error> { - self.agent.send(cmd).map_err(|e| { - Error::Smartcard(SmartcardError::Error(format!( - "scdc agent send failed: {e}" - ))) - }) + /// Just send a command, without looking at the results at all + fn send_cmd(&mut self, cmd: &str) -> Result<(), SmartcardError> { + self.agent + .send(cmd) + .map_err(|e| SmartcardError::Error(format!("scdc agent send failed: {e}"))) } - /// Call "SCD SERIALNO", which causes scdaemon to be started by gpg - /// agent (if it's not running yet). - fn serialno(&mut self) -> Result<(), Error> { + /// Call "SCD SERIALNO", which causes scdaemon to be started by gpg-agent + /// (if it's not running yet). + fn serialno(&mut self) -> Result<(), SmartcardError> { let rt = RT.lock().unwrap(); - let send = "SCD SERIALNO"; - self.send2(send)?; + let cmd = "SCD SERIALNO"; + self.send_cmd(cmd)?; while let Some(response) = rt.block_on(self.agent.next()) { - log::trace!("init res: {:x?}", response); + log::trace!("SCD SERIALNO res: {:x?}", response); if let Ok(Response::Status { .. }) = response { // drop remaining lines @@ -154,26 +152,22 @@ impl ScdBackend { } } - Err(Error::Smartcard(SmartcardError::Error( - "SCDC init() failed".into(), - ))) + Err(SmartcardError::Error("SCDC init() failed".into())) } - /// Ask scdameon to switch to using a specific OpenPGP card, based on + /// Ask scdaemon to switch to using a specific OpenPGP card, based on /// its `serial`. - fn select_card(&mut self, serial: &str) -> Result<(), Error> { - let send = format!("SCD SERIALNO --demand={serial}"); - self.send2(&send)?; - + fn select_by_serial(&mut self, serial: &str) -> Result<(), SmartcardError> { let rt = RT.lock().unwrap(); + let send = format!("SCD SERIALNO --demand={serial}"); + self.send_cmd(&send)?; + while let Some(response) = rt.block_on(self.agent.next()) { log::trace!("select res: {:x?}", response); if response.is_err() { - return Err(Error::Smartcard(SmartcardError::CardNotFound( - serial.into(), - ))); + return Err(SmartcardError::CardNotFound(serial.into())); } if let Ok(Response::Status { .. }) = response { @@ -186,21 +180,19 @@ impl ScdBackend { } } - Err(Error::Smartcard(SmartcardError::CardNotFound( - serial.into(), - ))) + Err(SmartcardError::CardNotFound(serial.into())) } - fn send(&mut self, cmd: &str) -> Result<(), Error> { - self.send2(cmd)?; - + fn execute(&mut self, cmd: &str) -> Result<(), SmartcardError> { let rt = RT.lock().unwrap(); + self.send_cmd(cmd)?; + while let Some(response) = rt.block_on(self.agent.next()) { log::trace!("select res: {:x?}", response); if let Err(e) = response { - return Err(Error::Smartcard(SmartcardError::Error(format!("{e:?}")))); + return Err(SmartcardError::Error(format!("{e:?}"))); } if response.is_ok() { @@ -213,14 +205,17 @@ impl ScdBackend { } } - Err(Error::Smartcard(SmartcardError::Error(format!( + Err(SmartcardError::Error(format!( "Error sending command {cmd}" - )))) + ))) } } impl CardBackend for ScdBackend { - fn transaction(&mut self) -> Result, Error> { + fn transaction( + &mut self, + _reselect_application: Option<&[u8]>, + ) -> Result, SmartcardError> { Ok(Box::new(ScdTransaction { scd: self })) } } @@ -230,41 +225,40 @@ pub struct ScdTransaction<'a> { } impl CardTransaction for ScdTransaction<'_> { - fn transmit(&mut self, cmd: &[u8], _: usize) -> Result, Error> { + fn transmit(&mut self, cmd: &[u8], _: usize) -> Result, SmartcardError> { log::trace!("SCDC cmd len {}", cmd.len()); let hex = hex::encode(cmd); - // (Unwrap is ok here, not having a card_caps is fine) - let ext = if self.card_caps().is_some() && self.card_caps().unwrap().ext_support() { - // If we know about card_caps, and can do extended length we - // set "exlen" accordingly ... - format!("--exlen={} ", self.card_caps().unwrap().max_rsp_bytes()) - } else { - // ... otherwise don't send "exlen" to scdaemon - "".to_string() - }; + // Always set "--exlen" (without explicit parameter for length) + // + // FIXME: Does this ever cause problems? + // + // Hypothesis: this should be ok. + // Allowing too big of a return value should not do any effective harm + // (just maybe cause some slightly too large memory allocations). + let ext = "--exlen ".to_string(); - let send = format!("SCD APDU {ext}{hex}\n"); + let send = format!("SCD APDU {ext}{hex}"); log::trace!("SCDC command: '{}'", send); if send.len() > ASSUAN_LINELENGTH { - return Err(Error::Smartcard(SmartcardError::Error(format!( + return Err(SmartcardError::Error(format!( "APDU command is too long ({}) to send via Assuan", send.len() - )))); + ))); } - self.scd.send2(&send)?; - let rt = RT.lock().unwrap(); + self.scd.send_cmd(&send)?; + while let Some(response) = rt.block_on(self.scd.agent.next()) { - log::trace!("res: {:x?}", response); + log::trace!("transmit res: {:x?}", response); if response.is_err() { - return Err(Error::Smartcard(SmartcardError::Error(format!( + return Err(SmartcardError::Error(format!( "Unexpected error response from SCD {response:?}" - )))); + ))); } if let Ok(Response::Data { partial }) = response { @@ -279,17 +273,7 @@ impl CardTransaction for ScdTransaction<'_> { } } - Err(Error::Smartcard(SmartcardError::Error( - "no response found".into(), - ))) - } - - fn init_card_caps(&mut self, caps: CardCaps) { - self.scd.card_caps = Some(caps); - } - - fn card_caps(&self) -> Option<&CardCaps> { - self.scd.card_caps.as_ref() + Err(SmartcardError::Error("no response found".into())) } /// Return limit for APDU command size via scdaemon (based on Assuan @@ -298,23 +282,31 @@ impl CardTransaction for ScdTransaction<'_> { Some(APDU_CMD_BYTES_MAX) } - /// FIXME: not implemented yet + /// Not implemented yet fn feature_pinpad_verify(&self) -> bool { false } - /// FIXME: not implemented yet + /// Not implemented yet fn feature_pinpad_modify(&self) -> bool { false } - /// FIXME: not implemented yet - fn pinpad_verify(&mut self, _id: PinType) -> Result, Error> { + /// Not implemented yet + fn pinpad_verify( + &mut self, + _id: PinType, + _card_caps: &Option, + ) -> Result, SmartcardError> { unimplemented!() } - /// FIXME: not implemented yet - fn pinpad_modify(&mut self, _id: PinType) -> Result, Error> { + /// Not implemented yet + fn pinpad_modify( + &mut self, + _id: PinType, + _card_caps: &Option, + ) -> Result, SmartcardError> { unimplemented!() } } From fac3ac646815a03acd99c425510b799da4a67d86 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 26 Aug 2023 16:56:21 +0200 Subject: [PATCH 037/115] README --- README.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 6cc2c99..10df6d3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ @@ -12,13 +12,15 @@ standard, in Rust. This project consists of the following library crates: - [openpgp-card](https://crates.io/crates/openpgp-card), which offers a - relatively low level OpenPGP card client API. + relatively low-level OpenPGP card client API. It is PGP implementation agnostic. -- [openpgp-card-pcsc](https://crates.io/crates/openpgp-card-pcsc), - a backend to communicate with smartcards via +- [card-backend](https://crates.io/crates/card-backend), + a shared trait for Smart Card backends +- [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc), + a backend implementation to communicate with smartcards via [pcsc](https://pcsclite.apdu.fr/). -- [openpgp-card-scdc](https://crates.io/crates/openpgp-card-scdc), - a backend to communicate with smartcards via an +- [card-backend-scdc](https://crates.io/crates/card-backend-scdc), + a backend implementation to communicate with smartcards via an [scdaemon](https://www.gnupg.org/documentation/manuals/gnupg/Invoking-SCDAEMON.html#Invoking-SCDAEMON) instance. - [openpgp-card-sequoia](https://crates.io/crates/openpgp-card-sequoia), @@ -29,8 +31,10 @@ This is how the libraries relate to each other (and to applications): ```mermaid graph BT - OP["openpgp-card-pcsc
(pcsclite backend)"] --> OC - OS["openpgp-card-scdc
(scdaemon backend)"] --> OC["openpgp-card
(low level API)"] + CB["card-backend
(shared trait)"] --> OP + CB --> OS + OP["card-backend-pcsc
(pcsclite backend)"] --> OC + OS["card-backend-scdc
(scdaemon backend)"] --> OC["openpgp-card
(low level API)"] OC --> OCS["openpgp-card-sequoia
(high level Sequoia PGP-based API)"] OC -.-> U1[Applications based on low level API] OCS -.-> U2[Sequoia PGP-based applications] @@ -43,9 +47,9 @@ Additionally, there are the following non-library crates that are built on top of the libraries described above: - [openpgp-card-tools](https://crates.io/crates/openpgp-card-sequoia), - a CLI tool to inspect, manage and use OpenPGP cards, aimed at end users. + the `opgpcard` CLI tool to inspect, manage and use OpenPGP cards, aimed at end users. - [openpgp-card-tests](https://gitlab.com/openpgp-card/openpgp-card/-/tree/main/card-functionality), - a test-suite that runs OpenPGP card operations on smartcards. + a test-suite that runs OpenPGP card operations on Smart Cards. - [openpgp-card-examples](https://gitlab.com/openpgp-card/openpgp-card/-/tree/main/card-examples), small example applications that demonstrate how you can use these libraries in your own projects to access OpenPGP card functionality. @@ -63,11 +67,11 @@ implementation. ### Backends -Typically, `openpgp-card` will be used with the `openpgp-card-pcsc` backend, -which uses the standard pcsclite library to communicate with cards. +Typically, `openpgp-card` will be used with the `card-backend-pcsc` backend, +which uses the standard pcsc-lite library to communicate with cards. However, alternative backends can be used and may be useful. -The experimental, alternative `openpgp-card-scdc` backend uses scdaemon from +The experimental, alternative `card-backend-scdc` backend uses scdaemon from the GnuPG project as a low-level transport layer to interact with OpenPGP cards. @@ -84,7 +88,7 @@ Backends implement: All higher level and/or OpenPGP card-specific logic (including command chaining) is handled in the `openpgp-card` layer. -### The **openpgp-card-sequoia** crate +### The openpgp-card-sequoia crate Offers a higher level interface, based around Sequoia PGP datastructures. @@ -100,8 +104,8 @@ library against OpenPGP cards. However, OpenPGP cards are, usually, physical devices that you plug into your computer, e.g. as USB sticks, or Smart cards (this is, of course, the usual point of these cards: they are independent devices, which are only loosely -coupled with your regular computing environment. However, for automated -testing, such as CI, this can be a complication.) +coupled with your regular computing environment). +For automated testing, such as CI, this is a complication. There are at least two approaches for running tests against software-based OpenPGP cards: From 9c41ab22861c107d75caf4bf41e0b88c236b75ef Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 27 Aug 2023 18:10:15 +0200 Subject: [PATCH 038/115] openpgp-card: add trace log for "Import key material" command --- openpgp-card/src/keys.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index 9e77277..507d809 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -215,7 +215,9 @@ pub(crate) fn key_import( card_tx.set_algorithm_attributes(key_type, &algo)?; } + log::info!("Import key material"); card_tx.send_command(key_cmd, false)?.check_ok()?; + card_tx.set_fingerprint(fp, key_type)?; card_tx.set_creation_time(key.timestamp(), key_type)?; From 61175dd64635ae202ac290c4fb3300f9434d7ebb Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 26 Aug 2023 19:28:23 +0200 Subject: [PATCH 039/115] openpgp-card: add terminate_df(), activate_file() to OpenPgpTransaction --- openpgp-card/src/openpgp.rs | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/openpgp-card/src/openpgp.rs b/openpgp-card/src/openpgp.rs index 5cdf73c..b656d4e 100644 --- a/openpgp-card/src/openpgp.rs +++ b/openpgp-card/src/openpgp.rs @@ -164,6 +164,26 @@ impl<'a> OpenPgpTransaction<'a> { .try_into() } + // TERMINATE DF + + /// 7.2.16 TERMINATE DF + pub fn terminate_df(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: terminate_df"); + + self.send_command(commands::terminate_df(), false)?; + Ok(()) + } + + // ACTIVATE FILE + + /// 7.2.17 ACTIVATE FILE + pub fn activate_file(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: activate_file"); + + self.send_command(commands::activate_file(), false)?; + Ok(()) + } + // --- pinpad --- /// Does the reader support FEATURE_VERIFY_PIN_DIRECT? @@ -463,17 +483,8 @@ impl<'a> OpenPgpTransaction<'a> { } } - // terminate_df [apdu 00 e6 00 00] - log::info!(" terminate_df"); - let term = commands::terminate_df(); - let resp = self.send_command(term, false)?; - resp.check_ok()?; - - // activate_file [apdu 00 44 00 00] - log::info!(" activate_file"); - let act = commands::activate_file(); - let resp = self.send_command(act, false)?; - resp.check_ok()?; + self.terminate_df()?; + self.activate_file()?; Ok(()) } From 84ee2a64f1a9d9170b53ef3a89a740db16a7bc85 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 28 Aug 2023 12:00:35 +0200 Subject: [PATCH 040/115] openpgp-card: explicitly limit command chunk size to 255 if extended length is unsupported --- openpgp-card/src/apdu.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpgp-card/src/apdu.rs b/openpgp-card/src/apdu.rs index 6fc9d9d..569481c 100644 --- a/openpgp-card/src/apdu.rs +++ b/openpgp-card/src/apdu.rs @@ -158,8 +158,10 @@ where log::trace!("chained command mode"); + let cmd_chunk_size = if ext_support { max_cmd_bytes } else { 255 }; + // Break up payload into chunks that fit into one command, each - let chunks: Vec<_> = cmd.data().chunks(max_cmd_bytes).collect(); + let chunks: Vec<_> = cmd.data().chunks(cmd_chunk_size).collect(); for (i, d) in chunks.iter().enumerate() { let last = i == chunks.len() - 1; From f4cc72c37b49e70952210245e69628f6b8f34471 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 28 Aug 2023 17:13:01 +0200 Subject: [PATCH 041/115] card-backend: Add CardTransaction::was_reset() This can signal to consumers that state on the card may have been reset (e.g. PIN verification state) --- card-backend/src/lib.rs | 5 +++++ openpgp-card/src/openpgp.rs | 5 +++++ pcsc/src/lib.rs | 13 ++++++++++++- scdc/src/lib.rs | 5 +++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/card-backend/src/lib.rs b/card-backend/src/lib.rs index dc90499..c0851a0 100644 --- a/card-backend/src/lib.rs +++ b/card-backend/src/lib.rs @@ -65,6 +65,11 @@ pub trait CardTransaction { pin: PinType, card_caps: &Option, ) -> Result, SmartcardError>; + + /// Has a reset been detected while starting this transaction? + /// + /// (Backends may choose to always return false) + fn was_reset(&self) -> bool; } /// Information about the capabilities of a card. diff --git a/openpgp-card/src/openpgp.rs b/openpgp-card/src/openpgp.rs index b656d4e..f2e14b7 100644 --- a/openpgp-card/src/openpgp.rs +++ b/openpgp-card/src/openpgp.rs @@ -124,6 +124,11 @@ impl OpenPgp { let card_caps = &mut self.card_caps; let tx = self.card.transaction(Some(OP_APP))?; + if tx.was_reset() { + // FIXME + // Signal state invalidation? (PIN verification, ...) + } + Ok(OpenPgpTransaction { tx, card_caps }) } } diff --git a/pcsc/src/lib.rs b/pcsc/src/lib.rs index ace040a..49bac15 100644 --- a/pcsc/src/lib.rs +++ b/pcsc/src/lib.rs @@ -49,6 +49,7 @@ impl From for Box { pub struct PcscTransaction<'b> { tx: pcsc::Transaction<'b>, reader_caps: HashMap, // FIXME: gets manually cloned + was_reset: bool, } impl<'b> PcscTransaction<'b> { @@ -74,11 +75,17 @@ impl<'b> PcscTransaction<'b> { Ok(tx) => { // A pcsc transaction has been successfully started - let mut pt = Self { tx, reader_caps }; + let mut pt = Self { + tx, + reader_caps, + was_reset: false, + }; if was_reset { log::trace!("Card was reset"); + pt.was_reset = true; + // If the caller expects that an application on the // card has been selected, re-select the application // here. @@ -401,6 +408,10 @@ impl CardTransaction for PcscTransaction<'_> { Ok(res.to_vec()) } + + fn was_reset(&self) -> bool { + self.was_reset + } } impl PcscBackend { diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index f728213..4d9fbd0 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -309,4 +309,9 @@ impl CardTransaction for ScdTransaction<'_> { ) -> Result, SmartcardError> { unimplemented!() } + + /// Not implemented here + fn was_reset(&self) -> bool { + false + } } From 625df59c867a2b88f39b4bd71307765a393d7748 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 28 Aug 2023 17:28:22 +0200 Subject: [PATCH 042/115] pcsc: Don't return an error if no reader is found Fixes #68 --- pcsc/src/lib.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/pcsc/src/lib.rs b/pcsc/src/lib.rs index 49bac15..0bc23c1 100644 --- a/pcsc/src/lib.rs +++ b/pcsc/src/lib.rs @@ -441,15 +441,10 @@ impl PcscBackend { log::trace!(" readers: {:?}", readers); - let mut found_reader = false; - let mut cards = vec![]; // Find a reader with a SmartCard. for reader in readers { - // We've seen at least one smartcard reader - found_reader = true; - log::trace!("Checking reader: {:?}", reader); // Try connecting to card in this reader @@ -472,11 +467,7 @@ impl PcscBackend { cards.push(card); } - if !found_reader { - Err(SmartcardError::NoReaderFoundError) - } else { - Ok(cards) - } + Ok(cards) } /// Returns an Iterator over Smart Cards that are accessible via PCSC. From 0c0702104338b9cd1d98bcb6f5aa0f14cba58ca3 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 09:56:32 +0200 Subject: [PATCH 043/115] pcsc: store reader name internally The reader name could later be used as a source of information to signal reader limitations to callers (via CardCaps, e.g. readers that don't support extended length commands) --- pcsc/src/lib.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/pcsc/src/lib.rs b/pcsc/src/lib.rs index 0bc23c1..36fc681 100644 --- a/pcsc/src/lib.rs +++ b/pcsc/src/lib.rs @@ -26,6 +26,12 @@ pub struct PcscBackend { card: pcsc::Card, mode: pcsc::ShareMode, reader_caps: HashMap, + + // The reader name could be used as a hint about capabilities + // (e.g. readers that don't support extended length) + #[allow(dead_code)] + reader_name: String, + // FIXME: add a "adjust_card_caps" fn to card-backend? (could replace `max_cmd_len`) } /// Boxing helper (for easier consumption of PcscBackend in openpgp_card and openpgp_card_sequoia) @@ -415,8 +421,9 @@ impl CardTransaction for PcscTransaction<'_> { } impl PcscBackend { - /// A list of "raw" opened PCSC Cards (without selecting any application) - fn raw_pcsc_cards(mode: pcsc::ShareMode) -> Result, SmartcardError> { + /// A list of "raw" opened PCSC Cards and reader names + /// (No application is selected) + fn raw_pcsc_cards(mode: pcsc::ShareMode) -> Result, SmartcardError> { log::trace!("raw_pcsc_cards start"); let ctx = match pcsc::Context::establish(pcsc::Scope::User) { @@ -445,7 +452,9 @@ impl PcscBackend { // Find a reader with a SmartCard. for reader in readers { - log::trace!("Checking reader: {:?}", reader); + let name = String::from_utf8_lossy(reader.to_bytes()); + + log::trace!("Checking reader: {:?}", name); // Try connecting to card in this reader let card = match ctx.connect(reader, mode, pcsc::Protocols::ANY) { @@ -464,7 +473,7 @@ impl PcscBackend { log::trace!("Found card"); - cards.push(card); + cards.push((card, name.to_string())); } Ok(cards) @@ -483,9 +492,10 @@ impl PcscBackend { Ok(cards.into_iter().map(move |card| { let backend = PcscBackend { - card, + card: card.0, mode, reader_caps: Default::default(), + reader_name: card.1, }; backend.initialize_card() From 4fda5d800ad91e35d688d2a80ca8d8f1335270ac Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 31 Aug 2023 13:17:32 +0200 Subject: [PATCH 044/115] scdc: remove redundant log output --- scdc/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index 4d9fbd0..9e80a08 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -254,7 +254,6 @@ impl CardTransaction for ScdTransaction<'_> { self.scd.send_cmd(&send)?; while let Some(response) = rt.block_on(self.scd.agent.next()) { - log::trace!("transmit res: {:x?}", response); if response.is_err() { return Err(SmartcardError::Error(format!( "Unexpected error response from SCD {response:?}" From 15646bc50b4b4d619b8ba2c10687985e6d477dad Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 26 Aug 2023 16:47:09 +0200 Subject: [PATCH 045/115] openpgp-card-sequoia: adjust to card-backend refactor Note that a `open_by_ident` fn was added here: the backend now doesn't have knowledge of applications (like OpenPGP) anymore, so it can't select a card by OpenPGP card ident anymore. --- openpgp-card-sequoia/Cargo.toml | 7 +-- openpgp-card-sequoia/src/lib.rs | 79 +++++++++++++++++++------------ openpgp-card-sequoia/src/types.rs | 2 +- 3 files changed, 54 insertions(+), 34 deletions(-) diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index 1936c47..fdccf11 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -5,13 +5,14 @@ name = "openpgp-card-sequoia" description = "Wrapper of openpgp-card for use with Sequoia PGP" license = "MIT OR Apache-2.0" -version = "0.1.5" +version = "0.2.0-pre" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" documentation = "https://docs.rs/crate/openpgp-card-sequoia" [dependencies] +card-backend = { path = "../card-backend", version = "0.1" } sequoia-openpgp = { version = "1.4", default-features = false } openpgp-card = { path = "../openpgp-card", version = "0.4" } chrono = "0.4" @@ -21,8 +22,8 @@ log = "0.4" rsa = "0.8.1" [dev-dependencies] -openpgp-card-pcsc = { path = "../pcsc", version = "0.3" } -#openpgp-card-scdc = { path = "../scdc", version = "0.3" } +card-backend-pcsc = { path = "../pcsc", version = "0.4" } +#card-backend-scdc = { path = "../scdc", version = "0.4" } env_logger = "0.10" testresult = "0.3.0" diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 8b7b07c..2a3accc 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -16,17 +16,17 @@ //! # Backends //! //! To make use of this crate, you need to use a backend for communication -//! with cards. The suggested default backend is `openpgp-card-pcsc`. +//! with cards. The suggested default backend is `card-backend-pcsc`. //! -//! With `openpgp-card-pcsc` you can either open all available cards: +//! With `card-backend-pcsc` you can either open all available cards: //! //! ```no_run -//! use openpgp_card_pcsc::PcscBackend; +//! use card_backend_pcsc::PcscBackend; //! use openpgp_card_sequoia::{state::Open, Card}; //! //! # fn main() -> Result<(), Box> { //! for backend in PcscBackend::cards(None)? { -//! let mut card: Card = backend.into(); +//! let mut card = Card::::new(backend?)?; //! let mut transaction = card.transaction()?; //! println!( //! "Found OpenPGP card with ident '{}'", @@ -40,12 +40,12 @@ //! Or you can open one particular card, by ident: //! //! ```no_run -//! use openpgp_card_pcsc::PcscBackend; +//! use card_backend_pcsc::PcscBackend; //! use openpgp_card_sequoia::{state::Open, Card}; //! //! # fn main() -> Result<(), Box> { -//! let backend = PcscBackend::open_by_ident("abcd:01234567", None)?; -//! let mut card: Card = backend.into(); +//! let cards = PcscBackend::card_backends(None)?; +//! let mut card = Card::::open_by_ident(cards, "abcd:01234567")?; //! let mut transaction = card.transaction()?; //! # Ok(()) //! # } @@ -60,13 +60,13 @@ //! implementation can then be obtained: //! //! ```no_run -//! use openpgp_card_pcsc::PcscBackend; +//! use card_backend_pcsc::PcscBackend; //! use openpgp_card_sequoia::{state::Open, Card}; //! # fn main() -> Result<(), Box> { //! // Open card via PCSC //! use sequoia_openpgp::policy::StandardPolicy; -//! let backend = PcscBackend::open_by_ident("abcd:01234567", None)?; -//! let mut card: Card = backend.into(); +//! let cards = PcscBackend::card_backends(None)?; +//! let mut card = Card::::open_by_ident(cards, "abcd:01234567")?; //! let mut transaction = card.transaction()?; //! //! // Get authorization for user access to the card with password @@ -95,13 +95,13 @@ //! user password before each signing operation!) //! //! ```no_run -//! use openpgp_card_pcsc::PcscBackend; +//! use card_backend_pcsc::PcscBackend; //! use openpgp_card_sequoia::{state::Open, Card}; //! //! # fn main() -> Result<(), Box> { //! // Open card via PCSC -//! let backend = PcscBackend::open_by_ident("abcd:01234567", None)?; -//! let mut card: Card = backend.into(); +//! let cards = PcscBackend::card_backends(None)?; +//! let mut card = Card::::open_by_ident(cards, "abcd:01234567")?; //! let mut transaction = card.transaction()?; //! //! // Get authorization for signing access to the card with password @@ -121,13 +121,13 @@ //! # Setting up and configuring a card //! //! ```no_run -//! use openpgp_card_pcsc::PcscBackend; +//! use card_backend_pcsc::PcscBackend; //! use openpgp_card_sequoia::{state::Open, Card}; //! //! # fn main() -> Result<(), Box> { //! // Open card via PCSC -//! let backend = PcscBackend::open_by_ident("abcd:01234567", None)?; -//! let mut card: Card = backend.into(); +//! let cards = PcscBackend::card_backends(None)?; +//! let mut card = Card::::open_by_ident(cards, "abcd:01234567")?; //! let mut transaction = card.transaction()?; //! //! // Get authorization for admin access to the card with password @@ -142,6 +142,7 @@ //! # } //! ``` +use card_backend::{CardBackend, SmartcardError}; use openpgp_card::algorithm::{Algo, AlgoInfo, AlgoSimple}; use openpgp_card::card_do::{ ApplicationIdentifier, CardholderRelatedData, ExtendedCapabilities, ExtendedLengthInfo, @@ -149,7 +150,7 @@ use openpgp_card::card_do::{ SecuritySupportTemplate, Sex, TouchPolicy, UIF, }; use openpgp_card::crypto_data::PublicKeyMaterial; -use openpgp_card::{CardBackend, Error, KeySet, KeyType, OpenPgp, OpenPgpTransaction}; +use openpgp_card::{Error, KeySet, KeyType, OpenPgp, OpenPgpTransaction}; use sequoia_openpgp::cert::prelude::ValidErasedKeyAmalgamation; use sequoia_openpgp::packet::key::SecretParts; use sequoia_openpgp::packet::{key, Key}; @@ -188,20 +189,38 @@ where state: S, } -impl From for Card -where - B: Into>, -{ - fn from(backend: B) -> Self { - let pgp = OpenPgp::new(backend.into()); - - Card:: { - state: Open { pgp }, - } - } -} - impl Card { + pub fn open_by_ident( + cards: impl Iterator, SmartcardError>>, + ident: &str, + ) -> Result { + for b in cards.filter_map(|c| c.ok()) { + let mut card = Self::new(b)?; + + let aid = { + let tx = card.transaction()?; + tx.state.ard.application_id()? + }; + + if aid.ident() == ident.to_ascii_uppercase() { + return Ok(card); + } + } + + Err(Error::NotFound(format!("Couldn't find card {}", ident))) + } + + pub fn new(backend: B) -> Result + where + B: Into>, + { + let pgp = OpenPgp::new(backend)?; + + Ok(Card:: { + state: Open { pgp }, + }) + } + pub fn transaction(&mut self) -> Result, Error> { let opt = self.state.pgp.transaction()?; diff --git a/openpgp-card-sequoia/src/types.rs b/openpgp-card-sequoia/src/types.rs index dfe0764..1bad2b2 100644 --- a/openpgp-card-sequoia/src/types.rs +++ b/openpgp-card-sequoia/src/types.rs @@ -6,4 +6,4 @@ pub use openpgp_card::algorithm::{Algo, AlgoSimple, Curve}; pub use openpgp_card::card_do::{Sex, TouchPolicy}; pub use openpgp_card::crypto_data::{EccType, PublicKeyMaterial}; -pub use openpgp_card::{CardBackend, Error, KeyType, StatusBytes}; +pub use openpgp_card::{Error, KeyType, StatusBytes}; From 7de74d702e00f3f42a5509d677488c6d3c2bc5b5 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 26 Aug 2023 16:58:30 +0200 Subject: [PATCH 046/115] openpgp-card-sequoia: adjust example --- Cargo.toml | 2 +- openpgp-card-sequoia/examples/test.rs | 241 +++++++++++++------------- 2 files changed, 123 insertions(+), 120 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 99dcdf4..b393d62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ [workspace] members = [ "openpgp-card", -# "openpgp-card-sequoia", + "openpgp-card-sequoia", "card-backend", "pcsc", "scdc", diff --git a/openpgp-card-sequoia/examples/test.rs b/openpgp-card-sequoia/examples/test.rs index e87b8ba..bbc521d 100644 --- a/openpgp-card-sequoia/examples/test.rs +++ b/openpgp-card-sequoia/examples/test.rs @@ -1,13 +1,13 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use std::env; use std::error::Error; use anyhow::Result; +use card_backend_pcsc::PcscBackend; use openpgp_card::card_do::Sex; use openpgp_card::KeyType; -use openpgp_card_pcsc::PcscBackend; use openpgp_card_sequoia::sq_util; use openpgp_card_sequoia::{state::Open, Card}; use sequoia_openpgp::parse::Parse; @@ -33,154 +33,157 @@ fn main() -> Result<(), Box> { let test_card_ident = env::var("TEST_CARD_IDENT"); if let Ok(test_card_ident) = test_card_ident { - let backend = PcscBackend::open_by_ident(&test_card_ident, None)?; + let cards = PcscBackend::card_backends(None)?; + let mut card = Card::::open_by_ident(cards, &test_card_ident)?; - let mut card: Card = backend.into(); - let mut transaction = card.transaction()?; + { + let mut transaction = card.transaction()?; - // card metadata + // card metadata - let app_id = transaction.application_identifier()?; - println!("{app_id:x?}\n"); + let app_id = transaction.application_identifier()?; + println!("{app_id:x?}\n"); - let eli = transaction.extended_length_information()?; - println!("extended_length_info: {eli:?}\n"); + let eli = transaction.extended_length_information()?; + println!("extended_length_info: {eli:?}\n"); - let hist = transaction.historical_bytes()?; - println!("{hist:#x?}\n"); + let hist = transaction.historical_bytes()?; + println!("{hist:#x?}\n"); - let ext = transaction.extended_capabilities()?; - println!("{ext:#x?}\n"); + let ext = transaction.extended_capabilities()?; + println!("{ext:#x?}\n"); - let pws = transaction.pw_status_bytes()?; - println!("{pws:#x?}\n"); + let pws = transaction.pw_status_bytes()?; + println!("{pws:#x?}\n"); - // cardholder - let ch = transaction.cardholder_related_data()?; - println!("{ch:#x?}\n"); + // cardholder + let ch = transaction.cardholder_related_data()?; + println!("{ch:#x?}\n"); - // crypto-ish metadata - let fp = transaction.fingerprints()?; - println!("Fingerprint {fp:#x?}\n"); + // crypto-ish metadata + let fp = transaction.fingerprints()?; + println!("Fingerprint {fp:#x?}\n"); - match transaction.algorithm_information() { - Ok(Some(ai)) => println!("Algorithm information:\n{ai}"), - Ok(None) => println!("No Algorithm information found"), - Err(e) => println!("Error getting Algorithm information: {e:?}"), + match transaction.algorithm_information() { + Ok(Some(ai)) => println!("Algorithm information:\n{ai}"), + Ok(None) => println!("No Algorithm information found"), + Err(e) => println!("Error getting Algorithm information: {e:?}"), + } + + println!("Current algorithm attributes on card:"); + let algo = transaction.algorithm_attributes(KeyType::Signing)?; + println!("Sig: {algo}"); + let algo = transaction.algorithm_attributes(KeyType::Decryption)?; + println!("Dec: {algo}"); + let algo = transaction.algorithm_attributes(KeyType::Authentication)?; + println!("Aut: {algo}"); + + println!(); + + // --------------------------------------------- + // CAUTION: Write commands ahead! + // Try not to overwrite your production cards. + // --------------------------------------------- + + assert_eq!(app_id.ident(), test_card_ident.to_ascii_uppercase()); + + let check = transaction.check_admin_verified(); + println!("has admin (pw3) been verified yet?\n{check:x?}\n"); + + println!("factory reset\n"); + transaction.factory_reset()?; + + transaction.verify_admin(b"12345678")?; + println!("verify for admin ok"); + + let check = transaction.check_user_verified(); + println!("has user (pw1/82) been verified yet? {check:x?}"); + + // Use Admin access to card + let mut admin = transaction.admin_card().expect("just verified"); + + println!(); + + admin.set_name("Bar<::open_by_ident(cards, &test_card_ident)?; - let mut card: Card = backend.into(); - let mut transaction = card.transaction()?; + { + let mut transaction = card.transaction()?; - // Check that we're still using the expected card - let app_id = transaction.application_identifier()?; - assert_eq!(app_id.ident(), test_card_ident.to_ascii_uppercase()); + // Check that we're still using the expected card + let app_id = transaction.application_identifier()?; + assert_eq!(app_id.ident(), test_card_ident.to_ascii_uppercase()); - let check = transaction.check_user_verified(); - println!("has user (pw1/82) been verified yet?\n{check:x?}\n"); + let check = transaction.check_user_verified(); + println!("has user (pw1/82) been verified yet?\n{check:x?}\n"); - transaction.verify_user(b"123456")?; - println!("verify for user (pw1/82) ok"); + transaction.verify_user(b"123456")?; + println!("verify for user (pw1/82) ok"); - let check = transaction.check_user_verified(); - println!("has user (pw1/82) been verified yet?\n{check:x?}\n"); + let check = transaction.check_user_verified(); + println!("has user (pw1/82) been verified yet?\n{check:x?}\n"); - // Use User access to card - let mut user = transaction - .user_card() - .expect("We just validated, this should not fail"); + // Use User access to card + let mut user = transaction + .user_card() + .expect("We just validated, this should not fail"); - let _cert = Cert::from_file(TEST_KEY_PATH)?; - let msg = std::fs::read_to_string(TEST_ENC_MSG).expect("Unable to read file"); + let _cert = Cert::from_file(TEST_KEY_PATH)?; + let msg = std::fs::read_to_string(TEST_ENC_MSG).expect("Unable to read file"); - println!("Encrypted message:\n{msg}"); + println!("Encrypted message:\n{msg}"); - let sp = StandardPolicy::new(); - let d = user.decryptor(&|| println!("Touch confirmation needed for decryption"))?; - let res = sq_util::decryption_helper(d, msg.into_bytes(), &sp)?; + let sp = StandardPolicy::new(); + let d = user.decryptor(&|| println!("Touch confirmation needed for decryption"))?; + let res = sq_util::decryption_helper(d, msg.into_bytes(), &sp)?; - let plain = String::from_utf8_lossy(&res); - println!("Decrypted plaintext: {plain}"); + let plain = String::from_utf8_lossy(&res); + println!("Decrypted plaintext: {plain}"); - assert_eq!(plain, "Hello world!\n"); + assert_eq!(plain, "Hello world!\n"); + } // ----------------------------- // Open fresh Card for signing // ----------------------------- - let backend = PcscBackend::open_by_ident(&test_card_ident, None)?; + let cards = PcscBackend::card_backends(None)?; + let mut card = Card::::open_by_ident(cards, &test_card_ident)?; - let mut card: Card = backend.into(); let mut transaction = card.transaction()?; // Sign @@ -211,7 +214,7 @@ fn main() -> Result<(), Box> { println!("The following OpenPGP cards are connected to your system:"); for backend in PcscBackend::cards(None)? { - let mut card: Card = backend.into(); + let mut card: Card = Card::::new(backend?)?; let open = card.transaction()?; println!(" {}", open.application_identifier()?.ident()); From 8d5b1c05636802f32dd445708390924acd8d7a73 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 26 Aug 2023 16:47:14 +0200 Subject: [PATCH 047/115] openpgp-card-example: adjust to card-backend refactor --- Cargo.toml | 2 +- openpgp-card-examples/Cargo.toml | 2 +- openpgp-card-examples/src/bin/decrypt.rs | 6 +++--- openpgp-card-examples/src/bin/detach-sign.rs | 6 +++--- openpgp-card-sequoia/Cargo.toml | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b393d62..9d71b56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,6 @@ members = [ "card-backend", "pcsc", "scdc", -# "openpgp-card-examples", + "openpgp-card-examples", # "card-functionality", ] diff --git a/openpgp-card-examples/Cargo.toml b/openpgp-card-examples/Cargo.toml index 5588278..aa3fc1e 100644 --- a/openpgp-card-examples/Cargo.toml +++ b/openpgp-card-examples/Cargo.toml @@ -14,7 +14,7 @@ documentation = "https://docs.rs/crate/openpgp-card-examples" [dependencies] sequoia-openpgp = "1.3" nettle = "7" -openpgp-card-pcsc = { path = "../pcsc" } +card-backend-pcsc = { path = "../pcsc" } openpgp-card-sequoia = { path = "../openpgp-card-sequoia" } chrono = "0.4" anyhow = "1" diff --git a/openpgp-card-examples/src/bin/decrypt.rs b/openpgp-card-examples/src/bin/decrypt.rs index 5119dfc..a0d5910 100644 --- a/openpgp-card-examples/src/bin/decrypt.rs +++ b/openpgp-card-examples/src/bin/decrypt.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2021 Wiktor Kwapisiewicz // SPDX-License-Identifier: MIT OR Apache-2.0 -use openpgp_card_pcsc::PcscBackend; +use card_backend_pcsc::PcscBackend; use openpgp_card_sequoia::{state::Open, Card}; use sequoia_openpgp::parse::{stream::DecryptorBuilder, Parse}; use sequoia_openpgp::policy::StandardPolicy; @@ -17,9 +17,9 @@ fn main() -> Result<(), Box> { let card_ident = &args[0]; let pin_file = &args[1]; - let backend = PcscBackend::open_by_ident(card_ident, None)?; + let cards = PcscBackend::card_backends(None)?; - let mut card: Card = backend.into(); + let mut card: Card = Card::::open_by_ident(cards, card_ident)?; let mut transaction = card.transaction()?; let pin = std::fs::read(pin_file)?; diff --git a/openpgp-card-examples/src/bin/detach-sign.rs b/openpgp-card-examples/src/bin/detach-sign.rs index 4cc87f8..f0acc11 100644 --- a/openpgp-card-examples/src/bin/detach-sign.rs +++ b/openpgp-card-examples/src/bin/detach-sign.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2021 Wiktor Kwapisiewicz // SPDX-License-Identifier: MIT OR Apache-2.0 -use openpgp_card_pcsc::PcscBackend; +use card_backend_pcsc::PcscBackend; use openpgp_card_sequoia::{state::Open, Card}; use sequoia_openpgp::serialize::stream::{Armorer, Message, Signer}; @@ -16,9 +16,9 @@ fn main() -> Result<(), Box> { let card_ident = &args[0]; let pin_file = &args[1]; - let backend = PcscBackend::open_by_ident(card_ident, None)?; + let cards = PcscBackend::card_backends(None)?; - let mut card: Card = backend.into(); + let mut card: Card = Card::::open_by_ident(cards, card_ident)?; let mut transaction = card.transaction()?; let pin = std::fs::read(pin_file)?; diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index fdccf11..73027c7 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -5,7 +5,7 @@ name = "openpgp-card-sequoia" description = "Wrapper of openpgp-card for use with Sequoia PGP" license = "MIT OR Apache-2.0" -version = "0.2.0-pre" +version = "0.2.0" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" From 212e7f335ff26ff83d7fedff912b2efad24173c8 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 15 Aug 2023 18:29:45 +0200 Subject: [PATCH 048/115] openpgp-card-sequoia: Transaction::to_*_card() methods Allow more ergonomic switching to User/Sign/Admin states by directly providing a PIN, while also allowing a `None` parameter if verification has already happened. --- openpgp-card-examples/src/bin/decrypt.rs | 4 +- openpgp-card-examples/src/bin/detach-sign.rs | 4 +- openpgp-card-sequoia/examples/test.rs | 6 +- openpgp-card-sequoia/src/lib.rs | 90 ++++++++++++++++---- openpgp-card-sequoia/src/util.rs | 18 ++-- 5 files changed, 88 insertions(+), 34 deletions(-) diff --git a/openpgp-card-examples/src/bin/decrypt.rs b/openpgp-card-examples/src/bin/decrypt.rs index a0d5910..7e5c174 100644 --- a/openpgp-card-examples/src/bin/decrypt.rs +++ b/openpgp-card-examples/src/bin/decrypt.rs @@ -24,9 +24,7 @@ fn main() -> Result<(), Box> { let pin = std::fs::read(pin_file)?; - transaction.verify_user(&pin)?; - - let mut user = transaction.user_card().unwrap(); + let mut user = transaction.to_user_card(&pin)?; let p = StandardPolicy::new(); diff --git a/openpgp-card-examples/src/bin/detach-sign.rs b/openpgp-card-examples/src/bin/detach-sign.rs index f0acc11..01fc84d 100644 --- a/openpgp-card-examples/src/bin/detach-sign.rs +++ b/openpgp-card-examples/src/bin/detach-sign.rs @@ -23,9 +23,7 @@ fn main() -> Result<(), Box> { let pin = std::fs::read(pin_file)?; - transaction.verify_user_for_signing(&pin)?; - - let mut sign = transaction.signing_card().unwrap(); + let mut sign = transaction.to_signing_card(&pin)?; let s = sign.signer(&|| println!("Touch confirmation needed for signing"))?; let stdout = std::io::stdout(); diff --git a/openpgp-card-sequoia/examples/test.rs b/openpgp-card-sequoia/examples/test.rs index bbc521d..4c3059c 100644 --- a/openpgp-card-sequoia/examples/test.rs +++ b/openpgp-card-sequoia/examples/test.rs @@ -100,7 +100,7 @@ fn main() -> Result<(), Box> { println!("has user (pw1/82) been verified yet? {check:x?}"); // Use Admin access to card - let mut admin = transaction.admin_card().expect("just verified"); + let mut admin = transaction.to_admin_card(None).expect("just verified"); println!(); @@ -160,7 +160,7 @@ fn main() -> Result<(), Box> { // Use User access to card let mut user = transaction - .user_card() + .to_user_card(None) .expect("We just validated, this should not fail"); let _cert = Cert::from_file(TEST_KEY_PATH)?; @@ -191,7 +191,7 @@ fn main() -> Result<(), Box> { println!("verify for sign (pw1/81) ok\n"); // Use Sign access to card - let mut sign = transaction.signing_card().expect("just verified"); + let mut sign = transaction.to_signing_card(None).expect("just verified"); let _cert = Cert::from_file(TEST_KEY_PATH)?; diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 2a3accc..98ff55f 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -69,9 +69,8 @@ //! let mut card = Card::::open_by_ident(cards, "abcd:01234567")?; //! let mut transaction = card.transaction()?; //! -//! // Get authorization for user access to the card with password -//! transaction.verify_user(b"123456")?; -//! let mut user = transaction.user_card().expect("This should not fail"); +//! // Get user access to the card (and authorize with the user pin) +//! let mut user = transaction.to_user_card("123456")?; //! //! // Get decryptor //! let decryptor = user.decryptor(&|| println!("Touch confirmation needed for decryption")); @@ -104,9 +103,8 @@ //! let mut card = Card::::open_by_ident(cards, "abcd:01234567")?; //! let mut transaction = card.transaction()?; //! -//! // Get authorization for signing access to the card with password -//! transaction.verify_user_for_signing(b"123456")?; -//! let mut user = transaction.signing_card().expect("This should not fail"); +//! // Get signing access to the card (and authorize with the user pin) +//! let mut user = transaction.to_signing_card("123456")?; //! //! // Get signer //! let signer = user.signer(&|| println!("Touch confirmation needed for signing")); @@ -130,9 +128,8 @@ //! let mut card = Card::::open_by_ident(cards, "abcd:01234567")?; //! let mut transaction = card.transaction()?; //! -//! // Get authorization for admin access to the card with password -//! transaction.verify_admin(b"12345678")?; -//! let mut admin = transaction.admin_card().expect("This should not fail"); +//! // Get admin access to the card (and authorize with the admin pin) +//! let mut admin = transaction.to_admin_card("12345678")?; //! //! // Set the Name and URL fields on the card //! admin.set_name("Alice Adams")?; @@ -174,6 +171,36 @@ pub mod util; /// Shorthand for Sequoia public key data (a single public (sub)key) pub type PublicKey = Key; +/// Optional PIN, used as a parameter to `Card::into_*_card`. +/// +/// Effectively acts like a `Option<&[u8]>`, but with a number of `From` +/// implementations for convenience. +pub struct OptionalPin<'p>(Option<&'p [u8]>); + +impl<'p> From> for OptionalPin<'p> { + fn from(value: Option<&'p [u8]>) -> Self { + OptionalPin(value) + } +} + +impl<'p> From<&'p str> for OptionalPin<'p> { + fn from(value: &'p str) -> Self { + OptionalPin(Some(value.as_bytes())) + } +} + +impl<'p> From<&'p Vec> for OptionalPin<'p> { + fn from(value: &'p Vec) -> Self { + OptionalPin(Some(value)) + } +} + +impl<'p, const S: usize> From<&'p [u8; S]> for OptionalPin<'p> { + fn from(value: &'p [u8; S]) -> Self { + OptionalPin(Some(value)) + } +} + /// Representation of an OpenPGP card. /// /// A card transitions between `State`s by starting a transaction (that groups together a number @@ -362,22 +389,55 @@ impl<'a> Card> { } /// Get a view of the card authenticated for "User" commands. - pub fn user_card<'b>(&'b mut self) -> Option>> { - Some(Card:: { + /// + /// If `pin` is not None, `verify_user` is called with that pin. + pub fn to_user_card<'b, 'p, P>(&'b mut self, pin: P) -> Result>, Error> + where + P: Into>, + { + let pin: OptionalPin = pin.into(); + + if let Some(pin) = pin.0 { + self.verify_user(pin)?; + } + + Ok(Card:: { state: User { tx: self }, }) } /// Get a view of the card authenticated for Signing. - pub fn signing_card<'b>(&'b mut self) -> Option>> { - Some(Card:: { + /// + /// If `pin` is not None, `verify_user_for_signing` is called with that pin. + pub fn to_signing_card<'b, 'p, P>(&'b mut self, pin: P) -> Result>, Error> + where + P: Into>, + { + let pin: OptionalPin = pin.into(); + + if let Some(pin) = pin.0 { + self.verify_user_for_signing(pin)?; + } + + Ok(Card:: { state: Sign { tx: self }, }) } /// Get a view of the card authenticated for "Admin" commands. - pub fn admin_card<'b>(&'b mut self) -> Option>> { - Some(Card:: { + /// + /// If `pin` is not None, `verify_admin` is called with that pin. + pub fn to_admin_card<'b, 'p, P>(&'b mut self, pin: P) -> Result>, Error> + where + P: Into>, + { + let pin: OptionalPin = pin.into(); + + if let Some(pin) = pin.0 { + self.verify_admin(pin)?; + } + + Ok(Card:: { state: Admin { tx: self }, }) } diff --git a/openpgp-card-sequoia/src/util.rs b/openpgp-card-sequoia/src/util.rs index 5583187..f51415d 100644 --- a/openpgp-card-sequoia/src/util.rs +++ b/openpgp-card-sequoia/src/util.rs @@ -6,7 +6,7 @@ use std::convert::TryFrom; use std::convert::TryInto; -use anyhow::{anyhow, Result}; +use anyhow::Result; use openpgp_card::algorithm::{Algo, Curve}; use openpgp_card::card_do::{Fingerprint, KeyGenerationTime}; use openpgp_card::crypto_data::{CardUploadableKey, PublicKeyMaterial}; @@ -58,16 +58,14 @@ pub fn make_cert( } else { open.verify_user_for_signing_pinpad(pinpad_prompt)?; } - if let Some(mut sign) = open.signing_card() { - // Card-backed signer for bindings - let mut card_signer = sign.signer_from_public(key_sig.clone(), touch_prompt); + let mut sign = open.to_signing_card(None)?; - // Make signature, return it - let s = op(&mut card_signer)?; - Ok(s) - } else { - Err(anyhow!("Failed to open card for signing")) - } + // Card-backed signer for bindings + let mut card_signer = sign.signer_from_public(key_sig.clone(), touch_prompt); + + // Make signature, return it + let s = op(&mut card_signer)?; + Ok::(s) }; // 1) use the signing key as primary key From 2601e58fe3ec23bb2f476834242140f0c6de77d2 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 17 Aug 2023 15:20:07 +0200 Subject: [PATCH 049/115] openpgp-card-sequoia: rename Card::as_open to ::as_transaction --- openpgp-card-sequoia/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 98ff55f..2026c0a 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -819,7 +819,7 @@ impl<'app, 'open> Card> { } impl<'app, 'open> Card> { - pub fn as_open(&'_ mut self) -> &mut Card> { + pub fn as_transaction(&'_ mut self) -> &mut Card> { self.state.tx } From dcf98c512cdb09065c666627be9e8ee44a4c1c79 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 17 Aug 2023 15:22:30 +0200 Subject: [PATCH 050/115] openpgp-card-sequoia: add Card::set_pw_status_bytes --- openpgp-card-sequoia/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 2026c0a..0ee8fa8 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -886,6 +886,14 @@ impl Card> { } } + pub fn set_pw_status_bytes( + &mut self, + pw_status: &PWStatusBytes, + long: bool, + ) -> Result<(), Error> { + self.card().set_pw_status_bytes(pw_status, long) + } + pub fn set_uif(&mut self, key: KeyType, policy: TouchPolicy) -> Result<(), Error> { let uif = match key { KeyType::Signing => self.state.tx.state.ard.uif_pso_cds()?, From ccf605f0861ea4f45353b5ac3a71e741ae1dc38d Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 17 Aug 2023 15:26:15 +0200 Subject: [PATCH 051/115] openpgp-card-sequoia: make Card::new private --- openpgp-card-sequoia/src/lib.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 0ee8fa8..be6fdf3 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -264,11 +264,8 @@ impl Card { } impl<'a> Card> { - /// Do not use! - /// - /// FIXME: this interface is currently used in `card-functionality`, for testing. - /// It will be removed. - pub fn new(mut opt: OpenPgpTransaction<'a>) -> Result { + // Internal constructor + fn new(mut opt: OpenPgpTransaction<'a>) -> Result { let ard = opt.application_related_data()?; Ok(Self { From 746f2f647d55d0424977715cd07d676baa5664e5 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 17 Aug 2023 15:03:18 +0200 Subject: [PATCH 052/115] card-functionality: adjust to backend and openpgp-card-sequoia API changes --- Cargo.toml | 2 +- card-functionality/Cargo.toml | 4 +- card-functionality/src/cards.rs | 49 +++-- card-functionality/src/import.rs | 29 +-- card-functionality/src/keygen.rs | 23 ++- card-functionality/src/list-cards.rs | 5 +- card-functionality/src/other.rs | 8 +- card-functionality/src/tests.rs | 286 ++++++++++++++------------- card-functionality/src/util.rs | 11 +- 9 files changed, 224 insertions(+), 193 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9d71b56..02b48d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,5 +9,5 @@ members = [ "pcsc", "scdc", "openpgp-card-examples", -# "card-functionality", + "card-functionality", ] diff --git a/card-functionality/Cargo.toml b/card-functionality/Cargo.toml index c26a162..7279371 100644 --- a/card-functionality/Cargo.toml +++ b/card-functionality/Cargo.toml @@ -31,8 +31,8 @@ path = "src/list-cards.rs" [dependencies] openpgp-card = { path = "../openpgp-card" } openpgp-card-sequoia = { path = "../openpgp-card-sequoia" } -openpgp-card-scdc = { path = "../scdc" } -openpgp-card-pcsc = { path = "../pcsc" } +card-backend-scdc = { path = "../scdc" } +card-backend-pcsc = { path = "../pcsc" } pcsc = "2.7" sequoia-openpgp = "1.3" anyhow = "1" diff --git a/card-functionality/src/cards.rs b/card-functionality/src/cards.rs index ea551df..d4d22b8 100644 --- a/card-functionality/src/cards.rs +++ b/card-functionality/src/cards.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Wrapping of cards for tests. Open a list of cards, based on a @@ -7,13 +7,13 @@ use std::collections::BTreeMap; use anyhow::Result; -use openpgp_card::{CardBackend, Error}; -use openpgp_card_pcsc::PcscBackend; -use openpgp_card_scdc::ScdBackend; -use pcsc::ShareMode; +use card_backend_pcsc::PcscBackend; +use card_backend_scdc::ScdBackend; +use openpgp_card::Error; +use openpgp_card_sequoia::state::Open; use serde_derive::Deserialize; -const SHARE_MODE: Option = Some(ShareMode::Shared); +// const SHARE_MODE: Option = Some(ShareMode::Shared); #[derive(Debug, Deserialize)] pub struct TestConfig { @@ -41,7 +41,7 @@ pub struct TestCardData { } impl TestCardData { - pub(crate) fn get_card(&self) -> Result> { + pub fn get_card(&self) -> Result> { self.tc.open() } @@ -92,7 +92,7 @@ pub enum TestCard { } impl TestCard { - pub fn open(&self) -> Result> { + pub fn open(&self) -> Result> { match self { Self::Pcsc(ident) => { // Attempt to shutdown SCD, if it is running. @@ -103,24 +103,39 @@ impl TestCard { // Make three attempts to open the card before failing // (this can be useful in ShareMode::Exclusive) let mut i = 1; - let card: Result, Error> = loop { - let res = PcscBackend::open_by_ident(ident, SHARE_MODE); + let card: Result, Error> = loop { + i += 1; - if i == 3 { - if let Ok(res) = res { - break Ok(Box::new(res)); - } + let cards = PcscBackend::card_backends(None)?; + let res = openpgp_card_sequoia::Card::::open_by_ident(cards, ident); + + println!("Got result for card: {}", ident); + + if let Err(e) = &res { + println!("Result is an error: {:x?}", e); + } else { + println!("Result is a happy card"); + } + + if let Ok(res) = res { + break Ok(res); + } + + if i > 3 { + break Err(Error::NotFound(format!("Couldn't open card {}", ident))); } // sleep for 100ms + println!("Will sleep for 100ms"); std::thread::sleep(std::time::Duration::from_millis(100)); - - i += 1; }; Ok(card?) } - Self::Scdc(serial) => Ok(Box::new(ScdBackend::open_by_serial(None, serial)?)), + Self::Scdc(serial) => { + let backend = ScdBackend::open_by_serial(None, serial)?; + Ok(openpgp_card_sequoia::Card::::new(backend)?) + } } } } diff --git a/card-functionality/src/import.rs b/card-functionality/src/import.rs index b477117..925a46f 100644 --- a/card-functionality/src/import.rs +++ b/card-functionality/src/import.rs @@ -7,6 +7,8 @@ use anyhow::Result; use card_functionality::cards::TestConfig; use card_functionality::tests::*; use card_functionality::util; +use openpgp_card_sequoia::state::Open; +use openpgp_card_sequoia::Card; use sequoia_openpgp::Cert; fn main() -> Result<()> { @@ -21,16 +23,23 @@ fn main() -> Result<()> { let cards = config.into_cardapps(); - for mut card in cards { + for card in cards { println!("** Run tests on card '{}' **", card.get_name()); + let mut c: Card = card.get_card()?; + println!(" -> Card opened"); + println!("Reset"); - let _ = run_test(&mut card, test_reset, &[])?; + let _ = run_test(&mut c, test_reset, &[])?; print!("Set user data"); - let userdata_out = run_test(&mut card, test_set_user_data, &[])?; + let userdata_out = run_test(&mut c, test_set_user_data, &[])?; println!(" {userdata_out:x?}"); + println!("Set login data"); + let login_data_out = run_test(&mut c, test_set_login_data, &[])?; + println!(" {login_data_out:x?}"); + let key_files = { let config = card.get_config(); if let Some(import) = &config.import { @@ -40,14 +49,10 @@ fn main() -> Result<()> { } }; - println!("Set login data"); - let login_data_out = run_test(&mut card, test_set_login_data, &[])?; - println!(" {login_data_out:x?}"); - for key_file in &key_files { // upload keys print!("Upload key '{key_file}'"); - let upload_res = run_test(&mut card, test_upload_keys, &[key_file]); + let upload_res = run_test(&mut c, test_upload_keys, &[key_file]); if let Err(TestError::KeyUploadError(_file, err)) = &upload_res { // The card doesn't support this key type, so skip to the @@ -66,16 +71,16 @@ fn main() -> Result<()> { // decrypt print!(" Decrypt"); - let c = Cert::from_str(&key)?; - let ciphertext = util::encrypt_to("Hello world!\n", &c)?; + let cert = Cert::from_str(&key)?; + let ciphertext = util::encrypt_to("Hello world!\n", &cert)?; - let dec_out = run_test(&mut card, test_decrypt, &[&key, &ciphertext])?; + let dec_out = run_test(&mut c, test_decrypt, &[&key, &ciphertext])?; println!(" {dec_out:x?}"); // sign print!(" Sign"); - let sign_out = run_test(&mut card, test_sign, &[&key])?; + let sign_out = run_test(&mut c, test_sign, &[&key])?; println!(" {sign_out:x?}"); } diff --git a/card-functionality/src/keygen.rs b/card-functionality/src/keygen.rs index 2cce5b5..ec4005a 100644 --- a/card-functionality/src/keygen.rs +++ b/card-functionality/src/keygen.rs @@ -7,6 +7,8 @@ use anyhow::Result; use card_functionality::cards::TestConfig; use card_functionality::tests::*; use card_functionality::util; +use openpgp_card_sequoia::state::Open; +use openpgp_card_sequoia::Card; use sequoia_openpgp::Cert; fn main() -> Result<()> { @@ -21,9 +23,12 @@ fn main() -> Result<()> { let cards = config.into_cardapps(); - for mut card in cards { + for card in cards { println!("** Run tests on card {} **", card.get_name()); + let mut c: Card = card.get_card()?; + println!(" -> Card opened"); + // println!("Get pubkey"); // let _ = run_test(&mut card, test_get_pub, &[])?; // @@ -34,14 +39,14 @@ fn main() -> Result<()> { // // continue; // only print caps println!("Reset"); - let _ = run_test(&mut card, test_reset, &[])?; + let _ = run_test(&mut c, test_reset, &[])?; // println!("Algo info"); // let _ = run_test(&mut card, test_print_algo_info, &[])?; // Set user data because keygen expects a name (for the user id) println!("Set user data"); - let _ = run_test(&mut card, test_set_user_data, &[])?; + let _ = run_test(&mut c, test_set_user_data, &[])?; let algos = { let config = card.get_config(); @@ -55,20 +60,20 @@ fn main() -> Result<()> { for algo in algos { println!("Generate key [{algo}]"); - let res = run_test(&mut card, test_keygen, &[&algo])?; + let res = run_test(&mut c, test_keygen, &[&algo])?; - if let TestResult::Text(cert) = &res[0] { + if let TestResult::Text(cert_str) = &res[0] { // sign print!(" Sign"); - let sign_out = run_test(&mut card, test_sign, &[cert])?; + let sign_out = run_test(&mut c, test_sign, &[cert_str])?; println!(" {sign_out:x?}"); // decrypt - let c = Cert::from_str(cert)?; - let ciphertext = util::encrypt_to("Hello world!\n", &c)?; + let cert = Cert::from_str(cert_str)?; + let ciphertext = util::encrypt_to("Hello world!\n", &cert)?; print!(" Decrypt"); - let dec_out = run_test(&mut card, test_decrypt, &[cert, &ciphertext])?; + let dec_out = run_test(&mut c, test_decrypt, &[cert_str, &ciphertext])?; println!(" {dec_out:x?}"); } else { panic!("Didn't get back a Cert from test_keygen"); diff --git a/card-functionality/src/list-cards.rs b/card-functionality/src/list-cards.rs index c96a72a..94e1e14 100644 --- a/card-functionality/src/list-cards.rs +++ b/card-functionality/src/list-cards.rs @@ -2,14 +2,15 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use anyhow::Result; -use openpgp_card_pcsc::PcscBackend; +use card_backend_pcsc::PcscBackend; use openpgp_card_sequoia::{state::Open, Card}; fn main() -> Result<()> { println!("The following OpenPGP cards are connected to your system:"); for backend in PcscBackend::cards(None)? { - let mut card: Card = backend.into(); + let mut card: Card = Card::::new(backend?)?; + println!(" {}", card.transaction()?.application_identifier()?.ident()); } diff --git a/card-functionality/src/other.rs b/card-functionality/src/other.rs index b061b93..e6278a7 100644 --- a/card-functionality/src/other.rs +++ b/card-functionality/src/other.rs @@ -4,6 +4,8 @@ use anyhow::Result; use card_functionality::cards::TestConfig; use card_functionality::tests::*; +use openpgp_card_sequoia::state::Open; +use openpgp_card_sequoia::Card; fn main() -> Result<()> { env_logger::init(); @@ -12,9 +14,11 @@ fn main() -> Result<()> { let cards = config.into_cardapps(); - for mut card in cards { + for card in cards { println!("** Run tests on card '{}' **", card.get_name()); + let mut c: Card = card.get_card()?; + // println!("Caps"); // let _ = run_test(&mut card, test_print_caps, &[])?; // continue; // only print caps @@ -23,7 +27,7 @@ fn main() -> Result<()> { // let _ = run_test(&mut card, test_print_algo_info, &[])?; println!("Reset"); - let _ = run_test(&mut card, test_reset, &[])?; + let _ = run_test(&mut c, test_reset, &[])?; // --- diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index 8bac04a..8d89e5d 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -8,12 +8,13 @@ use std::string::FromUtf8Error; use anyhow::Result; use openpgp_card::algorithm::AlgoSimple; use openpgp_card::card_do::{KeyGenerationTime, Sex}; -use openpgp_card::{Error, KeyType, OpenPgp, OpenPgpTransaction, StatusBytes}; +use openpgp_card::{Error, KeyType, OpenPgp, StatusBytes}; use openpgp_card_sequoia::sq_util; +use openpgp_card_sequoia::state::{Admin, Open}; use openpgp_card_sequoia::util::{ make_cert, public_key_material_and_fp_to_key, public_key_material_to_key, }; -use openpgp_card_sequoia::{state::Transaction, Card}; +use openpgp_card_sequoia::Card; use sequoia_openpgp::parse::Parse; use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::serialize::SerializeInto; @@ -21,7 +22,6 @@ use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm}; use sequoia_openpgp::Cert; use thiserror; -use crate::cards::TestCardData; use crate::util; #[derive(Debug)] @@ -52,9 +52,7 @@ pub enum TestError { } /// Run after each "upload keys", if key *was* uploaded (?) -pub fn test_decrypt(pgp: &mut OpenPgp, param: &[&str]) -> Result { - let mut pgpt = pgp.transaction()?; - +pub fn test_decrypt(card: &mut Card, param: &[&str]) -> Result { assert_eq!( param.len(), 2, @@ -63,13 +61,11 @@ pub fn test_decrypt(pgp: &mut OpenPgp, param: &[&str]) -> Result::new(pgpt)?; + let mut transaction = card.transaction()?; - let mut user = transaction.user_card().unwrap(); + let mut user = transaction.to_user_card("123456")?; let d = user.decryptor(&|| {})?; let res = sq_util::decrypt(d, msg.into_bytes(), &p)?; @@ -81,18 +77,14 @@ pub fn test_decrypt(pgp: &mut OpenPgp, param: &[&str]) -> Result Result { - let mut pgpt = pgp.transaction()?; - +pub fn test_sign(card: &mut Card, param: &[&str]) -> Result { assert_eq!(param.len(), 1, "test_sign needs a filename for 'cert'"); - pgpt.verify_pw1_sign(b"123456")?; - let cert = Cert::from_str(param[0])?; - let mut transaction = Card::::new(pgpt)?; + let mut transaction = card.transaction()?; - let mut sign = transaction.signing_card().unwrap(); + let mut sign = transaction.to_signing_card("123456").unwrap(); let s = sign.signer(&|| {})?; let msg = "Hello world, I am signed."; @@ -105,13 +97,13 @@ pub fn test_sign(pgp: &mut OpenPgp, param: &[&str]) -> Result, meta: &[(String, KeyGenerationTime)], ) -> Result<()> { - let ard = pgpt.application_related_data()?; + admin.as_transaction().reload_ard()?; // check fingerprints - let card_fp = ard.fingerprints()?; + let card_fp = admin.as_transaction().fingerprints()?; let sig = card_fp.signature().expect("signature fingerprint"); assert_eq!(format!("{sig:X}"), meta[0].0); @@ -125,7 +117,7 @@ fn check_key_upload_metadata( assert_eq!(format!("{auth:X}"), meta[2].0); // get_key_generation_times - let card_kg = ard.key_generation_times()?; + let card_kg = admin.as_transaction().key_generation_times()?; let sig = card_kg.signature().expect("signature creation time"); assert_eq!(sig, &meta[0].1); @@ -186,8 +178,8 @@ pub fn test_print_algo_info(pgp: &mut OpenPgp, _param: &[&str]) -> Result Result { - let mut pgpt = pgp.transaction()?; +pub fn test_upload_keys(card: &mut Card, param: &[&str]) -> Result { + let mut transaction = card.transaction()?; assert_eq!( param.len(), @@ -195,16 +187,15 @@ pub fn test_upload_keys(pgp: &mut OpenPgp, param: &[&str]) -> Result Result Result { - let pgpt = pgp.transaction()?; - - let mut transaction = Card::::new(pgpt)?; - transaction.verify_admin(b"12345678")?; - let mut admin = transaction.admin_card().expect("Couldn't get Admin card"); +pub fn test_keygen(card: &mut Card, param: &[&str]) -> Result { + let mut transaction = card.transaction()?; + let mut admin = transaction + .to_admin_card("12345678") + .expect("Couldn't get Admin card"); // Generate all three subkeys on card let algo = param[0]; @@ -262,16 +252,15 @@ pub fn test_keygen(pgp: &mut OpenPgp, param: &[&str]) -> Result Result { - let mut pgpt = pgp.transaction()?; +pub fn test_get_pub(mut card: Card, _param: &[&str]) -> Result { + let mut transaction = card.transaction()?; - let ard = pgpt.application_related_data()?; - let times = ard.key_generation_times()?; - let fps = ard.fingerprints()?; + let times = transaction.key_generation_times()?; + let fps = transaction.fingerprints()?; // -- - let sig = pgpt.public_key(KeyType::Signing)?; + let sig = transaction.public_key_material(KeyType::Signing)?; let ts = times.signature().unwrap().get().into(); let key = public_key_material_and_fp_to_key(&sig, KeyType::Signing, &ts, fps.signature().unwrap())?; @@ -280,7 +269,7 @@ pub fn test_get_pub(pgp: &mut OpenPgp, _param: &[&str]) -> Result Result Result Result { - let mut pgpt = pgp.transaction()?; +pub fn test_reset(card: &mut Card, _param: &[&str]) -> Result { + let mut transaction = card.transaction()?; - pgpt.factory_reset()?; + transaction.factory_reset()?; Ok(vec![]) } @@ -319,25 +308,28 @@ pub fn test_reset(pgp: &mut OpenPgp, _param: &[&str]) -> Result Result { - let mut pgpt = pgp.transaction()?; +pub fn test_set_user_data(card: &mut Card, _param: &[&str]) -> Result { + let mut transaction = card.transaction()?; - pgpt.verify_pw3(b"12345678")?; + let mut admin = transaction.to_admin_card("12345678")?; // name - pgpt.set_name(b"Bar< Result Result { - let mut pgpt = pgp.transaction()?; +pub fn test_set_login_data( + card: &mut Card, + _params: &[&str], +) -> std::result::Result { + let mut transaction = card.transaction()?; - pgpt.verify_pw3(b"12345678")?; + let mut admin = transaction.to_admin_card("12345678")?; - let test_login = b"someone@somewhere.com"; - pgpt.set_login(test_login)?; + let test_login = "someone@somewhere.com"; + admin.set_login_data(test_login)?; // Read the previously set login data - let read_login_data = pgpt.login_data()?; + let read_login_data = transaction.login_data()?; - assert_eq!(read_login_data, test_login.to_vec()); + assert_eq!(read_login_data, test_login); Ok(vec![]) } -pub fn test_private_data(pgp: &mut OpenPgp, _param: &[&str]) -> Result { - let mut pgpt = pgp.transaction()?; - - let out = vec![]; - - println!(); - - let d = pgpt.private_use_do(1)?; - println!("data 1 {d:?}"); - - pgpt.verify_pw1_user(b"123456")?; - - pgpt.set_private_use_do(1, "Foo bar1!".as_bytes().to_vec())?; - pgpt.set_private_use_do(3, "Foo bar3!".as_bytes().to_vec())?; - - pgpt.verify_pw3(b"12345678")?; - - pgpt.set_private_use_do(2, "Foo bar2!".as_bytes().to_vec())?; - pgpt.set_private_use_do(4, "Foo bar4!".as_bytes().to_vec())?; - - let d = pgpt.private_use_do(1)?; - println!("data 1 {d:?}"); - let d = pgpt.private_use_do(2)?; - println!("data 2 {d:?}"); - let d = pgpt.private_use_do(3)?; - println!("data 3 {d:?}"); - let d = pgpt.private_use_do(4)?; - println!("data 4 {d:?}"); - - Ok(out) -} +// pub fn test_private_data(mut card: Card, _param: &[&str]) -> Result { +// let mut transaction = card.transaction()?; +// +// let out = vec![]; +// +// println!(); +// +// let d = transaction.private_use_do(1)?; +// println!("data 1 {d:?}"); +// +// transaction.verify_pw1_user(b"123456")?; +// +// transaction.set_private_use_do(1, "Foo bar1!".as_bytes().to_vec())?; +// transaction.set_private_use_do(3, "Foo bar3!".as_bytes().to_vec())?; +// +// transaction.verify_pw3(b"12345678")?; +// +// transaction.set_private_use_do(2, "Foo bar2!".as_bytes().to_vec())?; +// transaction.set_private_use_do(4, "Foo bar4!".as_bytes().to_vec())?; +// +// let d = transaction.private_use_do(1)?; +// println!("data 1 {d:?}"); +// let d = transaction.private_use_do(2)?; +// println!("data 2 {d:?}"); +// let d = transaction.private_use_do(3)?; +// println!("data 3 {d:?}"); +// let d = transaction.private_use_do(4)?; +// println!("data 4 {d:?}"); +// +// Ok(out) +// } // pub fn test_cardholder_cert( // card_tx: &mut CardApp, @@ -460,25 +455,25 @@ pub fn test_private_data(pgp: &mut OpenPgp, _param: &[&str]) -> Result Result { - let mut pgpt = pgp.transaction()?; +pub fn test_pw_status(mut card: Card, _param: &[&str]) -> Result { + let mut transaction = card.transaction()?; let out = vec![]; - let ard = pgpt.application_related_data()?; - let mut pws = ard.pw_status_bytes()?; + let mut pws = transaction.pw_status_bytes()?; println!("pws {pws:?}"); - pgpt.verify_pw3(b"12345678")?; + let mut admin = transaction.to_admin_card("12345678")?; pws.set_pw1_cds_valid_once(false); pws.set_pw1_pin_block(true); - pgpt.set_pw_status_bytes(&pws, false)?; + admin.set_pw_status_bytes(&pws, false)?; - let ard = pgpt.application_related_data()?; - let pws = ard.pw_status_bytes()?; + transaction.reload_ard()?; + + let pws = transaction.pw_status_bytes()?; println!("pws {pws:?}"); Ok(out) @@ -487,8 +482,8 @@ pub fn test_pw_status(pgp: &mut OpenPgp, _param: &[&str]) -> Result Status /// - verify pw1 (check) -> Status -pub fn test_verify(pgp: &mut OpenPgp, _param: &[&str]) -> Result { - let mut pgpt = pgp.transaction()?; +pub fn test_verify(mut card: Card, _param: &[&str]) -> Result { + let mut transaction = card.transaction()?; // Steps: // @@ -505,7 +500,8 @@ pub fn test_verify(pgp: &mut OpenPgp, _param: &[&str]) -> Result Result { // e.g. yubikey5 returns an error status! out.push(TestResult::Status(s)); @@ -526,14 +522,18 @@ pub fn test_verify(pgp: &mut OpenPgp, _param: &[&str]) -> Result out.push(TestResult::StatusOk), } - pgpt.set_name(b"Admin< { // e.g. yubikey5 returns an error status! out.push(TestResult::Status(s)); @@ -544,36 +544,39 @@ pub fn test_verify(pgp: &mut OpenPgp, _param: &[&str]) -> Result out.push(TestResult::StatusOk), } - pgpt.set_name(b"There< Result { - let mut pgpt = pgp.transaction()?; +pub fn test_change_pw(mut card: Card, _param: &[&str]) -> Result { + let mut transaction = card.transaction()?; let out = vec![]; // first do admin-less pw1 on gnuk // (NOTE: Gnuk requires a key to be loaded before allowing pw changes!) println!("change pw1"); - pgpt.change_pw1(b"123456", b"abcdef00")?; + transaction.change_user_pin(b"123456", b"abcdef00")?; // also set admin pw, which means pw1 is now only user-pw again, on gnuk println!("change pw3"); // ca.change_pw3("abcdef00", "abcdefgh")?; // gnuk - pgpt.change_pw3(b"12345678", b"abcdefgh")?; + transaction.change_admin_pin(b"12345678", b"abcdefgh")?; println!("change pw1"); - pgpt.change_pw1(b"abcdef00", b"abcdef")?; // gnuk + transaction.change_user_pin(b"abcdef00", b"abcdef")?; // gnuk // ca.change_pw1("123456", "abcdef")?; println!("verify bad pw1"); - match pgpt.verify_pw1_user(b"123456ab") { + match transaction.verify_user(b"123456ab") { Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => { // this is expected } @@ -584,10 +587,10 @@ pub fn test_change_pw(pgp: &mut OpenPgp, _param: &[&str]) -> Result { // this is expected } @@ -598,36 +601,36 @@ pub fn test_change_pw(pgp: &mut OpenPgp, _param: &[&str]) -> Result, _param: &[&str], ) -> Result { - let mut pgpt = pgp.transaction()?; + let mut transaction = card.transaction()?; let out = vec![]; // set pw3, then pw1 (to bring gnuk into non-admin mode) println!("set pw3"); - pgpt.change_pw3(b"12345678", b"12345678")?; + transaction.change_admin_pin(b"12345678", b"12345678")?; println!("set pw1"); - pgpt.change_pw1(b"123456", b"123456")?; + transaction.change_user_pin(b"123456", b"123456")?; println!("break pw1"); - let _ = pgpt.verify_pw1_user(b"wrong0"); - let _ = pgpt.verify_pw1_user(b"wrong0"); - let _ = pgpt.verify_pw1_user(b"wrong0"); - let res = pgpt.verify_pw1_user(b"wrong0"); + let _ = transaction.verify_user(b"wrong0"); + let _ = transaction.verify_user(b"wrong0"); + let _ = transaction.verify_user(b"wrong0"); + let res = transaction.verify_user(b"wrong0"); match res { Err(Error::CardStatus(StatusBytes::AuthenticationMethodBlocked)) => { @@ -646,20 +649,21 @@ pub fn test_reset_retry_counter( } println!("verify pw3"); - pgpt.verify_pw3(b"12345678")?; + transaction.verify_admin(b"12345678")?; println!("set resetting code"); - pgpt.set_resetting_code(b"abcdefgh")?; + let mut admin = transaction.to_admin_card(None)?; + admin.set_resetting_code(b"abcdefgh")?; println!("reset retry counter"); // ca.reset_retry_counter_pw1("abcdef".as_bytes().to_vec(), None)?; - let _res = pgpt.reset_retry_counter_pw1(b"abcdef", Some(b"abcdefgh")); + let _res = transaction.reset_user_pin(b"abcdef", b"abcdefgh"); println!("verify good pw1"); - pgpt.verify_pw1_user(b"abcdef")?; + transaction.verify_user(b"abcdef")?; println!("verify bad pw1"); - match pgpt.verify_pw1_user(b"00000000") { + match transaction.verify_user(b"00000000") { Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => { // this is expected } @@ -673,11 +677,9 @@ pub fn test_reset_retry_counter( } pub fn run_test( - tc: &mut TestCardData, - t: fn(&mut OpenPgp, &[&str]) -> Result, + card: &mut Card, + t: fn(&mut Card, &[&str]) -> Result, param: &[&str], ) -> Result { - let card = tc.get_card()?; - let mut pgp = OpenPgp::new(card); - t(&mut pgp, param) + t(card, param) } diff --git a/card-functionality/src/util.rs b/card-functionality/src/util.rs index c25a4a6..9673186 100644 --- a/card-functionality/src/util.rs +++ b/card-functionality/src/util.rs @@ -6,9 +6,9 @@ use std::time::SystemTime; use anyhow::Result; use openpgp_card::card_do::KeyGenerationTime; -use openpgp_card::{KeyType, OpenPgpTransaction}; -use openpgp_card_sequoia::sq_util; -use openpgp_card_sequoia::util::vka_as_uploadable_key; +use openpgp_card::KeyType; +use openpgp_card_sequoia::state::Admin; +use openpgp_card_sequoia::{sq_util, Card}; use sequoia_openpgp::parse::stream::{ DetachedVerifierBuilder, MessageLayer, MessageStructure, VerificationHelper, }; @@ -20,7 +20,7 @@ use sequoia_openpgp::Cert; pub const SP: &StandardPolicy = &StandardPolicy::new(); pub(crate) fn upload_subkeys( - pgpt: &mut OpenPgpTransaction, + admin: &mut Card, cert: &Cert, policy: &dyn Policy, ) -> Result> { @@ -44,8 +44,7 @@ pub(crate) fn upload_subkeys( out.push((fp, creation.into())); // upload key - let cuk = vka_as_uploadable_key(vka, None); - pgpt.key_import(cuk, *kt)?; + admin.upload_key(vka, *kt, None)?; } } From 566fd6f9a0678320309a79891dce0ffeda60e770 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 28 Aug 2023 20:38:19 +0200 Subject: [PATCH 053/115] card-functionality: Perform the full set of tests in a single Card --- card-functionality/src/import.rs | 14 ++++--- card-functionality/src/keygen.rs | 12 +++--- card-functionality/src/other.rs | 3 +- card-functionality/src/tests.rs | 65 +++++++++++++++----------------- 4 files changed, 47 insertions(+), 47 deletions(-) diff --git a/card-functionality/src/import.rs b/card-functionality/src/import.rs index 925a46f..07de36f 100644 --- a/card-functionality/src/import.rs +++ b/card-functionality/src/import.rs @@ -28,16 +28,18 @@ fn main() -> Result<()> { let mut c: Card = card.get_card()?; println!(" -> Card opened"); + let mut tx = c.transaction()?; + println!(" started transaction"); println!("Reset"); - let _ = run_test(&mut c, test_reset, &[])?; + let _ = run_test(&mut tx, test_reset, &[])?; print!("Set user data"); - let userdata_out = run_test(&mut c, test_set_user_data, &[])?; + let userdata_out = run_test(&mut tx, test_set_user_data, &[])?; println!(" {userdata_out:x?}"); println!("Set login data"); - let login_data_out = run_test(&mut c, test_set_login_data, &[])?; + let login_data_out = run_test(&mut tx, test_set_login_data, &[])?; println!(" {login_data_out:x?}"); let key_files = { @@ -52,7 +54,7 @@ fn main() -> Result<()> { for key_file in &key_files { // upload keys print!("Upload key '{key_file}'"); - let upload_res = run_test(&mut c, test_upload_keys, &[key_file]); + let upload_res = run_test(&mut tx, test_upload_keys, &[key_file]); if let Err(TestError::KeyUploadError(_file, err)) = &upload_res { // The card doesn't support this key type, so skip to the @@ -74,13 +76,13 @@ fn main() -> Result<()> { let cert = Cert::from_str(&key)?; let ciphertext = util::encrypt_to("Hello world!\n", &cert)?; - let dec_out = run_test(&mut c, test_decrypt, &[&key, &ciphertext])?; + let dec_out = run_test(&mut tx, test_decrypt, &[&key, &ciphertext])?; println!(" {dec_out:x?}"); // sign print!(" Sign"); - let sign_out = run_test(&mut c, test_sign, &[&key])?; + let sign_out = run_test(&mut tx, test_sign, &[&key])?; println!(" {sign_out:x?}"); } diff --git a/card-functionality/src/keygen.rs b/card-functionality/src/keygen.rs index ec4005a..7684c4e 100644 --- a/card-functionality/src/keygen.rs +++ b/card-functionality/src/keygen.rs @@ -28,6 +28,8 @@ fn main() -> Result<()> { let mut c: Card = card.get_card()?; println!(" -> Card opened"); + let mut tx = c.transaction()?; + println!(" started transaction"); // println!("Get pubkey"); // let _ = run_test(&mut card, test_get_pub, &[])?; @@ -39,14 +41,14 @@ fn main() -> Result<()> { // // continue; // only print caps println!("Reset"); - let _ = run_test(&mut c, test_reset, &[])?; + let _ = run_test(&mut tx, test_reset, &[])?; // println!("Algo info"); // let _ = run_test(&mut card, test_print_algo_info, &[])?; // Set user data because keygen expects a name (for the user id) println!("Set user data"); - let _ = run_test(&mut c, test_set_user_data, &[])?; + let _ = run_test(&mut tx, test_set_user_data, &[])?; let algos = { let config = card.get_config(); @@ -60,12 +62,12 @@ fn main() -> Result<()> { for algo in algos { println!("Generate key [{algo}]"); - let res = run_test(&mut c, test_keygen, &[&algo])?; + let res = run_test(&mut tx, test_keygen, &[&algo])?; if let TestResult::Text(cert_str) = &res[0] { // sign print!(" Sign"); - let sign_out = run_test(&mut c, test_sign, &[cert_str])?; + let sign_out = run_test(&mut tx, test_sign, &[cert_str])?; println!(" {sign_out:x?}"); // decrypt @@ -73,7 +75,7 @@ fn main() -> Result<()> { let ciphertext = util::encrypt_to("Hello world!\n", &cert)?; print!(" Decrypt"); - let dec_out = run_test(&mut c, test_decrypt, &[cert_str, &ciphertext])?; + let dec_out = run_test(&mut tx, test_decrypt, &[cert_str, &ciphertext])?; println!(" {dec_out:x?}"); } else { panic!("Didn't get back a Cert from test_keygen"); diff --git a/card-functionality/src/other.rs b/card-functionality/src/other.rs index e6278a7..4672a28 100644 --- a/card-functionality/src/other.rs +++ b/card-functionality/src/other.rs @@ -18,6 +18,7 @@ fn main() -> Result<()> { println!("** Run tests on card '{}' **", card.get_name()); let mut c: Card = card.get_card()?; + let mut tx = c.transaction()?; // println!("Caps"); // let _ = run_test(&mut card, test_print_caps, &[])?; @@ -27,7 +28,7 @@ fn main() -> Result<()> { // let _ = run_test(&mut card, test_print_algo_info, &[])?; println!("Reset"); - let _ = run_test(&mut c, test_reset, &[])?; + let _ = run_test(&mut tx, test_reset, &[])?; // --- diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index 8d89e5d..8660e17 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -10,7 +10,7 @@ use openpgp_card::algorithm::AlgoSimple; use openpgp_card::card_do::{KeyGenerationTime, Sex}; use openpgp_card::{Error, KeyType, OpenPgp, StatusBytes}; use openpgp_card_sequoia::sq_util; -use openpgp_card_sequoia::state::{Admin, Open}; +use openpgp_card_sequoia::state::{Admin, Open, Transaction}; use openpgp_card_sequoia::util::{ make_cert, public_key_material_and_fp_to_key, public_key_material_to_key, }; @@ -52,7 +52,7 @@ pub enum TestError { } /// Run after each "upload keys", if key *was* uploaded (?) -pub fn test_decrypt(card: &mut Card, param: &[&str]) -> Result { +pub fn test_decrypt(tx: &mut Card, param: &[&str]) -> Result { assert_eq!( param.len(), 2, @@ -63,9 +63,7 @@ pub fn test_decrypt(card: &mut Card, param: &[&str]) -> Result, param: &[&str]) -> Result, param: &[&str]) -> Result { +pub fn test_sign(tx: &mut Card, param: &[&str]) -> Result { assert_eq!(param.len(), 1, "test_sign needs a filename for 'cert'"); let cert = Cert::from_str(param[0])?; - let mut transaction = card.transaction()?; - - let mut sign = transaction.to_signing_card("123456").unwrap(); + let mut sign = tx.to_signing_card("123456").unwrap(); let s = sign.signer(&|| {})?; let msg = "Hello world, I am signed."; @@ -178,9 +174,10 @@ pub fn test_print_algo_info(pgp: &mut OpenPgp, _param: &[&str]) -> Result, param: &[&str]) -> Result { - let mut transaction = card.transaction()?; - +pub fn test_upload_keys( + tx: &mut Card, + param: &[&str], +) -> Result { assert_eq!( param.len(), 1, @@ -190,7 +187,7 @@ pub fn test_upload_keys(card: &mut Card, param: &[&str]) -> Result, param: &[&str]) -> Result, param: &[&str]) -> Result { - let mut transaction = card.transaction()?; - let mut admin = transaction +pub fn test_keygen(tx: &mut Card, param: &[&str]) -> Result { + let mut admin = tx .to_admin_card("12345678") .expect("Couldn't get Admin card"); @@ -233,9 +229,11 @@ pub fn test_keygen(card: &mut Card, param: &[&str]) -> Result, _param: &[&str]) -> Result, _param: &[&str]) -> Result { - let mut transaction = card.transaction()?; - - transaction.factory_reset()?; +pub fn test_reset(tx: &mut Card, _param: &[&str]) -> Result { + tx.factory_reset()?; Ok(vec![]) } @@ -308,10 +304,11 @@ pub fn test_reset(card: &mut Card, _param: &[&str]) -> Result, _param: &[&str]) -> Result { - let mut transaction = card.transaction()?; - - let mut admin = transaction.to_admin_card("12345678")?; +pub fn test_set_user_data( + tx: &mut Card, + _param: &[&str], +) -> Result { + let mut admin = tx.to_admin_card("12345678")?; // name admin.set_name("Bar<, _param: &[&str]) -> Result, _param: &[&str]) -> Result, + tx: &mut Card, _params: &[&str], ) -> std::result::Result { - let mut transaction = card.transaction()?; - - let mut admin = transaction.to_admin_card("12345678")?; + let mut admin = tx.to_admin_card("12345678")?; let test_login = "someone@somewhere.com"; admin.set_login_data(test_login)?; // Read the previously set login data - let read_login_data = transaction.login_data()?; + let read_login_data = tx.login_data()?; assert_eq!(read_login_data, test_login); @@ -677,8 +672,8 @@ pub fn test_reset_retry_counter( } pub fn run_test( - card: &mut Card, - t: fn(&mut Card, &[&str]) -> Result, + card: &mut Card, + t: fn(&mut Card, &[&str]) -> Result, param: &[&str], ) -> Result { t(card, param) From d3e30d5c4ccc65b029b4745bed161fe6f258c587 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 21 Aug 2023 12:16:18 +0200 Subject: [PATCH 054/115] openpgp-card-sequoia: re-export Fingerprint --- openpgp-card-sequoia/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card-sequoia/src/types.rs b/openpgp-card-sequoia/src/types.rs index 1bad2b2..b7dc547 100644 --- a/openpgp-card-sequoia/src/types.rs +++ b/openpgp-card-sequoia/src/types.rs @@ -4,6 +4,6 @@ //! Re-exports of openpgp-card types to enable standalone-use of openpgp-card-sequoia. pub use openpgp_card::algorithm::{Algo, AlgoSimple, Curve}; -pub use openpgp_card::card_do::{Sex, TouchPolicy}; +pub use openpgp_card::card_do::{Fingerprint, Sex, TouchPolicy}; pub use openpgp_card::crypto_data::{EccType, PublicKeyMaterial}; pub use openpgp_card::{Error, KeyType, StatusBytes}; From 308bd804ae69bf4eccdb91e5504788cb7637b616 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 19 Aug 2023 17:22:02 +0200 Subject: [PATCH 055/115] openpgp-card-sequoia: rustdoc --- openpgp-card-sequoia/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index be6fdf3..673f3d7 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 //! This crate offers ergonomic abstractions to use -//! [OpenPGP cards](https://en.wikipedia.org/wiki/OpenPGP_card). +//! [OpenPGP card devices](https://en.wikipedia.org/wiki/OpenPGP_card). //! The central abstraction is the [Card] type, which offers access to all card operations. //! //! A [Card] object is always in one of the possible [State]s. The [State] determines which From 8e4ee0880283dc3d52c79abf882c9934b6d1997b Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 28 Aug 2023 20:23:04 +0200 Subject: [PATCH 056/115] openpgp-card: minor cleanup --- openpgp-card/src/openpgp.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpgp-card/src/openpgp.rs b/openpgp-card/src/openpgp.rs index f2e14b7..51c26af 100644 --- a/openpgp-card/src/openpgp.rs +++ b/openpgp-card/src/openpgp.rs @@ -1083,11 +1083,9 @@ impl<'a> OpenPgpTransaction<'a> { key: Box, key_type: KeyType, ) -> Result<(), Error> { - let algo_info = self.algorithm_information(); - // An error is ok - it's fine if a card doesn't offer a list of // supported algorithms - let algo_info = algo_info.unwrap_or(None); + let algo_info = self.algorithm_information().unwrap_or(None); keys::key_import(self, key, key_type, algo_info) } From e6658713cb2c066aa268ca3aa08116fa5371512e Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 10:15:43 +0200 Subject: [PATCH 057/115] openpgp-card: rename Algo -> AlgorithmAttributes --- openpgp-card-sequoia/src/lib.rs | 8 +++-- openpgp-card-sequoia/src/types.rs | 2 +- openpgp-card-sequoia/src/util.rs | 4 +-- openpgp-card/src/algorithm.rs | 44 +++++++++++++++----------- openpgp-card/src/card_do.rs | 14 +++++--- openpgp-card/src/card_do/algo_attrs.rs | 25 ++++++++------- openpgp-card/src/card_do/algo_info.rs | 18 +++++------ openpgp-card/src/crypto_data.rs | 8 ++--- openpgp-card/src/keys.rs | 28 +++++++++++----- openpgp-card/src/openpgp.rs | 6 ++-- 10 files changed, 93 insertions(+), 64 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 673f3d7..8716763 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -140,7 +140,7 @@ //! ``` use card_backend::{CardBackend, SmartcardError}; -use openpgp_card::algorithm::{Algo, AlgoInfo, AlgoSimple}; +use openpgp_card::algorithm::{AlgoInfo, AlgoSimple, AlgorithmAttributes}; use openpgp_card::card_do::{ ApplicationIdentifier, CardholderRelatedData, ExtendedCapabilities, ExtendedLengthInfo, Fingerprint, HistoricalBytes, KeyGenerationTime, KeyInformation, Lang, PWStatusBytes, @@ -467,7 +467,7 @@ impl<'a> Card> { self.state.ard.extended_capabilities() } - pub fn algorithm_attributes(&self, key_type: KeyType) -> Result { + pub fn algorithm_attributes(&self, key_type: KeyType) -> Result { self.state.ard.algorithm_attributes(key_type) } @@ -617,7 +617,9 @@ impl<'a> Card> { self.state.ard.attestation_key_fingerprint() } - pub fn attestation_key_algorithm_attributes(&mut self) -> Result, Error> { + pub fn attestation_key_algorithm_attributes( + &mut self, + ) -> Result, Error> { self.state.ard.attestation_key_algorithm_attributes() } diff --git a/openpgp-card-sequoia/src/types.rs b/openpgp-card-sequoia/src/types.rs index b7dc547..589ea19 100644 --- a/openpgp-card-sequoia/src/types.rs +++ b/openpgp-card-sequoia/src/types.rs @@ -3,7 +3,7 @@ //! Re-exports of openpgp-card types to enable standalone-use of openpgp-card-sequoia. -pub use openpgp_card::algorithm::{Algo, AlgoSimple, Curve}; +pub use openpgp_card::algorithm::{AlgoSimple, AlgorithmAttributes, Curve}; pub use openpgp_card::card_do::{Fingerprint, Sex, TouchPolicy}; pub use openpgp_card::crypto_data::{EccType, PublicKeyMaterial}; pub use openpgp_card::{Error, KeyType, StatusBytes}; diff --git a/openpgp-card-sequoia/src/util.rs b/openpgp-card-sequoia/src/util.rs index f51415d..4661bc8 100644 --- a/openpgp-card-sequoia/src/util.rs +++ b/openpgp-card-sequoia/src/util.rs @@ -7,7 +7,7 @@ use std::convert::TryFrom; use std::convert::TryInto; use anyhow::Result; -use openpgp_card::algorithm::{Algo, Curve}; +use openpgp_card::algorithm::{AlgorithmAttributes, Curve}; use openpgp_card::card_do::{Fingerprint, KeyGenerationTime}; use openpgp_card::crypto_data::{CardUploadableKey, PublicKeyMaterial}; use openpgp_card::{Error, KeyType}; @@ -235,7 +235,7 @@ pub fn public_key_material_to_key( } PublicKeyMaterial::E(ecc) => { let algo = ecc.algo().clone(); // FIXME? - if let Algo::Ecc(algo_ecc) = algo { + if let AlgorithmAttributes::Ecc(algo_ecc) = algo { let curve = match algo_ecc.curve() { Curve::NistP256r1 => sequoia_openpgp::types::Curve::NistP256, Curve::NistP384r1 => sequoia_openpgp::types::Curve::NistP384, diff --git a/openpgp-card/src/algorithm.rs b/openpgp-card/src/algorithm.rs index 47f6d7e..d1d4992 100644 --- a/openpgp-card/src/algorithm.rs +++ b/openpgp-card/src/algorithm.rs @@ -3,7 +3,7 @@ //! Data structures that define OpenPGP algorithms. //! -//! [`Algo`] and its components model "Algorithm Attributes" as described in +//! [`AlgorithmAttributes`] and its components model "Algorithm Attributes" as described in //! the OpenPGP card specification. //! //! [`AlgoSimple`] offers a shorthand for specifying an algorithm, @@ -85,31 +85,39 @@ impl AlgoSimple { key_type: KeyType, ard: &ApplicationRelatedData, algo_info: Option, - ) -> Result { + ) -> Result { let algo = match self { - Self::RSA1k => Algo::Rsa(keys::determine_rsa_attrs(1024, key_type, ard, algo_info)?), - Self::RSA2k => Algo::Rsa(keys::determine_rsa_attrs(2048, key_type, ard, algo_info)?), - Self::RSA3k => Algo::Rsa(keys::determine_rsa_attrs(3072, key_type, ard, algo_info)?), - Self::RSA4k => Algo::Rsa(keys::determine_rsa_attrs(4096, key_type, ard, algo_info)?), - Self::NIST256 => Algo::Ecc(keys::determine_ecc_attrs( + Self::RSA1k => { + AlgorithmAttributes::Rsa(keys::determine_rsa_attrs(1024, key_type, ard, algo_info)?) + } + Self::RSA2k => { + AlgorithmAttributes::Rsa(keys::determine_rsa_attrs(2048, key_type, ard, algo_info)?) + } + Self::RSA3k => { + AlgorithmAttributes::Rsa(keys::determine_rsa_attrs(3072, key_type, ard, algo_info)?) + } + Self::RSA4k => { + AlgorithmAttributes::Rsa(keys::determine_rsa_attrs(4096, key_type, ard, algo_info)?) + } + Self::NIST256 => AlgorithmAttributes::Ecc(keys::determine_ecc_attrs( Curve::NistP256r1.oid(), Self::ecc_type(key_type), key_type, algo_info, )?), - Self::NIST384 => Algo::Ecc(keys::determine_ecc_attrs( + Self::NIST384 => AlgorithmAttributes::Ecc(keys::determine_ecc_attrs( Curve::NistP384r1.oid(), Self::ecc_type(key_type), key_type, algo_info, )?), - Self::NIST521 => Algo::Ecc(keys::determine_ecc_attrs( + Self::NIST521 => AlgorithmAttributes::Ecc(keys::determine_ecc_attrs( Curve::NistP521r1.oid(), Self::ecc_type(key_type), key_type, algo_info, )?), - Self::Curve25519 => Algo::Ecc(keys::determine_ecc_attrs( + Self::Curve25519 => AlgorithmAttributes::Ecc(keys::determine_ecc_attrs( Self::curve_for_25519(key_type).oid(), Self::ecc_type_25519(key_type), key_type, @@ -127,7 +135,7 @@ impl AlgoSimple { /// algorithms for each key type. This list specifies which "Algorithm /// Attributes" can be set for key generation or key import. #[derive(Debug, Clone, Eq, PartialEq)] -pub struct AlgoInfo(pub(crate) Vec<(KeyType, Algo)>); +pub struct AlgoInfo(pub(crate) Vec<(KeyType, AlgorithmAttributes)>); /// 4.4.3.9 Algorithm Attributes /// @@ -139,13 +147,13 @@ pub struct AlgoInfo(pub(crate) Vec<(KeyType, Algo)>); /// - Export of public key data from the card (e.g. after key generation) #[derive(Debug, Clone, Eq, PartialEq)] #[non_exhaustive] -pub enum Algo { +pub enum AlgorithmAttributes { Rsa(RsaAttrs), Ecc(EccAttrs), Unknown(Vec), } -impl fmt::Display for Algo { +impl fmt::Display for AlgorithmAttributes { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Rsa(rsa) => { @@ -181,13 +189,13 @@ impl fmt::Display for Algo { } } -impl Algo { +impl AlgorithmAttributes { /// Get a DO representation of the Algo, for setting algorithm /// attributes on the card. pub(crate) fn to_data_object(&self) -> Result, Error> { match self { - Algo::Rsa(rsa) => Self::rsa_algo_attrs(rsa), - Algo::Ecc(ecc) => Self::ecc_algo_attrs(ecc.oid(), ecc.ecc_type()), + AlgorithmAttributes::Rsa(rsa) => Self::rsa_algo_attrs(rsa), + AlgorithmAttributes::Ecc(ecc) => Self::ecc_algo_attrs(ecc.oid(), ecc.ecc_type()), _ => Err(Error::UnsupportedAlgo(format!("Unexpected Algo {self:?}"))), } } @@ -225,7 +233,7 @@ impl Algo { } } -/// RSA specific attributes of [`Algo`] ("Algorithm Attributes") +/// RSA specific attributes of [`AlgorithmAttributes`] #[derive(Debug, Clone, Eq, PartialEq)] pub struct RsaAttrs { len_n: u16, @@ -255,7 +263,7 @@ impl RsaAttrs { } } -/// ECC specific attributes of [`Algo`] ("Algorithm Attributes") +/// ECC specific attributes of [`AlgorithmAttributes`] #[derive(Debug, Clone, Eq, PartialEq)] pub struct EccAttrs { ecc_type: EccType, diff --git a/openpgp-card/src/card_do.rs b/openpgp-card/src/card_do.rs index 88ddae8..2ab2b57 100644 --- a/openpgp-card/src/card_do.rs +++ b/openpgp-card/src/card_do.rs @@ -9,7 +9,7 @@ use std::time::{Duration, UNIX_EPOCH}; use chrono::{DateTime, Utc}; -use crate::{algorithm::Algo, tlv::Tlv, Error, KeySet, KeyType, Tags}; +use crate::{algorithm::AlgorithmAttributes, tlv::Tlv, Error, KeySet, KeyType, Tags}; mod algo_attrs; mod algo_info; @@ -105,11 +105,11 @@ impl ApplicationRelatedData { } /// Get algorithm attributes (for each key type) - pub fn algorithm_attributes(&self, key_type: KeyType) -> Result { + pub fn algorithm_attributes(&self, key_type: KeyType) -> Result { let aa = self.0.find(key_type.algorithm_tag()); if let Some(aa) = aa { - Algo::try_from(&aa.serialize()[..]) + AlgorithmAttributes::try_from(&aa.serialize()[..]) } else { Err(Error::NotFound(format!( "Failed to get algorithm attributes for {key_type:?}." @@ -235,10 +235,14 @@ impl ApplicationRelatedData { } /// Get Attestation key algorithm attributes. - pub fn attestation_key_algorithm_attributes(&mut self) -> Result, Error> { + pub fn attestation_key_algorithm_attributes( + &mut self, + ) -> Result, Error> { match self.0.find(Tags::AlgorithmAttributesAttestation) { None => Ok(None), - Some(data) => Ok(Some(Algo::try_from(data.serialize().as_slice())?)), + Some(data) => Ok(Some(AlgorithmAttributes::try_from( + data.serialize().as_slice(), + )?)), } } diff --git a/openpgp-card/src/card_do/algo_attrs.rs b/openpgp-card/src/card_do/algo_attrs.rs index f4e1f99..1aa83b5 100644 --- a/openpgp-card/src/card_do/algo_attrs.rs +++ b/openpgp-card/src/card_do/algo_attrs.rs @@ -10,7 +10,7 @@ use nom::bytes::complete::tag; use nom::combinator::map; use nom::{branch, bytes::complete as bytes, number::complete as number}; -use crate::algorithm::{Algo, Curve, EccAttrs, RsaAttrs}; +use crate::algorithm::{AlgorithmAttributes, Curve, EccAttrs, RsaAttrs}; use crate::card_do::complete; use crate::crypto_data::EccType; @@ -80,14 +80,17 @@ fn parse_oid(input: &[u8]) -> nom::IResult<&[u8], Curve> { ))(input) } -fn parse_rsa(input: &[u8]) -> nom::IResult<&[u8], Algo> { +fn parse_rsa(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { let (input, _) = bytes::tag([0x01])(input)?; let (input, len_n) = number::be_u16(input)?; let (input, len_e) = number::be_u16(input)?; let (input, import_format) = number::u8(input)?; - Ok((input, Algo::Rsa(RsaAttrs::new(len_n, len_e, import_format)))) + Ok(( + input, + AlgorithmAttributes::Rsa(RsaAttrs::new(len_n, len_e, import_format)), + )) } fn parse_import_format(input: &[u8]) -> nom::IResult<&[u8], Option> { @@ -99,7 +102,7 @@ fn default_import_format(input: &[u8]) -> nom::IResult<&[u8], Option> { Ok((input, None)) } -fn parse_ecdh(input: &[u8]) -> nom::IResult<&[u8], Algo> { +fn parse_ecdh(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { let (input, _) = bytes::tag([0x12])(input)?; let (input, curve) = parse_oid(input)?; @@ -107,11 +110,11 @@ fn parse_ecdh(input: &[u8]) -> nom::IResult<&[u8], Algo> { Ok(( input, - Algo::Ecc(EccAttrs::new(EccType::ECDH, curve, import_format)), + AlgorithmAttributes::Ecc(EccAttrs::new(EccType::ECDH, curve, import_format)), )) } -fn parse_ecdsa(input: &[u8]) -> nom::IResult<&[u8], Algo> { +fn parse_ecdsa(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { let (input, _) = bytes::tag([0x13])(input)?; let (input, curve) = parse_oid(input)?; @@ -119,11 +122,11 @@ fn parse_ecdsa(input: &[u8]) -> nom::IResult<&[u8], Algo> { Ok(( input, - Algo::Ecc(EccAttrs::new(EccType::ECDSA, curve, import_format)), + AlgorithmAttributes::Ecc(EccAttrs::new(EccType::ECDSA, curve, import_format)), )) } -fn parse_eddsa(input: &[u8]) -> nom::IResult<&[u8], Algo> { +fn parse_eddsa(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { let (input, _) = bytes::tag([0x16])(input)?; let (input, curve) = parse_oid(input)?; @@ -131,15 +134,15 @@ fn parse_eddsa(input: &[u8]) -> nom::IResult<&[u8], Algo> { Ok(( input, - Algo::Ecc(EccAttrs::new(EccType::EdDSA, curve, import_format)), + AlgorithmAttributes::Ecc(EccAttrs::new(EccType::EdDSA, curve, import_format)), )) } -pub(crate) fn parse(input: &[u8]) -> nom::IResult<&[u8], Algo> { +pub(crate) fn parse(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { branch::alt((parse_rsa, parse_ecdsa, parse_eddsa, parse_ecdh))(input) } -impl TryFrom<&[u8]> for Algo { +impl TryFrom<&[u8]> for AlgorithmAttributes { type Error = crate::Error; fn try_from(data: &[u8]) -> Result { diff --git a/openpgp-card/src/card_do/algo_info.rs b/openpgp-card/src/card_do/algo_info.rs index dbebbd2..5a1cee4 100644 --- a/openpgp-card/src/card_do/algo_info.rs +++ b/openpgp-card/src/card_do/algo_info.rs @@ -10,12 +10,12 @@ use nom::branch::alt; use nom::combinator::map; use nom::{branch, bytes::complete as bytes, combinator, multi, sequence}; -use crate::algorithm::{Algo, AlgoInfo}; +use crate::algorithm::{AlgoInfo, AlgorithmAttributes}; use crate::card_do::{algo_attrs, complete}; use crate::KeyType; impl AlgoInfo { - pub fn filter_by_keytype(&self, kt: KeyType) -> Vec<&Algo> { + pub fn filter_by_keytype(&self, kt: KeyType) -> Vec<&AlgorithmAttributes> { self.0 .iter() .filter(|(k, _)| *k == kt) @@ -48,11 +48,11 @@ fn key_type(input: &[u8]) -> nom::IResult<&[u8], KeyType> { ))(input) } -fn unknown(input: &[u8]) -> nom::IResult<&[u8], Algo> { - Ok((&[], Algo::Unknown(input.to_vec()))) +fn unknown(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { + Ok((&[], AlgorithmAttributes::Unknown(input.to_vec()))) } -fn parse_one(input: &[u8]) -> nom::IResult<&[u8], Algo> { +fn parse_one(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { let (input, a) = combinator::map( combinator::flat_map(crate::tlv::length::length, bytes::take), |i| alt((combinator::all_consuming(algo_attrs::parse), unknown))(i), @@ -61,18 +61,18 @@ fn parse_one(input: &[u8]) -> nom::IResult<&[u8], Algo> { Ok((input, a?.1)) } -fn parse_list(input: &[u8]) -> nom::IResult<&[u8], Vec<(KeyType, Algo)>> { +fn parse_list(input: &[u8]) -> nom::IResult<&[u8], Vec<(KeyType, AlgorithmAttributes)>> { multi::many0(sequence::pair(key_type, parse_one))(input) } -fn parse_tl_list(input: &[u8]) -> nom::IResult<&[u8], Vec<(KeyType, Algo)>> { +fn parse_tl_list(input: &[u8]) -> nom::IResult<&[u8], Vec<(KeyType, AlgorithmAttributes)>> { let (input, (_, _, list)) = sequence::tuple((bytes::tag([0xfa]), crate::tlv::length::length, parse_list))(input)?; Ok((input, list)) } -fn parse(input: &[u8]) -> nom::IResult<&[u8], Vec<(KeyType, Algo)>> { +fn parse(input: &[u8]) -> nom::IResult<&[u8], Vec<(KeyType, AlgorithmAttributes)>> { // Handle two variations of input format: // a) TLV format (e.g. YubiKey 5) // b) Plain list (e.g. Gnuk, FOSS-Store Smartcard 3.4) @@ -99,7 +99,7 @@ impl TryFrom<&[u8]> for AlgoInfo { mod test { use std::convert::TryFrom; - use crate::algorithm::{Algo::*, AlgoInfo, Curve::*, EccAttrs, RsaAttrs}; + use crate::algorithm::{AlgoInfo, AlgorithmAttributes::*, Curve::*, EccAttrs, RsaAttrs}; use crate::crypto_data::EccType::*; use crate::KeyType::*; diff --git a/openpgp-card/src/crypto_data.rs b/openpgp-card/src/crypto_data.rs index 075e301..5472bda 100644 --- a/openpgp-card/src/crypto_data.rs +++ b/openpgp-card/src/crypto_data.rs @@ -5,7 +5,7 @@ //! Private key data, public key data, cryptograms for decryption, hash //! data for signing. -use crate::algorithm::Algo; +use crate::algorithm::AlgorithmAttributes; use crate::card_do::{Fingerprint, KeyGenerationTime}; use crate::{oid, Error}; @@ -169,18 +169,18 @@ impl RSAPub { #[non_exhaustive] pub struct EccPub { data: Vec, - algo: Algo, + algo: AlgorithmAttributes, } impl EccPub { - pub fn new(data: Vec, algo: Algo) -> Self { + pub fn new(data: Vec, algo: AlgorithmAttributes) -> Self { Self { data, algo } } pub fn data(&self) -> &[u8] { &self.data } - pub fn algo(&self) -> &Algo { + pub fn algo(&self) -> &AlgorithmAttributes { &self.algo } } diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index 507d809..48c6a82 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -6,7 +6,7 @@ use std::convert::TryFrom; use std::time::{SystemTime, UNIX_EPOCH}; -use crate::algorithm::{Algo, AlgoInfo, Curve, EccAttrs, RsaAttrs}; +use crate::algorithm::{AlgoInfo, AlgorithmAttributes, Curve, EccAttrs, RsaAttrs}; use crate::apdu::command::Command; use crate::apdu::commands; use crate::card_do::{ApplicationRelatedData, Fingerprint, KeyGenerationTime}; @@ -32,7 +32,7 @@ pub(crate) fn gen_key_with_metadata( card_tx: &mut OpenPgpTransaction, fp_from_pub: fn(&PublicKeyMaterial, KeyGenerationTime, KeyType) -> Result, key_type: KeyType, - algo: Option<&Algo>, + algo: Option<&AlgorithmAttributes>, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { // Set algo on card if it's Some if let Some(target_algo) = algo { @@ -91,7 +91,7 @@ pub(crate) fn gen_key_with_metadata( } /// Transform a public key Tlv from the card into PublicKeyMaterial -fn tlv_to_pubkey(tlv: &Tlv, algo: &Algo) -> Result { +fn tlv_to_pubkey(tlv: &Tlv, algo: &AlgorithmAttributes) -> Result { let n = tlv.find(Tags::PublicKeyDataRsaModulus); let v = tlv.find(Tags::PublicKeyDataRsaExponent); @@ -193,7 +193,7 @@ pub(crate) fn key_import( let key_cmd = rsa_key_import_cmd(key_type, rsa_key, &rsa_attrs)?; - (Algo::Rsa(rsa_attrs), key_cmd) + (AlgorithmAttributes::Rsa(rsa_attrs), key_cmd) } PrivateKeyMaterial::E(ecc_key) => { let ecc_attrs = @@ -201,7 +201,7 @@ pub(crate) fn key_import( let key_cmd = ecc_key_import_cmd(key_type, ecc_key, &ecc_attrs)?; - (Algo::Ecc(ecc_attrs), key_cmd) + (AlgorithmAttributes::Ecc(ecc_attrs), key_cmd) } }; @@ -249,7 +249,7 @@ pub(crate) fn determine_rsa_attrs( let algo = ard.algorithm_attributes(key_type)?; // Is the algorithm on the card currently set to RSA? - if let Algo::Rsa(rsa) = algo { + if let AlgorithmAttributes::Rsa(rsa) = algo { // If so, use the algorithm parameters from the card and // adjust the bit length based on the user-provided key. RsaAttrs::new(rsa_bits, rsa.len_e(), rsa.import_format()) @@ -322,7 +322,13 @@ fn card_algo_rsa(algo_info: AlgoInfo, key_type: KeyType, rsa_bits: u16) -> Resul // Get RSA algo attributes let rsa_algos: Vec<_> = keytype_algos .iter() - .filter_map(|a| if let Algo::Rsa(r) = a { Some(r) } else { None }) + .filter_map(|a| { + if let AlgorithmAttributes::Rsa(r) = a { + Some(r) + } else { + None + } + }) .collect(); // Filter card algorithms by rsa bitlength of the key we want to upload @@ -355,7 +361,13 @@ fn check_card_algo_ecc(algo_info: AlgoInfo, key_type: KeyType, oid: &[u8]) -> Ve // Get attributes let ecc_algos: Vec<_> = keytype_algos .iter() - .filter_map(|a| if let Algo::Ecc(e) = a { Some(e) } else { None }) + .filter_map(|a| { + if let AlgorithmAttributes::Ecc(e) = a { + Some(e) + } else { + None + } + }) .collect(); // Find entries with this OID in the algorithm information for key_type diff --git a/openpgp-card/src/openpgp.rs b/openpgp-card/src/openpgp.rs index 51c26af..6ac47b6 100644 --- a/openpgp-card/src/openpgp.rs +++ b/openpgp-card/src/openpgp.rs @@ -5,7 +5,7 @@ use std::convert::{TryFrom, TryInto}; use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError}; -use crate::algorithm::{Algo, AlgoInfo, AlgoSimple}; +use crate::algorithm::{AlgoInfo, AlgoSimple, AlgorithmAttributes}; use crate::apdu::command::Command; use crate::apdu::commands; use crate::apdu::response::RawResponse; @@ -905,7 +905,7 @@ impl<'a> OpenPgpTransaction<'a> { pub fn set_algorithm_attributes( &mut self, key_type: KeyType, - algo: &Algo, + algo: &AlgorithmAttributes, ) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_algorithm_attributes"); @@ -1107,7 +1107,7 @@ impl<'a> OpenPgpTransaction<'a> { KeyType, ) -> Result, key_type: KeyType, - algo: Option<&Algo>, + algo: Option<&AlgorithmAttributes>, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { keys::gen_key_with_metadata(self, fp_from_pub, key_type, algo) } From 01ef1ec4d144ddd9674d9f80c6d1278449961469 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 13:07:43 +0200 Subject: [PATCH 058/115] openpgp-card: internal API tweaks Reduce unnecessary use of ApplicationRelatedData. --- openpgp-card/src/algorithm.rs | 52 +++++++++++++++++++++-------------- openpgp-card/src/keys.rs | 13 ++++----- openpgp-card/src/openpgp.rs | 12 ++++---- 3 files changed, 44 insertions(+), 33 deletions(-) diff --git a/openpgp-card/src/algorithm.rs b/openpgp-card/src/algorithm.rs index d1d4992..590723d 100644 --- a/openpgp-card/src/algorithm.rs +++ b/openpgp-card/src/algorithm.rs @@ -1,10 +1,10 @@ -// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 -//! Data structures that define OpenPGP algorithms. +//! Data structures that specify algorithms to use on an OpenPGP card. //! -//! [`AlgorithmAttributes`] and its components model "Algorithm Attributes" as described in -//! the OpenPGP card specification. +//! [`AlgorithmAttributes`] (and its components) model "Algorithm Attributes" +//! as described in the OpenPGP card specification. //! //! [`AlgoSimple`] offers a shorthand for specifying an algorithm, //! specifically for key generation on the card. @@ -31,7 +31,7 @@ pub enum AlgoSimple { } impl TryFrom<&str> for AlgoSimple { - type Error = crate::Error; + type Error = Error; fn try_from(algo: &str) -> Result { use AlgoSimple::*; @@ -80,25 +80,37 @@ impl AlgoSimple { /// This mapping differs between cards, based on `ard` and `algo_info` /// (e.g. the exact Algo variant can have a different size for e, in RSA; /// also, the import_format can differ). - pub(crate) fn determine_algo( + pub(crate) fn determine_algo_attributes( &self, key_type: KeyType, ard: &ApplicationRelatedData, algo_info: Option, - ) -> Result { + ) -> Result { let algo = match self { - Self::RSA1k => { - AlgorithmAttributes::Rsa(keys::determine_rsa_attrs(1024, key_type, ard, algo_info)?) - } - Self::RSA2k => { - AlgorithmAttributes::Rsa(keys::determine_rsa_attrs(2048, key_type, ard, algo_info)?) - } - Self::RSA3k => { - AlgorithmAttributes::Rsa(keys::determine_rsa_attrs(3072, key_type, ard, algo_info)?) - } - Self::RSA4k => { - AlgorithmAttributes::Rsa(keys::determine_rsa_attrs(4096, key_type, ard, algo_info)?) - } + Self::RSA1k => AlgorithmAttributes::Rsa(keys::determine_rsa_attrs( + 1024, + key_type, + ard.algorithm_attributes(key_type)?, + algo_info, + )?), + Self::RSA2k => AlgorithmAttributes::Rsa(keys::determine_rsa_attrs( + 2048, + key_type, + ard.algorithm_attributes(key_type)?, + algo_info, + )?), + Self::RSA3k => AlgorithmAttributes::Rsa(keys::determine_rsa_attrs( + 3072, + key_type, + ard.algorithm_attributes(key_type)?, + algo_info, + )?), + Self::RSA4k => AlgorithmAttributes::Rsa(keys::determine_rsa_attrs( + 4096, + key_type, + ard.algorithm_attributes(key_type)?, + algo_info, + )?), Self::NIST256 => AlgorithmAttributes::Ecc(keys::determine_ecc_attrs( Curve::NistP256r1.oid(), Self::ecc_type(key_type), @@ -334,7 +346,7 @@ impl Curve { } impl TryFrom<&[u8]> for Curve { - type Error = crate::Error; + type Error = Error; fn try_from(oid: &[u8]) -> Result { use Curve::*; diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index 48c6a82..54513c4 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -9,7 +9,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use crate::algorithm::{AlgoInfo, AlgorithmAttributes, Curve, EccAttrs, RsaAttrs}; use crate::apdu::command::Command; use crate::apdu::commands; -use crate::card_do::{ApplicationRelatedData, Fingerprint, KeyGenerationTime}; +use crate::card_do::{Fingerprint, KeyGenerationTime}; use crate::crypto_data::{ CardUploadableKey, EccKey, EccPub, EccType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey, RSAPub, @@ -189,7 +189,8 @@ pub(crate) fn key_import( // (round up to 4-bytes, in case the key has 8+ leading zero bits) let rsa_bits = (((rsa_key.n().len() * 8 + 31) / 32) * 32) as u16; - let rsa_attrs = determine_rsa_attrs(rsa_bits, key_type, &ard, algo_info)?; + let algo_attr = ard.algorithm_attributes(key_type)?; + let rsa_attrs = determine_rsa_attrs(rsa_bits, key_type, algo_attr, algo_info)?; let key_cmd = rsa_key_import_cmd(key_type, rsa_key, &rsa_attrs)?; @@ -233,9 +234,9 @@ pub(crate) fn key_import( pub(crate) fn determine_rsa_attrs( rsa_bits: u16, key_type: KeyType, - ard: &ApplicationRelatedData, + algo_attr: AlgorithmAttributes, algo_info: Option, -) -> Result { +) -> Result { // Figure out suitable RSA algorithm parameters: // Does the card offer a list of algorithms? @@ -246,10 +247,8 @@ pub(crate) fn determine_rsa_attrs( } else { // No -> Get the current algorithm attributes for key_type. - let algo = ard.algorithm_attributes(key_type)?; - // Is the algorithm on the card currently set to RSA? - if let AlgorithmAttributes::Rsa(rsa) = algo { + if let AlgorithmAttributes::Rsa(rsa) = algo_attr { // If so, use the algorithm parameters from the card and // adjust the bit length based on the user-provided key. RsaAttrs::new(rsa_bits, rsa.len_e(), rsa.import_format()) diff --git a/openpgp-card/src/openpgp.rs b/openpgp-card/src/openpgp.rs index 6ac47b6..1d88def 100644 --- a/openpgp-card/src/openpgp.rs +++ b/openpgp-card/src/openpgp.rs @@ -1077,7 +1077,7 @@ impl<'a> OpenPgpTransaction<'a> { // ----------------- /// Import an existing private key to the card. - /// (This implicitly sets the algorithm info, fingerprint and timestamp) + /// (This implicitly sets the algorithm attributes, fingerprint and timestamp) pub fn key_import( &mut self, key: Box, @@ -1093,10 +1093,10 @@ impl<'a> OpenPgpTransaction<'a> { /// Generate a key on the card. /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) /// - /// If the `algo` parameter is Some, then this algorithm will be set on + /// If the `algorithm_attributes` parameter is Some, then this algorithm will be set on /// the card for "key_type". /// - /// Note: `algo` needs to precisely specify the RSA bitsize of e (if + /// Note: `algorithm_attributes` needs to precisely specify the RSA bitsize of e (if /// applicable), and import format, with values that the current card /// supports. pub fn generate_key( @@ -1107,9 +1107,9 @@ impl<'a> OpenPgpTransaction<'a> { KeyType, ) -> Result, key_type: KeyType, - algo: Option<&AlgorithmAttributes>, + algorithm_attributes: Option<&AlgorithmAttributes>, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { - keys::gen_key_with_metadata(self, fp_from_pub, key_type, algo) + keys::gen_key_with_metadata(self, fp_from_pub, key_type, algorithm_attributes) } /// Generate a key on the card. @@ -1138,7 +1138,7 @@ impl<'a> OpenPgpTransaction<'a> { None }; - let algo = simple.determine_algo(key_type, &ard, algo_info)?; + let algo = simple.determine_algo_attributes(key_type, &ard, algo_info)?; Self::generate_key(self, fp_from_pub, key_type, Some(&algo)) } From 2d1bf919d47bf43b1dc9724845c654ffcbfadcc8 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 13:18:53 +0200 Subject: [PATCH 059/115] openpgp-card: rename Openpgp and OpenpgpTransaction, restructure modules --- card-functionality/src/tests.rs | 12 +- openpgp-card-sequoia/src/decryptor.rs | 6 +- openpgp-card-sequoia/src/lib.rs | 14 +- openpgp-card-sequoia/src/signer.rs | 12 +- openpgp-card-sequoia/src/state.rs | 5 +- openpgp-card/src/apdu/commands.rs | 7 +- openpgp-card/src/card_do.rs | 73 +- openpgp-card/src/card_do/cardholder.rs | 4 +- openpgp-card/src/keys.rs | 14 +- openpgp-card/src/lib.rs | 1461 +++++++++++++++++++----- openpgp-card/src/openpgp.rs | 1185 ------------------- openpgp-card/src/tags.rs | 259 +++++ openpgp-card/src/tlv.rs | 5 +- 13 files changed, 1537 insertions(+), 1520 deletions(-) delete mode 100644 openpgp-card/src/openpgp.rs create mode 100644 openpgp-card/src/tags.rs diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index 8660e17..631f2e2 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -8,7 +8,7 @@ use std::string::FromUtf8Error; use anyhow::Result; use openpgp_card::algorithm::AlgoSimple; use openpgp_card::card_do::{KeyGenerationTime, Sex}; -use openpgp_card::{Error, KeyType, OpenPgp, StatusBytes}; +use openpgp_card::{Error, KeyType, StatusBytes}; use openpgp_card_sequoia::sq_util; use openpgp_card_sequoia::state::{Admin, Open, Transaction}; use openpgp_card_sequoia::util::{ @@ -136,7 +136,10 @@ fn check_key_upload_algo_attrs() -> Result<()> { Ok(()) } -pub fn test_print_caps(pgp: &mut OpenPgp, _param: &[&str]) -> Result { +pub fn test_print_caps( + pgp: &mut openpgp_card::Card, + _param: &[&str], +) -> Result { let mut pgpt = pgp.transaction()?; let ard = pgpt.application_related_data()?; @@ -156,7 +159,10 @@ pub fn test_print_caps(pgp: &mut OpenPgp, _param: &[&str]) -> Result Result { +pub fn test_print_algo_info( + pgp: &mut openpgp_card::Card, + _param: &[&str], +) -> Result { let mut pgpt = pgp.transaction()?; let ard = pgpt.application_related_data()?; diff --git a/openpgp-card-sequoia/src/decryptor.rs b/openpgp-card-sequoia/src/decryptor.rs index a41a743..65300b5 100644 --- a/openpgp-card-sequoia/src/decryptor.rs +++ b/openpgp-card-sequoia/src/decryptor.rs @@ -3,7 +3,7 @@ use anyhow::anyhow; use openpgp_card::crypto_data::Cryptogram; -use openpgp_card::OpenPgpTransaction; +use openpgp_card::Transaction; use sequoia_openpgp::crypto::mpi; use sequoia_openpgp::crypto::SessionKey; use sequoia_openpgp::packet; @@ -15,7 +15,7 @@ use crate::PublicKey; pub struct CardDecryptor<'a, 'app> { /// The OpenPGP card (authenticated to allow decryption operations) - ca: &'a mut OpenPgpTransaction<'app>, + ca: &'a mut Transaction<'app>, /// The matching public key for the card's decryption key public: PublicKey, @@ -26,7 +26,7 @@ pub struct CardDecryptor<'a, 'app> { impl<'a, 'app> CardDecryptor<'a, 'app> { pub(crate) fn with_pubkey( - ca: &'a mut OpenPgpTransaction<'app>, + ca: &'a mut Transaction<'app>, public: PublicKey, touch_prompt: &'a (dyn Fn() + Send + Sync), ) -> CardDecryptor<'a, 'app> { diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 8716763..b1afaba 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -143,11 +143,11 @@ use card_backend::{CardBackend, SmartcardError}; use openpgp_card::algorithm::{AlgoInfo, AlgoSimple, AlgorithmAttributes}; use openpgp_card::card_do::{ ApplicationIdentifier, CardholderRelatedData, ExtendedCapabilities, ExtendedLengthInfo, - Fingerprint, HistoricalBytes, KeyGenerationTime, KeyInformation, Lang, PWStatusBytes, + Fingerprint, HistoricalBytes, KeyGenerationTime, KeyInformation, KeySet, Lang, PWStatusBytes, SecuritySupportTemplate, Sex, TouchPolicy, UIF, }; use openpgp_card::crypto_data::PublicKeyMaterial; -use openpgp_card::{Error, KeySet, KeyType, OpenPgp, OpenPgpTransaction}; +use openpgp_card::{Error, KeyType}; use sequoia_openpgp::cert::prelude::ValidErasedKeyAmalgamation; use sequoia_openpgp::packet::key::SecretParts; use sequoia_openpgp::packet::{key, Key}; @@ -241,7 +241,7 @@ impl Card { where B: Into>, { - let pgp = OpenPgp::new(backend)?; + let pgp = openpgp_card::Card::new(backend)?; Ok(Card:: { state: Open { pgp }, @@ -265,7 +265,7 @@ impl Card { impl<'a> Card> { // Internal constructor - fn new(mut opt: OpenPgpTransaction<'a>) -> Result { + fn new(mut opt: openpgp_card::Transaction<'a>) -> Result { let ard = opt.application_related_data()?; Ok(Self { @@ -715,7 +715,7 @@ impl<'a> Card> { impl<'app, 'open> Card> { /// Helper fn to easily access underlying openpgp_card object - fn card(&mut self) -> &mut OpenPgpTransaction<'app> { + fn card(&mut self) -> &mut openpgp_card::Transaction<'app> { &mut self.state.tx.state.opt } @@ -767,7 +767,7 @@ impl<'app, 'open> Card> { impl<'app, 'open> Card> { /// Helper fn to easily access underlying openpgp_card object - fn card(&mut self) -> &mut OpenPgpTransaction<'app> { + fn card(&mut self) -> &mut openpgp_card::Transaction<'app> { &mut self.state.tx.state.opt } @@ -823,7 +823,7 @@ impl<'app, 'open> Card> { } /// Helper fn to easily access underlying openpgp_card object - fn card(&mut self) -> &mut OpenPgpTransaction<'app> { + fn card(&mut self) -> &mut openpgp_card::Transaction<'app> { &mut self.state.tx.state.opt } } diff --git a/openpgp-card-sequoia/src/signer.rs b/openpgp-card-sequoia/src/signer.rs index 079dc70..d377701 100644 --- a/openpgp-card-sequoia/src/signer.rs +++ b/openpgp-card-sequoia/src/signer.rs @@ -5,7 +5,7 @@ use std::convert::TryInto; use anyhow::anyhow; use openpgp_card::crypto_data::Hash; -use openpgp_card::OpenPgpTransaction; +use openpgp_card::Transaction; use sequoia_openpgp::crypto; use sequoia_openpgp::crypto::mpi; use sequoia_openpgp::types::{Curve, PublicKeyAlgorithm}; @@ -14,7 +14,7 @@ use crate::PublicKey; pub struct CardSigner<'a, 'app> { /// The OpenPGP card (authenticated to allow signing operations) - ca: &'a mut OpenPgpTransaction<'app>, + ca: &'a mut Transaction<'app>, /// The matching public key for the card's signing key public: PublicKey, @@ -28,7 +28,7 @@ pub struct CardSigner<'a, 'app> { impl<'a, 'app> CardSigner<'a, 'app> { pub(crate) fn with_pubkey( - ca: &'a mut OpenPgpTransaction<'app>, + ca: &'a mut Transaction<'app>, public: PublicKey, touch_prompt: &'a (dyn Fn() + Send + Sync), ) -> CardSigner<'a, 'app> { @@ -41,7 +41,7 @@ impl<'a, 'app> CardSigner<'a, 'app> { } pub(crate) fn with_pubkey_for_auth( - ca: &'a mut OpenPgpTransaction<'app>, + ca: &'a mut Transaction<'app>, public: PublicKey, touch_prompt: &'a (dyn Fn() + Send + Sync), ) -> CardSigner<'a, 'app> { @@ -84,9 +84,9 @@ impl<'a, 'app> crypto::Signer for CardSigner<'a, 'app> { }; let sig_fn = if !self.auth { - OpenPgpTransaction::signature_for_hash + Transaction::signature_for_hash } else { - OpenPgpTransaction::authenticate_for_hash + Transaction::authenticate_for_hash }; // Delegate a signing (or auth) operation to the OpenPGP card. diff --git a/openpgp-card-sequoia/src/state.rs b/openpgp-card-sequoia/src/state.rs index 2143f3f..c33e023 100644 --- a/openpgp-card-sequoia/src/state.rs +++ b/openpgp-card-sequoia/src/state.rs @@ -4,7 +4,6 @@ //! States of a card are modeled by the types `Open`, `Transaction`, `User`, `Sign`, `Admin`. use openpgp_card::card_do::ApplicationRelatedData; -use openpgp_card::{OpenPgp, OpenPgpTransaction}; use crate::Card; @@ -23,7 +22,7 @@ impl State for Admin<'_, '_> {} /// /// A transaction can be started on the card, in this state. pub struct Open { - pub(crate) pgp: OpenPgp, + pub(crate) pgp: openpgp_card::Card, } /// State of an OpenPGP card once a transaction has been started. @@ -34,7 +33,7 @@ pub struct Open { /// /// (Note that a factory-reset can be performed in this base state.) pub struct Transaction<'a> { - pub(crate) opt: OpenPgpTransaction<'a>, + pub(crate) opt: openpgp_card::Transaction<'a>, // Cache of "application related data". // diff --git a/openpgp-card/src/apdu/commands.rs b/openpgp-card/src/apdu/commands.rs index cb20ccf..dcd2c67 100644 --- a/openpgp-card/src/apdu/commands.rs +++ b/openpgp-card/src/apdu/commands.rs @@ -1,15 +1,16 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Pre-defined `Command` values for the OpenPGP card application use crate::apdu::command::Command; -use crate::{KeyType, ShortTag, Tags, OP_APP}; +use crate::tags::{ShortTag, Tags}; +use crate::{KeyType, OPENPGP_APPLICATION}; /// 7.2.1 SELECT /// (select the OpenPGP application on the card) pub(crate) fn select_openpgp() -> Command { - Command::new(0x00, 0xA4, 0x04, 0x00, OP_APP.to_vec()) + Command::new(0x00, 0xA4, 0x04, 0x00, OPENPGP_APPLICATION.to_vec()) } /// 7.2.6 GET DATA diff --git a/openpgp-card/src/card_do.rs b/openpgp-card/src/card_do.rs index 2ab2b57..a54d349 100644 --- a/openpgp-card/src/card_do.rs +++ b/openpgp-card/src/card_do.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! OpenPGP card data objects (DO) @@ -9,7 +9,8 @@ use std::time::{Duration, UNIX_EPOCH}; use chrono::{DateTime, Utc}; -use crate::{algorithm::AlgorithmAttributes, tlv::Tlv, Error, KeySet, KeyType, Tags}; +use crate::tags::Tags; +use crate::{algorithm::AlgorithmAttributes, tlv::Tlv, Error, KeyType}; mod algo_attrs; mod algo_info; @@ -22,7 +23,7 @@ mod historical; mod key_generation_times; mod pw_status; -/// 4.4.3.1 Application Related Data +/// Application Related Data [Spec section 4.4.3.1] /// /// The "application related data" DO contains a set of DOs. /// This struct offers read access to these DOs. @@ -274,7 +275,7 @@ impl ApplicationRelatedData { } } -/// Security support template (see spec pg. 24) +/// Security support template [Spec page 24] #[derive(Debug)] pub struct SecuritySupportTemplate { // Digital signature counter [3 bytes] @@ -288,7 +289,7 @@ impl SecuritySupportTemplate { } } -/// An OpenPGP key generation Time (see spec pg. 24) +/// An OpenPGP key generation Time [Spec page 24] #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub struct KeyGenerationTime(u32); @@ -309,7 +310,7 @@ impl Display for KeyGenerationTime { } } -/// User Interaction Flag (UIF) (see spec pg. 24) +/// User Interaction Flag (UIF) [Spec page 24] #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub struct UIF([u8; 2]); @@ -422,7 +423,7 @@ impl From for TouchPolicy { } } -/// "additional hardware for user interaction" (see spec 4.1.3.2) +/// "additional hardware for user interaction" [Spec section 4.1.3.2] pub struct Features(u8); impl From for Features { @@ -464,7 +465,7 @@ impl Display for Features { } } -/// 4.4.3.8 Key Information +/// Key Information [Spec section 4.4.3.8] pub struct KeyInformation(Vec); impl From> for KeyInformation { @@ -583,7 +584,7 @@ impl Display for KeyStatus { } } -/// 4.2.1 Application Identifier (AID) +/// Application Identifier (AID) [Spec section 4.2.1] #[derive(Debug, Eq, PartialEq)] pub struct ApplicationIdentifier { application: u8, @@ -602,7 +603,7 @@ impl Display for ApplicationIdentifier { } } -/// 6 Historical Bytes +/// Historical Bytes [Spec chapter 6] #[derive(Debug, PartialEq, Eq)] pub struct HistoricalBytes { /// category indicator byte @@ -618,7 +619,7 @@ pub struct HistoricalBytes { sib: u8, } -/// Card Capabilities (see 6 Historical Bytes) +/// Card Capabilities [Spec chapter 6 (Historical Bytes)] #[derive(Debug, PartialEq, Eq)] pub struct CardCapabilities { command_chaining: bool, @@ -642,7 +643,7 @@ impl Display for CardCapabilities { } } -/// Card service data (see 6 Historical Bytes) +/// Card service data [Spec chapter 6 (Historical Bytes)] #[derive(Debug, PartialEq, Eq)] pub struct CardServiceData { select_by_full_df_name: bool, // Application Selection by full DF name (AID) @@ -689,7 +690,7 @@ impl Display for CardServiceData { } } -/// 4.4.3.7 Extended Capabilities +/// Extended Capabilities [Spec section 4.4.3.7] #[derive(Debug, Eq, PartialEq)] pub struct ExtendedCapabilities { secure_messaging: bool, @@ -779,7 +780,7 @@ impl Display for ExtendedCapabilities { } } -/// 4.1.3.1 Extended length information +/// Extended length information [Spec section 4.1.3.1] #[derive(Debug, Eq, PartialEq)] pub struct ExtendedLengthInfo { max_command_bytes: u16, @@ -794,7 +795,7 @@ impl Display for ExtendedLengthInfo { } } -/// Cardholder Related Data (see spec pg. 22) +/// Cardholder Related Data [Spec page 22] #[derive(Debug, PartialEq, Eq)] pub struct CardholderRelatedData { name: Option>, @@ -819,7 +820,7 @@ impl Display for CardholderRelatedData { } } -/// 4.4.3.5 Sex +/// Sex [Spec section 4.4.3.5] /// /// Encoded in accordance with #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -867,7 +868,9 @@ impl From for Sex { } } -/// Individual language for Language Preferences (4.4.3.4), accessible via `CardholderRelatedData`. +/// Individual language for Language Preferences [Spec section 4.4.3.4] +/// +/// This field is accessible via `CardholderRelatedData`. /// /// Encoded according to #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -922,7 +925,7 @@ impl From<&[u8; 2]> for Lang { } } -/// PW status Bytes (see spec page 23) +/// PW status Bytes [Spec page 23] #[derive(Debug, PartialEq, Eq)] pub struct PWStatusBytes { pub(crate) pw1_cds_valid_once: bool, @@ -992,7 +995,7 @@ impl PWStatusBytes { } } -/// Fingerprint (see spec pg. 23) +/// Fingerprint [Spec page 23] #[derive(Clone, Eq, PartialEq)] pub struct Fingerprint([u8; 20]); @@ -1026,3 +1029,35 @@ pub(crate) fn complete(result: nom::IResult<&[u8], O>) -> Result { ))) } } + +/// A KeySet binds together a triple of information about each Key slot on a card +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct KeySet { + signature: Option, + decryption: Option, + authentication: Option, +} + +impl From<(Option, Option, Option)> for KeySet { + fn from(tuple: (Option, Option, Option)) -> Self { + Self { + signature: tuple.0, + decryption: tuple.1, + authentication: tuple.2, + } + } +} + +impl KeySet { + pub fn signature(&self) -> Option<&T> { + self.signature.as_ref() + } + + pub fn decryption(&self) -> Option<&T> { + self.decryption.as_ref() + } + + pub fn authentication(&self) -> Option<&T> { + self.authentication.as_ref() + } +} diff --git a/openpgp-card/src/card_do/cardholder.rs b/openpgp-card/src/card_do/cardholder.rs index 4a332ac..53aa83d 100644 --- a/openpgp-card/src/card_do/cardholder.rs +++ b/openpgp-card/src/card_do/cardholder.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Cardholder Related Data (see spec pg. 22) @@ -6,8 +6,8 @@ use std::convert::TryFrom; use crate::card_do::{CardholderRelatedData, Lang, Sex}; +use crate::tags::Tags; use crate::tlv::{value::Value, Tlv}; -use crate::Tags; impl CardholderRelatedData { pub fn name(&self) -> Option<&[u8]> { diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index 54513c4..9b58ead 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Generate and import keys @@ -14,9 +14,9 @@ use crate::crypto_data::{ CardUploadableKey, EccKey, EccPub, EccType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey, RSAPub, }; -use crate::openpgp::OpenPgpTransaction; +use crate::tags::Tags; use crate::tlv::{length::tlv_encode_length, value::Value, Tlv}; -use crate::{Error, KeyType, Tag, Tags}; +use crate::{Error, KeyType, Tag, Transaction}; /// Generate asymmetric key pair on the card. /// @@ -29,7 +29,7 @@ use crate::{Error, KeyType, Tag, Tags}; /// `fp_from_pub` calculates the fingerprint for a public key data object and /// creation timestamp pub(crate) fn gen_key_with_metadata( - card_tx: &mut OpenPgpTransaction, + card_tx: &mut Transaction, fp_from_pub: fn(&PublicKeyMaterial, KeyGenerationTime, KeyType) -> Result, key_type: KeyType, algo: Option<&AlgorithmAttributes>, @@ -121,7 +121,7 @@ fn tlv_to_pubkey(tlv: &Tlv, algo: &AlgorithmAttributes) -> Result Result { log::info!("OpenPgpTransaction: generate_asymmetric_key_pair"); @@ -145,7 +145,7 @@ pub(crate) fn generate_asymmetric_key_pair( /// /// (See 7.2.14 GENERATE ASYMMETRIC KEY PAIR) pub(crate) fn public_key( - card_tx: &mut OpenPgpTransaction, + card_tx: &mut Transaction, key_type: KeyType, ) -> Result { log::info!("OpenPgpTransaction: public_key"); @@ -173,7 +173,7 @@ pub(crate) fn public_key( /// caused by checks before attempting to upload the key to the card, or by /// an error that the card reports during an attempt to upload the key). pub(crate) fn key_import( - card_tx: &mut OpenPgpTransaction, + card_tx: &mut Transaction, key: Box, key_type: KeyType, algo_info: Option, diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 18904fb..bb6872b 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Client library for @@ -14,7 +14,9 @@ //! //! This library can't directly access cards by itself. Instead, users //! need to supply a backend that implements the [`card_backend::CardBackend`] -//! / [`card_backend::CardTransaction`] traits. The companion crate +//! / [`card_backend::CardTransaction`] traits. +//! +//! The companion crate //! [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc) //! offers a backend that uses [PC/SC](https://en.wikipedia.org/wiki/PC/SC) to //! communicate with Smart Cards. @@ -23,8 +25,8 @@ //! crate offers a higher level wrapper based on the [Sequoia PGP](https://sequoia-pgp.org/) //! implementation. //! -//! See the [architecture diagram](https://gitlab.com/openpgp-card/openpgp-card#architecture) for -//! a visualization. +//! See the [architecture diagram](https://gitlab.com/openpgp-card/openpgp-card#architecture) +//! for an overview of the ecosystem around this crate. extern crate core; @@ -35,267 +37,29 @@ pub mod crypto_data; mod errors; pub(crate) mod keys; mod oid; -mod openpgp; +mod tags; mod tlv; +use std::convert::{TryFrom, TryInto}; + +use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError}; +use tags::{ShortTag, Tags}; + +use crate::algorithm::{AlgoInfo, AlgoSimple, AlgorithmAttributes}; +use crate::apdu::command::Command; +use crate::apdu::commands; +use crate::apdu::response::RawResponse; +use crate::card_do::{ + ApplicationRelatedData, CardholderRelatedData, Fingerprint, KeyGenerationTime, Lang, + PWStatusBytes, SecuritySupportTemplate, Sex, UIF, +}; +use crate::crypto_data::{CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial}; pub use crate::errors::{Error, StatusBytes}; -pub use crate::openpgp::{OpenPgp, OpenPgpTransaction}; use crate::tlv::tag::Tag; +use crate::tlv::value::Value; +use crate::tlv::Tlv; -pub(crate) const OP_APP: &[u8] = &[0xD2, 0x76, 0x00, 0x01, 0x24, 0x01]; - -/// Tags, as specified and used in the OpenPGP card 3.4.1 spec. -/// All tags in OpenPGP card are either 1 or 2 bytes long. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -#[non_exhaustive] -#[allow(dead_code)] -pub(crate) enum Tags { - // BER identifiers - OctetString, - Null, - ObjectIdentifier, - Sequence, - - // GET DATA - PrivateUse1, - PrivateUse2, - PrivateUse3, - PrivateUse4, - ApplicationIdentifier, - LoginData, - Url, - HistoricalBytes, - CardholderRelatedData, - Name, - LanguagePref, - Sex, - ApplicationRelatedData, - ExtendedLengthInformation, - GeneralFeatureManagement, - DiscretionaryDataObjects, - ExtendedCapabilities, - AlgorithmAttributesSignature, - AlgorithmAttributesDecryption, - AlgorithmAttributesAuthentication, - PWStatusBytes, - Fingerprints, - CaFingerprints, - GenerationTimes, - KeyInformation, - UifSig, - UifDec, - UifAuth, - UifAttestation, - SecuritySupportTemplate, - DigitalSignatureCounter, - CardholderCertificate, - AlgorithmAttributesAttestation, - FingerprintAttestation, - CaFingerprintAttestation, - GenerationTimeAttestation, - KdfDo, - AlgorithmInformation, - CertificateSecureMessaging, - AttestationCertificate, - - // PUT DATA (additional Tags that don't get used for GET DATA) - FingerprintSignature, - FingerprintDecryption, - FingerprintAuthentication, - CaFingerprint1, - CaFingerprint2, - CaFingerprint3, - GenerationTimeSignature, - GenerationTimeDecryption, - GenerationTimeAuthentication, - // FIXME: +D1, D2 - ResettingCode, - PsoEncDecKey, - - // OTHER - // 4.4.3.12 Private Key Template - ExtendedHeaderList, - CardholderPrivateKeyTemplate, - ConcatenatedKeyData, - CrtKeySignature, - CrtKeyConfidentiality, - CrtKeyAuthentication, - PrivateKeyDataRsaPublicExponent, - PrivateKeyDataRsaPrime1, - PrivateKeyDataRsaPrime2, - PrivateKeyDataRsaPq, - PrivateKeyDataRsaDp1, - PrivateKeyDataRsaDq1, - PrivateKeyDataRsaModulus, - PrivateKeyDataEccPrivateKey, - PrivateKeyDataEccPublicKey, - - // 7.2.14 GENERATE ASYMMETRIC KEY PAIR - PublicKey, - PublicKeyDataRsaModulus, - PublicKeyDataRsaExponent, - PublicKeyDataEccPoint, - - // 7.2.11 PSO: DECIPHER - Cipher, - ExternalPublicKey, - - // 7.2.5 SELECT DATA - GeneralReference, - TagList, -} - -impl From for Vec { - fn from(t: Tags) -> Self { - ShortTag::from(t).into() - } -} - -impl From for Tag { - fn from(t: Tags) -> Self { - ShortTag::from(t).into() - } -} - -impl From for ShortTag { - fn from(t: Tags) -> Self { - match t { - // BER identifiers https://en.wikipedia.org/wiki/X.690#BER_encoding - Tags::OctetString => [0x04].into(), - Tags::Null => [0x05].into(), - Tags::ObjectIdentifier => [0x06].into(), - Tags::Sequence => [0x30].into(), - - // GET DATA - Tags::PrivateUse1 => [0x01, 0x01].into(), - Tags::PrivateUse2 => [0x01, 0x02].into(), - Tags::PrivateUse3 => [0x01, 0x03].into(), - Tags::PrivateUse4 => [0x01, 0x04].into(), - Tags::ApplicationIdentifier => [0x4f].into(), - Tags::LoginData => [0x5e].into(), - Tags::Url => [0x5f, 0x50].into(), - Tags::HistoricalBytes => [0x5f, 0x52].into(), - Tags::CardholderRelatedData => [0x65].into(), - Tags::Name => [0x5b].into(), - Tags::LanguagePref => [0x5f, 0x2d].into(), - Tags::Sex => [0x5f, 0x35].into(), - Tags::ApplicationRelatedData => [0x6e].into(), - Tags::ExtendedLengthInformation => [0x7f, 0x66].into(), - Tags::GeneralFeatureManagement => [0x7f, 0x74].into(), - Tags::DiscretionaryDataObjects => [0x73].into(), - Tags::ExtendedCapabilities => [0xc0].into(), - Tags::AlgorithmAttributesSignature => [0xc1].into(), - Tags::AlgorithmAttributesDecryption => [0xc2].into(), - Tags::AlgorithmAttributesAuthentication => [0xc3].into(), - Tags::PWStatusBytes => [0xc4].into(), - Tags::Fingerprints => [0xc5].into(), - Tags::CaFingerprints => [0xc6].into(), - Tags::GenerationTimes => [0xcd].into(), - Tags::KeyInformation => [0xde].into(), - Tags::UifSig => [0xd6].into(), - Tags::UifDec => [0xd7].into(), - Tags::UifAuth => [0xd8].into(), - Tags::UifAttestation => [0xd9].into(), - Tags::SecuritySupportTemplate => [0x7a].into(), - Tags::DigitalSignatureCounter => [0x93].into(), - Tags::CardholderCertificate => [0x7f, 0x21].into(), - Tags::AlgorithmAttributesAttestation => [0xda].into(), - Tags::FingerprintAttestation => [0xdb].into(), - Tags::CaFingerprintAttestation => [0xdc].into(), - Tags::GenerationTimeAttestation => [0xdd].into(), - Tags::KdfDo => [0xf9].into(), - Tags::AlgorithmInformation => [0xfa].into(), - Tags::CertificateSecureMessaging => [0xfb].into(), - Tags::AttestationCertificate => [0xfc].into(), - - // PUT DATA - Tags::FingerprintSignature => [0xc7].into(), - Tags::FingerprintDecryption => [0xc8].into(), - Tags::FingerprintAuthentication => [0xc9].into(), - Tags::CaFingerprint1 => [0xca].into(), - Tags::CaFingerprint2 => [0xcb].into(), - Tags::CaFingerprint3 => [0xcc].into(), - Tags::GenerationTimeSignature => [0xce].into(), - Tags::GenerationTimeDecryption => [0xcf].into(), - Tags::GenerationTimeAuthentication => [0xd0].into(), - Tags::ResettingCode => [0xd3].into(), - Tags::PsoEncDecKey => [0xd5].into(), - - // OTHER - // 4.4.3.12 Private Key Template - Tags::ExtendedHeaderList => [0x4d].into(), - Tags::CardholderPrivateKeyTemplate => [0x7f, 0x48].into(), - Tags::ConcatenatedKeyData => [0x5f, 0x48].into(), - Tags::CrtKeySignature => [0xb6].into(), - Tags::CrtKeyConfidentiality => [0xb8].into(), - Tags::CrtKeyAuthentication => [0xa4].into(), - Tags::PrivateKeyDataRsaPublicExponent => [0x91].into(), - Tags::PrivateKeyDataRsaPrime1 => [0x92].into(), // Note: value reused! - Tags::PrivateKeyDataRsaPrime2 => [0x93].into(), - Tags::PrivateKeyDataRsaPq => [0x94].into(), - Tags::PrivateKeyDataRsaDp1 => [0x95].into(), - Tags::PrivateKeyDataRsaDq1 => [0x96].into(), - Tags::PrivateKeyDataRsaModulus => [0x97].into(), - Tags::PrivateKeyDataEccPrivateKey => [0x92].into(), // Note: value reused! - Tags::PrivateKeyDataEccPublicKey => [0x99].into(), - - // 7.2.14 GENERATE ASYMMETRIC KEY PAIR - Tags::PublicKey => [0x7f, 0x49].into(), - Tags::PublicKeyDataRsaModulus => [0x81].into(), - Tags::PublicKeyDataRsaExponent => [0x82].into(), - Tags::PublicKeyDataEccPoint => [0x86].into(), - - // 7.2.11 PSO: DECIPHER - Tags::Cipher => [0xa6].into(), - Tags::ExternalPublicKey => [0x86].into(), - - // 7.2.5 SELECT DATA - Tags::GeneralReference => [0x60].into(), - Tags::TagList => [0x5c].into(), - } - } -} - -/// A ShortTag is a Tlv tag that is guaranteed to be either 1 or 2 bytes long. -/// -/// This covers any tag that can be used in the OpenPGP card context (the spec doesn't describe how -/// longer tags might be used.) -/// -/// (The type tlv::Tag will usually/always contain 1 or 2 byte long tags, in this library. -/// But its length is not guaranteed by the type system) -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -enum ShortTag { - One(u8), - Two(u8, u8), -} - -impl From for Tag { - fn from(n: ShortTag) -> Self { - match n { - ShortTag::One(t0) => [t0].into(), - ShortTag::Two(t0, t1) => [t0, t1].into(), - } - } -} - -impl From<[u8; 1]> for ShortTag { - fn from(v: [u8; 1]) -> Self { - ShortTag::One(v[0]) - } -} -impl From<[u8; 2]> for ShortTag { - fn from(v: [u8; 2]) -> Self { - ShortTag::Two(v[0], v[1]) - } -} -impl From for Vec { - fn from(t: ShortTag) -> Self { - match t { - ShortTag::One(t0) => vec![t0], - ShortTag::Two(t0, t1) => vec![t0, t1], - } - } -} +pub(crate) const OPENPGP_APPLICATION: &[u8] = &[0xD2, 0x76, 0x00, 0x01, 0x24, 0x01]; /// Identify a Key slot on an OpenPGP card #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] @@ -304,6 +68,7 @@ pub enum KeyType { Signing, Decryption, Authentication, + Attestation, } @@ -348,34 +113,1170 @@ impl KeyType { } } -/// A KeySet binds together a triple of information about each Key on a card -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct KeySet { - signature: Option, - decryption: Option, - authentication: Option, +/// An OpenPGP card object (backed by a CardBackend implementation). +/// +/// Most users will probably want to use the `PcscCard` backend from the `card-backend-pcsc` crate. +/// +/// Users of this crate can keep a long lived [Card] object, including in long running programs. +/// All operations must be performed on a [Transaction] (which must be short lived). +pub struct Card { + card: Box, + card_caps: Option, } -impl From<(Option, Option, Option)> for KeySet { - fn from(tuple: (Option, Option, Option)) -> Self { - Self { - signature: tuple.0, - decryption: tuple.1, - authentication: tuple.2, +impl Card { + /// Turn a [CardBackend] into an [Card] object: + /// + /// The OpenPGP application is `SELECT`ed, and the card capabilities + /// of the card are retrieved from the "Application Related Data". + pub fn new(backend: B) -> Result + where + B: Into>, + { + let card: Box = backend.into(); + + let mut op = Self { + card, + card_caps: None, + }; + + let caps = { + let mut tx = op.transaction()?; + tx.select()?; + + // Init card_caps + let ard = tx.application_related_data()?; + + // Determine chaining/extended length support from card + // metadata and cache this information in the CardTransaction + // implementation (as a CardCaps) + let mut ext_support = false; + let mut chaining_support = false; + + if let Ok(hist) = ard.historical_bytes() { + if let Some(cc) = hist.card_capabilities() { + chaining_support = cc.command_chaining(); + ext_support = cc.extended_lc_le(); + } + } + + let ext_cap = ard.extended_capabilities()?; + + // Get max command/response byte sizes from card + let (mut max_cmd_bytes, max_rsp_bytes) = if let Ok(Some(eli)) = + ard.extended_length_information() + { + // In card 3.x, max lengths come from ExtendedLengthInfo + (eli.max_command_bytes(), eli.max_response_bytes()) + } else if let (Some(cmd), Some(rsp)) = (ext_cap.max_cmd_len(), ext_cap.max_resp_len()) { + // In card 2.x, max lengths come from ExtendedCapabilities + (cmd, rsp) + } else { + // Fallback: use 255 if we have no information from the card + (255, 255) + }; + + let pw_status = ard.pw_status_bytes()?; + let pw1_max = pw_status.pw1_max_len(); + let pw3_max = pw_status.pw3_max_len(); + + let caps = CardCaps::new( + ext_support, + chaining_support, + max_cmd_bytes, + max_rsp_bytes, + pw1_max, + pw3_max, + ); + + drop(tx); + + caps + }; + + log::trace!("init_card_caps to: {:x?}", caps); + op.card_caps = Some(caps); + + Ok(op) + } + + /// Get the internal `CardBackend`. + /// + /// This is useful to perform operations on the card with a different crate, + /// e.g. `yubikey-management`. + pub fn into_card(self) -> Box { + self.card + } + + /// Get an OpenPgpTransaction object. This starts a transaction on the underlying + /// CardBackend. + /// + /// Note: transactions on the Card cannot be long running, they will be reset within seconds + /// when idle. + /// + /// If the card has been reset, and `reselect_application` is set, then + /// that application will be `SELECT`ed after starting the transaction. + pub fn transaction(&mut self) -> Result { + let card_caps = &mut self.card_caps; + let tx = self.card.transaction(Some(OPENPGP_APPLICATION))?; + + if tx.was_reset() { + // FIXME + // Signal state invalidation? (PIN verification, ...) + } + + Ok(Transaction { tx, card_caps }) + } +} + +/// To perform commands on a [Card], a [Transaction] must be started. +/// This struct offers low-level access to OpenPGP card functionality. +/// +/// On backends that support transactions, operations are grouped together in transaction, while +/// an object of this type lives. +/// +/// A [Transaction] on typical underlying card subsystems must be short lived. +/// (Typically, smart cards can't be kept open for longer than a few seconds, +/// before they are automatically closed.) +pub struct Transaction<'a> { + tx: Box, + card_caps: &'a mut Option, +} + +impl<'a> Transaction<'a> { + pub(crate) fn tx(&mut self) -> &mut dyn CardTransaction { + self.tx.as_mut() + } + + pub(crate) fn send_command( + &mut self, + cmd: Command, + expect_reply: bool, + ) -> Result { + apdu::send_command(&mut *self.tx, cmd, *self.card_caps, expect_reply) + } + + // SELECT + + /// Select the OpenPGP card application + pub fn select(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: select"); + + self.send_command(commands::select_openpgp(), false)? + .try_into() + } + + // TERMINATE DF + + /// 7.2.16 TERMINATE DF + pub fn terminate_df(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: terminate_df"); + + self.send_command(commands::terminate_df(), false)?; + Ok(()) + } + + // ACTIVATE FILE + + /// 7.2.17 ACTIVATE FILE + pub fn activate_file(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: activate_file"); + + self.send_command(commands::activate_file(), false)?; + Ok(()) + } + + // --- pinpad --- + + /// Does the reader support FEATURE_VERIFY_PIN_DIRECT? + pub fn feature_pinpad_verify(&self) -> bool { + self.tx.feature_pinpad_verify() + } + + /// Does the reader support FEATURE_MODIFY_PIN_DIRECT? + pub fn feature_pinpad_modify(&self) -> bool { + self.tx.feature_pinpad_modify() + } + + // --- get data --- + + /// Get the "application related data" from the card. + /// + /// (This data should probably be cached in a higher layer. Some parts of + /// it are needed regularly, and it does not usually change during + /// normal use of a card.) + pub fn application_related_data(&mut self) -> Result { + log::info!("OpenPgpTransaction: application_related_data"); + + let resp = self.send_command(commands::application_related_data(), true)?; + let value = Value::from(resp.data()?, true)?; + + log::trace!(" ARD value: {:02x?}", value); + + Ok(ApplicationRelatedData(Tlv::new( + Tags::ApplicationRelatedData, + value, + ))) + } + + // --- login data (5e) --- + + /// Get URL (5f50) + pub fn url(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: url"); + + let resp = self.send_command(commands::url(), true)?; + + Ok(resp.data()?.to_vec()) + } + + /// Get Login Data (5e) + pub fn login_data(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: login_data"); + + let resp = self.send_command(commands::login_data(), true)?; + + Ok(resp.data()?.to_vec()) + } + + /// Get cardholder related data (65) + pub fn cardholder_related_data(&mut self) -> Result { + log::info!("OpenPgpTransaction: cardholder_related_data"); + + let crd = commands::cardholder_related_data(); + let resp = self.send_command(crd, true)?; + resp.check_ok()?; + + CardholderRelatedData::try_from(resp.data()?) + } + + /// Get security support template (7a) + pub fn security_support_template(&mut self) -> Result { + log::info!("OpenPgpTransaction: security_support_template"); + + let sst = commands::security_support_template(); + let resp = self.send_command(sst, true)?; + resp.check_ok()?; + + let tlv = Tlv::try_from(resp.data()?)?; + let res = tlv.find(Tag::from([0x93])).ok_or_else(|| { + Error::NotFound("Couldn't get SecuritySupportTemplate DO".to_string()) + })?; + + if let Value::S(data) = res { + let mut data = data.to_vec(); + if data.len() != 3 { + return Err(Error::ParseError(format!( + "Unexpected length {} for 'Digital signature counter' DO", + data.len() + ))); + } + + data.insert(0, 0); // prepend a zero + let data: [u8; 4] = data.try_into().unwrap(); + + let dsc: u32 = u32::from_be_bytes(data); + Ok(SecuritySupportTemplate { dsc }) + } else { + Err(Error::NotFound( + "Failed to process SecuritySupportTemplate".to_string(), + )) } } -} -impl KeySet { - pub fn signature(&self) -> Option<&T> { - self.signature.as_ref() + /// Get cardholder certificate (each for AUT, DEC and SIG). + /// + /// Call select_data() before calling this fn to select a particular + /// certificate (if the card supports multiple certificates). + /// + /// According to the OpenPGP card specification: + /// + /// The cardholder certificate DOs are designed to store a certificate (e. g. X.509) + /// for the keys in the card. They can be used to identify the card in a client-server + /// authentication, where specific non-OpenPGP-certificates are needed, for S-MIME and + /// other x.509 related functions. + /// + /// (See + /// for some discussion of the `cardholder certificate` OpenPGP card feature) + #[allow(dead_code)] + pub fn cardholder_certificate(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: cardholder_certificate"); + + let cmd = commands::cardholder_certificate(); + self.send_command(cmd, true)?.try_into() } - pub fn decryption(&self) -> Option<&T> { - self.decryption.as_ref() + /// Call "GET NEXT DATA" for the DO cardholder certificate. + /// + /// Cardholder certificate data for multiple slots can be read from the card by first calling + /// cardholder_certificate(), followed by up to two calls to next_cardholder_certificate(). + pub fn next_cardholder_certificate(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: next_cardholder_certificate"); + + let cmd = commands::get_next_cardholder_certificate(); + self.send_command(cmd, true)?.try_into() } - pub fn authentication(&self) -> Option<&T> { - self.authentication.as_ref() + /// Get "Algorithm Information" + pub fn algorithm_information(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: algorithm_information"); + + let resp = self.send_command(commands::algo_info(), true)?; + resp.check_ok()?; + + let ai = AlgoInfo::try_from(resp.data()?)?; + Ok(Some(ai)) + } + + /// Get "Attestation Certificate (Yubico)" + pub fn attestation_certificate(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: attestation_certificate"); + + let resp = self.send_command(commands::attestation_certificate(), true)?; + + Ok(resp.data()?.into()) + } + + /// Firmware Version (YubiKey specific (?)) + pub fn firmware_version(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: firmware_version"); + + let resp = self.send_command(commands::firmware_version(), true)?; + + Ok(resp.data()?.into()) + } + + /// Set identity (Nitrokey Start specific (?)). + /// [see: + /// + /// ] + pub fn set_identity(&mut self, id: u8) -> Result, Error> { + log::info!("OpenPgpTransaction: set_identity"); + + let resp = self.send_command(commands::set_identity(id), false); + + // Apparently it's normal to get "NotTransacted" from pcsclite when + // the identity switch was successful. + if let Err(Error::Smartcard(SmartcardError::NotTransacted)) = resp { + Ok(vec![]) + } else { + Ok(resp?.data()?.into()) + } + } + + /// SELECT DATA ("select a DO in the current template"). + /// + /// This command currently only applies to + /// [`cardholder_certificate`](Transaction::cardholder_certificate) and + /// [`set_cardholder_certificate`](Transaction::set_cardholder_certificate) + /// in OpenPGP card. + /// + /// `yk_workaround`: YubiKey 5 up to (and including) firmware version 5.4.3 need a workaround + /// for this command. Set to `true` to apply this workaround. + /// (When sending the SELECT DATA command as defined in the card spec, without enabling the + /// workaround, bad YubiKey firmware versions (<= 5.4.3) return + /// [`IncorrectParametersCommandDataField`](StatusBytes::IncorrectParametersCommandDataField)) + /// + /// (This library leaves it up to consumers to decide on a strategy for dealing with this + /// issue. Possible strategies include: + /// - asking the card for its [`firmware_version`](Transaction::firmware_version) + /// and using the workaround if version <=5.4.3 + /// - trying this command first without the workaround, then with workaround if the card + /// returns + /// [`IncorrectParametersCommandDataField`](StatusBytes::IncorrectParametersCommandDataField) + /// - for read operations: using + /// [`next_cardholder_certificate`](Transaction::next_cardholder_certificate) + /// instead of SELECT DATA) + pub fn select_data(&mut self, num: u8, tag: &[u8], yk_workaround: bool) -> Result<(), Error> { + log::info!("OpenPgpTransaction: select_data"); + + let tlv = Tlv::new( + Tags::GeneralReference, + Value::C(vec![Tlv::new(Tags::TagList, Value::S(tag.to_vec()))]), + ); + + let mut data = tlv.serialize(); + + if yk_workaround { + // Workaround for YubiKey 5. + // This hack is needed <= 5.4.3 according to ykman sources + // (see _select_certificate() in ykman/openpgp.py). + + assert!(data.len() <= 255); // catch blatant misuse: tags are 1-2 bytes long + + data.insert(0, data.len() as u8); + } + + let cmd = commands::select_data(num, data); + + // Possible response data (Control Parameter = CP) don't need to be evaluated by the + // application (See "7.2.5 SELECT DATA") + self.send_command(cmd, true)?.try_into()?; + + Ok(()) + } + + // --- optional private DOs (0101 - 0104) --- + + /// Get data from "private use" DO. + /// + /// `num` must be between 1 and 4. + pub fn private_use_do(&mut self, num: u8) -> Result, Error> { + log::info!("OpenPgpTransaction: private_use_do"); + + assert!((1..=4).contains(&num)); + + let cmd = commands::private_use_do(num); + let resp = self.send_command(cmd, true)?; + + Ok(resp.data()?.to_vec()) + } + + // ---------- + + /// Reset all state on this OpenPGP card. + /// + /// Note: the "factory reset" operation is not directly offered by the + /// card spec. It is implemented as a series of OpenPGP card commands: + /// - send 4 bad requests to verify pw1, + /// - send 4 bad requests to verify pw3, + /// - terminate_df, + /// - activate_file. + /// + /// With most cards, this sequence of operations causes the card + /// to revert to a "blank" state. + /// + /// (However, e.g. vanilla Gnuk doesn't support this functionality. + /// Gnuk needs to be built with the `--enable-factory-reset` + /// option to the `configure` script to enable this functionality). + pub fn factory_reset(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: factory_reset"); + + // send 4 bad requests to verify pw1 + // [apdu 00 20 00 81 08 40 40 40 40 40 40 40 40] + for _ in 0..4 { + log::info!(" verify_pw1_81"); + let verify = commands::verify_pw1_81([0x40; 8].to_vec()); + let resp = self.send_command(verify, false)?; + if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied + || resp.status() == StatusBytes::AuthenticationMethodBlocked + || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) + { + return Err(Error::InternalError( + "Unexpected status for reset, at pw1.".into(), + )); + } + } + + // send 4 bad requests to verify pw3 + // [apdu 00 20 00 83 08 40 40 40 40 40 40 40 40] + for _ in 0..4 { + log::info!(" verify_pw3"); + let verify = commands::verify_pw3([0x40; 8].to_vec()); + let resp = self.send_command(verify, false)?; + + if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied + || resp.status() == StatusBytes::AuthenticationMethodBlocked + || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) + { + return Err(Error::InternalError( + "Unexpected status for reset, at pw3.".into(), + )); + } + } + + self.terminate_df()?; + self.activate_file()?; + + Ok(()) + } + + // --- verify/modify --- + + /// Verify pw1 (user) for signing operation (mode 81). + /// + /// Depending on the PW1 status byte (see Extended Capabilities) this + /// access condition is only valid for one PSO:CDS command or remains + /// valid for several attempts. + pub fn verify_pw1_sign(&mut self, pin: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: verify_pw1_sign"); + + let verify = commands::verify_pw1_81(pin.to_vec()); + self.send_command(verify, false)?.try_into() + } + + /// Verify pw1 (user) for signing operation (mode 81) using a + /// pinpad on the card reader. If no usable pinpad is found, an error + /// is returned. + /// + /// Depending on the PW1 status byte (see Extended Capabilities) this + /// access condition is only valid for one PSO:CDS command or remains + /// valid for several attempts. + pub fn verify_pw1_sign_pinpad(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: verify_pw1_sign_pinpad"); + + let cc = *self.card_caps; + + let res = self.tx().pinpad_verify(PinType::Sign, &cc)?; + RawResponse::try_from(res)?.try_into() + } + + /// Check the current access of PW1 for signing (mode 81). + /// + /// If verification is not required, an empty Ok Response is returned. + /// + /// (Note: + /// - some cards don't correctly implement this feature, e.g. YubiKey 5 + /// - some cards that don't support this instruction may decrease the pin's error count, + /// eventually requiring the user to reset the pin) + pub fn check_pw1_sign(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: check_pw1_sign"); + + let verify = commands::verify_pw1_81(vec![]); + self.send_command(verify, false)?.try_into() + } + + /// Verify PW1 (user). + /// (For operations except signing, mode 82). + pub fn verify_pw1_user(&mut self, pin: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: verify_pw1_user"); + + let verify = commands::verify_pw1_82(pin.to_vec()); + self.send_command(verify, false)?.try_into() + } + + /// Verify PW1 (user) for operations except signing (mode 82), + /// using a pinpad on the card reader. If no usable pinpad is found, + /// an error is returned. + + pub fn verify_pw1_user_pinpad(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: verify_pw1_user_pinpad"); + + let cc = *self.card_caps; + + let res = self.tx().pinpad_verify(PinType::User, &cc)?; + RawResponse::try_from(res)?.try_into() + } + + /// Check the current access of PW1. + /// (For operations except signing, mode 82). + /// + /// If verification is not required, an empty Ok Response is returned. + /// + /// (Note: + /// - some cards don't correctly implement this feature, e.g. YubiKey 5 + /// - some cards that don't support this instruction may decrease the pin's error count, + /// eventually requiring the user to reset the pin) + pub fn check_pw1_user(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: check_pw1_user"); + + let verify = commands::verify_pw1_82(vec![]); + self.send_command(verify, false)?.try_into() + } + + /// Verify PW3 (admin). + pub fn verify_pw3(&mut self, pin: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: verify_pw3"); + + let verify = commands::verify_pw3(pin.to_vec()); + self.send_command(verify, false)?.try_into() + } + + /// Verify PW3 (admin) using a pinpad on the card reader. If no usable + /// pinpad is found, an error is returned. + pub fn verify_pw3_pinpad(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: verify_pw3_pinpad"); + + let cc = *self.card_caps; + + let res = self.tx().pinpad_verify(PinType::Admin, &cc)?; + RawResponse::try_from(res)?.try_into() + } + + /// Check the current access of PW3 (admin). + /// + /// If verification is not required, an empty Ok Response is returned. + /// + /// (Note: + /// - some cards don't correctly implement this feature, e.g. YubiKey 5 + /// - some cards that don't support this instruction may decrease the pin's error count, + /// eventually requiring the user to reset the pin) + pub fn check_pw3(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: check_pw3"); + + let verify = commands::verify_pw3(vec![]); + self.send_command(verify, false)?.try_into() + } + + /// Change the value of PW1 (user password). + /// + /// The current value of PW1 must be presented in `old` for authorization. + pub fn change_pw1(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: change_pw1"); + + let mut data = vec![]; + data.extend(old); + data.extend(new); + + let change = commands::change_pw1(data); + self.send_command(change, false)?.try_into() + } + + /// Change the value of PW1 (0x81) using a pinpad on the + /// card reader. If no usable pinpad is found, an error is returned. + pub fn change_pw1_pinpad(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: change_pw1_pinpad"); + + let cc = *self.card_caps; + + // Note: for change PW, only 0x81 and 0x83 are used! + // 0x82 is implicitly the same as 0x81. + let res = self.tx().pinpad_modify(PinType::Sign, &cc)?; + RawResponse::try_from(res)?.try_into() + } + + /// Change the value of PW3 (admin password). + /// + /// The current value of PW3 must be presented in `old` for authorization. + pub fn change_pw3(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: change_pw3"); + + let mut data = vec![]; + data.extend(old); + data.extend(new); + + let change = commands::change_pw3(data); + self.send_command(change, false)?.try_into() + } + + /// Change the value of PW3 (admin password) using a pinpad on the + /// card reader. If no usable pinpad is found, an error is returned. + pub fn change_pw3_pinpad(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: change_pw3_pinpad"); + + let cc = *self.card_caps; + + let res = self.tx().pinpad_modify(PinType::Admin, &cc)?; + RawResponse::try_from(res)?.try_into() + } + + /// Reset the error counter for PW1 (user password) and set a new value + /// for PW1. + /// + /// For authorization, either: + /// - PW3 must have been verified previously, + /// - secure messaging must be currently used, + /// - the resetting_code must be presented. + pub fn reset_retry_counter_pw1( + &mut self, + new_pw1: &[u8], + resetting_code: Option<&[u8]>, + ) -> Result<(), Error> { + log::info!("OpenPgpTransaction: reset_retry_counter_pw1"); + + let reset = commands::reset_retry_counter_pw1(resetting_code, new_pw1); + self.send_command(reset, false)?.try_into() + } + + // --- decrypt --- + + /// Decrypt the ciphertext in `dm`, on the card. + /// + /// (This is a wrapper around the low-level pso_decipher + /// operation, it builds the required `data` field from `dm`) + pub fn decipher(&mut self, dm: Cryptogram) -> Result, Error> { + match dm { + Cryptogram::RSA(message) => { + // "Padding indicator byte (00) for RSA" (pg. 69) + let mut data = vec![0x0]; + data.extend_from_slice(message); + + // Call the card to decrypt `data` + self.pso_decipher(data) + } + Cryptogram::ECDH(eph) => { + // "In case of ECDH the card supports a partial decrypt + // only. The input is a cipher DO with the following data:" + // A6 xx Cipher DO + // -> 7F49 xx Public Key DO + // -> 86 xx External Public Key + + // External Public Key + let epk = Tlv::new(Tags::ExternalPublicKey, Value::S(eph.to_vec())); + + // Public Key DO + let pkdo = Tlv::new(Tags::PublicKey, Value::C(vec![epk])); + + // Cipher DO + let cdo = Tlv::new(Tags::Cipher, Value::C(vec![pkdo])); + + self.pso_decipher(cdo.serialize()) + } + } + } + + /// Run decryption operation on the smartcard (low level operation) + /// (7.2.11 PSO: DECIPHER) + /// + /// (consider using the `decipher()` method if you don't want to create + /// the data field manually) + pub fn pso_decipher(&mut self, data: Vec) -> Result, Error> { + log::info!("OpenPgpTransaction: pso_decipher"); + + // The OpenPGP card is already connected and PW1 82 has been verified + let dec_cmd = commands::decryption(data); + let resp = self.send_command(dec_cmd, true)?; + resp.check_ok()?; + + Ok(resp.data().map(|d| d.to_vec())?) + } + + /// Set the key to be used for the pso_decipher and the internal_authenticate commands. + /// + /// Valid until next reset of of the card or the next call to `select` + /// The only keys that can be configured by this command are the `Decryption` and `Authentication` keys. + /// + /// The following first sets the *Authentication* key to be used for [pso_decipher](Transaction::pso_decipher) + /// and then sets the *Decryption* key to be used for [internal_authenticate](Transaction::internal_authenticate). + /// + /// ```no_run + /// # use openpgp_card::{KeyType, Transaction}; + /// # let mut tx: Transaction<'static> = panic!(); + /// tx.manage_security_environment(KeyType::Decryption, KeyType::Authentication)?; + /// tx.manage_security_environment(KeyType::Authentication, KeyType::Decryption)?; + /// # Result::<(), openpgp_card::Error>::Ok(()) + /// ``` + pub fn manage_security_environment( + &mut self, + for_operation: KeyType, + key_ref: KeyType, + ) -> Result<(), Error> { + log::info!("OpenPgpTransaction: manage_security_environment"); + + if !matches!(for_operation, KeyType::Authentication | KeyType::Decryption) + || !matches!(key_ref, KeyType::Authentication | KeyType::Decryption) + { + return Err(Error::UnsupportedAlgo("Only Decryption and Authentication keys can be manipulated by manage_security_environment".to_string())); + } + + let cmd = commands::manage_security_environment(for_operation, key_ref); + let resp = self.send_command(cmd, false)?; + resp.check_ok()?; + Ok(()) + } + + // --- sign --- + + /// Sign `hash`, on the card. + /// + /// This is a wrapper around the low-level + /// pso_compute_digital_signature operation. + /// It builds the required `data` field from `hash`. + /// + /// For RSA, this means a "DigestInfo" data structure is generated. + /// (see 7.2.10.2 DigestInfo for RSA). + /// + /// With ECC the hash data is processed as is, using + /// pso_compute_digital_signature. + pub fn signature_for_hash(&mut self, hash: Hash) -> Result, Error> { + self.pso_compute_digital_signature(digestinfo(hash)) + } + + /// Run signing operation on the smartcard (low level operation) + /// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE) + /// + /// (consider using the `signature_for_hash()` method if you don't + /// want to create the data field manually) + pub fn pso_compute_digital_signature(&mut self, data: Vec) -> Result, Error> { + log::info!("OpenPgpTransaction: pso_compute_digital_signature"); + + let cds_cmd = commands::signature(data); + + let resp = self.send_command(cds_cmd, true)?; + + Ok(resp.data().map(|d| d.to_vec())?) + } + + // --- internal authenticate --- + + /// Auth-sign `hash`, on the card. + /// + /// This is a wrapper around the low-level + /// internal_authenticate operation. + /// It builds the required `data` field from `hash`. + /// + /// For RSA, this means a "DigestInfo" data structure is generated. + /// (see 7.2.10.2 DigestInfo for RSA). + /// + /// With ECC the hash data is processed as is. + pub fn authenticate_for_hash(&mut self, hash: Hash) -> Result, Error> { + self.internal_authenticate(digestinfo(hash)) + } + + /// Run signing operation on the smartcard (low level operation) + /// (7.2.13 INTERNAL AUTHENTICATE) + /// + /// (consider using the `authenticate_for_hash()` method if you don't + /// want to create the data field manually) + pub fn internal_authenticate(&mut self, data: Vec) -> Result, Error> { + log::info!("OpenPgpTransaction: internal_authenticate"); + + let ia_cmd = commands::internal_authenticate(data); + let resp = self.send_command(ia_cmd, true)?; + + Ok(resp.data().map(|d| d.to_vec())?) + } + + // --- PUT DO --- + + /// Set data of "private use" DO. + /// + /// `num` must be between 1 and 4. + /// + /// Access condition: + /// - 1/3 need PW1 (82) + /// - 2/4 need PW3 + pub fn set_private_use_do(&mut self, num: u8, data: Vec) -> Result, Error> { + log::info!("OpenPgpTransaction: set_private_use_do"); + + assert!((1..=4).contains(&num)); + + let cmd = commands::put_private_use_do(num, data); + let resp = self.send_command(cmd, true)?; + + Ok(resp.data()?.to_vec()) + } + + pub fn set_login(&mut self, login: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_login"); + let put_login_data = commands::put_login_data(login.to_vec()); + self.send_command(put_login_data, false)?.try_into() + } + + pub fn set_name(&mut self, name: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_name"); + + let put_name = commands::put_name(name.to_vec()); + self.send_command(put_name, false)?.try_into() + } + + pub fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_lang"); + + let bytes: Vec = lang + .iter() + .flat_map(|&l| Into::>::into(l)) + .collect(); + + let put_lang = commands::put_lang(bytes); + self.send_command(put_lang, false)?.try_into() + } + + pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_sex"); + + let put_sex = commands::put_sex((&sex).into()); + self.send_command(put_sex, false)?.try_into() + } + + pub fn set_url(&mut self, url: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_url"); + + let put_url = commands::put_url(url.to_vec()); + self.send_command(put_url, false)?.try_into() + } + + /// Set cardholder certificate (for AUT, DEC or SIG). + /// + /// Call select_data() before calling this fn to select a particular + /// certificate (if the card supports multiple certificates). + pub fn set_cardholder_certificate(&mut self, data: Vec) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_cardholder_certificate"); + + let cmd = commands::put_cardholder_certificate(data); + self.send_command(cmd, false)?.try_into() + } + + /// Set algorithm attributes + /// (4.4.3.9 Algorithm Attributes) + pub fn set_algorithm_attributes( + &mut self, + key_type: KeyType, + algo: &AlgorithmAttributes, + ) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_algorithm_attributes"); + + // Command to PUT the algorithm attributes + let cmd = commands::put_data(key_type.algorithm_tag(), algo.to_data_object()?); + + self.send_command(cmd, false)?.try_into() + } + + /// Set PW Status Bytes. + /// + /// If `long` is false, send 1 byte to the card, otherwise 4. + /// According to the spec, length information should not be changed. + /// + /// So, effectively, with 'long == false' the setting `pw1_cds_multi` + /// can be changed. + /// With 'long == true', the settings `pw1_pin_block` and `pw3_pin_block` + /// can also be changed. + /// + /// (See OpenPGP card spec, pg. 28) + pub fn set_pw_status_bytes( + &mut self, + pw_status: &PWStatusBytes, + long: bool, + ) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_pw_status_bytes"); + + let data = pw_status.serialize_for_put(long); + + let cmd = commands::put_pw_status(data); + self.send_command(cmd, false)?.try_into() + } + + pub fn set_fingerprint(&mut self, fp: Fingerprint, key_type: KeyType) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_fingerprint"); + + let fp_cmd = commands::put_data(key_type.fingerprint_put_tag(), fp.as_bytes().to_vec()); + + self.send_command(fp_cmd, false)?.try_into() + } + + pub fn set_ca_fingerprint_1(&mut self, fp: Fingerprint) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_ca_fingerprint_1"); + + let fp_cmd = commands::put_data(Tags::CaFingerprint1, fp.as_bytes().to_vec()); + self.send_command(fp_cmd, false)?.try_into() + } + + pub fn set_ca_fingerprint_2(&mut self, fp: Fingerprint) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_ca_fingerprint_2"); + + let fp_cmd = commands::put_data(Tags::CaFingerprint2, fp.as_bytes().to_vec()); + self.send_command(fp_cmd, false)?.try_into() + } + + pub fn set_ca_fingerprint_3(&mut self, fp: Fingerprint) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_ca_fingerprint_3"); + + let fp_cmd = commands::put_data(Tags::CaFingerprint3, fp.as_bytes().to_vec()); + self.send_command(fp_cmd, false)?.try_into() + } + + pub fn set_creation_time( + &mut self, + time: KeyGenerationTime, + key_type: KeyType, + ) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_creation_time"); + + // Timestamp update + let time_value: Vec = time + .get() + .to_be_bytes() + .iter() + .skip_while(|&&e| e == 0) + .copied() + .collect(); + + let time_cmd = commands::put_data(key_type.timestamp_put_tag(), time_value); + + self.send_command(time_cmd, false)?.try_into() + } + + // FIXME: optional DO SM-Key-ENC + + // FIXME: optional DO SM-Key-MAC + + /// Set resetting code + /// (4.3.4 Resetting Code) + pub fn set_resetting_code(&mut self, resetting_code: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_resetting_code"); + + let cmd = commands::put_data(Tags::ResettingCode, resetting_code.to_vec()); + self.send_command(cmd, false)?.try_into() + } + + /// Set AES key for symmetric decryption/encryption operations. + /// + /// Optional DO (announced in Extended Capabilities) for + /// PSO:ENC/DEC with AES (32 bytes dec. in case of + /// AES256, 16 bytes dec. in case of AES128). + pub fn set_pso_enc_dec_key(&mut self, key: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_pso_enc_dec_key"); + + let fp_cmd = commands::put_data(Tags::PsoEncDecKey, key.to_vec()); + + self.send_command(fp_cmd, false)?.try_into() + } + + // FIXME: optional DO for PSO:ENC/DEC with AES + + /// Set UIF for PSO:CDS + pub fn set_uif_pso_cds(&mut self, uif: &UIF) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_uif_pso_cds"); + + let cmd = commands::put_data(Tags::UifSig, uif.as_bytes().to_vec()); + self.send_command(cmd, false)?.try_into() + } + + /// Set UIF for PSO:DEC + pub fn set_uif_pso_dec(&mut self, uif: &UIF) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_uif_pso_dec"); + + let cmd = commands::put_data(Tags::UifDec, uif.as_bytes().to_vec()); + self.send_command(cmd, false)?.try_into() + } + + /// Set UIF for PSO:AUT + pub fn set_uif_pso_aut(&mut self, uif: &UIF) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_uif_pso_aut"); + + let cmd = commands::put_data(Tags::UifAuth, uif.as_bytes().to_vec()); + self.send_command(cmd, false)?.try_into() + } + + /// Set UIF for Attestation key + pub fn set_uif_attestation(&mut self, uif: &UIF) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_uif_attestation"); + + let cmd = commands::put_data(Tags::UifAttestation, uif.as_bytes().to_vec()); + self.send_command(cmd, false)?.try_into() + } + + /// Generate Attestation (Yubico) + pub fn generate_attestation(&mut self, key_type: KeyType) -> Result<(), Error> { + log::info!("OpenPgpTransaction: generate_attestation"); + + let key = match key_type { + KeyType::Signing => 0x01, + KeyType::Decryption => 0x02, + KeyType::Authentication => 0x03, + _ => return Err(Error::InternalError("Unexpected KeyType".to_string())), + }; + + let cmd = commands::generate_attestation(key); + self.send_command(cmd, false)?.try_into() + } + + // FIXME: Attestation key algo attr, FP, CA-FP, creation time + + // FIXME: SM keys (ENC and MAC) with Tags D1 and D2 + + // FIXME: KDF DO + + // FIXME: certificate used with secure messaging + + // FIXME: Attestation Certificate (Yubico) + + // ----------------- + + /// Import an existing private key to the card. + /// (This implicitly sets the algorithm attributes, fingerprint and timestamp) + pub fn key_import( + &mut self, + key: Box, + key_type: KeyType, + ) -> Result<(), Error> { + // An error is ok - it's fine if a card doesn't offer a list of + // supported algorithms + let algo_info = self.algorithm_information().unwrap_or(None); + + keys::key_import(self, key, key_type, algo_info) + } + + /// Generate a key on the card. + /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) + /// + /// If the `algorithm_attributes` parameter is Some, then this algorithm will be set on + /// the card for "key_type". + /// + /// Note: `algorithm_attributes` needs to precisely specify the RSA bitsize of e (if + /// applicable), and import format, with values that the current card + /// supports. + pub fn generate_key( + &mut self, + fp_from_pub: fn( + &PublicKeyMaterial, + KeyGenerationTime, + KeyType, + ) -> Result, + key_type: KeyType, + algorithm_attributes: Option<&AlgorithmAttributes>, + ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { + keys::gen_key_with_metadata(self, fp_from_pub, key_type, algorithm_attributes) + } + + /// Generate a key on the card. + /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) + /// + /// This is a wrapper around generate_key() which allows + /// using the simplified `AlgoSimple` algorithm selector enum. + /// + /// Note: AlgoSimple doesn't specify card specific details (such as + /// bitsize of e for RSA, and import format). This function determines + /// these values based on information from the card. + pub fn generate_key_simple( + &mut self, + fp_from_pub: fn( + &PublicKeyMaterial, + KeyGenerationTime, + KeyType, + ) -> Result, + key_type: KeyType, + simple: AlgoSimple, + ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { + let ard = self.application_related_data()?; + let algo_info = if let Ok(ai) = self.algorithm_information() { + ai + } else { + None + }; + + let algo = simple.determine_algo_attributes(key_type, &ard, algo_info)?; + + Self::generate_key(self, fp_from_pub, key_type, Some(&algo)) + } + + /// Get public key material from the card. + /// + /// Note: this fn returns a set of raw public key data (not an + /// OpenPGP data structure). + /// + /// Note also that the information from the card is insufficient to + /// reconstruct a pre-existing OpenPGP public key that corresponds to + /// the private key on the card. + pub fn public_key(&mut self, key_type: KeyType) -> Result { + keys::public_key(self, key_type) + } +} + +fn digestinfo(hash: Hash) -> Vec { + match hash { + Hash::SHA256(_) | Hash::SHA384(_) | Hash::SHA512(_) => { + let tlv = Tlv::new( + Tags::Sequence, + Value::C(vec![ + Tlv::new( + Tags::Sequence, + Value::C(vec![ + Tlv::new( + Tags::ObjectIdentifier, + // unwrapping is ok, for SHA* + Value::S(hash.oid().unwrap().to_vec()), + ), + Tlv::new(Tags::Null, Value::S(vec![])), + ]), + ), + Tlv::new(Tags::OctetString, Value::S(hash.digest().to_vec())), + ]), + ); + + tlv.serialize() + } + Hash::EdDSA(d) => d.to_vec(), + Hash::ECDSA(d) => d.to_vec(), } } diff --git a/openpgp-card/src/openpgp.rs b/openpgp-card/src/openpgp.rs deleted file mode 100644 index 1d88def..0000000 --- a/openpgp-card/src/openpgp.rs +++ /dev/null @@ -1,1185 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use std::convert::{TryFrom, TryInto}; - -use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError}; - -use crate::algorithm::{AlgoInfo, AlgoSimple, AlgorithmAttributes}; -use crate::apdu::command::Command; -use crate::apdu::commands; -use crate::apdu::response::RawResponse; -use crate::card_do::{ - ApplicationRelatedData, CardholderRelatedData, Fingerprint, KeyGenerationTime, Lang, - PWStatusBytes, SecuritySupportTemplate, Sex, UIF, -}; -use crate::crypto_data::{CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial}; -use crate::tlv::{value::Value, Tlv}; -use crate::{apdu, keys, Error, KeyType, StatusBytes, Tag, Tags, OP_APP}; - -/// An OpenPGP card access object, backed by a CardBackend implementation. -/// -/// Most users will probably want to use the `PcscCard` backend from the `card-backend-pcsc` crate. -/// -/// Users of this crate can keep a long lived OpenPgp object. All operations must be performed on -/// a short lived `OpenPgpTransaction`. -pub struct OpenPgp { - card: Box, - card_caps: Option, -} - -impl OpenPgp { - /// Turn a [card_backend::CardBackend] into an [OpenPgp] object: - /// - /// The OpenPGP application is `SELECT`ed, and the card capabilities - /// of the card are retrieved from the "Application Related Data". - pub fn new(backend: B) -> Result - where - B: Into>, - { - let card: Box = backend.into(); - - let mut op = Self { - card, - card_caps: None, - }; - - let caps = { - let mut tx = op.transaction()?; - tx.select()?; - - // Init card_caps - let ard = tx.application_related_data()?; - - // Determine chaining/extended length support from card - // metadata and cache this information in the CardTransaction - // implementation (as a CardCaps) - let mut ext_support = false; - let mut chaining_support = false; - - if let Ok(hist) = ard.historical_bytes() { - if let Some(cc) = hist.card_capabilities() { - chaining_support = cc.command_chaining(); - ext_support = cc.extended_lc_le(); - } - } - - let ext_cap = ard.extended_capabilities()?; - - // Get max command/response byte sizes from card - let (max_cmd_bytes, max_rsp_bytes) = if let Ok(Some(eli)) = - ard.extended_length_information() - { - // In card 3.x, max lengths come from ExtendedLengthInfo - (eli.max_command_bytes(), eli.max_response_bytes()) - } else if let (Some(cmd), Some(rsp)) = (ext_cap.max_cmd_len(), ext_cap.max_resp_len()) { - // In card 2.x, max lengths come from ExtendedCapabilities - (cmd, rsp) - } else { - // Fallback: use 255 if we have no information from the card - (255, 255) - }; - - let pw_status = ard.pw_status_bytes()?; - let pw1_max = pw_status.pw1_max_len(); - let pw3_max = pw_status.pw3_max_len(); - - let caps = CardCaps::new( - ext_support, - chaining_support, - max_cmd_bytes, - max_rsp_bytes, - pw1_max, - pw3_max, - ); - - drop(tx); - - caps - }; - - log::trace!("init_card_caps to: {:x?}", caps); - op.card_caps = Some(caps); - - Ok(op) - } - - /// Get the internal `CardBackend`. - /// - /// This is useful to perform operations on the card with a different crate, - /// e.g. `yubikey-management`. - pub fn into_card(self) -> Box { - self.card - } - - /// Get an OpenPgpTransaction object. This starts a transaction on the underlying - /// CardBackend. - /// - /// Note: transactions on the Card cannot be long running, they will be reset within seconds - /// when idle. - /// - /// If the card has been reset, and `reselect_application` is set, then - /// that application will be `SELECT`ed after starting the transaction. - pub fn transaction(&mut self) -> Result { - let card_caps = &mut self.card_caps; - let tx = self.card.transaction(Some(OP_APP))?; - - if tx.was_reset() { - // FIXME - // Signal state invalidation? (PIN verification, ...) - } - - Ok(OpenPgpTransaction { tx, card_caps }) - } -} - -/// Low-level access to OpenPGP card functionality. -/// -/// On backends that support transactions, operations are grouped together in transaction, while -/// an object of this type lives. -/// -/// An OpenPgpTransaction on typical underlying card subsystems must be short lived. Typically, -/// smart cards can't be kept open for longer than a few seconds, before they are automatically -/// closed. -pub struct OpenPgpTransaction<'a> { - tx: Box, - card_caps: &'a mut Option, -} - -impl<'a> OpenPgpTransaction<'a> { - pub(crate) fn tx(&mut self) -> &mut dyn CardTransaction { - self.tx.as_mut() - } - - pub(crate) fn send_command( - &mut self, - cmd: Command, - expect_reply: bool, - ) -> Result { - apdu::send_command(&mut *self.tx, cmd, *self.card_caps, expect_reply) - } - - // SELECT - - /// Select the OpenPGP card application - pub fn select(&mut self) -> Result, Error> { - log::info!("OpenPgpTransaction: select"); - - self.send_command(commands::select_openpgp(), false)? - .try_into() - } - - // TERMINATE DF - - /// 7.2.16 TERMINATE DF - pub fn terminate_df(&mut self) -> Result<(), Error> { - log::info!("OpenPgpTransaction: terminate_df"); - - self.send_command(commands::terminate_df(), false)?; - Ok(()) - } - - // ACTIVATE FILE - - /// 7.2.17 ACTIVATE FILE - pub fn activate_file(&mut self) -> Result<(), Error> { - log::info!("OpenPgpTransaction: activate_file"); - - self.send_command(commands::activate_file(), false)?; - Ok(()) - } - - // --- pinpad --- - - /// Does the reader support FEATURE_VERIFY_PIN_DIRECT? - pub fn feature_pinpad_verify(&self) -> bool { - self.tx.feature_pinpad_verify() - } - - /// Does the reader support FEATURE_MODIFY_PIN_DIRECT? - pub fn feature_pinpad_modify(&self) -> bool { - self.tx.feature_pinpad_modify() - } - - // --- get data --- - - /// Get the "application related data" from the card. - /// - /// (This data should probably be cached in a higher layer. Some parts of - /// it are needed regularly, and it does not usually change during - /// normal use of a card.) - pub fn application_related_data(&mut self) -> Result { - log::info!("OpenPgpTransaction: application_related_data"); - - let resp = self.send_command(commands::application_related_data(), true)?; - let value = Value::from(resp.data()?, true)?; - - log::trace!(" ARD value: {:02x?}", value); - - Ok(ApplicationRelatedData(Tlv::new( - Tags::ApplicationRelatedData, - value, - ))) - } - - // --- login data (5e) --- - - /// Get URL (5f50) - pub fn url(&mut self) -> Result, Error> { - log::info!("OpenPgpTransaction: url"); - - let resp = self.send_command(commands::url(), true)?; - - Ok(resp.data()?.to_vec()) - } - - /// Get Login Data (5e) - pub fn login_data(&mut self) -> Result, Error> { - log::info!("OpenPgpTransaction: login_data"); - - let resp = self.send_command(commands::login_data(), true)?; - - Ok(resp.data()?.to_vec()) - } - - /// Get cardholder related data (65) - pub fn cardholder_related_data(&mut self) -> Result { - log::info!("OpenPgpTransaction: cardholder_related_data"); - - let crd = commands::cardholder_related_data(); - let resp = self.send_command(crd, true)?; - resp.check_ok()?; - - CardholderRelatedData::try_from(resp.data()?) - } - - /// Get security support template (7a) - pub fn security_support_template(&mut self) -> Result { - log::info!("OpenPgpTransaction: security_support_template"); - - let sst = commands::security_support_template(); - let resp = self.send_command(sst, true)?; - resp.check_ok()?; - - let tlv = Tlv::try_from(resp.data()?)?; - let res = tlv.find(Tag::from([0x93])).ok_or_else(|| { - Error::NotFound("Couldn't get SecuritySupportTemplate DO".to_string()) - })?; - - if let Value::S(data) = res { - let mut data = data.to_vec(); - if data.len() != 3 { - return Err(Error::ParseError(format!( - "Unexpected length {} for 'Digital signature counter' DO", - data.len() - ))); - } - - data.insert(0, 0); // prepend a zero - let data: [u8; 4] = data.try_into().unwrap(); - - let dsc: u32 = u32::from_be_bytes(data); - Ok(SecuritySupportTemplate { dsc }) - } else { - Err(Error::NotFound( - "Failed to process SecuritySupportTemplate".to_string(), - )) - } - } - - /// Get cardholder certificate (each for AUT, DEC and SIG). - /// - /// Call select_data() before calling this fn to select a particular - /// certificate (if the card supports multiple certificates). - /// - /// According to the OpenPGP card specification: - /// - /// The cardholder certificate DOs are designed to store a certificate (e. g. X.509) - /// for the keys in the card. They can be used to identify the card in a client-server - /// authentication, where specific non-OpenPGP-certificates are needed, for S-MIME and - /// other x.509 related functions. - /// - /// (See - /// for some discussion of the `cardholder certificate` OpenPGP card feature) - #[allow(dead_code)] - pub fn cardholder_certificate(&mut self) -> Result, Error> { - log::info!("OpenPgpTransaction: cardholder_certificate"); - - let cmd = commands::cardholder_certificate(); - self.send_command(cmd, true)?.try_into() - } - - /// Call "GET NEXT DATA" for the DO cardholder certificate. - /// - /// Cardholder certificate data for multiple slots can be read from the card by first calling - /// cardholder_certificate(), followed by up to two calls to next_cardholder_certificate(). - pub fn next_cardholder_certificate(&mut self) -> Result, Error> { - log::info!("OpenPgpTransaction: next_cardholder_certificate"); - - let cmd = commands::get_next_cardholder_certificate(); - self.send_command(cmd, true)?.try_into() - } - - /// Get "Algorithm Information" - pub fn algorithm_information(&mut self) -> Result, Error> { - log::info!("OpenPgpTransaction: algorithm_information"); - - let resp = self.send_command(commands::algo_info(), true)?; - resp.check_ok()?; - - let ai = AlgoInfo::try_from(resp.data()?)?; - Ok(Some(ai)) - } - - /// Get "Attestation Certificate (Yubico)" - pub fn attestation_certificate(&mut self) -> Result, Error> { - log::info!("OpenPgpTransaction: attestation_certificate"); - - let resp = self.send_command(commands::attestation_certificate(), true)?; - - Ok(resp.data()?.into()) - } - - /// Firmware Version (YubiKey specific (?)) - pub fn firmware_version(&mut self) -> Result, Error> { - log::info!("OpenPgpTransaction: firmware_version"); - - let resp = self.send_command(commands::firmware_version(), true)?; - - Ok(resp.data()?.into()) - } - - /// Set identity (Nitrokey Start specific (?)). - /// [see: - /// - /// ] - pub fn set_identity(&mut self, id: u8) -> Result, Error> { - log::info!("OpenPgpTransaction: set_identity"); - - let resp = self.send_command(commands::set_identity(id), false); - - // Apparently it's normal to get "NotTransacted" from pcsclite when - // the identity switch was successful. - if let Err(Error::Smartcard(SmartcardError::NotTransacted)) = resp { - Ok(vec![]) - } else { - Ok(resp?.data()?.into()) - } - } - - /// SELECT DATA ("select a DO in the current template"). - /// - /// This command currently only applies to - /// [`cardholder_certificate`](OpenPgpTransaction::cardholder_certificate) and - /// [`set_cardholder_certificate`](OpenPgpTransaction::set_cardholder_certificate) - /// in OpenPGP card. - /// - /// `yk_workaround`: YubiKey 5 up to (and including) firmware version 5.4.3 need a workaround - /// for this command. Set to `true` to apply this workaround. - /// (When sending the SELECT DATA command as defined in the card spec, without enabling the - /// workaround, bad YubiKey firmware versions (<= 5.4.3) return - /// [`IncorrectParametersCommandDataField`](StatusBytes::IncorrectParametersCommandDataField)) - /// - /// (This library leaves it up to consumers to decide on a strategy for dealing with this - /// issue. Possible strategies include: - /// - asking the card for its [`firmware_version`](OpenPgpTransaction::firmware_version) - /// and using the workaround if version <=5.4.3 - /// - trying this command first without the workaround, then with workaround if the card - /// returns - /// [`IncorrectParametersCommandDataField`](StatusBytes::IncorrectParametersCommandDataField) - /// - for read operations: using - /// [`next_cardholder_certificate`](OpenPgpTransaction::next_cardholder_certificate) - /// instead of SELECT DATA) - pub fn select_data(&mut self, num: u8, tag: &[u8], yk_workaround: bool) -> Result<(), Error> { - log::info!("OpenPgpTransaction: select_data"); - - let tlv = Tlv::new( - Tags::GeneralReference, - Value::C(vec![Tlv::new(Tags::TagList, Value::S(tag.to_vec()))]), - ); - - let mut data = tlv.serialize(); - - if yk_workaround { - // Workaround for YubiKey 5. - // This hack is needed <= 5.4.3 according to ykman sources - // (see _select_certificate() in ykman/openpgp.py). - - assert!(data.len() <= 255); // catch blatant misuse: tags are 1-2 bytes long - - data.insert(0, data.len() as u8); - } - - let cmd = commands::select_data(num, data); - - // Possible response data (Control Parameter = CP) don't need to be evaluated by the - // application (See "7.2.5 SELECT DATA") - self.send_command(cmd, true)?.try_into()?; - - Ok(()) - } - - // --- optional private DOs (0101 - 0104) --- - - /// Get data from "private use" DO. - /// - /// `num` must be between 1 and 4. - pub fn private_use_do(&mut self, num: u8) -> Result, Error> { - log::info!("OpenPgpTransaction: private_use_do"); - - assert!((1..=4).contains(&num)); - - let cmd = commands::private_use_do(num); - let resp = self.send_command(cmd, true)?; - - Ok(resp.data()?.to_vec()) - } - - // ---------- - - /// Reset all state on this OpenPGP card. - /// - /// Note: the "factory reset" operation is not directly offered by the - /// card spec. It is implemented as a series of OpenPGP card commands: - /// - send 4 bad requests to verify pw1, - /// - send 4 bad requests to verify pw3, - /// - terminate_df, - /// - activate_file. - /// - /// With most cards, this sequence of operations causes the card - /// to revert to a "blank" state. - /// - /// (However, e.g. vanilla Gnuk doesn't support this functionality. - /// Gnuk needs to be built with the `--enable-factory-reset` - /// option to the `configure` script to enable this functionality). - pub fn factory_reset(&mut self) -> Result<(), Error> { - log::info!("OpenPgpTransaction: factory_reset"); - - // send 4 bad requests to verify pw1 - // [apdu 00 20 00 81 08 40 40 40 40 40 40 40 40] - for _ in 0..4 { - log::info!(" verify_pw1_81"); - let verify = commands::verify_pw1_81([0x40; 8].to_vec()); - let resp = self.send_command(verify, false)?; - if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied - || resp.status() == StatusBytes::AuthenticationMethodBlocked - || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) - { - return Err(Error::InternalError( - "Unexpected status for reset, at pw1.".into(), - )); - } - } - - // send 4 bad requests to verify pw3 - // [apdu 00 20 00 83 08 40 40 40 40 40 40 40 40] - for _ in 0..4 { - log::info!(" verify_pw3"); - let verify = commands::verify_pw3([0x40; 8].to_vec()); - let resp = self.send_command(verify, false)?; - - if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied - || resp.status() == StatusBytes::AuthenticationMethodBlocked - || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) - { - return Err(Error::InternalError( - "Unexpected status for reset, at pw3.".into(), - )); - } - } - - self.terminate_df()?; - self.activate_file()?; - - Ok(()) - } - - // --- verify/modify --- - - /// Verify pw1 (user) for signing operation (mode 81). - /// - /// Depending on the PW1 status byte (see Extended Capabilities) this - /// access condition is only valid for one PSO:CDS command or remains - /// valid for several attempts. - pub fn verify_pw1_sign(&mut self, pin: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: verify_pw1_sign"); - - let verify = commands::verify_pw1_81(pin.to_vec()); - self.send_command(verify, false)?.try_into() - } - - /// Verify pw1 (user) for signing operation (mode 81) using a - /// pinpad on the card reader. If no usable pinpad is found, an error - /// is returned. - /// - /// Depending on the PW1 status byte (see Extended Capabilities) this - /// access condition is only valid for one PSO:CDS command or remains - /// valid for several attempts. - pub fn verify_pw1_sign_pinpad(&mut self) -> Result<(), Error> { - log::info!("OpenPgpTransaction: verify_pw1_sign_pinpad"); - - let cc = *self.card_caps; - - let res = self.tx().pinpad_verify(PinType::Sign, &cc)?; - RawResponse::try_from(res)?.try_into() - } - - /// Check the current access of PW1 for signing (mode 81). - /// - /// If verification is not required, an empty Ok Response is returned. - /// - /// (Note: - /// - some cards don't correctly implement this feature, e.g. YubiKey 5 - /// - some cards that don't support this instruction may decrease the pin's error count, - /// eventually requiring the user to reset the pin) - pub fn check_pw1_sign(&mut self) -> Result<(), Error> { - log::info!("OpenPgpTransaction: check_pw1_sign"); - - let verify = commands::verify_pw1_81(vec![]); - self.send_command(verify, false)?.try_into() - } - - /// Verify PW1 (user). - /// (For operations except signing, mode 82). - pub fn verify_pw1_user(&mut self, pin: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: verify_pw1_user"); - - let verify = commands::verify_pw1_82(pin.to_vec()); - self.send_command(verify, false)?.try_into() - } - - /// Verify PW1 (user) for operations except signing (mode 82), - /// using a pinpad on the card reader. If no usable pinpad is found, - /// an error is returned. - - pub fn verify_pw1_user_pinpad(&mut self) -> Result<(), Error> { - log::info!("OpenPgpTransaction: verify_pw1_user_pinpad"); - - let cc = *self.card_caps; - - let res = self.tx().pinpad_verify(PinType::User, &cc)?; - RawResponse::try_from(res)?.try_into() - } - - /// Check the current access of PW1. - /// (For operations except signing, mode 82). - /// - /// If verification is not required, an empty Ok Response is returned. - /// - /// (Note: - /// - some cards don't correctly implement this feature, e.g. YubiKey 5 - /// - some cards that don't support this instruction may decrease the pin's error count, - /// eventually requiring the user to reset the pin) - pub fn check_pw1_user(&mut self) -> Result<(), Error> { - log::info!("OpenPgpTransaction: check_pw1_user"); - - let verify = commands::verify_pw1_82(vec![]); - self.send_command(verify, false)?.try_into() - } - - /// Verify PW3 (admin). - pub fn verify_pw3(&mut self, pin: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: verify_pw3"); - - let verify = commands::verify_pw3(pin.to_vec()); - self.send_command(verify, false)?.try_into() - } - - /// Verify PW3 (admin) using a pinpad on the card reader. If no usable - /// pinpad is found, an error is returned. - pub fn verify_pw3_pinpad(&mut self) -> Result<(), Error> { - log::info!("OpenPgpTransaction: verify_pw3_pinpad"); - - let cc = *self.card_caps; - - let res = self.tx().pinpad_verify(PinType::Admin, &cc)?; - RawResponse::try_from(res)?.try_into() - } - - /// Check the current access of PW3 (admin). - /// - /// If verification is not required, an empty Ok Response is returned. - /// - /// (Note: - /// - some cards don't correctly implement this feature, e.g. YubiKey 5 - /// - some cards that don't support this instruction may decrease the pin's error count, - /// eventually requiring the user to reset the pin) - pub fn check_pw3(&mut self) -> Result<(), Error> { - log::info!("OpenPgpTransaction: check_pw3"); - - let verify = commands::verify_pw3(vec![]); - self.send_command(verify, false)?.try_into() - } - - /// Change the value of PW1 (user password). - /// - /// The current value of PW1 must be presented in `old` for authorization. - pub fn change_pw1(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: change_pw1"); - - let mut data = vec![]; - data.extend(old); - data.extend(new); - - let change = commands::change_pw1(data); - self.send_command(change, false)?.try_into() - } - - /// Change the value of PW1 (0x81) using a pinpad on the - /// card reader. If no usable pinpad is found, an error is returned. - pub fn change_pw1_pinpad(&mut self) -> Result<(), Error> { - log::info!("OpenPgpTransaction: change_pw1_pinpad"); - - let cc = *self.card_caps; - - // Note: for change PW, only 0x81 and 0x83 are used! - // 0x82 is implicitly the same as 0x81. - let res = self.tx().pinpad_modify(PinType::Sign, &cc)?; - RawResponse::try_from(res)?.try_into() - } - - /// Change the value of PW3 (admin password). - /// - /// The current value of PW3 must be presented in `old` for authorization. - pub fn change_pw3(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: change_pw3"); - - let mut data = vec![]; - data.extend(old); - data.extend(new); - - let change = commands::change_pw3(data); - self.send_command(change, false)?.try_into() - } - - /// Change the value of PW3 (admin password) using a pinpad on the - /// card reader. If no usable pinpad is found, an error is returned. - pub fn change_pw3_pinpad(&mut self) -> Result<(), Error> { - log::info!("OpenPgpTransaction: change_pw3_pinpad"); - - let cc = *self.card_caps; - - let res = self.tx().pinpad_modify(PinType::Admin, &cc)?; - RawResponse::try_from(res)?.try_into() - } - - /// Reset the error counter for PW1 (user password) and set a new value - /// for PW1. - /// - /// For authorization, either: - /// - PW3 must have been verified previously, - /// - secure messaging must be currently used, - /// - the resetting_code must be presented. - pub fn reset_retry_counter_pw1( - &mut self, - new_pw1: &[u8], - resetting_code: Option<&[u8]>, - ) -> Result<(), Error> { - log::info!("OpenPgpTransaction: reset_retry_counter_pw1"); - - let reset = commands::reset_retry_counter_pw1(resetting_code, new_pw1); - self.send_command(reset, false)?.try_into() - } - - // --- decrypt --- - - /// Decrypt the ciphertext in `dm`, on the card. - /// - /// (This is a wrapper around the low-level pso_decipher - /// operation, it builds the required `data` field from `dm`) - pub fn decipher(&mut self, dm: Cryptogram) -> Result, Error> { - match dm { - Cryptogram::RSA(message) => { - // "Padding indicator byte (00) for RSA" (pg. 69) - let mut data = vec![0x0]; - data.extend_from_slice(message); - - // Call the card to decrypt `data` - self.pso_decipher(data) - } - Cryptogram::ECDH(eph) => { - // "In case of ECDH the card supports a partial decrypt - // only. The input is a cipher DO with the following data:" - // A6 xx Cipher DO - // -> 7F49 xx Public Key DO - // -> 86 xx External Public Key - - // External Public Key - let epk = Tlv::new(Tags::ExternalPublicKey, Value::S(eph.to_vec())); - - // Public Key DO - let pkdo = Tlv::new(Tags::PublicKey, Value::C(vec![epk])); - - // Cipher DO - let cdo = Tlv::new(Tags::Cipher, Value::C(vec![pkdo])); - - self.pso_decipher(cdo.serialize()) - } - } - } - - /// Run decryption operation on the smartcard (low level operation) - /// (7.2.11 PSO: DECIPHER) - /// - /// (consider using the `decipher()` method if you don't want to create - /// the data field manually) - pub fn pso_decipher(&mut self, data: Vec) -> Result, Error> { - log::info!("OpenPgpTransaction: pso_decipher"); - - // The OpenPGP card is already connected and PW1 82 has been verified - let dec_cmd = commands::decryption(data); - let resp = self.send_command(dec_cmd, true)?; - resp.check_ok()?; - - Ok(resp.data().map(|d| d.to_vec())?) - } - - /// Set the key to be used for the pso_decipher and the internal_authenticate commands. - /// - /// Valid until next reset of of the card or the next call to `select` - /// The only keys that can be configured by this command are the `Decryption` and `Authentication` keys. - /// - /// The following first sets the *Authentication* key to be used for [pso_decipher](OpenPgpTransaction::pso_decipher) - /// and then sets the *Decryption* key to be used for [internal_authenticate](OpenPgpTransaction::internal_authenticate). - /// - /// ```no_run - /// # use openpgp_card::{KeyType, OpenPgpTransaction}; - /// # let mut tx: OpenPgpTransaction<'static> = panic!(); - /// tx.manage_security_environment(KeyType::Decryption, KeyType::Authentication)?; - /// tx.manage_security_environment(KeyType::Authentication, KeyType::Decryption)?; - /// # Result::<(), openpgp_card::Error>::Ok(()) - /// ``` - pub fn manage_security_environment( - &mut self, - for_operation: KeyType, - key_ref: KeyType, - ) -> Result<(), Error> { - log::info!("OpenPgpTransaction: manage_security_environment"); - - if !matches!(for_operation, KeyType::Authentication | KeyType::Decryption) - || !matches!(key_ref, KeyType::Authentication | KeyType::Decryption) - { - return Err(Error::UnsupportedAlgo("Only Decryption and Authentication keys can be manipulated by manage_security_environment".to_string())); - } - - let cmd = commands::manage_security_environment(for_operation, key_ref); - let resp = self.send_command(cmd, false)?; - resp.check_ok()?; - Ok(()) - } - - // --- sign --- - - /// Sign `hash`, on the card. - /// - /// This is a wrapper around the low-level - /// pso_compute_digital_signature operation. - /// It builds the required `data` field from `hash`. - /// - /// For RSA, this means a "DigestInfo" data structure is generated. - /// (see 7.2.10.2 DigestInfo for RSA). - /// - /// With ECC the hash data is processed as is, using - /// pso_compute_digital_signature. - pub fn signature_for_hash(&mut self, hash: Hash) -> Result, Error> { - self.pso_compute_digital_signature(digestinfo(hash)) - } - - /// Run signing operation on the smartcard (low level operation) - /// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE) - /// - /// (consider using the `signature_for_hash()` method if you don't - /// want to create the data field manually) - pub fn pso_compute_digital_signature(&mut self, data: Vec) -> Result, Error> { - log::info!("OpenPgpTransaction: pso_compute_digital_signature"); - - let cds_cmd = commands::signature(data); - - let resp = self.send_command(cds_cmd, true)?; - - Ok(resp.data().map(|d| d.to_vec())?) - } - - // --- internal authenticate --- - - /// Auth-sign `hash`, on the card. - /// - /// This is a wrapper around the low-level - /// internal_authenticate operation. - /// It builds the required `data` field from `hash`. - /// - /// For RSA, this means a "DigestInfo" data structure is generated. - /// (see 7.2.10.2 DigestInfo for RSA). - /// - /// With ECC the hash data is processed as is. - pub fn authenticate_for_hash(&mut self, hash: Hash) -> Result, Error> { - self.internal_authenticate(digestinfo(hash)) - } - - /// Run signing operation on the smartcard (low level operation) - /// (7.2.13 INTERNAL AUTHENTICATE) - /// - /// (consider using the `authenticate_for_hash()` method if you don't - /// want to create the data field manually) - pub fn internal_authenticate(&mut self, data: Vec) -> Result, Error> { - log::info!("OpenPgpTransaction: internal_authenticate"); - - let ia_cmd = commands::internal_authenticate(data); - let resp = self.send_command(ia_cmd, true)?; - - Ok(resp.data().map(|d| d.to_vec())?) - } - - // --- PUT DO --- - - /// Set data of "private use" DO. - /// - /// `num` must be between 1 and 4. - /// - /// Access condition: - /// - 1/3 need PW1 (82) - /// - 2/4 need PW3 - pub fn set_private_use_do(&mut self, num: u8, data: Vec) -> Result, Error> { - log::info!("OpenPgpTransaction: set_private_use_do"); - - assert!((1..=4).contains(&num)); - - let cmd = commands::put_private_use_do(num, data); - let resp = self.send_command(cmd, true)?; - - Ok(resp.data()?.to_vec()) - } - - pub fn set_login(&mut self, login: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_login"); - let put_login_data = commands::put_login_data(login.to_vec()); - self.send_command(put_login_data, false)?.try_into() - } - - pub fn set_name(&mut self, name: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_name"); - - let put_name = commands::put_name(name.to_vec()); - self.send_command(put_name, false)?.try_into() - } - - pub fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_lang"); - - let bytes: Vec = lang - .iter() - .flat_map(|&l| Into::>::into(l)) - .collect(); - - let put_lang = commands::put_lang(bytes); - self.send_command(put_lang, false)?.try_into() - } - - pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_sex"); - - let put_sex = commands::put_sex((&sex).into()); - self.send_command(put_sex, false)?.try_into() - } - - pub fn set_url(&mut self, url: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_url"); - - let put_url = commands::put_url(url.to_vec()); - self.send_command(put_url, false)?.try_into() - } - - /// Set cardholder certificate (for AUT, DEC or SIG). - /// - /// Call select_data() before calling this fn to select a particular - /// certificate (if the card supports multiple certificates). - pub fn set_cardholder_certificate(&mut self, data: Vec) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_cardholder_certificate"); - - let cmd = commands::put_cardholder_certificate(data); - self.send_command(cmd, false)?.try_into() - } - - /// Set algorithm attributes - /// (4.4.3.9 Algorithm Attributes) - pub fn set_algorithm_attributes( - &mut self, - key_type: KeyType, - algo: &AlgorithmAttributes, - ) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_algorithm_attributes"); - - // Command to PUT the algorithm attributes - let cmd = commands::put_data(key_type.algorithm_tag(), algo.to_data_object()?); - - self.send_command(cmd, false)?.try_into() - } - - /// Set PW Status Bytes. - /// - /// If `long` is false, send 1 byte to the card, otherwise 4. - /// According to the spec, length information should not be changed. - /// - /// So, effectively, with 'long == false' the setting `pw1_cds_multi` - /// can be changed. - /// With 'long == true', the settings `pw1_pin_block` and `pw3_pin_block` - /// can also be changed. - /// - /// (See OpenPGP card spec, pg. 28) - pub fn set_pw_status_bytes( - &mut self, - pw_status: &PWStatusBytes, - long: bool, - ) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_pw_status_bytes"); - - let data = pw_status.serialize_for_put(long); - - let cmd = commands::put_pw_status(data); - self.send_command(cmd, false)?.try_into() - } - - pub fn set_fingerprint(&mut self, fp: Fingerprint, key_type: KeyType) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_fingerprint"); - - let fp_cmd = commands::put_data(key_type.fingerprint_put_tag(), fp.as_bytes().to_vec()); - - self.send_command(fp_cmd, false)?.try_into() - } - - pub fn set_ca_fingerprint_1(&mut self, fp: Fingerprint) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_ca_fingerprint_1"); - - let fp_cmd = commands::put_data(Tags::CaFingerprint1, fp.as_bytes().to_vec()); - self.send_command(fp_cmd, false)?.try_into() - } - - pub fn set_ca_fingerprint_2(&mut self, fp: Fingerprint) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_ca_fingerprint_2"); - - let fp_cmd = commands::put_data(Tags::CaFingerprint2, fp.as_bytes().to_vec()); - self.send_command(fp_cmd, false)?.try_into() - } - - pub fn set_ca_fingerprint_3(&mut self, fp: Fingerprint) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_ca_fingerprint_3"); - - let fp_cmd = commands::put_data(Tags::CaFingerprint3, fp.as_bytes().to_vec()); - self.send_command(fp_cmd, false)?.try_into() - } - - pub fn set_creation_time( - &mut self, - time: KeyGenerationTime, - key_type: KeyType, - ) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_creation_time"); - - // Timestamp update - let time_value: Vec = time - .get() - .to_be_bytes() - .iter() - .skip_while(|&&e| e == 0) - .copied() - .collect(); - - let time_cmd = commands::put_data(key_type.timestamp_put_tag(), time_value); - - self.send_command(time_cmd, false)?.try_into() - } - - // FIXME: optional DO SM-Key-ENC - - // FIXME: optional DO SM-Key-MAC - - /// Set resetting code - /// (4.3.4 Resetting Code) - pub fn set_resetting_code(&mut self, resetting_code: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_resetting_code"); - - let cmd = commands::put_data(Tags::ResettingCode, resetting_code.to_vec()); - self.send_command(cmd, false)?.try_into() - } - - /// Set AES key for symmetric decryption/encryption operations. - /// - /// Optional DO (announced in Extended Capabilities) for - /// PSO:ENC/DEC with AES (32 bytes dec. in case of - /// AES256, 16 bytes dec. in case of AES128). - pub fn set_pso_enc_dec_key(&mut self, key: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_pso_enc_dec_key"); - - let fp_cmd = commands::put_data(Tags::PsoEncDecKey, key.to_vec()); - - self.send_command(fp_cmd, false)?.try_into() - } - - // FIXME: optional DO for PSO:ENC/DEC with AES - - /// Set UIF for PSO:CDS - pub fn set_uif_pso_cds(&mut self, uif: &UIF) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_uif_pso_cds"); - - let cmd = commands::put_data(Tags::UifSig, uif.as_bytes().to_vec()); - self.send_command(cmd, false)?.try_into() - } - - /// Set UIF for PSO:DEC - pub fn set_uif_pso_dec(&mut self, uif: &UIF) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_uif_pso_dec"); - - let cmd = commands::put_data(Tags::UifDec, uif.as_bytes().to_vec()); - self.send_command(cmd, false)?.try_into() - } - - /// Set UIF for PSO:AUT - pub fn set_uif_pso_aut(&mut self, uif: &UIF) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_uif_pso_aut"); - - let cmd = commands::put_data(Tags::UifAuth, uif.as_bytes().to_vec()); - self.send_command(cmd, false)?.try_into() - } - - /// Set UIF for Attestation key - pub fn set_uif_attestation(&mut self, uif: &UIF) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_uif_attestation"); - - let cmd = commands::put_data(Tags::UifAttestation, uif.as_bytes().to_vec()); - self.send_command(cmd, false)?.try_into() - } - - /// Generate Attestation (Yubico) - pub fn generate_attestation(&mut self, key_type: KeyType) -> Result<(), Error> { - log::info!("OpenPgpTransaction: generate_attestation"); - - let key = match key_type { - KeyType::Signing => 0x01, - KeyType::Decryption => 0x02, - KeyType::Authentication => 0x03, - _ => return Err(Error::InternalError("Unexpected KeyType".to_string())), - }; - - let cmd = commands::generate_attestation(key); - self.send_command(cmd, false)?.try_into() - } - - // FIXME: Attestation key algo attr, FP, CA-FP, creation time - - // FIXME: SM keys (ENC and MAC) with Tags D1 and D2 - - // FIXME: KDF DO - - // FIXME: certificate used with secure messaging - - // FIXME: Attestation Certificate (Yubico) - - // ----------------- - - /// Import an existing private key to the card. - /// (This implicitly sets the algorithm attributes, fingerprint and timestamp) - pub fn key_import( - &mut self, - key: Box, - key_type: KeyType, - ) -> Result<(), Error> { - // An error is ok - it's fine if a card doesn't offer a list of - // supported algorithms - let algo_info = self.algorithm_information().unwrap_or(None); - - keys::key_import(self, key, key_type, algo_info) - } - - /// Generate a key on the card. - /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) - /// - /// If the `algorithm_attributes` parameter is Some, then this algorithm will be set on - /// the card for "key_type". - /// - /// Note: `algorithm_attributes` needs to precisely specify the RSA bitsize of e (if - /// applicable), and import format, with values that the current card - /// supports. - pub fn generate_key( - &mut self, - fp_from_pub: fn( - &PublicKeyMaterial, - KeyGenerationTime, - KeyType, - ) -> Result, - key_type: KeyType, - algorithm_attributes: Option<&AlgorithmAttributes>, - ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { - keys::gen_key_with_metadata(self, fp_from_pub, key_type, algorithm_attributes) - } - - /// Generate a key on the card. - /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) - /// - /// This is a wrapper around generate_key() which allows - /// using the simplified `AlgoSimple` algorithm selector enum. - /// - /// Note: AlgoSimple doesn't specify card specific details (such as - /// bitsize of e for RSA, and import format). This function determines - /// these values based on information from the card. - pub fn generate_key_simple( - &mut self, - fp_from_pub: fn( - &PublicKeyMaterial, - KeyGenerationTime, - KeyType, - ) -> Result, - key_type: KeyType, - simple: AlgoSimple, - ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { - let ard = self.application_related_data()?; - let algo_info = if let Ok(ai) = self.algorithm_information() { - ai - } else { - None - }; - - let algo = simple.determine_algo_attributes(key_type, &ard, algo_info)?; - - Self::generate_key(self, fp_from_pub, key_type, Some(&algo)) - } - - /// Get public key material from the card. - /// - /// Note: this fn returns a set of raw public key data (not an - /// OpenPGP data structure). - /// - /// Note also that the information from the card is insufficient to - /// reconstruct a pre-existing OpenPGP public key that corresponds to - /// the private key on the card. - pub fn public_key(&mut self, key_type: KeyType) -> Result { - keys::public_key(self, key_type) - } -} - -fn digestinfo(hash: Hash) -> Vec { - match hash { - Hash::SHA256(_) | Hash::SHA384(_) | Hash::SHA512(_) => { - let tlv = Tlv::new( - Tags::Sequence, - Value::C(vec![ - Tlv::new( - Tags::Sequence, - Value::C(vec![ - Tlv::new( - Tags::ObjectIdentifier, - // unwrapping is ok, for SHA* - Value::S(hash.oid().unwrap().to_vec()), - ), - Tlv::new(Tags::Null, Value::S(vec![])), - ]), - ), - Tlv::new(Tags::OctetString, Value::S(hash.digest().to_vec())), - ]), - ); - - tlv.serialize() - } - Hash::EdDSA(d) => d.to_vec(), - Hash::ECDSA(d) => d.to_vec(), - } -} diff --git a/openpgp-card/src/tags.rs b/openpgp-card/src/tags.rs new file mode 100644 index 0000000..bd8b1af --- /dev/null +++ b/openpgp-card/src/tags.rs @@ -0,0 +1,259 @@ +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::tlv::tag::Tag; + +/// Tags, as specified and used in the OpenPGP card 3.4.1 spec. +/// All tags in OpenPGP card are either 1 or 2 bytes long. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[non_exhaustive] +#[allow(dead_code)] +pub(crate) enum Tags { + // BER identifiers + OctetString, + Null, + ObjectIdentifier, + Sequence, + + // GET DATA + PrivateUse1, + PrivateUse2, + PrivateUse3, + PrivateUse4, + ApplicationIdentifier, + LoginData, + Url, + HistoricalBytes, + CardholderRelatedData, + Name, + LanguagePref, + Sex, + ApplicationRelatedData, + ExtendedLengthInformation, + GeneralFeatureManagement, + DiscretionaryDataObjects, + ExtendedCapabilities, + AlgorithmAttributesSignature, + AlgorithmAttributesDecryption, + AlgorithmAttributesAuthentication, + PWStatusBytes, + Fingerprints, + CaFingerprints, + GenerationTimes, + KeyInformation, + UifSig, + UifDec, + UifAuth, + UifAttestation, + SecuritySupportTemplate, + DigitalSignatureCounter, + CardholderCertificate, + AlgorithmAttributesAttestation, + FingerprintAttestation, + CaFingerprintAttestation, + GenerationTimeAttestation, + KdfDo, + AlgorithmInformation, + CertificateSecureMessaging, + AttestationCertificate, + + // PUT DATA (additional Tags that don't get used for GET DATA) + FingerprintSignature, + FingerprintDecryption, + FingerprintAuthentication, + CaFingerprint1, + CaFingerprint2, + CaFingerprint3, + GenerationTimeSignature, + GenerationTimeDecryption, + GenerationTimeAuthentication, + // FIXME: +D1, D2 + ResettingCode, + PsoEncDecKey, + + // OTHER + // 4.4.3.12 Private Key Template + ExtendedHeaderList, + CardholderPrivateKeyTemplate, + ConcatenatedKeyData, + CrtKeySignature, + CrtKeyConfidentiality, + CrtKeyAuthentication, + PrivateKeyDataRsaPublicExponent, + PrivateKeyDataRsaPrime1, + PrivateKeyDataRsaPrime2, + PrivateKeyDataRsaPq, + PrivateKeyDataRsaDp1, + PrivateKeyDataRsaDq1, + PrivateKeyDataRsaModulus, + PrivateKeyDataEccPrivateKey, + PrivateKeyDataEccPublicKey, + + // 7.2.14 GENERATE ASYMMETRIC KEY PAIR + PublicKey, + PublicKeyDataRsaModulus, + PublicKeyDataRsaExponent, + PublicKeyDataEccPoint, + + // 7.2.11 PSO: DECIPHER + Cipher, + ExternalPublicKey, + + // 7.2.5 SELECT DATA + GeneralReference, + TagList, +} + +impl From for Vec { + fn from(t: Tags) -> Self { + ShortTag::from(t).into() + } +} + +impl From for Tag { + fn from(t: Tags) -> Self { + ShortTag::from(t).into() + } +} + +impl From for ShortTag { + fn from(t: Tags) -> Self { + match t { + // BER identifiers https://en.wikipedia.org/wiki/X.690#BER_encoding + Tags::OctetString => [0x04].into(), + Tags::Null => [0x05].into(), + Tags::ObjectIdentifier => [0x06].into(), + Tags::Sequence => [0x30].into(), + + // GET DATA + Tags::PrivateUse1 => [0x01, 0x01].into(), + Tags::PrivateUse2 => [0x01, 0x02].into(), + Tags::PrivateUse3 => [0x01, 0x03].into(), + Tags::PrivateUse4 => [0x01, 0x04].into(), + Tags::ApplicationIdentifier => [0x4f].into(), + Tags::LoginData => [0x5e].into(), + Tags::Url => [0x5f, 0x50].into(), + Tags::HistoricalBytes => [0x5f, 0x52].into(), + Tags::CardholderRelatedData => [0x65].into(), + Tags::Name => [0x5b].into(), + Tags::LanguagePref => [0x5f, 0x2d].into(), + Tags::Sex => [0x5f, 0x35].into(), + Tags::ApplicationRelatedData => [0x6e].into(), + Tags::ExtendedLengthInformation => [0x7f, 0x66].into(), + Tags::GeneralFeatureManagement => [0x7f, 0x74].into(), + Tags::DiscretionaryDataObjects => [0x73].into(), + Tags::ExtendedCapabilities => [0xc0].into(), + Tags::AlgorithmAttributesSignature => [0xc1].into(), + Tags::AlgorithmAttributesDecryption => [0xc2].into(), + Tags::AlgorithmAttributesAuthentication => [0xc3].into(), + Tags::PWStatusBytes => [0xc4].into(), + Tags::Fingerprints => [0xc5].into(), + Tags::CaFingerprints => [0xc6].into(), + Tags::GenerationTimes => [0xcd].into(), + Tags::KeyInformation => [0xde].into(), + Tags::UifSig => [0xd6].into(), + Tags::UifDec => [0xd7].into(), + Tags::UifAuth => [0xd8].into(), + Tags::UifAttestation => [0xd9].into(), + Tags::SecuritySupportTemplate => [0x7a].into(), + Tags::DigitalSignatureCounter => [0x93].into(), + Tags::CardholderCertificate => [0x7f, 0x21].into(), + Tags::AlgorithmAttributesAttestation => [0xda].into(), + Tags::FingerprintAttestation => [0xdb].into(), + Tags::CaFingerprintAttestation => [0xdc].into(), + Tags::GenerationTimeAttestation => [0xdd].into(), + Tags::KdfDo => [0xf9].into(), + Tags::AlgorithmInformation => [0xfa].into(), + Tags::CertificateSecureMessaging => [0xfb].into(), + Tags::AttestationCertificate => [0xfc].into(), + + // PUT DATA + Tags::FingerprintSignature => [0xc7].into(), + Tags::FingerprintDecryption => [0xc8].into(), + Tags::FingerprintAuthentication => [0xc9].into(), + Tags::CaFingerprint1 => [0xca].into(), + Tags::CaFingerprint2 => [0xcb].into(), + Tags::CaFingerprint3 => [0xcc].into(), + Tags::GenerationTimeSignature => [0xce].into(), + Tags::GenerationTimeDecryption => [0xcf].into(), + Tags::GenerationTimeAuthentication => [0xd0].into(), + Tags::ResettingCode => [0xd3].into(), + Tags::PsoEncDecKey => [0xd5].into(), + + // OTHER + // 4.4.3.12 Private Key Template + Tags::ExtendedHeaderList => [0x4d].into(), + Tags::CardholderPrivateKeyTemplate => [0x7f, 0x48].into(), + Tags::ConcatenatedKeyData => [0x5f, 0x48].into(), + Tags::CrtKeySignature => [0xb6].into(), + Tags::CrtKeyConfidentiality => [0xb8].into(), + Tags::CrtKeyAuthentication => [0xa4].into(), + Tags::PrivateKeyDataRsaPublicExponent => [0x91].into(), + Tags::PrivateKeyDataRsaPrime1 => [0x92].into(), // Note: value reused! + Tags::PrivateKeyDataRsaPrime2 => [0x93].into(), + Tags::PrivateKeyDataRsaPq => [0x94].into(), + Tags::PrivateKeyDataRsaDp1 => [0x95].into(), + Tags::PrivateKeyDataRsaDq1 => [0x96].into(), + Tags::PrivateKeyDataRsaModulus => [0x97].into(), + Tags::PrivateKeyDataEccPrivateKey => [0x92].into(), // Note: value reused! + Tags::PrivateKeyDataEccPublicKey => [0x99].into(), + + // 7.2.14 GENERATE ASYMMETRIC KEY PAIR + Tags::PublicKey => [0x7f, 0x49].into(), + Tags::PublicKeyDataRsaModulus => [0x81].into(), + Tags::PublicKeyDataRsaExponent => [0x82].into(), + Tags::PublicKeyDataEccPoint => [0x86].into(), + + // 7.2.11 PSO: DECIPHER + Tags::Cipher => [0xa6].into(), + Tags::ExternalPublicKey => [0x86].into(), + + // 7.2.5 SELECT DATA + Tags::GeneralReference => [0x60].into(), + Tags::TagList => [0x5c].into(), + } + } +} + +/// A ShortTag is a Tlv tag that is guaranteed to be either 1 or 2 bytes long. +/// +/// This covers any tag that can be used in the OpenPGP card context (the spec doesn't describe how +/// longer tags might be used.) +/// +/// (The type tlv::Tag will usually/always contain 1 or 2 byte long tags, in this library. +/// But its length is not guaranteed by the type system) +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum ShortTag { + One(u8), + Two(u8, u8), +} + +impl From for Tag { + fn from(n: ShortTag) -> Self { + match n { + ShortTag::One(t0) => [t0].into(), + ShortTag::Two(t0, t1) => [t0, t1].into(), + } + } +} + +impl From<[u8; 1]> for ShortTag { + fn from(v: [u8; 1]) -> Self { + ShortTag::One(v[0]) + } +} + +impl From<[u8; 2]> for ShortTag { + fn from(v: [u8; 2]) -> Self { + ShortTag::Two(v[0], v[1]) + } +} + +impl From for Vec { + fn from(t: ShortTag) -> Self { + match t { + ShortTag::One(t0) => vec![t0], + ShortTag::Two(t0, t1) => vec![t0, t1], + } + } +} diff --git a/openpgp-card/src/tlv.rs b/openpgp-card/src/tlv.rs index b661f93..2a10d87 100644 --- a/openpgp-card/src/tlv.rs +++ b/openpgp-card/src/tlv.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 pub(crate) mod length; @@ -87,7 +87,8 @@ mod test { use hex_literal::hex; use super::{Tlv, Value}; - use crate::{Error, Tags}; + use crate::tags::Tags; + use crate::Error; #[test] fn test_tlv0() { From 58facac819d4a1e94d6a66ea014d85536b7364da Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 30 Aug 2023 12:03:41 +0200 Subject: [PATCH 060/115] openpgp-card: fix rustdoc markup --- openpgp-card/src/lib.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index bb6872b..d07a329 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -117,15 +117,15 @@ impl KeyType { /// /// Most users will probably want to use the `PcscCard` backend from the `card-backend-pcsc` crate. /// -/// Users of this crate can keep a long lived [Card] object, including in long running programs. -/// All operations must be performed on a [Transaction] (which must be short lived). +/// Users of this crate can keep a long lived [`Card`] object, including in long running programs. +/// All operations must be performed on a [`Transaction`] (which must be short lived). pub struct Card { card: Box, card_caps: Option, } impl Card { - /// Turn a [CardBackend] into an [Card] object: + /// Turn a [`CardBackend`] into a [`Card`] object: /// /// The OpenPGP application is `SELECT`ed, and the card capabilities /// of the card are retrieved from the "Application Related Data". @@ -229,13 +229,13 @@ impl Card { } } -/// To perform commands on a [Card], a [Transaction] must be started. +/// To perform commands on a [`Card`], a [`Transaction`] must be started. /// This struct offers low-level access to OpenPGP card functionality. /// /// On backends that support transactions, operations are grouped together in transaction, while /// an object of this type lives. /// -/// A [Transaction] on typical underlying card subsystems must be short lived. +/// A [`Transaction`] on typical underlying card subsystems must be short lived. /// (Typically, smart cards can't be kept open for longer than a few seconds, /// before they are automatically closed.) pub struct Transaction<'a> { @@ -479,13 +479,11 @@ impl<'a> Transaction<'a> { /// /// (This library leaves it up to consumers to decide on a strategy for dealing with this /// issue. Possible strategies include: - /// - asking the card for its [`firmware_version`](Transaction::firmware_version) + /// - asking the card for its [`Transaction::firmware_version`] /// and using the workaround if version <=5.4.3 /// - trying this command first without the workaround, then with workaround if the card - /// returns - /// [`IncorrectParametersCommandDataField`](StatusBytes::IncorrectParametersCommandDataField) - /// - for read operations: using - /// [`next_cardholder_certificate`](Transaction::next_cardholder_certificate) + /// returns [`StatusBytes::IncorrectParametersCommandDataField`] + /// - for read operations: using [`Transaction::next_cardholder_certificate`] /// instead of SELECT DATA) pub fn select_data(&mut self, num: u8, tag: &[u8], yk_workaround: bool) -> Result<(), Error> { log::info!("OpenPgpTransaction: select_data"); @@ -818,7 +816,7 @@ impl<'a> Transaction<'a> { /// Run decryption operation on the smartcard (low level operation) /// (7.2.11 PSO: DECIPHER) /// - /// (consider using the `decipher()` method if you don't want to create + /// (consider using the [`Self::decipher`] method if you don't want to create /// the data field manually) pub fn pso_decipher(&mut self, data: Vec) -> Result, Error> { log::info!("OpenPgpTransaction: pso_decipher"); @@ -836,8 +834,8 @@ impl<'a> Transaction<'a> { /// Valid until next reset of of the card or the next call to `select` /// The only keys that can be configured by this command are the `Decryption` and `Authentication` keys. /// - /// The following first sets the *Authentication* key to be used for [pso_decipher](Transaction::pso_decipher) - /// and then sets the *Decryption* key to be used for [internal_authenticate](Transaction::internal_authenticate). + /// The following first sets the *Authentication* key to be used for [`Transaction::pso_decipher`] + /// and then sets the *Decryption* key to be used for [`Transaction::internal_authenticate`]. /// /// ```no_run /// # use openpgp_card::{KeyType, Transaction}; @@ -877,7 +875,7 @@ impl<'a> Transaction<'a> { /// (see 7.2.10.2 DigestInfo for RSA). /// /// With ECC the hash data is processed as is, using - /// pso_compute_digital_signature. + /// [`Self::pso_compute_digital_signature`]. pub fn signature_for_hash(&mut self, hash: Hash) -> Result, Error> { self.pso_compute_digital_signature(digestinfo(hash)) } @@ -885,7 +883,7 @@ impl<'a> Transaction<'a> { /// Run signing operation on the smartcard (low level operation) /// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE) /// - /// (consider using the `signature_for_hash()` method if you don't + /// (consider using the [`Self::signature_for_hash`] method if you don't /// want to create the data field manually) pub fn pso_compute_digital_signature(&mut self, data: Vec) -> Result, Error> { log::info!("OpenPgpTransaction: pso_compute_digital_signature"); From b88caa24714ac11769682beea240dbce8a0ba09d Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 14:21:55 +0200 Subject: [PATCH 061/115] openpgp-card: rename UIF -> UserInteractionFlag --- openpgp-card-sequoia/src/lib.rs | 10 +++++----- openpgp-card/src/card_do.rs | 20 ++++++++++---------- openpgp-card/src/lib.rs | 10 +++++----- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index b1afaba..abc92ef 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -144,7 +144,7 @@ use openpgp_card::algorithm::{AlgoInfo, AlgoSimple, AlgorithmAttributes}; use openpgp_card::card_do::{ ApplicationIdentifier, CardholderRelatedData, ExtendedCapabilities, ExtendedLengthInfo, Fingerprint, HistoricalBytes, KeyGenerationTime, KeyInformation, KeySet, Lang, PWStatusBytes, - SecuritySupportTemplate, Sex, TouchPolicy, UIF, + SecuritySupportTemplate, Sex, TouchPolicy, UserInteractionFlag, }; use openpgp_card::crypto_data::PublicKeyMaterial; use openpgp_card::{Error, KeyType}; @@ -492,19 +492,19 @@ impl<'a> Card> { self.state.ard.key_information() } - pub fn uif_signing(&self) -> Result, Error> { + pub fn uif_signing(&self) -> Result, Error> { self.state.ard.uif_pso_cds() } - pub fn uif_decryption(&self) -> Result, Error> { + pub fn uif_decryption(&self) -> Result, Error> { self.state.ard.uif_pso_dec() } - pub fn uif_authentication(&self) -> Result, Error> { + pub fn uif_authentication(&self) -> Result, Error> { self.state.ard.uif_pso_aut() } - pub fn uif_attestation(&self) -> Result, Error> { + pub fn uif_attestation(&self) -> Result, Error> { self.state.ard.uif_attestation() } diff --git a/openpgp-card/src/card_do.rs b/openpgp-card/src/card_do.rs index a54d349..0e81c64 100644 --- a/openpgp-card/src/card_do.rs +++ b/openpgp-card/src/card_do.rs @@ -193,7 +193,7 @@ impl ApplicationRelatedData { Ok(ki.map(|v| v.serialize().into())) } - pub fn uif_pso_cds(&self) -> Result, Error> { + pub fn uif_pso_cds(&self) -> Result, Error> { let uif = self.0.find(Tags::UifSig); match uif { @@ -202,7 +202,7 @@ impl ApplicationRelatedData { } } - pub fn uif_pso_dec(&self) -> Result, Error> { + pub fn uif_pso_dec(&self) -> Result, Error> { let uif = self.0.find(Tags::UifDec); match uif { @@ -211,7 +211,7 @@ impl ApplicationRelatedData { } } - pub fn uif_pso_aut(&self) -> Result, Error> { + pub fn uif_pso_aut(&self) -> Result, Error> { let uif = self.0.find(Tags::UifAuth); match uif { @@ -265,7 +265,7 @@ impl ApplicationRelatedData { } } - pub fn uif_attestation(&self) -> Result, Error> { + pub fn uif_attestation(&self) -> Result, Error> { let uif = self.0.find(Tags::UifAttestation); match uif { @@ -310,23 +310,23 @@ impl Display for KeyGenerationTime { } } -/// User Interaction Flag (UIF) [Spec page 24] +/// User Interaction Flag [Spec page 24] #[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub struct UIF([u8; 2]); +pub struct UserInteractionFlag([u8; 2]); -impl TryFrom> for UIF { +impl TryFrom> for UserInteractionFlag { type Error = Error; fn try_from(v: Vec) -> Result { if v.len() == 2 { - Ok(UIF(v.try_into().unwrap())) + Ok(UserInteractionFlag(v.try_into().unwrap())) } else { Err(Error::ParseError(format!("Can't get UID from {v:x?}"))) } } } -impl UIF { +impl UserInteractionFlag { pub fn touch_policy(&self) -> TouchPolicy { self.0[0].into() } @@ -344,7 +344,7 @@ impl UIF { } } -impl Display for UIF { +impl Display for UserInteractionFlag { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index d07a329..77a0607 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -51,7 +51,7 @@ use crate::apdu::commands; use crate::apdu::response::RawResponse; use crate::card_do::{ ApplicationRelatedData, CardholderRelatedData, Fingerprint, KeyGenerationTime, Lang, - PWStatusBytes, SecuritySupportTemplate, Sex, UIF, + PWStatusBytes, SecuritySupportTemplate, Sex, UserInteractionFlag, }; use crate::crypto_data::{CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial}; pub use crate::errors::{Error, StatusBytes}; @@ -1113,7 +1113,7 @@ impl<'a> Transaction<'a> { // FIXME: optional DO for PSO:ENC/DEC with AES /// Set UIF for PSO:CDS - pub fn set_uif_pso_cds(&mut self, uif: &UIF) -> Result<(), Error> { + pub fn set_uif_pso_cds(&mut self, uif: &UserInteractionFlag) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_uif_pso_cds"); let cmd = commands::put_data(Tags::UifSig, uif.as_bytes().to_vec()); @@ -1121,7 +1121,7 @@ impl<'a> Transaction<'a> { } /// Set UIF for PSO:DEC - pub fn set_uif_pso_dec(&mut self, uif: &UIF) -> Result<(), Error> { + pub fn set_uif_pso_dec(&mut self, uif: &UserInteractionFlag) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_uif_pso_dec"); let cmd = commands::put_data(Tags::UifDec, uif.as_bytes().to_vec()); @@ -1129,7 +1129,7 @@ impl<'a> Transaction<'a> { } /// Set UIF for PSO:AUT - pub fn set_uif_pso_aut(&mut self, uif: &UIF) -> Result<(), Error> { + pub fn set_uif_pso_aut(&mut self, uif: &UserInteractionFlag) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_uif_pso_aut"); let cmd = commands::put_data(Tags::UifAuth, uif.as_bytes().to_vec()); @@ -1137,7 +1137,7 @@ impl<'a> Transaction<'a> { } /// Set UIF for Attestation key - pub fn set_uif_attestation(&mut self, uif: &UIF) -> Result<(), Error> { + pub fn set_uif_attestation(&mut self, uif: &UserInteractionFlag) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_uif_attestation"); let cmd = commands::put_data(Tags::UifAttestation, uif.as_bytes().to_vec()); From f659a623d2788feba1f497e7adfdd22acd6d5fd1 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 14:57:29 +0200 Subject: [PATCH 062/115] openpgp-card: handle backend capability hints in Card::new --- openpgp-card/src/apdu.rs | 11 +---------- openpgp-card/src/lib.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/openpgp-card/src/apdu.rs b/openpgp-card/src/apdu.rs index 569481c..7f9dec0 100644 --- a/openpgp-card/src/apdu.rs +++ b/openpgp-card/src/apdu.rs @@ -101,7 +101,7 @@ fn send_command_low_level( where C: CardTransaction + ?Sized, { - let (ext_support, chaining_support, mut max_cmd_bytes, max_rsp_bytes) = + let (ext_support, chaining_support, max_cmd_bytes, max_rsp_bytes) = if let Some(caps) = card_caps { log::trace!("found card caps data!"); @@ -118,15 +118,6 @@ where (false, false, 255, 255) }; - // If the CardTransaction implementation has an inherent limit for the cmd - // size, take that limit into account. - // (E.g. when using scdaemon as a CardTransaction backend, there is a - // limitation to 1000 bytes length for Assuan commands, which - // translates to maximum command length of a bit under 500 bytes) - if let Some(max_card_cmd_bytes) = card_tx.max_cmd_len() { - max_cmd_bytes = usize::min(max_cmd_bytes, max_card_cmd_bytes); - } - log::trace!( "ext le/lc {}, chaining {}, max cmd {}, max rsp {}", ext_support, diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 77a0607..807fb0c 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -180,6 +180,21 @@ impl Card { let pw1_max = pw_status.pw1_max_len(); let pw3_max = pw_status.pw3_max_len(); + // If the CardTransaction implementation has an inherent limit for the cmd + // size, take that limit into account. + // (E.g. when using scdaemon as a CardTransaction backend, there is a + // limitation to 1000 bytes length for Assuan commands, which + // translates to maximum command length of a bit under 500 bytes) + if let Some(max_card_cmd_bytes) = tx.tx.max_cmd_len() { + max_cmd_bytes = u16::min(max_cmd_bytes, max_card_cmd_bytes as u16); + } + + // FIXME: add a more general mechanism to ask the backend for + // amendments to the CardCaps (e.g. to change support for + // "extended length") + // + // Also see https://blog.apdu.fr/posts/2011/05/extended-apdu-status-per-reader/ + let caps = CardCaps::new( ext_support, chaining_support, From 315aa7a94cc3f88a5c6466a27eb731a453931c78 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 15:05:32 +0200 Subject: [PATCH 063/115] openpgp-card: Transaction::card_caps doesn't need to be mut --- openpgp-card/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 807fb0c..9a84926 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -255,7 +255,7 @@ impl Card { /// before they are automatically closed.) pub struct Transaction<'a> { tx: Box, - card_caps: &'a mut Option, + card_caps: &'a Option, } impl<'a> Transaction<'a> { From 9761e0e66467a77628f4db9c4b15dd35a9ce3a6b Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 15:07:31 +0200 Subject: [PATCH 064/115] openpgp-card: internal API cleanup, continued --- openpgp-card/src/algorithm.rs | 19 ++++++++++--------- openpgp-card/src/lib.rs | 9 +++------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/openpgp-card/src/algorithm.rs b/openpgp-card/src/algorithm.rs index 590723d..9be5a35 100644 --- a/openpgp-card/src/algorithm.rs +++ b/openpgp-card/src/algorithm.rs @@ -12,7 +12,6 @@ use std::convert::TryFrom; use std::fmt; -use crate::card_do::ApplicationRelatedData; use crate::crypto_data::EccType; use crate::{keys, oid, Error, KeyType}; @@ -77,38 +76,40 @@ impl AlgoSimple { /// Return the appropriate Algo for this AlgoSimple. /// - /// This mapping differs between cards, based on `ard` and `algo_info` - /// (e.g. the exact Algo variant can have a different size for e, in RSA; - /// also, the import_format can differ). + /// This mapping depends on the actual card in use + /// (e.g.: the size of "e", in RSA can differ; + /// or a different `import_format` can be selected). + /// + /// These card-specific settings are derived from `algorithm_attributes` and `algo_info`. pub(crate) fn determine_algo_attributes( &self, key_type: KeyType, - ard: &ApplicationRelatedData, + algorithm_attributes: AlgorithmAttributes, algo_info: Option, ) -> Result { let algo = match self { Self::RSA1k => AlgorithmAttributes::Rsa(keys::determine_rsa_attrs( 1024, key_type, - ard.algorithm_attributes(key_type)?, + algorithm_attributes, algo_info, )?), Self::RSA2k => AlgorithmAttributes::Rsa(keys::determine_rsa_attrs( 2048, key_type, - ard.algorithm_attributes(key_type)?, + algorithm_attributes, algo_info, )?), Self::RSA3k => AlgorithmAttributes::Rsa(keys::determine_rsa_attrs( 3072, key_type, - ard.algorithm_attributes(key_type)?, + algorithm_attributes, algo_info, )?), Self::RSA4k => AlgorithmAttributes::Rsa(keys::determine_rsa_attrs( 4096, key_type, - ard.algorithm_attributes(key_type)?, + algorithm_attributes, algo_info, )?), Self::NIST256 => AlgorithmAttributes::Ecc(keys::determine_ecc_attrs( diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 9a84926..fcb3844 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -1242,14 +1242,11 @@ impl<'a> Transaction<'a> { simple: AlgoSimple, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { let ard = self.application_related_data()?; - let algo_info = if let Ok(ai) = self.algorithm_information() { - ai - } else { - None - }; + let algorithm_attributes = ard.algorithm_attributes(key_type)?; - let algo = simple.determine_algo_attributes(key_type, &ard, algo_info)?; + let algo_info = self.algorithm_information().ok().flatten(); + let algo = simple.determine_algo_attributes(key_type, algorithm_attributes, algo_info)?; Self::generate_key(self, fp_from_pub, key_type, Some(&algo)) } From 11ce179c0062e140375666139e94da4babca59d0 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 16:11:08 +0200 Subject: [PATCH 065/115] openpgp-card: lint fix (replace deprecated chrono call) --- openpgp-card/src/card_do/key_generation_times.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card/src/card_do/key_generation_times.rs b/openpgp-card/src/card_do/key_generation_times.rs index cfc560c..ed84d45 100644 --- a/openpgp-card/src/card_do/key_generation_times.rs +++ b/openpgp-card/src/card_do/key_generation_times.rs @@ -16,7 +16,7 @@ impl From for DateTime { let naive_datetime = NaiveDateTime::from_timestamp_opt(kg.0 as i64, 0) .expect("invalid or out-of-range datetime"); - DateTime::from_utc(naive_datetime, Utc) + DateTime::from_naive_utc_and_offset(naive_datetime, Utc) } } From 32c59a15b1fb07217fa9a54069d4ae75eba16697 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 16:29:54 +0200 Subject: [PATCH 066/115] openpgp-card: move setting of AlgorithmAttributes out of gen_key_with_metadata() --- openpgp-card/src/keys.rs | 61 +++++++++++----------------------------- openpgp-card/src/lib.rs | 36 +++++++++++++++++++++--- 2 files changed, 48 insertions(+), 49 deletions(-) diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index 9b58ead..8f58523 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -18,69 +18,40 @@ use crate::tags::Tags; use crate::tlv::{length::tlv_encode_length, value::Value, Tlv}; use crate::{Error, KeyType, Tag, Transaction}; -/// Generate asymmetric key pair on the card. +/// Generate asymmetric key pair on the card and set metadata (time, fingerprint). /// -/// This is a convenience wrapper around gen_key() that: -/// - sets algorithm attributes (if not None) +/// This function doesn't set the algorithm_attributes! +/// +/// This is a convenience wrapper around [generate_asymmetric_key_pair] that: /// - generates a key pair on the card /// - sets the creation time on the card to the current host time /// - calculates fingerprint for the key and sets it on the card /// /// `fp_from_pub` calculates the fingerprint for a public key data object and /// creation timestamp -pub(crate) fn gen_key_with_metadata( +pub(crate) fn gen_key_set_metadata( card_tx: &mut Transaction, fp_from_pub: fn(&PublicKeyMaterial, KeyGenerationTime, KeyType) -> Result, + algorithm_attributes: &AlgorithmAttributes, key_type: KeyType, - algo: Option<&AlgorithmAttributes>, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { - // Set algo on card if it's Some - if let Some(target_algo) = algo { - // FIXME: caching - let ard = card_tx.application_related_data()?; // no caching, here! - let ecap = ard.extended_capabilities()?; - - // Only set algo if card supports setting of algo attr - if ecap.algo_attrs_changeable() { - card_tx.set_algorithm_attributes(key_type, target_algo)?; - } else { - // Check if the current algo on the card is the one we want, if - // not we return an error. - - // NOTE: For RSA, the target algo shouldn't prescribe an - // Import-Format. The Import-Format should always depend on what - // the card supports. - - // let cur_algo = ard.get_algorithm_attributes(key_type)?; - // assert_eq!(&cur_algo, target_algo); - - // FIXME: return error - } - } - - // get current (possibly updated) state of algo - let ard = card_tx.application_related_data()?; // no caching, here! - let cur_algo = ard.algorithm_attributes(key_type)?; - - // generate key - let tlv = generate_asymmetric_key_pair(card_tx, key_type)?; - - // derive pubkey - let pubkey = tlv_to_pubkey(&tlv, &cur_algo)?; - - log::trace!("public {:x?}", pubkey); - - // set creation time + // get creation timestamp let time = SystemTime::now(); - - // Store creation timestamp (unix time format, limited to u32) let ts = time .duration_since(UNIX_EPOCH) .map_err(|e| Error::InternalError(format!("This should never happen {e}")))? .as_secs() as u32; - let ts = ts.into(); + // generate key + let tlv = generate_asymmetric_key_pair(card_tx, key_type)?; + // derive pubkey + let pubkey = tlv_to_pubkey(&tlv, algorithm_attributes)?; + + log::trace!("public {:x?}", pubkey); + + // Store creation timestamp (unix time format, limited to u32) + let ts = ts.into(); card_tx.set_creation_time(ts, key_type)?; // calculate/store fingerprint diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index fcb3844..31fbb81 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -1203,10 +1203,10 @@ impl<'a> Transaction<'a> { /// Generate a key on the card. /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) /// - /// If the `algorithm_attributes` parameter is Some, then this algorithm will be set on - /// the card for "key_type". + /// If the `algorithm_attributes` parameter is Some, then that algorithm will be set on + /// the card for the `key_type` slot. /// - /// Note: `algorithm_attributes` needs to precisely specify the RSA bitsize of e (if + /// Note: `algorithm_attributes` needs to precisely specify the RSA bit-size of e (if /// applicable), and import format, with values that the current card /// supports. pub fn generate_key( @@ -1219,7 +1219,35 @@ impl<'a> Transaction<'a> { key_type: KeyType, algorithm_attributes: Option<&AlgorithmAttributes>, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { - keys::gen_key_with_metadata(self, fp_from_pub, key_type, algorithm_attributes) + // Set algo on card if it's Some + if let Some(target_algo) = algorithm_attributes { + // FIXME: caching + let ard = self.application_related_data()?; // no caching, here! + let ecap = ard.extended_capabilities()?; + + // Only set algo if card supports setting of algo attr + if ecap.algo_attrs_changeable() { + self.set_algorithm_attributes(key_type, target_algo)?; + } else { + // Check if the current algo on the card is the one we want, if + // not we return an error. + + // NOTE: For RSA, the target algo shouldn't prescribe an + // Import-Format. The Import-Format should always depend on what + // the card supports. + + // let cur_algo = ard.get_algorithm_attributes(key_type)?; + // assert_eq!(&cur_algo, target_algo); + + // FIXME: return error? + } + } + + // get current (possibly updated) state of algorithm_attributes + let ard = self.application_related_data()?; // no caching, here! + let cur_algo = ard.algorithm_attributes(key_type)?; + + keys::gen_key_set_metadata(self, fp_from_pub, &cur_algo, key_type) } /// Generate a key on the card. From f7f7a1dd3cca5424b867325bcfd5b946e7778b76 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 16:19:00 +0200 Subject: [PATCH 067/115] openpgp-card: rename AlgoInfo->AlgorithmInformation --- openpgp-card-sequoia/src/lib.rs | 4 ++-- openpgp-card/src/algorithm.rs | 15 +++++++------- openpgp-card/src/card_do/algo_info.rs | 28 ++++++++++++++------------- openpgp-card/src/keys.rs | 20 +++++++++++++------ openpgp-card/src/lib.rs | 6 +++--- 5 files changed, 42 insertions(+), 31 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index abc92ef..12178f2 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -140,7 +140,7 @@ //! ``` use card_backend::{CardBackend, SmartcardError}; -use openpgp_card::algorithm::{AlgoInfo, AlgoSimple, AlgorithmAttributes}; +use openpgp_card::algorithm::{AlgoSimple, AlgorithmAttributes, AlgorithmInformation}; use openpgp_card::card_do::{ ApplicationIdentifier, CardholderRelatedData, ExtendedCapabilities, ExtendedLengthInfo, Fingerprint, HistoricalBytes, KeyGenerationTime, KeyInformation, KeySet, Lang, PWStatusBytes, @@ -581,7 +581,7 @@ impl<'a> Card> { } // DO "Algorithm Information" (0xFA) - pub fn algorithm_information(&mut self) -> Result, Error> { + pub fn algorithm_information(&mut self) -> Result, Error> { // The DO "Algorithm Information" (Tag FA) shall be present if // Algorithm attributes can be changed let ec = self.extended_capabilities()?; diff --git a/openpgp-card/src/algorithm.rs b/openpgp-card/src/algorithm.rs index 9be5a35..6a772c8 100644 --- a/openpgp-card/src/algorithm.rs +++ b/openpgp-card/src/algorithm.rs @@ -85,7 +85,7 @@ impl AlgoSimple { &self, key_type: KeyType, algorithm_attributes: AlgorithmAttributes, - algo_info: Option, + algo_info: Option, ) -> Result { let algo = match self { Self::RSA1k => AlgorithmAttributes::Rsa(keys::determine_rsa_attrs( @@ -142,15 +142,16 @@ impl AlgoSimple { } } -/// 4.4.3.11 Algorithm Information +/// Algorithm Information [Spec section 4.4.3.11] /// -/// Modern cards (since OpenPGP card v3.4) provide a list of supported -/// algorithms for each key type. This list specifies which "Algorithm -/// Attributes" can be set for key generation or key import. +/// Modern OpenPGP cards (starting with version v3.4) provide a list of +/// algorithms they support for each key slot. +/// The Algorithm Information list specifies which [`AlgorithmAttributes`] +/// can be used on that card (for key generation or key import). #[derive(Debug, Clone, Eq, PartialEq)] -pub struct AlgoInfo(pub(crate) Vec<(KeyType, AlgorithmAttributes)>); +pub struct AlgorithmInformation(pub(crate) Vec<(KeyType, AlgorithmAttributes)>); -/// 4.4.3.9 Algorithm Attributes +/// Algorithm Attributes [Spec section 4.4.3.9] /// /// An `Algo` describes the algorithm settings for a key on the card. /// diff --git a/openpgp-card/src/card_do/algo_info.rs b/openpgp-card/src/card_do/algo_info.rs index 5a1cee4..1014172 100644 --- a/openpgp-card/src/card_do/algo_info.rs +++ b/openpgp-card/src/card_do/algo_info.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! 4.4.3.11 Algorithm Information @@ -10,11 +10,11 @@ use nom::branch::alt; use nom::combinator::map; use nom::{branch, bytes::complete as bytes, combinator, multi, sequence}; -use crate::algorithm::{AlgoInfo, AlgorithmAttributes}; +use crate::algorithm::{AlgorithmAttributes, AlgorithmInformation}; use crate::card_do::{algo_attrs, complete}; use crate::KeyType; -impl AlgoInfo { +impl AlgorithmInformation { pub fn filter_by_keytype(&self, kt: KeyType) -> Vec<&AlgorithmAttributes> { self.0 .iter() @@ -24,7 +24,7 @@ impl AlgoInfo { } } -impl fmt::Display for AlgoInfo { +impl fmt::Display for AlgorithmInformation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for (kt, a) in &self.0 { let kt = match kt { @@ -85,11 +85,11 @@ fn parse(input: &[u8]) -> nom::IResult<&[u8], Vec<(KeyType, AlgorithmAttributes) ))(input) } -impl TryFrom<&[u8]> for AlgoInfo { +impl TryFrom<&[u8]> for AlgorithmInformation { type Error = crate::Error; fn try_from(input: &[u8]) -> Result { - Ok(AlgoInfo(complete(parse(input))?)) + Ok(AlgorithmInformation(complete(parse(input))?)) } } @@ -99,7 +99,9 @@ impl TryFrom<&[u8]> for AlgoInfo { mod test { use std::convert::TryFrom; - use crate::algorithm::{AlgoInfo, AlgorithmAttributes::*, Curve::*, EccAttrs, RsaAttrs}; + use crate::algorithm::{ + AlgorithmAttributes::*, AlgorithmInformation, Curve::*, EccAttrs, RsaAttrs, + }; use crate::crypto_data::EccType::*; use crate::KeyType::*; @@ -118,11 +120,11 @@ mod test { 0x1, ]; - let ai = AlgoInfo::try_from(&data[..]).unwrap(); + let ai = AlgorithmInformation::try_from(&data[..]).unwrap(); assert_eq!( ai, - AlgoInfo(vec![ + AlgorithmInformation(vec![ (Signing, Rsa(RsaAttrs::new(2048, 32, 0))), (Signing, Rsa(RsaAttrs::new(4096, 32, 0))), (Signing, Ecc(EccAttrs::new(ECDSA, NistP256r1, None))), @@ -164,11 +166,11 @@ mod test { 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xd, ]; - let ai = AlgoInfo::try_from(&data[..]).unwrap(); + let ai = AlgorithmInformation::try_from(&data[..]).unwrap(); assert_eq!( ai, - AlgoInfo(vec![ + AlgorithmInformation(vec![ (Signing, Rsa(RsaAttrs::new(2048, 32, 0))), (Signing, Rsa(RsaAttrs::new(3072, 32, 0))), (Signing, Rsa(RsaAttrs::new(4096, 32, 0))), @@ -245,11 +247,11 @@ mod test { 0xda, 0xb, 0x16, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x97, 0x55, 0x1, 0x5, 0x1, ]; - let ai = AlgoInfo::try_from(&data[..]).unwrap(); + let ai = AlgorithmInformation::try_from(&data[..]).unwrap(); assert_eq!( ai, - AlgoInfo(vec![ + AlgorithmInformation(vec![ (Signing, Rsa(RsaAttrs::new(2048, 17, 0))), (Signing, Rsa(RsaAttrs::new(3072, 17, 0))), (Signing, Rsa(RsaAttrs::new(4096, 17, 0))), diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index 8f58523..de6be27 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -6,7 +6,7 @@ use std::convert::TryFrom; use std::time::{SystemTime, UNIX_EPOCH}; -use crate::algorithm::{AlgoInfo, AlgorithmAttributes, Curve, EccAttrs, RsaAttrs}; +use crate::algorithm::{AlgorithmAttributes, AlgorithmInformation, Curve, EccAttrs, RsaAttrs}; use crate::apdu::command::Command; use crate::apdu::commands; use crate::card_do::{Fingerprint, KeyGenerationTime}; @@ -147,7 +147,7 @@ pub(crate) fn key_import( card_tx: &mut Transaction, key: Box, key_type: KeyType, - algo_info: Option, + algo_info: Option, ) -> Result<(), Error> { log::info!("OpenPgpTransaction: key_import"); @@ -206,7 +206,7 @@ pub(crate) fn determine_rsa_attrs( rsa_bits: u16, key_type: KeyType, algo_attr: AlgorithmAttributes, - algo_info: Option, + algo_info: Option, ) -> Result { // Figure out suitable RSA algorithm parameters: @@ -248,7 +248,7 @@ pub(crate) fn determine_ecc_attrs( oid: &[u8], ecc_type: EccType, key_type: KeyType, - algo_info: Option, + algo_info: Option, ) -> Result { // If we have an algo_info, refuse upload if oid is not listed if let Some(algo_info) = algo_info { @@ -284,7 +284,11 @@ pub(crate) fn determine_ecc_attrs( } /// Look up RsaAttrs parameters in algo_info based on key_type and rsa_bits -fn card_algo_rsa(algo_info: AlgoInfo, key_type: KeyType, rsa_bits: u16) -> Result { +fn card_algo_rsa( + algo_info: AlgorithmInformation, + key_type: KeyType, + rsa_bits: u16, +) -> Result { // Find suitable algorithm parameters (from card's list of algorithms). // Get Algos for this keytype @@ -322,7 +326,11 @@ fn card_algo_rsa(algo_info: AlgoInfo, key_type: KeyType, rsa_bits: u16) -> Resul } /// Get all entries from algo_info with matching `oid` and `key_type`. -fn check_card_algo_ecc(algo_info: AlgoInfo, key_type: KeyType, oid: &[u8]) -> Vec { +fn check_card_algo_ecc( + algo_info: AlgorithmInformation, + key_type: KeyType, + oid: &[u8], +) -> Vec { // Find suitable algorithm parameters (from card's list of algorithms). // Get Algos for this keytype diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 31fbb81..93a7184 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -45,7 +45,7 @@ use std::convert::{TryFrom, TryInto}; use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError}; use tags::{ShortTag, Tags}; -use crate::algorithm::{AlgoInfo, AlgoSimple, AlgorithmAttributes}; +use crate::algorithm::{AlgoSimple, AlgorithmAttributes, AlgorithmInformation}; use crate::apdu::command::Command; use crate::apdu::commands; use crate::apdu::response::RawResponse; @@ -433,13 +433,13 @@ impl<'a> Transaction<'a> { } /// Get "Algorithm Information" - pub fn algorithm_information(&mut self) -> Result, Error> { + pub fn algorithm_information(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: algorithm_information"); let resp = self.send_command(commands::algo_info(), true)?; resp.check_ok()?; - let ai = AlgoInfo::try_from(resp.data()?)?; + let ai = AlgorithmInformation::try_from(resp.data()?)?; Ok(Some(ai)) } From da776bc4cfb79fabff9045de17fd14ea1a17ac1b Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 17:16:52 +0200 Subject: [PATCH 068/115] openpgp-card: rename AlgorithmInformation::filter_by_keytype -> for_keytype --- openpgp-card/src/card_do/algo_info.rs | 4 ++-- openpgp-card/src/keys.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpgp-card/src/card_do/algo_info.rs b/openpgp-card/src/card_do/algo_info.rs index 1014172..f24f498 100644 --- a/openpgp-card/src/card_do/algo_info.rs +++ b/openpgp-card/src/card_do/algo_info.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 -//! 4.4.3.11 Algorithm Information +//! Algorithm Information [Spec section 4.4.3.11] use std::convert::TryFrom; use std::fmt; @@ -15,7 +15,7 @@ use crate::card_do::{algo_attrs, complete}; use crate::KeyType; impl AlgorithmInformation { - pub fn filter_by_keytype(&self, kt: KeyType) -> Vec<&AlgorithmAttributes> { + pub fn for_keytype(&self, kt: KeyType) -> Vec<&AlgorithmAttributes> { self.0 .iter() .filter(|(k, _)| *k == kt) diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index de6be27..ee1e069 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -292,7 +292,7 @@ fn card_algo_rsa( // Find suitable algorithm parameters (from card's list of algorithms). // Get Algos for this keytype - let keytype_algos: Vec<_> = algo_info.filter_by_keytype(key_type); + let keytype_algos: Vec<_> = algo_info.for_keytype(key_type); // Get RSA algo attributes let rsa_algos: Vec<_> = keytype_algos .iter() @@ -334,7 +334,7 @@ fn check_card_algo_ecc( // Find suitable algorithm parameters (from card's list of algorithms). // Get Algos for this keytype - let keytype_algos: Vec<_> = algo_info.filter_by_keytype(key_type); + let keytype_algos: Vec<_> = algo_info.for_keytype(key_type); // Get attributes let ecc_algos: Vec<_> = keytype_algos From 423c9d23eeb73e7d924f66bafc4ecc12b911db11 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 17:37:55 +0200 Subject: [PATCH 069/115] openpgp-card: add Curve::Unknown variant --- openpgp-card-sequoia/src/util.rs | 4 ++-- openpgp-card/src/algorithm.rs | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/openpgp-card-sequoia/src/util.rs b/openpgp-card-sequoia/src/util.rs index 4661bc8..0288efe 100644 --- a/openpgp-card-sequoia/src/util.rs +++ b/openpgp-card-sequoia/src/util.rs @@ -247,7 +247,7 @@ pub fn public_key_material_to_key( match key_type { KeyType::Authentication | KeyType::Signing => { - if algo_ecc.curve() == Curve::Ed25519 { + if algo_ecc.curve() == &Curve::Ed25519 { // EdDSA let k4 = Key4::import_public_ed25519(ecc.data(), time).map_err(|e| { @@ -277,7 +277,7 @@ pub fn public_key_material_to_key( } } KeyType::Decryption => { - if algo_ecc.curve() == Curve::Cv25519 { + if algo_ecc.curve() == &Curve::Cv25519 { // EdDSA let k4 = Key4::import_public_cv25519(ecc.data(), hash, sym, time) .map_err(|e| { diff --git a/openpgp-card/src/algorithm.rs b/openpgp-card/src/algorithm.rs index 6a772c8..46a475c 100644 --- a/openpgp-card/src/algorithm.rs +++ b/openpgp-card/src/algorithm.rs @@ -298,8 +298,8 @@ impl EccAttrs { self.ecc_type } - pub fn curve(&self) -> Curve { - self.curve + pub fn curve(&self) -> &Curve { + &self.curve } pub fn oid(&self) -> &[u8] { @@ -312,7 +312,7 @@ impl EccAttrs { } /// Enum for naming ECC curves, and mapping them to/from their OIDs. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] #[non_exhaustive] pub enum Curve { NistP256r1, @@ -326,6 +326,7 @@ pub enum Curve { Cv25519, Ed448, X448, + Unknown(Vec), } impl Curve { @@ -343,6 +344,7 @@ impl Curve { Cv25519 => oid::CV25519, Ed448 => oid::ED448, X448 => oid::X448, + Unknown(v) => v, } } } @@ -370,7 +372,7 @@ impl TryFrom<&[u8]> for Curve { oid::ED448 => Ed448, oid::X448 => X448, - _ => return Err(Error::ParseError(format!("Unknown curve OID {oid:?}"))), + o => Unknown(o.to_vec()), }; Ok(curve) From ff1afee7c532d24b1fca8a39349ae25226b5df6e Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 17:41:14 +0200 Subject: [PATCH 070/115] openpgp-card: rename RsaAttrs->RsaAttributes, EccAttrs->EccAttributes --- openpgp-card/src/algorithm.rs | 22 ++- openpgp-card/src/card_do/algo_attrs.rs | 10 +- openpgp-card/src/card_do/algo_info.rs | 263 ++++++++++++++++--------- openpgp-card/src/keys.rs | 24 +-- 4 files changed, 202 insertions(+), 117 deletions(-) diff --git a/openpgp-card/src/algorithm.rs b/openpgp-card/src/algorithm.rs index 46a475c..384815a 100644 --- a/openpgp-card/src/algorithm.rs +++ b/openpgp-card/src/algorithm.rs @@ -162,8 +162,8 @@ pub struct AlgorithmInformation(pub(crate) Vec<(KeyType, AlgorithmAttributes)>); #[derive(Debug, Clone, Eq, PartialEq)] #[non_exhaustive] pub enum AlgorithmAttributes { - Rsa(RsaAttrs), - Ecc(EccAttrs), + Rsa(RsaAttributes), + Ecc(EccAttributes), Unknown(Vec), } @@ -215,7 +215,7 @@ impl AlgorithmAttributes { } /// Helper: generate `data` for algorithm attributes with RSA - fn rsa_algo_attrs(algo_attrs: &RsaAttrs) -> Result, Error> { + fn rsa_algo_attrs(algo_attrs: &RsaAttributes) -> Result, Error> { // Algorithm ID (01 = RSA (Encrypt or Sign)) let mut algo_attributes = vec![0x01]; @@ -249,15 +249,15 @@ impl AlgorithmAttributes { /// RSA specific attributes of [`AlgorithmAttributes`] #[derive(Debug, Clone, Eq, PartialEq)] -pub struct RsaAttrs { +pub struct RsaAttributes { len_n: u16, len_e: u16, import_format: u8, } -impl RsaAttrs { +impl RsaAttributes { pub fn new(len_n: u16, len_e: u16, import_format: u8) -> Self { - RsaAttrs { + Self { len_n, len_e, import_format, @@ -279,13 +279,13 @@ impl RsaAttrs { /// ECC specific attributes of [`AlgorithmAttributes`] #[derive(Debug, Clone, Eq, PartialEq)] -pub struct EccAttrs { +pub struct EccAttributes { ecc_type: EccType, curve: Curve, import_format: Option, } -impl EccAttrs { +impl EccAttributes { pub fn new(ecc_type: EccType, curve: Curve, import_format: Option) -> Self { Self { ecc_type, @@ -326,6 +326,7 @@ pub enum Curve { Cv25519, Ed448, X448, + Unknown(Vec), } @@ -344,7 +345,8 @@ impl Curve { Cv25519 => oid::CV25519, Ed448 => oid::ED448, X448 => oid::X448, - Unknown(v) => v, + + Unknown(oid) => oid, } } } @@ -372,7 +374,7 @@ impl TryFrom<&[u8]> for Curve { oid::ED448 => Ed448, oid::X448 => X448, - o => Unknown(o.to_vec()), + _ => Unknown(oid.to_vec()), }; Ok(curve) diff --git a/openpgp-card/src/card_do/algo_attrs.rs b/openpgp-card/src/card_do/algo_attrs.rs index 1aa83b5..5750a50 100644 --- a/openpgp-card/src/card_do/algo_attrs.rs +++ b/openpgp-card/src/card_do/algo_attrs.rs @@ -10,7 +10,7 @@ use nom::bytes::complete::tag; use nom::combinator::map; use nom::{branch, bytes::complete as bytes, number::complete as number}; -use crate::algorithm::{AlgorithmAttributes, Curve, EccAttrs, RsaAttrs}; +use crate::algorithm::{AlgorithmAttributes, Curve, EccAttributes, RsaAttributes}; use crate::card_do::complete; use crate::crypto_data::EccType; @@ -89,7 +89,7 @@ fn parse_rsa(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { Ok(( input, - AlgorithmAttributes::Rsa(RsaAttrs::new(len_n, len_e, import_format)), + AlgorithmAttributes::Rsa(RsaAttributes::new(len_n, len_e, import_format)), )) } @@ -110,7 +110,7 @@ fn parse_ecdh(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { Ok(( input, - AlgorithmAttributes::Ecc(EccAttrs::new(EccType::ECDH, curve, import_format)), + AlgorithmAttributes::Ecc(EccAttributes::new(EccType::ECDH, curve, import_format)), )) } @@ -122,7 +122,7 @@ fn parse_ecdsa(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { Ok(( input, - AlgorithmAttributes::Ecc(EccAttrs::new(EccType::ECDSA, curve, import_format)), + AlgorithmAttributes::Ecc(EccAttributes::new(EccType::ECDSA, curve, import_format)), )) } @@ -134,7 +134,7 @@ fn parse_eddsa(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { Ok(( input, - AlgorithmAttributes::Ecc(EccAttrs::new(EccType::EdDSA, curve, import_format)), + AlgorithmAttributes::Ecc(EccAttributes::new(EccType::EdDSA, curve, import_format)), )) } diff --git a/openpgp-card/src/card_do/algo_info.rs b/openpgp-card/src/card_do/algo_info.rs index f24f498..268efac 100644 --- a/openpgp-card/src/card_do/algo_info.rs +++ b/openpgp-card/src/card_do/algo_info.rs @@ -100,7 +100,7 @@ mod test { use std::convert::TryFrom; use crate::algorithm::{ - AlgorithmAttributes::*, AlgorithmInformation, Curve::*, EccAttrs, RsaAttrs, + AlgorithmAttributes::*, AlgorithmInformation, Curve::*, EccAttributes, RsaAttributes, }; use crate::crypto_data::EccType::*; use crate::KeyType::*; @@ -125,21 +125,30 @@ mod test { assert_eq!( ai, AlgorithmInformation(vec![ - (Signing, Rsa(RsaAttrs::new(2048, 32, 0))), - (Signing, Rsa(RsaAttrs::new(4096, 32, 0))), - (Signing, Ecc(EccAttrs::new(ECDSA, NistP256r1, None))), - (Signing, Ecc(EccAttrs::new(ECDSA, Secp256k1, None))), - (Signing, Ecc(EccAttrs::new(EdDSA, Ed25519, None))), - (Decryption, Rsa(RsaAttrs::new(2048, 32, 0))), - (Decryption, Rsa(RsaAttrs::new(4096, 32, 0))), - (Decryption, Ecc(EccAttrs::new(ECDSA, NistP256r1, None))), - (Decryption, Ecc(EccAttrs::new(ECDSA, Secp256k1, None))), - (Decryption, Ecc(EccAttrs::new(ECDH, Cv25519, None))), - (Authentication, Rsa(RsaAttrs::new(2048, 32, 0))), - (Authentication, Rsa(RsaAttrs::new(4096, 32, 0))), - (Authentication, Ecc(EccAttrs::new(ECDSA, NistP256r1, None))), - (Authentication, Ecc(EccAttrs::new(ECDSA, Secp256k1, None))), - (Authentication, Ecc(EccAttrs::new(EdDSA, Ed25519, None))) + (Signing, Rsa(RsaAttributes::new(2048, 32, 0))), + (Signing, Rsa(RsaAttributes::new(4096, 32, 0))), + (Signing, Ecc(EccAttributes::new(ECDSA, NistP256r1, None))), + (Signing, Ecc(EccAttributes::new(ECDSA, Secp256k1, None))), + (Signing, Ecc(EccAttributes::new(EdDSA, Ed25519, None))), + (Decryption, Rsa(RsaAttributes::new(2048, 32, 0))), + (Decryption, Rsa(RsaAttributes::new(4096, 32, 0))), + (Decryption, Ecc(EccAttributes::new(ECDSA, NistP256r1, None))), + (Decryption, Ecc(EccAttributes::new(ECDSA, Secp256k1, None))), + (Decryption, Ecc(EccAttributes::new(ECDH, Cv25519, None))), + (Authentication, Rsa(RsaAttributes::new(2048, 32, 0))), + (Authentication, Rsa(RsaAttributes::new(4096, 32, 0))), + ( + Authentication, + Ecc(EccAttributes::new(ECDSA, NistP256r1, None)) + ), + ( + Authentication, + Ecc(EccAttributes::new(ECDSA, Secp256k1, None)) + ), + ( + Authentication, + Ecc(EccAttributes::new(EdDSA, Ed25519, None)) + ) ]) ); } @@ -171,41 +180,68 @@ mod test { assert_eq!( ai, AlgorithmInformation(vec![ - (Signing, Rsa(RsaAttrs::new(2048, 32, 0))), - (Signing, Rsa(RsaAttrs::new(3072, 32, 0))), - (Signing, Rsa(RsaAttrs::new(4096, 32, 0))), - (Signing, Ecc(EccAttrs::new(ECDSA, NistP256r1, None))), - (Signing, Ecc(EccAttrs::new(ECDSA, NistP384r1, None))), - (Signing, Ecc(EccAttrs::new(ECDSA, NistP521r1, None))), - (Signing, Ecc(EccAttrs::new(ECDSA, BrainpoolP256r1, None))), - (Signing, Ecc(EccAttrs::new(ECDSA, BrainpoolP384r1, None))), - (Signing, Ecc(EccAttrs::new(ECDSA, BrainpoolP512r1, None))), - (Decryption, Rsa(RsaAttrs::new(2048, 32, 0))), - (Decryption, Rsa(RsaAttrs::new(3072, 32, 0))), - (Decryption, Rsa(RsaAttrs::new(4096, 32, 0))), - (Decryption, Ecc(EccAttrs::new(ECDH, NistP256r1, None))), - (Decryption, Ecc(EccAttrs::new(ECDH, NistP384r1, None))), - (Decryption, Ecc(EccAttrs::new(ECDH, NistP521r1, None))), - (Decryption, Ecc(EccAttrs::new(ECDH, BrainpoolP256r1, None))), - (Decryption, Ecc(EccAttrs::new(ECDH, BrainpoolP384r1, None))), - (Decryption, Ecc(EccAttrs::new(ECDH, BrainpoolP512r1, None))), - (Authentication, Rsa(RsaAttrs::new(2048, 32, 0))), - (Authentication, Rsa(RsaAttrs::new(3072, 32, 0))), - (Authentication, Rsa(RsaAttrs::new(4096, 32, 0))), - (Authentication, Ecc(EccAttrs::new(ECDSA, NistP256r1, None))), - (Authentication, Ecc(EccAttrs::new(ECDSA, NistP384r1, None))), - (Authentication, Ecc(EccAttrs::new(ECDSA, NistP521r1, None))), + (Signing, Rsa(RsaAttributes::new(2048, 32, 0))), + (Signing, Rsa(RsaAttributes::new(3072, 32, 0))), + (Signing, Rsa(RsaAttributes::new(4096, 32, 0))), + (Signing, Ecc(EccAttributes::new(ECDSA, NistP256r1, None))), + (Signing, Ecc(EccAttributes::new(ECDSA, NistP384r1, None))), + (Signing, Ecc(EccAttributes::new(ECDSA, NistP521r1, None))), + ( + Signing, + Ecc(EccAttributes::new(ECDSA, BrainpoolP256r1, None)) + ), + ( + Signing, + Ecc(EccAttributes::new(ECDSA, BrainpoolP384r1, None)) + ), + ( + Signing, + Ecc(EccAttributes::new(ECDSA, BrainpoolP512r1, None)) + ), + (Decryption, Rsa(RsaAttributes::new(2048, 32, 0))), + (Decryption, Rsa(RsaAttributes::new(3072, 32, 0))), + (Decryption, Rsa(RsaAttributes::new(4096, 32, 0))), + (Decryption, Ecc(EccAttributes::new(ECDH, NistP256r1, None))), + (Decryption, Ecc(EccAttributes::new(ECDH, NistP384r1, None))), + (Decryption, Ecc(EccAttributes::new(ECDH, NistP521r1, None))), + ( + Decryption, + Ecc(EccAttributes::new(ECDH, BrainpoolP256r1, None)) + ), + ( + Decryption, + Ecc(EccAttributes::new(ECDH, BrainpoolP384r1, None)) + ), + ( + Decryption, + Ecc(EccAttributes::new(ECDH, BrainpoolP512r1, None)) + ), + (Authentication, Rsa(RsaAttributes::new(2048, 32, 0))), + (Authentication, Rsa(RsaAttributes::new(3072, 32, 0))), + (Authentication, Rsa(RsaAttributes::new(4096, 32, 0))), ( Authentication, - Ecc(EccAttrs::new(ECDSA, BrainpoolP256r1, None)) + Ecc(EccAttributes::new(ECDSA, NistP256r1, None)) ), ( Authentication, - Ecc(EccAttrs::new(ECDSA, BrainpoolP384r1, None)) + Ecc(EccAttributes::new(ECDSA, NistP384r1, None)) ), ( Authentication, - Ecc(EccAttrs::new(ECDSA, BrainpoolP512r1, None)) + Ecc(EccAttributes::new(ECDSA, NistP521r1, None)) + ), + ( + Authentication, + Ecc(EccAttributes::new(ECDSA, BrainpoolP256r1, None)) + ), + ( + Authentication, + Ecc(EccAttributes::new(ECDSA, BrainpoolP384r1, None)) + ), + ( + Authentication, + Ecc(EccAttributes::new(ECDSA, BrainpoolP512r1, None)) ) ]) ); @@ -252,72 +288,117 @@ mod test { assert_eq!( ai, AlgorithmInformation(vec![ - (Signing, Rsa(RsaAttrs::new(2048, 17, 0))), - (Signing, Rsa(RsaAttrs::new(3072, 17, 0))), - (Signing, Rsa(RsaAttrs::new(4096, 17, 0))), - (Signing, Ecc(EccAttrs::new(ECDSA, NistP256r1, None))), - (Signing, Ecc(EccAttrs::new(ECDSA, NistP384r1, None))), - (Signing, Ecc(EccAttrs::new(ECDSA, NistP521r1, None))), - (Signing, Ecc(EccAttrs::new(ECDSA, Secp256k1, None))), - (Signing, Ecc(EccAttrs::new(ECDSA, BrainpoolP256r1, None))), - (Signing, Ecc(EccAttrs::new(ECDSA, BrainpoolP384r1, None))), - (Signing, Ecc(EccAttrs::new(ECDSA, BrainpoolP512r1, None))), - (Signing, Ecc(EccAttrs::new(EdDSA, Ed25519, None))), - (Signing, Ecc(EccAttrs::new(EdDSA, Cv25519, None))), - (Decryption, Rsa(RsaAttrs::new(2048, 17, 0))), - (Decryption, Rsa(RsaAttrs::new(3072, 17, 0))), - (Decryption, Rsa(RsaAttrs::new(4096, 17, 0))), - (Decryption, Ecc(EccAttrs::new(ECDH, NistP256r1, None))), - (Decryption, Ecc(EccAttrs::new(ECDH, NistP384r1, None))), - (Decryption, Ecc(EccAttrs::new(ECDH, NistP521r1, None))), - (Decryption, Ecc(EccAttrs::new(ECDH, Secp256k1, None))), - (Decryption, Ecc(EccAttrs::new(ECDH, BrainpoolP256r1, None))), - (Decryption, Ecc(EccAttrs::new(ECDH, BrainpoolP384r1, None))), - (Decryption, Ecc(EccAttrs::new(ECDH, BrainpoolP512r1, None))), - (Decryption, Ecc(EccAttrs::new(EdDSA, Ed25519, None))), - (Decryption, Ecc(EccAttrs::new(EdDSA, Cv25519, None))), - (Authentication, Rsa(RsaAttrs::new(2048, 17, 0))), - (Authentication, Rsa(RsaAttrs::new(3072, 17, 0))), - (Authentication, Rsa(RsaAttrs::new(4096, 17, 0))), - (Authentication, Ecc(EccAttrs::new(ECDSA, NistP256r1, None))), - (Authentication, Ecc(EccAttrs::new(ECDSA, NistP384r1, None))), - (Authentication, Ecc(EccAttrs::new(ECDSA, NistP521r1, None))), - (Authentication, Ecc(EccAttrs::new(ECDSA, Secp256k1, None))), + (Signing, Rsa(RsaAttributes::new(2048, 17, 0))), + (Signing, Rsa(RsaAttributes::new(3072, 17, 0))), + (Signing, Rsa(RsaAttributes::new(4096, 17, 0))), + (Signing, Ecc(EccAttributes::new(ECDSA, NistP256r1, None))), + (Signing, Ecc(EccAttributes::new(ECDSA, NistP384r1, None))), + (Signing, Ecc(EccAttributes::new(ECDSA, NistP521r1, None))), + (Signing, Ecc(EccAttributes::new(ECDSA, Secp256k1, None))), + ( + Signing, + Ecc(EccAttributes::new(ECDSA, BrainpoolP256r1, None)) + ), + ( + Signing, + Ecc(EccAttributes::new(ECDSA, BrainpoolP384r1, None)) + ), + ( + Signing, + Ecc(EccAttributes::new(ECDSA, BrainpoolP512r1, None)) + ), + (Signing, Ecc(EccAttributes::new(EdDSA, Ed25519, None))), + (Signing, Ecc(EccAttributes::new(EdDSA, Cv25519, None))), + (Decryption, Rsa(RsaAttributes::new(2048, 17, 0))), + (Decryption, Rsa(RsaAttributes::new(3072, 17, 0))), + (Decryption, Rsa(RsaAttributes::new(4096, 17, 0))), + (Decryption, Ecc(EccAttributes::new(ECDH, NistP256r1, None))), + (Decryption, Ecc(EccAttributes::new(ECDH, NistP384r1, None))), + (Decryption, Ecc(EccAttributes::new(ECDH, NistP521r1, None))), + (Decryption, Ecc(EccAttributes::new(ECDH, Secp256k1, None))), + ( + Decryption, + Ecc(EccAttributes::new(ECDH, BrainpoolP256r1, None)) + ), + ( + Decryption, + Ecc(EccAttributes::new(ECDH, BrainpoolP384r1, None)) + ), + ( + Decryption, + Ecc(EccAttributes::new(ECDH, BrainpoolP512r1, None)) + ), + (Decryption, Ecc(EccAttributes::new(EdDSA, Ed25519, None))), + (Decryption, Ecc(EccAttributes::new(EdDSA, Cv25519, None))), + (Authentication, Rsa(RsaAttributes::new(2048, 17, 0))), + (Authentication, Rsa(RsaAttributes::new(3072, 17, 0))), + (Authentication, Rsa(RsaAttributes::new(4096, 17, 0))), ( Authentication, - Ecc(EccAttrs::new(ECDSA, BrainpoolP256r1, None)) + Ecc(EccAttributes::new(ECDSA, NistP256r1, None)) ), ( Authentication, - Ecc(EccAttrs::new(ECDSA, BrainpoolP384r1, None)) + Ecc(EccAttributes::new(ECDSA, NistP384r1, None)) ), ( Authentication, - Ecc(EccAttrs::new(ECDSA, BrainpoolP512r1, None)) + Ecc(EccAttributes::new(ECDSA, NistP521r1, None)) ), - (Authentication, Ecc(EccAttrs::new(EdDSA, Ed25519, None))), - (Authentication, Ecc(EccAttrs::new(EdDSA, Cv25519, None))), - (Attestation, Rsa(RsaAttrs::new(2048, 17, 0))), - (Attestation, Rsa(RsaAttrs::new(3072, 17, 0))), - (Attestation, Rsa(RsaAttrs::new(4096, 17, 0))), - (Attestation, Ecc(EccAttrs::new(ECDSA, NistP256r1, None))), - (Attestation, Ecc(EccAttrs::new(ECDSA, NistP384r1, None))), - (Attestation, Ecc(EccAttrs::new(ECDSA, NistP521r1, None))), - (Attestation, Ecc(EccAttrs::new(ECDSA, Secp256k1, None))), + ( + Authentication, + Ecc(EccAttributes::new(ECDSA, Secp256k1, None)) + ), + ( + Authentication, + Ecc(EccAttributes::new(ECDSA, BrainpoolP256r1, None)) + ), + ( + Authentication, + Ecc(EccAttributes::new(ECDSA, BrainpoolP384r1, None)) + ), + ( + Authentication, + Ecc(EccAttributes::new(ECDSA, BrainpoolP512r1, None)) + ), + ( + Authentication, + Ecc(EccAttributes::new(EdDSA, Ed25519, None)) + ), + ( + Authentication, + Ecc(EccAttributes::new(EdDSA, Cv25519, None)) + ), + (Attestation, Rsa(RsaAttributes::new(2048, 17, 0))), + (Attestation, Rsa(RsaAttributes::new(3072, 17, 0))), + (Attestation, Rsa(RsaAttributes::new(4096, 17, 0))), ( Attestation, - Ecc(EccAttrs::new(ECDSA, BrainpoolP256r1, None)) + Ecc(EccAttributes::new(ECDSA, NistP256r1, None)) ), ( Attestation, - Ecc(EccAttrs::new(ECDSA, BrainpoolP384r1, None)) + Ecc(EccAttributes::new(ECDSA, NistP384r1, None)) ), ( Attestation, - Ecc(EccAttrs::new(ECDSA, BrainpoolP512r1, None)) + Ecc(EccAttributes::new(ECDSA, NistP521r1, None)) ), - (Attestation, Ecc(EccAttrs::new(EdDSA, Ed25519, None))), - (Attestation, Ecc(EccAttrs::new(EdDSA, Cv25519, None))) + (Attestation, Ecc(EccAttributes::new(ECDSA, Secp256k1, None))), + ( + Attestation, + Ecc(EccAttributes::new(ECDSA, BrainpoolP256r1, None)) + ), + ( + Attestation, + Ecc(EccAttributes::new(ECDSA, BrainpoolP384r1, None)) + ), + ( + Attestation, + Ecc(EccAttributes::new(ECDSA, BrainpoolP512r1, None)) + ), + (Attestation, Ecc(EccAttributes::new(EdDSA, Ed25519, None))), + (Attestation, Ecc(EccAttributes::new(EdDSA, Cv25519, None))) ]) ); } diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index ee1e069..7880657 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -6,7 +6,9 @@ use std::convert::TryFrom; use std::time::{SystemTime, UNIX_EPOCH}; -use crate::algorithm::{AlgorithmAttributes, AlgorithmInformation, Curve, EccAttrs, RsaAttrs}; +use crate::algorithm::{ + AlgorithmAttributes, AlgorithmInformation, Curve, EccAttributes, RsaAttributes, +}; use crate::apdu::command::Command; use crate::apdu::commands; use crate::card_do::{Fingerprint, KeyGenerationTime}; @@ -207,7 +209,7 @@ pub(crate) fn determine_rsa_attrs( key_type: KeyType, algo_attr: AlgorithmAttributes, algo_info: Option, -) -> Result { +) -> Result { // Figure out suitable RSA algorithm parameters: // Does the card offer a list of algorithms? @@ -222,7 +224,7 @@ pub(crate) fn determine_rsa_attrs( if let AlgorithmAttributes::Rsa(rsa) = algo_attr { // If so, use the algorithm parameters from the card and // adjust the bit length based on the user-provided key. - RsaAttrs::new(rsa_bits, rsa.len_e(), rsa.import_format()) + RsaAttributes::new(rsa_bits, rsa.len_e(), rsa.import_format()) } else { // The card doesn't provide an algorithm list, and the // current algorithm on the card is not RSA. @@ -235,7 +237,7 @@ pub(crate) fn determine_rsa_attrs( // list of which RSA parameters that model of card // supports] - RsaAttrs::new(rsa_bits, 32, 0) + RsaAttributes::new(rsa_bits, 32, 0) } }; @@ -249,7 +251,7 @@ pub(crate) fn determine_ecc_attrs( ecc_type: EccType, key_type: KeyType, algo_info: Option, -) -> Result { +) -> Result { // If we have an algo_info, refuse upload if oid is not listed if let Some(algo_info) = algo_info { let algos = check_card_algo_ecc(algo_info, key_type, oid); @@ -269,7 +271,7 @@ pub(crate) fn determine_ecc_attrs( // We do however, use import_format from algorithm information. if !algos.is_empty() { - return Ok(EccAttrs::new( + return Ok(EccAttributes::new( ecc_type, Curve::try_from(oid)?, algos[0].import_format(), @@ -280,7 +282,7 @@ pub(crate) fn determine_ecc_attrs( // Return a default when we have no algo_info. // (Do cards that support ecc but have no algo_info exist?) - Ok(EccAttrs::new(ecc_type, Curve::try_from(oid)?, None)) + Ok(EccAttributes::new(ecc_type, Curve::try_from(oid)?, None)) } /// Look up RsaAttrs parameters in algo_info based on key_type and rsa_bits @@ -288,7 +290,7 @@ fn card_algo_rsa( algo_info: AlgorithmInformation, key_type: KeyType, rsa_bits: u16, -) -> Result { +) -> Result { // Find suitable algorithm parameters (from card's list of algorithms). // Get Algos for this keytype @@ -330,7 +332,7 @@ fn check_card_algo_ecc( algo_info: AlgorithmInformation, key_type: KeyType, oid: &[u8], -) -> Vec { +) -> Vec { // Find suitable algorithm parameters (from card's list of algorithms). // Get Algos for this keytype @@ -361,7 +363,7 @@ fn check_card_algo_ecc( fn rsa_key_import_cmd( key_type: KeyType, rsa_key: Box, - rsa_attrs: &RsaAttrs, + rsa_attrs: &RsaAttributes, ) -> Result { // Assemble key command (see 4.4.3.12 Private Key Template) @@ -459,7 +461,7 @@ fn rsa_key_import_cmd( fn ecc_key_import_cmd( key_type: KeyType, ecc_key: Box, - ecc_attrs: &EccAttrs, + ecc_attrs: &EccAttributes, ) -> Result { let private = ecc_key.private(); From 2b0111b9232a689f1e1a9f467892e073bff767a4 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 16:05:08 +0200 Subject: [PATCH 071/115] openpgp-card: rustdoc --- openpgp-card/src/algorithm.rs | 5 ++- openpgp-card/src/card_do.rs | 19 +++++---- openpgp-card/src/card_do/extended_cap.rs | 8 ++++ openpgp-card/src/lib.rs | 50 ++++++++++++------------ 4 files changed, 47 insertions(+), 35 deletions(-) diff --git a/openpgp-card/src/algorithm.rs b/openpgp-card/src/algorithm.rs index 384815a..e9cb9a7 100644 --- a/openpgp-card/src/algorithm.rs +++ b/openpgp-card/src/algorithm.rs @@ -142,7 +142,8 @@ impl AlgoSimple { } } -/// Algorithm Information [Spec section 4.4.3.11] +/// "Algorithm Information" enumerates which algorithms the current card supports +/// [Spec section 4.4.3.11] /// /// Modern OpenPGP cards (starting with version v3.4) provide a list of /// algorithms they support for each key slot. @@ -153,7 +154,7 @@ pub struct AlgorithmInformation(pub(crate) Vec<(KeyType, AlgorithmAttributes)>); /// Algorithm Attributes [Spec section 4.4.3.9] /// -/// An `Algo` describes the algorithm settings for a key on the card. +/// [`AlgorithmAttributes`] describes the algorithm settings for a key on the card. /// /// This setting specifies the data format of: /// - Key import diff --git a/openpgp-card/src/card_do.rs b/openpgp-card/src/card_do.rs index 0e81c64..4aa408c 100644 --- a/openpgp-card/src/card_do.rs +++ b/openpgp-card/src/card_do.rs @@ -77,11 +77,13 @@ impl ApplicationRelatedData { #[allow(dead_code)] fn general_feature_management() -> Option { + // FIXME unimplemented!() } #[allow(dead_code)] fn discretionary_data_objects() { + // FIXME unimplemented!() } @@ -423,7 +425,8 @@ impl From for TouchPolicy { } } -/// "additional hardware for user interaction" [Spec section 4.1.3.2] +/// Features of "additional hardware for user interaction" [Spec section 4.1.3.2]. +/// (Settings for these features are contained in [`UserInteractionFlag`]) pub struct Features(u8); impl From for Features { @@ -551,8 +554,8 @@ impl Display for KeyInformation { } } -/// KeyStatus is contained in `KeyInformation`. It encodes if key material on a card was imported -/// or generated on the card. +/// KeyStatus is contained in [`KeyInformation`]. +/// It encodes if key material on a card was imported or generated on the card. #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[non_exhaustive] pub enum KeyStatus { @@ -820,7 +823,8 @@ impl Display for CardholderRelatedData { } } -/// Sex [Spec section 4.4.3.5] +/// Sex [Spec section 4.4.3.5]. +/// The Sex setting is accessible via [`CardholderRelatedData`]. /// /// Encoded in accordance with #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -868,9 +872,8 @@ impl From for Sex { } } -/// Individual language for Language Preferences [Spec section 4.4.3.4] -/// -/// This field is accessible via `CardholderRelatedData`. +/// Individual language for Language Preferences [Spec section 4.4.3.4]. +/// Language preferences are accessible via [`CardholderRelatedData`]. /// /// Encoded according to #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -995,7 +998,7 @@ impl PWStatusBytes { } } -/// Fingerprint [Spec page 23] +/// OpenPGP Fingerprint for a key slot [Spec page 23] #[derive(Clone, Eq, PartialEq)] pub struct Fingerprint([u8; 20]); diff --git a/openpgp-card/src/card_do/extended_cap.rs b/openpgp-card/src/card_do/extended_cap.rs index 1a34853..7a1a813 100644 --- a/openpgp-card/src/card_do/extended_cap.rs +++ b/openpgp-card/src/card_do/extended_cap.rs @@ -17,10 +17,18 @@ impl ExtendedCapabilities { self.algo_attrs_changeable } + /// Only available in OpenPGP card version 2.x + /// + /// (For OpenPGP card version 3.x, see + /// [`crate::card_do::ExtendedLengthInfo`]) pub fn max_cmd_len(&self) -> Option { self.max_cmd_len } + /// Only available in OpenPGP card version 2.x + /// + /// (For OpenPGP card version 3.x, see + /// [`crate::card_do::ExtendedLengthInfo`]) pub fn max_resp_len(&self) -> Option { self.max_resp_len } diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 93a7184..9d7ed7c 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -3,8 +3,8 @@ //! Client library for //! [OpenPGP card](https://en.wikipedia.org/wiki/OpenPGP_card) -//! devices (such as Gnuk, YubiKey, or Java smartcards running an OpenPGP -//! card application). +//! devices (such as Gnuk, Nitrokey, YubiKey, or Java smartcards running an +//! OpenPGP card application). //! //! This library aims to offer //! - access to all features in the OpenPGP @@ -12,19 +12,17 @@ //! - without relying on a particular //! [OpenPGP implementation](https://www.openpgp.org/software/developer/). //! -//! This library can't directly access cards by itself. Instead, users -//! need to supply a backend that implements the [`card_backend::CardBackend`] -//! / [`card_backend::CardTransaction`] traits. -//! -//! The companion crate -//! [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc) -//! offers a backend that uses [PC/SC](https://en.wikipedia.org/wiki/PC/SC) to -//! communicate with Smart Cards. -//! //! The [openpgp-card-sequoia](https://crates.io/crates/openpgp-card-sequoia) //! crate offers a higher level wrapper based on the [Sequoia PGP](https://sequoia-pgp.org/) //! implementation. //! +//! Note that this library can't directly access cards by itself. +//! Instead, users need to supply a backend that implements the +//! [`CardBackend`] and [`CardTransaction`] traits. +//! For example [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc) +//! offers a backend implementation that uses [PC/SC](https://en.wikipedia.org/wiki/PC/SC) to +//! communicate with Smart Cards. +//! //! See the [architecture diagram](https://gitlab.com/openpgp-card/openpgp-card#architecture) //! for an overview of the ecosystem around this crate. @@ -69,6 +67,7 @@ pub enum KeyType { Decryption, Authentication, + /// Attestation is a Yubico proprietary key slot Attestation, } @@ -120,7 +119,11 @@ impl KeyType { /// Users of this crate can keep a long lived [`Card`] object, including in long running programs. /// All operations must be performed on a [`Transaction`] (which must be short lived). pub struct Card { + /// A connection to the smart card card: Box, + + /// Capabilites of the card, determined from hints by the Backend, + /// as well as the Application Related Data card_caps: Option, } @@ -223,21 +226,18 @@ impl Card { self.card } - /// Get an OpenPgpTransaction object. This starts a transaction on the underlying - /// CardBackend. + /// Start a transaction on the underlying CardBackend. + /// The resulting [Transaction] object allows performing commands on the card. /// - /// Note: transactions on the Card cannot be long running, they will be reset within seconds - /// when idle. - /// - /// If the card has been reset, and `reselect_application` is set, then - /// that application will be `SELECT`ed after starting the transaction. + /// Note: Transactions on the Card cannot be long running. + /// They may be reset by the smart card subsystem within seconds, when idle. pub fn transaction(&mut self) -> Result { let card_caps = &mut self.card_caps; let tx = self.card.transaction(Some(OPENPGP_APPLICATION))?; if tx.was_reset() { - // FIXME - // Signal state invalidation? (PIN verification, ...) + // FIXME: Signal state invalidation to the library user? + // (E.g.: PIN verifications may have been lost.) } Ok(Transaction { tx, card_caps }) @@ -713,7 +713,7 @@ impl<'a> Transaction<'a> { /// (Note: /// - some cards don't correctly implement this feature, e.g. YubiKey 5 /// - some cards that don't support this instruction may decrease the pin's error count, - /// eventually requiring the user to reset the pin) + /// eventually requiring the user to factory reset the card) pub fn check_pw3(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: check_pw3"); @@ -849,8 +849,8 @@ impl<'a> Transaction<'a> { /// Valid until next reset of of the card or the next call to `select` /// The only keys that can be configured by this command are the `Decryption` and `Authentication` keys. /// - /// The following first sets the *Authentication* key to be used for [`Transaction::pso_decipher`] - /// and then sets the *Decryption* key to be used for [`Transaction::internal_authenticate`]. + /// The following first sets the *Authentication* key to be used for [`Self::pso_decipher`] + /// and then sets the *Decryption* key to be used for [`Self::internal_authenticate`]. /// /// ```no_run /// # use openpgp_card::{KeyType, Transaction}; @@ -1253,8 +1253,8 @@ impl<'a> Transaction<'a> { /// Generate a key on the card. /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) /// - /// This is a wrapper around generate_key() which allows - /// using the simplified `AlgoSimple` algorithm selector enum. + /// This is a wrapper around [`Self::generate_key`] which allows + /// using the simplified [`AlgoSimple`] algorithm selector enum. /// /// Note: AlgoSimple doesn't specify card specific details (such as /// bitsize of e for RSA, and import format). This function determines From 5440fdeb1e4be5bb67423e8867c625a223d1cec4 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 30 Aug 2023 14:05:57 +0200 Subject: [PATCH 072/115] openpgp-card: move `commands` out of the `apdu` module --- openpgp-card/src/apdu.rs | 6 +++--- openpgp-card/src/{apdu => }/commands.rs | 0 openpgp-card/src/keys.rs | 2 +- openpgp-card/src/lib.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename openpgp-card/src/{apdu => }/commands.rs (100%) diff --git a/openpgp-card/src/apdu.rs b/openpgp-card/src/apdu.rs index 7f9dec0..5b85bb4 100644 --- a/openpgp-card/src/apdu.rs +++ b/openpgp-card/src/apdu.rs @@ -5,15 +5,15 @@ //! Commands and responses to commands pub(crate) mod command; -pub(crate) mod commands; pub mod response; use std::convert::TryFrom; use card_backend::{CardCaps, CardTransaction}; -use crate::apdu::command::Expect; -use crate::apdu::{command::Command, response::RawResponse}; +use crate::apdu::command::{Command, Expect}; +use crate::apdu::response::RawResponse; +use crate::commands; use crate::{Error, StatusBytes}; /// "Maximum amount of bytes in a short APDU command or response" (from pcsc) diff --git a/openpgp-card/src/apdu/commands.rs b/openpgp-card/src/commands.rs similarity index 100% rename from openpgp-card/src/apdu/commands.rs rename to openpgp-card/src/commands.rs diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index 7880657..f098f65 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -10,8 +10,8 @@ use crate::algorithm::{ AlgorithmAttributes, AlgorithmInformation, Curve, EccAttributes, RsaAttributes, }; use crate::apdu::command::Command; -use crate::apdu::commands; use crate::card_do::{Fingerprint, KeyGenerationTime}; +use crate::commands; use crate::crypto_data::{ CardUploadableKey, EccKey, EccPub, EccType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey, RSAPub, diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 9d7ed7c..ca3c595 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -31,6 +31,7 @@ extern crate core; pub mod algorithm; pub(crate) mod apdu; pub mod card_do; +mod commands; pub mod crypto_data; mod errors; pub(crate) mod keys; @@ -45,7 +46,6 @@ use tags::{ShortTag, Tags}; use crate::algorithm::{AlgoSimple, AlgorithmAttributes, AlgorithmInformation}; use crate::apdu::command::Command; -use crate::apdu::commands; use crate::apdu::response::RawResponse; use crate::card_do::{ ApplicationRelatedData, CardholderRelatedData, Fingerprint, KeyGenerationTime, Lang, From 0e0da2491800ebf75b2955c0bfbbfd366985cc10 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 30 Aug 2023 14:07:53 +0200 Subject: [PATCH 073/115] openpgp-card: clean up visibilities --- openpgp-card/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index ca3c595..b42085f 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -29,12 +29,12 @@ extern crate core; pub mod algorithm; -pub(crate) mod apdu; +mod apdu; pub mod card_do; mod commands; pub mod crypto_data; mod errors; -pub(crate) mod keys; +mod keys; mod oid; mod tags; mod tlv; @@ -57,7 +57,7 @@ use crate::tlv::tag::Tag; use crate::tlv::value::Value; use crate::tlv::Tlv; -pub(crate) const OPENPGP_APPLICATION: &[u8] = &[0xD2, 0x76, 0x00, 0x01, 0x24, 0x01]; +const OPENPGP_APPLICATION: &[u8] = &[0xD2, 0x76, 0x00, 0x01, 0x24, 0x01]; /// Identify a Key slot on an OpenPGP card #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] From 925d5c6f9c7fbb8c2d11a1e5be61605ab0f72b38 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 30 Aug 2023 15:01:40 +0200 Subject: [PATCH 074/115] openpgp-card: Cache immutable card information from ApplicationRelatedData in Card::new --- openpgp-card/src/lib.rs | 119 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 6 deletions(-) diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index b42085f..5f2fb17 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -48,8 +48,9 @@ use crate::algorithm::{AlgoSimple, AlgorithmAttributes, AlgorithmInformation}; use crate::apdu::command::Command; use crate::apdu::response::RawResponse; use crate::card_do::{ - ApplicationRelatedData, CardholderRelatedData, Fingerprint, KeyGenerationTime, Lang, - PWStatusBytes, SecuritySupportTemplate, Sex, UserInteractionFlag, + ApplicationIdentifier, ApplicationRelatedData, CardholderRelatedData, ExtendedCapabilities, + ExtendedLengthInfo, Fingerprint, HistoricalBytes, KeyGenerationTime, Lang, PWStatusBytes, + SecuritySupportTemplate, Sex, UserInteractionFlag, }; use crate::crypto_data::{CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial}; pub use crate::errors::{Error, StatusBytes}; @@ -112,6 +113,21 @@ impl KeyType { } } +/// A struct to cache immutable information of a card. +/// Some of the data is stored during [`Card::new`]. +/// Other information can optionally be cached later (e.g. `ai`) +#[derive(Debug)] +struct CardImmutable { + aid: ApplicationIdentifier, + ec: ExtendedCapabilities, + hb: Option, // new in v2.0 + eli: Option, // new in v3.0 + + // First `Option` layer encodes if this cache field has been initialized, + // if `Some`, then the second `Option` layer encodes if the field exists on the card. + ai: Option>, // new in v3.4 +} + /// An OpenPGP card object (backed by a CardBackend implementation). /// /// Most users will probably want to use the `PcscCard` backend from the `card-backend-pcsc` crate. @@ -125,6 +141,11 @@ pub struct Card { /// Capabilites of the card, determined from hints by the Backend, /// as well as the Application Related Data card_caps: Option, + + /// A cache data structure for information that is immutable on OpenPGP cards. + /// Some of the information gets initialized when connecting to the card. + /// Other information my be cached on first read. + immutable: Option, } impl Card { @@ -141,9 +162,10 @@ impl Card { let mut op = Self { card, card_caps: None, + immutable: None, }; - let caps = { + let (caps, imm) = { let mut tx = op.transaction()?; tx.select()?; @@ -207,14 +229,25 @@ impl Card { pw3_max, ); + let imm = CardImmutable { + aid: ard.application_id()?, + ec: ard.extended_capabilities()?, + hb: Some(ard.historical_bytes()?), + eli: ard.extended_length_information()?, + ai: None, // FIXME: initialize elsewhere? + }; + drop(tx); - caps + (caps, imm) }; - log::trace!("init_card_caps to: {:x?}", caps); + log::trace!("set card_caps to: {:x?}", caps); op.card_caps = Some(caps); + log::trace!("set immutable card state to: {:x?}", imm); + op.immutable = Some(imm); + Ok(op) } @@ -233,6 +266,8 @@ impl Card { /// They may be reset by the smart card subsystem within seconds, when idle. pub fn transaction(&mut self) -> Result { let card_caps = &mut self.card_caps; + let immutable = &mut self.immutable; // FIXME: unwrap + let tx = self.card.transaction(Some(OPENPGP_APPLICATION))?; if tx.was_reset() { @@ -240,7 +275,11 @@ impl Card { // (E.g.: PIN verifications may have been lost.) } - Ok(Transaction { tx, card_caps }) + Ok(Transaction { + tx, + card_caps, + immutable, + }) } } @@ -256,6 +295,7 @@ impl Card { pub struct Transaction<'a> { tx: Box, card_caps: &'a Option, + immutable: &'a mut Option, } impl<'a> Transaction<'a> { @@ -334,6 +374,73 @@ impl<'a> Transaction<'a> { ))) } + // -- cached card data -- + + /// Get read access to cached immutable card information + fn card_immutable(&self) -> Result<&CardImmutable, Error> { + if let Some(imm) = &self.immutable { + Ok(imm) + } else { + // We expect that self.immutable has been initialized here + Err(Error::InternalError( + "Unexpected state of immutable cache".to_string(), + )) + } + } + + /// Application Identifier. + /// + /// This function returns data that is cached during initialization. + /// Calling it doesn't require sending a command to the card. + pub fn application_identifier(&self) -> Result<&ApplicationIdentifier, Error> { + Ok(&self.card_immutable()?.aid) + } + + /// Extended capabilities. + /// + /// This function returns data that is cached during initialization. + /// Calling it doesn't require sending a command to the card. + pub fn extended_capabilities(&self) -> Result<&ExtendedCapabilities, Error> { + Ok(&self.card_immutable()?.ec) + } + + /// Historical Bytes (if available). + /// + /// This function returns data that is cached during initialization. + /// Calling it doesn't require sending a command to the card. + pub fn historical_bytes(&self) -> Result<&Option, Error> { + Ok(&self.card_immutable()?.hb) + } + + /// Extended length info (if available). + /// + /// This function returns data that is cached during initialization. + /// Calling it doesn't require sending a command to the card. + pub fn extended_length_info(&self) -> Result<&Option, Error> { + Ok(&self.card_immutable()?.eli) + } + + #[allow(dead_code)] + fn algorithm_information_cached(&mut self) -> Result, Error> { + // FIXME: merge this fn with the regular/public `algorithm_information()` fn? + + if self.immutable.is_none() { + // We expect that self.immutable has been initialized here + return Err(Error::InternalError( + "Unexpected state of immutable cache".to_string(), + )); + } + + if self.immutable.as_ref().unwrap().ai.is_none() { + // Cached ai is unset, initialize it now! + + let ai = self.algorithm_information()?; + self.immutable.as_mut().unwrap().ai = Some(ai); + } + + Ok(self.immutable.as_ref().unwrap().ai.clone().unwrap()) + } + // --- login data (5e) --- /// Get URL (5f50) From 86ba745ea6503cafe6d9aea46e87230c88e997b0 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 30 Aug 2023 15:08:15 +0200 Subject: [PATCH 075/115] openpgp-card: use cache for immutable card settings --- openpgp-card/src/card_do.rs | 12 ++++++------ openpgp-card/src/lib.rs | 24 +++++++++++------------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/openpgp-card/src/card_do.rs b/openpgp-card/src/card_do.rs index 4aa408c..47627e6 100644 --- a/openpgp-card/src/card_do.rs +++ b/openpgp-card/src/card_do.rs @@ -588,7 +588,7 @@ impl Display for KeyStatus { } /// Application Identifier (AID) [Spec section 4.2.1] -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct ApplicationIdentifier { application: u8, version: u16, @@ -607,7 +607,7 @@ impl Display for ApplicationIdentifier { } /// Historical Bytes [Spec chapter 6] -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct HistoricalBytes { /// category indicator byte cib: u8, @@ -623,7 +623,7 @@ pub struct HistoricalBytes { } /// Card Capabilities [Spec chapter 6 (Historical Bytes)] -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct CardCapabilities { command_chaining: bool, extended_lc_le: bool, @@ -647,7 +647,7 @@ impl Display for CardCapabilities { } /// Card service data [Spec chapter 6 (Historical Bytes)] -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct CardServiceData { select_by_full_df_name: bool, // Application Selection by full DF name (AID) select_by_partial_df_name: bool, // Application Selection by partial DF name @@ -694,7 +694,7 @@ impl Display for CardServiceData { } /// Extended Capabilities [Spec section 4.4.3.7] -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, Clone, Copy, PartialEq)] pub struct ExtendedCapabilities { secure_messaging: bool, get_challenge: bool, @@ -784,7 +784,7 @@ impl Display for ExtendedCapabilities { } /// Extended length information [Spec section 4.1.3.1] -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, Clone, Copy, PartialEq)] pub struct ExtendedLengthInfo { max_command_bytes: u16, max_response_bytes: u16, diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 5f2fb17..c613478 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -392,32 +392,32 @@ impl<'a> Transaction<'a> { /// /// This function returns data that is cached during initialization. /// Calling it doesn't require sending a command to the card. - pub fn application_identifier(&self) -> Result<&ApplicationIdentifier, Error> { - Ok(&self.card_immutable()?.aid) + pub fn application_identifier(&self) -> Result { + Ok(self.card_immutable()?.aid) } /// Extended capabilities. /// /// This function returns data that is cached during initialization. /// Calling it doesn't require sending a command to the card. - pub fn extended_capabilities(&self) -> Result<&ExtendedCapabilities, Error> { - Ok(&self.card_immutable()?.ec) + pub fn extended_capabilities(&self) -> Result { + Ok(self.card_immutable()?.ec) } /// Historical Bytes (if available). /// /// This function returns data that is cached during initialization. /// Calling it doesn't require sending a command to the card. - pub fn historical_bytes(&self) -> Result<&Option, Error> { - Ok(&self.card_immutable()?.hb) + pub fn historical_bytes(&self) -> Result, Error> { + Ok(self.card_immutable()?.hb) } /// Extended length info (if available). /// /// This function returns data that is cached during initialization. /// Calling it doesn't require sending a command to the card. - pub fn extended_length_info(&self) -> Result<&Option, Error> { - Ok(&self.card_immutable()?.eli) + pub fn extended_length_info(&self) -> Result, Error> { + Ok(self.card_immutable()?.eli) } #[allow(dead_code)] @@ -1302,7 +1302,7 @@ impl<'a> Transaction<'a> { ) -> Result<(), Error> { // An error is ok - it's fine if a card doesn't offer a list of // supported algorithms - let algo_info = self.algorithm_information().unwrap_or(None); + let algo_info = self.algorithm_information_cached().ok().flatten(); keys::key_import(self, key, key_type, algo_info) } @@ -1328,9 +1328,7 @@ impl<'a> Transaction<'a> { ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { // Set algo on card if it's Some if let Some(target_algo) = algorithm_attributes { - // FIXME: caching - let ard = self.application_related_data()?; // no caching, here! - let ecap = ard.extended_capabilities()?; + let ecap = self.extended_capabilities()?; // Only set algo if card supports setting of algo attr if ecap.algo_attrs_changeable() { @@ -1379,7 +1377,7 @@ impl<'a> Transaction<'a> { let ard = self.application_related_data()?; let algorithm_attributes = ard.algorithm_attributes(key_type)?; - let algo_info = self.algorithm_information().ok().flatten(); + let algo_info = self.algorithm_information_cached().ok().flatten(); let algo = simple.determine_algo_attributes(key_type, algorithm_attributes, algo_info)?; Self::generate_key(self, fp_from_pub, key_type, Some(&algo)) From 32095298aad5ed054adef86c595caf529e47d7bb Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 30 Aug 2023 20:27:17 +0200 Subject: [PATCH 076/115] openpgp-card: Minor cleanup --- openpgp-card/src/lib.rs | 121 +++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 69 deletions(-) diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index c613478..dfc7c57 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -447,49 +447,42 @@ impl<'a> Transaction<'a> { pub fn url(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: url"); - let resp = self.send_command(commands::url(), true)?; - - Ok(resp.data()?.to_vec()) + self.send_command(commands::url(), true)?.try_into() } /// Get Login Data (5e) pub fn login_data(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: login_data"); - let resp = self.send_command(commands::login_data(), true)?; - - Ok(resp.data()?.to_vec()) + self.send_command(commands::login_data(), true)?.try_into() } /// Get cardholder related data (65) pub fn cardholder_related_data(&mut self) -> Result { log::info!("OpenPgpTransaction: cardholder_related_data"); - let crd = commands::cardholder_related_data(); - let resp = self.send_command(crd, true)?; - resp.check_ok()?; + let resp = self.send_command(commands::cardholder_related_data(), true)?; - CardholderRelatedData::try_from(resp.data()?) + resp.data()?.try_into() } /// Get security support template (7a) pub fn security_support_template(&mut self) -> Result { log::info!("OpenPgpTransaction: security_support_template"); - let sst = commands::security_support_template(); - let resp = self.send_command(sst, true)?; - resp.check_ok()?; + let resp = self.send_command(commands::security_support_template(), true)?; let tlv = Tlv::try_from(resp.data()?)?; - let res = tlv.find(Tag::from([0x93])).ok_or_else(|| { - Error::NotFound("Couldn't get SecuritySupportTemplate DO".to_string()) + + let dst = tlv.find(Tags::DigitalSignatureCounter).ok_or_else(|| { + Error::NotFound("Couldn't get DigitalSignatureCounter DO".to_string()) })?; - if let Value::S(data) = res { + if let Value::S(data) = dst { let mut data = data.to_vec(); if data.len() != 3 { return Err(Error::ParseError(format!( - "Unexpected length {} for 'Digital signature counter' DO", + "Unexpected length {} for DigitalSignatureCounter DO", data.len() ))); } @@ -524,8 +517,8 @@ impl<'a> Transaction<'a> { pub fn cardholder_certificate(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: cardholder_certificate"); - let cmd = commands::cardholder_certificate(); - self.send_command(cmd, true)?.try_into() + self.send_command(commands::cardholder_certificate(), true)? + .try_into() } /// Call "GET NEXT DATA" for the DO cardholder certificate. @@ -535,8 +528,8 @@ impl<'a> Transaction<'a> { pub fn next_cardholder_certificate(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: next_cardholder_certificate"); - let cmd = commands::get_next_cardholder_certificate(); - self.send_command(cmd, true)?.try_into() + self.send_command(commands::get_next_cardholder_certificate(), true)? + .try_into() } /// Get "Algorithm Information" @@ -544,9 +537,8 @@ impl<'a> Transaction<'a> { log::info!("OpenPgpTransaction: algorithm_information"); let resp = self.send_command(commands::algo_info(), true)?; - resp.check_ok()?; - let ai = AlgorithmInformation::try_from(resp.data()?)?; + let ai = resp.data()?.try_into()?; Ok(Some(ai)) } @@ -554,18 +546,16 @@ impl<'a> Transaction<'a> { pub fn attestation_certificate(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: attestation_certificate"); - let resp = self.send_command(commands::attestation_certificate(), true)?; - - Ok(resp.data()?.into()) + self.send_command(commands::attestation_certificate(), true)? + .try_into() } /// Firmware Version (YubiKey specific (?)) pub fn firmware_version(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: firmware_version"); - let resp = self.send_command(commands::firmware_version(), true)?; - - Ok(resp.data()?.into()) + self.send_command(commands::firmware_version(), true)? + .try_into() } /// Set identity (Nitrokey Start specific (?)). @@ -582,7 +572,7 @@ impl<'a> Transaction<'a> { if let Err(Error::Smartcard(SmartcardError::NotTransacted)) = resp { Ok(vec![]) } else { - Ok(resp?.data()?.into()) + resp?.try_into() } } @@ -631,7 +621,7 @@ impl<'a> Transaction<'a> { // Possible response data (Control Parameter = CP) don't need to be evaluated by the // application (See "7.2.5 SELECT DATA") - self.send_command(cmd, true)?.try_into()?; + self.send_command(cmd, true)?.check_ok()?; Ok(()) } @@ -647,9 +637,7 @@ impl<'a> Transaction<'a> { assert!((1..=4).contains(&num)); let cmd = commands::private_use_do(num); - let resp = self.send_command(cmd, true)?; - - Ok(resp.data()?.to_vec()) + self.send_command(cmd, true)?.try_into() } // ---------- @@ -721,8 +709,9 @@ impl<'a> Transaction<'a> { pub fn verify_pw1_sign(&mut self, pin: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: verify_pw1_sign"); - let verify = commands::verify_pw1_81(pin.to_vec()); - self.send_command(verify, false)?.try_into() + let cmd = commands::verify_pw1_81(pin.to_vec()); + + self.send_command(cmd, false)?.try_into() } /// Verify pw1 (user) for signing operation (mode 81) using a @@ -894,8 +883,8 @@ impl<'a> Transaction<'a> { ) -> Result<(), Error> { log::info!("OpenPgpTransaction: reset_retry_counter_pw1"); - let reset = commands::reset_retry_counter_pw1(resetting_code, new_pw1); - self.send_command(reset, false)?.try_into() + let cmd = commands::reset_retry_counter_pw1(resetting_code, new_pw1); + self.send_command(cmd, false)?.try_into() } // --- decrypt --- @@ -946,9 +935,8 @@ impl<'a> Transaction<'a> { // The OpenPGP card is already connected and PW1 82 has been verified let dec_cmd = commands::decryption(data); let resp = self.send_command(dec_cmd, true)?; - resp.check_ok()?; - Ok(resp.data().map(|d| d.to_vec())?) + Ok(resp.data()?.to_vec()) } /// Set the key to be used for the pso_decipher and the internal_authenticate commands. @@ -1069,41 +1057,39 @@ impl<'a> Transaction<'a> { pub fn set_login(&mut self, login: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_login"); - let put_login_data = commands::put_login_data(login.to_vec()); - self.send_command(put_login_data, false)?.try_into() + + let cmd = commands::put_login_data(login.to_vec()); + self.send_command(cmd, false)?.try_into() } pub fn set_name(&mut self, name: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_name"); - let put_name = commands::put_name(name.to_vec()); - self.send_command(put_name, false)?.try_into() + let cmd = commands::put_name(name.to_vec()); + self.send_command(cmd, false)?.try_into() } pub fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_lang"); - let bytes: Vec = lang - .iter() - .flat_map(|&l| Into::>::into(l)) - .collect(); + let bytes: Vec<_> = lang.iter().flat_map(|&l| Vec::::from(l)).collect(); - let put_lang = commands::put_lang(bytes); - self.send_command(put_lang, false)?.try_into() + let cmd = commands::put_lang(bytes); + self.send_command(cmd, false)?.try_into() } pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_sex"); - let put_sex = commands::put_sex((&sex).into()); - self.send_command(put_sex, false)?.try_into() + let cmd = commands::put_sex((&sex).into()); + self.send_command(cmd, false)?.try_into() } pub fn set_url(&mut self, url: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_url"); - let put_url = commands::put_url(url.to_vec()); - self.send_command(put_url, false)?.try_into() + let cmd = commands::put_url(url.to_vec()); + self.send_command(cmd, false)?.try_into() } /// Set cardholder certificate (for AUT, DEC or SIG). @@ -1159,30 +1145,30 @@ impl<'a> Transaction<'a> { pub fn set_fingerprint(&mut self, fp: Fingerprint, key_type: KeyType) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_fingerprint"); - let fp_cmd = commands::put_data(key_type.fingerprint_put_tag(), fp.as_bytes().to_vec()); + let cmd = commands::put_data(key_type.fingerprint_put_tag(), fp.as_bytes().to_vec()); - self.send_command(fp_cmd, false)?.try_into() + self.send_command(cmd, false)?.try_into() } pub fn set_ca_fingerprint_1(&mut self, fp: Fingerprint) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_ca_fingerprint_1"); - let fp_cmd = commands::put_data(Tags::CaFingerprint1, fp.as_bytes().to_vec()); - self.send_command(fp_cmd, false)?.try_into() + let cmd = commands::put_data(Tags::CaFingerprint1, fp.as_bytes().to_vec()); + self.send_command(cmd, false)?.try_into() } pub fn set_ca_fingerprint_2(&mut self, fp: Fingerprint) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_ca_fingerprint_2"); - let fp_cmd = commands::put_data(Tags::CaFingerprint2, fp.as_bytes().to_vec()); - self.send_command(fp_cmd, false)?.try_into() + let cmd = commands::put_data(Tags::CaFingerprint2, fp.as_bytes().to_vec()); + self.send_command(cmd, false)?.try_into() } pub fn set_ca_fingerprint_3(&mut self, fp: Fingerprint) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_ca_fingerprint_3"); - let fp_cmd = commands::put_data(Tags::CaFingerprint3, fp.as_bytes().to_vec()); - self.send_command(fp_cmd, false)?.try_into() + let cmd = commands::put_data(Tags::CaFingerprint3, fp.as_bytes().to_vec()); + self.send_command(cmd, false)?.try_into() } pub fn set_creation_time( @@ -1201,9 +1187,9 @@ impl<'a> Transaction<'a> { .copied() .collect(); - let time_cmd = commands::put_data(key_type.timestamp_put_tag(), time_value); + let cmd = commands::put_data(key_type.timestamp_put_tag(), time_value); - self.send_command(time_cmd, false)?.try_into() + self.send_command(cmd, false)?.try_into() } // FIXME: optional DO SM-Key-ENC @@ -1227,13 +1213,10 @@ impl<'a> Transaction<'a> { pub fn set_pso_enc_dec_key(&mut self, key: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_pso_enc_dec_key"); - let fp_cmd = commands::put_data(Tags::PsoEncDecKey, key.to_vec()); - - self.send_command(fp_cmd, false)?.try_into() + let cmd = commands::put_data(Tags::PsoEncDecKey, key.to_vec()); + self.send_command(cmd, false)?.try_into() } - // FIXME: optional DO for PSO:ENC/DEC with AES - /// Set UIF for PSO:CDS pub fn set_uif_pso_cds(&mut self, uif: &UserInteractionFlag) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_uif_pso_cds"); From b1c4b46b22b93c3a209cbd62f1b07251c91f63f1 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 31 Aug 2023 00:37:53 +0200 Subject: [PATCH 077/115] openpgp-card: Rework key import functions --- openpgp-card/src/keys.rs | 114 ++++++++++++++++++++++++++++----------- openpgp-card/src/lib.rs | 6 +-- 2 files changed, 85 insertions(+), 35 deletions(-) diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index f098f65..6d80af8 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -142,58 +142,112 @@ pub(crate) fn public_key( /// Import private key material to the card as a specific KeyType. /// -/// If the key is suitable for `key_type`, an Error is returned (either +/// If the key is unsuitable for `key_type`, an Error is returned (either /// caused by checks before attempting to upload the key to the card, or by /// an error that the card reports during an attempt to upload the key). pub(crate) fn key_import( card_tx: &mut Transaction, key: Box, key_type: KeyType, - algo_info: Option, ) -> Result<(), Error> { log::info!("OpenPgpTransaction: key_import"); + match key.private_key()? { + PrivateKeyMaterial::R(rsa_key) => key_import_rsa( + card_tx, + key_type, + rsa_key, + key.fingerprint()?, + key.timestamp(), + ), + PrivateKeyMaterial::E(ecc_key) => key_import_ecc( + card_tx, + key_type, + ecc_key, + key.fingerprint()?, + key.timestamp(), + ), + } +} + +fn key_import_rsa( + card_tx: &mut Transaction, + key_type: KeyType, + rsa_key: Box, + fp: Fingerprint, + ts: KeyGenerationTime, +) -> Result<(), Error> { + // An error is ok (it's fine if a card doesn't offer a list of supported algorithms) + let algo_info = card_tx.algorithm_information_cached().ok().flatten(); + + // RSA bitsize + // (round up to 4-bytes, in case the key has 8+ leading zero bits) + let rsa_bits = (((rsa_key.n().len() * 8 + 31) / 32) * 32) as u16; + // FIXME: caching? let ard = card_tx.application_related_data()?; - let (algo, key_cmd) = match key.private_key()? { - PrivateKeyMaterial::R(rsa_key) => { - // RSA bitsize - // (round up to 4-bytes, in case the key has 8+ leading zero bits) - let rsa_bits = (((rsa_key.n().len() * 8 + 31) / 32) * 32) as u16; + let algo_attr = ard.algorithm_attributes(key_type)?; + let rsa_attrs = determine_rsa_attrs(rsa_bits, key_type, algo_attr, algo_info)?; - let algo_attr = ard.algorithm_attributes(key_type)?; - let rsa_attrs = determine_rsa_attrs(rsa_bits, key_type, algo_attr, algo_info)?; - - let key_cmd = rsa_key_import_cmd(key_type, rsa_key, &rsa_attrs)?; - - (AlgorithmAttributes::Rsa(rsa_attrs), key_cmd) - } - PrivateKeyMaterial::E(ecc_key) => { - let ecc_attrs = - determine_ecc_attrs(ecc_key.oid(), ecc_key.ecc_type(), key_type, algo_info)?; - - let key_cmd = ecc_key_import_cmd(key_type, ecc_key, &ecc_attrs)?; - - (AlgorithmAttributes::Ecc(ecc_attrs), key_cmd) - } - }; - - let fp = key.fingerprint()?; + let import_cmd = rsa_key_import_cmd(key_type, rsa_key, &rsa_attrs)?; // Now that we have marshalled all necessary information, perform all // set-operations on the card. + import_key_set_metadata( + card_tx, + key_type, + import_cmd, + fp, + ts, + AlgorithmAttributes::Rsa(rsa_attrs), + ) +} + +fn key_import_ecc( + card_tx: &mut Transaction, + key_type: KeyType, + ecc_key: Box, + fp: Fingerprint, + ts: KeyGenerationTime, +) -> Result<(), Error> { + // An error is ok (it's fine if a card doesn't offer a list of supported algorithms) + let algo_info = card_tx.algorithm_information_cached().ok().flatten(); + + let ecc_attrs = determine_ecc_attrs(ecc_key.oid(), ecc_key.ecc_type(), key_type, algo_info)?; + let import_cmd = ecc_key_import_cmd(key_type, ecc_key, &ecc_attrs)?; + + // Now that we have marshalled all necessary information, perform all + // set-operations on the card. + import_key_set_metadata( + card_tx, + key_type, + import_cmd, + fp, + ts, + AlgorithmAttributes::Ecc(ecc_attrs), + ) +} + +fn import_key_set_metadata( + card_tx: &mut Transaction, + key_type: KeyType, + import_cmd: Command, + fp: Fingerprint, + ts: KeyGenerationTime, + algorithm_attributes: AlgorithmAttributes, +) -> Result<(), Error> { + log::info!("Import key material"); // Only set algo attrs if "Extended Capabilities" lists the feature - if ard.extended_capabilities()?.algo_attrs_changeable() { - card_tx.set_algorithm_attributes(key_type, &algo)?; + if card_tx.extended_capabilities()?.algo_attrs_changeable() { + card_tx.set_algorithm_attributes(key_type, &algorithm_attributes)?; } - log::info!("Import key material"); - card_tx.send_command(key_cmd, false)?.check_ok()?; + card_tx.send_command(import_cmd, false)?.check_ok()?; card_tx.set_fingerprint(fp, key_type)?; - card_tx.set_creation_time(key.timestamp(), key_type)?; + card_tx.set_creation_time(ts, key_type)?; Ok(()) } diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index dfc7c57..d48e87f 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -1283,11 +1283,7 @@ impl<'a> Transaction<'a> { key: Box, key_type: KeyType, ) -> Result<(), Error> { - // An error is ok - it's fine if a card doesn't offer a list of - // supported algorithms - let algo_info = self.algorithm_information_cached().ok().flatten(); - - keys::key_import(self, key, key_type, algo_info) + keys::key_import(self, key, key_type) } /// Generate a key on the card. From 0067fe1d48a23adf4e1f512c95c1d45cf53eb3d4 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 31 Aug 2023 12:24:52 +0200 Subject: [PATCH 078/115] openpgp-card: don't PUT algorithm_attributes if feature is unsupported And improve rustdocs. --- openpgp-card/src/lib.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index d48e87f..b7c6e06 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -1103,17 +1103,32 @@ impl<'a> Transaction<'a> { self.send_command(cmd, false)?.try_into() } - /// Set algorithm attributes - /// (4.4.3.9 Algorithm Attributes) + /// Set algorithm attributes for a key slot (4.4.3.9 Algorithm Attributes) + /// + /// Note: `algorithm_attributes` needs to precisely specify the + /// RSA bit-size of e (if applicable), and import format, with values + /// that the current card supports. pub fn set_algorithm_attributes( &mut self, key_type: KeyType, - algo: &AlgorithmAttributes, + algorithm_attributes: &AlgorithmAttributes, ) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_algorithm_attributes"); + // Don't set algorithm if the feature is not available? + let ecap = self.extended_capabilities()?; + if !ecap.algo_attrs_changeable() { + // Don't change the algorithm attributes, if the card doesn't support change + // FIXME: Compare current and requested setting and return an error, if they differ? + + return Ok(()); + } + // Command to PUT the algorithm attributes - let cmd = commands::put_data(key_type.algorithm_tag(), algo.to_data_object()?); + let cmd = commands::put_data( + key_type.algorithm_tag(), + algorithm_attributes.to_data_object()?, + ); self.send_command(cmd, false)?.try_into() } From a54b057e00df692e56cebe8bd9ebf28aa5435507 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 31 Aug 2023 13:00:18 +0200 Subject: [PATCH 079/115] openpgp-card: Add matching_algorithm_attributes() in AlgoSimple Gets matching AlgorithmAttributes for the current card. --- openpgp-card-sequoia/src/lib.rs | 9 ++++++--- openpgp-card/src/algorithm.rs | 21 ++++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 12178f2..355316a 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -969,9 +969,12 @@ impl Card> { key_type: KeyType, algo: Option, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { - match algo { - Some(algo) => self.card().generate_key_simple(Self::ptf, key_type, algo), - None => self.card().generate_key(Self::ptf, key_type, None), + if let Some(algo) = algo { + // set algorithm attributes + let attr = algo.matching_algorithm_attributes(self.card(), key_type)?; + self.card().set_algorithm_attributes(key_type, &attr)?; } + + self.card().generate_key(Self::ptf, key_type, None) } } diff --git a/openpgp-card/src/algorithm.rs b/openpgp-card/src/algorithm.rs index e9cb9a7..aa1aeae 100644 --- a/openpgp-card/src/algorithm.rs +++ b/openpgp-card/src/algorithm.rs @@ -13,7 +13,7 @@ use std::convert::TryFrom; use std::fmt; use crate::crypto_data::EccType; -use crate::{keys, oid, Error, KeyType}; +use crate::{keys, oid, Error, KeyType, Transaction}; /// A shorthand way to specify algorithms (e.g. for key generation). #[derive(Clone, Copy, Debug)] @@ -50,6 +50,25 @@ impl TryFrom<&str> for AlgoSimple { } impl AlgoSimple { + /// Get algorithm attributes for slot `key_type` from this AlgoSimple. + /// + /// AlgoSimple doesn't specify card specific details (such as bit-size + /// of e for RSA, and import format). + /// This function determines these values based on information from the + /// card behind `tx`. + pub fn matching_algorithm_attributes( + &self, + tx: &mut Transaction, + key_type: KeyType, + ) -> Result { + let ard = tx.application_related_data()?; + let algorithm_attributes = ard.algorithm_attributes(key_type)?; + + let algo_info = tx.algorithm_information_cached().ok().flatten(); + + self.determine_algo_attributes(key_type, algorithm_attributes, algo_info) + } + /// Get corresponding EccType by KeyType (except for Curve25519) fn ecc_type(key_type: KeyType) -> EccType { match key_type { From 82662e6d460eca62067f93b09a0df2b039845e9e Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 31 Aug 2023 13:00:49 +0200 Subject: [PATCH 080/115] openpgp-card: Remove algorithm setting from key generation Also entirely remove set_algorithm_attributes_simple. Callers should use AlgoSimple::matching_algorithm_attributes to determine the appropriate AlgorithmAttributes. --- openpgp-card-sequoia/src/lib.rs | 2 +- openpgp-card/src/lib.rs | 60 +-------------------------------- 2 files changed, 2 insertions(+), 60 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 355316a..5f45049 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -975,6 +975,6 @@ impl Card> { self.card().set_algorithm_attributes(key_type, &attr)?; } - self.card().generate_key(Self::ptf, key_type, None) + self.card().generate_key(Self::ptf, key_type) } } diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index b7c6e06..3073338 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -44,7 +44,7 @@ use std::convert::{TryFrom, TryInto}; use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError}; use tags::{ShortTag, Tags}; -use crate::algorithm::{AlgoSimple, AlgorithmAttributes, AlgorithmInformation}; +use crate::algorithm::{AlgorithmAttributes, AlgorithmInformation}; use crate::apdu::command::Command; use crate::apdu::response::RawResponse; use crate::card_do::{ @@ -1303,13 +1303,6 @@ impl<'a> Transaction<'a> { /// Generate a key on the card. /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) - /// - /// If the `algorithm_attributes` parameter is Some, then that algorithm will be set on - /// the card for the `key_type` slot. - /// - /// Note: `algorithm_attributes` needs to precisely specify the RSA bit-size of e (if - /// applicable), and import format, with values that the current card - /// supports. pub fn generate_key( &mut self, fp_from_pub: fn( @@ -1318,30 +1311,7 @@ impl<'a> Transaction<'a> { KeyType, ) -> Result, key_type: KeyType, - algorithm_attributes: Option<&AlgorithmAttributes>, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { - // Set algo on card if it's Some - if let Some(target_algo) = algorithm_attributes { - let ecap = self.extended_capabilities()?; - - // Only set algo if card supports setting of algo attr - if ecap.algo_attrs_changeable() { - self.set_algorithm_attributes(key_type, target_algo)?; - } else { - // Check if the current algo on the card is the one we want, if - // not we return an error. - - // NOTE: For RSA, the target algo shouldn't prescribe an - // Import-Format. The Import-Format should always depend on what - // the card supports. - - // let cur_algo = ard.get_algorithm_attributes(key_type)?; - // assert_eq!(&cur_algo, target_algo); - - // FIXME: return error? - } - } - // get current (possibly updated) state of algorithm_attributes let ard = self.application_related_data()?; // no caching, here! let cur_algo = ard.algorithm_attributes(key_type)?; @@ -1349,34 +1319,6 @@ impl<'a> Transaction<'a> { keys::gen_key_set_metadata(self, fp_from_pub, &cur_algo, key_type) } - /// Generate a key on the card. - /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) - /// - /// This is a wrapper around [`Self::generate_key`] which allows - /// using the simplified [`AlgoSimple`] algorithm selector enum. - /// - /// Note: AlgoSimple doesn't specify card specific details (such as - /// bitsize of e for RSA, and import format). This function determines - /// these values based on information from the card. - pub fn generate_key_simple( - &mut self, - fp_from_pub: fn( - &PublicKeyMaterial, - KeyGenerationTime, - KeyType, - ) -> Result, - key_type: KeyType, - simple: AlgoSimple, - ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { - let ard = self.application_related_data()?; - let algorithm_attributes = ard.algorithm_attributes(key_type)?; - - let algo_info = self.algorithm_information_cached().ok().flatten(); - - let algo = simple.determine_algo_attributes(key_type, algorithm_attributes, algo_info)?; - Self::generate_key(self, fp_from_pub, key_type, Some(&algo)) - } - /// Get public key material from the card. /// /// Note: this fn returns a set of raw public key data (not an From 4d5d9e7ee14cca69b2aa70a4a90519fb23e76daf Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 2 Sep 2023 16:46:56 +0200 Subject: [PATCH 081/115] openpgp-card: allow additional status in factory reset The opcard-rs implementation may return StatusBytes::ExecutionErrorNonVolatileMemoryUnchanged in some circumstances. Also see https://gitlab.com/openpgp-card/openpgp-card/-/issues/70 Fixes #70 --- openpgp-card/src/errors.rs | 4 ++++ openpgp-card/src/lib.rs | 37 +++++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/openpgp-card/src/errors.rs b/openpgp-card/src/errors.rs index 02d93c4..79eea68 100644 --- a/openpgp-card/src/errors.rs +++ b/openpgp-card/src/errors.rs @@ -73,6 +73,9 @@ pub enum StatusBytes { #[error("Password not checked, {0} allowed retries")] PasswordNotChecked(u8), + #[error("Execution error with non-volatile memory unchanged")] + ExecutionErrorNonVolatileMemoryUnchanged, + #[error("Triggering by the card {0}")] TriggeringByCard(u8), @@ -145,6 +148,7 @@ impl From<(u8, u8)> for StatusBytes { (0x62, 0x85) => StatusBytes::TerminationState, (0x63, 0xC0..=0xCF) => StatusBytes::PasswordNotChecked(status.1 & 0xf), + (0x64, 0x00) => StatusBytes::ExecutionErrorNonVolatileMemoryUnchanged, (0x64, 0x02..=0x80) => StatusBytes::TriggeringByCard(status.1), (0x65, 0x01) => StatusBytes::MemoryFailure, (0x66, 0x00) => StatusBytes::SecurityRelatedIssues, diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 3073338..bf085a2 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -661,15 +661,18 @@ impl<'a> Transaction<'a> { log::info!("OpenPgpTransaction: factory_reset"); // send 4 bad requests to verify pw1 - // [apdu 00 20 00 81 08 40 40 40 40 40 40 40 40] for _ in 0..4 { - log::info!(" verify_pw1_81"); - let verify = commands::verify_pw1_81([0x40; 8].to_vec()); - let resp = self.send_command(verify, false)?; - if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied - || resp.status() == StatusBytes::AuthenticationMethodBlocked - || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) - { + let resp = self.verify_pw1_sign(&[0x40; 8]); + + if !(matches!( + resp, + Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) + | Err(Error::CardStatus(StatusBytes::AuthenticationMethodBlocked)) + | Err(Error::CardStatus( + StatusBytes::ExecutionErrorNonVolatileMemoryUnchanged + )) + | Err(Error::CardStatus(StatusBytes::PasswordNotChecked(_))) + )) { return Err(Error::InternalError( "Unexpected status for reset, at pw1.".into(), )); @@ -677,16 +680,18 @@ impl<'a> Transaction<'a> { } // send 4 bad requests to verify pw3 - // [apdu 00 20 00 83 08 40 40 40 40 40 40 40 40] for _ in 0..4 { - log::info!(" verify_pw3"); - let verify = commands::verify_pw3([0x40; 8].to_vec()); - let resp = self.send_command(verify, false)?; + let resp = self.verify_pw3(&[0x40; 8]); - if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied - || resp.status() == StatusBytes::AuthenticationMethodBlocked - || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) - { + if !(matches!( + resp, + Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) + | Err(Error::CardStatus(StatusBytes::AuthenticationMethodBlocked)) + | Err(Error::CardStatus( + StatusBytes::ExecutionErrorNonVolatileMemoryUnchanged + )) + | Err(Error::CardStatus(StatusBytes::PasswordNotChecked(_))) + )) { return Err(Error::InternalError( "Unexpected status for reset, at pw3.".into(), )); From 8ee389ca7a58145474d2a9313ca1e6e3c7a923ab Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 3 Sep 2023 23:12:03 +0200 Subject: [PATCH 082/115] openpgp-card: attestation_key_generation_time() only needs &self --- openpgp-card/src/card_do.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card/src/card_do.rs b/openpgp-card/src/card_do.rs index 47627e6..a3448e0 100644 --- a/openpgp-card/src/card_do.rs +++ b/openpgp-card/src/card_do.rs @@ -250,7 +250,7 @@ impl ApplicationRelatedData { } /// Get Attestation key generation time. - pub fn attestation_key_generation_time(&mut self) -> Result, Error> { + pub fn attestation_key_generation_time(&self) -> Result, Error> { match self.0.find(Tags::GenerationTimeAttestation) { None => Ok(None), Some(data) => { From 724be38e4f02e55573331f09f2b5f54f61a94a1e Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 5 Sep 2023 10:47:04 +0200 Subject: [PATCH 083/115] openpgp-card: in private_use_do accessors, don't panic, but return error for illegal num. Fix: set_private_use_do() doesn't return data. --- openpgp-card/src/lib.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index bf085a2..9633b68 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -634,7 +634,12 @@ impl<'a> Transaction<'a> { pub fn private_use_do(&mut self, num: u8) -> Result, Error> { log::info!("OpenPgpTransaction: private_use_do"); - assert!((1..=4).contains(&num)); + if !(1..=4).contains(&num) { + return Err(Error::UnsupportedFeature(format!( + "Illegal Private Use DO num '{}'", + num, + ))); + } let cmd = commands::private_use_do(num); self.send_command(cmd, true)?.try_into() @@ -1049,15 +1054,18 @@ impl<'a> Transaction<'a> { /// Access condition: /// - 1/3 need PW1 (82) /// - 2/4 need PW3 - pub fn set_private_use_do(&mut self, num: u8, data: Vec) -> Result, Error> { + pub fn set_private_use_do(&mut self, num: u8, data: Vec) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_private_use_do"); - assert!((1..=4).contains(&num)); + if !(1..=4).contains(&num) { + return Err(Error::UnsupportedFeature(format!( + "Illegal Private Use DO num '{}'", + num, + ))); + } let cmd = commands::put_private_use_do(num, data); - let resp = self.send_command(cmd, true)?; - - Ok(resp.data()?.to_vec()) + self.send_command(cmd, true)?.try_into() } pub fn set_login(&mut self, login: &[u8]) -> Result<(), Error> { From 465847ea793f2b2193b585c5dde5784d8c665004 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 5 Sep 2023 13:07:37 +0200 Subject: [PATCH 084/115] openpgp-card: ExtendedCapabilities, make v2 command length getters private. Command length negotiation is handled in this library, this information should never be useful to callers. --- openpgp-card/src/card_do/extended_cap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpgp-card/src/card_do/extended_cap.rs b/openpgp-card/src/card_do/extended_cap.rs index 7a1a813..d5a4c9a 100644 --- a/openpgp-card/src/card_do/extended_cap.rs +++ b/openpgp-card/src/card_do/extended_cap.rs @@ -21,7 +21,7 @@ impl ExtendedCapabilities { /// /// (For OpenPGP card version 3.x, see /// [`crate::card_do::ExtendedLengthInfo`]) - pub fn max_cmd_len(&self) -> Option { + pub(crate) fn max_cmd_len(&self) -> Option { self.max_cmd_len } @@ -29,7 +29,7 @@ impl ExtendedCapabilities { /// /// (For OpenPGP card version 3.x, see /// [`crate::card_do::ExtendedLengthInfo`]) - pub fn max_resp_len(&self) -> Option { + pub(crate) fn max_resp_len(&self) -> Option { self.max_resp_len } } From 536bcf788bf1fce603750541cd8674a14f9d69bb Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 5 Sep 2023 14:58:28 +0200 Subject: [PATCH 085/115] openpgp-card: ExtendedCapabilities, add getters for capabilities --- openpgp-card/src/card_do/extended_cap.rs | 78 +++++++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/openpgp-card/src/card_do/extended_cap.rs b/openpgp-card/src/card_do/extended_cap.rs index d5a4c9a..cb4510c 100644 --- a/openpgp-card/src/card_do/extended_cap.rs +++ b/openpgp-card/src/card_do/extended_cap.rs @@ -9,12 +9,86 @@ use crate::card_do::ExtendedCapabilities; use crate::Error; impl ExtendedCapabilities { + /// Secure Messaging supported. + /// + /// (This feature is currently only available in the SmartPGP implementation) + pub fn secure_messaging(&self) -> bool { + self.secure_messaging + } + + /// Support for GET CHALLENGE. + /// + /// (GET CHALLENGE generates a random number of a specified length on the smart card) + pub fn get_challenge(&self) -> bool { + self.get_challenge + } + + /// Maximum length of random number that can be requested from the card + /// (if GET CHALLENGE is supported). + /// + /// If GET CHALLENGE is not supported, the coding is 0 + pub fn max_len_challenge(&self) -> u16 { + self.max_len_challenge + } + + /// Support for Key Import + pub fn key_import(&self) -> bool { + self.key_import + } + + /// PW Status changeable + /// (also see [`crate::card_do::PWStatusBytes`]) + pub fn pw_status_change(&self) -> bool { + self.pw_status_change + } + + /// Support for Private use DOs + pub fn private_use_dos(&self) -> bool { + self.private_use_dos + } + + /// Algorithm attributes changeable + /// (also see [`crate::algorithm::AlgorithmAttributes`]) + pub fn algo_attrs_changeable(&self) -> bool { + self.algo_attrs_changeable + } + + /// Support for encryption/decryption with AES + pub fn aes(&self) -> bool { + self.aes + } + + /// KDF-related functionality available + pub fn kdf_do(&self) -> bool { + self.kdf_do + } + + /// Maximum length of Cardholder Certificates + pub fn max_len_cardholder_cert(&self) -> u16 { + self.max_len_cardholder_cert + } + + /// Maximum length of "special DOs" + /// (Private Use, Login data, URL, Algorithm attributes, KDF etc.) + /// + /// (OpenPGP card version 3.x only) pub fn max_len_special_do(&self) -> Option { self.max_len_special_do } - pub fn algo_attrs_changeable(&self) -> bool { - self.algo_attrs_changeable + /// (Private Use, Login data, URL, Algorithm attributes, KDF etc.) + /// + /// (OpenPGP card version 3.x only) + pub fn pin_block_2_format_support(&self) -> Option { + self.pin_block_2_format_support + } + + /// MANAGE SECURITY ENVIRONMENT supported (for DEC and AUT keys). + /// (See [`crate::Transaction::manage_security_environment`]) + /// + /// (OpenPGP card version 3.x only) + pub fn mse_command_support(&self) -> Option { + self.mse_command_support } /// Only available in OpenPGP card version 2.x From e476103e6d6b63d5172b2af1c6967e068b2c7f11 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 5 Sep 2023 00:13:10 +0200 Subject: [PATCH 086/115] In select_data(): fold yk_workaround parameter into the openpgp-card business logic --- openpgp-card-sequoia/src/lib.rs | 4 ++-- openpgp-card/src/lib.rs | 35 ++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 5f45049..3020b81 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -560,8 +560,8 @@ impl<'a> Card> { } /// SELECT DATA ("select a DO in the current template"). - pub fn select_data(&mut self, num: u8, tag: &[u8], yk_workaround: bool) -> Result<(), Error> { - self.state.opt.select_data(num, tag, yk_workaround) + pub fn select_data(&mut self, num: u8, tag: &[u8]) -> Result<(), Error> { + self.state.opt.select_data(num, tag) } /// Get cardholder certificate. diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 9633b68..3369f22 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -582,13 +582,7 @@ impl<'a> Transaction<'a> { /// [`cardholder_certificate`](Transaction::cardholder_certificate) and /// [`set_cardholder_certificate`](Transaction::set_cardholder_certificate) /// in OpenPGP card. - /// - /// `yk_workaround`: YubiKey 5 up to (and including) firmware version 5.4.3 need a workaround - /// for this command. Set to `true` to apply this workaround. - /// (When sending the SELECT DATA command as defined in the card spec, without enabling the - /// workaround, bad YubiKey firmware versions (<= 5.4.3) return - /// [`IncorrectParametersCommandDataField`](StatusBytes::IncorrectParametersCommandDataField)) - /// + /// (This library leaves it up to consumers to decide on a strategy for dealing with this /// issue. Possible strategies include: /// - asking the card for its [`Transaction::firmware_version`] @@ -597,7 +591,7 @@ impl<'a> Transaction<'a> { /// returns [`StatusBytes::IncorrectParametersCommandDataField`] /// - for read operations: using [`Transaction::next_cardholder_certificate`] /// instead of SELECT DATA) - pub fn select_data(&mut self, num: u8, tag: &[u8], yk_workaround: bool) -> Result<(), Error> { + pub fn select_data(&mut self, num: u8, tag: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: select_data"); let tlv = Tlv::new( @@ -607,14 +601,27 @@ impl<'a> Transaction<'a> { let mut data = tlv.serialize(); - if yk_workaround { - // Workaround for YubiKey 5. - // This hack is needed <= 5.4.3 according to ykman sources - // (see _select_certificate() in ykman/openpgp.py). + // YubiKey 5 up to (and including) firmware version 5.4.3 need a workaround + // for this command. + // + // When sending the SELECT DATA command as defined in the card spec, without enabling the + // workaround, bad YubiKey firmware versions (<= 5.4.3) return + // `StatusBytes::IncorrectParametersCommandDataField` + // + // FIXME: caching for `firmware_version`? + if let Ok(version) = self.firmware_version() { + if version.len() == 3 + && version[0] == 5 + && (version[1] < 4 || (version[1] == 4 && version[2] <= 3)) + { + // Workaround for YubiKey 5. + // This hack is needed <= 5.4.3 according to ykman sources + // (see _select_certificate() in ykman/openpgp.py). - assert!(data.len() <= 255); // catch blatant misuse: tags are 1-2 bytes long + assert!(data.len() <= 255); // catch blatant misuse: tags are 1-2 bytes long - data.insert(0, data.len() as u8); + data.insert(0, data.len() as u8); + } } let cmd = commands::select_data(num, data); From 31eee9e7383bd5109c3382083c8d9b8b4470b02d Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 2 Sep 2023 23:16:42 +0200 Subject: [PATCH 087/115] backend: add CardBackend::limit_card_caps This mechanism allows the pcsc backend to signal to openpgp-card that a reader doesn't support "extended length". --- card-backend/src/lib.rs | 5 +++++ openpgp-card/src/lib.rs | 23 +++++++---------------- pcsc/src/lib.rs | 19 +++++++++++++++++++ scdc/src/lib.rs | 13 +++++++++++++ 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/card-backend/src/lib.rs b/card-backend/src/lib.rs index c0851a0..ef534c4 100644 --- a/card-backend/src/lib.rs +++ b/card-backend/src/lib.rs @@ -12,6 +12,11 @@ /// A [CardBackend] is only used to get access to a [CardTransaction] object, /// which supports transmitting commands to the card. pub trait CardBackend { + /// If a CardBackend introduces a additional (possibly backend-specific) + /// limits for any fields in CardCaps, this fn can indicate that limit by + /// returning an amended [`CardCaps`]. + fn limit_card_caps(&self, card_caps: CardCaps) -> CardCaps; + fn transaction( &mut self, reselect_application: Option<&[u8]>, diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 3369f22..f327aea 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -188,7 +188,7 @@ impl Card { let ext_cap = ard.extended_capabilities()?; // Get max command/response byte sizes from card - let (mut max_cmd_bytes, max_rsp_bytes) = if let Ok(Some(eli)) = + let (max_cmd_bytes, max_rsp_bytes) = if let Ok(Some(eli)) = ard.extended_length_information() { // In card 3.x, max lengths come from ExtendedLengthInfo @@ -205,21 +205,6 @@ impl Card { let pw1_max = pw_status.pw1_max_len(); let pw3_max = pw_status.pw3_max_len(); - // If the CardTransaction implementation has an inherent limit for the cmd - // size, take that limit into account. - // (E.g. when using scdaemon as a CardTransaction backend, there is a - // limitation to 1000 bytes length for Assuan commands, which - // translates to maximum command length of a bit under 500 bytes) - if let Some(max_card_cmd_bytes) = tx.tx.max_cmd_len() { - max_cmd_bytes = u16::min(max_cmd_bytes, max_card_cmd_bytes as u16); - } - - // FIXME: add a more general mechanism to ask the backend for - // amendments to the CardCaps (e.g. to change support for - // "extended length") - // - // Also see https://blog.apdu.fr/posts/2011/05/extended-apdu-status-per-reader/ - let caps = CardCaps::new( ext_support, chaining_support, @@ -239,6 +224,12 @@ impl Card { drop(tx); + // General mechanism to ask the backend for amendments to + // the CardCaps (e.g. to change support for "extended length") + // + // Also see https://blog.apdu.fr/posts/2011/05/extended-apdu-status-per-reader/ + let caps = op.card.limit_card_caps(caps); + (caps, imm) }; diff --git a/pcsc/src/lib.rs b/pcsc/src/lib.rs index 36fc681..71f5d18 100644 --- a/pcsc/src/lib.rs +++ b/pcsc/src/lib.rs @@ -546,6 +546,25 @@ impl PcscBackend { } impl CardBackend for PcscBackend { + fn limit_card_caps(&self, card_caps: CardCaps) -> CardCaps { + let mut ext = card_caps.ext_support(); + + // Disable "extended length" support when the card reader is known not to support it + if self.reader_name.starts_with("ACS ACR122U") { + log::debug!("Disabling ext_support for reader {}", self.reader_name); + ext = false; + } + + CardCaps::new( + ext, + card_caps.chaining_support(), + card_caps.max_cmd_bytes(), + card_caps.max_rsp_bytes(), + card_caps.pw1_max_len(), + card_caps.pw3_max_len(), + ) + } + /// Get a CardTransaction for this PcscBackend (this starts a transaction) fn transaction( &mut self, diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index 9e80a08..9dacb4c 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -212,6 +212,19 @@ impl ScdBackend { } impl CardBackend for ScdBackend { + fn limit_card_caps(&self, card_caps: CardCaps) -> CardCaps { + let max_cmd_bytes = u16::min(APDU_CMD_BYTES_MAX as u16, card_caps.max_cmd_bytes()); + + CardCaps::new( + card_caps.ext_support(), + card_caps.chaining_support(), + max_cmd_bytes, + card_caps.max_rsp_bytes(), + card_caps.pw1_max_len(), + card_caps.pw3_max_len(), + ) + } + fn transaction( &mut self, _reselect_application: Option<&[u8]>, From 87a9f4f2166c5694fe1e93bc360fbc493c82680b Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 2 Sep 2023 23:20:30 +0200 Subject: [PATCH 088/115] bump backend versions card-backend to 0.2.0, pcsc and scdc to 0.5.0 --- card-backend/Cargo.toml | 2 +- openpgp-card-sequoia/Cargo.toml | 6 +++--- openpgp-card/Cargo.toml | 2 +- pcsc/Cargo.toml | 4 ++-- scdc/Cargo.toml | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/card-backend/Cargo.toml b/card-backend/Cargo.toml index 17f9266..4afb3f5 100644 --- a/card-backend/Cargo.toml +++ b/card-backend/Cargo.toml @@ -6,7 +6,7 @@ name = "card-backend" description = "Card backend trait, for use with the openpgp-card crate" authors = ["Heiko Schaefer "] license = "MIT OR Apache-2.0" -version = "0.1.0" +version = "0.2.0" edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" documentation = "https://docs.rs/crate/card-backend" diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index 73027c7..c3a0ad7 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://gitlab.com/openpgp-card/openpgp-card" documentation = "https://docs.rs/crate/openpgp-card-sequoia" [dependencies] -card-backend = { path = "../card-backend", version = "0.1" } +card-backend = { path = "../card-backend", version = "0.2" } sequoia-openpgp = { version = "1.4", default-features = false } openpgp-card = { path = "../openpgp-card", version = "0.4" } chrono = "0.4" @@ -22,8 +22,8 @@ log = "0.4" rsa = "0.8.1" [dev-dependencies] -card-backend-pcsc = { path = "../pcsc", version = "0.4" } -#card-backend-scdc = { path = "../scdc", version = "0.4" } +card-backend-pcsc = { path = "../pcsc", version = "0.5" } +#card-backend-scdc = { path = "../scdc", version = "0.5" } env_logger = "0.10" testresult = "0.3.0" diff --git a/openpgp-card/Cargo.toml b/openpgp-card/Cargo.toml index 4b2a448..74aabb1 100644 --- a/openpgp-card/Cargo.toml +++ b/openpgp-card/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://gitlab.com/openpgp-card/openpgp-card" documentation = "https://docs.rs/crate/openpgp-card" [dependencies] -card-backend = { path = "../card-backend", version = "0.1" } +card-backend = { path = "../card-backend", version = "0.2" } nom = "7" hex-slice = "0.1" thiserror = "1" diff --git a/pcsc/Cargo.toml b/pcsc/Cargo.toml index e6a69c9..2149b80 100644 --- a/pcsc/Cargo.toml +++ b/pcsc/Cargo.toml @@ -6,13 +6,13 @@ name = "card-backend-pcsc" description = "PCSC card backend, e.g. for use with the openpgp-card crate" authors = ["Heiko Schaefer "] license = "MIT OR Apache-2.0" -version = "0.4.0" +version = "0.5.0" edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" documentation = "https://docs.rs/crate/card-backend-pcsc" [dependencies] -card-backend = { path = "../card-backend", version = "0.1" } +card-backend = { path = "../card-backend", version = "0.2" } iso7816-tlv = "0.4" pcsc = "2.7" log = "0.4" diff --git a/scdc/Cargo.toml b/scdc/Cargo.toml index 38b5ccd..c6485d8 100644 --- a/scdc/Cargo.toml +++ b/scdc/Cargo.toml @@ -6,13 +6,13 @@ name = "card-backend-scdc" description = "Experimental SCDaemon Client, e.g. for use with the openpgp-card crate" authors = ["Heiko Schaefer "] license = "MIT OR Apache-2.0" -version = "0.4.0" +version = "0.5.0" edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" documentation = "https://docs.rs/crate/openpgp-card-scdc" [dependencies] -card-backend = { path = "../card-backend", version = "0.1" } +card-backend = { path = "../card-backend", version = "0.2" } sequoia-ipc = "0.29" hex = "0.4" futures = "0.3" From 1681d947100a219579febf4b882fb126d8e3a860 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 31 Aug 2023 22:43:29 +0200 Subject: [PATCH 089/115] openpgp-card-sequoia: add set_algorithm() (and remove algorithm setting from generate_key) Also add set_algorithm_attributes(). --- card-functionality/src/tests.rs | 9 ++++++--- openpgp-card-sequoia/src/lib.rs | 35 +++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index 631f2e2..e7561e6 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -218,11 +218,13 @@ pub fn test_keygen(tx: &mut Card, param: &[&str]) -> Result, param: &[&str]) -> Result> { ) } - pub fn generate_key_simple( + /// Configure the key slot `key_type` to `algorithm_attributes`. + /// This can be useful in preparation for [`Self::generate_key`]. + /// + /// Note that legal values for [`AlgorithmAttributes`] are card-specific. + /// Different OpenPGP card implementations may support different + /// algorithms, sometimes with differing requirements for the encoding + /// (e.g. field sizes) + pub fn set_algorithm_attributes( &mut self, key_type: KeyType, - algo: Option, - ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { - if let Some(algo) = algo { - // set algorithm attributes - let attr = algo.matching_algorithm_attributes(self.card(), key_type)?; - self.card().set_algorithm_attributes(key_type, &attr)?; - } + algorithm_attributes: &AlgorithmAttributes, + ) -> Result<(), Error> { + self.card() + .set_algorithm_attributes(key_type, algorithm_attributes) + } + /// Configure the key slot `key_type` to the algorithm `algo`. + /// This can be useful in preparation for [`Self::generate_key`]. + pub fn set_algorithm(&mut self, key_type: KeyType, algo: AlgoSimple) -> Result<(), Error> { + let attr = algo.matching_algorithm_attributes(self.card(), key_type)?; + self.set_algorithm_attributes(key_type, &attr) + } + + /// Generate a new cryptographic key in slot `key_type`, with the currently + /// configured cryptographic algorithm + /// (see [`Self::set_algorithm`] for changing the algorithm setting). + pub fn generate_key( + &mut self, + key_type: KeyType, + ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { self.card().generate_key(Self::ptf, key_type) } } From 8f80020f9c698ec8849c2f807775ade288b0e9f9 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 3 Sep 2023 21:54:02 +0200 Subject: [PATCH 090/115] openpgp-card-sequoia: use immutable fields from openpgp-card::Card --- openpgp-card-sequoia/src/lib.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 8c9ab67..8b325e6 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -442,15 +442,28 @@ impl<'a> Card> { // --- application data --- pub fn application_identifier(&self) -> Result { - self.state.ard.application_id() + // Use immutable data cache from underlying Card + self.state.opt.application_identifier() + } + + pub fn extended_capabilities(&self) -> Result { + // Use immutable data cache from underlying Card + self.state.opt.extended_capabilities() } pub fn historical_bytes(&self) -> Result { - self.state.ard.historical_bytes() + // Use immutable data cache from underlying Card + match self.state.opt.historical_bytes()? { + Some(hb) => Ok(hb), + None => Err(Error::NotFound( + "Card doesn't have historical bytes DO".to_string(), + )), + } } pub fn extended_length_information(&self) -> Result, Error> { - self.state.ard.extended_length_information() + // Use immutable data cache from underlying Card + self.state.opt.extended_length_info() } #[allow(dead_code)] @@ -463,10 +476,7 @@ impl<'a> Card> { unimplemented!() } - pub fn extended_capabilities(&self) -> Result { - self.state.ard.extended_capabilities() - } - + /// Get algorithm attributes for a key slot. pub fn algorithm_attributes(&self, key_type: KeyType) -> Result { self.state.ard.algorithm_attributes(key_type) } From d55980cef68fa6900d7fea6e17e3c603bfca9f80 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 3 Sep 2023 22:13:22 +0200 Subject: [PATCH 091/115] openpgp-card-sequoia: add fingerprint, key_generation_time getters with key_type parameter --- openpgp-card-sequoia/src/lib.rs | 93 +++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 8b325e6..360c661 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -476,20 +476,37 @@ impl<'a> Card> { unimplemented!() } - /// Get algorithm attributes for a key slot. - pub fn algorithm_attributes(&self, key_type: KeyType) -> Result { - self.state.ard.algorithm_attributes(key_type) - } - /// PW status Bytes pub fn pw_status_bytes(&self) -> Result { self.state.ard.pw_status_bytes() } + /// Get algorithm attributes for a key slot. + pub fn algorithm_attributes(&self, key_type: KeyType) -> Result { + self.state.ard.algorithm_attributes(key_type) + } + pub fn fingerprints(&self) -> Result, Error> { self.state.ard.fingerprints() } + pub fn fingerprint(&mut self, key_type: KeyType) -> Result, Error> { + let fp = match key_type { + KeyType::Signing => self.fingerprints()?.signature().cloned(), + KeyType::Decryption => self.fingerprints()?.decryption().cloned(), + KeyType::Authentication => self.fingerprints()?.authentication().cloned(), + KeyType::Attestation => self.state.ard.attestation_key_fingerprint()?, + _ => { + return Err(Error::UnsupportedFeature(format!( + "Can't get fingerprint for key_type {:?}", + key_type, + ))) + } + }; + + Ok(fp) + } + pub fn ca_fingerprints(&self) -> Result<[Option; 3], Error> { self.state.ard.ca_fingerprints() } @@ -498,6 +515,26 @@ impl<'a> Card> { self.state.ard.key_generation_times() } + pub fn key_generation_time( + &mut self, + key_type: KeyType, + ) -> Result, Error> { + let ts = match key_type { + KeyType::Signing => self.key_generation_times()?.signature().cloned(), + KeyType::Decryption => self.key_generation_times()?.decryption().cloned(), + KeyType::Authentication => self.key_generation_times()?.authentication().cloned(), + KeyType::Attestation => self.state.ard.attestation_key_generation_time()?, + _ => { + return Err(Error::UnsupportedFeature(format!( + "Can't get creation time for key_type {:?}", + key_type, + ))) + } + }; + + Ok(ts) + } + pub fn key_information(&self) -> Result, Error> { self.state.ard.key_information() } @@ -623,20 +660,6 @@ impl<'a> Card> { self.state.opt.attestation_certificate() } - pub fn attestation_key_fingerprint(&mut self) -> Result, Error> { - self.state.ard.attestation_key_fingerprint() - } - - pub fn attestation_key_algorithm_attributes( - &mut self, - ) -> Result, Error> { - self.state.ard.attestation_key_algorithm_attributes() - } - - pub fn attestation_key_generation_time(&mut self) -> Result, Error> { - self.state.ard.attestation_key_generation_time() - } - /// Firmware Version, YubiKey specific (?) pub fn firmware_version(&mut self) -> Result, Error> { self.state.opt.firmware_version() @@ -673,19 +696,26 @@ impl<'a> Card> { /// Get PublicKey representation for a key slot on the card pub fn public_key(&mut self, kt: KeyType) -> Result, Error> { - // FIXME: only read these once, if multiple subkeys are retrieved from the card - let times = self.key_generation_times()?; let fps = self.fingerprints()?; + let ts = self.key_generation_time(kt)?; + + let fp = match kt { + KeyType::Signing => fps.signature(), + KeyType::Decryption => fps.decryption(), + KeyType::Authentication => fps.authentication(), + _ => None, + }; + match kt { KeyType::Signing => { - if let Ok(pkm) = self.public_key_material(KeyType::Signing) { - if let Some(ts) = times.signature() { + if let Ok(pkm) = self.public_key_material(kt) { + if let Some(ts) = ts { return Ok(Some(public_key_material_and_fp_to_key( &pkm, KeyType::Signing, - ts, - fps.signature().expect("Signature fingerprint is unset"), + &ts, + fp.expect("Signature fingerprint is unset"), )?)); } } @@ -693,12 +723,12 @@ impl<'a> Card> { } KeyType::Decryption => { if let Ok(pkm) = self.public_key_material(KeyType::Decryption) { - if let Some(ts) = times.decryption() { + if let Some(ts) = ts { return Ok(Some(public_key_material_and_fp_to_key( &pkm, KeyType::Decryption, - ts, - fps.decryption().expect("Decryption fingerprint is unset"), + &ts, + fp.expect("Decryption fingerprint is unset"), )?)); } } @@ -706,13 +736,12 @@ impl<'a> Card> { } KeyType::Authentication => { if let Ok(pkm) = self.public_key_material(KeyType::Authentication) { - if let Some(ts) = times.authentication() { + if let Some(ts) = ts { return Ok(Some(public_key_material_and_fp_to_key( &pkm, KeyType::Authentication, - ts, - fps.authentication() - .expect("Authentication fingerprint is unset"), + &ts, + fp.expect("Authentication fingerprint is unset"), )?)); } } From 736199a8ded018dab9156c551bf4843af74ce579 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 3 Sep 2023 22:38:19 +0200 Subject: [PATCH 092/115] openpgp-card-sequoia: move all uif_* getters into uif() --- openpgp-card-sequoia/src/lib.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 360c661..dea3da7 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -539,20 +539,17 @@ impl<'a> Card> { self.state.ard.key_information() } - pub fn uif_signing(&self) -> Result, Error> { - self.state.ard.uif_pso_cds() - } - - pub fn uif_decryption(&self) -> Result, Error> { - self.state.ard.uif_pso_dec() - } - - pub fn uif_authentication(&self) -> Result, Error> { - self.state.ard.uif_pso_aut() - } - - pub fn uif_attestation(&self) -> Result, Error> { - self.state.ard.uif_attestation() + pub fn uif(&self, key_type: KeyType) -> Result, Error> { + match key_type { + KeyType::Signing => self.state.ard.uif_pso_cds(), + KeyType::Decryption => self.state.ard.uif_pso_dec(), + KeyType::Authentication => self.state.ard.uif_pso_aut(), + KeyType::Attestation => self.state.ard.uif_attestation(), + _ => Err(Error::UnsupportedFeature(format!( + "Can't get UIF for key_type {:?}", + key_type, + ))), + } } // --- optional private DOs (0101 - 0104) --- From 2ef3e1f0a8a156b384841c89577fec54d9d28142 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 3 Sep 2023 22:43:22 +0200 Subject: [PATCH 093/115] openpgp-card-sequoia: rename uif->user_interaction_flag --- openpgp-card-sequoia/src/lib.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index dea3da7..48b8460 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -539,7 +539,10 @@ impl<'a> Card> { self.state.ard.key_information() } - pub fn uif(&self, key_type: KeyType) -> Result, Error> { + pub fn user_interaction_flag( + &self, + key_type: KeyType, + ) -> Result, Error> { match key_type { KeyType::Signing => self.state.ard.uif_pso_cds(), KeyType::Decryption => self.state.ard.uif_pso_dec(), @@ -929,7 +932,11 @@ impl Card> { self.card().set_pw_status_bytes(pw_status, long) } - pub fn set_uif(&mut self, key: KeyType, policy: TouchPolicy) -> Result<(), Error> { + pub fn set_user_interaction_flag( + &mut self, + key: KeyType, + policy: TouchPolicy, + ) -> Result<(), Error> { let uif = match key { KeyType::Signing => self.state.tx.state.ard.uif_pso_cds()?, KeyType::Decryption => self.state.tx.state.ard.uif_pso_dec()?, From 1de083e1b8aca1e57ce74c05ed202ffc2b6cf86e Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 3 Sep 2023 23:22:05 +0200 Subject: [PATCH 094/115] openpgp-card-sequoia: access ard through getter/setters --- openpgp-card-sequoia/src/lib.rs | 50 +++++++++++++++---------------- openpgp-card-sequoia/src/state.rs | 22 +++++++++++++- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 48b8460..3c27656 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -226,7 +226,7 @@ impl Card { let aid = { let tx = card.transaction()?; - tx.state.ard.application_id()? + tx.state.ard().application_id()? }; if aid.ident() == ident.to_ascii_uppercase() { @@ -269,13 +269,7 @@ impl<'a> Card> { let ard = opt.application_related_data()?; Ok(Self { - state: Transaction { - opt, - ard, - pw1: false, - pw1_sign: false, - pw3: false, - }, + state: Transaction::new(opt, ard), }) } @@ -286,7 +280,11 @@ impl<'a> Card> { /// see these changes reflected in `self.ard`. pub fn reload_ard(&mut self) -> Result<(), Error> { // FIXME: this should be implemented internally, transparent to users - self.state.ard = self.state.opt.application_related_data()?; + + let ard = self.state.opt.application_related_data()?; + + self.state.set_ard(ard); + Ok(()) } @@ -478,16 +476,16 @@ impl<'a> Card> { /// PW status Bytes pub fn pw_status_bytes(&self) -> Result { - self.state.ard.pw_status_bytes() + self.state.ard().pw_status_bytes() } /// Get algorithm attributes for a key slot. pub fn algorithm_attributes(&self, key_type: KeyType) -> Result { - self.state.ard.algorithm_attributes(key_type) + self.state.ard().algorithm_attributes(key_type) } pub fn fingerprints(&self) -> Result, Error> { - self.state.ard.fingerprints() + self.state.ard().fingerprints() } pub fn fingerprint(&mut self, key_type: KeyType) -> Result, Error> { @@ -495,7 +493,7 @@ impl<'a> Card> { KeyType::Signing => self.fingerprints()?.signature().cloned(), KeyType::Decryption => self.fingerprints()?.decryption().cloned(), KeyType::Authentication => self.fingerprints()?.authentication().cloned(), - KeyType::Attestation => self.state.ard.attestation_key_fingerprint()?, + KeyType::Attestation => self.state.ard().attestation_key_fingerprint()?, _ => { return Err(Error::UnsupportedFeature(format!( "Can't get fingerprint for key_type {:?}", @@ -508,11 +506,11 @@ impl<'a> Card> { } pub fn ca_fingerprints(&self) -> Result<[Option; 3], Error> { - self.state.ard.ca_fingerprints() + self.state.ard().ca_fingerprints() } pub fn key_generation_times(&self) -> Result, Error> { - self.state.ard.key_generation_times() + self.state.ard().key_generation_times() } pub fn key_generation_time( @@ -523,7 +521,7 @@ impl<'a> Card> { KeyType::Signing => self.key_generation_times()?.signature().cloned(), KeyType::Decryption => self.key_generation_times()?.decryption().cloned(), KeyType::Authentication => self.key_generation_times()?.authentication().cloned(), - KeyType::Attestation => self.state.ard.attestation_key_generation_time()?, + KeyType::Attestation => self.state.ard().attestation_key_generation_time()?, _ => { return Err(Error::UnsupportedFeature(format!( "Can't get creation time for key_type {:?}", @@ -536,7 +534,7 @@ impl<'a> Card> { } pub fn key_information(&self) -> Result, Error> { - self.state.ard.key_information() + self.state.ard().key_information() } pub fn user_interaction_flag( @@ -544,10 +542,10 @@ impl<'a> Card> { key_type: KeyType, ) -> Result, Error> { match key_type { - KeyType::Signing => self.state.ard.uif_pso_cds(), - KeyType::Decryption => self.state.ard.uif_pso_dec(), - KeyType::Authentication => self.state.ard.uif_pso_aut(), - KeyType::Attestation => self.state.ard.uif_attestation(), + KeyType::Signing => self.state.ard().uif_pso_cds(), + KeyType::Decryption => self.state.ard().uif_pso_dec(), + KeyType::Authentication => self.state.ard().uif_pso_aut(), + KeyType::Attestation => self.state.ard().uif_attestation(), _ => Err(Error::UnsupportedFeature(format!( "Can't get UIF for key_type {:?}", key_type, @@ -846,7 +844,7 @@ impl<'app, 'open> Card> { // Touch is required if: // - the card supports the feature // - and the policy is set to a value other than 'Off' - if let Some(uif) = self.state.tx.state.ard.uif_attestation()? { + if let Some(uif) = self.state.tx.state.ard().uif_attestation()? { if uif.touch_policy().touch_required() { (touch_prompt)(); } @@ -938,10 +936,10 @@ impl Card> { policy: TouchPolicy, ) -> Result<(), Error> { let uif = match key { - KeyType::Signing => self.state.tx.state.ard.uif_pso_cds()?, - KeyType::Decryption => self.state.tx.state.ard.uif_pso_dec()?, - KeyType::Authentication => self.state.tx.state.ard.uif_pso_aut()?, - KeyType::Attestation => self.state.tx.state.ard.uif_attestation()?, + KeyType::Signing => self.state.tx.state.ard().uif_pso_cds()?, + KeyType::Decryption => self.state.tx.state.ard().uif_pso_dec()?, + KeyType::Authentication => self.state.tx.state.ard().uif_pso_aut()?, + KeyType::Attestation => self.state.tx.state.ard().uif_attestation()?, _ => unimplemented!(), }; diff --git a/openpgp-card-sequoia/src/state.rs b/openpgp-card-sequoia/src/state.rs index c33e023..046aa86 100644 --- a/openpgp-card-sequoia/src/state.rs +++ b/openpgp-card-sequoia/src/state.rs @@ -42,7 +42,7 @@ pub struct Transaction<'a> { // // This field should probably be an Option<> that gets invalidated when appropriate and // re-fetched lazily. - pub(crate) ard: ApplicationRelatedData, + ard: ApplicationRelatedData, // verify status of pw1 pub(crate) pw1: bool, @@ -54,6 +54,26 @@ pub struct Transaction<'a> { pub(crate) pw3: bool, } +impl<'a> Transaction<'a> { + pub(crate) fn new(opt: openpgp_card::Transaction<'a>, ard: ApplicationRelatedData) -> Self { + Transaction { + opt, + ard, + pw1: false, + pw1_sign: false, + pw3: false, + } + } + + pub(crate) fn ard(&self) -> &ApplicationRelatedData { + &self.ard + } + + pub(crate) fn set_ard(&mut self, ard: ApplicationRelatedData) { + self.ard = ard + } +} + /// State of an OpenPGP card after successfully verifying the User PIN /// (this verification allow user operations other than signing). /// From f7936a75fd9f3defaa805bdd94a2d2a5d836fb85 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 4 Sep 2023 09:10:01 +0200 Subject: [PATCH 095/115] openpgp-card-sequoia: Login Data is a binary field --- card-functionality/src/tests.rs | 4 ++-- openpgp-card-sequoia/src/lib.rs | 15 ++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index e7561e6..8fa01d3 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -357,12 +357,12 @@ pub fn test_set_login_data( let mut admin = tx.to_admin_card("12345678")?; let test_login = "someone@somewhere.com"; - admin.set_login_data(test_login)?; + admin.set_login_data(test_login.as_bytes())?; // Read the previously set login data let read_login_data = tx.login_data()?; - assert_eq!(read_login_data, test_login); + assert_eq!(&read_login_data, test_login.as_bytes()); Ok(vec![]) } diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 3c27656..07c7298 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -555,9 +555,14 @@ impl<'a> Card> { // --- optional private DOs (0101 - 0104) --- - // --- login data (5e) --- - pub fn login_data(&mut self) -> Result { - Ok(String::from_utf8_lossy(&self.state.opt.login_data()?).to_string()) + /// Login Data + /// + /// This DO can be used to store any information used for the Log-In + /// process in a client/server authentication (e.g. user name of a + /// network). + /// The maximum length of this DO is announced in Extended Capabilities. + pub fn login_data(&mut self) -> Result, Error> { + self.state.opt.login_data() } // --- URL (5f50) --- @@ -893,8 +898,8 @@ impl Card> { self.card().set_sex(sex) } - pub fn set_login_data(&mut self, login_data: &str) -> Result<(), Error> { - self.card().set_login(login_data.as_bytes()) + pub fn set_login_data(&mut self, login_data: &[u8]) -> Result<(), Error> { + self.card().set_login(login_data) } /// Set "hardholder" URL on the card. From 21ba1aadbb81098001d4cf6133251edbb485b287 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 3 Sep 2023 20:42:48 +0200 Subject: [PATCH 096/115] openpgp-card-sequoia: rustdoc improvements (And minor comment notes) --- openpgp-card-sequoia/src/lib.rs | 240 ++++++++++++++++++++++++------ openpgp-card-sequoia/src/state.rs | 5 +- 2 files changed, 199 insertions(+), 46 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 07c7298..5676e61 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -55,16 +55,16 @@ //! //! ## Decryption //! -//! To use a card for decryption, it needs to be opened, user authorization -//! needs to be available. A `sequoia_openpgp::crypto::Decryptor` -//! implementation can then be obtained: +//! To use a card for decryption, it needs to be opened, and user +//! authorization needs to be available. +//! A [`sequoia_openpgp::crypto::Decryptor`] implementation can then be obtained: //! //! ```no_run //! use card_backend_pcsc::PcscBackend; //! use openpgp_card_sequoia::{state::Open, Card}; //! # fn main() -> Result<(), Box> { +//! //! // Open card via PCSC -//! use sequoia_openpgp::policy::StandardPolicy; //! let cards = PcscBackend::card_backends(None)?; //! let mut card = Card::::open_by_ident(cards, "abcd:01234567")?; //! let mut transaction = card.transaction()?; @@ -84,9 +84,9 @@ //! //! ## Signing //! -//! To use a card for signing, it needs to be opened, signing authorization -//! needs to be available. A `sequoia_openpgp::crypto::Signer` -//! implementation can then be obtained. +//! To use a card for signing, it needs to be opened, and signing +//! authorization needs to be available. +//! A [`sequoia_openpgp::crypto::Signer`] implementation can then be obtained. //! //! (Note that by default, some OpenPGP Cards will only allow one signing //! operation to be performed after the password has been presented for @@ -208,7 +208,7 @@ impl<'p, const S: usize> From<&'p [u8; S]> for OptionalPin<'p> { /// /// Depending on the `State` of the card, and the access privileges that are associated with that /// state, different operations can be performed. In many cases, client software will want to -/// transition between states while performing one activity for the user. +/// transition between states while performing one workflow for the user. pub struct Card where S: State, @@ -217,6 +217,10 @@ where } impl Card { + /// Takes an iterator over CardBackends, tries to SELECT the OpenPGP card + /// application on each of them, and checks if its application id matches + /// `ident`. + /// Returns a [`Card`] for the first match, if any. pub fn open_by_ident( cards: impl Iterator, SmartcardError>>, ident: &str, @@ -237,6 +241,8 @@ impl Card { Err(Error::NotFound(format!("Couldn't find card {}", ident))) } + /// Returns a [`Card`] based on `backend` (after SELECTing the + /// OpenPGP card application). pub fn new(backend: B) -> Result where B: Into>, @@ -248,23 +254,30 @@ impl Card { }) } + /// Starts a transaction on the underlying backend (if the backend + /// implementation supports transactions, otherwise the backend + /// will operate without transactions guarantees). + /// + /// The resulting [`Card`] object allows performing + /// operations on the card. pub fn transaction(&mut self) -> Result, Error> { let opt = self.state.pgp.transaction()?; Card::::new(opt) } - /// Get the internal `CardBackend`. + /// Retrieve the underlying [`CardBackend`]. /// - /// This is useful to perform operations on the card with a different crate, - /// e.g. `yubikey-management`. + /// This is useful to take the card object into a different context + /// (e.g. to perform operations on the card with the `yubikey-management` + /// crate, without closing the connection to the card). pub fn into_card(self) -> Box { self.state.pgp.into_card() } } impl<'a> Card> { - // Internal constructor + /// Internal constructor fn new(mut opt: openpgp_card::Transaction<'a>) -> Result { let ard = opt.application_related_data()?; @@ -288,20 +301,25 @@ impl<'a> Card> { Ok(()) } + /// True if the reader for this card supports PIN verification with a pin pad. pub fn feature_pinpad_verify(&mut self) -> bool { self.state.opt.feature_pinpad_verify() } + /// True if the reader for this card supports PIN modification with a pin pad. pub fn feature_pinpad_modify(&mut self) -> bool { self.state.opt.feature_pinpad_modify() } + /// Verify the User PIN (for operations such as decryption) pub fn verify_user(&mut self, pin: &[u8]) -> Result<(), Error> { self.state.opt.verify_pw1_user(pin)?; self.state.pw1 = true; Ok(()) } + /// Verify the User PIN with a physical PIN pad (if available, + /// see [`Self::feature_pinpad_verify`]). pub fn verify_user_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> { pinpad_prompt(); @@ -310,6 +328,11 @@ impl<'a> Card> { Ok(()) } + /// Verify the User PIN for signing operations. + /// + /// (Note that depending on the configuration of the card, this may enable + /// performing just one signing operation, or an unlimited amount of + /// signing operations). pub fn verify_user_for_signing(&mut self, pin: &[u8]) -> Result<(), Error> { self.state.opt.verify_pw1_sign(pin)?; @@ -319,6 +342,8 @@ impl<'a> Card> { Ok(()) } + /// Verify the User PIN for signing operations with a physical PIN pad + /// (if available, see [`Self::feature_pinpad_verify`]). pub fn verify_user_for_signing_pinpad( &mut self, pinpad_prompt: &dyn Fn(), @@ -333,12 +358,15 @@ impl<'a> Card> { Ok(()) } + /// Verify the Admin PIN. pub fn verify_admin(&mut self, pin: &[u8]) -> Result<(), Error> { self.state.opt.verify_pw3(pin)?; self.state.pw3 = true; Ok(()) } + /// Verify the Admin PIN with a physical PIN pad + /// (if available, see [`Self::feature_pinpad_verify`]). pub fn verify_admin_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> { pinpad_prompt(); @@ -349,41 +377,51 @@ impl<'a> Card> { /// Ask the card if the user password has been successfully verified. /// - /// NOTE: on some cards this functionality seems broken. + /// NOTE: on some cards this functionality seems broken and may decrease + /// the pin's error count! pub fn check_user_verified(&mut self) -> Result<(), Error> { self.state.opt.check_pw1_user() } /// Ask the card if the admin password has been successfully verified. /// - /// NOTE: on some cards this functionality seems broken. + /// NOTE: on some cards this functionality seems broken and may decrease + /// the pin's error count! pub fn check_admin_verified(&mut self) -> Result<(), Error> { self.state.opt.check_pw3() } + /// Change the User PIN, based on the old User PIN. pub fn change_user_pin(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> { self.state.opt.change_pw1(old, new) } + /// Change the User PIN, based on the old User PIN, with a physical PIN + /// pad (if available, see [`Self::feature_pinpad_modify`]). pub fn change_user_pin_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> { pinpad_prompt(); self.state.opt.change_pw1_pinpad() } + /// Change the User PIN, based on the resetting code `rst`. pub fn reset_user_pin(&mut self, rst: &[u8], new: &[u8]) -> Result<(), Error> { self.state.opt.reset_retry_counter_pw1(new, Some(rst)) } + /// Change the Admin PIN, based on the old Admin PIN. pub fn change_admin_pin(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> { self.state.opt.change_pw3(old, new) } + /// Change the Admin PIN, based on the old Admin PIN, with a physical PIN + /// pad (if available, see [`Self::feature_pinpad_modify`]). pub fn change_admin_pin_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> { pinpad_prompt(); self.state.opt.change_pw3_pinpad() } - /// Get a view of the card authenticated for "User" commands. + /// Get a view of the card in the [`Card`] state, and authenticate + /// for that state with `pin`, if available. /// /// If `pin` is not None, `verify_user` is called with that pin. pub fn to_user_card<'b, 'p, P>(&'b mut self, pin: P) -> Result>, Error> @@ -401,7 +439,8 @@ impl<'a> Card> { }) } - /// Get a view of the card authenticated for Signing. + /// Get a view of the card in the [`Card`] state, and authenticate + /// for that state with `pin`, if available. /// /// If `pin` is not None, `verify_user_for_signing` is called with that pin. pub fn to_signing_card<'b, 'p, P>(&'b mut self, pin: P) -> Result>, Error> @@ -419,7 +458,8 @@ impl<'a> Card> { }) } - /// Get a view of the card authenticated for "Admin" commands. + /// Get a view of the card in the [`Card`] state, and authenticate + /// for that state with `pin`, if available. /// /// If `pin` is not None, `verify_admin` is called with that pin. pub fn to_admin_card<'b, 'p, P>(&'b mut self, pin: P) -> Result>, Error> @@ -439,18 +479,40 @@ impl<'a> Card> { // --- application data --- + /// The Application Identifier is unique for each card. + /// It includes a manufacturer code and serial number. + /// + /// (This is an immutable field on the card. The value is cached in the + /// underlying Card object. It can be retrieved without incurring a call + /// to the card) pub fn application_identifier(&self) -> Result { - // Use immutable data cache from underlying Card + // Use immutable data cache from underlying Card object self.state.opt.application_identifier() } + /// The "Extended Capabilities" data object describes features of a card + /// to the caller. + /// This includes the availability and length of various data fields. + /// + /// (This is an immutable field on the card. The value is cached in the + /// underlying Card object. It can be retrieved without incurring a call + /// to the card) pub fn extended_capabilities(&self) -> Result { - // Use immutable data cache from underlying Card + // Use immutable data cache from underlying Card object self.state.opt.extended_capabilities() } + /// The "Historical Bytes" data object describes features of a card + /// to the caller. + /// The information in this field is probably not relevant for most + /// users of this library, however, some of it is used for the internal + /// operation of the `openpgp-card` library. + /// + /// (This is an immutable field on the card. The value is cached in the + /// underlying Card object. It can be retrieved without incurring a call + /// to the card) pub fn historical_bytes(&self) -> Result { - // Use immutable data cache from underlying Card + // Use immutable data cache from underlying Card object match self.state.opt.historical_bytes()? { Some(hb) => Ok(hb), None => Err(Error::NotFound( @@ -459,8 +521,19 @@ impl<'a> Card> { } } + /// The "Extended Length Information" data object was introduced in + /// version 3.0 of the OpenPGP card standard. + /// + /// The information in this field should not be relevant for + /// users of this library. + /// However, it is used for the internal operation of the `openpgp-card` + /// library. + /// + /// (This is an immutable field on the card. The value is cached in the + /// underlying Card object. It can be retrieved without incurring a call + /// to the card) pub fn extended_length_information(&self) -> Result, Error> { - // Use immutable data cache from underlying Card + // Use immutable data cache from underlying Card object self.state.opt.extended_length_info() } @@ -474,7 +547,7 @@ impl<'a> Card> { unimplemented!() } - /// PW status Bytes + /// PW Status Bytes pub fn pw_status_bytes(&self) -> Result { self.state.ard().pw_status_bytes() } @@ -484,10 +557,18 @@ impl<'a> Card> { self.state.ard().algorithm_attributes(key_type) } + /// Get the Fingerprints for the three basic [`KeyType`]s. + /// + /// (The fingerprints for the three basic key slots are stored in a + /// shared field on the card, thus they can be retrieved in one go) pub fn fingerprints(&self) -> Result, Error> { self.state.ard().fingerprints() } + /// Get the Fingerprint for one [`KeyType`]. + /// + /// This function allows retrieval for all slots, including + /// [`KeyType::Attestation`], if available. pub fn fingerprint(&mut self, key_type: KeyType) -> Result, Error> { let fp = match key_type { KeyType::Signing => self.fingerprints()?.signature().cloned(), @@ -505,14 +586,18 @@ impl<'a> Card> { Ok(fp) } - pub fn ca_fingerprints(&self) -> Result<[Option; 3], Error> { - self.state.ard().ca_fingerprints() - } - + /// Get the Key Creation Times for the three basic [`KeyType`]s. + /// + /// (The creation time for the three basic key slots are stored in a + /// shared field on the card, thus they can be retrieved in one go) pub fn key_generation_times(&self) -> Result, Error> { self.state.ard().key_generation_times() } + /// Get the Key Creation Time for one [`KeyType`]. + /// + /// This function allows retrieval for all slots, including + /// [`KeyType::Attestation`], if available. pub fn key_generation_time( &mut self, key_type: KeyType, @@ -537,6 +622,9 @@ impl<'a> Card> { self.state.ard().key_information() } + /// Get the [`UserInteractionFlag`] for a key slot. + /// This includes the [`TouchPolicy`], if the card supports touch + /// confirmation. pub fn user_interaction_flag( &self, key_type: KeyType, @@ -553,7 +641,26 @@ impl<'a> Card> { } } - // --- optional private DOs (0101 - 0104) --- + /// List of CA-Fingerprints of “Ultimately Trusted Keys”. + /// May be used to verify Public Keys from servers. + pub fn ca_fingerprints(&self) -> Result<[Option; 3], Error> { + self.state.ard().ca_fingerprints() + } + + /// Get optional "Private use" data from the card. + /// + /// The presence and maximum length of these DOs is announced + /// in [`ExtendedCapabilities`]. + /// + /// If available, there are 4 data fields for private use: + /// + /// - `1`: read accessible without PIN verification + /// - `2`: read accessible without PIN verification + /// - `3`: read accessible with User PIN verification + /// - `4`: read accessible with Admin PIN verification + pub fn private_use_do(&mut self, num: u8) -> Result, Error> { + self.state.opt.private_use_do(num) + } /// Login Data /// @@ -567,7 +674,7 @@ impl<'a> Card> { // --- URL (5f50) --- - /// Get "hardholder" URL from the card. + /// Get "cardholder" URL from the card. /// /// "The URL should contain a link to a set of public keys in OpenPGP format, related to /// the card." @@ -575,7 +682,7 @@ impl<'a> Card> { Ok(String::from_utf8_lossy(&self.state.opt.url()?).to_string()) } - // --- cardholder related data (65) --- + /// Cardholder related data (contains the fields: Name, Language preferences and Sex) pub fn cardholder_related_data(&mut self) -> Result { self.state.opt.cardholder_related_data() } @@ -604,7 +711,7 @@ impl<'a> Card> { } } - // --- security support template (7a) --- + /// Get security support template (contains the digital signature count). pub fn security_support_template(&mut self) -> Result { self.state.opt.security_support_template() } @@ -630,7 +737,7 @@ impl<'a> Card> { self.state.opt.next_cardholder_certificate() } - // DO "Algorithm Information" (0xFA) + /// Algorithm Information (list of supported Algorithm attributes). pub fn algorithm_information(&mut self) -> Result, Error> { // The DO "Algorithm Information" (Tag FA) shall be present if // Algorithm attributes can be changed @@ -644,8 +751,9 @@ impl<'a> Card> { self.state.opt.algorithm_information() } - /// "MANAGE SECURITY ENVIRONMENT" - /// Make `key_ref` usable for the operation normally done by the key designated by `for_operation` + /// "MANAGE SECURITY ENVIRONMENT". + /// Make `key_ref` usable for the operation normally done by the key + /// designated by `for_operation` pub fn manage_security_environment( &mut self, for_operation: KeyType, @@ -684,20 +792,15 @@ impl<'a> Card> { // ---------- + /// Get the raw public key material for a key slot on the card + /// (also see [`Self::public_key`] for getting a Sequoia PGP key object) pub fn public_key_material(&mut self, key_type: KeyType) -> Result { self.state.opt.public_key(key_type) } - // ---------- - - /// Delete all state on this OpenPGP card - pub fn factory_reset(&mut self) -> Result<(), Error> { - self.state.opt.factory_reset() - } - - // -- higher level abstractions - - /// Get PublicKey representation for a key slot on the card + /// Get a sequoia public key representation + /// ([`Key`]) + /// for a key slot on the card pub fn public_key(&mut self, kt: KeyType) -> Result, Error> { let fps = self.fingerprints()?; @@ -753,6 +856,13 @@ impl<'a> Card> { _ => unimplemented!(), } } + + // ---------- + + /// Reset all state on this OpenPGP card + pub fn factory_reset(&mut self) -> Result<(), Error> { + self.state.opt.factory_reset() + } } impl<'app, 'open> Card> { @@ -805,6 +915,21 @@ impl<'app, 'open> Card> { ) -> CardSigner<'_, 'app> { CardSigner::with_pubkey_for_auth(self.card(), pubkey, touch_prompt) } + + /// Set optional "Private use" data on the card. + /// + /// The presence and maximum length of these DOs is announced + /// in [`ExtendedCapabilities`]. + /// + /// If available, there are 4 data fields for private use: + /// + /// - `1`: write accessible with User PIN verification + /// - `2`: write accessible with Admin PIN verification + /// - `3`: write accessible with User PIN verification + /// - `4`: write accessible with Admin PIN verification + pub fn set_private_use_do(&mut self, num: u8, data: Vec) -> Result<(), Error> { + self.card().set_private_use_do(num, data) + } } impl<'app, 'open> Card> { @@ -898,6 +1023,21 @@ impl Card> { self.card().set_sex(sex) } + /// Set optional "Private use" data on the card. + /// + /// The presence and maximum length of these DOs is announced + /// in [`ExtendedCapabilities`]. + /// + /// If available, there are 4 data fields for private use: + /// + /// - `1`: write accessible with User PIN verification + /// - `2`: write accessible with Admin PIN verification + /// - `3`: write accessible with User PIN verification + /// - `4`: write accessible with Admin PIN verification + pub fn set_private_use_do(&mut self, num: u8, data: Vec) -> Result<(), Error> { + self.card().set_private_use_do(num, data) + } + pub fn set_login_data(&mut self, login_data: &[u8]) -> Result<(), Error> { self.card().set_login(login_data) } @@ -927,6 +1067,16 @@ impl Card> { } } + /// Set PW Status Bytes. + /// + /// According to the spec, length information should not be changed. + /// + /// If `long` is false, sends 1 byte to the card, otherwise 4. + /// + /// So, effectively, with `long == false` the setting `pw1_cds_multi` + /// can be changed. + /// With `long == true`, the settings `pw1_pin_block` and `pw3_pin_block` + /// can also be changed. pub fn set_pw_status_bytes( &mut self, pw_status: &PWStatusBytes, @@ -1005,8 +1155,8 @@ impl Card> { pkm, &time, key_type, - Some(HashAlgorithm::SHA256), - Some(SymmetricAlgorithm::AES128), + Some(HashAlgorithm::SHA256), // FIXME + Some(SymmetricAlgorithm::AES128), // FIXME ) } diff --git a/openpgp-card-sequoia/src/state.rs b/openpgp-card-sequoia/src/state.rs index 046aa86..db03ac6 100644 --- a/openpgp-card-sequoia/src/state.rs +++ b/openpgp-card-sequoia/src/state.rs @@ -18,7 +18,7 @@ impl State for User<'_, '_> {} impl State for Sign<'_, '_> {} impl State for Admin<'_, '_> {} -/// State of an OpenPGP card in its base state, no transaction has been started. +/// An OpenPGP card in its base state, no transaction has been started. /// /// A transaction can be started on the card, in this state. pub struct Open { @@ -45,12 +45,15 @@ pub struct Transaction<'a> { ard: ApplicationRelatedData, // verify status of pw1 + // FIXME: this mechanism needs more thought pub(crate) pw1: bool, // verify status of pw1 for signing + // FIXME: this mechanism needs more thought pub(crate) pw1_sign: bool, // verify status of pw3 + // FIXME: this mechanism needs more thought pub(crate) pw3: bool, } From 858d91b1f8f537acb37fcaf24a6ad96921f9afa2 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 4 Sep 2023 17:02:20 +0200 Subject: [PATCH 097/115] openpgp-card-sequoia: don't do automatic cardholder name encoding, and document this Normalize fn name: set_name() -> set_cardholder_name(). --- card-functionality/src/tests.rs | 8 ++--- openpgp-card-sequoia/examples/test.rs | 2 +- openpgp-card-sequoia/src/lib.rs | 48 ++++++++++++++++++--------- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index 8fa01d3..8a6ea4a 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -320,7 +320,7 @@ pub fn test_set_user_data( let mut admin = tx.to_admin_card("12345678")?; // name - admin.set_name("Bar<, _param: &[&str]) -> Result, _param: &[&str]) -> Result, _param: &[&str]) -> Result Result<(), Box> { println!(); - admin.set_name("Bar< Card> { s.iter().map(|&c| c as char).collect() } - /// Get cardholder name as a String (this also normalizes the "<" and "<<" filler chars) - pub fn cardholder_name(&mut self) -> Result, Error> { + /// Get cardholder name. + /// + /// This is an ISO 8859-1 (Latin 1) String of up to 39 characters. + /// + /// Note that the standard specifies that this field should be encoded + /// according to ISO/IEC 7501-1: + /// + /// "The data element consists of surname (e. g. family name and given + /// name(s)) and forename(s) (including name suffix, e. g., Jr. and number). + /// Each item is separated by a ´<´ filler character (3C), the family- and + /// fore-name(s) are separated by two ´<<´ filler characters." + /// + /// This library doesn't perform this encoding. + pub fn cardholder_name(&mut self) -> Result { let crd = self.state.opt.cardholder_related_data()?; - if let Some(name) = crd.name() { - let name = Self::latin1_to_string(name); - // re-format name ("last< = name.split("<<").collect(); - let name = name.iter().cloned().rev().collect::>().join(" "); - - // replace item separators with spaces - let name = name.replace('<', " "); - - Ok(Some(name)) - } else { - Ok(None) + match crd.name() { + Some(name) => Ok(Self::latin1_to_string(name)), + None => Ok("".to_string()), } } @@ -996,7 +999,20 @@ impl<'app, 'open> Card> { } impl Card> { - pub fn set_name(&mut self, name: &str) -> Result<(), Error> { + /// Set cardholder name. + /// + /// This is an ISO 8859-1 (Latin 1) String of max. 39 characters. + /// + /// Note that the standard specifies that this field should be encoded according + /// to ISO/IEC 7501-1: + /// + /// "The data element consists of surname (e. g. family name and given + /// name(s)) and forename(s) (including name suffix, e. g., Jr. and number). + /// Each item is separated by a ´<´ filler character (3C), the family- and + /// fore-name(s) are separated by two ´<<´ filler characters." + /// + /// This library doesn't perform this encoding. + pub fn set_cardholder_name(&mut self, name: &str) -> Result<(), Error> { // All chars must be in ASCII7 if name.chars().any(|c| !c.is_ascii()) { return Err(Error::InternalError("Invalid char in name".into())); From f12b052d35d8f263b25110790596532fdd1aa60d Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 5 Sep 2023 00:00:00 +0200 Subject: [PATCH 098/115] openpgp-card-sequoia: drop security_support_template() in favor of digital_signature_count() --- openpgp-card-sequoia/src/lib.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 4536d58..57da32f 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -144,7 +144,7 @@ use openpgp_card::algorithm::{AlgoSimple, AlgorithmAttributes, AlgorithmInformat use openpgp_card::card_do::{ ApplicationIdentifier, CardholderRelatedData, ExtendedCapabilities, ExtendedLengthInfo, Fingerprint, HistoricalBytes, KeyGenerationTime, KeyInformation, KeySet, Lang, PWStatusBytes, - SecuritySupportTemplate, Sex, TouchPolicy, UserInteractionFlag, + Sex, TouchPolicy, UserInteractionFlag, }; use openpgp_card::crypto_data::PublicKeyMaterial; use openpgp_card::{Error, KeyType}; @@ -714,9 +714,13 @@ impl<'a> Card> { } } - /// Get security support template (contains the digital signature count). - pub fn security_support_template(&mut self) -> Result { - self.state.opt.security_support_template() + /// Get the current digital signature count (how many signatures have been issued by the card) + pub fn digital_signature_count(&mut self) -> Result { + Ok(self + .state + .opt + .security_support_template()? + .signature_count()) } /// SELECT DATA ("select a DO in the current template"). From b6fef9511ae46baf42f3e0e4447d32ec65e96440 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 5 Sep 2023 15:45:25 +0200 Subject: [PATCH 099/115] openpgp-card-sequoia: handle PINs as &str According to the spec, PINs are supposed to be handled in utf8 format (not binary) --- card-functionality/src/tests.rs | 52 +++++++++++++-------------- openpgp-card-sequoia/examples/test.rs | 6 ++-- openpgp-card-sequoia/src/lib.rs | 40 +++++++++++---------- openpgp-card-sequoia/src/util.rs | 2 +- 4 files changed, 51 insertions(+), 49 deletions(-) diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index 8a6ea4a..2af2ef2 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -193,7 +193,7 @@ pub fn test_upload_keys( let cert = Cert::from_file(param[0])?; let p = StandardPolicy::new(); - let mut admin = tx.to_admin_card(b"12345678")?; + let mut admin = tx.to_admin_card("12345678")?; let meta = util::upload_subkeys(&mut admin, &cert, &p) .map_err(|e| TestError::KeyUploadError(param[0].to_string(), e))?; @@ -246,7 +246,7 @@ pub fn test_keygen(tx: &mut Card, param: &[&str]) -> Result, _param: &[&str]) -> Result { @@ -535,7 +535,7 @@ pub fn test_verify(mut card: Card, _param: &[&str]) -> Result { @@ -567,20 +567,20 @@ pub fn test_change_pw(mut card: Card, _param: &[&str]) -> Result { // this is expected } @@ -591,10 +591,10 @@ pub fn test_change_pw(mut card: Card, _param: &[&str]) -> Result { // this is expected } @@ -605,13 +605,13 @@ pub fn test_change_pw(mut card: Card, _param: &[&str]) -> Result { @@ -653,21 +653,21 @@ pub fn test_reset_retry_counter( } println!("verify pw3"); - transaction.verify_admin(b"12345678")?; + transaction.verify_admin("12345678")?; println!("set resetting code"); let mut admin = transaction.to_admin_card(None)?; - admin.set_resetting_code(b"abcdefgh")?; + admin.set_resetting_code("abcdefgh")?; println!("reset retry counter"); // ca.reset_retry_counter_pw1("abcdef".as_bytes().to_vec(), None)?; - let _res = transaction.reset_user_pin(b"abcdef", b"abcdefgh"); + let _res = transaction.reset_user_pin("abcdef", "abcdefgh"); println!("verify good pw1"); - transaction.verify_user(b"abcdef")?; + transaction.verify_user("abcdef")?; println!("verify bad pw1"); - match transaction.verify_user(b"00000000") { + match transaction.verify_user("00000000") { Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => { // this is expected } diff --git a/openpgp-card-sequoia/examples/test.rs b/openpgp-card-sequoia/examples/test.rs index e208e83..19b8f7f 100644 --- a/openpgp-card-sequoia/examples/test.rs +++ b/openpgp-card-sequoia/examples/test.rs @@ -93,7 +93,7 @@ fn main() -> Result<(), Box> { println!("factory reset\n"); transaction.factory_reset()?; - transaction.verify_admin(b"12345678")?; + transaction.verify_admin("12345678")?; println!("verify for admin ok"); let check = transaction.check_user_verified(); @@ -152,7 +152,7 @@ fn main() -> Result<(), Box> { let check = transaction.check_user_verified(); println!("has user (pw1/82) been verified yet?\n{check:x?}\n"); - transaction.verify_user(b"123456")?; + transaction.verify_user("123456")?; println!("verify for user (pw1/82) ok"); let check = transaction.check_user_verified(); @@ -187,7 +187,7 @@ fn main() -> Result<(), Box> { let mut transaction = card.transaction()?; // Sign - transaction.verify_user_for_signing(b"123456")?; + transaction.verify_user_for_signing("123456")?; println!("verify for sign (pw1/81) ok\n"); // Use Sign access to card diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 57da32f..142e3bf 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -312,8 +312,8 @@ impl<'a> Card> { } /// Verify the User PIN (for operations such as decryption) - pub fn verify_user(&mut self, pin: &[u8]) -> Result<(), Error> { - self.state.opt.verify_pw1_user(pin)?; + pub fn verify_user(&mut self, pin: &str) -> Result<(), Error> { + self.state.opt.verify_pw1_user(pin.as_bytes())?; self.state.pw1 = true; Ok(()) } @@ -333,8 +333,8 @@ impl<'a> Card> { /// (Note that depending on the configuration of the card, this may enable /// performing just one signing operation, or an unlimited amount of /// signing operations). - pub fn verify_user_for_signing(&mut self, pin: &[u8]) -> Result<(), Error> { - self.state.opt.verify_pw1_sign(pin)?; + pub fn verify_user_for_signing(&mut self, pin: &str) -> Result<(), Error> { + self.state.opt.verify_pw1_sign(pin.as_bytes())?; // FIXME: depending on card mode, pw1_sign is only usable once @@ -359,8 +359,8 @@ impl<'a> Card> { } /// Verify the Admin PIN. - pub fn verify_admin(&mut self, pin: &[u8]) -> Result<(), Error> { - self.state.opt.verify_pw3(pin)?; + pub fn verify_admin(&mut self, pin: &str) -> Result<(), Error> { + self.state.opt.verify_pw3(pin.as_bytes())?; self.state.pw3 = true; Ok(()) } @@ -392,8 +392,8 @@ impl<'a> Card> { } /// Change the User PIN, based on the old User PIN. - pub fn change_user_pin(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> { - self.state.opt.change_pw1(old, new) + pub fn change_user_pin(&mut self, old: &str, new: &str) -> Result<(), Error> { + self.state.opt.change_pw1(old.as_bytes(), new.as_bytes()) } /// Change the User PIN, based on the old User PIN, with a physical PIN @@ -404,13 +404,15 @@ impl<'a> Card> { } /// Change the User PIN, based on the resetting code `rst`. - pub fn reset_user_pin(&mut self, rst: &[u8], new: &[u8]) -> Result<(), Error> { - self.state.opt.reset_retry_counter_pw1(new, Some(rst)) + pub fn reset_user_pin(&mut self, rst: &str, new: &str) -> Result<(), Error> { + self.state + .opt + .reset_retry_counter_pw1(new.as_bytes(), Some(rst.as_bytes())) } /// Change the Admin PIN, based on the old Admin PIN. - pub fn change_admin_pin(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> { - self.state.opt.change_pw3(old, new) + pub fn change_admin_pin(&mut self, old: &str, new: &str) -> Result<(), Error> { + self.state.opt.change_pw3(old.as_bytes(), new.as_bytes()) } /// Change the Admin PIN, based on the old Admin PIN, with a physical PIN @@ -431,7 +433,7 @@ impl<'a> Card> { let pin: OptionalPin = pin.into(); if let Some(pin) = pin.0 { - self.verify_user(pin)?; + self.verify_user(String::from_utf8_lossy(pin).as_ref())?; } Ok(Card:: { @@ -450,7 +452,7 @@ impl<'a> Card> { let pin: OptionalPin = pin.into(); if let Some(pin) = pin.0 { - self.verify_user_for_signing(pin)?; + self.verify_user_for_signing(String::from_utf8_lossy(pin).as_ref())?; } Ok(Card:: { @@ -469,7 +471,7 @@ impl<'a> Card> { let pin: OptionalPin = pin.into(); if let Some(pin) = pin.0 { - self.verify_admin(pin)?; + self.verify_admin(String::from_utf8_lossy(pin).as_ref())?; } Ok(Card:: { @@ -1137,16 +1139,16 @@ impl Card> { Ok(()) } - pub fn set_resetting_code(&mut self, pin: &[u8]) -> Result<(), Error> { - self.card().set_resetting_code(pin) + pub fn set_resetting_code(&mut self, pin: &str) -> Result<(), Error> { + self.card().set_resetting_code(pin.as_bytes()) } pub fn set_pso_enc_dec_key(&mut self, key: &[u8]) -> Result<(), Error> { self.card().set_pso_enc_dec_key(key) } - pub fn reset_user_pin(&mut self, new: &[u8]) -> Result<(), Error> { - self.card().reset_retry_counter_pw1(new, None) + pub fn reset_user_pin(&mut self, new: &str) -> Result<(), Error> { + self.card().reset_retry_counter_pw1(new.as_bytes(), None) } /// Upload a ValidErasedKeyAmalgamation to the card as a specific KeyType. diff --git a/openpgp-card-sequoia/src/util.rs b/openpgp-card-sequoia/src/util.rs index 0288efe..91ec37b 100644 --- a/openpgp-card-sequoia/src/util.rs +++ b/openpgp-card-sequoia/src/util.rs @@ -42,7 +42,7 @@ pub fn make_cert( key_sig: PublicKey, key_dec: Option, key_aut: Option, - pw1: Option<&[u8]>, + pw1: Option<&str>, pinpad_prompt: &dyn Fn(), touch_prompt: &(dyn Fn() + Send + Sync), user_ids: &[String], From 041228a4a20e9564694329d0d5ac42aaf501df5c Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 5 Sep 2023 17:38:51 +0200 Subject: [PATCH 100/115] openpgp-card-sequoia: normalize naming of pin verify fns --- card-functionality/src/tests.rs | 26 +++++++++++++------------- openpgp-card-sequoia/examples/test.rs | 6 +++--- openpgp-card-sequoia/src/lib.rs | 17 +++++++---------- openpgp-card-sequoia/src/util.rs | 4 ++-- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index 2af2ef2..809c7a2 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -513,7 +513,7 @@ pub fn test_verify(mut card: Card, _param: &[&str]) -> Result { @@ -535,7 +535,7 @@ pub fn test_verify(mut card: Card, _param: &[&str]) -> Result { @@ -580,7 +580,7 @@ pub fn test_change_pw(mut card: Card, _param: &[&str]) -> Result { // this is expected } @@ -591,10 +591,10 @@ pub fn test_change_pw(mut card: Card, _param: &[&str]) -> Result { // this is expected } @@ -605,7 +605,7 @@ pub fn test_change_pw(mut card: Card, _param: &[&str]) -> Result { @@ -653,7 +653,7 @@ pub fn test_reset_retry_counter( } println!("verify pw3"); - transaction.verify_admin("12345678")?; + transaction.verify_admin_pin("12345678")?; println!("set resetting code"); let mut admin = transaction.to_admin_card(None)?; @@ -664,10 +664,10 @@ pub fn test_reset_retry_counter( let _res = transaction.reset_user_pin("abcdef", "abcdefgh"); println!("verify good pw1"); - transaction.verify_user("abcdef")?; + transaction.verify_user_pin("abcdef")?; println!("verify bad pw1"); - match transaction.verify_user("00000000") { + match transaction.verify_user_pin("00000000") { Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => { // this is expected } diff --git a/openpgp-card-sequoia/examples/test.rs b/openpgp-card-sequoia/examples/test.rs index 19b8f7f..2d3cdd4 100644 --- a/openpgp-card-sequoia/examples/test.rs +++ b/openpgp-card-sequoia/examples/test.rs @@ -93,7 +93,7 @@ fn main() -> Result<(), Box> { println!("factory reset\n"); transaction.factory_reset()?; - transaction.verify_admin("12345678")?; + transaction.verify_admin_pin("12345678")?; println!("verify for admin ok"); let check = transaction.check_user_verified(); @@ -152,7 +152,7 @@ fn main() -> Result<(), Box> { let check = transaction.check_user_verified(); println!("has user (pw1/82) been verified yet?\n{check:x?}\n"); - transaction.verify_user("123456")?; + transaction.verify_user_pin("123456")?; println!("verify for user (pw1/82) ok"); let check = transaction.check_user_verified(); @@ -187,7 +187,7 @@ fn main() -> Result<(), Box> { let mut transaction = card.transaction()?; // Sign - transaction.verify_user_for_signing("123456")?; + transaction.verify_user_signing_pin("123456")?; println!("verify for sign (pw1/81) ok\n"); // Use Sign access to card diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 142e3bf..241899a 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -312,7 +312,7 @@ impl<'a> Card> { } /// Verify the User PIN (for operations such as decryption) - pub fn verify_user(&mut self, pin: &str) -> Result<(), Error> { + pub fn verify_user_pin(&mut self, pin: &str) -> Result<(), Error> { self.state.opt.verify_pw1_user(pin.as_bytes())?; self.state.pw1 = true; Ok(()) @@ -333,7 +333,7 @@ impl<'a> Card> { /// (Note that depending on the configuration of the card, this may enable /// performing just one signing operation, or an unlimited amount of /// signing operations). - pub fn verify_user_for_signing(&mut self, pin: &str) -> Result<(), Error> { + pub fn verify_user_signing_pin(&mut self, pin: &str) -> Result<(), Error> { self.state.opt.verify_pw1_sign(pin.as_bytes())?; // FIXME: depending on card mode, pw1_sign is only usable once @@ -344,10 +344,7 @@ impl<'a> Card> { /// Verify the User PIN for signing operations with a physical PIN pad /// (if available, see [`Self::feature_pinpad_verify`]). - pub fn verify_user_for_signing_pinpad( - &mut self, - pinpad_prompt: &dyn Fn(), - ) -> Result<(), Error> { + pub fn verify_user_signing_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> { pinpad_prompt(); self.state.opt.verify_pw1_sign_pinpad()?; @@ -359,7 +356,7 @@ impl<'a> Card> { } /// Verify the Admin PIN. - pub fn verify_admin(&mut self, pin: &str) -> Result<(), Error> { + pub fn verify_admin_pin(&mut self, pin: &str) -> Result<(), Error> { self.state.opt.verify_pw3(pin.as_bytes())?; self.state.pw3 = true; Ok(()) @@ -433,7 +430,7 @@ impl<'a> Card> { let pin: OptionalPin = pin.into(); if let Some(pin) = pin.0 { - self.verify_user(String::from_utf8_lossy(pin).as_ref())?; + self.verify_user_pin(String::from_utf8_lossy(pin).as_ref())?; } Ok(Card:: { @@ -452,7 +449,7 @@ impl<'a> Card> { let pin: OptionalPin = pin.into(); if let Some(pin) = pin.0 { - self.verify_user_for_signing(String::from_utf8_lossy(pin).as_ref())?; + self.verify_user_signing_pin(String::from_utf8_lossy(pin).as_ref())?; } Ok(Card:: { @@ -471,7 +468,7 @@ impl<'a> Card> { let pin: OptionalPin = pin.into(); if let Some(pin) = pin.0 { - self.verify_admin(String::from_utf8_lossy(pin).as_ref())?; + self.verify_admin_pin(String::from_utf8_lossy(pin).as_ref())?; } Ok(Card:: { diff --git a/openpgp-card-sequoia/src/util.rs b/openpgp-card-sequoia/src/util.rs index 91ec37b..e62bff9 100644 --- a/openpgp-card-sequoia/src/util.rs +++ b/openpgp-card-sequoia/src/util.rs @@ -54,9 +54,9 @@ pub fn make_cert( |op: &mut dyn Fn(&mut dyn sequoia_openpgp::crypto::Signer) -> Result| { // Allow signing on the card if let Some(pw1) = pw1 { - open.verify_user_for_signing(pw1)?; + open.verify_user_signing_pin(pw1)?; } else { - open.verify_user_for_signing_pinpad(pinpad_prompt)?; + open.verify_user_signing_pinpad(pinpad_prompt)?; } let mut sign = open.to_signing_card(None)?; From 468402364a21eeb251994995ebd746be1788a3f9 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 5 Sep 2023 17:44:56 +0200 Subject: [PATCH 101/115] openpgp-card-sequoia: rename set_user_interaction_flag to set_touch_policy --- openpgp-card-sequoia/src/lib.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 241899a..25a9c8d 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -1104,11 +1104,12 @@ impl Card> { self.card().set_pw_status_bytes(pw_status, long) } - pub fn set_user_interaction_flag( - &mut self, - key: KeyType, - policy: TouchPolicy, - ) -> Result<(), Error> { + /// Set the touch policy for a key slot (if the card supports this + /// feature). + /// + /// Note that the current touch policy setting (if available) can be read + /// via [`Card::user_interaction_flag`]. + pub fn set_touch_policy(&mut self, key: KeyType, policy: TouchPolicy) -> Result<(), Error> { let uif = match key { KeyType::Signing => self.state.tx.state.ard().uif_pso_cds()?, KeyType::Decryption => self.state.tx.state.ard().uif_pso_dec()?, From 290ca2acd076747ae390d488f49f054a458f2459 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 5 Sep 2023 17:45:20 +0200 Subject: [PATCH 102/115] openpgp-card-sequoia: add set_user_pin_signing_validity() --- openpgp-card-sequoia/src/lib.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 25a9c8d..52a5f52 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -1104,6 +1104,19 @@ impl Card> { self.card().set_pw_status_bytes(pw_status, long) } + /// Configure the "only valid for one PSO:CDS" setting in PW Status Bytes. + /// + /// If `once` is `true`, the User PIN must be verified before each + /// signing operation on the card. + /// If `once` is `false`, one User PIN verification is good for an + /// unlimited number of signing operations. + pub fn set_user_pin_signing_validity(&mut self, once: bool) -> Result<(), Error> { + let mut pws = self.as_transaction().pw_status_bytes()?; + pws.set_pw1_cds_valid_once(once); + + self.set_pw_status_bytes(&pws, false) + } + /// Set the touch policy for a key slot (if the card supports this /// feature). /// From 01cc2caafc7f045c9b33daba75cc9eafc0d55ee2 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 5 Sep 2023 18:23:22 +0200 Subject: [PATCH 103/115] openpgp-card-sequoia: rustdoc improvements --- openpgp-card-sequoia/src/lib.rs | 60 +++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 52a5f52..c102425 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -203,10 +203,10 @@ impl<'p, const S: usize> From<&'p [u8; S]> for OptionalPin<'p> { /// Representation of an OpenPGP card. /// -/// A card transitions between `State`s by starting a transaction (that groups together a number +/// A card transitions between [`State`]s by starting a transaction (that groups together a number /// of operations into an atomic sequence) and via PIN presentation. /// -/// Depending on the `State` of the card, and the access privileges that are associated with that +/// Depending on the [`State`] of the card, and the access privileges that are associated with that /// state, different operations can be performed. In many cases, client software will want to /// transition between states while performing one workflow for the user. pub struct Card @@ -217,7 +217,7 @@ where } impl Card { - /// Takes an iterator over CardBackends, tries to SELECT the OpenPGP card + /// Takes an iterator over [`CardBackend`]s, tries to SELECT the OpenPGP card /// application on each of them, and checks if its application id matches /// `ident`. /// Returns a [`Card`] for the first match, if any. @@ -256,7 +256,7 @@ impl Card { /// Starts a transaction on the underlying backend (if the backend /// implementation supports transactions, otherwise the backend - /// will operate without transactions guarantees). + /// will operate without transaction guarantees). /// /// The resulting [`Card`] object allows performing /// operations on the card. @@ -290,7 +290,8 @@ impl<'a> Card> { /// with the current data on the card. /// /// This is needed e.g. after importing or generating keys on a card, to - /// see these changes reflected in `self.ard`. + /// see these changes reflected in the internal cached + /// [`openpgp_card::card_do::ApplicationRelatedData`]. pub fn reload_ard(&mut self) -> Result<(), Error> { // FIXME: this should be implemented internally, transparent to users @@ -1150,19 +1151,28 @@ impl Card> { Ok(()) } - pub fn set_resetting_code(&mut self, pin: &str) -> Result<(), Error> { - self.card().set_resetting_code(pin.as_bytes()) - } - - pub fn set_pso_enc_dec_key(&mut self, key: &[u8]) -> Result<(), Error> { - self.card().set_pso_enc_dec_key(key) - } - + /// Set the User PIN on the card (also resets the User PIN error count) pub fn reset_user_pin(&mut self, new: &str) -> Result<(), Error> { self.card().reset_retry_counter_pw1(new.as_bytes(), None) } - /// Upload a ValidErasedKeyAmalgamation to the card as a specific KeyType. + /// Define the "resetting code" on the card + pub fn set_resetting_code(&mut self, pin: &str) -> Result<(), Error> { + self.card().set_resetting_code(pin.as_bytes()) + } + + /// Set optional AES encryption/decryption key + /// (32 bytes for AES256, or 16 bytes for AES128), + /// if the card supports this feature. + /// + /// The availability of this feature is announced in + /// [`Card::extended_capabilities`]. + pub fn set_pso_enc_dec_key(&mut self, key: &[u8]) -> Result<(), Error> { + self.card().set_pso_enc_dec_key(key) + } + + /// Upload a Sequoia PGP [`ValidErasedKeyAmalgamation`] to the card into + /// a specific key slot. /// /// (The caller needs to make sure that `vka` is suitable as `key_type`) pub fn upload_key( @@ -1193,6 +1203,18 @@ impl Card> { ) } + /// Configure the `algorithm_attributes` for key slot `key_type` based on + /// the algorithm `algo`. + /// This can be useful in preparation for [`Self::generate_key`]. + /// + /// This is a convenience wrapper for [`Self::set_algorithm_attributes`] + /// that determines the exact appropriate [`AlgorithmAttributes`] by + /// reading information from the card. + pub fn set_algorithm(&mut self, key_type: KeyType, algo: AlgoSimple) -> Result<(), Error> { + let attr = algo.matching_algorithm_attributes(self.card(), key_type)?; + self.set_algorithm_attributes(key_type, &attr) + } + /// Configure the key slot `key_type` to `algorithm_attributes`. /// This can be useful in preparation for [`Self::generate_key`]. /// @@ -1200,6 +1222,9 @@ impl Card> { /// Different OpenPGP card implementations may support different /// algorithms, sometimes with differing requirements for the encoding /// (e.g. field sizes) + /// + /// See [`Self::set_algorithm`] for a convenience function that sets + /// the algorithm attributes based on an [`AlgoSimple`]. pub fn set_algorithm_attributes( &mut self, key_type: KeyType, @@ -1209,13 +1234,6 @@ impl Card> { .set_algorithm_attributes(key_type, algorithm_attributes) } - /// Configure the key slot `key_type` to the algorithm `algo`. - /// This can be useful in preparation for [`Self::generate_key`]. - pub fn set_algorithm(&mut self, key_type: KeyType, algo: AlgoSimple) -> Result<(), Error> { - let attr = algo.matching_algorithm_attributes(self.card(), key_type)?; - self.set_algorithm_attributes(key_type, &attr) - } - /// Generate a new cryptographic key in slot `key_type`, with the currently /// configured cryptographic algorithm /// (see [`Self::set_algorithm`] for changing the algorithm setting). From 52a145528efe3198a93d1e4e15d5a0349381f65a Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 5 Sep 2023 18:31:21 +0200 Subject: [PATCH 104/115] openpgp-card-sequoia: cleanup internal ptf() hack This fixes the generation of a mismatching Fingerprint on the card and OpenPGP public key when using generate_key(), which may have been cause by inconsistent kek/kdf parameter use for some ECC decryption subkeys. --- card-functionality/src/tests.rs | 9 +-------- openpgp-card-sequoia/src/lib.rs | 26 +++----------------------- openpgp-card-sequoia/src/util.rs | 10 +++++----- 3 files changed, 9 insertions(+), 36 deletions(-) diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index 809c7a2..2eba414 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -18,7 +18,6 @@ use openpgp_card_sequoia::Card; use sequoia_openpgp::parse::Parse; use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::serialize::SerializeInto; -use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm}; use sequoia_openpgp::Cert; use thiserror; @@ -225,13 +224,7 @@ pub fn test_keygen(tx: &mut Card, param: &[&str]) -> Result> { self.card().key_import(key, key_type) } - /// Wrapper fn for `public_to_fingerprint` that uses SHA256/AES128 as default parameters. - /// - /// FIXME: This is a hack. - /// These parameters should probably be automatically determined based on the algorithm used? - fn ptf( - pkm: &PublicKeyMaterial, - time: KeyGenerationTime, - key_type: KeyType, - ) -> Result { - public_to_fingerprint( - pkm, - &time, - key_type, - Some(HashAlgorithm::SHA256), // FIXME - Some(SymmetricAlgorithm::AES128), // FIXME - ) - } - /// Configure the `algorithm_attributes` for key slot `key_type` based on /// the algorithm `algo`. /// This can be useful in preparation for [`Self::generate_key`]. @@ -1241,6 +1220,7 @@ impl Card> { &mut self, key_type: KeyType, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { - self.card().generate_key(Self::ptf, key_type) + self.card() + .generate_key(crate::util::public_to_fingerprint, key_type) } } diff --git a/openpgp-card-sequoia/src/util.rs b/openpgp-card-sequoia/src/util.rs index e62bff9..2c19caf 100644 --- a/openpgp-card-sequoia/src/util.rs +++ b/openpgp-card-sequoia/src/util.rs @@ -321,16 +321,16 @@ pub fn public_key_material_to_key( /// Mapping function to get a fingerprint from "PublicKeyMaterial + /// timestamp + KeyType" (intended for use with `CardApp.generate_key()`). /// -/// For ECC decryption keys, `hash` and `sym` can be optionally specified. +/// For ECC decryption keys, `hash` and `sym` are set by Sequoia. +/// This fingerprint calculation is based on the parameters that get +/// selected in [`public_key_material_to_key`]. pub(crate) fn public_to_fingerprint( pkm: &PublicKeyMaterial, - time: &KeyGenerationTime, + time: KeyGenerationTime, kt: KeyType, - hash: Option, - sym: Option, ) -> Result { // Transform PublicKeyMaterial into a Sequoia Key - let key = public_key_material_to_key(pkm, kt, time, hash, sym)?; + let key = public_key_material_to_key(pkm, kt, &time, None, None)?; // Get fingerprint from the Sequoia Key let fp = key.fingerprint(); From e6bb9cccca9c7fe2d05fc7c31f6c910d800faf6b Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 6 Sep 2023 01:09:11 +0200 Subject: [PATCH 105/115] openpgp-card-sequoia: rename Card::into_card -> into_backend --- openpgp-card-sequoia/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 6fe6a64..bf41645 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -268,7 +268,7 @@ impl Card { /// This is useful to take the card object into a different context /// (e.g. to perform operations on the card with the `yubikey-management` /// crate, without closing the connection to the card). - pub fn into_card(self) -> Box { + pub fn into_backend(self) -> Box { self.state.pgp.into_card() } } From 46b265637c17dada75c5fdf77aca80ac7bc65ac4 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 10 Sep 2023 13:58:46 +0200 Subject: [PATCH 106/115] ci: run cargo-semver-checks --- .gitlab-ci.yml | 51 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 21ff91f..3173632 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -47,22 +47,6 @@ cargo-fmt: - cargo +nightly fmt -- --check cache: [ ] -cargo-deny: - stage: lint - image: rust:latest - before_script: - # The cargo deny binary is cached, so installing it errors if it was - # restored from cache. Ignore that error. - # Clear the cache to force a rebuild. - - cargo install --locked cargo-deny || true - - *report-rust - script: - - cargo deny check - cache: - paths: - - cargo/bin/cargo-deny - key: "deny" - cargo-clippy: stage: lint image: rust:latest @@ -82,6 +66,41 @@ cargo-clippy: # override the key key: "rust-latest" +semver-checks: + stage: lint + image: rust:latest + before_script: + - mkdir -p /run/user/$UID + - apt update -y -qq + - apt install -y -qq --no-install-recommends git clang make pkg-config nettle-dev libssl-dev capnproto ca-certificates libpcsclite-dev + - apt clean + - *report-rust + script: + - cargo install cargo-semver-checks + - cargo semver-checks -p card-backend -p card-backend-pcsc -p card-backend-scdc -p openpgp-card -p openpgp-card-sequoia + allow_failure: true + cache: + # inherit all general cache settings + <<: *general_cache_config + # override the key + key: "rust-latest" + +cargo-deny: + stage: lint + image: rust:latest + before_script: + # The cargo deny binary is cached, so installing it errors if it was + # restored from cache. Ignore that error. + # Clear the cache to force a rebuild. + - cargo install --locked cargo-deny || true + - *report-rust + script: + - cargo deny check + cache: + paths: + - cargo/bin/cargo-deny + key: "deny" + udeps: stage: udeps needs: [ ] From 20ee493ea87758001a5344a718af3db0090381af Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 10 Sep 2023 13:59:04 +0200 Subject: [PATCH 107/115] openpgp-card-sequoia: README adjustment --- openpgp-card-sequoia/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpgp-card-sequoia/README.md b/openpgp-card-sequoia/README.md index 5417c3b..18cab62 100644 --- a/openpgp-card-sequoia/README.md +++ b/openpgp-card-sequoia/README.md @@ -1,5 +1,5 @@ @@ -12,7 +12,7 @@ It offers convenient access to [OpenPGP card](https://en.wikipedia.org/wiki/OpenPGP_card) functionality using [Sequoia PGP](https://sequoia-pgp.org/). -Note: the current API of this crate is an early draft, reflected by version numbers in the 0.0.x range. +Note: The API of this crate is not finalized yet, please expect occasional breaking changes. **Example code** From 3235f1a8a65a6b7ef0c44488cd608f9c162ead94 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 3 Nov 2023 09:24:13 +0100 Subject: [PATCH 108/115] Ignore sequoia-openpgp deprecations. sequoia-openpgp's deprecations in 1.17 effectively force users that enable "-D warnings" to either upgrade to 1.17 (which would force our downstreams to also use that version). Alternatives are: - upgrade sequoia-openpgp - disable "-D warnings" - ignore the deprecations with #allow. --- card-functionality/src/util.rs | 2 ++ openpgp-card-sequoia/src/decryptor.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/card-functionality/src/util.rs b/card-functionality/src/util.rs index 9673186..9b4d476 100644 --- a/card-functionality/src/util.rs +++ b/card-functionality/src/util.rs @@ -14,6 +14,7 @@ use sequoia_openpgp::parse::stream::{ }; use sequoia_openpgp::parse::Parse; use sequoia_openpgp::policy::{Policy, StandardPolicy}; +#[allow(deprecated)] use sequoia_openpgp::serialize::stream::{Armorer, Encryptor, LiteralWriter, Message}; use sequoia_openpgp::Cert; @@ -124,6 +125,7 @@ pub fn encrypt_to(cleartext: &str, cert: &Cert) -> Result { let message = Message::new(&mut sink); let message = Armorer::new(message).build()?; + #[allow(deprecated)] let message = Encryptor::for_recipients(message, recipients).build()?; let mut w = LiteralWriter::new(message).build()?; w.write_all(cleartext.as_bytes())?; diff --git a/openpgp-card-sequoia/src/decryptor.rs b/openpgp-card-sequoia/src/decryptor.rs index 65300b5..0fdbed7 100644 --- a/openpgp-card-sequoia/src/decryptor.rs +++ b/openpgp-card-sequoia/src/decryptor.rs @@ -114,6 +114,7 @@ impl<'a, 'app> crypto::Decryptor for CardDecryptor<'a, 'app> { #[allow(non_snake_case)] let S: crypto::mem::Protected = dec.into(); + #[allow(deprecated)] Ok(crypto::ecdh::decrypt_unwrap(&self.public, &S, ciphertext)?) } From cb53e0826cd784e9baec1a23331d1d300221a935 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 3 Nov 2023 09:27:48 +0100 Subject: [PATCH 109/115] ci: Install cmake for semver-checks --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3173632..389a7fd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -72,7 +72,7 @@ semver-checks: before_script: - mkdir -p /run/user/$UID - apt update -y -qq - - apt install -y -qq --no-install-recommends git clang make pkg-config nettle-dev libssl-dev capnproto ca-certificates libpcsclite-dev + - apt install -y -qq --no-install-recommends git clang make cmake pkg-config nettle-dev libssl-dev capnproto ca-certificates libpcsclite-dev - apt clean - *report-rust script: From 318571db4f21ba669e1f2133fa56569a869e5489 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 3 Nov 2023 15:02:43 +0100 Subject: [PATCH 110/115] ci: pick dependency versions so that rustc 1.63 can build --- .gitlab-ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 389a7fd..626612d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -144,6 +144,9 @@ cargo-test-debian-bookworm: - apt clean - *report-rust - cargo update + - cargo update -p sequoia-openpgp --precise 1.15.0 + - cargo update -p buffered-reader --precise 1.1.5 + - cargo update -p regex --precise 1.9.6 - cargo update -p lalrpop@0.20.0 --precise 0.19.12 # hack to work with Rust 1.63 - cargo update -p petgraph --precise 0.6.3 # hack to work with Rust 1.63 script: @@ -227,6 +230,9 @@ run_cardtest_ykneo: - export PATH="$HOME/.cargo/bin:$PATH" - *report-rust - cargo update + - cargo update -p sequoia-openpgp --precise 1.15.0 + - cargo update -p buffered-reader --precise 1.1.5 + - cargo update -p regex --precise 1.9.6 - cargo update -p lalrpop@0.20.0 --precise 0.19.12 # hack to work with Rust 1.63 - cargo update -p petgraph --precise 0.6.3 # hack to work with Rust 1.63 script: @@ -251,6 +257,9 @@ run_cardtest_fluffypgp: - export PATH="$HOME/.cargo/bin:$PATH" - *report-rust - cargo update + - cargo update -p sequoia-openpgp --precise 1.15.0 + - cargo update -p buffered-reader --precise 1.1.5 + - cargo update -p regex --precise 1.9.6 - cargo update -p lalrpop@0.20.0 --precise 0.19.12 # hack to work with Rust 1.63 - cargo update -p petgraph --precise 0.6.3 # hack to work with Rust 1.63 script: From 00491e8de83411b1f4bb2ddceef612051b7b20f8 Mon Sep 17 00:00:00 2001 From: RyanSquared Date: Sun, 7 Jan 2024 21:43:26 -0500 Subject: [PATCH 111/115] openpgp-card: send all bytes of creation time --- openpgp-card/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index f327aea..82081f1 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -1209,7 +1209,6 @@ impl<'a> Transaction<'a> { .get() .to_be_bytes() .iter() - .skip_while(|&&e| e == 0) .copied() .collect(); From 799d077d559f0184cd6f14a7a71ae0fba5f79c35 Mon Sep 17 00:00:00 2001 From: RyanSquared Date: Sun, 7 Jan 2024 21:51:21 -0500 Subject: [PATCH 112/115] openpgp-card: optimize use of iter/collect to use to_vec --- openpgp-card/src/lib.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 82081f1..e34e86a 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -1205,12 +1205,7 @@ impl<'a> Transaction<'a> { log::info!("OpenPgpTransaction: set_creation_time"); // Timestamp update - let time_value: Vec = time - .get() - .to_be_bytes() - .iter() - .copied() - .collect(); + let time_value: Vec = time.get().to_be_bytes().to_vec(); let cmd = commands::put_data(key_type.timestamp_put_tag(), time_value); From 3d407eaa8ecf47cf84ad62e32d6d38a7a035eba4 Mon Sep 17 00:00:00 2001 From: RyanSquared Date: Sun, 7 Jan 2024 22:06:06 -0500 Subject: [PATCH 113/115] cargo-deny: skip RUSTSEC-2023-0071 --- deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/deny.toml b/deny.toml index 3e7ccc7..2ff949c 100644 --- a/deny.toml +++ b/deny.toml @@ -11,6 +11,7 @@ notice = "warn" ignore = [ # Ignore time issue for now as there is no solution "RUSTSEC-2020-0071", + "RUSTSEC-2023-0071", ] [licenses] unlicensed = "deny" From 9e500e145cd1e0ca7075bbbaad3998c4f42c098e Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 8 Jan 2024 04:35:29 +0100 Subject: [PATCH 114/115] openpgp-card: add manufacturer ID for Nitrokey --- openpgp-card/src/card_do/application_id.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpgp-card/src/card_do/application_id.rs b/openpgp-card/src/card_do/application_id.rs index 579d8f6..5fdcf9f 100644 --- a/openpgp-card/src/card_do/application_id.rs +++ b/openpgp-card/src/card_do/application_id.rs @@ -57,6 +57,9 @@ impl ApplicationIdentifier { /// Mapping of manufacturer id to a name, data from: /// [2022-04-07] + /// + /// Also see: + /// https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=scd/app-openpgp.c;hb=HEAD#l292 pub fn manufacturer_name(&self) -> &'static str { match self.manufacturer { 0x0000 => "Testcard", @@ -70,6 +73,7 @@ impl ApplicationIdentifier { 0x0008 => "LogoEmail", 0x0009 => "Fidesmo AB", 0x000A => "Dangerous Things", + 0x000F => "Nitrokey GmbH", 0x000B => "Feitian Technologies", 0x002A => "Magrathea", 0x0042 => "GnuPG e.V.", From c0f01442e5798c2b33e92d7d6feb30c6ec5a71b7 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 8 Jan 2024 04:35:48 +0100 Subject: [PATCH 115/115] openpgp-card: bump version to 0.4.1 --- openpgp-card/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpgp-card/Cargo.toml b/openpgp-card/Cargo.toml index 74aabb1..933f098 100644 --- a/openpgp-card/Cargo.toml +++ b/openpgp-card/Cargo.toml @@ -5,7 +5,7 @@ name = "openpgp-card" description = "A client implementation for the OpenPGP card specification" license = "MIT OR Apache-2.0" -version = "0.4.0" +version = "0.4.1" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card"