v1.8.0, sign-jwt works
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -332,7 +332,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "card-cli"
|
name = "card-cli"
|
||||||
version = "1.7.11"
|
version = "1.8.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"authenticator",
|
"authenticator",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "card-cli"
|
name = "card-cli"
|
||||||
version = "1.7.11"
|
version = "1.8.0"
|
||||||
authors = ["Hatter Jiang <jht5945@gmail.com>"]
|
authors = ["Hatter Jiang <jht5945@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
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_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg::MessageType;
|
|
||||||
use yubikey::{piv, YubiKey};
|
use yubikey::{piv, YubiKey};
|
||||||
use yubikey::piv::{AlgorithmId, SlotId};
|
use yubikey::piv::{AlgorithmId, SlotId};
|
||||||
|
|
||||||
|
use crate::rsautil;
|
||||||
use crate::util::base64_encode;
|
use crate::util::base64_encode;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
@@ -33,35 +33,16 @@ impl Command for CommandImpl {
|
|||||||
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
||||||
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
|
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 {
|
if let Some(sha256_hex) = sha256_hex_opt {
|
||||||
let hash = opt_result!(hex::decode(sha256_hex), "Decode sha256 failed: {}");
|
let sha256 = opt_result!(hex::decode(sha256_hex), "Decode sha256 failed: {}");
|
||||||
|
let raw_in = rsautil::pkcs15_rsa_2048_sign_padding(&sha256);
|
||||||
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 sign_result = piv::sign_data(&mut yk, &raw_in, AlgorithmId::Rsa2048, SlotId::Signature);
|
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 = opt_result!(sign_result, "Sign data failed: {}");
|
||||||
let sign_bytes = sign.as_slice();
|
let sign_bytes = sign.as_slice();
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
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_hex", hex::encode(sign_bytes));
|
||||||
json.insert("sign_base64", base64_encode(sign_bytes));
|
json.insert("sign_base64", base64_encode(sign_bytes));
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
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<Vec<u8>> {
|
|
||||||
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::<u8>::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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ use jwt::{AlgorithmType, Header, ToBase64};
|
|||||||
use jwt::header::HeaderType;
|
use jwt::header::HeaderType;
|
||||||
use rust_util::{util_msg, XResult};
|
use rust_util::{util_msg, XResult};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use yubikey::{Certificate, YubiKey};
|
||||||
use yubikey::piv::{AlgorithmId, sign_data};
|
use yubikey::piv::{AlgorithmId, sign_data};
|
||||||
use yubikey::YubiKey;
|
|
||||||
|
|
||||||
use crate::{digest, pivutil, util};
|
use crate::{digest, pivutil, rsautil, util};
|
||||||
|
|
||||||
const SEPARATOR: &str = ".";
|
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");
|
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 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 payload = sub_arg_matches.value_of("payload");
|
||||||
|
|
||||||
let header = Header {
|
let header = Header {
|
||||||
@@ -48,16 +48,20 @@ impl Command for CommandImpl {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut jwt_claims = BTreeMap::new();
|
let mut jwt_claims = BTreeMap::new();
|
||||||
if payload.is_none() {
|
match (payload, claims) {
|
||||||
for claim in claims {
|
(Some(_), _) => {}
|
||||||
match split_claim(claim) {
|
(_, Some(claims)) => {
|
||||||
None => { warning!("Claim '{}' do not contains ':'", claim); }
|
for claim in claims {
|
||||||
Some((k, v)) => { jwt_claims.insert(k, v); }
|
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!("Payload or Claims is required."),
|
||||||
return simple_error!("Claim sub is not assigned.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let token_string = sign_jwt(slot, &pin_opt, header, &payload, &jwt_claims)?;
|
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: {}");
|
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO set algorithm
|
let cert = match Certificate::read(&mut yk, slot_id) {
|
||||||
let (jwt_algorithm, yk_algorithm) = (AlgorithmType::Es256, AlgorithmId::EccP256);
|
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;
|
header.algorithm = jwt_algorithm;
|
||||||
debugging!("Header: {:?}", header);
|
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 header = opt_result!(header.to_base64(), "Header to base64 failed: {}");
|
||||||
let claims = match payload {
|
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: {}"),
|
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(header.as_bytes());
|
||||||
tobe_signed.extend_from_slice(SEPARATOR.as_bytes());
|
tobe_signed.extend_from_slice(SEPARATOR.as_bytes());
|
||||||
tobe_signed.extend_from_slice(claims.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!(
|
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);
|
let signature = util::base64_encode_url_safe_no_pad(signed_data);
|
||||||
|
|
||||||
|
|||||||
@@ -48,4 +48,4 @@ define_copy_array!(copy_sha256, 0x20);
|
|||||||
define_copy_array!(copy_sha384, 0x30);
|
define_copy_array!(copy_sha384, 0x30);
|
||||||
define_copy_array!(copy_sha512, 0x40);
|
define_copy_array!(copy_sha512, 0x40);
|
||||||
|
|
||||||
define_copy_array!(copy_rsa2048, 0x100);
|
// define_copy_array!(copy_rsa2048, 0x100);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use openssl::bn::{BigNum, BigNumContext};
|
use openssl::bn::{BigNum, BigNumContext};
|
||||||
use openssl::pkey::PKey;
|
use openssl::pkey::PKey;
|
||||||
use openssl::rsa::{Padding, Rsa};
|
use openssl::rsa::{Padding, Rsa};
|
||||||
use rust_util::XResult;
|
use rust_util::{util_msg, XResult};
|
||||||
|
use rust_util::util_msg::MessageType;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RsaCrt {
|
pub struct RsaCrt {
|
||||||
@@ -111,4 +112,42 @@ fn inner_from(p: BigNum, q: BigNum, e: BigNum) -> XResult<RsaCrt> {
|
|||||||
exponent2: dq,
|
exponent2: dq,
|
||||||
coefficient: qinv,
|
coefficient: qinv,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pkcs15_rsa_2048_sign_padding(sha256: &[u8]) -> Vec<u8> {
|
||||||
|
// 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<Vec<u8>> {
|
||||||
|
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::<u8>::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)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user