diff --git a/Cargo.lock b/Cargo.lock index 0a20b9c..cfac5fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -332,7 +332,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.7.11" +version = "1.8.0" dependencies = [ "authenticator", "base64 0.21.5", diff --git a/Cargo.toml b/Cargo.toml index acaf3e4..f6dd1b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.7.11" +version = "1.8.0" authors = ["Hatter Jiang "] edition = "2018" diff --git a/src/cmd_pivrsasign.rs b/src/cmd_pivrsasign.rs index ff8c4e7..049edea 100644 --- a/src/cmd_pivrsasign.rs +++ b/src/cmd_pivrsasign.rs @@ -1,12 +1,12 @@ use std::collections::BTreeMap; use clap::{App, Arg, ArgMatches, SubCommand}; -use rust_util::{util_msg, XResult}; +use rust_util::util_msg; use rust_util::util_clap::{Command, CommandError}; -use rust_util::util_msg::MessageType; use yubikey::{piv, YubiKey}; use yubikey::piv::{AlgorithmId, SlotId}; +use crate::rsautil; use crate::util::base64_encode; pub struct CommandImpl; @@ -33,35 +33,16 @@ impl Command for CommandImpl { let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); - // https://www.ibm.com/docs/en/zos/2.2.0?topic=cryptography-pkcs-1-formats - // MD5 X’3020300C 06082A86 4886F70D 02050500 0410’ || 16-byte hash value - // SHA-1 X'30213009 06052B0E 03021A05 000414’ || 20-byte hash value - // SHA-224 X’302D300D 06096086 48016503 04020405 00041C’ || 28-byte hash value - // SHA-256 X’3031300D 06096086 48016503 04020105 000420’ || 32-byte hash value - // SHA-384 X’3041300D 06096086 48016503 04020205 000430’ || 48-byte hash value - // SHA-512 X’3051300D 06096086 48016503 04020305 000440’ || 64-byte hash value - let sha256_der_prefix = hex::decode("3031300d060960864801650304020105000420").unwrap(); - if let Some(sha256_hex) = sha256_hex_opt { - let hash = opt_result!(hex::decode(sha256_hex), "Decode sha256 failed: {}"); - - let mut hash_with_oid = Vec::with_capacity(128); - hash_with_oid.extend_from_slice(&sha256_der_prefix); - hash_with_oid.extend_from_slice(&hash); - let hash_padding = pkcs1_padding_for_sign(&hash_with_oid, 2048).unwrap(); - util_msg::when(MessageType::DEBUG, || { - debugging!("Hash: {}", hex::encode(&hash)); - debugging!("Hash with OID: {}", hex::encode(&hash_with_oid)); - debugging!("PKCS1 padding: {}", hex::encode(&hash_padding)); - }); - let raw_in = crate::digest::copy_rsa2048(&hash_padding).unwrap(); + let sha256 = opt_result!(hex::decode(sha256_hex), "Decode sha256 failed: {}"); + let raw_in = rsautil::pkcs15_rsa_2048_sign_padding(&sha256); let sign_result = piv::sign_data(&mut yk, &raw_in, AlgorithmId::Rsa2048, SlotId::Signature); let sign = opt_result!(sign_result, "Sign data failed: {}"); let sign_bytes = sign.as_slice(); if json_output { let mut json = BTreeMap::<&'_ str, String>::new(); - json.insert("hash_hex", hex::encode(&hash)); + json.insert("hash_hex", hex::encode(&sha256)); json.insert("sign_hex", hex::encode(sign_bytes)); json.insert("sign_base64", base64_encode(sign_bytes)); println!("{}", serde_json::to_string_pretty(&json).unwrap()); @@ -74,18 +55,3 @@ impl Command for CommandImpl { } } -fn pkcs1_padding_for_sign(bs: &[u8], bit_len: usize) -> XResult> { - let byte_len = bit_len / 8; - let max_len = byte_len - (1 + 1 + 8 + 2); - if bs.len() > max_len { - return simple_error!("Length is too large: {} > {}", bs.len(), max_len); - } - let mut output = Vec::::with_capacity(byte_len); - output.push(0x00); - output.push(0x01); - let ps_len = byte_len - bs.len() - (1 + 1 + 1); - output.extend_from_slice(&vec![0xff_u8; ps_len]); - output.push(0x00); - output.extend_from_slice(bs); - Ok(output) -} diff --git a/src/cmd_signjwt.rs b/src/cmd_signjwt.rs index d8c532e..b4bb1ab 100644 --- a/src/cmd_signjwt.rs +++ b/src/cmd_signjwt.rs @@ -6,10 +6,10 @@ use jwt::{AlgorithmType, Header, ToBase64}; use jwt::header::HeaderType; use rust_util::{util_msg, XResult}; use rust_util::util_clap::{Command, CommandError}; +use yubikey::{Certificate, YubiKey}; use yubikey::piv::{AlgorithmId, sign_data}; -use yubikey::YubiKey; -use crate::{digest, pivutil, util}; +use crate::{digest, pivutil, rsautil, util}; const SEPARATOR: &str = "."; @@ -39,7 +39,7 @@ impl Command for CommandImpl { sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"); let key_id = sub_arg_matches.value_of("key-id"); - let claims = opt_value_result!(sub_arg_matches.values_of("claims"), "Claims is required."); + let claims = sub_arg_matches.values_of("claims"); let payload = sub_arg_matches.value_of("payload"); let header = Header { @@ -48,16 +48,20 @@ impl Command for CommandImpl { ..Default::default() }; let mut jwt_claims = BTreeMap::new(); - if payload.is_none() { - for claim in claims { - match split_claim(claim) { - None => { warning!("Claim '{}' do not contains ':'", claim); } - Some((k, v)) => { jwt_claims.insert(k, v); } + match (payload, claims) { + (Some(_), _) => {} + (_, Some(claims)) => { + for claim in claims { + match split_claim(claim) { + None => { warning!("Claim '{}' do not contains ':'", claim); } + Some((k, v)) => { jwt_claims.insert(k, v); } + } + } + if !jwt_claims.contains_key("sub") { + return simple_error!("Claim sub is not assigned."); } } - if !jwt_claims.contains_key("sub") { - return simple_error!("Claim sub is not assigned."); - } + _ => return simple_error!("Payload or Claims is required."), } let token_string = sign_jwt(slot, &pin_opt, header, &payload, &jwt_claims)?; @@ -82,8 +86,18 @@ fn sign_jwt(slot: &str, pin_opt: &Option<&str>, mut header: Header, payload: &Op opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); } - // TODO set algorithm - let (jwt_algorithm, yk_algorithm) = (AlgorithmType::Es256, AlgorithmId::EccP256); + let cert = match Certificate::read(&mut yk, slot_id) { + Ok(c) => c, + Err(e) => return simple_error!("Read YubiKey certificate failed: {}", e), + }; + let piv_algorithm_id = pivutil::get_algorithm_id(&cert.cert.tbs_certificate.subject_public_key_info)?; + + let (jwt_algorithm, yk_algorithm) = match piv_algorithm_id { + AlgorithmId::Rsa1024 => return simple_error!("RSA 1024 bits not supported."), + AlgorithmId::Rsa2048 => (AlgorithmType::Rs256, AlgorithmId::Rsa2048), + AlgorithmId::EccP256 => (AlgorithmType::Es256, AlgorithmId::EccP256), + AlgorithmId::EccP384 => (AlgorithmType::Es384, AlgorithmId::EccP384), + }; header.algorithm = jwt_algorithm; debugging!("Header: {:?}", header); @@ -91,7 +105,7 @@ fn sign_jwt(slot: &str, pin_opt: &Option<&str>, mut header: Header, payload: &Op let header = opt_result!(header.to_base64(), "Header to base64 failed: {}"); let claims = match payload { - Some(payload) => Cow::Owned(payload.to_string()), + Some(payload) => Cow::Owned(util::base64_encode_url_safe_no_pad(payload.as_bytes())), None => opt_result!(claims.to_base64(), "Claims to base64 failed: {}"), }; @@ -99,10 +113,15 @@ fn sign_jwt(slot: &str, pin_opt: &Option<&str>, mut header: Header, payload: &Op tobe_signed.extend_from_slice(header.as_bytes()); tobe_signed.extend_from_slice(SEPARATOR.as_bytes()); tobe_signed.extend_from_slice(claims.as_bytes()); - let sha256 = digest::sha256_bytes(&tobe_signed); + let raw_in = match jwt_algorithm { + AlgorithmType::Rs256 => rsautil::pkcs15_rsa_2048_sign_padding(&digest::sha256_bytes(&tobe_signed)), + AlgorithmType::Es256 => digest::sha256_bytes(&tobe_signed), + AlgorithmType::Es384 => digest::sha384_bytes(&tobe_signed), + _ => return simple_error!("SHOULD NOT HAPPEN: {:?}", jwt_algorithm), + }; let signed_data = opt_result!( - sign_data(&mut yk, &sha256, yk_algorithm, slot_id), "Sign YubiKey failed: {}"); + sign_data(&mut yk, &raw_in, yk_algorithm, slot_id), "Sign YubiKey failed: {}"); let signature = util::base64_encode_url_safe_no_pad(signed_data); diff --git a/src/digest.rs b/src/digest.rs index e489aea..bb72978 100644 --- a/src/digest.rs +++ b/src/digest.rs @@ -48,4 +48,4 @@ define_copy_array!(copy_sha256, 0x20); define_copy_array!(copy_sha384, 0x30); define_copy_array!(copy_sha512, 0x40); -define_copy_array!(copy_rsa2048, 0x100); +// define_copy_array!(copy_rsa2048, 0x100); diff --git a/src/rsautil.rs b/src/rsautil.rs index ad70f2a..2fd8de7 100644 --- a/src/rsautil.rs +++ b/src/rsautil.rs @@ -1,7 +1,8 @@ use openssl::bn::{BigNum, BigNumContext}; use openssl::pkey::PKey; use openssl::rsa::{Padding, Rsa}; -use rust_util::XResult; +use rust_util::{util_msg, XResult}; +use rust_util::util_msg::MessageType; #[derive(Debug)] pub struct RsaCrt { @@ -111,4 +112,42 @@ fn inner_from(p: BigNum, q: BigNum, e: BigNum) -> XResult { exponent2: dq, coefficient: qinv, }) -} \ No newline at end of file +} + +pub fn pkcs15_rsa_2048_sign_padding(sha256: &[u8]) -> Vec { + // https://www.ibm.com/docs/en/zos/2.2.0?topic=cryptography-pkcs-1-formats + // MD5 X’3020300C 06082A86 4886F70D 02050500 0410’ || 16-byte hash value + // SHA-1 X'30213009 06052B0E 03021A05 000414’ || 20-byte hash value + // SHA-224 X’302D300D 06096086 48016503 04020405 00041C’ || 28-byte hash value + // SHA-256 X’3031300D 06096086 48016503 04020105 000420’ || 32-byte hash value + // SHA-384 X’3041300D 06096086 48016503 04020205 000430’ || 48-byte hash value + // SHA-512 X’3051300D 06096086 48016503 04020305 000440’ || 64-byte hash value + let sha256_der_prefix = hex::decode("3031300d060960864801650304020105000420").unwrap(); + + let mut hash_with_oid = Vec::with_capacity(128); + hash_with_oid.extend_from_slice(&sha256_der_prefix); + hash_with_oid.extend_from_slice(&sha256); + let hash_padding = pkcs1_padding_for_sign(&hash_with_oid, 2048).unwrap(); + util_msg::when(MessageType::DEBUG, || { + debugging!("Hash: {}", hex::encode(&sha256)); + debugging!("Hash with OID: {}", hex::encode(&hash_with_oid)); + debugging!("PKCS1 padding: {}", hex::encode(&hash_padding)); + }); + hash_padding +} + +fn pkcs1_padding_for_sign(bs: &[u8], bit_len: usize) -> XResult> { + let byte_len = bit_len / 8; + let max_len = byte_len - (1 + 1 + 8 + 2); + if bs.len() > max_len { + return simple_error!("Length is too large: {} > {}", bs.len(), max_len); + } + let mut output = Vec::::with_capacity(byte_len); + output.push(0x00); + output.push(0x01); + let ps_len = byte_len - bs.len() - (1 + 1 + 1); + output.extend_from_slice(&vec![0xff_u8; ps_len]); + output.push(0x00); + output.extend_from_slice(bs); + Ok(output) +}