From 61804ab0299d46efafd5e9733dfbff10348ae274 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Mon, 16 Dec 2024 22:42:34 +0800 Subject: [PATCH] feat: v1.10.12, se-recover, se-ecdh support public key point --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cmd_se_ecdh.rs | 31 +++++++++++++++++-- src/cmd_se_recover.rs | 67 +++++++++++++++++++++++++++++++++++++++++ src/main.rs | 4 +++ src/seutil.rs | 67 ++++++++++++++++++++++++++--------------- swift-lib/src/lib.swift | 42 ++++++++++++++++++++++++++ 7 files changed, 185 insertions(+), 30 deletions(-) create mode 100644 src/cmd_se_recover.rs diff --git a/Cargo.lock b/Cargo.lock index d17c685..7320365 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,7 +487,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.10.11" +version = "1.10.12" dependencies = [ "authenticator 0.3.1", "base64 0.21.7", diff --git a/Cargo.toml b/Cargo.toml index 9a714c4..1711d5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.10.11" +version = "1.10.12" authors = ["Hatter Jiang "] edition = "2018" diff --git a/src/cmd_se_ecdh.rs b/src/cmd_se_ecdh.rs index c873c0c..0a1d487 100644 --- a/src/cmd_se_ecdh.rs +++ b/src/cmd_se_ecdh.rs @@ -1,8 +1,11 @@ use crate::keyutil::{parse_key_uri, KeyUri}; use crate::seutil; use clap::{App, Arg, ArgMatches, SubCommand}; +use p256::elliptic_curve::sec1::FromEncodedPoint; +use p256::{EncodedPoint, PublicKey}; use rust_util::util_clap::{Command, CommandError}; use rust_util::util_msg; +use spki::EncodePublicKey; use std::collections::BTreeMap; pub struct CommandImpl; @@ -47,9 +50,31 @@ impl Command for CommandImpl { let KeyUri::SecureEnclaveKey(se_key_uri) = parse_key_uri(key)?; debugging!("Secure enclave key URI: {:?}", se_key_uri); - let ephemeral_public_key_bytes = hex::decode(epk)?; - let dh = - seutil::secure_enclave_p256_dh(&se_key_uri.private_key, &ephemeral_public_key_bytes)?; + let ephemeral_public_key_der_bytes; + if epk.starts_with("04") { + let ephemeral_public_key_point_bytes = opt_result!( + hex::decode(epk), + "Decode public key point from hex failed: {}" + ); + let encoded_point = opt_result!( + EncodedPoint::from_bytes(ephemeral_public_key_point_bytes), + "Parse public key point failed: {}" + ); + let public_key_opt = PublicKey::from_encoded_point(&encoded_point); + if public_key_opt.is_none().into() { + return simple_error!("Parse public key failed."); + } + let public_key = public_key_opt.unwrap(); + ephemeral_public_key_der_bytes = public_key.to_public_key_der()?.as_bytes().to_vec(); + } else { + ephemeral_public_key_der_bytes = + opt_result!(hex::decode(epk), "Decode public key from hex failed: {}"); + } + + let dh = seutil::secure_enclave_p256_dh( + &se_key_uri.private_key, + &ephemeral_public_key_der_bytes, + )?; let dh_hex = hex::encode(&dh); if json_output { diff --git a/src/cmd_se_recover.rs b/src/cmd_se_recover.rs new file mode 100644 index 0000000..9672f4f --- /dev/null +++ b/src/cmd_se_recover.rs @@ -0,0 +1,67 @@ +use crate::keyutil::{parse_key_uri, KeyUri, KeyUsage}; +use crate::pkiutil::bytes_to_pem; +use crate::seutil; +use crate::util::base64_encode; +use clap::{App, Arg, ArgMatches, SubCommand}; +use rust_util::util_clap::{Command, CommandError}; +use rust_util::util_msg; +use std::collections::BTreeMap; + +pub struct CommandImpl; + +impl Command for CommandImpl { + fn name(&self) -> &str { + "se-recover" + } + + fn subcommand<'a>(&self) -> App<'a, 'a> { + SubCommand::with_name(self.name()) + .about("Secure Enclave recover subcommand") + .arg( + Arg::with_name("key") + .long("key") + .required(true) + .takes_value(true) + .help("Key uri"), + ) + .arg(Arg::with_name("json").long("json").help("JSON output")) + } + + fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + if !seutil::is_support_se() { + return simple_error!("Secure Enclave is NOT supported."); + } + let key = sub_arg_matches.value_of("key").unwrap(); + + let json_output = sub_arg_matches.is_present("json"); + if json_output { + util_msg::set_logger_std_out(false); + } + + let KeyUri::SecureEnclaveKey(se_key_uri) = parse_key_uri(key)?; + debugging!("Secure enclave key URI: {:?}", se_key_uri); + + let (public_key_point, public_key_der, _private_key) = + seutil::recover_secure_enclave_p256_public_key( + &se_key_uri.private_key, + se_key_uri.usage == KeyUsage::Singing, + )?; + + let public_key_point_hex = hex::encode(&public_key_point); + let public_key_pem = bytes_to_pem("PUBLIC KEY", &*public_key_der); + if json_output { + let mut json = BTreeMap::<&'_ str, String>::new(); + json.insert("public_key_point", public_key_point_hex); + json.insert("public_key_pem", base64_encode(&*public_key_der)); + json.insert("key", key.to_string()); + + println!("{}", serde_json::to_string_pretty(&json).unwrap()); + } else { + success!("Public key(point): {}", public_key_point_hex); + success!("Public key PEM: \n{}", public_key_pem); + success!("Key: {}", key); + } + + Ok(None) + } +} diff --git a/src/main.rs b/src/main.rs index 1f9aaea..aaeb244 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,6 +39,8 @@ mod cmd_se_ecdh; mod cmd_se_ecsign; #[cfg(feature = "with-secure-enclave")] mod cmd_se_generate; +#[cfg(feature = "with-secure-enclave")] +mod cmd_se_recover; mod cmd_signfile; mod cmd_signjwt; mod cmd_sshagent; @@ -134,6 +136,8 @@ fn inner_main() -> CommandError { #[cfg(feature = "with-secure-enclave")] Box::new(cmd_se_generate::CommandImpl), #[cfg(feature = "with-secure-enclave")] + Box::new(cmd_se_recover::CommandImpl), + #[cfg(feature = "with-secure-enclave")] Box::new(cmd_se_ecsign::CommandImpl), #[cfg(feature = "with-secure-enclave")] Box::new(cmd_se_ecdh::CommandImpl), diff --git a/src/seutil.rs b/src/seutil.rs index fb7bf4d..2a0478e 100644 --- a/src/seutil.rs +++ b/src/seutil.rs @@ -8,6 +8,8 @@ swift!(fn generate_secure_enclave_p256_ecdh_keypair() -> SRString); swift!(fn generate_secure_enclave_p256_ecsign_keypair() -> SRString); swift!(fn compute_secure_enclave_p256_ecdh(private_key_base64: SRString, ephemera_public_key_base64: SRString) -> SRString); swift!(fn compute_secure_enclave_p256_ecsign(private_key_base64: SRString, content: SRString) -> SRString); +swift!(fn recover_secure_enclave_p256_ecsign_public_key(private_key_base64: SRString) -> SRString); +swift!(fn recover_secure_enclave_p256_ecdh_public_key(private_key_base64: SRString) -> SRString); pub fn is_support_se() -> bool { unsafe { is_support_secure_enclave() } @@ -19,31 +21,19 @@ pub fn generate_secure_enclave_p256_keypair(sign: bool) -> XResult<(Vec, Vec } else { unsafe { generate_secure_enclave_p256_ecdh_keypair() } }; - let p256_keypair_result_str = p256_keypair_result.as_str(); - if !p256_keypair_result_str.starts_with("ok:") { - return simple_error!( - "Generate P256 in secure enclave failed: {}", - p256_keypair_result_str - ); - } - let public_key_and_private_key = p256_keypair_result_str.chars().skip(3).collect::(); - let public_key_and_private_keys = public_key_and_private_key.split(',').collect::>(); - if public_key_and_private_keys.len() != 3 { - return simple_error!( - "Generate P256 in secure enclave result is bad: {}", - public_key_and_private_key - ); - } - let public_key_point = opt_result!( - base64_decode(public_key_and_private_keys[0]), - "Public key point is not base64 encoded: {}" - ); - let public_key_der = opt_result!( - base64_decode(public_key_and_private_keys[1]), - "Public key der is not base64 encoded: {}" - ); - let private_key = public_key_and_private_keys[2].to_string(); - Ok((public_key_point, public_key_der, private_key)) + parse_p256_keypair_result(p256_keypair_result.as_str()) +} + +pub fn recover_secure_enclave_p256_public_key( + private_key: &str, + sign: bool, +) -> XResult<(Vec, Vec, String)> { + let p256_keypair_result = if sign { + unsafe { recover_secure_enclave_p256_ecsign_public_key(SRString::from(private_key)) } + } else { + unsafe { recover_secure_enclave_p256_ecdh_public_key(SRString::from(private_key)) } + }; + parse_p256_keypair_result(p256_keypair_result.as_str()) } pub fn secure_enclave_p256_dh( @@ -94,3 +84,30 @@ pub fn secure_enclave_p256_sign(private_key: &str, content: &[u8]) -> XResult XResult<(Vec, Vec, String)> { + if !p256_keypair_result_str.starts_with("ok:") { + return simple_error!( + "Generate P256 in secure enclave failed: {}", + p256_keypair_result_str + ); + } + let public_key_and_private_key = p256_keypair_result_str.chars().skip(3).collect::(); + let public_key_and_private_keys = public_key_and_private_key.split(',').collect::>(); + if public_key_and_private_keys.len() != 3 { + return simple_error!( + "Generate P256 in secure enclave result is bad: {}", + public_key_and_private_key + ); + } + let public_key_point = opt_result!( + base64_decode(public_key_and_private_keys[0]), + "Public key point is not base64 encoded: {}" + ); + let public_key_der = opt_result!( + base64_decode(public_key_and_private_keys[1]), + "Public key der is not base64 encoded: {}" + ); + let private_key = public_key_and_private_keys[2].to_string(); + Ok((public_key_point, public_key_der, private_key)) +} diff --git a/swift-lib/src/lib.swift b/swift-lib/src/lib.swift index beb8aa2..83f8d10 100644 --- a/swift-lib/src/lib.swift +++ b/swift-lib/src/lib.swift @@ -53,6 +53,48 @@ func generateSecureEnclaveP256KeyPair(sign: Bool) -> SRString { } } +@_cdecl("recover_secure_enclave_p256_ecsign_public_key") +func recoverSecureEnclaveP256PublicKeyEcsign(privateKeyDataRepresentation: SRString) -> SRString { + return recoverSecureEnclaveP256PublicKey(privateKeyDataRepresentation: privateKeyDataRepresentation, sign: true); +} + +@_cdecl("recover_secure_enclave_p256_ecdh_public_key") +func recoverSecureEnclaveP256PublicKeyEcdh(privateKeyDataRepresentation: SRString) -> SRString { + return recoverSecureEnclaveP256PublicKey(privateKeyDataRepresentation: privateKeyDataRepresentation, sign: false); +} + +func recoverSecureEnclaveP256PublicKey(privateKeyDataRepresentation: SRString, sign: Bool) -> SRString { + guard let privateKeyDataRepresentation = Data( + base64Encoded: privateKeyDataRepresentation.toString() + ) else { + return SRString("err:private key base64 decode failed") + } + do { + let context = LAContext(); + if (sign) { + let privateKeyReference = try SecureEnclave.P256.Signing.PrivateKey( + dataRepresentation: privateKeyDataRepresentation, + authenticationContext: context + ) + let publicKeyBase64 = privateKeyReference.publicKey.x963Representation.base64EncodedString() + let publicKeyPem = privateKeyReference.publicKey.derRepresentation.base64EncodedString() + let dataRepresentationBase64 = privateKeyReference.dataRepresentation.base64EncodedString() + return SRString("ok:\(publicKeyBase64),\(publicKeyPem),\(dataRepresentationBase64)") + } else { + let privateKeyReference = try SecureEnclave.P256.KeyAgreement.PrivateKey( + dataRepresentation: privateKeyDataRepresentation, + authenticationContext: context + ) + let publicKeyBase64 = privateKeyReference.publicKey.x963Representation.base64EncodedString() + let publicKeyPem = privateKeyReference.publicKey.derRepresentation.base64EncodedString() + let dataRepresentationBase64 = privateKeyReference.dataRepresentation.base64EncodedString() + return SRString("ok:\(publicKeyBase64),\(publicKeyPem),\(dataRepresentationBase64)") + } + } catch { + return SRString("err:\(error)") + } +} + @_cdecl("compute_secure_enclave_p256_ecdh") func computeSecureEnclaveP256Ecdh(privateKeyDataRepresentation: SRString, ephemeraPublicKey: SRString) -> SRString { guard let privateKeyDataRepresentation = Data(