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>
301 lines
11 KiB
Objective-C
301 lines
11 KiB
Objective-C
#import <Foundation/Foundation.h>
|
|
#import <Security/Security.h>
|
|
#include "secure_enclave.h"
|
|
#include <string.h>
|
|
|
|
// 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;
|
|
}
|
|
}
|