//go:build darwin #import #import #include "secure_enclave.h" #include // snprintf_error writes an error message string to the output buffer. static void snprintf_error(char *error_out, int error_out_len, NSString *msg) { if (error_out && error_out_len > 0) { snprintf(error_out, error_out_len, "%s", msg.UTF8String); } } // lookup_ctk_identity finds a CTK identity by label and returns the private key. static SecKeyRef lookup_ctk_private_key(const char *label, char *error_out, int error_out_len) { NSDictionary *query = @{ (id)kSecClass: (id)kSecClassIdentity, (id)kSecAttrLabel: [NSString stringWithUTF8String:label], (id)kSecMatchLimit: (id)kSecMatchLimitOne, (id)kSecReturnRef: @YES, }; SecIdentityRef identity = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&identity); if (status != errSecSuccess || !identity) { NSString *msg = [NSString stringWithFormat:@"CTK identity '%s' not found: OSStatus %d", label, (int)status]; snprintf_error(error_out, error_out_len, msg); return NULL; } SecKeyRef privateKey = NULL; status = SecIdentityCopyPrivateKey(identity, &privateKey); CFRelease(identity); if (status != errSecSuccess || !privateKey) { NSString *msg = [NSString stringWithFormat: @"failed to get private key from CTK identity '%s': OSStatus %d", label, (int)status]; snprintf_error(error_out, error_out_len, msg); return NULL; } return privateKey; } int se_create_key(const char *label, uint8_t *pub_key_out, int *pub_key_len, char *hash_out, int hash_out_len, char *error_out, int error_out_len) { @autoreleasepool { NSString *labelStr = [NSString stringWithUTF8String:label]; // Shell out to sc_auth (which has SE entitlements) to create the key NSTask *task = [[NSTask alloc] init]; task.executableURL = [NSURL fileURLWithPath:@"/usr/sbin/sc_auth"]; task.arguments = @[ @"create-ctk-identity", @"-k", @"p-256-ne", @"-t", @"none", @"-l", labelStr, ]; NSPipe *stderrPipe = [NSPipe pipe]; task.standardOutput = [NSPipe pipe]; task.standardError = stderrPipe; NSError *nsError = nil; if (![task launchAndReturnError:&nsError]) { NSString *msg = [NSString stringWithFormat:@"failed to launch sc_auth: %@", nsError.localizedDescription]; snprintf_error(error_out, error_out_len, msg); return -1; } [task waitUntilExit]; if (task.terminationStatus != 0) { NSData *stderrData = [stderrPipe.fileHandleForReading readDataToEndOfFile]; NSString *stderrStr = [[NSString alloc] initWithData:stderrData encoding:NSUTF8StringEncoding]; NSString *msg = [NSString stringWithFormat:@"sc_auth failed: %@", stderrStr ?: @"unknown error"]; snprintf_error(error_out, error_out_len, msg); return -1; } // Retrieve the public key from the created identity SecKeyRef privateKey = lookup_ctk_private_key(label, error_out, error_out_len); if (!privateKey) { return -1; } SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey); CFRelease(privateKey); if (!publicKey) { snprintf_error(error_out, error_out_len, @"failed to get public key"); return -1; } CFErrorRef cfError = NULL; CFDataRef pubKeyData = SecKeyCopyExternalRepresentation(publicKey, &cfError); CFRelease(publicKey); if (!pubKeyData) { NSError *err = (__bridge_transfer NSError *)cfError; NSString *msg = [NSString stringWithFormat:@"failed to export public key: %@", err.localizedDescription]; snprintf_error(error_out, error_out_len, msg); return -1; } const UInt8 *bytes = CFDataGetBytePtr(pubKeyData); CFIndex length = CFDataGetLength(pubKeyData); if (length > *pub_key_len) { CFRelease(pubKeyData); snprintf_error(error_out, error_out_len, @"public key buffer too small"); return -1; } memcpy(pub_key_out, bytes, length); *pub_key_len = (int)length; CFRelease(pubKeyData); // Get the identity hash by parsing sc_auth list output hash_out[0] = '\0'; NSTask *listTask = [[NSTask alloc] init]; listTask.executableURL = [NSURL fileURLWithPath:@"/usr/sbin/sc_auth"]; listTask.arguments = @[@"list-ctk-identities"]; NSPipe *listPipe = [NSPipe pipe]; listTask.standardOutput = listPipe; listTask.standardError = [NSPipe pipe]; if ([listTask launchAndReturnError:&nsError]) { [listTask waitUntilExit]; NSData *listData = [listPipe.fileHandleForReading readDataToEndOfFile]; NSString *listStr = [[NSString alloc] initWithData:listData encoding:NSUTF8StringEncoding]; for (NSString *line in [listStr componentsSeparatedByString:@"\n"]) { if ([line containsString:labelStr]) { NSMutableArray *tokens = [NSMutableArray array]; for (NSString *part in [line componentsSeparatedByCharactersInSet: [NSCharacterSet whitespaceCharacterSet]]) { if (part.length > 0) { [tokens addObject:part]; } } if (tokens.count > 1) { snprintf(hash_out, hash_out_len, "%s", [tokens[1] UTF8String]); } break; } } } return 0; } } int se_encrypt(const char *label, const uint8_t *plaintext, int plaintext_len, uint8_t *ciphertext_out, int *ciphertext_len, char *error_out, int error_out_len) { @autoreleasepool { SecKeyRef privateKey = lookup_ctk_private_key(label, error_out, error_out_len); if (!privateKey) { return -1; } SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey); CFRelease(privateKey); if (!publicKey) { snprintf_error(error_out, error_out_len, @"failed to get public key for encryption"); return -1; } NSData *plaintextData = [NSData dataWithBytes:plaintext length:plaintext_len]; CFErrorRef cfError = NULL; CFDataRef encrypted = SecKeyCreateEncryptedData( publicKey, kSecKeyAlgorithmECIESEncryptionStandardVariableIVX963SHA256AESGCM, (__bridge CFDataRef)plaintextData, &cfError ); CFRelease(publicKey); if (!encrypted) { NSError *nsError = (__bridge_transfer NSError *)cfError; NSString *msg = [NSString stringWithFormat:@"ECIES encryption failed: %@", nsError.localizedDescription]; snprintf_error(error_out, error_out_len, msg); return -1; } const UInt8 *encBytes = CFDataGetBytePtr(encrypted); CFIndex encLength = CFDataGetLength(encrypted); if (encLength > *ciphertext_len) { CFRelease(encrypted); snprintf_error(error_out, error_out_len, @"ciphertext buffer too small"); return -1; } memcpy(ciphertext_out, encBytes, encLength); *ciphertext_len = (int)encLength; CFRelease(encrypted); return 0; } } int se_decrypt(const char *label, const uint8_t *ciphertext, int ciphertext_len, uint8_t *plaintext_out, int *plaintext_len, char *error_out, int error_out_len) { @autoreleasepool { SecKeyRef privateKey = lookup_ctk_private_key(label, error_out, error_out_len); if (!privateKey) { return -1; } NSData *ciphertextData = [NSData dataWithBytes:ciphertext length:ciphertext_len]; CFErrorRef cfError = NULL; CFDataRef decrypted = SecKeyCreateDecryptedData( privateKey, kSecKeyAlgorithmECIESEncryptionStandardVariableIVX963SHA256AESGCM, (__bridge CFDataRef)ciphertextData, &cfError ); CFRelease(privateKey); if (!decrypted) { NSError *nsError = (__bridge_transfer NSError *)cfError; NSString *msg = [NSString stringWithFormat:@"ECIES decryption failed: %@", nsError.localizedDescription]; snprintf_error(error_out, error_out_len, msg); return -1; } const UInt8 *decBytes = CFDataGetBytePtr(decrypted); CFIndex decLength = CFDataGetLength(decrypted); if (decLength > *plaintext_len) { CFRelease(decrypted); snprintf_error(error_out, error_out_len, @"plaintext buffer too small"); return -1; } memcpy(plaintext_out, decBytes, decLength); *plaintext_len = (int)decLength; CFRelease(decrypted); return 0; } } int se_delete_key(const char *hash, char *error_out, int error_out_len) { @autoreleasepool { NSTask *task = [[NSTask alloc] init]; task.executableURL = [NSURL fileURLWithPath:@"/usr/sbin/sc_auth"]; task.arguments = @[ @"delete-ctk-identity", @"-h", [NSString stringWithUTF8String:hash], ]; NSPipe *stderrPipe = [NSPipe pipe]; task.standardOutput = [NSPipe pipe]; task.standardError = stderrPipe; NSError *nsError = nil; if (![task launchAndReturnError:&nsError]) { NSString *msg = [NSString stringWithFormat:@"failed to launch sc_auth: %@", nsError.localizedDescription]; snprintf_error(error_out, error_out_len, msg); return -1; } [task waitUntilExit]; if (task.terminationStatus != 0) { NSData *stderrData = [stderrPipe.fileHandleForReading readDataToEndOfFile]; NSString *stderrStr = [[NSString alloc] initWithData:stderrData encoding:NSUTF8StringEncoding]; NSString *msg = [NSString stringWithFormat:@"sc_auth delete failed: %@", stderrStr ?: @"unknown error"]; snprintf_error(error_out, error_out_len, msg); return -1; } return 0; } }