web-dev-qa-db-de.com

Implementieren von Clientzertifikaten und Serverauthentifizierung für iOS

Ich habe kürzlich einen äußerst mühsamen Prozess durchlaufen, um etwas zu bauen, das sehr einfach sein sollte, aber an keinem Ort im Wesentlichen auffindbar zu sein scheint. Ich möchte versuchen, alles hierher zu bringen, um zu fragen, ob ich etwas falsch mache, und wenn nicht, um allen zu helfen, die diese Informationen benötigen.

Hintergrund: Das Produkt/der Dienst, für das bzw. den ich Sicherheit bieten wollte, basiert auf WCF-Diensten auf einem Windows-Server, auf die nur über benutzerdefinierte Client-Apps auf einem PC oder einem iPad zugegriffen werden kann. Ein Server pro Kunde, kein Browserzugriff. Alles war bereits TLS-gesichert mit Authentifizierung und Autorisierung unter Verwendung von Windows-Standardmechanismen und Zertifikaten einer kommerziellen Zertifizierungsstelle.

Um den Zugriff weiter einzuschränken, wurden Client/Server-Zertifikate für die Windows-Plattform mit selbstsignierten Zertifikaten implementiert (kommerzielle Zertifizierungsstellen sind in Fällen, in denen trotz gegenteiliger Behauptungen keine gegenseitige Authentifizierung ohne öffentlichen/Browser-Zugriff erforderlich ist, und sie sind schwieriger managen).

All dies für das iPad zum Laufen zu bringen, war ein schrecklich dokumentierter Albtraum mit einer erstaunlichen Menge an Desinformationen oder teilweise korrekten Empfehlungen. Im Folgenden habe ich versucht, auf die besten Quellen zu verweisen, aber ich entschuldige mich, wenn ich versehentlich eine Zuschreibung verpasst habe. Bitte kommentieren Sie, wenn etwas falsch/irreführend an diesem Beitrag ist.

Vielen Dank

8
saminpa

Die Hauptschritte sind:

  1. Erstellen Sie ein System zum Generieren von Zertifikaten (einfach, aber nicht trivial, wenn es sich um ein Produktionssystem handelt).
  2. Übertragen Sie die Zertifikate auf ein iPad (NICHT im App Store Bundle enthalten!)
  3. Speichern Sie alle erhaltenen Anmeldeinformationen im App-Schlüsselbund (wo Apple sagt, dass sie hingehören).
  4. Rufen Sie die gespeicherten Anmeldeinformationen aus dem Schlüsselbund zur Verwendung in NSURLConnections ab
  5. Authentifizieren Sie das Serverzertifikat und geben Sie die Client-Anmeldeinformationen zurück

Schritt 1. Zertifikate generieren

Ref: http://developer-should-know.tumblr.com/post/127063737582/how-to-create-your-own-pki-with-openssl

Sie können andere Methoden verwenden, aber OpenSSL für Windows [ http://slproweb.com/products.html] ist ziemlich beeindruckend, mit der Ausnahme, dass die Standardschnittstelle cmdline ist und die Dokumentation schwer zu befolgen ist.

Was ich wünschte, jemand hätte es mir von vornherein erklärt, ist offensichtlich, aber nicht: [a] Die App wird in ein Stammverzeichnis installiert und enthält Konfigurationsdateien, die standardmäßig für Einstellungen verwendet werden, die nicht in der Befehlszeile [b] angegeben sind Die Speicherorte der Zwischen- und Ausgabedateien sollten in den Konfigurationsdateien angegeben werden. [c] Bestimmte Dateien müssen von Hand erstellt werden, bevor die Befehle ausgeführt werden Passen Sie die cfg-Dateien entsprechend an.

In meinem Fall bedeutete dies eine RootCA für mein Unternehmen, ein Zwischenzertifikat pro Kunde (so eingestellt, dass nur Clientzertifikate erstellt werden), ein Serverzertifikat pro Kunde und Clientzertifikate nach Bedarf. (Dies ist die Mindestkonfiguration. Verwenden Sie niemals CA/Client-Paare. Behalten Sie das Stammverzeichnis in einer Lockbox.) Hier ist meine Dateistruktur:

c:\sslcert
    root
    certs
        YourCompany (duplicate this structure as required)
             intermediate
             server
             client
             crl (optional)

Im sslcert-Ordner der obersten Ebene

.rnd        (empty file)
certindex.txt   (empty file)
serial.txt  (Text file seeded with the text “01”, hold the quotes)

Im Stammordner

RootCA.cfg

Im Ordner certs\template

IntermediateCA.cfg

Legen Sie das Arbeitsverzeichnis fest und starten Sie OpenSSL cd\sslcert c:\OpenSSL-Win32\bin\openssl.exe

Erstellen Sie Root-Schlüssel und Zertifikat in einem Schritt

req -config ./root/RootCA.cfg -new -x509 -days 7300 -extensions v3_ca -keyout root/YourCompanyRootCAkey.pem -out root/YourCompanyRootCAcert.cer

HINWEIS für Anfänger: Mit der -Erweiterung können Sie einen von mehreren Unterabschnitten in derselben cfg-Datei anwenden.

Schlüssel und Zertifikat prüfen (optional)

x509 -noout -text -in root/YourCompanyRootCAcert.cer

Fordern Sie ein neues Zwischenzertifikat an

req -config certs/YourCompany/IntermediateCA.cfg -new -keyout certs/YourCompany/intermediate/intermediateCAkey.pem -out certs/YourCompany/intermediate/intermediateCAreq.pem  

Zwischenzertifikat signieren unter Verwendung des in der Root-Konfiguration gefundenen Root-Zertifikats

ca -config root/RootCA.cfg -extensions v3_intermediate_ca -days 3650 -notext -in certs/YourCompany/intermediate/intermediateCAreq.pem -out certs/YourCompany/intermediate/YourCompanyIntermediateCAcert.cer

Schlüssel und Zertifikat prüfen (optional)

x509 -noout -text -in certs/YourCompany/intermediate/YourCompanyIntermediateCAcert.cer

Erstellen Sie die Zertifikatkettendatei, indem Sie das Zwischenzertifikat und das Stammzertifikat verketten (dies ist nur ein einfaches Anhängen über die Befehlszeile - die neue Kette wird dem endgültigen Paket p12 hinzugefügt).

c:\sslcert> type c:\sslcert\certs\YourCompany\intermediate\YourCompanyIntermediateCAcert.cer c:\sslcert\root\YourCompanyRootCAcert.cer > c:\sslcert\certs\YourCompany\intermediate\YourCompanyCAchain.cer

Fordern Sie einen neuen Client-Schlüssel und ein neues Zertifikat an

genrsa -aes256 -out certs/YourCompany/client/YourCompanyClientkey.pem 2048
req -config certs/YourCompany/IntermediateCA.cfg -key 
certs/YourCompany/client/YourCompanyClientkey.pem -new -sha256 -out         certs/YourCompany/client/YourCompanyClientreq.pem

Unterzeichnen und testen Sie das Client-Zertifikat mit zwischengeschalteter Berechtigung

ca -config certs/YourCompany/IntermediateCA.cfg -extensions usr_cert -days 1095 -notext -md sha256 -in certs/YourCompany/client/YourCompanyClientreq.pem -out certs/YourCompany/client/YourCompanyClientcert.cer
x509 -noout -text -in certs/YourCompany/client/YourCompanyClientcert.cer
verify -CAfile certs/YourCompany/intermediate/YourCompanyCAchain.cer certs/YourCompany/client/YourCompanyClientcert.cer

Paket Client-Zertifikat

pkcs12 -export -in certs/YourCompany/client/YourCompanyClientcert.cer -name “YourCompany Smips Client” -inkey certs/YourCompany/client/YourCompanyClientkey.pem -certfile certs/YourCompany/intermediate/YourCompanyCAchain.cer -out certs/YourCompany/client/YourCompanyClientWithName.p12

Benennen Sie pkcs für den Import in iOS aus E-Mail/iTunes

c:\sslcert> copy c:\sslcert\certs\YourCompany\client\YourCompanyClient.p12 c:\sslcert\certs\YourCompany\client\YourCompanyClient.yourext12

Fordern Sie einen neuen Serverschlüssel und ein neues Zertifikat an

genrsa -aes256 -out certs/YourCompany/server/YourCompanyServerkey.pem 2048
req -config certs/YourCompany/IntermediateCA.cfg -key certs/YourCompany/server/YourCompanyServerkey.pem -new -sha256 -out certs/YourCompany/server/YourCompanyServerreq.pem

Serverzertifikat signieren und testen mit zwischengeschalteter Berechtigung

ca -config certs/YourCompany/IntermediateCA.cfg -extensions server_cert -days 1095 -notext -md sha256 -in certs/YourCompany/server/YourCompanyServerreq.pem -out certs/YourCompany/server/YourCompanyServercert.cer
x509 -noout -text -in certs/YourCompany/server/YourCompanyServercert.cer
verify -CAfile certs/YourCompany/intermediate/YourCompanyCAchain.cer certs/YourCompany/server/YourCompanyServercert.cer

Package Server-Zertifikat

pkcs12 -export -in certs/YourCompany/server/YourCompanyServercert.cer -name “YourCompany Smips Server” -inkey certs/YourCompany/server/YourCompanyServerkey.pem -certfile certs/YourCompany/intermediate/YourCompanyCAchain.cer -out certs/YourCompany/server/YourCompanyServer.p12

Hier sind die cfg-Dateien: Root

dir                 = .

[ ca ]
default_ca              = CA_default

[ CA_default ]
serial              = $dir/serial.txt
database                = $dir/certindex.txt
new_certs_dir           = $dir/certs
certs                   = $dir/certs
private_key             = $dir/root/yourcompanyRootCAkey.pem
certificate             = $dir/root/yourcompanyRootCAcert.cer
default_days            = 7300
default_md              = sha256
preserve                = no
email_in_dn             = no
nameopt             = default_ca 
certopt             = default_ca 
policy              = policy_strict

[ policy_strict ]
countryName                 = match
stateOrProvinceName         = match
organizationName            = match
organizationalUnitName      = optional
commonName                  = supplied
emailAddress                = optional

[ req ]
default_bits            = 4096      # Size of keys
default_keyfile         = key.pem       # name of generated keys
default_md              = sha256        # message digest algorithm
string_mask             = nombstr       # permitted characters
distinguished_name      = req_distinguished_name
x509_extensions         = v3_ca

[ req_distinguished_name ]
0.organizationName           = Organization Name
organizationalUnitName       = Organizational Unit Name
emailAddress                 = Email Address
emailAddress_max            = 40
localityName            = Locality Name (city, district)
stateOrProvinceName     = State or Province Name (full name)
countryName             = Country Name (2 letter code)
countryName_min         = 2
countryName_max         = 2
commonName              = Common Name (hostname, IP, or your name)
commonName_max          = 64

0.organizationName_default  = yourcompany
organizationalUnitName_default  = yourcompanyRoot Certification
emailAddress_default        = [email protected]
localityName_default        = Okeefenokee
stateOrProvinceName_default = Wisconsin
countryName_default     = US

[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ crl_ext ]
authorityKeyIdentifier=keyid:always

Mittlere

dir = .

# [For non-command-line folks, everything is keyed to the working directory here (.) so if your working Prompt says c:\sslcerts> then the cfg will look for serial.txt at c:\sslcerts\serial.txt and bomb if it doesn’t find things laid out accordingly. Thats why you set up a directory structure to match these entries]

[ ca ]
default_ca              = CA_default

[ CA_default ]
serial                  = $dir/serial.txt
database                = $dir/certindex.txt
crl_dir                 = $dir/certs/yourcompany/crl
new_certs_dir               = $dir/certs
certs                   = $dir/certs
private_key             = $dir/certs/yourcompany/intermediate/IntermediateCAkey.pem
certificate             = $dir/certs/yourcompany/intermediate/yourcompanyIntermediateCAcert.cer
default_days                = 3650
default_md              = sha256
preserve                = no
email_in_dn             = no
nameopt                 = default_ca
certopt                 = default_ca 
crlnumber               = $dir/certs/yourcompany/crl/crlnumber
crl                 = $dir/certs/yourcompany/crl/crl.pem
crl_extensions              = crl_ext
default_crl_days            = 365
policy                  = policy_loose

[ policy_loose ]
countryName                     = optional
stateOrProvinceName             = optional
localityName                    = optional
organizationName                = optional
organizationalUnitName          = optional
commonName                      = supplied
emailAddress                    = optional

[ req ]
default_bits                = 4096              # Size of keys
default_keyfile             = $dir/certs/yourcompany/intermediate/IntermediateCAkey.pem
default_md              = sha256            # message digest 

# the old default was md1 - change this]

algorithm
string_mask             = nombstr           # permitted characters
distinguished_name          = req_distinguished_name
x509_extensions             = v3_intermediate_ca

[ req_distinguished_name ]
0.organizationName                  = Organization Name
organizationalUnitName              = Organizational Unit Name
emailAddress                        = Email Address
emailAddress_max            = 40
localityName                = Locality Name (city, district)
stateOrProvinceName         = State or Province Name (full name)
countryName             = Country Name (2 letter code)
countryName_min             = 2
countryName_max             = 2
commonName              = Common Name (hostname, IP, or your name)
commonName_max              = 64

0.organizationName_default      = yourcompany
organizationalUnitName_default      = yourcompany Intermediate Certification
emailAddress_default            = [email protected]
localityName_default            = Okeefenokee
stateOrProvinceName_default     = Wisconsin [should be spelled out]
countryName_default         = US

[ v3_intermediate_ca ]
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid:always,issuer
basicConstraints            = critical, CA:true, pathlen:0
keyUsage                = critical, digitalSignature, cRLSign, keyCertSign

# Important - the pathlen parameter prevents this cert from being used to create new intermediate certs. The subsequent subsections for server and client certs allows you to specify their type and intended usage, as distinct from the intermediate cert, in the same cfg file 

[ usr_cert ]
basicConstraints            = CA:FALSE
nsCertType              = client, email
nsComment               = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid,issuer
keyUsage                = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage            = clientAuth, emailProtection

[ server_cert ]
basicConstraints            = CA:FALSE
nsCertType              = server
nsComment               = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid,issuer:always
keyUsage                = critical, digitalSignature, keyEncipherment
extendedKeyUsage            = serverAuth

[ crl_ext ]
authorityKeyIdentifier          = keyid:always

2. Zertifikate auf ein iPad übertragen

Ref: Wie registriere ich die App, um die PDF-Datei in meiner App auf dem iPad zu öffnen?

Apple empfiehlt, einen neuen Dateityp zu registrieren, der von Ihrer App verarbeitet wird, und eine p12-Datei, die mit der neuen benutzerdefinierten Erweiterung umbenannt wurde, auf ein Gerät (manuell oder per E-Mail) zu übertragen, um Client-Zertifikate zu installieren. Die p12-Datei sollte die öffentliche Zertifizierungskette sowie die Client-Zertifizierungsinformationen enthalten, wie in Schritt 1 oben definiert. Wenn Sie versuchen, eine solche Datei zu öffnen, sendet das Gerät einen Start/Wakeup-Befehl an Ihren App-Delegaten, den Sie bearbeiten müssen (nicht in didload, da dies ein Wake sein könnte).

Dies hat sich mit v8 oder 9 etwas geändert, aber ich muss 7 unterstützen, damit dies für den veralteten Handler gilt. Dieselbe Lösung und es beginnt mit dem Hinzufügen zur App Plist-Datei, wie in den folgenden Screenshots gezeigt.

Beachten Sie, dass Sie zwei neue Symbole und eine Dateierweiterung benötigen, die wahrscheinlich nicht von einer anderen App beansprucht werden

 enter image description here

 enter image description here

Als nächstes benötigen Sie den Delegierten/Handler, der selbsterklärend sein sollte. Da dieser Teil nichts mit dem normalen Kontrollfluss zu tun hat, kümmere ich mich um die gesamte Delegatenverarbeitung in AppDelegate.m. (Ist das so falsch?) Richten Sie die Methoden/Variablen wie erforderlich ein und ignorieren Sie die paranoide zusätzliche Prüfung auf das Vorhandensein von Dateien ...

Ref: https://www.raywenderlich.com/6475/basic-security-in-ios-5-tutorial-part-1

- (BOOL) application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication
           annotation:(id)annotation {

    if (url) {

        self.p12Data = [NSData dataWithContentsOfFile:[url path]];

        if (!p12Data) {
            [self messageBox:@"Warning" : @"Failed to read data file, cancelling certificate import"];
        }
        else {
            [self presentAlertViewForPassPhrase];
        }

        NSFileManager * fileManager = [NSFileManager defaultManager];
        if ( [fileManager fileExistsAtPath:[url path]] ) {
            [fileManager removeItemAtPath:[url path] error:NULL];
        }
    }

    return YES;
}

- (void)presentAlertViewForPassPhrase {

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Certificate Credentials"
                                                    message:@"Please enter the passphrase for your certificate"
                                                   delegate:self
                                          cancelButtonTitle:@"Cancel"
                                          otherButtonTitles:@"Done", nil];
    [alert setAlertViewStyle:UIAlertViewStyleSecureTextInput];
    [alert show];
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {

    if (buttonIndex == 1) {                                             // User selected "Done"
        UITextField *ppField = [alertView textFieldAtIndex:0];
        if ([ppField.text length] > 0) {
            [self loadCertificates:ppField.text];
        }
        //Handle Else
    }
    else
    {                                                                   // User selected "Cancel"
        [self messageBox:@"Information" : @"Certificate import cancelled"];
    }
}

3. Speichern Sie die erhaltenen Anmeldeinformationen im App-Schlüsselbund

Jetzt, da Sie die Rohdaten für p12 haben, sollte es einfach sein, herauszufinden, was als nächstes zu tun ist ... NICHT. Die gesamte Dokumentation scheint für die Speicherung von Namen/pwd bestimmt zu sein, und eine beängstigende Anzahl von Postern schlägt vor, das Serverzertifikat im Dateisystem zu speichern. Dies ist in Ordnung, macht aber keinen Sinn, wenn Sie den Schlüsselbund haben und Apple sagt, dass dies der Zweck ist. Last but not least, wie unterscheiden Sie gespeicherte Zertifikate und wie aktualisieren Sie sie?

Kurz gesagt, ich habe mich für ein vollständiges Löschen/erneutes Speichern entschieden, nachdem ich alle Arten von Dingen ausprobiert hatte, die nicht funktionieren, um zu überprüfen, ob es sich um ein Update oder ein anfängliches Laden handelt - außerdem wollte ich dies in erster Linie tun, da es mein ist App-Kette. All dies ist CF-Material und ich benutze ARC nicht, weil ich es ablehne, etwas zu portieren, was ich nicht muss. Soweit ich das beurteilen kann, gibt es keine Warnungen, solange Sie CF zuweisen, nach der Verwendung in NS und CFRelease umwandeln.

Dies sind wichtige Referenzen:

Alle Schlüsselbundelemente in meiner iOS-Anwendung auflisten

[Unverzichtbar, um zu visualisieren, wie Ihr Schlüsselbund aussieht]

Wie lösche ich alle Schlüsselbund-Objekte, auf die eine App zugreifen kann?

Was macht ein Schlüsselbund-Element einzigartig (in iOS)?

http://help.sap.com/saphelp_smp307sdk/helpdata/de/7c/03830b70061014a937d8267bb3f358/content.htm

[ https://developer.Apple.com/library/ios/samplecode/AdvancedURLConnections/Listings/Credentials_m.html , in dem steht:

// IMPORTANT: SecCertificateRef's are not uniqued (that is, you can get two
// different SecCertificateRef values that described the same fundamental
// certificate in the keychain), nor can they be compared with CFEqual. So
// we match up certificates based on their data values.

Zusammenfassend lässt sich sagen, dass (duh) das Zertifikat am einfachsten mit einem Label versehen wird, damit Sie es eindeutig nachschlagen und feststellen können, dass es beim Speichern einer Identität automatisch in Schlüssel und Zertifikat aufgeteilt wird, was möglicherweise nicht der Fall ist sicher - haben zu einigen Schwierigkeiten mit dem Ersatz geführt.

Der Code (Erklärung folgt):

- (void) loadCertificates:(NSString *)passPhrase {

    BOOL lastError = false;
    NSMutableDictionary * p12Options = [[NSMutableDictionary alloc] init];
    [p12Options setObject:passPhrase forKey:(id)kSecImportExportPassphrase];
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
    OSStatus err = SecPKCS12Import((CFDataRef)p12Data, (CFDictionaryRef)p12Options, &items);
    if (err != noErr) {
        [self messageBox:@"Error" : @"Unable to extract security information with the supplied credentials. Please retry"];
        lastError = true;
    }
    if (!lastError && err == noErr && CFArrayGetCount(items) > 0) {
        CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
        //Clean-up

        NSArray *secItemClasses = [NSArray arrayWithObjects:
                                   (id)kSecClassCertificate,
                                   (id)kSecClassKey,
                                   (id)kSecClassIdentity,
                                   nil];

        for (id secItemClass in secItemClasses) {
            NSDictionary *spec = @{(id)kSecClass: secItemClass};
            err = SecItemDelete((CFDictionaryRef)spec);
        }

        //Client Identity & Certificate

        SecIdentityRef clientIdentity = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);

        NSDictionary *addIdentityQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                          kClientIdentityLabel, kSecAttrLabel,
                                          (id)clientIdentity, kSecValueRef,
                                          nil];
        err = SecItemAdd((CFDictionaryRef)addIdentityQuery, NULL);
        if (err == errSecDuplicateItem) {
            NSLog(@"Duplicate identity");
        }
        if (err != noErr) {
            [self messageBox:@"Warning" : @"Failed to save the new identity"];
            lastError = true;
        }
        //Server Certificate
        CFArrayRef chain = CFDictionaryGetValue(identityDict, kSecImportItemCertChain);
        CFIndex N = CFArrayGetCount(chain);
        BOOL brk = false;
        for (CFIndex i=0; (i < N) && (brk == false); i++) {
            SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(chain, i);
            CFStringRef summary = SecCertificateCopySubjectSummary(cert);
            NSString* strSummary = [[NSString alloc] initWithString:(NSString *)summary];
            if ([strSummary containsString:@"Root"] || (i == N)) {

                NSDictionary *addCertQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                              kServerCertificateLabel, kSecAttrLabel,
                                              (id)cert, kSecValueRef,
                                              nil];
                err = SecItemAdd((CFDictionaryRef)addCertQuery, NULL);
                if (err == errSecDuplicateItem) {
                    NSLog(@"Duplicate root certificate");
            }
            if (err != noErr) {
                [self messageBox:@"Warning" : @"Failed to save the new server certificate"];
                lastError = true;
            }
            brk = true;
        }
        [strSummary release];
        CFRelease(summary);
    }
}
else {
    [self messageBox:@"Error" : @"Unable to extract security information with the supplied credentials. Please retry"];
    lastError = true;
}
    [p12Options release];
    CFRelease(items);
    if (!lastError) [self messageBox:@"Information" : @"Certificate import succeeded"];
}

dabei sind kClientIdentityLabel und kServerCertificateLabel beliebige Bezeichnungen.

Die kSec-Funktionen sind zu umfangreich, um sie hier ausführlich zu erklären. Es genügt zu sagen, dass alles gelöscht wird, dann wird die extrahierte Clientidentität gespeichert, gefolgt vom Extrahieren der Stammzertifizierungsstelle, die dann separat gespeichert wird. Warum die Schleife? weil ich nicht wusste, ob es technisch korrekt ist anzunehmen, dass sich die Wurzel am Ende der Kette befindet, aber es wird sein, wenn ich den p12 generiere, so dass der Code nur für den Moment da ist.

Beachten Sie, dass Fehler von kSec verschlüsselt sind, sodass diese Site unverzichtbar ist: https://www.osstatus.com

4. Abrufen der gespeicherten Anmeldeinformationen vom Schlüsselbund

Sobald die Anmeldeinformationen im Schlüsselbund sind, können Sie sie auf diese Weise extrahieren (die Fehlermodi lassen etwas zu wünschen übrig):

- (void) reloadCredentials {

    self.clientCredential = nil;
    self.serverCertificateData = nil;

    if (self.useClientCertificateIfPresent) {

        NSDictionary* idQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                 kClientIdentityLabel,            kSecAttrLabel,
                                 (id)kSecClassIdentity,           kSecClass,
                                 kCFBooleanTrue,                  kSecReturnRef,
                                 kSecMatchLimitAll,               kSecMatchLimit,
                                 nil];
        CFArrayRef result = nil;
        OSStatus err = SecItemCopyMatching((CFDictionaryRef)idQuery, (CFTypeRef*)&result);
        if (err == errSecItemNotFound) {
            [self messageBox:@"Warning" : @"Client credentials not found. Server connection may fail"];
        }
        else if (err == noErr && result != nil ) {

            SecIdentityRef clientIdentity = (SecIdentityRef)CFArrayGetValueAtIndex(result, 0);

            SecCertificateRef clientCertificate;
            SecIdentityCopyCertificate(clientIdentity, &clientCertificate);
            const void *certs[] = { clientCertificate };
            CFArrayRef certsArray = CFArrayCreate(NULL, certs, 1, NULL);
            self.clientCredential = [NSURLCredential credentialWithIdentity:clientIdentity certificates:(NSArray*)certsArray
                                                                      persistence:NSURLCredentialPersistenceNone];
            CFRelease(certsArray);
            CFRelease(clientCertificate);
            CFRelease(result);
        }
        else {
            [self messageBox:@"Warning" : @"Client or Server credentials not found. Server connection may fail"];
        }

        NSDictionary* serverCertQuery = [NSDictionary dictionaryWithObjectsAndKeys:
                                         kServerCertificateLabel,         kSecAttrLabel,
                                         (id)kSecClassCertificate,        kSecClass,
                                         kCFBooleanTrue,                  kSecReturnRef,
                                         kSecMatchLimitAll,               kSecMatchLimit,
                                         nil];
        CFArrayRef result1 = nil;
        err = SecItemCopyMatching((CFDictionaryRef)serverCertQuery, (CFTypeRef*)&result1);
        if (err == errSecItemNotFound) {
            [self messageBox:@"Warning" : @"Server certificate not found. Server connection may fail"];
        }
        else if (err == noErr && result1 != nil ) {

            SecCertificateRef certRef = (SecCertificateRef)CFArrayGetValueAtIndex(result1, 0);
            CFDataRef certRefData = SecCertificateCopyData(certRef);
            self.serverCertificateData = (NSData *)certRefData;
            CFRelease(certRefData);
            CFRelease(result1);
        }
        else {
            [self messageBox:@"Warning" : @"Client or Server credentials not found. Server connection may fail"];
        }
    }
}

5. Serverzertifikat authentifizieren und Client-Anmeldeinformationen zurückgeben

Hallo Junge. Dies ist eine Bearbeitung, um zu erklären, wie die abgerufenen Zertifikate tatsächlich verwendet werden (es sollte der einfach offensichtliche Teil sein ...)

Erstens wird die bereits zweifelhafte Dokumentation von Apple durch das neue Application Transport Security Framework überholt (siehe zum Beispiel: http://useyourloaf.com/blog/app-transport-security/ ). Ich werde hier nicht darauf eingehen, aber die Idee ist, alle zu zwingen, standardmäßig immer https und vertrauenswürdige Zertifikate zu verwenden. In meinem Szenario können Sie diese Funktion durch Anheften von Zertifikaten und gegenseitige Authentifizierung zwischen dedizierten Clients und einem privaten Server sicher deaktivieren, indem Sie Ihrer Plist ein Wörterbuch wie folgt hinzufügen:

 enter image description here

Als Nächstes hatten Sie in Schritt 4 bereits den Client-Berechtigungsnachweis, um sofort auf diese Herausforderung zu antworten, wenn sie auftritt. Das Serverzertifikat wird jedoch als NSData im DER-Format angezeigt, das von SecCertificateCopyData erstellt wurde, und es ist nicht klar, was geschehen soll, wenn diese Herausforderung eintrifft.

Es stellt sich heraus, dass Sie den Algorithmus in Abschnitt 6 des "X.509-Standards" implementieren sollen ( https://tools.ietf.org/html/rfc5280 ). Glücklicherweise wird dies hinter den Kulissen von der iOS SecTrustEvaluate-Funktion implementiert, aber es gibt Gerüste zu bauen und seltsame Dinge zu verstehen.

[Leichtes Problem - Platzmangel !! Es wurde eine neue Frage hinzugefügt, einschließlich des Endes dieses Schritts.]

https://stackoverflow.com/questions/35964632/correctly-use-a-pinned-self-signed-certificate-in-ios-9-2

[Fortsetzung von anderem Beitrag]

So, das war es. Tut mir leid, dass die Produktionsqualität nicht ganz so gut ist, aber ich wollte das zusammenfügen, solange es noch frisch in meinem Kopf war. Ich werde den Beitrag aktualisieren, wenn ich Fehler finde.

Ich hoffe, das hilft und hier ist ein letzter Link zu einem sehr guten Buch, das Ihnen unter anderem das Gefühl gibt, kommerziellen Zertifizierungsstellen zu vertrauen ...

https://www.cs.auckland.ac.nz/~pgut001/pubs/book.pdf

12
saminpa

[Ich habe gerade (!) Gemerkt, dass ich eine weitere Antwort hinzufügen kann, da der Fortsetzungslink herabgestimmt und geschlossen wurde und es zwei Anfragen für die zusätzlichen Informationen gibt, die oben nicht passen. Die Antwort unten beginnt mit der Frage im gelöschten Beitrag.]

... Der Teil, über den ich immer noch nicht klar bin, ist, warum ich eine neue Vertrauensstellung und Richtlinie erstellen musste, um ein Ankerzertifikat zu implementieren und zu fixieren.

Wenn ich den Anker einfach zu der vom Server empfangenen Vertrauensstellung hinzufügte, konnte ich den Zeiger nicht erfolgreich auf die vom Server empfangene NSURLCredential zurückgeben, es schien geändert und vom Absender abgelehnt zu werden (?).

Die Frage ist, ist das wirklich die richtige Verarbeitung oder kann sie verdichtet werden? Das wird etwas langweilig, aber ich möchte etwas nicht akzeptieren, nur weil es "funktioniert". Meine aktuelle Lösung ist unten gezeigt.

In Schritt 4 hatten Sie bereits den Client-Berechtigungsnachweis, um auf diese Art von Herausforderung ohne Manipulation zu antworten, aber das Serverzertifikat schwebt als NSData im DER-Format, das von SecCertificateCopyData erstellt wurde, und es ist nicht klar, was geschehen soll, wenn diese Herausforderung eintrifft.

Es stellt sich heraus, dass Sie den Algorithmus in Abschnitt 6 des "X.509-Standards" implementieren sollen ( https://tools.ietf.org/html/rfc5280 ). Glücklicherweise wird dies hinter den Kulissen von der iOS SecTrustEvaluate-Funktion implementiert, aber es gibt Gerüste zu bauen und seltsame Dinge zu verstehen. Zuerst den Code (nach einem Hinweis auf meine ursprüngliche Quelle):

SecTrustEvaluate gibt mit SecPolicyCreateSSL immer kSecTrustResultRecoverableTrustFailure zurück

    - (void)_handleServerTrustChallenge {

    OSStatus status;
    BOOL trusted = false;
    SecTrustResultType trustResult;
    SecTrustRef serverTrust = self.challenge.protectionSpace.serverTrust;
    NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; //candidate credential for return to sender if valid

    if (svDelegate.serverCertificateData) {

        //locally stored information
        SecCertificateRef storedCertificate = SecCertificateCreateWithData(NULL, (CFDataRef)svDelegate.serverCertificateData);
        NSMutableArray *anchorCertArray = [NSMutableArray arrayWithCapacity:1];
        [anchorCertArray addObject:(id)storedCertificate];

        //incoming credentials from server
        NSMutableArray *receivedCertChain = [NSMutableArray array];
        for(int i = 0; i < SecTrustGetCertificateCount(serverTrust); i++)
            [receivedCertChain addObject:(id) SecTrustGetCertificateAtIndex(serverTrust,i))];

        //new custom policy object to use in creating new trust
        //YES indicates extendedKeyUsage is set to serverAuth; effectively ignore server name check by specifying incoming name
        SecPolicyRef newPolicyRef = SecPolicyCreateSSL(YES, (CFStringRef)self.challenge.protectionSpace.Host);

        //create and evaluate new trust with pinned certificate
        SecTrustRef newTrustRef = NULL;
        SecTrustCreateWithCertificates((CFArrayRef) receivedCertChain, newPolicyRef, &newTrustRef);
        status = SecTrustSetAnchorCertificates(newTrustRef, (CFArrayRef) anchorCertArray);
        if (status == noErr) status = SecTrustSetAnchorCertificatesOnly(newTrustRef, TRUE);
        if (status == noErr) status = SecTrustEvaluate(newTrustRef, &trustResult);  

        //----- debug -------
        //CFShow(newPolicyRef);
        //NSLog(@"%@", receivedCertChain);     

        CFRelease(newTrustRef);
        CFRelease(newPolicyRef);
        CFRelease(storedCertificate);
    }
    else {  //Server certificate not stored, rely on standard trusted Root CA authorities

        status = SecTrustEvaluate(serverTrust, &trustResult);
    }

    trusted = (status == noErr) && (trustResult == kSecTrustResultUnspecified);

    if (!trusted) credential = nil;
    [self stopWithCredential:credential];
    [self.delegate challengeHandlerDidFinish:self];
}

Also überprüfe ich zuerst, ob das Server-Zertifikat geladen wurde (sonst über herkömmliche vertrauenswürdige CA-Methode).

Als nächstes wählen Sie das zu bewertende "Vertrauensobjekt" aus. Ich konnte dies nicht tun, ohne eine Arbeitskopie des Vertrauensobjekts zu erstellen, das ich über die Leitung vom Server erhalten habe. B/C hat die Referenz 'NSURLCredential * credential = [NSURLCredential credentialForTrust: serverTrust]' irgendwie durcheinander gebracht, wenn ich sie direkt verwendet habe. Aus den wirklich schrecklichen Apple-Dokumenten geht jedoch hervor, dass dies ein koscherer Ansatz ist (ich empfehle, den x.509-RFC zu überfliegen, wenn Sie etwas davon verstehen möchten).

https://developer.Apple.com/library/ios/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html

[ https://developer.Apple.com/library/ios/technotes/tn2232/_index.html#//Apple_ref/doc/uid/DTS40012884-CH1-SECCUSTOMROOT[2]

Die Vertrauenswürdigkeit erfordert eine "Richtlinie", die eingehende Zertifikatskette zur Auswertung und ein oder mehrere "Ankerzertifikate", die im Grunde den Ursprung in einem beliebigen Koordinatensystem definieren - alles wird nach dem Nullpunkt validiert, auch wenn es sich nicht um ein Stammzertifikat handelt .

Sie laden also die eingehende Kette und das gespeicherte Zertifikat in Arrays, die der neuen Vertrauensstellung bereitgestellt werden sollen, und erstellen mit SecPolicyCreateSSL eine neue Richtlinie. Dies setzt ein Flag, das angibt, dass das Zertifikat für serverAuth und den eingehenden Server ausgestellt werden soll name sollte ignoriert werden (um eine gewisse Flexibilität der Infrastruktur zu ermöglichen).

Als Nächstes erstellen Sie die neue Vertrauensstellung mithilfe der neuen Richtlinie und des zu authentifizierenden Zertifikatsarrays. Dann setzen Sie den Anker und stellen sicher, dass die Kette nur mit Ihrem Ankerzertifikat bewertet wird, nicht mit irgendetwas im iOS-Schlüsselbund.

Wenn Sie das Vertrauen bewerten, kann es seltsam erscheinen, dass Sie kSecTrustResultUnspecified akzeptieren und nicht fortfahren oder etwas Positiveres. In der Tat bedeutet "Fortfahren", dass Sie Außerkraftsetzungen von der Benutzeroberfläche aus folgen, sodass diese tatsächlich fehlerhaft sind. Nicht spezifiziert bedeutet nur, dass gemäß der angegebenen Richtlinie nichts falsch ist.

Schließlich geben Sie den Berechtigungsnachweis vom eingehenden Vertrauensobjekt zurück (nicht vom neuen) und alles sollte golden sein ...

0
saminpa