1// Copyright 2015-present 650 Industries. All rights reserved. 2 3#import "EXApiUtil.h" 4 5#import <CommonCrypto/CommonDigest.h> 6#import <React/RCTUtils.h> 7 8#import "EXCachedResource.h" 9 10@import EXManifests; 11 12NS_ASSUME_NONNULL_BEGIN 13 14static NSString* kPublicKeyTag = @"exp.host.publickey"; 15 16@implementation EXApiUtil 17 18+ (NSURL *)bundleUrlFromManifest:(EXManifestsManifest *)manifest 19{ 20 return [[self class] encodedUrlFromString:manifest.bundleUrl]; 21} 22 23+ (NSURL *)encodedUrlFromString:(NSString *)urlString 24{ 25 NSURL *url = [NSURL URLWithString:urlString]; 26 if (!url) { 27 url = [NSURL URLWithString:[urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]; 28 } 29 return url; 30} 31 32+ (void)verifySignatureWithPublicKeyUrl:(NSURL *)publicKeyUrl 33 data:(NSString *)data 34 signature:(NSString *)signature 35 successBlock:(EXVerifySignatureSuccessBlock)successBlock 36 errorBlock:(EXVerifySignatureErrorBlock)errorBlock 37{ 38 [self fetchAndVerifySignatureWithPublicKeyUrl:publicKeyUrl 39 data:data 40 signature:signature 41 useCache:YES 42 successBlock:successBlock 43 errorBlock:errorBlock]; 44} 45 46+ (void)fetchAndVerifySignatureWithPublicKeyUrl:(NSURL *)publicKeyUrl 47 data:(NSString *)data 48 signature:(NSString *)signature 49 useCache:(BOOL)useCache 50 successBlock:(EXVerifySignatureSuccessBlock)successBlock 51 errorBlock:(EXVerifySignatureErrorBlock)errorBlock 52{ 53 if (!data || !signature) { 54 errorBlock([NSError errorWithDomain:@"EXAPIUtilDomain" code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"Cannot verify the manifest because it is empty or has no signature." }]); 55 return; 56 } 57 EXCachedResource *publicKeyResource = [[EXCachedResource alloc] initWithResourceName:@"manifestPublicKey" 58 resourceType:@"pem" 59 remoteUrl:publicKeyUrl 60 cachePath:nil]; 61 EXCachedResourceBehavior cacheBehavior = useCache ? EXCachedResourceUseCacheImmediately : EXCachedResourceNoCache; 62 [publicKeyResource loadResourceWithBehavior:cacheBehavior progressBlock:nil successBlock:^(NSData *publicKeyData) { 63 dispatch_async(dispatch_get_main_queue(), ^{ 64 SecKeyRef publicKey = [self keyRefFromPEMData:publicKeyData]; 65 66 NSData *signatureData = [[NSData alloc] initWithBase64EncodedString:signature options:0]; 67 NSData *signedData = [data dataUsingEncoding:NSUTF8StringEncoding]; 68 69 BOOL isValid = NO; 70 if (publicKey) { 71 isValid = [self verifyRSASHA256SignedData:signedData signatureData:signatureData publicKey:publicKey]; 72 CFRelease(publicKey); 73 } 74 if (!isValid && useCache) { 75 [self fetchAndVerifySignatureWithPublicKeyUrl:publicKeyUrl 76 data:data 77 signature:signature 78 useCache:NO 79 successBlock:successBlock 80 errorBlock:errorBlock]; 81 } else { 82 successBlock(isValid); 83 } 84 }); 85 } errorBlock:^(NSError *error) { 86 errorBlock(error); 87 }]; 88} 89 90/** 91 * Returns a CFRef to a SecKey given the raw pem data. 92 * The CFRef should be CFReleased when you're finished. 93 * 94 * Here is the Apple doc for this black hole: 95 * https://developer.apple.com/library/prerelease/content/documentation/Security/Conceptual/CertKeyTrustProgGuide/iPhone_Tasks/iPhone_Tasks.html#//apple_ref/doc/uid/TP40001358-CH208-SW13 96 */ 97+ (_Nullable SecKeyRef)keyRefFromPEMData:(NSData *)pemData 98{ 99 NSString *pemString = [[NSString alloc] initWithData:pemData encoding:NSUTF8StringEncoding]; 100 101 NSString *key = [NSString string]; 102 NSArray<NSString *> *keyLines = [pemString componentsSeparatedByString:@"\n"]; 103 BOOL foundKey = NO; 104 105 for (NSString *line in keyLines) { 106 if ([line isEqualToString:@"-----BEGIN PUBLIC KEY-----"]) { 107 foundKey = YES; 108 } else if ([line isEqualToString:@"-----END PUBLIC KEY-----"]) { 109 foundKey = NO; 110 } else if (foundKey) { 111 key = [key stringByAppendingString:line]; 112 } 113 } 114 115 if (key.length == 0) { 116 return nil; 117 } 118 119 NSData *keyData = [[NSData alloc] initWithBase64EncodedString:key options:0]; 120 if (keyData == nil) { 121 return nil; 122 } 123 124 NSData *tag = [NSData dataWithBytes:[kPublicKeyTag UTF8String] length:[kPublicKeyTag length]]; 125 126 // Delete any old lingering key with the same tag. 127 NSDictionary *deleteParams = @{ 128 (__bridge id)kSecClass: (__bridge id)kSecClassKey, 129 (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA, 130 (__bridge id)kSecAttrApplicationTag: tag, 131 }; 132 OSStatus secStatus = SecItemDelete((CFDictionaryRef)deleteParams); 133 134 SecKeyRef savedKeyRef = nil; 135 136 // Add key to system keychain. 137 NSDictionary *saveParams = @{ 138 (__bridge id)kSecClass: (__bridge id) kSecClassKey, 139 (__bridge id)kSecAttrKeyType: (__bridge id) kSecAttrKeyTypeRSA, 140 (__bridge id)kSecAttrApplicationTag: tag, 141 (__bridge id)kSecAttrKeyClass: (__bridge id) kSecAttrKeyClassPublic, 142 (__bridge id)kSecReturnPersistentRef: (__bridge id)kCFBooleanTrue, 143 (__bridge id)kSecValueData: keyData, 144 (__bridge id)kSecAttrKeySizeInBits: [NSNumber numberWithUnsignedInteger:keyData.length], 145 (__bridge id)kSecAttrEffectiveKeySize: [NSNumber numberWithUnsignedInteger:keyData.length], 146 (__bridge id)kSecAttrCanDerive: (__bridge id) kCFBooleanFalse, 147 (__bridge id)kSecAttrCanEncrypt: (__bridge id) kCFBooleanTrue, 148 (__bridge id)kSecAttrCanDecrypt: (__bridge id) kCFBooleanFalse, 149 (__bridge id)kSecAttrCanVerify: (__bridge id) kCFBooleanTrue, 150 (__bridge id)kSecAttrCanSign: (__bridge id) kCFBooleanFalse, 151 (__bridge id)kSecAttrCanWrap: (__bridge id) kCFBooleanTrue, 152 (__bridge id)kSecAttrCanUnwrap: (__bridge id) kCFBooleanFalse, 153 }; 154 155 secStatus = SecItemAdd((CFDictionaryRef)saveParams, (CFTypeRef *)&savedKeyRef); 156 157 if (savedKeyRef != nil) { 158 CFRelease(savedKeyRef); 159 } 160 161 if (secStatus != noErr && secStatus != errSecDuplicateItem) { 162 return nil; 163 } 164 165 // Fetch the SecKeyRef version of the key. 166 // note that kSecAttrKeyClass: kSecAttrKeyClassPublic doesn't seem to be required here. 167 // also: this doesn't work on iOS < 10.0 168 SecKeyRef keyRef = nil; 169 NSDictionary *queryParams = @{ 170 (__bridge id)kSecClass: (__bridge id) kSecClassKey, 171 (__bridge id)kSecAttrKeyType: (__bridge id) kSecAttrKeyTypeRSA, 172 (__bridge id)kSecAttrApplicationTag: tag, 173 (__bridge id)kSecReturnRef: (__bridge id) kCFBooleanTrue, 174 }; 175 secStatus = SecItemCopyMatching((CFDictionaryRef)queryParams, (CFTypeRef *)&keyRef); 176 177 if (secStatus != noErr) { 178 return nil; 179 } 180 181 return keyRef; 182} 183 184+ (BOOL)verifyRSASHA256SignedData:(NSData *)signedData signatureData:(NSData *)signatureData publicKey:(_Nullable SecKeyRef)publicKey 185{ 186 if (!publicKey) { 187 return NO; 188 } 189 190 uint8_t hashBytes[CC_SHA256_DIGEST_LENGTH]; 191 if (!CC_SHA256([signedData bytes], (CC_LONG)[signedData length], hashBytes)) { 192 return NO; 193 } 194 195 OSStatus status = SecKeyRawVerify(publicKey, 196 kSecPaddingPKCS1SHA256, 197 hashBytes, 198 CC_SHA256_DIGEST_LENGTH, 199 [signatureData bytes], 200 [signatureData length]); 201 202 return status == errSecSuccess; 203} 204 205@end 206 207NS_ASSUME_NONNULL_END 208