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
|
SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
@ -36,9 +36,12 @@ binaries.
|
||||||
## opgpcard
|
## opgpcard
|
||||||
|
|
||||||
A tool to inspect, configure and use OpenPGP cards. All calls of this tool are
|
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).
|
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 and inspect cards
|
||||||
|
|
||||||
List idents of all currently connected 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`
|
`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"
|
$ 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"
|
$ 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
|
Import private key onto a card. This works if at most one (sub)key per role
|
||||||
(sign, decrypt, auth) exists in `key.priv`:
|
(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
|
$ 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
|
Import private key onto a card while explicitly selecting subkeys. Explicitly
|
||||||
specified fingerprints are necessary if more than one subkey exists
|
specified fingerprints are necessary if more than one subkey exists
|
||||||
in `key.priv` for any role (note: spaces in fingerprints are ignored).
|
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
|
When fingerprints are only specified for a subset of the roles, no keys will
|
||||||
be imported for the other roles.
|
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
|
$ 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 Signing
|
||||||
Generate subkey for Decryption
|
Generate subkey for Decryption
|
||||||
Generate subkey for Authentication
|
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>
|
$ 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
|
### Decrypting
|
||||||
|
|
||||||
Decryption using a card (if no input file is set, stdin is read):
|
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>
|
$ 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
|
||||||
|
|
||||||
Factory reset:
|
Factory reset:
|
||||||
|
|
|
@ -23,6 +23,9 @@ use std::io::Write;
|
||||||
mod cli;
|
mod cli;
|
||||||
mod util;
|
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>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
|
@ -77,15 +80,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut pgp = OpenPgp::new(&mut card);
|
let mut pgp = OpenPgp::new(&mut card);
|
||||||
|
|
||||||
let mut open = Open::new(pgp.transaction()?)?;
|
let mut open = Open::new(pgp.transaction()?)?;
|
||||||
|
let admin_pin = util::get_pin(&mut open, admin_pin, ENTER_ADMIN_PIN);
|
||||||
|
|
||||||
match cmd {
|
match cmd {
|
||||||
cli::AdminCommand::Name { name } => {
|
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)?;
|
let _ = admin.set_name(&name)?;
|
||||||
}
|
}
|
||||||
cli::AdminCommand::Url { url } => {
|
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)?;
|
let _ = admin.set_url(&url)?;
|
||||||
}
|
}
|
||||||
|
@ -96,7 +100,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
auth_fp,
|
auth_fp,
|
||||||
} => {
|
} => {
|
||||||
let key = Cert::from_file(keyfile)?;
|
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 (&sig_fp, &dec_fp, &auth_fp) == (&None, &None, &None) {
|
||||||
// If no fingerprint has been provided, we check if
|
// If no fingerprint has been provided, we check if
|
||||||
|
@ -114,10 +118,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
no_auth,
|
no_auth,
|
||||||
algo,
|
algo,
|
||||||
} => {
|
} => {
|
||||||
|
let user_pin = util::get_pin(&mut open, user_pin, ENTER_USER_PIN);
|
||||||
|
|
||||||
generate_keys(
|
generate_keys(
|
||||||
open,
|
open,
|
||||||
admin_pin,
|
admin_pin.as_deref(),
|
||||||
user_pin,
|
user_pin.as_deref(),
|
||||||
output,
|
output,
|
||||||
!no_decrypt,
|
!no_decrypt,
|
||||||
!no_auth,
|
!no_auth,
|
||||||
|
@ -379,7 +385,9 @@ fn decrypt(
|
||||||
|
|
||||||
let mut open = Open::new(pgp.transaction()?)?;
|
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 d = user.decryptor(&cert)?;
|
||||||
|
|
||||||
let db = DecryptorBuilder::from_reader(input)?;
|
let db = DecryptorBuilder::from_reader(input)?;
|
||||||
|
@ -405,7 +413,9 @@ fn sign_detached(
|
||||||
|
|
||||||
let mut open = Open::new(pgp.transaction()?)?;
|
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 s = sign.signer(&cert)?;
|
||||||
|
|
||||||
let message = Armorer::new(Message::new(std::io::stdout())).build()?;
|
let message = Armorer::new(Message::new(std::io::stdout())).build()?;
|
||||||
|
@ -492,8 +502,8 @@ fn key_import_explicit(
|
||||||
|
|
||||||
fn generate_keys(
|
fn generate_keys(
|
||||||
mut open: Open,
|
mut open: Open,
|
||||||
pw3_path: Option<PathBuf>,
|
admin_pin: Option<&[u8]>,
|
||||||
pw1_path: Option<PathBuf>,
|
user_pin: Option<&[u8]>,
|
||||||
output: Option<PathBuf>,
|
output: Option<PathBuf>,
|
||||||
decrypt: bool,
|
decrypt: bool,
|
||||||
auth: bool,
|
auth: bool,
|
||||||
|
@ -529,7 +539,7 @@ fn generate_keys(
|
||||||
// 2) Then, generate keys on the card.
|
// 2) Then, generate keys on the card.
|
||||||
// We need "admin" access to the card for this).
|
// We need "admin" access to the card for this).
|
||||||
let (key_sig, key_dec, key_aut) = {
|
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)?
|
gen_subkeys(&mut admin, decrypt, auth, a)?
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow!("Failed to open card in admin mode."));
|
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
|
// 3) Generate a Cert from the generated keys. For this, we
|
||||||
// need "signing" access to the card (to make binding signatures within
|
// need "signing" access to the card (to make binding signatures within
|
||||||
// the Cert).
|
// the Cert).
|
||||||
let pin = if let Some(pw1) = pw1_path {
|
if user_pin.is_none() && open.feature_pinpad_verify() {
|
||||||
Some(util::load_pin(&pw1)?)
|
println!(
|
||||||
} else {
|
"The public cert will now be generated.\n\n\
|
||||||
if open.feature_pinpad_verify() {
|
You will need to enter your user PIN multiple times during this process.\n\n"
|
||||||
println!();
|
);
|
||||||
println!(
|
}
|
||||||
"Next: generating your public cert. You will need to enter \
|
|
||||||
your user PIN multiple times to make binding signatures."
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Err(anyhow!("No user PIN file provided, and no pinpad found"));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let cert = make_cert(
|
let cert = make_cert(&mut open, key_sig, key_dec, key_aut, user_pin, &|| {
|
||||||
&mut open,
|
println!("Enter user PIN on card reader pinpad.")
|
||||||
key_sig,
|
})?;
|
||||||
key_dec,
|
|
||||||
key_aut,
|
|
||||||
pin.as_deref(),
|
|
||||||
&|| println!("Enter user PIN on card reader pinpad."),
|
|
||||||
)?;
|
|
||||||
let armored = String::from_utf8(cert.armored().to_vec()?)?;
|
let armored = String::from_utf8(cert.armored().to_vec()?)?;
|
||||||
|
|
||||||
// Write armored certificate to the output file (or stdout)
|
// 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
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
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)
|
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>(
|
pub(crate) fn verify_to_user<'app, 'open>(
|
||||||
open: &'open mut Open<'app>,
|
open: &'open mut Open<'app>,
|
||||||
pin_file: Option<PathBuf>,
|
pin: Option<&[u8]>,
|
||||||
) -> Result<User<'app, 'open>, Box<dyn std::error::Error>> {
|
) -> Result<User<'app, 'open>, Box<dyn std::error::Error>> {
|
||||||
if let Some(path) = pin_file {
|
if let Some(pin) = pin {
|
||||||
open.verify_user(&load_pin(&path)?)?;
|
open.verify_user(pin)?;
|
||||||
} else {
|
} else {
|
||||||
if !open.feature_pinpad_verify() {
|
if !open.feature_pinpad_verify() {
|
||||||
return Err(anyhow!("No user PIN file provided, and no pinpad found").into());
|
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>(
|
pub(crate) fn verify_to_sign<'app, 'open>(
|
||||||
open: &'open mut Open<'app>,
|
open: &'open mut Open<'app>,
|
||||||
pin_file: Option<PathBuf>,
|
pin: Option<&[u8]>,
|
||||||
) -> Result<Sign<'app, 'open>, Box<dyn std::error::Error>> {
|
) -> Result<Sign<'app, 'open>, Box<dyn std::error::Error>> {
|
||||||
if let Some(path) = pin_file {
|
if let Some(pin) = pin {
|
||||||
open.verify_user_for_signing(&load_pin(&path)?)?;
|
open.verify_user_for_signing(pin)?;
|
||||||
} else {
|
} else {
|
||||||
if !open.feature_pinpad_verify() {
|
if !open.feature_pinpad_verify() {
|
||||||
return Err(anyhow!("No user PIN file provided, and no pinpad found").into());
|
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())
|
.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>(
|
pub(crate) fn verify_to_admin<'app, 'open>(
|
||||||
open: &'open mut Open<'app>,
|
open: &'open mut Open<'app>,
|
||||||
pin_file: Option<PathBuf>,
|
pin: Option<&[u8]>,
|
||||||
) -> Result<Admin<'app, 'open>, Box<dyn std::error::Error>> {
|
) -> Result<Admin<'app, 'open>, Box<dyn std::error::Error>> {
|
||||||
if let Some(path) = pin_file {
|
if let Some(pin) = pin {
|
||||||
open.verify_admin(&load_pin(&path)?)?;
|
open.verify_admin(pin)?;
|
||||||
} else {
|
} else {
|
||||||
if !open.feature_pinpad_verify() {
|
if !open.feature_pinpad_verify() {
|
||||||
return Err(anyhow!("No admin PIN file provided, and no pinpad found").into());
|
return Err(anyhow!("No admin PIN file provided, and no pinpad found").into());
|
||||||
|
|
Loading…
Reference in a new issue