From 42bc09fe077172f0f086fcbf4003ede1d0a576b7 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sat, 9 Dec 2023 13:58:30 +0800 Subject: [PATCH] feat: v1.2.0, support macos secure enclave --- Cargo.lock | 2 +- Cargo.toml | 2 +- justfile | 8 ++++-- src/cmd_decrypt.rs | 37 ++++++++++++++++++++++++ src/cmd_encrypt.rs | 2 +- src/cmd_initkeychainkey.rs | 18 ++++++++---- src/util_keychainkey.rs | 21 +++++++++++++- swift-lib/src/lib.swift | 1 - swift/se.swift | 59 -------------------------------------- 9 files changed, 79 insertions(+), 71 deletions(-) delete mode 100644 swift/se.swift diff --git a/Cargo.lock b/Cargo.lock index 311505a..d9f10b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "tiny-encrypt" -version = "1.1.2" +version = "1.2.0" dependencies = [ "aes-gcm-stream", "base64", diff --git a/Cargo.toml b/Cargo.toml index ed89e8b..f6613e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-encrypt" -version = "1.1.2" +version = "1.2.0" edition = "2021" license = "MIT" description = "A simple and tiny file encrypt tool" diff --git a/justfile b/justfile index 65c64d3..bb77663 100644 --- a/justfile +++ b/justfile @@ -9,5 +9,9 @@ build: build-no-features: cargo build --release --no-default-features -compile-libse-static: - cd swift && swiftc se.swift -emit-module -emit-library -static +# Try build all +try-build-all: + cargo build --no-default-features + cargo build --no-default-features --features decrypt + cargo build --no-default-features --features macos + cargo build --no-default-features --features secure-enclave diff --git a/src/cmd_decrypt.rs b/src/cmd_decrypt.rs index 1dbcf1d..524f067 100644 --- a/src/cmd_decrypt.rs +++ b/src/cmd_decrypt.rs @@ -32,6 +32,8 @@ use crate::util::SecVec; use crate::util_digest::DigestWrite; #[cfg(feature = "macos")] use crate::util_keychainstatic; +#[cfg(feature = "secure-enclave")] +use crate::util_keychainkey; use crate::util_progress::Progress; use crate::wrap_key::WrapKey; @@ -435,6 +437,8 @@ pub fn try_decrypt_key(config: &Option, #[cfg(feature = "macos")] TinyEncryptEnvelopType::StaticX25519 => try_decrypt_key_ecdh_static_x25519(config, envelop), TinyEncryptEnvelopType::Ecdh | TinyEncryptEnvelopType::EcdhP384 => try_decrypt_key_ecdh(config, envelop, pin, slot), + #[cfg(feature = "secure-enclave")] + TinyEncryptEnvelopType::KeyP256 => try_decrypt_se_key_ecdh(config, envelop), unknown_type => simple_error!("Unknown or unsupported type: {}", unknown_type.get_name()), } } @@ -479,6 +483,39 @@ fn try_decrypt_key_ecdh(config: &Option, Ok(decrypted_key) } +#[cfg(feature = "secure-enclave")] +fn try_decrypt_se_key_ecdh(config: &Option, + envelop: &TinyEncryptEnvelop) -> XResult> { + let wrap_key = WrapKey::parse(&envelop.encrypted_key)?; + let cryptor = match wrap_key.header.enc.as_str() { + ENC_AES256_GCM_P256 => Cryptor::Aes256Gcm, + ENC_CHACHA20_POLY1305_P256 => Cryptor::ChaCha20Poly1305, + _ => return simple_error!("Unsupported header enc: {}", &wrap_key.header.enc), + }; + let e_pub_key_bytes = wrap_key.header.get_e_pub_key_bytes()?; + + let config = opt_value_result!(config, "Tiny encrypt config is not found"); + let config_envelop = opt_value_result!( + config.find_by_kid(&envelop.kid), "Cannot find config for: {}", &envelop.kid); + let config_envelop_args = opt_value_result!(&config_envelop.args, "No arguments found for: {}", &envelop.kid); + if config_envelop_args.is_empty() { + return simple_error!("Not enough arguments for: {}", &envelop.kid); + } + let private_key_base64 = &config_envelop_args[0]; + + let shared_secret = opt_result!(util_keychainkey::decrypt_data( + private_key_base64, + &e_pub_key_bytes + ), "Decrypt via secure enclave failed: {}"); + let key = util::simple_kdf(shared_secret.as_slice()); + let key_nonce = KeyNonce { k: &key, n: &wrap_key.nonce }; + let decrypted_key = crypto_simple::decrypt( + cryptor, &key_nonce, &wrap_key.encrypted_data)?; + util::zeroize(key); + util::zeroize(shared_secret); + Ok(decrypted_key) +} + fn try_decrypt_key_ecdh_pgp_x25519(envelop: &TinyEncryptEnvelop, pin: &Option) -> XResult> { let wrap_key = WrapKey::parse(&envelop.encrypted_key)?; let cryptor = match wrap_key.header.enc.as_str() { diff --git a/src/cmd_encrypt.rs b/src/cmd_encrypt.rs index ac1d5ce..e58a23c 100644 --- a/src/cmd_encrypt.rs +++ b/src/cmd_encrypt.rs @@ -271,7 +271,7 @@ fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptConfig TinyEncryptEnvelopType::PgpX25519 | TinyEncryptEnvelopType::StaticX25519 => { encrypted_envelops.push(encrypt_envelop_ecdh_x25519(cryptor, key, envelop)?); } - TinyEncryptEnvelopType::Ecdh => { + TinyEncryptEnvelopType::Ecdh | TinyEncryptEnvelopType::KeyP256 => { encrypted_envelops.push(encrypt_envelop_ecdh(cryptor, key, envelop)?); } TinyEncryptEnvelopType::EcdhP384 => { diff --git a/src/cmd_initkeychainkey.rs b/src/cmd_initkeychainkey.rs index 0f68068..54ab608 100644 --- a/src/cmd_initkeychainkey.rs +++ b/src/cmd_initkeychainkey.rs @@ -2,9 +2,11 @@ use clap::Args; use rust_util::{debugging, information, opt_result, opt_value_result, simple_error, success, XResult}; use security_framework::os::macos::keychain::SecKeychain; -use crate::{util_keychainkey, util_keychainstatic}; use crate::config::TinyEncryptConfigEnvelop; use crate::spec::TinyEncryptEnvelopType; +#[cfg(feature = "secure-enclave")] +use crate::util_keychainkey; +use crate::util_keychainstatic; #[derive(Debug, Args)] pub struct CmdKeychainKey { @@ -27,22 +29,28 @@ const DEFAULT_SERVICE_NAME: &str = "tiny-encrypt"; pub fn keychain_key(cmd_keychain_key: CmdKeychainKey) -> XResult<()> { if cmd_keychain_key.secure_enclave { - keychain_key_se(cmd_keychain_key) + #[cfg(feature = "secure-enclave")] + return keychain_key_se(cmd_keychain_key); + #[cfg(not(feature = "secure-enclave"))] + return simple_error!("Feature secure-enclave is not built"); } else { keychain_key_static(cmd_keychain_key) } } +#[cfg(feature = "secure-enclave")] pub fn keychain_key_se(cmd_keychain_key: CmdKeychainKey) -> XResult<()> { if !util_keychainkey::is_support_se() { return simple_error!("Secure enclave is not supported."); } let (public_key_hex, private_key_base64) = util_keychainkey::generate_se_p256_keypair()?; + let public_key_compressed_hex = public_key_hex.chars() + .skip(2).take(public_key_hex.len() / 2 - 1).collect::(); let config_envelop = TinyEncryptConfigEnvelop { r#type: TinyEncryptEnvelopType::KeyP256, sid: cmd_keychain_key.key_name.clone(), - kid: format!("keychain:{}", &public_key_hex), + kid: format!("keychain:02{}", &public_key_compressed_hex), desc: Some("Keychain Secure Enclave".to_string()), args: Some(vec![ private_key_base64 @@ -59,13 +67,13 @@ pub fn keychain_key_static(cmd_keychain_key: CmdKeychainKey) -> XResult<()> { let service_name = cmd_keychain_key.server_name.as_deref().unwrap_or(DEFAULT_SERVICE_NAME); let sec_keychain = opt_result!(SecKeychain::default(), "Get keychain failed: {}"); let key_name = opt_value_result!(&cmd_keychain_key.key_name, "Key name is required."); - if sec_keychain.find_generic_password(service_name, &key_name).is_ok() { + if sec_keychain.find_generic_password(service_name, key_name).is_ok() { return simple_error!("Static x25519 exists: {}.{}", service_name, &key_name); } let (keychain_key, public_key) = util_keychainstatic::generate_static_x25519_secret(); opt_result!( - sec_keychain.set_generic_password(service_name, &key_name, keychain_key.as_bytes()), + sec_keychain.set_generic_password(service_name, key_name, keychain_key.as_bytes()), "Write static x25519 failed: {}" ); diff --git a/src/util_keychainkey.rs b/src/util_keychainkey.rs index bc48808..57ac175 100644 --- a/src/util_keychainkey.rs +++ b/src/util_keychainkey.rs @@ -12,6 +12,25 @@ pub fn is_support_se() -> bool { unsafe { is_support_secure_enclave() } } + +pub fn decrypt_data(private_key_base64: &str, ephemeral_public_key_bytes: &[u8]) -> XResult> { + let ephemera_public_key_base64 = util::encode_base64(ephemeral_public_key_bytes); + let result = unsafe { + compute_secure_enclave_p256_ecdh( + SRString::from(private_key_base64), SRString::from(ephemera_public_key_base64.as_str()), + ) + }; + let result = result.as_str(); + if !result.starts_with("ok:SharedSecret:") { + return simple_error!("ECDH P256 in secure enclave failed: {}", result); + } + + let shared_secret_hex = result.chars().skip("ok:SharedSecret:".len()).collect::(); + let shared_secret_hex = shared_secret_hex.trim(); + + Ok(opt_result!(hex::decode(shared_secret_hex), "Decrypt shared secret hex: {}, failed: {}", shared_secret_hex)) +} + pub fn generate_se_p256_keypair() -> XResult<(String, String)> { if !is_support_se() { return simple_error!("Secure enclave is not supported."); @@ -22,7 +41,7 @@ pub fn generate_se_p256_keypair() -> XResult<(String, String)> { return simple_error!("Generate P256 in secure enclave failed: {}", result); } let public_key_and_private_key = result.chars().skip(3).collect::(); - let public_key_and_private_keys = public_key_and_private_key.split(",").collect::>(); + let public_key_and_private_keys = public_key_and_private_key.split(',').collect::>(); if public_key_and_private_keys.len() != 2 { return simple_error!("Generate P256 in secure enclave result is bad: {}", public_key_and_private_key); } diff --git a/swift-lib/src/lib.swift b/swift-lib/src/lib.swift index 9430a07..16fd5d0 100644 --- a/swift-lib/src/lib.swift +++ b/swift-lib/src/lib.swift @@ -57,7 +57,6 @@ func computeSecureEnclaveP256Ecdh(privateKeyDataRepresentation: SRString, epheme let sharedSecret = try p.sharedSecretFromKeyAgreement( with: ephemeralPublicKey) - print("Shared secret: \(sharedSecret)") return SRString("ok:\(sharedSecret.description)") } catch { diff --git a/swift/se.swift b/swift/se.swift deleted file mode 100644 index 13942da..0000000 --- a/swift/se.swift +++ /dev/null @@ -1,59 +0,0 @@ -// import Swift -// import Foundation -// import Security -import CryptoKit -import LocalAuthentication - -// reference: -// https://zenn.dev/iceman/scraps/380f69137c7ea2 -// https://www.andyibanez.com/posts/cryptokit-secure-enclave/ -@_cdecl("is_support_secure_enclave") -func isSupportSecureEnclave() -> Bool { - return SecureEnclave.isAvailable -} - -@_cdecl("print_greeting") -func printGreeting(modifier: UnsafePointer) { - print("Hello \(String(cString: modifier))World!") -} - -enum StringError: Error { - case base64error -} - -func generateKeyPair() throws { - var error: Unmanaged? = nil; - guard let accessCtrl = SecAccessControlCreateWithFlags( - nil, - kSecAttrAccessibleWhenUnlockedThisDeviceOnly, - [.privateKeyUsage, .biometryCurrentSet], - &error - ) else { - throw error!.takeRetainedValue() as Swift.Error - } - var privateKeyReference = try CryptoKit.SecureEnclave.P256.KeyAgreement.PrivateKey.init( - accessControl: accessCtrl - ); - var dataRepresentation = privateKeyReference.dataRepresentation; - print("Private key reference: \(privateKeyReference)"); - print("Private key reference - publicKey: \(privateKeyReference.publicKey)"); - print("Private key reference - dataRepresentation: \(privateKeyReference.dataRepresentation)"); - print("Private key reference - dataRepresentation: \(privateKeyReference.dataRepresentation.base64EncodedString())"); -} - -func computeEcdh() throws { - guard let dataRepresentation = Data(base64Encoded: "BIIB2DGCAdQwgfUMAnJrMYHuMAsMA2JpZAQElx-----" - ) else { - throw StringError.base64error - } - - let context = LAContext(); - let p = try SecureEnclave.P256.KeyAgreement.PrivateKey( - dataRepresentation: dataRepresentation, authenticationContext: context) - - let ephemeralSecretKey = P256.KeyAgreement.PrivateKey() - let sharedSecret = try ephemeralSecretKey.sharedSecretFromKeyAgreement(with: p.publicKey) - let sharedSecret2 = try p.sharedSecretFromKeyAgreement(with: ephemeralSecretKey.publicKey) - print("Shared secret: \(sharedSecret)") - print("Shared secret: \(sharedSecret2)") -}