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>
130 lines
4.1 KiB
Go
130 lines
4.1 KiB
Go
//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
|
|
}
|