IOS: предварительно установить сертификат SSL в цепочке ключей - программно

Я хочу установить/сохранить сертификат в цепочке ключей до того, как пользователь посещает сайт. У меня есть HTTPS-сервер, и мое приложение аутентифицирует пользователя, прежде чем он отправится в https://mysite. Есть ли способ, который я могу установить/сохранить сертификат через почтовый запрос в цепочке ключей. ИЛИ Я копирую этот сертификат (файл) в пакет ресурсов, чтобы отметить его доверенным.

спасибо

ал

Ответ 1

После того, как у вас есть сертификат сервера в формате der, вы можете попробовать следующий код:

+ (void) addCertToKeychain:(NSData*)certInDer
{
    OSStatus            err = noErr;
    SecCertificateRef   cert;

    cert = SecCertificateCreateWithData(NULL, (CFDataRef) certInDer);
    assert(cert != NULL);

    CFTypeRef result;

    NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
                          (id)kSecClassCertificate, kSecClass,
                          cert, kSecValueRef, 
                          nil];

    err = SecItemAdd((CFDictionaryRef)dict, &result);
    assert(err == noErr || err == errSecDuplicateItem);

    CFRelease(cert);
}

Он добавит сертификат в тестовую среду keychain вашего приложения, то есть никакое другое приложение не будет доверять вашему сертификату.

Ответ 2

От: http://blog.asolutions.com/2011/02/using-tls-with-self-signed-certificates-or-custom-root-certificates-in-ios/

У вас есть два варианта: добавьте сертификат серверов в цепочку ключей или выполните проверку вручную. Независимо от вашего подхода, вам нужно включить общедоступный сертификат DER-кодированного X.509 в ваше приложение. В приведенном ниже примере он называется "ios-trusted-cert.der" ) и создает с ним SecCertificateRef. (Если ваш серверный сертификат является частью цепочки для корневого центра сертификации, вы должны установить корневой центр сертификации, а не сертификат своих серверов.)

NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSData *iosTrustedCertDerData =
  [NSData dataWithContentsOfFile:[bundle pathForResource:@"ios-trusted-cert"
                                                    ofType:@"der"]];
SecCertificateRef certificate =
  SecCertificateCreateWithData(NULL,
                               (CFDataRef) iosTrustedCertDerData);

Помните, что SecCertificateCreateWithData следует правилу создания права на владение памятью, поэтому вы должны CFRelease, когда вам больше не нужно, чтобы избежать утечек памяти.

Затем вы можете добавить свой сертификат в цепочку ключей приложений. Это подходит, если вы хотите, чтобы iOS доверяла вашему сертификату для каждого нового сокета, который вы создаете.

- (void) useKeychain: (SecCertificateRef) certificate {
  OSStatus err =
    SecItemAdd((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
                                  (id) kSecClassCertificate, kSecClass,
                                  certificate, kSecValueRef,
                                  nil],
               NULL);
  if ((err == noErr) || // success!
    (err == errSecDuplicateItem)) { // the cert was already added.  Success!
    // create your socket normally.
    // This is oversimplified.  Refer to the CFNetwork Guide for more details.
    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;
    CFStreamCreatePairWithSocketToHost(NULL,
                                       (CFStringRef)@"localhost",
                                       8443,
                                       &readStream,
                                       &writeStream);
    CFReadStreamSetProperty(readStream,
                            kCFStreamPropertySocketSecurityLevel,
                            kCFStreamSocketSecurityLevelTLSv1);
    CFReadStreamOpen(readStream);
    CFWriteStreamOpen(writeStream);
  } else {
    // handle the error.  There is probably something wrong with your cert.
  }
}

Если вы хотите только проверить сертификат для сокета, который вы создаете, и для каких-либо других сокетов в вашем приложении, вы можете проверить свое доверие к сертификату вручную. Во-первых, создайте сокет (предположим, что ваш сервер прослушивает порт 8443 на том же компьютере, что и ваш клиент) и отключите его проверку цепочки сертификатов в настройках ssl:

- (void) verifiesManually: (SecCertificateRef) certificate {
  CFReadStreamRef readStream;
  CFWriteStreamRef writeStream;
  CFStreamCreatePairWithSocketToHost(NULL,
                                     (CFStringRef)@"localhost",
                                     8443,
                                     &readStream,
                                     &writeStream);
  // Set this kCFStreamPropertySocketSecurityLevel before
  // setting kCFStreamPropertySSLSettings.
  // Setting kCFStreamPropertySocketSecurityLevel
  // appears to override previous settings in kCFStreamPropertySSLSettings
  CFReadStreamSetProperty(readStream,
                          kCFStreamPropertySocketSecurityLevel,
                          kCFStreamSocketSecurityLevelTLSv1);
  // this disables certificate chain validation in ssl settings.
  NSDictionary *sslSettings =
    [NSDictionary dictionaryWithObjectsAndKeys:
     (id)kCFBooleanFalse, (id)kCFStreamSSLValidatesCertificateChain,
     nil];
  CFReadStreamSetProperty(readStream,
                          kCFStreamPropertySSLSettings,
                          sslSettings);
  NSInputStream *inputStream = (NSInputStream *)readStream;
  NSOutputStream *outputStream = (NSOutputStream *)writeStream;
  [inputStream setDelegate:self];
  [outputStream setDelegate:self];
  [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                         forMode:NSDefaultRunLoopMode];
  [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                          forMode:NSDefaultRunLoopMode];
  CFReadStreamOpen(readStream);
  CFWriteStreamOpen(writeStream);
}

Затем, когда вы получаете обратный вызов, что ваш сокет готов к записи данных, вы должны проверить доверие к сертификату, на который был включен ваш сервер, прежде чем записывать какие-либо данные или читать какие-либо данные с сервера. Сначала (1) создайте политику SSL клиента с именем хоста сервера, к которому вы подключились. Имя хоста включено в серверный сертификат для проверки подлинности того сервера, на который настроен DNS, которым вы доверяете. Далее (2) вы захватываете фактические сертификаты сервера из сокета. Может быть несколько сертификатов, связанных с сервером, если сертификат сервера является частью цепочки сертификатов. Когда у вас есть фактические сертификаты сервера, вы можете (3) создать объект доверия. Объект доверия представляет собой локальный контекст для оценок доверия. Он изолирует индивидуальные оценки доверия, тогда как сертификаты связки ключей применяются ко всем доверенным сокетам. После того, как у вас есть объект доверия, вы можете (4) установить сертификаты привязки, которые являются доверенными сертификатами. Наконец, (5) вы можете оценить объект доверия и узнать, можно ли доверять серверу.

#pragma mark -
#pragma mark NSStreamDelegate
- (void)stream:(NSStream *)aStream
   handleEvent:(NSStreamEvent)eventCode {
  switch (eventCode) {
    case NSStreamEventNone:
    break;
    case NSStreamEventOpenCompleted:
    break;
    case NSStreamEventHasBytesAvailable:
    break;
    case NSStreamEventHasSpaceAvailable:
      // #1
      // NO for client, YES for server.  In this example, we are a client
      // replace "localhost" with the name of the server to which you are connecting
      SecPolicyRef policy = SecPolicyCreateSSL(NO, CFSTR("localhost"));
      SecTrustRef trust = NULL;
      // #2
      CFArrayRef streamCertificates =
        [aStream propertyForKey:(NSString *) kCFStreamPropertySSLPeerCertificates];
      // #3
      SecTrustCreateWithCertificates(streamCertificates,
                                     policy,
                                     &trust);
      // #4
      SecTrustSetAnchorCertificates(trust,
                                    (CFArrayRef) [NSArray arrayWithObject:(id) self.certificate]);
      // #5
      SecTrustResultType trustResultType = kSecTrustResultInvalid;
      OSStatus status = SecTrustEvaluate(trust, &trustResultType);
      if (status == errSecSuccess) {
        // expect trustResultType == kSecTrustResultUnspecified
        // until my cert exists in the keychain see technote for more detail.
        if (trustResultType == kSecTrustResultUnspecified) {
          NSLog(@"We can trust this certificate! TrustResultType: %d", trustResultType);
        } else {
          NSLog(@"Cannot trust certificate. TrustResultType: %d", trustResultType);
        }
      } else {
        NSLog(@"Creating trust failed: %d", status);
        [aStream close];
      }
      if (trust) {
        CFRelease(trust);
      }
      if (policy) {
        CFRelease(policy);
      }
    break;
    case NSStreamEventErrorOccurred:
      NSLog(@"unexpected NSStreamEventErrorOccurred: %@", [aStream streamError]);
    break;
    case NSStreamEventEndEncountered:
    break;
    default:
    break;
  }
}