it-swarm.com.de

So befestigen Sie den öffentlichen Schlüssel eines Zertifikats unter iOS

Während wir die Sicherheit einer von uns entwickelten iOS-Anwendung verbessern, haben wir festgestellt, dass PIN (das gesamte oder ein Teil des) SSL-Zertifikats des Servers erforderlich ist, um Man-in-the-Middle-Angriffe zu verhindern. 

Obwohl es verschiedene Ansätze dafür gibt, habe ich bei der Suche nur Beispiele für das Anheften des gesamten Zertifikats gefunden. Diese Praxis stellt ein Problem dar: Sobald das Zertifikat aktualisiert wird, kann Ihre Anwendung keine Verbindung mehr herstellen. Wenn Sie den öffentlichen Schlüssel anstelle des gesamten Zertifikats festnageln, befinden Sie sich (in meinen Augen) in einer ebenso sicheren Situation, während Sie gegenüber Zertifikatsaktualisierungen auf dem Server widerstandsfähiger sind. 

Aber wie machst du das?

47
Javier Quevedo

Wenn Sie wissen möchten, wie Sie diese Informationen aus dem Zertifikat in Ihrem iOS-Code extrahieren, haben Sie hier eine Möglichkeit, dies zu tun.

Fügen Sie zunächst das Sicherheitsframework hinzu. 

#import <Security/Security.h>

Die addsl Bibliotheken hinzufügen. Sie können sie von https://github.com/st3fan/ios-openssl herunterladen.

#import <openssl/x509.h>

Mit dem NSURLConnectionDelegate-Protokoll können Sie entscheiden, ob die Verbindung auf einen Schutzbereich reagieren kann. Kurz gesagt: In diesem Fall können Sie das vom Server kommende Zertifikat anzeigen und entscheiden, ob die Verbindung fortgesetzt oder abgebrochen werden soll. Sie möchten hier den öffentlichen Schlüssel der Zertifikate mit dem von Ihnen gepinnten Schlüssel vergleichen. Nun ist die Frage, wie Sie einen solchen öffentlichen Schlüssel erhalten. Schauen Sie sich den folgenden Code an:

Holen Sie sich zuerst das Zertifikat im X509-Format (Sie benötigen dazu die SSL-Bibliotheken)

const unsigned char *certificateDataBytes = (const unsigned char *)[serverCertificateData bytes];
X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [serverCertificateData length]);

Jetzt bereiten wir uns darauf vor, die Daten des öffentlichen Schlüssels zu lesen

ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509);

NSString *publicKeyString = [[NSString alloc] init];    

An dieser Stelle können Sie die pubKey2-Zeichenfolge durchlaufen und die Bytes im HEX-Format mit der folgenden Schleife in eine Zeichenfolge extrahieren

 for (int i = 0; i < pubKey2->length; i++)
{
    NSString *aString = [NSString stringWithFormat:@"%02x", pubKey2->data[i]];
    publicKeyString = [publicKeyString stringByAppendingString:aString];
}

Drucken Sie den öffentlichen Schlüssel, um ihn anzuzeigen

 NSLog(@"%@", publicKeyString);

Der vollständige Code

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
const unsigned char *certificateDataBytes = (const unsigned char *)[serverCertificateData bytes];
X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [serverCertificateData length]);
ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509);

NSString *publicKeyString = [[NSString alloc] init];    

for (int i = 0; i < pubKey2->length; i++)
 {
     NSString *aString = [NSString stringWithFormat:@"%02x", pubKey2->data[i]];
     publicKeyString = [publicKeyString stringByAppendingString:aString];
 }

if ([publicKeyString isEqual:myPinnedPublicKeyString]){
    NSLog(@"YES THEY ARE EQUAL, PROCEED");
    return YES;
}else{
   NSLog(@"Security Breach");
   [connection cancel];
   return NO;
}

}
34
Javier Quevedo

Soweit ich weiß, dass Sie den erwarteten öffentlichen Schlüssel nicht direkt in iOS erstellen können, müssen Sie dies über ein Zertifikat tun .. Die Schritte sind daher ähnlich wie beim Anheften des Zertifikats, aber zusätzlich müssen Sie den öffentlichen Schlüssel extrahieren aus dem tatsächlichen Zertifikat und aus einem Referenzzertifikat (dem erwarteten öffentlichen Schlüssel).

Was Sie tun müssen, ist:

  1. Verwenden Sie ein NSURLConnectionDelegate, um die Daten abzurufen, und implementieren Sie willSendRequestForAuthenticationChallenge.
  2. Fügen Sie ein Referenzzertifikat im Format DER ein. Im Beispiel habe ich eine einfache Ressourcendatei verwendet.
  3. Extrahieren Sie den vom Server präsentierten öffentlichen Schlüssel
  4. Extrahieren Sie den öffentlichen Schlüssel aus Ihrem Referenzzertifikat
  5. Vergleichen Sie die beiden
  6. Wenn sie übereinstimmen, fahren Sie mit den regelmäßigen Prüfungen fort (Hostname, Zertifikatsignierung usw.).
  7. Wenn sie nicht übereinstimmen, scheitern.

Ein Beispielcode:

 (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    // get the public key offered by the server
    SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
    SecKeyRef actualKey = SecTrustCopyPublicKey(serverTrust);

    // load the reference certificate
    NSString *certFile = [[NSBundle mainBundle] pathForResource:@"ref-cert" ofType:@"der"];
    NSData* certData = [NSData dataWithContentsOfFile:certFile];
    SecCertificateRef expectedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);

    // extract the expected public key
    SecKeyRef expectedKey = NULL;
    SecCertificateRef certRefs[1] = { expectedCertificate };
    CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, (void *) certRefs, 1, NULL);
    SecPolicyRef policy = SecPolicyCreateBasicX509();
    SecTrustRef expTrust = NULL;
    OSStatus status = SecTrustCreateWithCertificates(certArray, policy, &expTrust);
    if (status == errSecSuccess) {
      expectedKey = SecTrustCopyPublicKey(expTrust);
    }
    CFRelease(expTrust);
    CFRelease(policy);
    CFRelease(certArray);

    // check a match
    if (actualKey != NULL && expectedKey != NULL && [(__bridge id) actualKey isEqual:(__bridge id)expectedKey]) {
      // public keys match, continue with other checks
      [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
    } else {
      // public keys do not match
      [challenge.sender cancelAuthenticationChallenge:challenge];
    }
    if(actualKey) {
      CFRelease(actualKey);
    }
    if(expectedKey) {
      CFRelease(expectedKey);
    }
 }

Haftungsausschluss: Dies ist nur ein Beispielcode, der nicht gründlich getestet wurde. Für eine vollständige Implementierung beginnen Sie mit dem Zertifikat zum Festlegen von Zertifikaten durch OWASP .

Denken Sie daran, dass das Anheften von Zertifikaten mit SSL Kill Switch und ähnlichen Tools immer vermieden werden kann.

19
beetstra

Sie können SSL-Festlegen mit öffentlichen Schlüsseln mithilfe der SecTrustCopyPublicKey-Funktion des Security.frameworks durchführen. Ein Beispiel finden Sie unter connection: willSendRequestForAuthenticationChallenge: des AFNetworking-Projekts.

Wenn Sie openSSL für iOS benötigen, verwenden Sie https://Gist.github.com/foozmeat/5154962 Es basiert auf st3fan/ios-openssl, was derzeit nicht funktioniert.

9
Jano

Sie können das hier erwähnte PhoneGap (Build) -Plugin verwenden: http://www.x-services.nl/certificate-pinning-plugin-for-phonegap-to-prevent-man-in-the-middle-attacks/ 734

Das Plugin unterstützt mehrere Zertifikate, sodass Server und Client nicht gleichzeitig aktualisiert werden müssen. Wenn sich Ihr Fingerabdruck alle zwei Jahre ändert, implementieren Sie einen Mechanismus, der die Aktualisierung der Clients erzwingt (fügen Sie Ihrer App eine Version hinzu und erstellen Sie auf dem Server eine API-Methode "minimalRequiredVersion") zu niedrig (z. B. wenn das neue Zertifikat aktiviert wird).

5
Eddy Verbruggen

Wenn Sie AFNetworking (genauer AFSecurityPolicy) verwenden und den Modus AFSSLPinningModePublicKey wählen, ist es egal, ob sich Ihre Zertifikate ändern oder nicht, solange die öffentlichen Schlüssel gleich bleiben. Ja, es ist wahr, dass AFSecurityPolicy keine Methode für das direkte Festlegen Ihrer öffentlichen Schlüssel bietet. Sie können Ihre Zertifikate nur festlegen, indem Sie setPinnedCertificates aufrufen. Wenn Sie sich jedoch die Implementierung von setPinnedCertificates anschauen, werden Sie feststellen, dass das Framework die öffentlichen Schlüssel aus den Zertifikaten extrahiert und dann die Schlüssel vergleicht. 

Kurz gesagt, übergeben Sie die Zertifikate und machen Sie sich keine Sorgen, dass sich diese in der Zukunft ändern. Das Framework kümmert sich nur um die öffentlichen Schlüssel in diesen Zertifikaten.

Der folgende Code funktioniert für mich. 

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
[manager.securityPolicy setPinnedCertificates:myCertificate];
3
SeaJelly

Hier die schnelle Antwort. Speichern Sie das Zertifikat (als .cer-Datei) Ihrer Website im Hauptpaket. Dann verwenden Sie this URLSessionDelegate-Methode:

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    guard
        challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
        let serverTrust = challenge.protectionSpace.serverTrust,
        SecTrustEvaluate(serverTrust, nil) == errSecSuccess,
        let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0) else {

            reject(with: completionHandler)
            return
    }

    let serverCertData = SecCertificateCopyData(serverCert) as Data

    guard
        let localCertPath = Bundle.main.path(forResource: "shop.rewe.de", ofType: "cer"),
        let localCertData = NSData(contentsOfFile: localCertPath) as Data?,

        localCertData == serverCertData else {

            reject(with: completionHandler)
            return
    }

    accept(with: serverTrust, completionHandler)

}

... 

func reject(with completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) {
    completionHandler(.cancelAuthenticationChallenge, nil)
}

func accept(with serverTrust: SecTrust, _ completionHandler: ((URLSession.AuthChallengeDisposition, URLCredential?) -> Void)) {
    completionHandler(.useCredential, URLCredential(trust: serverTrust))
}

Sie können die .cer-Datei mit this erhalten.

2
schirrmacher

... um das gesamte Zertifikat zu pinnen. Diese Praxis wirft ein Problem auf ...

Außerdem ändert Google das Zertifikat monatlich (oder so), behält jedoch die Öffentlichkeit bei oder zertifiziert sie erneut. Das Festhalten von Zertifikaten führt daher zu einer Vielzahl von falschen Warnungen, während das Festhalten von öffentlichen Schlüsseln wichtige Kontinuitätstests bestehen wird.

Ich glaube, Google tut es, um CRLs, OCSP und Widerrufslisten überschaubar zu halten, und ich erwarte, dass andere dies auch tun werden. Für meine Websites bestätige ich die Schlüssel in der Regel erneut, um die Schlüsselkontinuität zu gewährleisten.

Aber wie machst du das?

Zertifikat und Public Key Pinning . Der Artikel beschreibt die Vorgehensweise und bietet Beispielcode für OpenSSL, Android, iOS und .Net. Es gibt mindestens ein Problem mit iOS, das an dem unter iOS diskutierten Framework beteiligt ist: Stellen Sie einen aussagekräftigen Fehler von NSUrlConnection dar. DidReceiveAuthenticationChallenge (Certificate Failure) .

Peter Gutmann hat in seinem Buch Engineering Security einen großen Umgang mit Schlüsselkontinuität und Fixierung.

1
jww

Wenn Sie AFNetworking verwenden, verwenden Sie AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];