diff --git a/src/cmd_initkeychainkey.rs b/src/cmd_initkeychainkey.rs index f277709..0f68068 100644 --- a/src/cmd_initkeychainkey.rs +++ b/src/cmd_initkeychainkey.rs @@ -1,13 +1,16 @@ use clap::Args; -use rust_util::{debugging, information, opt_result, simple_error, success, XResult}; +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; -use crate::util_keychainstatic; #[derive(Debug, Args)] pub struct CmdKeychainKey { + /// Secure Enclave + #[arg(long, short = 'S')] + pub secure_enclave: bool, // /// Keychain name, or default // #[arg(long, short = 'c')] // pub keychain_name: Option, @@ -16,39 +19,70 @@ pub struct CmdKeychainKey { pub server_name: Option, /// Key name #[arg(long, short = 'n')] - pub key_name: String, + pub key_name: Option, } #[allow(dead_code)] 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) + } else { + keychain_key_static(cmd_keychain_key) + } +} + +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 config_envelop = TinyEncryptConfigEnvelop { + r#type: TinyEncryptEnvelopType::KeyP256, + sid: cmd_keychain_key.key_name.clone(), + kid: format!("keychain:{}", &public_key_hex), + desc: Some("Keychain Secure Enclave".to_string()), + args: Some(vec![ + private_key_base64 + ]), + public_part: public_key_hex, + }; + + information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap()); + + Ok(()) +} + +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: {}"); - if sec_keychain.find_generic_password(service_name, &cmd_keychain_key.key_name).is_ok() { - return simple_error!("Static x25519 exists: {}.{}", service_name, &cmd_keychain_key.key_name); + 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() { + 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, &cmd_keychain_key.key_name, keychain_key.as_bytes()), + sec_keychain.set_generic_password(service_name, &key_name, keychain_key.as_bytes()), "Write static x25519 failed: {}" ); let public_key_hex = hex::encode(public_key.as_bytes()); debugging!("Keychain key : {}", keychain_key); - success!("Keychain name: {}", &cmd_keychain_key.key_name); + success!("Keychain name: {}", &key_name); success!("Public key : {}", &public_key_hex); let config_envelop = TinyEncryptConfigEnvelop { r#type: TinyEncryptEnvelopType::StaticX25519, - sid: Some(cmd_keychain_key.key_name.clone()), + sid: Some(key_name.clone()), kid: format!("keychain:{}", &public_key_hex), desc: Some("Keychain static".to_string()), args: Some(vec![ "".to_string(), service_name.to_string(), - cmd_keychain_key.key_name.clone(), + key_name.clone(), ]), public_part: public_key_hex, }; diff --git a/src/util_keychainkey.rs b/src/util_keychainkey.rs index 44f145c..bc48808 100644 --- a/src/util_keychainkey.rs +++ b/src/util_keychainkey.rs @@ -1,12 +1,34 @@ +use rust_util::{opt_result, simple_error, XResult}; use swift_rs::{Bool, SRString}; use swift_rs::swift; +use crate::util; + swift!(fn is_support_secure_enclave() -> Bool); -swift!(fn print_greeting(name: SRString) -> Bool); +swift!(fn generate_secure_enclave_p256_keypair() -> SRString); +swift!(fn compute_secure_enclave_p256_ecdh(private_key_base64: SRString, ephemera_public_key_base64: SRString) -> SRString); pub fn is_support_se() -> bool { - unsafe { - print_greeting(SRString::from("hatter")); - } unsafe { is_support_secure_enclave() } } + +pub fn generate_se_p256_keypair() -> XResult<(String, String)> { + if !is_support_se() { + return simple_error!("Secure enclave is not supported."); + } + let result = unsafe { generate_secure_enclave_p256_keypair() }; + let result = result.as_str(); + if !result.starts_with("ok:") { + 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::>(); + if public_key_and_private_keys.len() != 2 { + return simple_error!("Generate P256 in secure enclave result is bad: {}", public_key_and_private_key); + } + let public_key = hex::encode( + opt_result!(util::decode_base64(public_key_and_private_keys[0]), "Public key is not base64 encoded: {}")); + let private_key = public_key_and_private_keys[1].to_string(); + + Ok((public_key, private_key)) +} diff --git a/swift-lib/src/lib.swift b/swift-lib/src/lib.swift index f82f111..9430a07 100644 --- a/swift-lib/src/lib.swift +++ b/swift-lib/src/lib.swift @@ -8,30 +8,9 @@ import LocalAuthentication // https://www.andyibanez.com/posts/cryptokit-secure-enclave/ @_cdecl("is_support_secure_enclave") func isSupportSecureEnclave() -> Bool { - // TODO pending delete - let epub = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE76jmqKrSs8tIVcvYYLpCA2za9GG7VxLdaI8FqynT+G65QgakCjT/P2ey7plz4KEl6ffORfZtZXO+lq2qQaaBHw==" - - guard let ephemeralPublicKeyRepresentation = Data( - base64Encoded: epub - ) else { - print("err:ephemeral public key base64 decode failed") - return false - } - do { - let a = try CryptoKit.P256.KeyAgreement.PublicKey.init(derRepresentation: ephemeralPublicKeyRepresentation) - print("\(a)") - } catch { - print("error: \(error)") - } return SecureEnclave.isAvailable } -// TODO delete print_greeting -@_cdecl("print_greeting") -func printGreeting(name: SRString) { - print("Hello \(name.toString())!") -} - @_cdecl("generate_secure_enclave_p256_keypair") func generateSecureEnclaveP256KeyPair() -> SRString { var error: Unmanaged? = nil; @@ -47,17 +26,15 @@ func generateSecureEnclaveP256KeyPair() -> SRString { let privateKeyReference = try CryptoKit.SecureEnclave.P256.KeyAgreement.PrivateKey.init( accessControl: accessCtrl ); - let 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())"); - return SRString("") + let publicKeyBase64 = privateKeyReference.publicKey.x963Representation.base64EncodedString() + let dataRepresentationBase64 = privateKeyReference.dataRepresentation.base64EncodedString() + return SRString("ok:\(publicKeyBase64),\(dataRepresentationBase64)") } catch { return SRString("err:\(error)") } } +@_cdecl("compute_secure_enclave_p256_ecdh") func computeSecureEnclaveP256Ecdh(privateKeyDataRepresentation: SRString, ephemeraPublicKey: SRString) -> SRString { guard let dataRepresentation = Data( base64Encoded: privateKeyDataRepresentation.toString() @@ -87,96 +64,3 @@ func computeSecureEnclaveP256Ecdh(privateKeyDataRepresentation: SRString, epheme return SRString("err:\(error)") } } - - -@_cdecl("get_file_thumbnail_base64") -func getFileThumbnailBase64(path: SRString) -> SRString { - let path = path.toString(); - - let image = NSWorkspace.shared.icon(forFile: path) - let bitmap = NSBitmapImageRep(data: image.tiffRepresentation!)!.representation(using: .png, properties: [:])! - - return SRString(bitmap.base64EncodedString()) -} - -class Volume: NSObject { - var name: SRString - var path: SRString - var total_capacity: Int - var available_capacity: Int - var is_removable: Bool - var is_ejectable: Bool - var is_root_filesystem: Bool - - public init(name: String, path: String, total_capacity: Int, available_capacity: Int, is_removable: Bool, is_ejectable: Bool, is_root_filesystem: Bool) { - self.name = SRString(name); - self.path = SRString(path); - self.total_capacity = total_capacity - self.available_capacity = available_capacity - self.is_removable = is_removable - self.is_ejectable = is_ejectable - self.is_root_filesystem = is_root_filesystem - } -} - -@_cdecl("get_mounts") -func getMounts() -> SRObjectArray { - let keys: [URLResourceKey] = [ - .volumeNameKey, - .volumeIsRemovableKey, - .volumeIsEjectableKey, - .volumeTotalCapacityKey, - .volumeAvailableCapacityKey, - .volumeIsRootFileSystemKey, - ] - - let paths = autoreleasepool { - FileManager().mountedVolumeURLs(includingResourceValuesForKeys: keys, options: []) - } - - var validMounts: [Volume] = [] - - if let urls = paths { - autoreleasepool { - for url in urls { - let components = url.pathComponents - if components.count == 1 || components.count > 1 - && components[1] == "Volumes" - { - let metadata = try? url.promisedItemResourceValues(forKeys: Set(keys)) - - let volume = Volume( - name: metadata?.volumeName ?? "", - path: url.path, - total_capacity: metadata?.volumeTotalCapacity ?? 0, - available_capacity: metadata?.volumeAvailableCapacity ?? 0, - is_removable: metadata?.volumeIsRemovable ?? false, - is_ejectable: metadata?.volumeIsEjectable ?? false, - is_root_filesystem: metadata?.volumeIsRootFileSystem ?? false - ) - - - validMounts.append(volume) - } - } - } - } - - return SRObjectArray(validMounts) -} - -class Test: NSObject { - var null: Bool - - public init(_ null: Bool) - { - self.null = null; - } -} - -@_cdecl("return_nullable") -func returnNullable(null: Bool) -> Test? { - if (null == true) { return nil } - - return Test(null) -}