Add more logic to key generation
This commit is contained in:
parent
79cfcb09c2
commit
cda224d738
4 changed files with 189 additions and 62 deletions
|
@ -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
|
When fingerprints are only specified for a subset of the roles, no
|
||||||
keys will be imported for the other roles.
|
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 card metadata
|
||||||
|
|
||||||
Set cardholder name:
|
Set cardholder name:
|
||||||
|
|
|
@ -35,7 +35,7 @@ pub enum Command {
|
||||||
#[structopt(name = "card ident", short = "c", long = "card")]
|
#[structopt(name = "card ident", short = "c", long = "card")]
|
||||||
ident: String,
|
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,
|
pin_file: PathBuf,
|
||||||
|
|
||||||
#[structopt(subcommand)]
|
#[structopt(subcommand)]
|
||||||
|
@ -45,7 +45,7 @@ pub enum Command {
|
||||||
#[structopt(name = "card ident", short = "c", long = "card")]
|
#[structopt(name = "card ident", short = "c", long = "card")]
|
||||||
ident: String,
|
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,
|
pin_file: PathBuf,
|
||||||
|
|
||||||
#[structopt(
|
#[structopt(
|
||||||
|
@ -121,6 +121,17 @@ pub enum AdminCommand {
|
||||||
/// A signing key is always created, decryption and authentication keys
|
/// A signing key is always created, decryption and authentication keys
|
||||||
/// are optional.
|
/// are optional.
|
||||||
Generate {
|
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")]
|
#[structopt(long = "no-decrypt")]
|
||||||
no_decrypt: bool,
|
no_decrypt: bool,
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use sequoia_openpgp::parse::{stream::DecryptorBuilder, Parse};
|
use sequoia_openpgp::parse::{stream::DecryptorBuilder, Parse};
|
||||||
|
@ -12,11 +12,12 @@ use sequoia_openpgp::serialize::SerializeInto;
|
||||||
use sequoia_openpgp::Cert;
|
use sequoia_openpgp::Cert;
|
||||||
|
|
||||||
use openpgp_card_sequoia::card::{Admin, Open};
|
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::util::{make_cert, public_key_material_to_key};
|
||||||
|
use openpgp_card_sequoia::{sq_util, PublicKey};
|
||||||
|
|
||||||
use openpgp_card::algorithm::AlgoSimple;
|
use openpgp_card::algorithm::AlgoSimple;
|
||||||
use openpgp_card::{card_do::Sex, KeyType};
|
use openpgp_card::{card_do::Sex, KeyType};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod util;
|
mod util;
|
||||||
|
@ -105,17 +106,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cli::AdminCommand::Generate {
|
cli::AdminCommand::Generate {
|
||||||
|
user_pin_file,
|
||||||
|
output,
|
||||||
no_decrypt,
|
no_decrypt,
|
||||||
no_auth,
|
no_auth,
|
||||||
algo,
|
algo,
|
||||||
} => {
|
} => {
|
||||||
let pw3 = util::get_pin(&pin_file)?;
|
let pw3 = util::get_pin(&pin_file)?;
|
||||||
// FIXME: get PW1 from user
|
let pw1 = util::get_pin(&user_pin_file)?;
|
||||||
|
|
||||||
generate_keys(
|
generate_keys(
|
||||||
open,
|
open,
|
||||||
&pw3,
|
&pw3,
|
||||||
"123456",
|
&pw1,
|
||||||
|
output,
|
||||||
!no_decrypt,
|
!no_decrypt,
|
||||||
!no_auth,
|
!no_auth,
|
||||||
algo,
|
algo,
|
||||||
|
@ -416,54 +420,166 @@ fn generate_keys(
|
||||||
mut open: Open,
|
mut open: Open,
|
||||||
pw3: &str,
|
pw3: &str,
|
||||||
pw1: &str,
|
pw1: &str,
|
||||||
|
output: Option<PathBuf>,
|
||||||
decrypt: bool,
|
decrypt: bool,
|
||||||
auth: bool,
|
auth: bool,
|
||||||
algo: Option<String>,
|
algo: Option<String>,
|
||||||
) -> Result<()> {
|
) -> 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:
|
let algos = match algo.as_deref() {
|
||||||
// (rsa2048|rsa3072|rsa4096|nistp256|nistp384|nistp521|25519) or None
|
None => vec![],
|
||||||
// let alg = AlgoSimple::from(algo);
|
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();
|
log::info!(
|
||||||
let a: &str = &a;
|
" Key generation will be attempted with these algos: {:?}",
|
||||||
|
algos
|
||||||
|
);
|
||||||
|
|
||||||
// temporary approach:
|
// 2) Then, generate keys on the card.
|
||||||
let alg = AlgoSimple::from(a);
|
// We need "admin" access to the card for this).
|
||||||
|
|
||||||
// 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).
|
|
||||||
|
|
||||||
open.verify_admin(pw3)?;
|
open.verify_admin(pw3)?;
|
||||||
|
|
||||||
let (key_sig, key_dec, key_aut) = {
|
let (key_sig, key_dec, key_aut) = {
|
||||||
if let Some(mut admin) = open.admin_card() {
|
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");
|
println!(" Generate subkey for Signing");
|
||||||
let (pkm, ts) =
|
let (alg, key_sig) = if algos.is_empty() {
|
||||||
admin.generate_key_simple(KeyType::Signing, alg)?;
|
// Handle unset algorithm from user (-> leave algo as is on the card)
|
||||||
let key_sig =
|
log::info!(" Running key generation with implicit algo");
|
||||||
public_key_material_to_key(&pkm, KeyType::Signing, ts)?;
|
|
||||||
|
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 {
|
let key_dec = if decrypt {
|
||||||
println!(" Generate subkey for Decryption");
|
println!(" Generate subkey for Decryption");
|
||||||
let (pkm, ts) =
|
let (pkm, ts) = admin.generate_key_simple(KeyType::Decryption, alg)?;
|
||||||
admin.generate_key_simple(KeyType::Decryption, alg)?;
|
Some(public_key_material_to_key(&pkm, KeyType::Decryption, ts)?)
|
||||||
Some(public_key_material_to_key(
|
|
||||||
&pkm,
|
|
||||||
KeyType::Decryption,
|
|
||||||
ts,
|
|
||||||
)?)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// make authentication subkey (unless disabled), with the same
|
||||||
|
// algorithm as the sig key
|
||||||
|
|
||||||
let key_aut = if auth {
|
let key_aut = if auth {
|
||||||
println!(" Generate subkey for Authentication");
|
println!(" Generate subkey for Authentication");
|
||||||
let (pkm, ts) =
|
let (pkm, ts) =
|
||||||
|
@ -478,22 +594,5 @@ fn generate_keys(
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
(key_sig, key_dec, key_aut)
|
Ok((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(())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,3 +58,14 @@ pub(crate) fn open_or_stdin(
|
||||||
None => Ok(Box::new(std::io::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())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue