SecKeyProxy.m   [plain text]


/*
 * Copyright (c) 2006-2015 Apple Inc. All Rights Reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

/*
 * SecKeyProxy.m - Remote access to SecKey instance
 */

#import <Foundation/Foundation.h>
#import <Foundation/NSXPCConnection_Private.h>

#include <Security/SecBasePriv.h>
#include <Security/SecKeyInternal.h>
#include <Security/SecIdentityPriv.h>
#include <utilities/debugging.h>
#include <utilities/SecCFError.h>

#include <Security/SecKeyProxy.h>

// MARK: XPC-level protocol for communication between server and client.
@protocol SecKeyProxyProtocol
- (void)initializeWithReply:(void (^)(NSData * _Nullable certificate))reply;
- (void)getBlockSizeWithReply:(void (^)(size_t blockSize))reply;
- (void)getAttributesWithReply:(void (^)(NSDictionary *attributes))reply;
- (void)getExternalRepresentationWithReply:(void (^)(NSData *data, NSError *error))reply;
- (void)getDescriptionWithReply:(void (^)(NSString *description))reply;
- (void)getAlgorithmIDWithReply:(void (^)(NSInteger algorithmID))reply;
- (void)getPublicKey:(void (^)(NSXPCListenerEndpoint *endpoint))reply;
- (void)performOperation:(SecKeyOperationType)operation algorithm:(NSString *)algorithm parameters:(NSArray *)parameters reply:(void (^)(NSArray *result, NSError *error))reply;
@end

// MARK: XPC target object for SecKeyProxy side
// Logically could be embedded in SecKeyProxy, but that would cause ownership cycles, since target object is always owned by its associated XPC connection.
@interface SecKeyProxyTarget : NSObject<SecKeyProxyProtocol> {
    id _key;
    NSData *_certificate;
    SecKeyProxy *_publicKeyProxy;
}
- (instancetype)initWithKey:(id)key certificate:(nullable NSData *)certificate;
@property (readonly, nonatomic) SecKeyRef key;
@end

@implementation SecKeyProxyTarget
- (instancetype)initWithKey:(id)key certificate:(nullable NSData *)certificate {
    if (self = [super init]) {
        _key = key;
        _certificate = certificate;
    }
    return self;
}

- (SecKeyRef)key {
    return (__bridge SecKeyRef)_key;
}

- (void)initializeWithReply:(void (^)(NSData *_Nullable))reply {
    return reply(_certificate);
}

- (void)getBlockSizeWithReply:(void (^)(size_t))reply {
    return reply(SecKeyGetBlockSize(self.key));
}

- (void)getAttributesWithReply:(void (^)(NSDictionary *))reply {
    return reply(CFBridgingRelease(SecKeyCopyAttributes(self.key)));
}

- (void)getExternalRepresentationWithReply:(void (^)(NSData *, NSError *))reply {
    NSError *error;
    NSData *data = CFBridgingRelease(SecKeyCopyExternalRepresentation(self.key, (void *)&error));
    return reply(data, error);
}

- (void)getDescriptionWithReply:(void (^)(NSString *))reply {
    NSString *description = CFBridgingRelease(CFCopyDescription(self.key));

    // Strip wrapping "<SecKeyRef " and ">" if present.
    if ([description hasPrefix:@"<SecKeyRef "] && [description hasSuffix:@">"]) {
        description = [description substringWithRange:NSMakeRange(11, description.length - 12)];
    } else if ([description hasPrefix:@"<SecKeyRef: "] && [description hasSuffix:@">"]) {
        description = [description substringWithRange:NSMakeRange(12, description.length - 13)];
    }

    return reply(description);
}

- (void)getAlgorithmIDWithReply:(void (^)(NSInteger))reply {
    return reply(SecKeyGetAlgorithmId(self.key));
}

- (void)getPublicKey:(void (^)(NSXPCListenerEndpoint *endpoint))reply {
    if (_publicKeyProxy == nil) {
        id publicKey = CFBridgingRelease(SecKeyCopyPublicKey(self.key));
        if (publicKey == nil) {
            return reply(nil);
        }
        _publicKeyProxy = [[SecKeyProxy alloc] initWithKey:(__bridge SecKeyRef)publicKey];
    }
    return reply(_publicKeyProxy.endpoint);
}

- (void)performOperation:(SecKeyOperationType)operation algorithm:(NSString *)algorithm parameters:(NSArray *)parameters reply:(void (^)(NSArray *, NSError *))reply {
    NSMutableArray *algorithms = @[algorithm].mutableCopy;
    CFTypeRef in1 = (__bridge CFTypeRef)(parameters.count > 0 ? parameters[0] : nil);
    CFTypeRef in2 = (__bridge CFTypeRef)(parameters.count > 1 ? parameters[1] : nil);
    NSError *error;
    SecKeyOperationContext context = { self.key, operation, (__bridge CFMutableArrayRef)algorithms };
    id result = CFBridgingRelease(SecKeyRunAlgorithmAndCopyResult(&context, in1, in2, (void *)&error));
    return reply(result ? @[result] : @[], error);
}
@end

// MARK: SecKeyProxy implementation
@interface SecKeyProxy() <NSXPCListenerDelegate>
@end

@implementation SecKeyProxy
- (instancetype)initWithKey:(SecKeyRef)key certificate:(nullable NSData *)certificate {
    if (self = [super init]) {
        _key = CFBridgingRelease(CFRetain(key));
        _certificate = certificate;
        _listener = [NSXPCListener anonymousListener];
        _listener.delegate = self;

        // All connections created to this proxy instance are serialized to this single queue.
        [_listener _setQueue: dispatch_queue_create("SecKeyProxy", NULL)];
        [_listener resume];
    }

    return self;
}

- (instancetype)initWithKey:(SecKeyRef)key {
    return [self initWithKey:key certificate:nil];
}

- (instancetype)initWithIdentity:(SecIdentityRef)identity {
    id key;
    id certificate;
    SecIdentityCopyPrivateKey(identity, (void *)&key);
    SecIdentityCopyCertificate(identity, (void *)&certificate);
    if (key == nil && certificate == nil) {
        return nil;
    }

    // Extract data from the certificate.
    NSData *certificateData = CFBridgingRelease(SecCertificateCopyData((SecCertificateRef)certificate));
    if (certificateData == nil) {
        return nil;
    }

    return [self initWithKey:(__bridge SecKeyRef)key certificate:certificateData];
}

- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
    newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(SecKeyProxyProtocol)];
    newConnection.exportedObject = [[SecKeyProxyTarget alloc] initWithKey:_key certificate:_certificate];
    [newConnection _setQueue:[_listener _queue]];
    [newConnection resume];
    return YES;
}

- (void)invalidate {
    [_listener invalidate];
}

- (void)dealloc {
    [self invalidate];
}

- (NSXPCListenerEndpoint *)endpoint {
    return _listener.endpoint;
}

// MARK: Client side: remote-connected SecKey instance.
static OSStatus SecRemoteKeyInit(SecKeyRef key, const uint8_t *keyData, CFIndex keyDataLength, SecKeyEncoding encoding) {
    // keyData and key->key are both actually type-punned NSXPCConnection owning pointers.
    key->key = (void *)keyData;
    return errSecSuccess;
}

static void SecRemoteKeyDestroy(SecKeyRef key) {
    NSXPCConnection *conn = CFBridgingRelease(key->key);
    [conn invalidate];
}

+ (id<SecKeyProxyProtocol>)targetForKey:(SecKeyRef)key error:(CFErrorRef *)error {
    NSXPCConnection *connection = (__bridge NSXPCConnection *)key->key;
    id<SecKeyProxyProtocol> result = [connection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * _Nonnull _error) {
        if (error != NULL) {
            *error = (__bridge_retained CFErrorRef)_error;
        }
    }];
    return result;
}

static size_t SecRemoteKeyBlockSize(SecKeyRef key) {
    __block size_t localBlockSize = 0;
    [[SecKeyProxy targetForKey:key error:NULL] getBlockSizeWithReply:^(size_t blockSize) {
        localBlockSize = blockSize;
    }];
    return localBlockSize;
}

static CFDictionaryRef SecRemoteKeyCopyAttributeDictionary(SecKeyRef key) {
    __block NSDictionary *localAttributes;
    [[SecKeyProxy targetForKey:key error:NULL] getAttributesWithReply:^(NSDictionary *attributes) {
        localAttributes = attributes;
    }];
    return CFBridgingRetain(localAttributes);
}

static CFDataRef SecRemoteKeyCopyExternalRepresentation(SecKeyRef key, CFErrorRef *error) {
    __block NSData *localData;
    __block NSError *localError;
    [[SecKeyProxy targetForKey:key error:error] getExternalRepresentationWithReply:^(NSData *data, NSError *error) {
        localData = data;
        localError = error;
    }];
    if (localData == nil && error != NULL) {
        *error = (__bridge_retained CFErrorRef)localError;
    }
    return CFBridgingRetain(localData);
}

static CFStringRef SecRemoteKeyCopyDescription(SecKeyRef key) {
    __block NSString *localDescription;
    [[SecKeyProxy targetForKey:key error:NULL] getDescriptionWithReply:^(NSString *description) {
        localDescription = [NSString stringWithFormat:@"<SecKeyRef remoteKey: %@>", description];
    }];
    return CFBridgingRetain(localDescription);
}

static CFIndex SecRemoteKeyGetAlgorithmID(SecKeyRef key) {
    __block CFIndex localAlgorithmID = kSecNullAlgorithmID;
    [[SecKeyProxy targetForKey:key error:NULL] getAlgorithmIDWithReply:^(NSInteger algorithmID) {
        localAlgorithmID = algorithmID;
    }];
    return localAlgorithmID;
}

static SecKeyRef SecRemoteKeyCopyPublicKey(SecKeyRef key) {
    __block id publicKey;
    [[SecKeyProxy targetForKey:key error:NULL] getPublicKey:^(NSXPCListenerEndpoint *endpoint) {
        if (endpoint != nil) {
            publicKey = CFBridgingRelease([SecKeyProxy createKeyFromEndpoint:endpoint error:nil]);
        }
    }];
    return (__bridge_retained SecKeyRef)publicKey;
}

static CFTypeRef SecRemoteKeyCopyOperationResult(SecKeyRef key, SecKeyOperationType operation, SecKeyAlgorithm algorithm, CFArrayRef algorithms, SecKeyOperationMode mode, CFTypeRef in1, CFTypeRef in2, CFErrorRef *error) {
    NSMutableArray *parameters = @[].mutableCopy;
    if (in1 != NULL) {
        [parameters addObject:(__bridge id)in1];
        if (in2 != NULL) {
            [parameters addObject:(__bridge id)in2];
        }
    }
    __block id localResult;
    [[SecKeyProxy targetForKey:key error:error] performOperation:operation algorithm:(__bridge NSString *)algorithm parameters:parameters reply:^(NSArray *result, NSError *_error) {
        if (result.count > 0) {
            localResult = result[0];
        }
        else if (error != NULL) {
            *error = (__bridge_retained CFErrorRef)_error;
        }
    }];
    return CFBridgingRetain(localResult);
}

static const SecKeyDescriptor SecRemoteKeyDescriptor = {
    .version = kSecKeyDescriptorVersion,
    .name = "RemoteKey",
    .init = SecRemoteKeyInit,
    .destroy = SecRemoteKeyDestroy,
    .blockSize = SecRemoteKeyBlockSize,
    .copyDictionary = SecRemoteKeyCopyAttributeDictionary,
    .copyExternalRepresentation = SecRemoteKeyCopyExternalRepresentation,
    .describe = SecRemoteKeyCopyDescription,
    .getAlgorithmID = SecRemoteKeyGetAlgorithmID,
    .copyPublicKey = SecRemoteKeyCopyPublicKey,
    .copyOperationResult = SecRemoteKeyCopyOperationResult,
};

+ (SecKeyRef)createItemFromEndpoint:(NSXPCListenerEndpoint *)endpoint certificate:(NSData **)certificate error:(NSError * _Nullable __autoreleasing *)error {
    // Connect to the server proxy object.
    NSXPCConnection *connection = [[NSXPCConnection alloc] initWithListenerEndpoint:endpoint];
    connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(SecKeyProxyProtocol)];
    [connection resume];

    // Initialize remote object.
    __block NSError *localError;
    __block NSData *localCertificate;
    [[connection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
        localError = [NSError errorWithDomain:(__bridge NSString *)kSecErrorDomain code:errSecItemNotFound userInfo:@{NSUnderlyingErrorKey: error}];
    }] initializeWithReply:^(NSData *_Nullable _certificate){
        localCertificate = _certificate;
    }];
    if (localError == nil) {
        if (certificate != nil) {
            *certificate = localCertificate;
        }
    } else {
        [connection invalidate];
        if (error != NULL) {
            *error = localError;
        }
        return NULL;
    }

    // Wrap returned connection in SecKeyRef instance.
    return SecKeyCreate(kCFAllocatorDefault, &SecRemoteKeyDescriptor, CFBridgingRetain(connection), 0, kSecKeyEncodingRaw);
}

+ (SecKeyRef)createKeyFromEndpoint:(NSXPCListenerEndpoint *)endpoint error:(NSError * _Nullable __autoreleasing *)error {
    return [self createItemFromEndpoint:endpoint certificate:nil error:error];
}

+ (SecIdentityRef)createIdentityFromEndpoint:(NSXPCListenerEndpoint *)endpoint error:(NSError * _Nullable __autoreleasing *)error {
    NSData *certificateData;
    id key = CFBridgingRelease([self createItemFromEndpoint:endpoint certificate:&certificateData error:error]);
    if (key == nil) {
        return NULL;
    }
    if (certificateData == nil) {
        if (error != NULL) {
            *error = [NSError errorWithDomain:(NSString *)kSecErrorDomain code:errSecParam userInfo:@{(id)NSLocalizedDescriptionKey: @"Attempt to create remote identity from key-only proxy"}];
        }
        return NULL;
    }

    id certificate = CFBridgingRelease(SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef)certificateData));
    return SecIdentityCreate(kCFAllocatorDefault, (__bridge SecCertificateRef)certificate, (__bridge SecKeyRef)key);
}
@end