Optionally allow interactive PIN entry.
This commit is contained in:
parent
f069fb1e20
commit
1f7d17bc70
3 changed files with 123 additions and 51 deletions
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in a new issue