OCSP.m 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. //
  2. // OCSP.m
  3. // TunneledWebRequest
  4. //
  5. /*
  6. Licensed under Creative Commons Zero (CC0).
  7. https://creativecommons.org/publicdomain/zero/1.0/
  8. */
  9. // NOTE: this file is shared by TunneledWebRequest and TunneledWebView
  10. #import "OCSP.h"
  11. #import "openssl/ocsp.h"
  12. #import "openssl/safestack.h"
  13. #import "openssl/x509.h"
  14. #import "openssl/x509v3.h"
  15. NSErrorDomain _Nonnull const OCSPErrorDomain = @"OCSPErrorDomain";
  16. typedef NS_ERROR_ENUM(OCSPErrorDomain, OCSPErrorCode) {
  17. OCSPErrorCodeUnknown = -1,
  18. OCSPErrorCodeInvalidNumCerts = 1,
  19. OCSPErrorCodeNoLeafCert,
  20. OCSPErrorCodeNoIssuerCert,
  21. OCSPErrorCodeNoOCSPURLs,
  22. OCSPErrorCodeEVPAllocFailed,
  23. OCSPErrorCodeCertToIdFailed,
  24. OCSPErrorCodeReqAllocFailed,
  25. OCSPErrorCodeAddCertsToReqFailed,
  26. OCSPErrorCodeFailedToSerializeOCSPReq,
  27. OCSPErrorCodeConstructedInvalidURL
  28. };
  29. @implementation OCSP
  30. + (NSArray<NSURL*>*_Nullable)ocspURLs:(SecTrustRef)secTrustRef error:(NSError**)error {
  31. NSMutableArray <void(^)(void)> *cleanup = [[NSMutableArray alloc] init];
  32. CFIndex certificateCount = SecTrustGetCertificateCount(secTrustRef);
  33. if (certificateCount < 2) {
  34. NSString *errorString = [NSString stringWithFormat:@"Expected 2 or more certificates "
  35. "(at least leaf and issuer) in the "
  36. "trust chain but only found %ld",
  37. (long)certificateCount];
  38. *error = [NSError errorWithDomain:OCSPErrorDomain
  39. code:OCSPErrorCodeInvalidNumCerts
  40. userInfo:@{NSLocalizedDescriptionKey:errorString}];
  41. return nil;
  42. }
  43. X509 *leaf = [OCSP certAtIndex:secTrustRef withIndex:0];
  44. if (leaf == NULL) {
  45. *error = [NSError errorWithDomain:OCSPErrorDomain
  46. code:OCSPErrorCodeNoLeafCert
  47. userInfo:@{NSLocalizedDescriptionKey:@"Failed to get leaf "
  48. "certficate"}];
  49. return nil;
  50. }
  51. [cleanup addObject:^(){
  52. X509_free(leaf);
  53. }];
  54. X509 *issuer = [OCSP certAtIndex:secTrustRef withIndex:1];
  55. if (issuer == NULL) {
  56. *error = [NSError errorWithDomain:OCSPErrorDomain
  57. code:OCSPErrorCodeNoIssuerCert
  58. userInfo:@{NSLocalizedDescriptionKey:@"Failed to get issuer "
  59. "certificate"}];
  60. [OCSP execCleanupTasks:cleanup];
  61. return nil;
  62. }
  63. [cleanup addObject:^(){
  64. X509_free(issuer);
  65. }];
  66. NSArray<NSString*>* ocspURLs = [OCSP OCSPURLs:leaf];
  67. if ([ocspURLs count] == 0) {
  68. *error = [NSError errorWithDomain:OCSPErrorDomain
  69. code:OCSPErrorCodeNoOCSPURLs
  70. userInfo:@{NSLocalizedDescriptionKey:@"Found 0 OCSP URLs in leaf "
  71. "certificate"}];
  72. [OCSP execCleanupTasks:cleanup];
  73. return nil;
  74. }
  75. const EVP_MD *cert_id_md = EVP_sha1();
  76. if (cert_id_md == NULL) {
  77. *error = [NSError errorWithDomain:OCSPErrorDomain
  78. code:OCSPErrorCodeEVPAllocFailed
  79. userInfo:@{NSLocalizedDescriptionKey:@"Failed to allocate new EVP "
  80. "sha1"}];
  81. [OCSP execCleanupTasks:cleanup];
  82. return nil;
  83. }
  84. OCSP_CERTID *id_t = OCSP_cert_to_id(cert_id_md, leaf, issuer);
  85. if (id_t == NULL) {
  86. *error = [NSError errorWithDomain:OCSPErrorDomain
  87. code:OCSPErrorCodeCertToIdFailed
  88. userInfo:@{NSLocalizedDescriptionKey:@"Failed to create "
  89. "OCSP_CERTID structure"}];
  90. [OCSP execCleanupTasks:cleanup];
  91. return nil;
  92. }
  93. // Construct OCSP request
  94. //
  95. // https://www.ietf.org/rfc/rfc2560.txt
  96. //
  97. // An OCSP request using the GET method is constructed as follows:
  98. //
  99. // GET {url}/{url-encoding of base-64 encoding of the DER encoding of
  100. // the OCSPRequest}
  101. OCSP_REQUEST *req = OCSP_REQUEST_new();
  102. if (req == NULL) {
  103. *error = [NSError errorWithDomain:OCSPErrorDomain
  104. code:OCSPErrorCodeReqAllocFailed
  105. userInfo:@{NSLocalizedDescriptionKey:@"Failed to allocate new "
  106. "OCSP request"}];
  107. [OCSP execCleanupTasks:cleanup];
  108. return nil;
  109. }
  110. [cleanup addObject:^(){
  111. OCSP_REQUEST_free(req);
  112. }];
  113. if (OCSP_request_add0_id(req, id_t) == NULL) {
  114. *error = [NSError errorWithDomain:OCSPErrorDomain
  115. code:OCSPErrorCodeAddCertsToReqFailed
  116. userInfo:@{NSLocalizedDescriptionKey:@"Failed to add certs to "
  117. "OCSP request"}];
  118. [OCSP execCleanupTasks:cleanup];
  119. return nil;
  120. }
  121. unsigned char *ocspReq = NULL;
  122. int len = i2d_OCSP_REQUEST(req, &ocspReq);
  123. if (ocspReq == NULL) {
  124. *error = [NSError errorWithDomain:OCSPErrorDomain
  125. code:OCSPErrorCodeFailedToSerializeOCSPReq
  126. userInfo:@{NSLocalizedDescriptionKey:@"Failed to serialize OCSP "
  127. "request"}];
  128. [OCSP execCleanupTasks:cleanup];
  129. return nil;
  130. }
  131. [cleanup addObject:^(){
  132. free(ocspReq);
  133. }];
  134. NSData *ocspReqData = [NSData dataWithBytes:ocspReq length:len];
  135. NSString *encodedOCSPReqData = [ocspReqData base64EncodedStringWithOptions:kNilOptions];
  136. NSString *escapedAndEncodedOCSPReqData = [encodedOCSPReqData
  137. stringByAddingPercentEncodingWithAllowedCharacters:
  138. NSCharacterSet.URLFragmentAllowedCharacterSet];
  139. NSMutableArray<NSURL*>* reqURLs = [[NSMutableArray alloc] initWithCapacity:[ocspURLs count]];
  140. for (NSString *ocspURL in ocspURLs) {
  141. NSString *reqURL = [NSString stringWithFormat:@"%@/%@",
  142. ocspURL,
  143. escapedAndEncodedOCSPReqData];
  144. NSURL *url = [NSURL URLWithString:reqURL];
  145. if (url == nil) {
  146. NSString *localizedDescription = [NSString stringWithFormat:@"Constructed invalid URL "
  147. "for OCSP request: %@",
  148. reqURL];
  149. *error = [NSError errorWithDomain:OCSPErrorDomain
  150. code:OCSPErrorCodeConstructedInvalidURL
  151. userInfo:@{NSLocalizedDescriptionKey:localizedDescription}];
  152. [OCSP execCleanupTasks:cleanup];
  153. return nil;
  154. }
  155. [reqURLs addObject:url];
  156. }
  157. [OCSP execCleanupTasks:cleanup];
  158. return reqURLs;
  159. }
  160. #pragma mark - Internal Helpers
  161. + (X509*)certAtIndex:(SecTrustRef)trust withIndex:(int)index {
  162. if (SecTrustGetCertificateCount(trust) < index) {
  163. return nil;
  164. }
  165. SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, index);
  166. NSData *data = (__bridge_transfer NSData *)SecCertificateCopyData(cert);
  167. const unsigned char *p = [data bytes];
  168. X509 *x = d2i_X509(NULL, &p, [data length]);
  169. return x;
  170. }
  171. + (NSArray<NSString*>*)OCSPURLs:(X509*)x {
  172. STACK_OF(OPENSSL_STRING) *ocspURLs = X509_get1_ocsp(x);
  173. NSMutableArray *URLs = [[NSMutableArray alloc]
  174. initWithCapacity:sk_OPENSSL_STRING_num(ocspURLs)];
  175. for (int i = 0; i < sk_OPENSSL_STRING_num(ocspURLs); i++) {
  176. [URLs addObject:[NSString stringWithCString:sk_OPENSSL_STRING_value(ocspURLs, i)
  177. encoding:NSUTF8StringEncoding]];
  178. }
  179. sk_OPENSSL_STRING_free(ocspURLs);
  180. return URLs;
  181. }
  182. + (void)execCleanupTasks:(NSArray<void(^)(void)> *)cleanupTasks {
  183. for (void (^cleanupTask)(void) in cleanupTasks) {
  184. cleanupTask();
  185. }
  186. }
  187. @end