Implement attestation-related functionality in opgpcard

This commit is contained in:
Heiko Schaefer 2022-05-23 17:52:33 +02:00
parent 7b3152a88e
commit 22c29262d3
No known key found for this signature in database
GPG key ID: 4A849A1904CCBD7D
4 changed files with 120 additions and 0 deletions

View file

@ -17,6 +17,7 @@ openpgp-card = { path = "../openpgp-card", version = "0.2.4" }
openpgp-card-pcsc = { path = "../pcsc", version = "0.2" }
openpgp-card-sequoia = { path = "../openpgp-card-sequoia", version = "0.0.11" }
sshkeys = "0.3.2"
pem = "1"
rpassword = "6"
anyhow = "1"
clap = { version = "3.1", features = ["derive"] }

View file

@ -109,6 +109,12 @@ pub enum Command {
input: Option<PathBuf>,
},
/// Attestation management (Yubico)
Attestation {
#[clap(subcommand)]
cmd: AttCommand,
},
/// Completely reset a card (deletes all data, including the keys on the card!)
FactoryReset {
#[clap(name = "card ident", short = 'c', long = "card")]
@ -221,3 +227,34 @@ pub enum PinCommand {
user_pin_new: Option<PathBuf>,
},
}
#[derive(Parser, Debug)]
pub enum AttCommand {
/// Print the card's "Attestation Certificate"
Cert {
#[clap(name = "card ident", short = 'c', long = "card")]
ident: Option<String>,
},
/// Generate "Attestation Statement" for one of the key slots on the card
Generate {
#[clap(name = "card ident", short = 'c', long = "card")]
ident: String,
#[clap(name = "Key slot (SIG|DEC|AUT)", short = 'k', long = "key")]
key: String,
#[clap(name = "User PIN file", short = 'p', long = "user-pin")]
user_pin: Option<PathBuf>,
},
/// Print a "cardholder certificate" from the card.
/// This shows the "Attestation Statement", if one has been generated.
Statement {
#[clap(name = "card ident", short = 'c', long = "card")]
ident: Option<String>,
#[clap(name = "Key slot (SIG|DEC|AUT)", short = 'k', long = "key")]
key: String,
},
}

View file

@ -78,6 +78,77 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
);
}
}
cli::Command::Attestation { cmd } => match cmd {
cli::AttCommand::Cert { ident } => {
let mut card = pick_card_for_reading(ident)?;
let mut pgp = OpenPgp::new(&mut *card);
let mut open = Open::new(pgp.transaction()?)?;
if let Ok(ac) = open.attestation_certificate() {
let pem = util::pem_encode(ac);
println!("{}", pem);
}
}
cli::AttCommand::Generate {
ident,
key,
user_pin,
} => {
let mut card = util::open_card(&ident)?;
let mut pgp = OpenPgp::new(&mut card);
let mut open = Open::new(pgp.transaction()?)?;
let user_pin = util::get_pin(&mut open, user_pin, ENTER_USER_PIN);
let mut sign = util::verify_to_sign(&mut open, user_pin.as_deref())?;
let kt = match key.as_str() {
"SIG" => KeyType::Signing,
"DEC" => KeyType::Decryption,
"AUT" => KeyType::Authentication,
_ => {
return Err(anyhow!("Unexpected Key Type {}", key).into());
}
};
sign.generate_attestation(kt)?;
}
cli::AttCommand::Statement { ident, key } => {
let mut card = pick_card_for_reading(ident)?;
let mut pgp = OpenPgp::new(&mut *card);
let mut open = Open::new(pgp.transaction()?)?;
// Load cardholder certificate from card.
// FIXME/Note: SELECT_DATA seemed to not work as expected on YK5,
let cert = match key.as_str() {
"AUT" => open.cardholder_certificate()?,
"DEC" => {
// skip first cardholder certificate
let _ = open.cardholder_certificate()?;
open.next_cardholder_certificate()?
}
"SIG" => {
// skip first two cardholder certificates
let _ = open.cardholder_certificate()?;
let _ = open.next_cardholder_certificate()?;
open.next_cardholder_certificate()?
}
_ => {
return Err(anyhow!("Unexpected Key Type {}", key).into());
}
};
if !cert.is_empty() {
let pem = util::pem_encode(cert);
println!("{}", pem);
} else {
println!("Cardholder certificate slot is empty");
}
}
},
cli::Command::FactoryReset { ident } => {
factory_reset(&ident)?;
}

View file

@ -231,3 +231,14 @@ pub(crate) fn print_gnuk_note(err: Error, card: &Open) -> Result<()> {
}
Ok(())
}
pub(crate) fn pem_encode(data: Vec<u8>) -> String {
const PEM_TAG: &str = "CERTIFICATE";
let pem = pem::Pem {
tag: String::from(PEM_TAG),
contents: data,
};
pem::encode(&pem)
}