Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add class to encrypt data using Secure Enclave #401

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions DashSync/shared/Libraries/SecEnclaveCrypto.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// SecEnclaveCrypto.h
// SecEnclaveCrypto
//
// Created by Andrew Podkovyrin on 06.08.2021.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface SecEnclaveCrypto : NSObject

+ (BOOL)isAvailable;

- (BOOL)hasPrivateKeyName:(NSString *)name
error:(NSError *_Nullable __autoreleasing *)error;

- (nullable NSData *)encrypt:(NSData *)plainTextData
withPublicKeyName:(NSString *)name
error:(NSError *_Nullable __autoreleasing *)error;

- (nullable NSData *)decrypt:(NSData *)cipherTextData
withPrivateKeyName:(NSString *)name
error:(NSError *_Nullable __autoreleasing *)error;

- (void)deletePrivateKeyWithName:(NSString *)name;

@end

NS_ASSUME_NONNULL_END
280 changes: 280 additions & 0 deletions DashSync/shared/Libraries/SecEnclaveCrypto.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
//
// SecEnclaveCrypto.m
// SecEnclaveCrypto
//
// Created by Andrew Podkovyrin on 06.08.2021.
//

#import "SecEnclaveCrypto.h"

#import <Security/Security.h>

NS_ASSUME_NONNULL_BEGIN

@implementation SecEnclaveCrypto

#pragma mark - Public

+ (BOOL)isAvailable {
NSString *tmpName = [[NSUUID UUID] UUIDString];
SecEnclaveCrypto *tmpCrypto = [[SecEnclaveCrypto alloc] init];

NSError *createError = nil;
SecKeyRef privateKey = [tmpCrypto createPrivateKeyWithName:tmpName error:&createError];
if (privateKey == nil) {
return NO;
}

SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey);
if (publicKey == nil) {
CFRelease(privateKey);
[tmpCrypto deletePrivateKeyWithName:tmpName];
return NO;
}

BOOL canEncrypt = SecKeyIsAlgorithmSupported(publicKey, kSecKeyOperationTypeEncrypt, [self algorithm]);
BOOL canDecrypt = SecKeyIsAlgorithmSupported(privateKey, kSecKeyOperationTypeDecrypt, [self algorithm]);

CFRelease(publicKey);
CFRelease(privateKey);
[tmpCrypto deletePrivateKeyWithName:tmpName];

return canEncrypt && canDecrypt;
}

- (BOOL)hasPrivateKeyName:(NSString *)name error:(NSError *_Nullable __autoreleasing *)error {
NSError *checkError = nil;
SecKeyRef privateKey = [self getPrivateKeyWithName:name error:&checkError];
if (checkError.code == errSecItemNotFound) {
return NO;
}
if (error && checkError) {
*error = checkError;
return NO;
}
BOOL hasKey = privateKey != nil;
CFRelease(privateKey);
return hasKey;
}

- (nullable NSData *)encrypt:(NSData *)plainTextData
withPublicKeyName:(NSString *)name
error:(NSError *_Nullable __autoreleasing *)error {
NSError *getError = nil;
SecKeyRef privateKey = [self getPrivateKeyWithName:name error:&getError];
if (privateKey == nil) {
privateKey = [self createPrivateKeyWithName:name error:error];
if (privateKey == nil) {
return nil;
}
}

SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey);
CFRelease(privateKey);
if (publicKey == nil) {
if (error) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:errSecPublicKeyInconsistent
userInfo:nil];
}
return nil;
}

NSData *encrypted = [self encrypt:plainTextData withPublicKey:publicKey error:error];
CFRelease(publicKey);
return encrypted;
}

- (nullable NSData *)decrypt:(NSData *)cipherTextData
withPrivateKeyName:(NSString *)name
error:(NSError *_Nullable __autoreleasing *)error {
SecKeyRef privateKey = [self getPrivateKeyWithName:name error:error];
if (privateKey == nil) {
return nil;
}

NSData *decrypted = [self decrypt:cipherTextData withPrivateKey:privateKey error:error];
CFRelease(privateKey);
return decrypted;
}

- (void)deletePrivateKeyWithName:(NSString *)name {
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassKey,
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeECSECPrimeRandom,
(__bridge id)kSecAttrApplicationTag: [self tagDataFor:name],
};

__unused OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
}

#pragma mark - Private

- (NSData *)tagDataFor:(NSString *)name {
return [name dataUsingEncoding:NSUTF8StringEncoding];
}

- (nullable SecKeyRef)getPrivateKeyWithName:(NSString *)name
error:(NSError *_Nullable __autoreleasing *)error {
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassKey,
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeECSECPrimeRandom,
(__bridge id)kSecAttrApplicationTag: [self tagDataFor:name],
(__bridge id)kSecReturnRef: @YES,
};

CFDataRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);

if (status == errSecSuccess) {
return (SecKeyRef)result;
} else {
if (error) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:status
userInfo:nil];
}
return nil;
}
}

- (NSInteger)getPrivateKeyCountWithName:(NSString *)name {
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassKey,
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeECSECPrimeRandom,
(__bridge id)kSecAttrApplicationTag: [self tagDataFor:name],
(__bridge id)kSecReturnRef: @YES,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll,
};

CFArrayRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecSuccess) {
NSInteger count = CFArrayGetCount(result);
CFRelease(result);
return count;
} else {
return 0;
}
}

- (nullable NSData *)encrypt:(NSData *)plainTextData
withPublicKey:(SecKeyRef)publicKey
error:(NSError *_Nullable __autoreleasing *)error {
if (SecKeyIsAlgorithmSupported(publicKey, kSecKeyOperationTypeEncrypt, [self.class algorithm]) == NO) {
if (error) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:errSecInvalidAlgorithm
userInfo:nil];
}
return nil;
}

CFErrorRef cfError = nil;
CFDataRef encrypted = SecKeyCreateEncryptedData(
publicKey,
[self.class algorithm],
(CFDataRef)plainTextData,
&cfError);
NSData *result = CFBridgingRelease(encrypted);
if (cfError && error) {
*error = CFBridgingRelease(cfError);
return nil;
}
return result;
}

- (nullable NSData *)decrypt:(NSData *)cipherTextData
withPrivateKey:(SecKeyRef)privateKey
error:(NSError *_Nullable __autoreleasing *)error {
if (SecKeyIsAlgorithmSupported(privateKey, kSecKeyOperationTypeDecrypt, [self.class algorithm]) == NO) {
if (error) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:errSecInvalidAlgorithm
userInfo:nil];
}
return nil;
}

CFErrorRef cfError = nil;
CFDataRef decrypted = SecKeyCreateDecryptedData(
privateKey,
[self.class algorithm],
(CFDataRef)cipherTextData,
&cfError);
NSData *result = CFBridgingRelease(decrypted);
if (cfError && error) {
*error = CFBridgingRelease(cfError);
return nil;
}
return result;
}

- (nullable SecKeyRef)createPrivateKeyWithName:(NSString *)name
error:(NSError *_Nullable __autoreleasing *)error {
if ([self getPrivateKeyCountWithName:name] != 0) {
return nil; // key already exists
}

CFErrorRef cfError = nil;
SecAccessControlRef access = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
kSecAccessControlPrivateKeyUsage,
&cfError);
if (access == nil) {
if (cfError && error) {
*error = CFBridgingRelease(cfError);
}
return nil;
}

NSMutableDictionary *query = [NSMutableDictionary dictionaryWithDictionary:@{
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeECSECPrimeRandom,
(__bridge id)kSecAttrKeySizeInBits: @([self.class numberOfBitsInKey]),
(__bridge id)kSecPrivateKeyAttrs: @{
(__bridge id)kSecAttrIsPermanent: @YES,
(__bridge id)kSecAttrApplicationTag: [self tagDataFor:name],
(__bridge id)kSecAttrAccessControl: (__bridge id)access,
},
}];
if ([self isSimulator] == NO) {
query[(__bridge id)kSecAttrTokenID] = (__bridge id)kSecAttrTokenIDSecureEnclave;
}

SecKeyRef key = SecKeyCreateRandomKey((__bridge CFDictionaryRef)query, &cfError);
CFRelease(access);
if (key == nil) {
if (cfError && error) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:CFErrorGetCode(cfError)
userInfo:nil];
}
return nil;
}

return key;
}

#pragma mark - Private Constants

+ (SecKeyAlgorithm)algorithm {
// https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_in_the_secure_enclave
return kSecKeyAlgorithmECIESEncryptionCofactorVariableIVX963SHA256AESGCM;
}

+ (NSInteger)numberOfBitsInKey {
return 256;
}

- (BOOL)isSimulator {
#if TARGET_OS_SIMULATOR
return YES;
#else
return NO;
#endif /* TARGET_OS_SIMULATOR */
}

@end

NS_ASSUME_NONNULL_END
4 changes: 4 additions & 0 deletions Example/DashSync.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
2A7DCCB220DA10870097049F /* DSBloomFilterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7DCCB120DA10870097049F /* DSBloomFilterTests.m */; };
2A7DCCB420DA198F0097049F /* DSPaymentProtocolTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A7DCCB320DA198F0097049F /* DSPaymentProtocolTests.m */; };
2A90EDAA2268C15D00D95B6B /* DSUInt256IndexPathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A90EDA92268C15D00D95B6B /* DSUInt256IndexPathTests.m */; };
2A96327126C08E6400D055CC /* DSSecEnclaveCryptoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A96327026C08E6400D055CC /* DSSecEnclaveCryptoTests.m */; };
2A9FFEC622328D0E00956D5F /* DSContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFEC522328D0E00956D5F /* DSContactsViewController.m */; };
2A9FFEC92232920900956D5F /* DSContactsTabBarViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFEC82232920900956D5F /* DSContactsTabBarViewController.m */; };
2A9FFF012233B90000956D5F /* DSContactsNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A9FFF002233B90000956D5F /* DSContactsNavigationController.m */; };
Expand Down Expand Up @@ -557,6 +558,7 @@
2A7DCCB120DA10870097049F /* DSBloomFilterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSBloomFilterTests.m; sourceTree = "<group>"; };
2A7DCCB320DA198F0097049F /* DSPaymentProtocolTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSPaymentProtocolTests.m; sourceTree = "<group>"; };
2A90EDA92268C15D00D95B6B /* DSUInt256IndexPathTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSUInt256IndexPathTests.m; sourceTree = "<group>"; };
2A96327026C08E6400D055CC /* DSSecEnclaveCryptoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSSecEnclaveCryptoTests.m; sourceTree = "<group>"; };
2A9FFEC422328D0E00956D5F /* DSContactsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSContactsViewController.h; sourceTree = "<group>"; };
2A9FFEC522328D0E00956D5F /* DSContactsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSContactsViewController.m; sourceTree = "<group>"; };
2A9FFEC72232920900956D5F /* DSContactsTabBarViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSContactsTabBarViewController.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2249,6 +2251,7 @@
FBAF79782385CA0200F4944E /* DSSparseMerkleTreeTests.m */,
FBE07DD22467440200A1079C /* DSChainedSigningTests.m */,
2A90EDA92268C15D00D95B6B /* DSUInt256IndexPathTests.m */,
2A96327026C08E6400D055CC /* DSSecEnclaveCryptoTests.m */,
);
name = LibraryTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -3050,6 +3053,7 @@
FB1B98D123D6A3BD00D50F69 /* DSTransitionTests.m in Sources */,
FB29C31425A3595D001F4F43 /* DSInstantSendLockTests.m in Sources */,
FB3ADDE6238E62CD00C31D23 /* DSChainLockTests.m in Sources */,
2A96327126C08E6400D055CC /* DSSecEnclaveCryptoTests.m in Sources */,
FBE4603C256B1AEF0052A6DE /* DSDIP14Tests.m in Sources */,
FB87A0CE24B7C28700C22DF7 /* DSMiningTests.m in Sources */,
FBDB9B7E20FF7AB900AD61B3 /* DSDeterministicMasternodeListTests.m in Sources */,
Expand Down
Loading