Implement, document PIN management in opgpcard
This commit is contained in:
parent
fbdb9e87b2
commit
99e0c6caaf
4 changed files with 417 additions and 71 deletions
190
tools/README.md
190
tools/README.md
|
@ -5,8 +5,8 @@ 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`.
|
||||
This crate contains the `opgpcard` tool for inspecting, configuring and using OpenPGP
|
||||
cards.
|
||||
|
||||
# Install
|
||||
|
||||
|
@ -36,7 +36,7 @@ binaries.
|
|||
## opgpcard
|
||||
|
||||
A tool to inspect, configure and use OpenPGP cards. All calls of this tool are
|
||||
usable in a non-interactive way (this tool is designed to be easily usable from
|
||||
usable in a non-interactive way (this tool is designed both for interactive use, and 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,
|
||||
|
@ -413,6 +413,120 @@ or interactively
|
|||
$ opgpcard decrypt -c ABCD:01234567 -r <cert-file> <input-file>
|
||||
```
|
||||
|
||||
### PIN management
|
||||
|
||||
OpenPGP cards use PINs (numerical passwords) to verify that a user is allowed to perform an operation.
|
||||
|
||||
To use the 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 unconfigured (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 -c ABCD:01234567 set-user
|
||||
```
|
||||
|
||||
For non-interactive PIN change:
|
||||
|
||||
```
|
||||
$ opgpcard pin -c ABCD:01234567 set-user -p <old-user-pin-file> -q <new-user-pin-file>
|
||||
```
|
||||
|
||||
#### Set new admin PIN
|
||||
|
||||
This requires the (previous) admin PIN.
|
||||
|
||||
```
|
||||
$ opgpcard pin -c ABCD:01234567 set-admin
|
||||
```
|
||||
|
||||
For non-interactive PIN change:
|
||||
|
||||
```
|
||||
$ opgpcard pin -c ABCD:01234567 set-admin -p <old-admin-pin-file> -q <new-admin-pin-file>
|
||||
```
|
||||
|
||||
#### 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 any more.
|
||||
|
||||
```
|
||||
$ opgpcard pin -c ABCD:01234567 reset-user
|
||||
```
|
||||
|
||||
For non-interactive PIN change:
|
||||
|
||||
```
|
||||
$ opgpcard pin -c ABCD:01234567 reset-user -P <admin-pin-file> -p <new-user-pin-file>
|
||||
```
|
||||
|
||||
#### 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 -c 0006:16019180 set-reset
|
||||
```
|
||||
|
||||
To non-interactively set the resetting code:
|
||||
|
||||
```
|
||||
$ opgpcard pin -c 0006:16019180 set-reset -P <admin-pin-file> -r <resetting-code-file>
|
||||
```
|
||||
|
||||
#### 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 -c 0006:16019180 reset-user-rc
|
||||
Enter resetting code:
|
||||
Enter new user PIN:
|
||||
Repeat the new user PIN:
|
||||
|
||||
User PIN has been set.
|
||||
```
|
||||
|
||||
To non-interactively use the resetting code:
|
||||
|
||||
```
|
||||
$ opgpcard pin -c 0006:16019180 reset-user-rc -r <resetting-code-file> -p <new-user-pin-file>
|
||||
```
|
||||
|
||||
### Factory reset
|
||||
|
||||
Factory reset:
|
||||
|
@ -427,7 +541,7 @@ NOTE: you do not need a PIN to reset a card!
|
|||
|
||||
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:
|
||||
, you can pass user and/or admin PINs via file-descriptors (instead of from a file on disk):
|
||||
|
||||
```
|
||||
$ opgpcard sign --detached -c ABCD:01234567 -p /dev/fd/3 -s <cert-file> 3<<<123456
|
||||
|
@ -440,70 +554,6 @@ $ opgpcard admin -c ABCD:01234567 -P /dev/fd/3 generate -p /dev/fd/4 -o <output-
|
|||
### Directly entering PINs on card readers with pinpad
|
||||
|
||||
If your OpenPGP card is inserted in a card reader with a pinpad, this tool
|
||||
offers you the option to use the pinpad to enter the user- or admin-PINs.
|
||||
To do this, you can omit the `-p` and/or '`-P`' parameters - then you will
|
||||
offers you the option to use the pinpad 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.
|
||||
|
||||
## opgpcard-pin
|
||||
|
||||
An interactive tool to set the admin and user PINs, and to reset the user PIN
|
||||
on OpenPGP cards.
|
||||
|
||||
### Set a new user PIN
|
||||
|
||||
Setting a new user PIN requires the admin PIN:
|
||||
|
||||
```
|
||||
opgpcard-pin -c ABCD:01234567 set-user-pin
|
||||
```
|
||||
(The default admin PIN on unconfigured cards is typically `12345678`)
|
||||
|
||||
### Set new admin PIN
|
||||
|
||||
This requires the (previous) admin PIN.
|
||||
|
||||
```
|
||||
opgpcard-pin -c ABCD:01234567 set-admin-pin
|
||||
```
|
||||
|
||||
(The default admin PIN on unconfigured cards is typically `12345678`)
|
||||
|
||||
### Recover from blocked user PIN (using the admin PIN)
|
||||
|
||||
When a user has entered a wrong user PIN too often, the card goes into a blocked state, in which presenting the
|
||||
user PIN 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".
|
||||
|
||||
Reset user PIN after it has been blocked (requires admin PIN):
|
||||
|
||||
```
|
||||
opgpcard-pin -c ABCD:01234567 reset-user-pin -a
|
||||
```
|
||||
|
||||
### Recover from blocked user PIN (using the resetting code)
|
||||
|
||||
The resetting code is an optional/alternative method to recover from a blocked user PIN.
|
||||
|
||||
Context: in some (e.g. 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.
|
||||
|
||||
Set resetting code (requires admin PIN):
|
||||
|
||||
```
|
||||
opgpcard-pin -c ABCD:01234567 set-reset-code
|
||||
```
|
||||
|
||||
Once a reset code has been defined, the user can
|
||||
reset the blocked user PIN, using the resetting code:
|
||||
|
||||
```
|
||||
opgpcard-pin -c ABCD:01234567 reset-user-pin
|
||||
```
|
||||
|
||||
### Directly entering PINs on card readers with pinpad
|
||||
|
||||
If your OpenPGP card is inserted in a card reader with a pinpad, this tool
|
||||
assumes you will want to enter all PINs via that pinpad. It will prompt
|
||||
you to enter PINs accordingly.
|
|
@ -63,6 +63,13 @@ pub enum Command {
|
|||
#[clap(subcommand)]
|
||||
cmd: AdminCommand,
|
||||
},
|
||||
Pin {
|
||||
#[clap(name = "card ident", short = 'c', long = "card")]
|
||||
ident: String,
|
||||
|
||||
#[clap(subcommand)]
|
||||
cmd: PinCommand,
|
||||
},
|
||||
Decrypt {
|
||||
#[clap(name = "card ident", short = 'c', long = "card")]
|
||||
ident: String,
|
||||
|
@ -144,3 +151,51 @@ pub enum AdminCommand {
|
|||
algo: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub enum PinCommand {
|
||||
/// Set User PIN
|
||||
SetUser {
|
||||
#[clap(name = "User PIN file old", short = 'p', long = "user-pin-old")]
|
||||
user_pin_old: Option<PathBuf>,
|
||||
|
||||
#[clap(name = "User PIN file new", short = 'q', long = "user-pin-new")]
|
||||
user_pin_new: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Set Admin PIN
|
||||
SetAdmin {
|
||||
#[clap(name = "Admin PIN file old", short = 'P', long = "admin-pin-old")]
|
||||
admin_pin_old: Option<PathBuf>,
|
||||
|
||||
#[clap(name = "Admin PIN file new", short = 'Q', long = "admin-pin-new")]
|
||||
admin_pin_new: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Reset User PIN with admin PIN
|
||||
ResetUser {
|
||||
#[clap(name = "Admin PIN file", short = 'P', long = "admin-pin")]
|
||||
admin_pin: Option<PathBuf>,
|
||||
|
||||
#[clap(name = "User PIN file new", short = 'p', long = "user-pin-new")]
|
||||
user_pin_new: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Set Resetting Code
|
||||
SetReset {
|
||||
#[clap(name = "Admin PIN file", short = 'P', long = "admin-pin")]
|
||||
admin_pin: Option<PathBuf>,
|
||||
|
||||
#[clap(name = "Resetting code file", short = 'r', long = "reset-code")]
|
||||
reset_code: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Reset User PIN with 'resetting code'
|
||||
ResetUserRc {
|
||||
#[clap(name = "Resetting code file", short = 'r', long = "reset-code")]
|
||||
reset_code: Option<PathBuf>,
|
||||
|
||||
#[clap(name = "User PIN file new", short = 'p', long = "user-pin-new")]
|
||||
user_pin_new: Option<PathBuf>,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ use openpgp_card_sequoia::util::{
|
|||
};
|
||||
use openpgp_card_sequoia::{sq_util, PublicKey};
|
||||
|
||||
use crate::util::{load_pin, print_gnuk_note};
|
||||
use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm};
|
||||
use std::io::Write;
|
||||
|
||||
|
@ -141,6 +142,210 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
}
|
||||
}
|
||||
cli::Command::Pin { ident, cmd } => {
|
||||
let mut card = util::open_card(&ident)?;
|
||||
let mut pgp = OpenPgp::new(&mut card);
|
||||
let pgpt = pgp.transaction()?;
|
||||
|
||||
let pinpad_modify = pgpt.feature_pinpad_modify();
|
||||
|
||||
let mut open = Open::new(pgpt)?;
|
||||
|
||||
match cmd {
|
||||
cli::PinCommand::SetUser {
|
||||
user_pin_old,
|
||||
user_pin_new,
|
||||
} => {
|
||||
let res = if !pinpad_modify {
|
||||
// get current user pin
|
||||
let user_pin1 = util::get_pin(&mut open, user_pin_old, ENTER_USER_PIN)
|
||||
.expect("this should never be None");
|
||||
|
||||
// verify pin
|
||||
open.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
|
||||
open.change_user_pin(&user_pin1, &pin_new)
|
||||
} else {
|
||||
// set new user pin via pinpad
|
||||
open.change_user_pin_pinpad(&|| {
|
||||
println!(
|
||||
"Enter old user PIN on card reader pinpad, then new user PIN (twice)."
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
if res.is_err() {
|
||||
println!("\nFailed to change the user PIN!");
|
||||
println!("{:?}", res);
|
||||
|
||||
if let Err(err) = res {
|
||||
print_gnuk_note(err, &open)?;
|
||||
}
|
||||
} else {
|
||||
println!("\nUser PIN has been set.");
|
||||
}
|
||||
}
|
||||
cli::PinCommand::SetAdmin {
|
||||
admin_pin_old,
|
||||
admin_pin_new,
|
||||
} => {
|
||||
if !pinpad_modify {
|
||||
// get current admin pin
|
||||
let admin_pin1 = util::get_pin(&mut open, admin_pin_old, ENTER_ADMIN_PIN)
|
||||
.expect("this should never be None");
|
||||
|
||||
// verify pin
|
||||
open.verify_admin(&admin_pin1)?;
|
||||
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
|
||||
open.change_admin_pin(&admin_pin1, &pin_new)?;
|
||||
} else {
|
||||
// set new admin pin via pinpad
|
||||
open.change_admin_pin_pinpad(&|| {
|
||||
println!(
|
||||
"Enter old admin PIN on card reader pinpad, then new admin PIN (twice)."
|
||||
)
|
||||
})?;
|
||||
};
|
||||
|
||||
println!("\nAdmin PIN has been set.");
|
||||
}
|
||||
|
||||
cli::PinCommand::ResetUser {
|
||||
admin_pin,
|
||||
user_pin_new,
|
||||
} => {
|
||||
// verify admin pin
|
||||
match util::get_pin(&mut open, admin_pin, ENTER_ADMIN_PIN) {
|
||||
Some(admin_pin) => {
|
||||
// verify pin
|
||||
open.verify_admin(&admin_pin)?;
|
||||
}
|
||||
None => {
|
||||
open.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) = open.admin_card() {
|
||||
admin.reset_user_pin(&pin)
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Failed to use card in admin-mode.").into());
|
||||
};
|
||||
|
||||
if res.is_err() {
|
||||
println!("\nFailed to change the user PIN!");
|
||||
if let Err(err) = res {
|
||||
print_gnuk_note(err, &open)?;
|
||||
}
|
||||
} else {
|
||||
println!("\nUser PIN has been set.");
|
||||
}
|
||||
}
|
||||
|
||||
cli::PinCommand::SetReset {
|
||||
admin_pin,
|
||||
reset_code,
|
||||
} => {
|
||||
// verify admin pin
|
||||
match util::get_pin(&mut open, admin_pin, ENTER_ADMIN_PIN) {
|
||||
Some(admin_pin) => {
|
||||
// verify pin
|
||||
open.verify_admin(&admin_pin)?;
|
||||
}
|
||||
None => {
|
||||
open.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) = open.admin_card() {
|
||||
admin.set_resetting_code(&code)?;
|
||||
println!("\nResetting code has been set.");
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Failed to use card in admin-mode.").into());
|
||||
};
|
||||
}
|
||||
|
||||
cli::PinCommand::ResetUserRc {
|
||||
reset_code,
|
||||
user_pin_new,
|
||||
} => {
|
||||
// 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::read_password_from_tty(Some("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 open.reset_user_pin(&rst, &pin) {
|
||||
Err(err) => {
|
||||
println!("\nFailed to change the user PIN!");
|
||||
print_gnuk_note(err, &open)?;
|
||||
}
|
||||
Ok(_) => println!("\nUser PIN has been set."),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
use openpgp_card::algorithm::{Algo, Curve};
|
||||
use openpgp_card::crypto_data::{EccType, PublicKeyMaterial};
|
||||
use openpgp_card::{CardBackend, Error};
|
||||
use openpgp_card::{CardBackend, Error, StatusBytes};
|
||||
use openpgp_card_pcsc::PcscBackend;
|
||||
use openpgp_card_sequoia::card::{Admin, Open, Sign, User};
|
||||
|
||||
|
@ -37,6 +37,19 @@ pub(crate) fn get_pin(open: &mut Open, pin_file: Option<PathBuf>, msg: &str) ->
|
|||
}
|
||||
}
|
||||
|
||||
/// 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<Vec<u8>> {
|
||||
// get new user pin
|
||||
let newpin1 = rpassword::read_password_from_tty(Some(msg1))?;
|
||||
let newpin2 = rpassword::read_password_from_tty(Some(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>(
|
||||
open: &'open mut Open<'app>,
|
||||
pin: Option<&[u8]>,
|
||||
|
@ -195,3 +208,26 @@ pub(crate) fn get_ssh_pubkey_string(pkm: &PublicKeyMaterial, ident: String) -> R
|
|||
|
||||
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: &Open) -> Result<()> {
|
||||
if matches!(
|
||||
err,
|
||||
Error::CardStatus(StatusBytes::ConditionOfUseNotSatisfied)
|
||||
) {
|
||||
// check if no keys exist on the card
|
||||
let fps = card.fingerprints()?;
|
||||
if fps.signature() == None && fps.decryption() == None && fps.authentication() == None {
|
||||
println!(
|
||||
"\nNOTE: Some cards (e.g. Gnuk) don't allow \
|
||||
User PIN change while no keys exist on the card."
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue