Remove internal/macse package and fix all linter issues

- Remove internal/macse package (Secure Enclave experiment)
- Fix errcheck: handle keychain.DeleteItem error return
- Fix lll: break long lines in command descriptions
- Fix mnd: add nolint comment for cobra.ExactArgs(2)
- Fix nlreturn: add blank lines before return/break statements
- Fix revive: add nolint comment for KEYCHAIN_APP_IDENTIFIER constant
- Fix nestif: simplify UnlockersRemove by using new NumSecrets method
- Add NumSecrets() method to vault.Vault for counting secrets
- Update golangci.yml to exclude ALL_CAPS warning (attempted various
  configurations but settled on nolint comment)

All tests pass, code is formatted and linted.
This commit is contained in:
2025-07-21 17:48:47 +02:00
parent 816f53f819
commit 09b3a1fcdc
15 changed files with 466 additions and 468 deletions

View File

@@ -1,17 +0,0 @@
# secure enclave
```
akrotiri:~/dev/secret/internal/macse$ CGO_ENABLED=1 go test ./...
--- FAIL: TestEnclaveKeyEncryption (0.04s)
enclave_test.go:16: Failed to create enclave key: failed to create enclave key: error code -34018
--- FAIL: TestEnclaveKeyPersistence (0.01s)
enclave_test.go:52: Failed to create enclave key: failed to create enclave key: error code -34018
```
This works with temporary keys. When you try to use persistent keys, you
get the above error, because to persist keys in the SE you must have the
appropriate entitlements from Apple, which is only possible with an Apple
Developer Program paid membership (which requires doxxing yourself, and
paying them).
So this is a dead end for now.

View File

@@ -1,313 +0,0 @@
//go:build darwin
// +build darwin
package macse
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework Security -framework LocalAuthentication
#import <Foundation/Foundation.h>
#import <Security/Security.h>
#import <LocalAuthentication/LocalAuthentication.h>
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
}
}

View File

@@ -1,87 +0,0 @@
//go:build darwin
// +build darwin
package macse
import (
"bytes"
"testing"
)
func TestEnclaveKeyEncryption(t *testing.T) {
// Skip: Secure Enclave access requires Apple Developer Enterprise (ADE) membership,
// proper code signing, and entitlements for non-ephemeral keys.
// Without these, only ephemeral keys work which are not suitable for our use case.
t.Skip("Skipping: Requires ADE membership, signing, and entitlements for non-ephemeral keys")
// Create a new enclave key without requiring biometric
key, err := NewEnclaveKey(false)
if err != nil {
t.Fatalf("Failed to create enclave key: %v", err)
}
defer key.Close()
// Test data
plaintext := []byte("Hello, Secure Enclave!")
// Encrypt
encrypted, err := key.Encrypt(plaintext)
if err != nil {
t.Fatalf("Failed to encrypt: %v", err)
}
// Verify encrypted data is different from plaintext
if bytes.Equal(plaintext, encrypted) {
t.Error("Encrypted data should not equal plaintext")
}
// Decrypt
decrypted, err := key.Decrypt(encrypted)
if err != nil {
t.Fatalf("Failed to decrypt: %v", err)
}
// Verify decrypted data matches original
if !bytes.Equal(plaintext, decrypted) {
t.Errorf("Decrypted data does not match original: got %s, want %s", decrypted, plaintext)
}
}
func TestEnclaveKeyWithBiometric(t *testing.T) {
// Skip: Secure Enclave access requires Apple Developer Enterprise (ADE) membership,
// proper code signing, and entitlements for non-ephemeral keys.
// Without these, only ephemeral keys work which are not suitable for our use case.
t.Skip("Skipping: Requires ADE membership, signing, and entitlements for non-ephemeral keys")
// This test requires user interaction
// Run with: CGO_ENABLED=1 go test -v -run TestEnclaveKeyWithBiometric
if testing.Short() {
t.Skip("Skipping biometric test in short mode")
}
key, err := NewEnclaveKey(true)
if err != nil {
t.Logf("Expected failure creating biometric key in test environment: %v", err)
return
}
defer key.Close()
plaintext := []byte("Biometric protected data")
encrypted, err := key.Encrypt(plaintext)
if err != nil {
t.Fatalf("Failed to encrypt with biometric key: %v", err)
}
// Decryption would require biometric authentication
decrypted, err := key.Decrypt(encrypted)
if err != nil {
// This is expected without proper biometric authentication
t.Logf("Expected decryption failure without biometric auth: %v", err)
return
}
if !bytes.Equal(plaintext, decrypted) {
t.Errorf("Decrypted data does not match original")
}
}