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