diff --git a/README.md b/README.md index b2d20b68..95a56563 100644 --- a/README.md +++ b/README.md @@ -393,7 +393,7 @@ SHA fingerprint of the server certificate (`featureFingerprint == YES`). For custom mTLS certificates use `certificateProvider` parameter of `IDnowSettings`. -Starting from SDK version 6.5.0 we offer mTLS support for API connections. +Starting from SDK version 6.5.0 we offer mTLS support for API connections, couple of enhancements were added in 9.1.0. mTLS enables server/client certificate validation. SDK can provide custom client certificate and several server certificates. @@ -403,18 +403,51 @@ What has changed: - REST supports mTLS - WebSocket support for mTLS. For this purpose, SRWebsocket implementation was slightly updated. So, now we have a local version of SRWebsocket. -To enable mTLS, it should be available in the customer backend configuration, and client (consumer) should supply certificate provider to the SDK. +How to do it: -Certificate Generation: +1 - Create subclass of `IDnowCertificateProvider` similar to [IDNMyMtlsCertificateProvider](https://github.com/idnow/de.idnow.ios/blob/master/examples/IDnow/IDnow/IDNMyMtlsCertificateProvider.m) -Client certificate and private key pair can be generated in a number of ways, for example, with Certificate Sign Request on Mac OS X Keychain. +2 - During the SDK configuration step set the certificate provider: + +```objectivec +[IDnowSettings sharedSettings].certificateProvider = [[IDNMyMtlsCertificateProvider alloc] init]; +``` + +```swift +let settings = IDnowSettings() +settings.certificateProvider = IDNMyMtlsCertificateProvider() +``` + +Data streams for server certificates either SHA256 fingerprint data or raw data from cert files (.cert extension) -Client Certificates: +```objectivec +- (NSArray*)provideServerFingerPrintByteStreams; +- (NSArray*)provideServerCertificateByteStreams; +``` +Feature flags for certificate provider allow usage of the corresponding features: + +```objectivec +- (BOOL)featureCertificate; // use client certificate - // DEFAULT: true +- (BOOL)featureFingerPrint; // use server certificate fingerprints - // DEFAULT: true +- (BOOL)featureServerCert; // use server certificates - // DEFAULT: true +``` -For iOS, key pair can be imported by platform tools from p12 package format. For example: +SHA256 fingerprint for STAGING1 and TEST environments: ```objectivec +static NSString* TEST_SERVER_FINGERPRINT = @"90:5A:42:E5:A5:42:C3:9A:C9:FF:1E:F5:78:29:CB:F8:29:9B:C2:A0:4E:06:C6:B1:E7:3F:EE:F4:7B:D7:DE:AF"; +static NSString* STAGING1_SERVER_FINGERPRINT = @"A3:8E:02:5C:B4:17:FA:2A:6D:A2:4F:BF:BF:2D:19:0C:52:E3:65:28:DC:4D:C7:61:18:E1:31:F1:44:BA:C8:06"; +``` + +To enable mTLS, it should be available in the customer backend configuration, and integrator app should supply certificate provider to the SDK. +```objectivec +- (SecIdentityRef)provideCertificateIdentity; +``` + +The key pair can be imported as p12 package format and provide to the SDK. For example: + +```objectivec + (SecIdentityRef)loadIdentityFromP12:(NSData *)p12Data password:(NSString *)password { const void *keys[] = { kSecImportExportPassphrase }; const void *values[] = { (__bridge CFStringRef)password }; @@ -424,42 +457,159 @@ For iOS, key pair can be imported by platform tools from p12 package format. For if (status != errSecSuccess) { return NULL; } - + CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0); SecIdentityRef identityApp = (SecIdentityRef) CFDictionaryGetValue(identityDict, kSecImportItemIdentity); - + CFRetain(identityApp); CFRelease(optionsDictionary); CFRelease(p12Items); - + return identityApp; } ``` -How to do it: +Certificate Generation: +Client certificate and private key pair can be generated in a number of ways, for example, with Certificate Sign Request on Mac OS X Keychain. -1 - Create subclass of `IDnowCertificateProvider` similar to [IDNMyMtlsCertificateProvider](https://github.com/idnow/de.idnow.ios/blob/master/examples/IDnow/IDnow/IDNMyMtlsCertificateProvider.m) -2 - During the SDK configuration step set the certificate provider: +Example: ```objectivec -[IDnowSettings sharedSettings].certificateProvider = [[IDNMyMtlsCertificateProvider alloc] init]; -``` +// Header file (*.h) +#import "IDnowCertificateProvider.h" -```swift -let settings = IDnowSettings() -settings.certificateProvider = IDNMyMtlsCertificateProvider() -``` +@interface IDNMyMtlsCertificateProvider : IDnowCertificateProvider -Feature flags for certificate provider allow usage of the corresponding features: +@property (nonatomic, strong) NSString *clientCertFileName; -```objectivec -- (BOOL)featureCertificate; // use client certificate -- (BOOL)featureFingerPrint; // use server certificate fingerprints -- (BOOL)featureServerCert; // use server certificates -``` +- (SecIdentityRef)provideCertificateIdentity; +- (NSArray*)provideServerFingerPrintByteStreams; + +- (NSArray *)provideServerCertificateByteStreams; + +- (NSArray *)availableClientCertificateFileNames; + +@end -You can check the certificate provider + certificates [here](https://github.com/idnow/de.idnow.ios/tree/master/IDnow). +// Implementation file (*.m) + +#import "IDNMyMtlsCertificateProvider.h" + +static NSString* TEST_SERVER_FINGERPRINT = @"90:5A:42:E5:A5:42:C3:9A:C9:FF:1E:F5:78:29:CB:F8:29:9B:C2:A0:4E:06:C6:B1:E7:3F:EE:F4:7B:D7:DE:AF"; +static NSString* STAGING1_SERVER_FINGERPRINT = @"A3:8E:02:5C:B4:17:FA:2A:6D:A2:4F:BF:BF:2D:19:0C:52:E3:65:28:DC:4D:C7:61:18:E1:31:F1:44:BA:C8:06"; + +@interface IDNMyMtlsCertificateProvider() + +@property (nonatomic, strong) NSMutableArray *serverCerts; + +- (NSMutableData*)readBytesFromFile:(NSString*)fileName; + +@end + +@implementation IDNMyMtlsCertificateProvider + +- (instancetype)init { + self = [super init]; + if (self != nil) { + self.serverCerts = [NSMutableArray array]; + [self loadServerCerts]; + + self.clientCertFileName = self.availableClientCertificateFileNames.firstObject; + } + + return self; +} + +- (SecIdentityRef)provideCertificateIdentity { + NSMutableData* data = [self readBytesFromFile:self.clientCertFileName]; + return [[self class] loadIdentityFromP12:data password:@"YOUR_PASSWORD"]; +} + +- (NSArray*)provideServerFingerPrintByteStreams +{ + NSMutableArray* data = [NSMutableArray new]; + for (NSString *fingerprint in @[TEST_SERVER_FINGERPRINT, STAGING1_SERVER_FINGERPRINT]) { + [data addObject:[self dataFromHex:fingerprint]]; + } + + return data; +} + +- (NSArray *)provideServerCertificateByteStreams { + return self.serverCerts; +} + +#pragma mark - Helpers + +- (NSArray *)pathesForServerCerts { + return [[NSBundle mainBundle] pathsForResourcesOfType:@"cer" inDirectory:@"server_certs"]; +} + +- (NSArray *)availableClientCertificateFileNames { + return [[NSBundle mainBundle] pathsForResourcesOfType:@"p12" inDirectory:@"client_certs"]; +} + +- (NSMutableData*)readBytesFromFile:(NSString*)filePath +{ + NSFileManager* manager = [NSFileManager defaultManager]; + NSMutableData* data = nil; + if ([manager fileExistsAtPath:filePath]) { + data = [[NSMutableData alloc] initWithContentsOfFile:filePath]; + } + + return data; +} + +- (NSData *)dataFromHex:(NSString *)hex { + NSMutableData *result = [[NSMutableData alloc] init]; + NSString *sourceHex = [hex stringByReplacingOccurrencesOfString:@":" withString:@""]; + char currentByte[3] = {'\0','\0','\0'}; + for (int i = 0; i < [sourceHex length] / 2; i++) { + currentByte[0] = [sourceHex characterAtIndex:i*2]; + currentByte[1] = [sourceHex characterAtIndex:i*2+1]; + char wholeByte = strtol(currentByte, NULL, 16); + [result appendBytes:&wholeByte length:1]; + } + + return result; +} + +- (void)loadServerCerts { + NSArray* serverCertFileNames = [self pathesForServerCerts]; + + [serverCertFileNames enumerateObjectsUsingBlock:^(NSString *fileName, NSUInteger idx, BOOL * _Nonnull stop) { + NSData *data = [self readBytesFromFile:fileName]; + [self.serverCerts addObject:data]; + }]; +} + +#pragma mark - Loading cert identity + ++ (SecIdentityRef)loadIdentityFromP12:(NSData *)p12Data password:(NSString *)password { + const void *keys[] = { kSecImportExportPassphrase }; + const void *values[] = { (__bridge CFStringRef)password }; + CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); + CFArrayRef p12Items; + OSStatus status = SecPKCS12Import((__bridge CFDataRef) p12Data, optionsDictionary, &p12Items); + if (status != errSecSuccess) { + return NULL; + } + + CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0); + SecIdentityRef identityApp = (SecIdentityRef) CFDictionaryGetValue(identityDict, kSecImportItemIdentity); + + CFRetain(identityApp); + CFRelease(optionsDictionary); + CFRelease(p12Items); + + return identityApp; +} + +@end + +``` +You can check more the certificate provider + certificates [here](https://github.com/idnow/de.idnow.ios/blob/master/examples/IDnow/IDnow). ## Branding diff --git a/examples/IDnow/IDnow/IDNMyMtlsCertificateProvider.h b/examples/IDnow/IDnow/IDNMyMtlsCertificateProvider.h index 8df37e3e..2b0d30d4 100644 --- a/examples/IDnow/IDnow/IDNMyMtlsCertificateProvider.h +++ b/examples/IDnow/IDnow/IDNMyMtlsCertificateProvider.h @@ -10,8 +10,13 @@ @interface IDNMyMtlsCertificateProvider : IDnowCertificateProvider +@property (nonatomic, strong) NSString *clientCertFileName; + - (SecIdentityRef)provideCertificateIdentity; - (NSArray*)provideServerFingerPrintByteStreams; -- (BOOL)verifyServerCertificate:(NSData*)serverCertificate; + +- (NSArray *)provideServerCertificateByteStreams; + +- (NSArray *)availableClientCertificateFileNames; @end diff --git a/examples/IDnow/IDnow/IDNMyMtlsCertificateProvider.m b/examples/IDnow/IDnow/IDNMyMtlsCertificateProvider.m index 2c92965f..c43e2009 100644 --- a/examples/IDnow/IDnow/IDNMyMtlsCertificateProvider.m +++ b/examples/IDnow/IDnow/IDNMyMtlsCertificateProvider.m @@ -8,9 +8,8 @@ #import "IDNMyMtlsCertificateProvider.h" -static NSString* CLIENT_IDENTITY_FILENAME = @"client_cert.p12"; -static NSString* TEST_SERVER_FINGERPRINT = - @"90:3A:43:09:E3:34:6E:8F:EE:DA:03:11:5E:03:12:E0:3D:10:3D:19:67:40:70:A5:39:54:F5:75:CF:80:99:66"; +static NSString* TEST_SERVER_FINGERPRINT = @"90:5A:42:E5:A5:42:C3:9A:C9:FF:1E:F5:78:29:CB:F8:29:9B:C2:A0:4E:06:C6:B1:E7:3F:EE:F4:7B:D7:DE:AF"; +static NSString* STAGING1_SERVER_FINGERPRINT = @"A3:8E:02:5C:B4:17:FA:2A:6D:A2:4F:BF:BF:2D:19:0C:52:E3:65:28:DC:4D:C7:61:18:E1:31:F1:44:BA:C8:06"; @interface IDNMyMtlsCertificateProvider() @@ -27,42 +26,42 @@ - (instancetype)init { if (self != nil) { self.serverCerts = [NSMutableArray array]; [self loadServerCerts]; + + self.clientCertFileName = self.availableClientCertificateFileNames.firstObject; } return self; } - (SecIdentityRef)provideCertificateIdentity { - NSMutableData* data = [self readBytesFromFile:[self pathForClientCert:CLIENT_IDENTITY_FILENAME]]; - return [[self class] loadIdentityFromP12:data password:@"unbreakablePassword"]; + NSMutableData* data = [self readBytesFromFile:self.clientCertFileName]; + return [[self class] loadIdentityFromP12:data password:@"YOUR_PASSWORD"]; } - (NSArray*)provideServerFingerPrintByteStreams { - NSData* data = [self dataFromHex:TEST_SERVER_FINGERPRINT]; - return @[data]; + NSMutableArray* data = [NSMutableArray new]; + for (NSString *fingerprint in @[STAGING1_SERVER_FINGERPRINT]) { + [data addObject:[self dataFromHex:fingerprint]]; + } + + return data; } -- (BOOL)verifyServerCertificate:(NSData*)serverCertificate { - NSUInteger matchIndex = [self.serverCerts indexOfObjectPassingTest:^BOOL(NSData *obj, NSUInteger idx, BOOL * _Nonnull stop) { - *stop = [serverCertificate isEqualToData:obj]; - return *stop; - }]; - - return matchIndex != NSNotFound; +- (NSArray *)provideServerCertificateByteStreams { + return self.serverCerts; } #pragma mark - Helpers -- (NSString *)pathForClientCert:(NSString *)certFileName { - NSString *relPath = [@"client_cert" stringByAppendingPathComponent:certFileName]; - return [[NSBundle mainBundle] pathForResource:relPath ofType:@""]; -} - - (NSArray *)pathesForServerCerts { return [[NSBundle mainBundle] pathsForResourcesOfType:@"cer" inDirectory:@"server_certs"]; } +- (NSArray *)availableClientCertificateFileNames { + return [[NSBundle mainBundle] pathsForResourcesOfType:@"p12" inDirectory:@"client_certs"]; +} + - (NSMutableData*)readBytesFromFile:(NSString*)filePath { NSFileManager* manager = [NSFileManager defaultManager]; diff --git a/examples/IDnow/IDnow/sample-certs/server_cert.der b/examples/IDnow/IDnow/sample-certs/server_cert.der deleted file mode 100644 index 624c7dae..00000000 Binary files a/examples/IDnow/IDnow/sample-certs/server_cert.der and /dev/null differ diff --git a/examples/IDnow/IDnow/sample-certs/server_staging1.cer b/examples/IDnow/IDnow/sample-certs/server_staging1.cer new file mode 100644 index 00000000..8fcb9aa4 Binary files /dev/null and b/examples/IDnow/IDnow/sample-certs/server_staging1.cer differ diff --git a/examples/IDnow/IDnow/sample-certs/server_test.cer b/examples/IDnow/IDnow/sample-certs/server_test.cer new file mode 100644 index 00000000..775e8d3d Binary files /dev/null and b/examples/IDnow/IDnow/sample-certs/server_test.cer differ