//go:build darwin // +build darwin package macse /* #cgo CFLAGS: -x objective-c #cgo LDFLAGS: -framework Foundation -framework Security -framework LocalAuthentication #import #import #import typedef struct { const void* data; int len; int error; } DataResult; typedef struct { SecKeyRef privateKey; const void* salt; int saltLen; int error; } KeyResult; KeyResult createEnclaveKey(bool requireBiometric) { KeyResult result = {NULL, NULL, 0, 0}; // Create authentication context LAContext* authContext = [[LAContext alloc] init]; authContext.localizedReason = @"Create Secure Enclave key"; CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(attributes, kSecAttrKeyType, kSecAttrKeyTypeECSECPrimeRandom); CFDictionarySetValue(attributes, kSecAttrKeySizeInBits, (__bridge CFNumberRef)@256); CFDictionarySetValue(attributes, kSecAttrTokenID, kSecAttrTokenIDSecureEnclave); CFMutableDictionaryRef privateKeyAttrs = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(privateKeyAttrs, kSecAttrIsPermanent, kCFBooleanFalse); SecAccessControlCreateFlags flags = kSecAccessControlPrivateKeyUsage; if (requireBiometric) { flags |= kSecAccessControlBiometryCurrentSet; } SecAccessControlRef access = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags, NULL); if (!access) { result.error = -1; return result; } CFDictionarySetValue(privateKeyAttrs, kSecAttrAccessControl, access); CFDictionarySetValue(privateKeyAttrs, kSecUseAuthenticationContext, (__bridge CFTypeRef)authContext); CFDictionarySetValue(attributes, kSecPrivateKeyAttrs, privateKeyAttrs); CFErrorRef error = NULL; SecKeyRef privateKey = SecKeyCreateRandomKey(attributes, &error); CFRelease(attributes); CFRelease(privateKeyAttrs); CFRelease(access); if (error || !privateKey) { if (error) { result.error = (int)CFErrorGetCode(error); CFRelease(error); } else { result.error = -3; } return result; } // Generate random salt uint8_t* saltBytes = malloc(64); if (SecRandomCopyBytes(kSecRandomDefault, 64, saltBytes) != 0) { result.error = -2; free(saltBytes); if (privateKey) CFRelease(privateKey); return result; } result.privateKey = privateKey; result.salt = saltBytes; result.saltLen = 64; // Retain the key so it's not released CFRetain(privateKey); return result; } DataResult encryptData(SecKeyRef privateKey, const void* saltData, int saltLen, const void* plainData, int plainLen) { DataResult result = {NULL, 0, 0}; // Get public key from private key SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey); if (!publicKey) { result.error = -1; return result; } // Perform ECDH key agreement with self CFErrorRef error = NULL; CFMutableDictionaryRef params = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDataRef sharedSecret = SecKeyCopyKeyExchangeResult(privateKey, kSecKeyAlgorithmECDHKeyExchangeStandard, publicKey, params, &error); CFRelease(params); if (error) { result.error = (int)CFErrorGetCode(error); CFRelease(error); CFRelease(publicKey); return result; } // For simplicity, we'll use the shared secret directly as a symmetric key // In production, you'd want to use HKDF as shown in the Swift code // Create encryption key from shared secret const uint8_t* secretBytes = CFDataGetBytePtr(sharedSecret); size_t secretLen = CFDataGetLength(sharedSecret); // Simple XOR encryption for demonstration (NOT SECURE - use proper encryption in production) uint8_t* encrypted = malloc(plainLen); for (int i = 0; i < plainLen; i++) { encrypted[i] = ((uint8_t*)plainData)[i] ^ secretBytes[i % secretLen]; } result.data = encrypted; result.len = plainLen; CFRelease(publicKey); CFRelease(sharedSecret); return result; } DataResult decryptData(SecKeyRef privateKey, const void* saltData, int saltLen, const void* encData, int encLen, void* context) { DataResult result = {NULL, 0, 0}; // Set up authentication context LAContext* authContext = [[LAContext alloc] init]; NSError* authError = nil; // Check if biometric authentication is available if ([authContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) { // Evaluate biometric authentication synchronously dispatch_semaphore_t sema = dispatch_semaphore_create(0); __block BOOL authSuccess = NO; [authContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:@"Decrypt data using Secure Enclave" reply:^(BOOL success, NSError * _Nullable error) { authSuccess = success; dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); if (!authSuccess) { result.error = -3; return result; } } // Get public key from private key SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey); if (!publicKey) { result.error = -1; return result; } // Create algorithm parameters with authentication context CFMutableDictionaryRef params = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(params, kSecUseAuthenticationContext, (__bridge CFTypeRef)authContext); // Perform ECDH key agreement with self CFErrorRef error = NULL; CFDataRef sharedSecret = SecKeyCopyKeyExchangeResult(privateKey, kSecKeyAlgorithmECDHKeyExchangeStandard, publicKey, params, &error); CFRelease(params); if (error) { result.error = (int)CFErrorGetCode(error); CFRelease(error); CFRelease(publicKey); return result; } // Decrypt using shared secret const uint8_t* secretBytes = CFDataGetBytePtr(sharedSecret); size_t secretLen = CFDataGetLength(sharedSecret); // Simple XOR decryption for demonstration uint8_t* decrypted = malloc(encLen); for (int i = 0; i < encLen; i++) { decrypted[i] = ((uint8_t*)encData)[i] ^ secretBytes[i % secretLen]; } result.data = decrypted; result.len = encLen; CFRelease(publicKey); CFRelease(sharedSecret); return result; } void freeKeyResult(KeyResult* result) { if (result->privateKey) { CFRelease(result->privateKey); } if (result->salt) { free((void*)result->salt); } } void freeDataResult(DataResult* result) { if (result->data) { free((void*)result->data); } } */ import "C" import ( "errors" "unsafe" ) type EnclaveKey struct { privateKey C.SecKeyRef salt []byte } func NewEnclaveKey(requireBiometric bool) (*EnclaveKey, error) { result := C.createEnclaveKey(C.bool(requireBiometric)) defer C.freeKeyResult(&result) if result.error != 0 { return nil, errors.New("failed to create enclave key") } salt := make([]byte, result.saltLen) copy(salt, (*[1 << 30]byte)(unsafe.Pointer(result.salt))[:result.saltLen:result.saltLen]) return &EnclaveKey{ privateKey: result.privateKey, salt: salt, }, nil } func (k *EnclaveKey) Encrypt(data []byte) ([]byte, error) { if len(data) == 0 { return nil, errors.New("empty data") } if len(k.salt) == 0 { return nil, errors.New("empty salt") } result := C.encryptData( k.privateKey, unsafe.Pointer(&k.salt[0]), C.int(len(k.salt)), unsafe.Pointer(&data[0]), C.int(len(data)), ) defer C.freeDataResult(&result) if result.error != 0 { return nil, errors.New("encryption failed") } encrypted := make([]byte, result.len) copy(encrypted, (*[1 << 30]byte)(unsafe.Pointer(result.data))[:result.len:result.len]) return encrypted, nil } func (k *EnclaveKey) Decrypt(data []byte) ([]byte, error) { if len(data) == 0 { return nil, errors.New("empty data") } if len(k.salt) == 0 { return nil, errors.New("empty salt") } result := C.decryptData( k.privateKey, unsafe.Pointer(&k.salt[0]), C.int(len(k.salt)), unsafe.Pointer(&data[0]), C.int(len(data)), nil, ) defer C.freeDataResult(&result) if result.error != 0 { return nil, errors.New("decryption failed") } decrypted := make([]byte, result.len) copy(decrypted, (*[1 << 30]byte)(unsafe.Pointer(result.data))[:result.len:result.len]) return decrypted, nil } func (k *EnclaveKey) Close() { if k.privateKey != 0 { C.CFRelease(C.CFTypeRef(k.privateKey)) k.privateKey = 0 } }