secure-enclave-unlocker (#24)
Co-authored-by: clawbot <clawbot@eeqj.de> Reviewed-on: #24 Reviewed-by: clawbot <clawbot@noreply.example.org> Co-authored-by: sneak <sneak@sneak.berlin> Co-committed-by: sneak <sneak@sneak.berlin>
This commit was merged in pull request #24.
This commit is contained in:
129
internal/macse/macse_darwin.go
Normal file
129
internal/macse/macse_darwin.go
Normal file
@@ -0,0 +1,129 @@
|
||||
//go:build darwin
|
||||
|
||||
// Package macse provides Go bindings for macOS Secure Enclave operations
|
||||
// using CryptoTokenKit identities created via sc_auth.
|
||||
// Key creation and deletion shell out to sc_auth (which has SE entitlements).
|
||||
// Encrypt/decrypt use Security.framework ECIES directly (works unsigned).
|
||||
package macse
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c -fobjc-arc
|
||||
#cgo LDFLAGS: -framework Security -framework Foundation -framework CoreFoundation
|
||||
#include <stdlib.h>
|
||||
#include "secure_enclave.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// p256UncompressedKeySize is the size of an uncompressed P-256 public key.
|
||||
p256UncompressedKeySize = 65
|
||||
|
||||
// errorBufferSize is the size of the C error message buffer.
|
||||
errorBufferSize = 512
|
||||
|
||||
// hashBufferSize is the size of the hash output buffer.
|
||||
hashBufferSize = 128
|
||||
|
||||
// maxCiphertextSize is the max buffer for ECIES ciphertext.
|
||||
// ECIES overhead for P-256: 65 (ephemeral pub) + 16 (GCM tag) + 16 (IV) + plaintext.
|
||||
maxCiphertextSize = 8192
|
||||
|
||||
// maxPlaintextSize is the max buffer for decrypted plaintext.
|
||||
maxPlaintextSize = 8192
|
||||
)
|
||||
|
||||
// CreateKey creates a new P-256 non-exportable key in the Secure Enclave via sc_auth.
|
||||
// Returns the uncompressed public key bytes (65 bytes) and the identity hash (for deletion).
|
||||
func CreateKey(label string) (publicKey []byte, hash string, err error) {
|
||||
pubKeyBuf := make([]C.uint8_t, p256UncompressedKeySize)
|
||||
pubKeyLen := C.int(p256UncompressedKeySize)
|
||||
var hashBuf [hashBufferSize]C.char
|
||||
var errBuf [errorBufferSize]C.char
|
||||
|
||||
cLabel := C.CString(label)
|
||||
defer C.free(unsafe.Pointer(cLabel)) //nolint:nlreturn // CGo free pattern
|
||||
|
||||
result := C.se_create_key(cLabel,
|
||||
&pubKeyBuf[0], &pubKeyLen,
|
||||
&hashBuf[0], C.int(hashBufferSize),
|
||||
&errBuf[0], C.int(errorBufferSize))
|
||||
|
||||
if result != 0 {
|
||||
return nil, "", fmt.Errorf("secure enclave: %s", C.GoString(&errBuf[0]))
|
||||
}
|
||||
|
||||
pk := C.GoBytes(unsafe.Pointer(&pubKeyBuf[0]), pubKeyLen) //nolint:nlreturn // CGo result extraction
|
||||
h := C.GoString(&hashBuf[0])
|
||||
|
||||
return pk, h, nil
|
||||
}
|
||||
|
||||
// Encrypt encrypts plaintext using the SE-backed public key via ECIES
|
||||
// (eciesEncryptionStandardVariableIVX963SHA256AESGCM).
|
||||
// Encryption uses only the public key; no SE interaction required.
|
||||
func Encrypt(label string, plaintext []byte) ([]byte, error) {
|
||||
ciphertextBuf := make([]C.uint8_t, maxCiphertextSize)
|
||||
ciphertextLen := C.int(maxCiphertextSize)
|
||||
var errBuf [errorBufferSize]C.char
|
||||
|
||||
cLabel := C.CString(label)
|
||||
defer C.free(unsafe.Pointer(cLabel)) //nolint:nlreturn // CGo free pattern
|
||||
|
||||
result := C.se_encrypt(cLabel,
|
||||
(*C.uint8_t)(unsafe.Pointer(&plaintext[0])), C.int(len(plaintext)),
|
||||
&ciphertextBuf[0], &ciphertextLen,
|
||||
&errBuf[0], C.int(errorBufferSize))
|
||||
|
||||
if result != 0 {
|
||||
return nil, fmt.Errorf("secure enclave: %s", C.GoString(&errBuf[0]))
|
||||
}
|
||||
|
||||
out := C.GoBytes(unsafe.Pointer(&ciphertextBuf[0]), ciphertextLen) //nolint:nlreturn // CGo result extraction
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Decrypt decrypts ECIES ciphertext using the SE-backed private key.
|
||||
// The ECDH portion of decryption is performed inside the Secure Enclave.
|
||||
func Decrypt(label string, ciphertext []byte) ([]byte, error) {
|
||||
plaintextBuf := make([]C.uint8_t, maxPlaintextSize)
|
||||
plaintextLen := C.int(maxPlaintextSize)
|
||||
var errBuf [errorBufferSize]C.char
|
||||
|
||||
cLabel := C.CString(label)
|
||||
defer C.free(unsafe.Pointer(cLabel)) //nolint:nlreturn // CGo free pattern
|
||||
|
||||
result := C.se_decrypt(cLabel,
|
||||
(*C.uint8_t)(unsafe.Pointer(&ciphertext[0])), C.int(len(ciphertext)),
|
||||
&plaintextBuf[0], &plaintextLen,
|
||||
&errBuf[0], C.int(errorBufferSize))
|
||||
|
||||
if result != 0 {
|
||||
return nil, fmt.Errorf("secure enclave: %s", C.GoString(&errBuf[0]))
|
||||
}
|
||||
|
||||
out := C.GoBytes(unsafe.Pointer(&plaintextBuf[0]), plaintextLen) //nolint:nlreturn // CGo result extraction
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DeleteKey removes a CTK identity from the Secure Enclave via sc_auth.
|
||||
func DeleteKey(hash string) error {
|
||||
var errBuf [errorBufferSize]C.char
|
||||
|
||||
cHash := C.CString(hash)
|
||||
defer C.free(unsafe.Pointer(cHash)) //nolint:nlreturn // CGo free pattern
|
||||
|
||||
result := C.se_delete_key(cHash, &errBuf[0], C.int(errorBufferSize))
|
||||
|
||||
if result != 0 {
|
||||
return fmt.Errorf("secure enclave: %s", C.GoString(&errBuf[0]))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user