Add more logic to key generation

This commit is contained in:
Heiko Schaefer 2021-11-03 02:24:21 +01:00
parent 79cfcb09c2
commit cda224d738
No known key found for this signature in database
GPG key ID: 4A849A1904CCBD7D
4 changed files with 189 additions and 62 deletions

View file

@ -62,6 +62,12 @@ $ opgpcard admin -c ABCD:12345678 -p <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
```
$ opgpcard admin -c ABCD:12345678 -p <admin-pin-file> generate --user-pin-file <user-pin-file> -o <output-file> 25519
```
### Set card metadata
Set cardholder name:

View file

@ -35,7 +35,7 @@ pub enum Command {
#[structopt(name = "card ident", short = "c", long = "card")]
ident: String,
#[structopt(name = "admin pin file", short = "p", long = "pin-file")]
#[structopt(name = "Admin PIN file", short = "p", long = "pin-file")]
pin_file: PathBuf,
#[structopt(subcommand)]
@ -45,7 +45,7 @@ pub enum Command {
#[structopt(name = "card ident", short = "c", long = "card")]
ident: String,
#[structopt(name = "user pin file", short = "p", long = "pin-file")]
#[structopt(name = "User PIN file", short = "p", long = "pin-file")]
pin_file: PathBuf,
#[structopt(
@ -121,6 +121,17 @@ pub enum AdminCommand {
/// A signing key is always created, decryption and authentication keys
/// are optional.
Generate {
#[structopt(name = "User PIN file", long = "user-pin-file")]
user_pin_file: PathBuf,
#[structopt(
about = "Output file (stdout if unset)",
name = "output",
long = "output",
short = "o"
)]
output: Option<PathBuf>,
#[structopt(long = "no-decrypt")]
no_decrypt: bool,

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use anyhow::Result;
use std::path::Path;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
use sequoia_openpgp::parse::{stream::DecryptorBuilder, Parse};
@ -12,11 +12,12 @@ use sequoia_openpgp::serialize::SerializeInto;
use sequoia_openpgp::Cert;
use openpgp_card_sequoia::card::{Admin, Open};
use openpgp_card_sequoia::sq_util;
use openpgp_card_sequoia::util::{make_cert, public_key_material_to_key};
use openpgp_card_sequoia::{sq_util, PublicKey};
use openpgp_card::algorithm::AlgoSimple;
use openpgp_card::{card_do::Sex, KeyType};
use std::io::Write;
mod cli;
mod util;
@ -105,17 +106,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
cli::AdminCommand::Generate {
user_pin_file,
output,
no_decrypt,
no_auth,
algo,
} => {
let pw3 = util::get_pin(&pin_file)?;
// FIXME: get PW1 from user
let pw1 = util::get_pin(&user_pin_file)?;
generate_keys(
open,
&pw3,
"123456",
&pw1,
output,
!no_decrypt,
!no_auth,
algo,
@ -416,54 +420,166 @@ fn generate_keys(
mut open: Open,
pw3: &str,
pw1: &str,
output: Option<PathBuf>,
decrypt: bool,
auth: bool,
algo: Option<String>,
) -> Result<()> {
// Figure out which algorithm the user wants
// 1) Interpret the user's choice of algorithm.
//
// Unset (None) means that the algorithm that is specified on the card
// should remain unchanged.
//
// For RSA, different cards use different exact algorithm
// specifications. In particular, the length of the value `e` differs
// between cards. Some devices use 32 bit length for e, others use 17 bit.
// In some cases, it's possible to get this information from the card,
// but I believe this information is not obtainable in all cases.
// Because of this, for generation of RSA keys, here we take the approach
// of first trying one variant, and then if that fails, try the other.
// FIXME:
// (rsa2048|rsa3072|rsa4096|nistp256|nistp384|nistp521|25519) or None
// let alg = AlgoSimple::from(algo);
let algos = match algo.as_deref() {
None => vec![],
Some("rsa2048") => vec![AlgoSimple::RSA2k(32), AlgoSimple::RSA2k(17)],
Some("rsa3072") => vec![AlgoSimple::RSA3k(32), AlgoSimple::RSA3k(17)],
Some("rsa4096") => vec![AlgoSimple::RSA4k(32), AlgoSimple::RSA4k(17)],
Some("nistp256") => vec![AlgoSimple::NIST256],
Some("nistp384") => vec![AlgoSimple::NIST384],
Some("nistp521") => vec![AlgoSimple::NIST521],
Some("25519") => vec![AlgoSimple::Curve25519],
_ => unimplemented!("unexpected algorithm"),
};
let a = algo.unwrap();
let a: &str = &a;
log::info!(
" Key generation will be attempted with these algos: {:?}",
algos
);
// temporary approach:
let alg = AlgoSimple::from(a);
// FIXME: if rsa, try 32 and 17 bit e
// FIXME: handle None (leave algo as is)
// ---
// Then, we make the card generate keys (we need "admin" access to
// the card for that).
// 2) Then, generate keys on the card.
// We need "admin" access to the card for this).
open.verify_admin(pw3)?;
let (key_sig, key_dec, key_aut) = {
if let Some(mut admin) = open.admin_card() {
gen_subkeys(&mut admin, decrypt, auth, algos)?
} else {
// FIXME: couldn't get admin mode
unimplemented!()
}
};
// 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 cert = make_cert(&mut open, key_sig, key_dec, key_aut, pw1)?;
let armored = String::from_utf8(cert.armored().to_vec()?)?;
// Write armored certificate to the output file (or stdout)
let mut output = util::open_or_stdout(output.as_deref())?;
output.write(armored.as_bytes())?;
Ok(())
}
fn gen_subkeys(
admin: &mut Admin,
decrypt: bool,
auth: bool,
algos: Vec<AlgoSimple>,
) -> Result<(PublicKey, Option<PublicKey>, Option<PublicKey>)> {
// We begin with the signing subkey, which is mandatory.
// If there are multiple algorithms we should try (i.e. two rsa
// variants), we'll attempt to make the signing subkey with each of
// them, and continue with the first one that works, if any.
println!(" Generate subkey for Signing");
let (pkm, ts) =
admin.generate_key_simple(KeyType::Signing, alg)?;
let key_sig =
public_key_material_to_key(&pkm, KeyType::Signing, ts)?;
let (alg, key_sig) = if algos.is_empty() {
// Handle unset algorithm from user (-> leave algo as is on the card)
log::info!(" Running key generation with implicit algo");
let (pkm, ts) = admin.generate_key_simple(KeyType::Signing, None)?;
let key = public_key_material_to_key(&pkm, KeyType::Signing, ts)?;
(None, key)
} else {
// Try list of algos until one works - or return list of all errors.
let mut errors = vec![];
let mut algo: Option<AlgoSimple> = None;
let mut key_sig: Option<PublicKey> = None;
// Check each algo while making key_sig.
//
// Return the result for the first one that works, and keep using
// that algo.
//
// If none of them work, fail and return all errors
for (n, alg) in algos.iter().enumerate() {
let a = Some(alg.clone());
log::info!(" Trying key generation with algo {:?}", alg);
match admin.generate_key_simple(KeyType::Signing, a) {
Ok((pkm, ts)) => {
// generated a valid key -> return it
let key = public_key_material_to_key(
&pkm,
KeyType::Signing,
ts,
)?;
algo = a;
key_sig = Some(key);
break;
}
Err(e) => match (n, n == algos.len() - 1) {
// there was only one algo, and it failed
(0, true) => return Err(e.into()),
// there was an error, but there are more algo to try
(_, false) => errors.push(e),
// there were multiple algo, there was an error, and
// this is the last algo
(_, true) => {
errors.push(e);
let err = anyhow::anyhow!(
"Key generation failed for all algorithm \
variants {:x?} ({:?})",
errors,
algos
);
return Err(err);
}
},
};
}
// Neither of these should be possible, but the compiler can't tell.
assert!(algo.is_some());
assert!(key_sig.is_some());
(algo, key_sig.unwrap())
};
// make decryption subkey (unless disabled), with the same algorithm as
// the sig key
let key_dec = if decrypt {
println!(" Generate subkey for Decryption");
let (pkm, ts) =
admin.generate_key_simple(KeyType::Decryption, alg)?;
Some(public_key_material_to_key(
&pkm,
KeyType::Decryption,
ts,
)?)
let (pkm, ts) = admin.generate_key_simple(KeyType::Decryption, alg)?;
Some(public_key_material_to_key(&pkm, KeyType::Decryption, ts)?)
} else {
None
};
// make authentication subkey (unless disabled), with the same
// algorithm as the sig key
let key_aut = if auth {
println!(" Generate subkey for Authentication");
let (pkm, ts) =
@ -478,22 +594,5 @@ fn generate_keys(
None
};
(key_sig, key_dec, key_aut)
} else {
// FIXME: couldn't get admin mode
unimplemented!()
}
};
// Then we generate a Cert for this set of generated keys. For this, we
// need "signing" access to the card, to make a number of binding
// signatures.
// FIXME: get pw1 from user
let cert = make_cert(&mut open, key_sig, key_dec, key_aut, pw1)?;
let armored = String::from_utf8(cert.armored().to_vec()?)?;
println!("{}", armored);
Ok(())
Ok((key_sig, key_dec, key_aut))
}

View file

@ -58,3 +58,14 @@ pub(crate) fn open_or_stdin(
None => Ok(Box::new(std::io::stdin())),
}
}
pub(crate) fn open_or_stdout(
f: Option<&Path>,
) -> Result<Box<dyn std::io::Write + Send + Sync>> {
match f {
Some(f) => Ok(Box::new(
std::fs::File::create(f).context("Failed to open input file")?,
)),
None => Ok(Box::new(std::io::stdout())),
}
}