Add openpgp-card-tools crate
This commit is contained in:
parent
aa7528ec9a
commit
59d77f584d
8 changed files with 898 additions and 2 deletions
|
@ -1,13 +1,13 @@
|
|||
# SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||
# SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"openpgp-card-apps",
|
||||
"openpgp-card",
|
||||
"openpgp-card-sequoia",
|
||||
"pcsc",
|
||||
"scdc",
|
||||
"openpgp-card-apps",
|
||||
"tools",
|
||||
"card-functionality",
|
||||
]
|
||||
|
|
28
tools/Cargo.toml
Normal file
28
tools/Cargo.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
# SPDX-FileCopyrightText: 2021 Wiktor Kwapisiewicz <wiktor@metacode.biz>
|
||||
# SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
[package]
|
||||
name = "openpgp-card-tools"
|
||||
description = "Tools for using OpenPGP cards from the command line"
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.0.1"
|
||||
authors = ["Heiko Schaefer <heiko@schaefer.name>"]
|
||||
edition = "2018"
|
||||
repository = "https://gitlab.com/hkos/openpgp-card"
|
||||
documentation = "https://docs.rs/crate/openpgp-card-tools"
|
||||
|
||||
[dependencies]
|
||||
sequoia-openpgp = "1.3"
|
||||
nettle = "7"
|
||||
openpgp-card = { path = "../openpgp-card", version = "0.0.4" }
|
||||
openpgp-card-pcsc = { path = "../pcsc", version = "0.0.4" }
|
||||
openpgp-card-scdc = { path = "../scdc", version = "0.0.2" }
|
||||
openpgp-card-sequoia = { path = "../openpgp-card-sequoia", version = "0.0.3" }
|
||||
rpassword = "5"
|
||||
chrono = "0.4"
|
||||
anyhow = "1"
|
||||
thiserror = "1"
|
||||
structopt = "0.3"
|
||||
clap = "2.33"
|
||||
env_logger = "0.8"
|
||||
log = "0.4"
|
129
tools/README.md
Normal file
129
tools/README.md
Normal file
|
@ -0,0 +1,129 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||
SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
-->
|
||||
|
||||
# OpenPGP card tools
|
||||
|
||||
This crate contains two tools for inspecting, configuring and using OpenPGP
|
||||
cards:
|
||||
|
||||
`opgpcard` and `opgpcard-pin`
|
||||
|
||||
## opgpcard
|
||||
|
||||
A tool to inspect, configure and use OpenPGP cards. All calls of this tool
|
||||
are non-interactive (this tool is designed to be easily usable from
|
||||
shell-scripts).
|
||||
|
||||
### List and inspect cards
|
||||
|
||||
List idents of all currently connected cards:
|
||||
```
|
||||
$ opgpcard list
|
||||
```
|
||||
|
||||
Print status information about a card. The card is implicitly selected.
|
||||
However, this only works if exactly one card is connected:
|
||||
```
|
||||
$ opgpcard status
|
||||
```
|
||||
|
||||
Explicitly print the status information for a specific card:
|
||||
```
|
||||
$ opgpcard status -c ABCD:12345678
|
||||
```
|
||||
|
||||
Add `-v` for more verbose card status, including the list of supported
|
||||
algorithms of the card:
|
||||
```
|
||||
$ opgpcard status -c ABCD:12345678 -v
|
||||
```
|
||||
|
||||
### 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 -c ABCD:12345678 -p <pin-file> 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 (note: spaces in fingerprints are
|
||||
ignored).
|
||||
```
|
||||
$ opgpcard admin -c ABCD:12345678 -p <pin-file> 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.
|
||||
|
||||
### Set card metadata
|
||||
|
||||
Set cardholder name:
|
||||
```
|
||||
$ opgpcard admin -c ABCD:12345678 -p <pin-file> name "Bar<<Foo"
|
||||
```
|
||||
|
||||
Set cardholder URL:
|
||||
```
|
||||
$ opgpcard admin -c ABCD:12345678 -p <pin-file> url "https://keyurl.example"
|
||||
```
|
||||
|
||||
### Signing
|
||||
|
||||
For now, this tool only supports creating detached signatures, like this
|
||||
(if no input file is set, stdin is read):
|
||||
|
||||
```
|
||||
$ opgpcard sign --detached -c ABCD:12345678 -p <pin-file> -s <cert-file> <input-file>
|
||||
```
|
||||
|
||||
### Decrypting
|
||||
|
||||
Decryption using a card (if no input file is set, stdin is read):
|
||||
|
||||
```
|
||||
$ opgpcard decrypt -c ABCD:12345678 -p <pin-file> -r <cert-file> <input-file>
|
||||
```
|
||||
|
||||
### Factory reset
|
||||
|
||||
Factory reset:
|
||||
```
|
||||
$ opgpcard factory-reset -c ABCD:12345678
|
||||
```
|
||||
|
||||
## opgpcard-pin
|
||||
|
||||
An interactive tool to set the admin and user PINs, and to reset the user
|
||||
PIN on OpenPGP cards.
|
||||
|
||||
Set the user PIN (requires admin PIN):
|
||||
```
|
||||
opgpcard-pin -c 1234:12345678 set-user-pin
|
||||
```
|
||||
|
||||
Set new admin PIN (requires admin PIN):
|
||||
```
|
||||
opgpcard-pin -c 1234:12345678 set-admin-pin
|
||||
```
|
||||
|
||||
Reset user PIN after it has been blocked (requires admin PIN):
|
||||
```
|
||||
opgpcard-pin -c 1234:12345678 reset-user-pin -a
|
||||
```
|
||||
|
||||
Set resetting code (requires admin PIN):
|
||||
```
|
||||
opgpcard-pin -c 1234:12345678 set-reset-code
|
||||
```
|
||||
|
||||
Reset user PIN (requires resetting code):
|
||||
```
|
||||
opgpcard-pin -c 1234:12345678 reset-user-pin
|
||||
```
|
31
tools/src/bin/opgpcard-pin/cli.rs
Normal file
31
tools/src/bin/opgpcard-pin/cli.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use clap::AppSettings;
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
#[structopt(name = "opgpcard",
|
||||
author = "Heiko Schäfer <heiko@schaefer.name>",
|
||||
global_settings(& [AppSettings::VersionlessSubcommands,
|
||||
AppSettings::DisableHelpSubcommand, AppSettings::DeriveDisplayOrder]),
|
||||
about = "A tool for managing OpenPGP cards."
|
||||
)]
|
||||
pub struct Cli {
|
||||
#[structopt(name = "card ident", short = "c", long = "card")]
|
||||
pub ident: String,
|
||||
|
||||
#[structopt(subcommand)]
|
||||
pub cmd: Command,
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
pub enum Command {
|
||||
SetUserPin {},
|
||||
SetAdminPin {},
|
||||
SetResetCode {},
|
||||
ResetUserPin {
|
||||
#[structopt(name = "reset as admin", short = "a", long = "admin")]
|
||||
admin: bool,
|
||||
},
|
||||
}
|
147
tools/src/bin/opgpcard-pin/main.rs
Normal file
147
tools/src/bin/opgpcard-pin/main.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use anyhow::Result;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use openpgp_card_pcsc::PcscClient;
|
||||
use openpgp_card_sequoia::card::Open;
|
||||
|
||||
mod cli;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cli = cli::Cli::from_args();
|
||||
|
||||
let ccb = PcscClient::open_by_ident(&cli.ident)?;
|
||||
let mut card = Open::open_card(ccb)?;
|
||||
|
||||
match cli.cmd {
|
||||
cli::Command::SetUserPin {} => {
|
||||
// get current user pin
|
||||
let pin =
|
||||
rpassword::read_password_from_tty(Some("Enter user PIN: "))?;
|
||||
|
||||
// verify pin
|
||||
card.verify_user(&pin)?;
|
||||
|
||||
// get new user pin
|
||||
let newpin1 = rpassword::read_password_from_tty(Some(
|
||||
"Enter new user PIN: ",
|
||||
))?;
|
||||
let newpin2 = rpassword::read_password_from_tty(Some(
|
||||
"Repeat the new user PIN: ",
|
||||
))?;
|
||||
|
||||
if newpin1 != newpin2 {
|
||||
return Err(anyhow::anyhow!("PINs do not match.").into());
|
||||
}
|
||||
|
||||
// set new user pin
|
||||
card.change_user_pin(&pin, &newpin1)?;
|
||||
|
||||
println!("\nUser PIN has been set.");
|
||||
}
|
||||
cli::Command::SetAdminPin {} => {
|
||||
// get current admin pin
|
||||
let pin =
|
||||
rpassword::read_password_from_tty(Some("Enter admin PIN: "))?;
|
||||
|
||||
// verify pin
|
||||
card.verify_admin(&pin)?;
|
||||
|
||||
// get new admin pin
|
||||
let newpin1 = rpassword::read_password_from_tty(Some(
|
||||
"Enter new admin PIN: ",
|
||||
))?;
|
||||
let newpin2 = rpassword::read_password_from_tty(Some(
|
||||
"Repeat the new admin PIN: ",
|
||||
))?;
|
||||
|
||||
if newpin1 != newpin2 {
|
||||
return Err(anyhow::anyhow!("PINs do not match.").into());
|
||||
}
|
||||
|
||||
// set new user pin
|
||||
card.change_admin_pin(&pin, &newpin1)?;
|
||||
|
||||
println!("\nAdmin PIN has been set.");
|
||||
}
|
||||
cli::Command::SetResetCode {} => {
|
||||
// get current admin pin
|
||||
let pin =
|
||||
rpassword::read_password_from_tty(Some("Enter admin PIN: "))?;
|
||||
|
||||
// verify admin pin
|
||||
card.verify_admin(&pin)?;
|
||||
|
||||
if let Some(mut admin) = card.admin_card() {
|
||||
// ask user for new resetting code
|
||||
let newpin1 = rpassword::read_password_from_tty(Some(
|
||||
"Enter new resetting code: ",
|
||||
))?;
|
||||
let newpin2 = rpassword::read_password_from_tty(Some(
|
||||
"Repeat the new resetting code: ",
|
||||
))?;
|
||||
|
||||
if newpin1 == newpin2 {
|
||||
admin.set_resetting_code(&pin)?;
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("PINs do not match.").into());
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Failed to use card in admin-mode."
|
||||
)
|
||||
.into());
|
||||
}
|
||||
println!("\nResetting code has been set.");
|
||||
}
|
||||
cli::Command::ResetUserPin { admin } => {
|
||||
// either with resetting code, or by presenting pw3
|
||||
|
||||
let rst = if admin {
|
||||
// get current admin pin
|
||||
let pin = rpassword::read_password_from_tty(Some(
|
||||
"Enter admin PIN: ",
|
||||
))?;
|
||||
|
||||
// verify pin
|
||||
card.verify_admin(&pin)?;
|
||||
|
||||
None
|
||||
} else {
|
||||
// get current admin pin
|
||||
let rst = rpassword::read_password_from_tty(Some(
|
||||
"Enter resetting code: ",
|
||||
))?;
|
||||
|
||||
Some(rst)
|
||||
};
|
||||
|
||||
// get new user pin
|
||||
let newpin1 = rpassword::read_password_from_tty(Some(
|
||||
"Enter new user PIN: ",
|
||||
))?;
|
||||
let newpin2 = rpassword::read_password_from_tty(Some(
|
||||
"Repeat the new user PIN: ",
|
||||
))?;
|
||||
|
||||
if newpin1 != newpin2 {
|
||||
return Err(anyhow::anyhow!("PINs do not match.").into());
|
||||
}
|
||||
if let Some(rst) = rst {
|
||||
// reset to new user pin
|
||||
card.reset_user_pin(&rst, &newpin1)?;
|
||||
} else {
|
||||
if let Some(mut admin) = card.admin_card() {
|
||||
admin.reset_user_pin(&newpin1)?;
|
||||
} else {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
println!("\nUser PIN has been set.");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
117
tools/src/bin/opgpcard/cli.rs
Normal file
117
tools/src/bin/opgpcard/cli.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use clap::AppSettings;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
#[structopt(name = "opgpcard",
|
||||
author = "Heiko Schäfer <heiko@schaefer.name>",
|
||||
global_settings(& [AppSettings::VersionlessSubcommands,
|
||||
AppSettings::DisableHelpSubcommand, AppSettings::DeriveDisplayOrder]),
|
||||
about = "A tool for managing OpenPGP cards."
|
||||
)]
|
||||
pub struct Cli {
|
||||
#[structopt(subcommand)]
|
||||
pub cmd: Command,
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
pub enum Command {
|
||||
List {},
|
||||
Status {
|
||||
#[structopt(name = "card ident", short = "c", long = "card")]
|
||||
ident: Option<String>,
|
||||
|
||||
#[structopt(name = "verbose", short = "v", long = "verbose")]
|
||||
verbose: bool,
|
||||
},
|
||||
FactoryReset {
|
||||
#[structopt(name = "card ident", short = "c", long = "card")]
|
||||
ident: String,
|
||||
},
|
||||
Admin {
|
||||
#[structopt(name = "card ident", short = "c", long = "card")]
|
||||
ident: String,
|
||||
|
||||
#[structopt(name = "admin pin file", short = "p", long = "pin-file")]
|
||||
pin_file: PathBuf,
|
||||
|
||||
#[structopt(subcommand)]
|
||||
cmd: AdminCommand,
|
||||
},
|
||||
Decrypt {
|
||||
#[structopt(name = "card ident", short = "c", long = "card")]
|
||||
ident: String,
|
||||
|
||||
#[structopt(name = "user pin file", short = "p", long = "pin-file")]
|
||||
pin_file: PathBuf,
|
||||
|
||||
#[structopt(
|
||||
name = "recipient-cert-file",
|
||||
short = "r",
|
||||
long = "recipient-cert"
|
||||
)]
|
||||
cert_file: PathBuf,
|
||||
|
||||
#[structopt(about = "Input file (stdin if unset)", name = "input")]
|
||||
input: Option<PathBuf>,
|
||||
},
|
||||
Sign {
|
||||
#[structopt(name = "card ident", short = "c", long = "card")]
|
||||
ident: String,
|
||||
|
||||
#[structopt(name = "user pin file", short = "p", long = "pin-file")]
|
||||
pin_file: PathBuf,
|
||||
|
||||
#[structopt(name = "detached", short = "d", long = "detached")]
|
||||
detached: bool,
|
||||
|
||||
#[structopt(
|
||||
name = "signer-cert-file",
|
||||
short = "s",
|
||||
long = "signer-cert"
|
||||
)]
|
||||
cert_file: PathBuf,
|
||||
|
||||
#[structopt(about = "Input file (stdin if unset)", name = "input")]
|
||||
input: Option<PathBuf>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
pub enum AdminCommand {
|
||||
/// Set name
|
||||
Name { name: String },
|
||||
|
||||
/// Set URL
|
||||
Url { url: String },
|
||||
|
||||
/// Import a Key.
|
||||
/// If no fingerprint is provided, the key will
|
||||
Import {
|
||||
keyfile: PathBuf,
|
||||
|
||||
#[structopt(
|
||||
name = "Signature key fingerprint",
|
||||
short = "s",
|
||||
long = "sig-fp"
|
||||
)]
|
||||
sig_fp: Option<String>,
|
||||
|
||||
#[structopt(
|
||||
name = "Decryption key fingerprint",
|
||||
short = "d",
|
||||
long = "dec-fp"
|
||||
)]
|
||||
dec_fp: Option<String>,
|
||||
|
||||
#[structopt(
|
||||
name = "Authentication key fingerprint",
|
||||
short = "a",
|
||||
long = "auth-fp"
|
||||
)]
|
||||
auth_fp: Option<String>,
|
||||
},
|
||||
}
|
377
tools/src/bin/opgpcard/main.rs
Normal file
377
tools/src/bin/opgpcard/main.rs
Normal file
|
@ -0,0 +1,377 @@
|
|||
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use anyhow::Result;
|
||||
use std::path::Path;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use sequoia_openpgp::parse::{stream::DecryptorBuilder, Parse};
|
||||
use sequoia_openpgp::policy::StandardPolicy;
|
||||
use sequoia_openpgp::serialize::stream::{Armorer, Message, Signer};
|
||||
use sequoia_openpgp::Cert;
|
||||
|
||||
use openpgp_card_sequoia::card::Admin;
|
||||
use openpgp_card_sequoia::sq_util;
|
||||
|
||||
use openpgp_card::{card_do::Sex, KeyType};
|
||||
|
||||
mod cli;
|
||||
mod util;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cli = cli::Cli::from_args();
|
||||
|
||||
match cli.cmd {
|
||||
cli::Command::List {} => {
|
||||
list_cards()?;
|
||||
}
|
||||
cli::Command::Status { ident, verbose } => {
|
||||
print_status(ident, verbose)?;
|
||||
}
|
||||
cli::Command::Decrypt {
|
||||
ident,
|
||||
pin_file,
|
||||
cert_file,
|
||||
input,
|
||||
} => {
|
||||
decrypt(&ident, &pin_file, &cert_file, input.as_deref())?;
|
||||
}
|
||||
cli::Command::Sign {
|
||||
ident,
|
||||
pin_file,
|
||||
cert_file,
|
||||
detached,
|
||||
input,
|
||||
} => {
|
||||
if detached {
|
||||
sign_detached(
|
||||
&ident,
|
||||
&pin_file,
|
||||
&cert_file,
|
||||
input.as_deref(),
|
||||
)?;
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Only detached signatures are supported for now"
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
cli::Command::FactoryReset { ident } => {
|
||||
factory_reset(&ident)?;
|
||||
}
|
||||
cli::Command::Admin {
|
||||
ident,
|
||||
pin_file,
|
||||
cmd,
|
||||
} => {
|
||||
let mut open = util::open_card(&ident)?;
|
||||
let mut admin = util::get_admin(&mut open, &pin_file)?;
|
||||
|
||||
match cmd {
|
||||
cli::AdminCommand::Name { name } => {
|
||||
let _ = admin.set_name(&name)?;
|
||||
}
|
||||
cli::AdminCommand::Url { url } => {
|
||||
let _ = admin.set_url(&url)?;
|
||||
}
|
||||
cli::AdminCommand::Import {
|
||||
keyfile,
|
||||
sig_fp,
|
||||
dec_fp,
|
||||
auth_fp,
|
||||
} => {
|
||||
let key = Cert::from_file(keyfile)?;
|
||||
|
||||
if (&sig_fp, &dec_fp, &auth_fp) == (&None, &None, &None) {
|
||||
// If no fingerprint has been provided, we check if
|
||||
// there is zero or one (sub)key for each keytype,
|
||||
// and if so, import these keys to the card.
|
||||
key_import_yolo(admin, &key)?;
|
||||
} else {
|
||||
key_import_explicit(
|
||||
admin, &key, sig_fp, dec_fp, auth_fp,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_cards() -> Result<()> {
|
||||
let cards = util::cards()?;
|
||||
if !cards.is_empty() {
|
||||
println!("Available OpenPGP cards:");
|
||||
|
||||
for card in cards {
|
||||
println!(" {}", card.application_identifier()?.ident());
|
||||
}
|
||||
} else {
|
||||
println!("No OpenPGP cards found.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_status(ident: Option<String>, verbose: bool) -> Result<()> {
|
||||
let mut open = if let Some(ident) = ident {
|
||||
util::open_card(&ident)?
|
||||
} else {
|
||||
let mut cards = util::cards()?;
|
||||
if cards.len() == 1 {
|
||||
cards.pop().unwrap()
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Found {} cards", cards.len()).into());
|
||||
}
|
||||
};
|
||||
|
||||
print!("OpenPGP card {}", open.application_identifier()?.ident());
|
||||
|
||||
let ai = open.application_identifier()?;
|
||||
let version = ai.version().to_be_bytes();
|
||||
println!(" (card version {}.{})\n", version[0], version[1]);
|
||||
|
||||
// card / cardholder metadata
|
||||
let crd = open.cardholder_related_data()?;
|
||||
|
||||
if let Some(name) = crd.name() {
|
||||
print!("Cardholder: ");
|
||||
|
||||
// This field is silly, maybe ignore it?!
|
||||
if let Some(sex) = crd.sex() {
|
||||
if sex == Sex::Male {
|
||||
print!("Mr. ");
|
||||
} else if sex == Sex::Female {
|
||||
print!("Mrs. ");
|
||||
}
|
||||
}
|
||||
|
||||
// re-format name ("last<<first")
|
||||
let name: Vec<_> = name.split("<<").collect();
|
||||
let name = name.iter().cloned().rev().collect::<Vec<_>>().join(" ");
|
||||
|
||||
println!("{}", name);
|
||||
}
|
||||
|
||||
let url = open.url()?;
|
||||
if !url.is_empty() {
|
||||
println!("URL: {}", url);
|
||||
}
|
||||
|
||||
if let Some(lang) = crd.lang() {
|
||||
let lang = lang
|
||||
.iter()
|
||||
.map(|lang| lang.iter().collect::<String>())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
println!("Language preferences '{}'", lang);
|
||||
}
|
||||
|
||||
// information about subkeys
|
||||
|
||||
let fps = open.fingerprints()?;
|
||||
let kgt = open.key_generation_times()?;
|
||||
|
||||
println!();
|
||||
println!(
|
||||
"Signature key ({})",
|
||||
open.algorithm_attributes(KeyType::Signing)?,
|
||||
);
|
||||
if let Some(fp) = fps.signature() {
|
||||
println!(" fingerprint: {}", fp.to_spaced_hex());
|
||||
}
|
||||
if let Some(kgt) = kgt.signature() {
|
||||
println! {" created: {}",kgt.formatted()};
|
||||
}
|
||||
|
||||
println!();
|
||||
println!(
|
||||
"Decryption key ({})",
|
||||
open.algorithm_attributes(KeyType::Decryption)?,
|
||||
);
|
||||
if let Some(fp) = fps.decryption() {
|
||||
println!(" fingerprint: {}", fp.to_spaced_hex());
|
||||
}
|
||||
if let Some(kgt) = kgt.decryption() {
|
||||
println! {" created: {}",kgt.formatted()};
|
||||
}
|
||||
|
||||
println!();
|
||||
println!(
|
||||
"Authentication key ({})",
|
||||
open.algorithm_attributes(KeyType::Authentication)?,
|
||||
);
|
||||
if let Some(fp) = fps.authentication() {
|
||||
println!(" fingerprint: {}", fp.to_spaced_hex());
|
||||
}
|
||||
if let Some(kgt) = kgt.authentication() {
|
||||
println! {" created: {}",kgt.formatted()};
|
||||
}
|
||||
|
||||
// technical details about the card and its state
|
||||
|
||||
println!();
|
||||
|
||||
let sst = open.security_support_template()?;
|
||||
println!("Signature counter: {}", sst.get_signature_count());
|
||||
|
||||
let pws = open.pw_status_bytes()?;
|
||||
|
||||
println!(
|
||||
"Signature pin only valid once: {}",
|
||||
pws.get_pw1_cds_valid_once()
|
||||
);
|
||||
|
||||
println!("Password validation retry count:");
|
||||
println!(
|
||||
" user pw: {}, reset: {}, admin pw: {}",
|
||||
pws.get_err_count_pw1(),
|
||||
pws.get_err_count_rst(),
|
||||
pws.get_err_count_pw3(),
|
||||
);
|
||||
|
||||
// FIXME: add General key info; login data; KDF setting
|
||||
|
||||
if verbose {
|
||||
if let Some(ai) = open.algorithm_information()? {
|
||||
println!();
|
||||
println!("Supported algorithms:");
|
||||
println!("{}", ai);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decrypt(
|
||||
ident: &str,
|
||||
pin_file: &Path,
|
||||
cert_file: &Path,
|
||||
input: Option<&Path>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let p = StandardPolicy::new();
|
||||
let cert = Cert::from_file(cert_file)?;
|
||||
|
||||
let input = util::open_or_stdin(input.as_deref())?;
|
||||
|
||||
let mut open = util::open_card(&ident)?;
|
||||
let mut user = util::get_user(&mut open, &pin_file)?;
|
||||
let d = user.decryptor(&cert, &p)?;
|
||||
|
||||
let db = DecryptorBuilder::from_reader(input)?;
|
||||
let mut decryptor = db.with_policy(&p, None, d)?;
|
||||
|
||||
std::io::copy(&mut decryptor, &mut std::io::stdout())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sign_detached(
|
||||
ident: &str,
|
||||
pin_file: &Path,
|
||||
cert_file: &Path,
|
||||
input: Option<&Path>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let p = StandardPolicy::new();
|
||||
let cert = Cert::from_file(cert_file)?;
|
||||
|
||||
let mut input = util::open_or_stdin(input.as_deref())?;
|
||||
|
||||
let mut open = util::open_card(&ident)?;
|
||||
let mut sign = util::get_sign(&mut open, &pin_file)?;
|
||||
let s = sign.signer(&cert, &p)?;
|
||||
|
||||
let message = Armorer::new(Message::new(std::io::stdout())).build()?;
|
||||
let mut signer = Signer::new(message, s).detached().build()?;
|
||||
|
||||
std::io::copy(&mut input, &mut signer)?;
|
||||
signer.finalize()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn factory_reset(ident: &str) -> Result<()> {
|
||||
println!("Resetting Card {}", ident);
|
||||
util::open_card(ident)?.factory_reset()
|
||||
}
|
||||
|
||||
fn key_import_yolo(mut admin: Admin, key: &Cert) -> Result<()> {
|
||||
let p = StandardPolicy::new();
|
||||
|
||||
let sig =
|
||||
openpgp_card_sequoia::sq_util::get_subkey(&key, &p, KeyType::Signing)?;
|
||||
|
||||
let dec = openpgp_card_sequoia::sq_util::get_subkey(
|
||||
&key,
|
||||
&p,
|
||||
KeyType::Decryption,
|
||||
)?;
|
||||
|
||||
let auth = openpgp_card_sequoia::sq_util::get_subkey(
|
||||
&key,
|
||||
&p,
|
||||
KeyType::Authentication,
|
||||
)?;
|
||||
|
||||
if let Some(sig) = sig {
|
||||
println!("Uploading {} as signing key", sig.fingerprint());
|
||||
admin.upload_key(sig, KeyType::Signing, None)?;
|
||||
}
|
||||
if let Some(dec) = dec {
|
||||
println!("Uploading {} as decryption key", dec.fingerprint());
|
||||
admin.upload_key(dec, KeyType::Decryption, None)?;
|
||||
}
|
||||
if let Some(auth) = auth {
|
||||
println!("Uploading {} as authentication key", auth.fingerprint());
|
||||
admin.upload_key(auth, KeyType::Authentication, None)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn key_import_explicit(
|
||||
mut admin: Admin,
|
||||
key: &Cert,
|
||||
sig_fp: Option<String>,
|
||||
dec_fp: Option<String>,
|
||||
auth_fp: Option<String>,
|
||||
) -> Result<()> {
|
||||
let p = StandardPolicy::new();
|
||||
|
||||
if let Some(sig_fp) = sig_fp {
|
||||
if let Some(sig) =
|
||||
sq_util::get_subkey_by_fingerprint(&key, &p, &sig_fp)?
|
||||
{
|
||||
println!("Uploading {} as signing key", sig.fingerprint());
|
||||
admin.upload_key(sig, KeyType::Signing, None)?;
|
||||
} else {
|
||||
println!("ERROR: Couldn't find {} as signing key", sig_fp);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(dec_fp) = dec_fp {
|
||||
if let Some(dec) =
|
||||
sq_util::get_subkey_by_fingerprint(&key, &p, &dec_fp)?
|
||||
{
|
||||
println!("Uploading {} as decryption key", dec.fingerprint());
|
||||
admin.upload_key(dec, KeyType::Decryption, None)?;
|
||||
} else {
|
||||
println!("ERROR: Couldn't find {} as decryption key", dec_fp);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(auth_fp) = auth_fp {
|
||||
if let Some(auth) =
|
||||
sq_util::get_subkey_by_fingerprint(&key, &p, &auth_fp)?
|
||||
{
|
||||
println!("Uploading {} as authentication key", auth.fingerprint());
|
||||
admin.upload_key(auth, KeyType::Authentication, None)?;
|
||||
} else {
|
||||
println!("ERROR: Couldn't find {} as authentication key", auth_fp);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
67
tools/src/bin/opgpcard/util.rs
Normal file
67
tools/src/bin/opgpcard/util.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use openpgp_card::Error;
|
||||
use openpgp_card_pcsc::PcscClient;
|
||||
use openpgp_card_sequoia::card::{Admin, Open, Sign, User};
|
||||
use std::path::Path;
|
||||
|
||||
pub(crate) fn cards() -> Result<Vec<Open>> {
|
||||
let mut cards = vec![];
|
||||
|
||||
for card in PcscClient::cards()? {
|
||||
cards.push(Open::open_card(card)?);
|
||||
}
|
||||
|
||||
Ok(cards)
|
||||
}
|
||||
|
||||
pub(crate) fn open_card(ident: &str) -> Result<Open, Error> {
|
||||
let ccb = PcscClient::open_by_ident(ident)?;
|
||||
Open::open_card(ccb)
|
||||
}
|
||||
|
||||
pub(crate) fn get_user<'a>(
|
||||
open: &'a mut Open,
|
||||
pin_file: &Path,
|
||||
) -> Result<User<'a>, Box<dyn std::error::Error>> {
|
||||
open.verify_user(&get_pin(pin_file)?)?;
|
||||
open.user_card()
|
||||
.ok_or_else(|| anyhow::anyhow!("Couldn't get user access").into())
|
||||
}
|
||||
|
||||
pub(crate) fn get_sign<'a>(
|
||||
open: &'a mut Open,
|
||||
pin_file: &Path,
|
||||
) -> Result<Sign<'a>, Box<dyn std::error::Error>> {
|
||||
open.verify_user_for_signing(&get_pin(pin_file)?)?;
|
||||
open.signing_card()
|
||||
.ok_or_else(|| anyhow::anyhow!("Couldn't get sign access").into())
|
||||
}
|
||||
|
||||
pub(crate) fn get_admin<'a>(
|
||||
open: &'a mut Open,
|
||||
pin_file: &Path,
|
||||
) -> Result<Admin<'a>, Box<dyn std::error::Error>> {
|
||||
open.verify_admin(&get_pin(pin_file)?)?;
|
||||
open.admin_card()
|
||||
.ok_or_else(|| anyhow::anyhow!("Couldn't get admin access").into())
|
||||
}
|
||||
|
||||
fn get_pin(pin_file: &Path) -> Result<String> {
|
||||
let pin = std::fs::read_to_string(pin_file)?;
|
||||
Ok(pin.trim().to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn open_or_stdin(
|
||||
f: Option<&Path>,
|
||||
) -> Result<Box<dyn std::io::Read + Send + Sync>> {
|
||||
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())),
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue