Optionally allow interactive PIN entry.

This commit is contained in:
Heiko Schaefer 2022-03-29 23:25:01 +02:00
parent f069fb1e20
commit 1f7d17bc70
No known key found for this signature in database
GPG key ID: 4A849A1904CCBD7D
3 changed files with 123 additions and 51 deletions

View file

@ -1,5 +1,5 @@
<!--
SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
SPDX-License-Identifier: MIT OR Apache-2.0
-->
@ -36,9 +36,12 @@ binaries.
## 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
usable in a non-interactive way (this tool is designed to be easily usable from
shell-scripts).
Alternatively, PINs can be entered interactively on the host computer, or via a pinpad on the smartcard reader,
if available.
### List and inspect cards
List idents of all currently connected cards:
@ -163,21 +166,42 @@ In the example output above, this string is the ssh public key:
`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII2dcYBqMCamidT5MpE3Cl3MIKcYMBekGXbK2aaN6JaH opgpcard:ABCD:01234567`
### Set card metadata
### Admin commands
Set cardholder name:
All `admin` commands need the admin PIN. It can be provided as a file, with `-P <admin-pin-file>`,
for non-interactive use.
Alternatively, the PIN can be entered interactively on the host computer, or via a pinpad if the OpenPGP card is
used in a smartcard reader that has a pinpad.
#### Set cardholder name
Set cardholder name, with pin file:
```
$ opgpcard admin -c ABCD:01234567 -P <admin-pin-file> name "Foo Bar"
```
Set cardholder URL:
Set cardholder name, with interactive PIN input
(either on the host computer, or via a smartcard reader pinpad):
```
$ opgpcard admin -c ABCD:01234567 name "Foo Bar"
```
#### Set cardholder URL
```
$ opgpcard admin -c ABCD:01234567 -P <admin-pin-file> url "https://key.url.example"
```
### Import keys
or interactively
```
$ opgpcard admin -c ABCD:01234567 url "https://key.url.example"
```
#### 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`:
@ -186,6 +210,12 @@ Import private key onto a card. This works if at most one (sub)key per role
$ opgpcard admin -c ABCD:01234567 -P <admin-pin-file> import key.priv
```
or interactively
```
$ opgpcard admin -c 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 (note: spaces in fingerprints are ignored).
@ -200,10 +230,26 @@ $ opgpcard admin -c ABCD:01234567 -P <admin-pin-file> import key.priv \
When fingerprints are only specified for a subset of the roles, no keys will
be imported for the other roles.
### Generate Keys on the card
#### Generate Keys on the card
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).
The user PIN can be provided with the `-p <user-pin-file>`, or interactively on the host computer or via the smartcard
reader pinpad.
```
$ opgpcard admin -c ABCD:01234567 -P <admin-pin-file> generate -p <user-pin-file> -o <output-cert-file> 25519
```
or interactively
```
$ opgpcard admin -c ABCD:01234567 generate -o <output-cert-file> 25519
```
Output will look like:
```
Generate subkey for Signing
Generate subkey for Decryption
Generate subkey for Authentication
@ -241,6 +287,12 @@ For now, this tool only supports creating detached signatures, like this
$ opgpcard sign --detached -c ABCD:01234567 -p <user-pin-file> -s <cert-file> <input-file>
```
or interactively
```
$ opgpcard sign --detached -c ABCD:01234567 -s <cert-file> <input-file>
```
### Decrypting
Decryption using a card (if no input file is set, stdin is read):
@ -249,6 +301,12 @@ Decryption using a card (if no input file is set, stdin is read):
$ opgpcard decrypt -c ABCD:01234567 -p <user-pin-file> -r <cert-file> <input-file>
```
or interactively
```
$ opgpcard decrypt -c ABCD:01234567 -r <cert-file> <input-file>
```
### Factory reset
Factory reset:

View file

@ -23,6 +23,9 @@ use std::io::Write;
mod cli;
mod util;
const ENTER_USER_PIN: &str = "Enter user PIN:";
const ENTER_ADMIN_PIN: &str = "Enter admin PIN:";
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
@ -77,15 +80,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut pgp = OpenPgp::new(&mut card);
let mut open = Open::new(pgp.transaction()?)?;
let admin_pin = util::get_pin(&mut open, admin_pin, ENTER_ADMIN_PIN);
match cmd {
cli::AdminCommand::Name { name } => {
let mut admin = util::verify_to_admin(&mut open, admin_pin)?;
let mut admin = util::verify_to_admin(&mut open, admin_pin.as_deref())?;
let _ = admin.set_name(&name)?;
}
cli::AdminCommand::Url { url } => {
let mut admin = util::verify_to_admin(&mut open, admin_pin)?;
let mut admin = util::verify_to_admin(&mut open, admin_pin.as_deref())?;
let _ = admin.set_url(&url)?;
}
@ -96,7 +100,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
auth_fp,
} => {
let key = Cert::from_file(keyfile)?;
let admin = util::verify_to_admin(&mut open, admin_pin)?;
let admin = util::verify_to_admin(&mut open, admin_pin.as_deref())?;
if (&sig_fp, &dec_fp, &auth_fp) == (&None, &None, &None) {
// If no fingerprint has been provided, we check if
@ -114,10 +118,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
no_auth,
algo,
} => {
let user_pin = util::get_pin(&mut open, user_pin, ENTER_USER_PIN);
generate_keys(
open,
admin_pin,
user_pin,
admin_pin.as_deref(),
user_pin.as_deref(),
output,
!no_decrypt,
!no_auth,
@ -379,7 +385,9 @@ fn decrypt(
let mut open = Open::new(pgp.transaction()?)?;
let mut user = util::verify_to_user(&mut open, pin_file)?;
let user_pin = util::get_pin(&mut open, pin_file, ENTER_USER_PIN);
let mut user = util::verify_to_user(&mut open, user_pin.as_deref())?;
let d = user.decryptor(&cert)?;
let db = DecryptorBuilder::from_reader(input)?;
@ -405,7 +413,9 @@ fn sign_detached(
let mut open = Open::new(pgp.transaction()?)?;
let mut sign = util::verify_to_sign(&mut open, pin_file)?;
let user_pin = util::get_pin(&mut open, pin_file, ENTER_USER_PIN);
let mut sign = util::verify_to_sign(&mut open, user_pin.as_deref())?;
let s = sign.signer(&cert)?;
let message = Armorer::new(Message::new(std::io::stdout())).build()?;
@ -492,8 +502,8 @@ fn key_import_explicit(
fn generate_keys(
mut open: Open,
pw3_path: Option<PathBuf>,
pw1_path: Option<PathBuf>,
admin_pin: Option<&[u8]>,
user_pin: Option<&[u8]>,
output: Option<PathBuf>,
decrypt: bool,
auth: bool,
@ -529,7 +539,7 @@ fn generate_keys(
// 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 open, pw3_path) {
if let Ok(mut admin) = util::verify_to_admin(&mut open, admin_pin) {
gen_subkeys(&mut admin, decrypt, auth, a)?
} else {
return Err(anyhow!("Failed to open card in admin mode."));
@ -539,29 +549,16 @@ fn generate_keys(
// 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 pin = if let Some(pw1) = pw1_path {
Some(util::load_pin(&pw1)?)
} else {
if open.feature_pinpad_verify() {
println!();
if user_pin.is_none() && open.feature_pinpad_verify() {
println!(
"Next: generating your public cert. You will need to enter \
your user PIN multiple times to make binding signatures."
"The public cert will now be generated.\n\n\
You will need to enter your user PIN multiple times during this process.\n\n"
);
} else {
return Err(anyhow!("No user PIN file provided, and no pinpad found"));
}
None
};
let cert = make_cert(
&mut open,
key_sig,
key_dec,
key_aut,
pin.as_deref(),
&|| println!("Enter user PIN on card reader pinpad."),
)?;
let cert = make_cert(&mut open, key_sig, key_dec, key_aut, user_pin, &|| {
println!("Enter user PIN on card reader pinpad.")
})?;
let armored = String::from_utf8(cert.armored().to_vec()?)?;
// Write armored certificate to the output file (or stdout)

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0
use anyhow::{anyhow, Context, Result};
@ -18,12 +18,31 @@ pub(crate) fn open_card(ident: &str) -> Result<impl CardBackend, Error> {
PcscBackend::open_by_ident(ident, None)
}
/// 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(open: &mut Open, pin_file: Option<PathBuf>, msg: &str) -> Option<Vec<u8>> {
if let Some(path) = pin_file {
// we have a pin file
Some(load_pin(&path).ok()?)
} else if !open.feature_pinpad_verify() {
// we have no pin file and no pinpad
let pin = rpassword::read_password_from_tty(Some(msg)).ok()?;
Some(pin.into_bytes())
} else {
// we have a pinpad
None
}
}
pub(crate) fn verify_to_user<'app, 'open>(
open: &'open mut Open<'app>,
pin_file: Option<PathBuf>,
pin: Option<&[u8]>,
) -> Result<User<'app, 'open>, Box<dyn std::error::Error>> {
if let Some(path) = pin_file {
open.verify_user(&load_pin(&path)?)?;
if let Some(pin) = pin {
open.verify_user(pin)?;
} else {
if !open.feature_pinpad_verify() {
return Err(anyhow!("No user PIN file provided, and no pinpad found").into());
@ -38,10 +57,10 @@ pub(crate) fn verify_to_user<'app, 'open>(
pub(crate) fn verify_to_sign<'app, 'open>(
open: &'open mut Open<'app>,
pin_file: Option<PathBuf>,
pin: Option<&[u8]>,
) -> Result<Sign<'app, 'open>, Box<dyn std::error::Error>> {
if let Some(path) = pin_file {
open.verify_user_for_signing(&load_pin(&path)?)?;
if let Some(pin) = pin {
open.verify_user_for_signing(pin)?;
} else {
if !open.feature_pinpad_verify() {
return Err(anyhow!("No user PIN file provided, and no pinpad found").into());
@ -52,14 +71,12 @@ pub(crate) fn verify_to_sign<'app, 'open>(
.ok_or_else(|| anyhow!("Couldn't get sign access").into())
}
// pub fn admin_card<'b>(&'b mut self) -> Option<Admin<'a, 'b>> {
pub(crate) fn verify_to_admin<'app, 'open>(
open: &'open mut Open<'app>,
pin_file: Option<PathBuf>,
pin: Option<&[u8]>,
) -> Result<Admin<'app, 'open>, Box<dyn std::error::Error>> {
if let Some(path) = pin_file {
open.verify_admin(&load_pin(&path)?)?;
if let Some(pin) = pin {
open.verify_admin(pin)?;
} else {
if !open.feature_pinpad_verify() {
return Err(anyhow!("No admin PIN file provided, and no pinpad found").into());