feat: generate se keypair
This commit is contained in:
@@ -1,13 +1,16 @@
|
|||||||
use clap::Args;
|
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 security_framework::os::macos::keychain::SecKeychain;
|
||||||
|
|
||||||
|
use crate::{util_keychainkey, util_keychainstatic};
|
||||||
use crate::config::TinyEncryptConfigEnvelop;
|
use crate::config::TinyEncryptConfigEnvelop;
|
||||||
use crate::spec::TinyEncryptEnvelopType;
|
use crate::spec::TinyEncryptEnvelopType;
|
||||||
use crate::util_keychainstatic;
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct CmdKeychainKey {
|
pub struct CmdKeychainKey {
|
||||||
|
/// Secure Enclave
|
||||||
|
#[arg(long, short = 'S')]
|
||||||
|
pub secure_enclave: bool,
|
||||||
// /// Keychain name, or default
|
// /// Keychain name, or default
|
||||||
// #[arg(long, short = 'c')]
|
// #[arg(long, short = 'c')]
|
||||||
// pub keychain_name: Option<String>,
|
// pub keychain_name: Option<String>,
|
||||||
@@ -16,39 +19,70 @@ pub struct CmdKeychainKey {
|
|||||||
pub server_name: Option<String>,
|
pub server_name: Option<String>,
|
||||||
/// Key name
|
/// Key name
|
||||||
#[arg(long, short = 'n')]
|
#[arg(long, short = 'n')]
|
||||||
pub key_name: String,
|
pub key_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
const DEFAULT_SERVICE_NAME: &str = "tiny-encrypt";
|
const DEFAULT_SERVICE_NAME: &str = "tiny-encrypt";
|
||||||
|
|
||||||
pub fn keychain_key(cmd_keychain_key: CmdKeychainKey) -> XResult<()> {
|
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 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 sec_keychain = opt_result!(SecKeychain::default(), "Get keychain failed: {}");
|
||||||
if sec_keychain.find_generic_password(service_name, &cmd_keychain_key.key_name).is_ok() {
|
let key_name = opt_value_result!(&cmd_keychain_key.key_name, "Key name is required.");
|
||||||
return simple_error!("Static x25519 exists: {}.{}", service_name, &cmd_keychain_key.key_name);
|
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();
|
let (keychain_key, public_key) = util_keychainstatic::generate_static_x25519_secret();
|
||||||
opt_result!(
|
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: {}"
|
"Write static x25519 failed: {}"
|
||||||
);
|
);
|
||||||
|
|
||||||
let public_key_hex = hex::encode(public_key.as_bytes());
|
let public_key_hex = hex::encode(public_key.as_bytes());
|
||||||
debugging!("Keychain key : {}", keychain_key);
|
debugging!("Keychain key : {}", keychain_key);
|
||||||
success!("Keychain name: {}", &cmd_keychain_key.key_name);
|
success!("Keychain name: {}", &key_name);
|
||||||
success!("Public key : {}", &public_key_hex);
|
success!("Public key : {}", &public_key_hex);
|
||||||
|
|
||||||
let config_envelop = TinyEncryptConfigEnvelop {
|
let config_envelop = TinyEncryptConfigEnvelop {
|
||||||
r#type: TinyEncryptEnvelopType::StaticX25519,
|
r#type: TinyEncryptEnvelopType::StaticX25519,
|
||||||
sid: Some(cmd_keychain_key.key_name.clone()),
|
sid: Some(key_name.clone()),
|
||||||
kid: format!("keychain:{}", &public_key_hex),
|
kid: format!("keychain:{}", &public_key_hex),
|
||||||
desc: Some("Keychain static".to_string()),
|
desc: Some("Keychain static".to_string()),
|
||||||
args: Some(vec![
|
args: Some(vec![
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
service_name.to_string(),
|
service_name.to_string(),
|
||||||
cmd_keychain_key.key_name.clone(),
|
key_name.clone(),
|
||||||
]),
|
]),
|
||||||
public_part: public_key_hex,
|
public_part: public_key_hex,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,34 @@
|
|||||||
|
use rust_util::{opt_result, simple_error, XResult};
|
||||||
use swift_rs::{Bool, SRString};
|
use swift_rs::{Bool, SRString};
|
||||||
use swift_rs::swift;
|
use swift_rs::swift;
|
||||||
|
|
||||||
|
use crate::util;
|
||||||
|
|
||||||
swift!(fn is_support_secure_enclave() -> Bool);
|
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 {
|
pub fn is_support_se() -> bool {
|
||||||
unsafe {
|
|
||||||
print_greeting(SRString::from("hatter"));
|
|
||||||
}
|
|
||||||
unsafe { is_support_secure_enclave() }
|
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::<String>();
|
||||||
|
let public_key_and_private_keys = public_key_and_private_key.split(",").collect::<Vec<_>>();
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,30 +8,9 @@ import LocalAuthentication
|
|||||||
// https://www.andyibanez.com/posts/cryptokit-secure-enclave/
|
// https://www.andyibanez.com/posts/cryptokit-secure-enclave/
|
||||||
@_cdecl("is_support_secure_enclave")
|
@_cdecl("is_support_secure_enclave")
|
||||||
func isSupportSecureEnclave() -> Bool {
|
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
|
return SecureEnclave.isAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO delete print_greeting
|
|
||||||
@_cdecl("print_greeting")
|
|
||||||
func printGreeting(name: SRString) {
|
|
||||||
print("Hello \(name.toString())!")
|
|
||||||
}
|
|
||||||
|
|
||||||
@_cdecl("generate_secure_enclave_p256_keypair")
|
@_cdecl("generate_secure_enclave_p256_keypair")
|
||||||
func generateSecureEnclaveP256KeyPair() -> SRString {
|
func generateSecureEnclaveP256KeyPair() -> SRString {
|
||||||
var error: Unmanaged<CFError>? = nil;
|
var error: Unmanaged<CFError>? = nil;
|
||||||
@@ -47,17 +26,15 @@ func generateSecureEnclaveP256KeyPair() -> SRString {
|
|||||||
let privateKeyReference = try CryptoKit.SecureEnclave.P256.KeyAgreement.PrivateKey.init(
|
let privateKeyReference = try CryptoKit.SecureEnclave.P256.KeyAgreement.PrivateKey.init(
|
||||||
accessControl: accessCtrl
|
accessControl: accessCtrl
|
||||||
);
|
);
|
||||||
let dataRepresentation = privateKeyReference.dataRepresentation;
|
let publicKeyBase64 = privateKeyReference.publicKey.x963Representation.base64EncodedString()
|
||||||
print("Private key reference: \(privateKeyReference)");
|
let dataRepresentationBase64 = privateKeyReference.dataRepresentation.base64EncodedString()
|
||||||
print("Private key reference - publicKey: \(privateKeyReference.publicKey)");
|
return SRString("ok:\(publicKeyBase64),\(dataRepresentationBase64)")
|
||||||
print("Private key reference - dataRepresentation: \(privateKeyReference.dataRepresentation)");
|
|
||||||
print("Private key reference - dataRepresentation: \(privateKeyReference.dataRepresentation.base64EncodedString())");
|
|
||||||
return SRString("")
|
|
||||||
} catch {
|
} catch {
|
||||||
return SRString("err:\(error)")
|
return SRString("err:\(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@_cdecl("compute_secure_enclave_p256_ecdh")
|
||||||
func computeSecureEnclaveP256Ecdh(privateKeyDataRepresentation: SRString, ephemeraPublicKey: SRString) -> SRString {
|
func computeSecureEnclaveP256Ecdh(privateKeyDataRepresentation: SRString, ephemeraPublicKey: SRString) -> SRString {
|
||||||
guard let dataRepresentation = Data(
|
guard let dataRepresentation = Data(
|
||||||
base64Encoded: privateKeyDataRepresentation.toString()
|
base64Encoded: privateKeyDataRepresentation.toString()
|
||||||
@@ -87,96 +64,3 @@ func computeSecureEnclaveP256Ecdh(privateKeyDataRepresentation: SRString, epheme
|
|||||||
return SRString("err:\(error)")
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user