TSKSPKIHashCache.m 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. /*
  2. TSKSPKIHashCache.m
  3. TrustKit
  4. Copyright 2015 The TrustKit Project Authors
  5. Licensed under the MIT license, see associated LICENSE file for terms.
  6. See AUTHORS file for the list of project authors.
  7. */
  8. #import "TSKSPKIHashCache.h"
  9. #import "../TSKLog.h"
  10. #import <CommonCrypto/CommonDigest.h>
  11. #pragma mark Missing ASN1 SPKI Headers
  12. // These are the ASN1 headers for the Subject Public Key Info section of a certificate
  13. // TODO(AD): Are they returned by the new iOS API https://developer.apple.com/documentation/security/2963103-seccertificatecopykey ?
  14. static const unsigned char rsa2048Asn1Header[] =
  15. {
  16. 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
  17. 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00
  18. };
  19. static const unsigned char rsa4096Asn1Header[] =
  20. {
  21. 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
  22. 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00
  23. };
  24. static const unsigned char ecDsaSecp256r1Asn1Header[] =
  25. {
  26. 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
  27. 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
  28. 0x42, 0x00
  29. };
  30. static const unsigned char ecDsaSecp384r1Asn1Header[] =
  31. {
  32. 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
  33. 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00
  34. };
  35. static char *getAsn1HeaderBytes(NSString *publicKeyType, NSNumber *publicKeySize)
  36. {
  37. if (([publicKeyType isEqualToString:(NSString *)kSecAttrKeyTypeRSA]) && ([publicKeySize integerValue] == 2048))
  38. {
  39. return (char *)rsa2048Asn1Header;
  40. }
  41. else if (([publicKeyType isEqualToString:(NSString *)kSecAttrKeyTypeRSA]) && ([publicKeySize integerValue] == 4096))
  42. {
  43. return (char *)rsa4096Asn1Header;
  44. }
  45. else if (([publicKeyType isEqualToString:(NSString *)kSecAttrKeyTypeECSECPrimeRandom]) && ([publicKeySize integerValue] == 256))
  46. {
  47. return (char *)ecDsaSecp256r1Asn1Header;
  48. }
  49. else if (([publicKeyType isEqualToString:(NSString *)kSecAttrKeyTypeECSECPrimeRandom]) && ([publicKeySize integerValue] == 384))
  50. {
  51. return (char *)ecDsaSecp384r1Asn1Header;
  52. }
  53. @throw([NSException exceptionWithName:@"Unsupported public key algorithm" reason:@"Tried to generate the SPKI hash for an unsupported key algorithm" userInfo:nil]);
  54. }
  55. static unsigned int getAsn1HeaderSize(NSString *publicKeyType, NSNumber *publicKeySize)
  56. {
  57. if (([publicKeyType isEqualToString:(NSString *)kSecAttrKeyTypeRSA]) && ([publicKeySize integerValue] == 2048))
  58. {
  59. return sizeof(rsa2048Asn1Header);
  60. }
  61. else if (([publicKeyType isEqualToString:(NSString *)kSecAttrKeyTypeRSA]) && ([publicKeySize integerValue] == 4096))
  62. {
  63. return sizeof(rsa4096Asn1Header);
  64. }
  65. else if (([publicKeyType isEqualToString:(NSString *)kSecAttrKeyTypeECSECPrimeRandom]) && ([publicKeySize integerValue] == 256))
  66. {
  67. return sizeof(ecDsaSecp256r1Asn1Header);
  68. }
  69. else if (([publicKeyType isEqualToString:(NSString *)kSecAttrKeyTypeECSECPrimeRandom]) && ([publicKeySize integerValue] == 384))
  70. {
  71. return sizeof(ecDsaSecp384r1Asn1Header);
  72. }
  73. @throw([NSException exceptionWithName:@"Unsupported public key algorithm" reason:@"Tried to generate the SPKI hash for an unsupported key algorithm" userInfo:nil]);
  74. }
  75. @interface TSKSPKIHashCache ()
  76. // Dictionnary to cache SPKI hashes instead of having to compute them on every connection
  77. @property (nonatomic) SPKICacheDictionnary *spkiCache;
  78. @property (nonatomic) dispatch_queue_t lockQueue;
  79. @property (nonatomic) NSString *spkiCacheFilename;
  80. /**
  81. Load the SPKI cache from the filesystem. This triggers blocking file I/O.
  82. */
  83. - (SPKICacheDictionnary *)loadSPKICacheFromFileSystem;
  84. @end
  85. @implementation TSKSPKIHashCache
  86. - (instancetype)initWithIdentifier:(NSString *)uniqueIdentifier
  87. {
  88. self = [super init];
  89. if (self) {
  90. // Initialize our locks
  91. _lockQueue = dispatch_queue_create("TSKSPKIHashLock", DISPATCH_QUEUE_CONCURRENT);
  92. // Ensure a non-nil identifier was provided
  93. NSAssert(uniqueIdentifier, @"TSKSPKIHashCache initializer must be passed a unique identifier");
  94. _spkiCacheFilename = uniqueIdentifier;
  95. // First try to load a cached version from the filesystem
  96. _spkiCache = [self loadSPKICacheFromFileSystem];
  97. TSKLog(@"Loaded %lu SPKI cache entries from the filesystem", (unsigned long)_spkiCache.count);
  98. if (_spkiCache == nil)
  99. {
  100. _spkiCache = [NSMutableDictionary new];
  101. }
  102. }
  103. return self;
  104. }
  105. - (NSData *)hashSubjectPublicKeyInfoFromCertificate:(SecCertificateRef)certificate
  106. {
  107. __block NSData *cachedSubjectPublicKeyInfo;
  108. // Have we seen this certificate before? Look for the SPKI in the cache
  109. NSData *certificateData = (__bridge_transfer NSData *)(SecCertificateCopyData(certificate));
  110. dispatch_sync(_lockQueue, ^{
  111. cachedSubjectPublicKeyInfo = self->_spkiCache[certificateData];
  112. });
  113. if (cachedSubjectPublicKeyInfo)
  114. {
  115. TSKLog(@"Subject Public Key Info hash was found in the cache");
  116. return cachedSubjectPublicKeyInfo;
  117. }
  118. // We didn't this certificate in the cache so we need to generate its SPKI hash
  119. TSKLog(@"Generating Subject Public Key Info hash...");
  120. // First extract the public key
  121. SecKeyRef publicKey = [self copyPublicKeyFromCertificate:certificate];
  122. // Obtain the public key bytes from the key reference
  123. NSData *publicKeyData = (__bridge_transfer NSData *)SecKeyCopyExternalRepresentation(publicKey, NULL);
  124. if (publicKeyData == nil)
  125. {
  126. TSKLog(@"Error - could not extract the public key bytes");
  127. CFRelease(publicKey);
  128. return nil;
  129. }
  130. // Obtain the SPKI header based on the key's algorithm
  131. CFDictionaryRef publicKeyAttributes = SecKeyCopyAttributes(publicKey);
  132. NSString *publicKeyType = CFDictionaryGetValue(publicKeyAttributes, kSecAttrKeyType);
  133. NSNumber *publicKeysize = CFDictionaryGetValue(publicKeyAttributes, kSecAttrKeySizeInBits);
  134. CFRelease(publicKeyAttributes);
  135. char *asn1HeaderBytes = getAsn1HeaderBytes(publicKeyType, publicKeysize);
  136. unsigned int asn1HeaderSize = getAsn1HeaderSize(publicKeyType, publicKeysize);
  137. CFRelease(publicKey);
  138. // Generate a hash of the subject public key info
  139. NSMutableData *subjectPublicKeyInfoHash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
  140. CC_SHA256_CTX shaCtx;
  141. CC_SHA256_Init(&shaCtx);
  142. // Add the missing ASN1 header for public keys to re-create the subject public key info
  143. CC_SHA256_Update(&shaCtx, asn1HeaderBytes, asn1HeaderSize);
  144. // Add the public key
  145. CC_SHA256_Update(&shaCtx, [publicKeyData bytes], (unsigned int)[publicKeyData length]);
  146. CC_SHA256_Final((unsigned char *)[subjectPublicKeyInfoHash bytes], &shaCtx);
  147. // Store the hash in our memory cache
  148. dispatch_barrier_sync(_lockQueue, ^{
  149. self->_spkiCache[certificateData] = subjectPublicKeyInfoHash;
  150. });
  151. // Update the cache on the filesystem
  152. if (self.spkiCacheFilename.length > 0) {
  153. NSData *serializedSpkiCache = nil;
  154. if (@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)) { // prefer NSSecureCoding API when available
  155. serializedSpkiCache = [NSKeyedArchiver archivedDataWithRootObject:_spkiCache requiringSecureCoding:YES error:nil];
  156. } else {
  157. serializedSpkiCache = [NSKeyedArchiver archivedDataWithRootObject:_spkiCache];
  158. }
  159. if ([serializedSpkiCache writeToURL:[self SPKICachePath] atomically:YES] == NO)
  160. {
  161. NSAssert(false, @"Failed to write cache");
  162. TSKLog(@"Could not persist SPKI cache to the filesystem");
  163. }
  164. }
  165. return subjectPublicKeyInfoHash;
  166. }
  167. - (SPKICacheDictionnary *)loadSPKICacheFromFileSystem
  168. {
  169. NSMutableDictionary *spkiCache = nil;
  170. NSData *serializedSpkiCache = [NSData dataWithContentsOfURL:[self SPKICachePath]];
  171. if (serializedSpkiCache) {
  172. if (@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)) { // prefer NSSecureCoding API when available
  173. NSError *decodingError = nil;
  174. spkiCache = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:@[[SPKICacheDictionnary class], [NSData class]]] fromData:serializedSpkiCache error:&decodingError];
  175. if (decodingError) {
  176. TSKLog(@"Could not retrieve SPKI cache from the filesystem: %@", decodingError);
  177. }
  178. } else {
  179. spkiCache = [NSKeyedUnarchiver unarchiveObjectWithData:serializedSpkiCache];
  180. }
  181. }
  182. return spkiCache;
  183. }
  184. #pragma mark Public Key Converter - iOS 10.0+, macOS 10.12+, watchOS 3.0, tvOS 10.0
  185. - (SecKeyRef)copyPublicKeyFromCertificate:(SecCertificateRef)certificate
  186. {
  187. // Create an X509 trust using the using the certificate
  188. SecTrustRef trust;
  189. SecPolicyRef policy = SecPolicyCreateBasicX509();
  190. SecTrustCreateWithCertificates(certificate, policy, &trust);
  191. // Get a public key reference for the certificate from the trust
  192. SecTrustResultType result;
  193. SecTrustEvaluate(trust, &result);
  194. SecKeyRef publicKey = SecTrustCopyPublicKey(trust);
  195. CFRelease(policy);
  196. CFRelease(trust);
  197. return publicKey;
  198. }
  199. - (NSURL *)SPKICachePath
  200. {
  201. NSURL *cachesDirUrl = [NSFileManager.defaultManager URLsForDirectory:NSCachesDirectory
  202. inDomains:NSUserDomainMask].firstObject;
  203. return [cachesDirUrl URLByAppendingPathComponent:self.spkiCacheFilename];
  204. }
  205. @end
  206. @implementation TSKSPKIHashCache (TestSupport)
  207. - (void)resetSubjectPublicKeyInfoDiskCache
  208. {
  209. // Discard SPKI cache
  210. [NSFileManager.defaultManager removeItemAtURL:[self SPKICachePath] error:nil];
  211. }
  212. - (SPKICacheDictionnary *)getSubjectPublicKeyInfoHashesCache
  213. {
  214. return _spkiCache;
  215. }
  216. @end