web-dev-qa-db-fra.com

Comment implémenter les certificats clients et l'authentification du serveur pour iOS

Je suis récemment passé par un processus extrêmement ardu pour construire quelque chose qui devrait être très simple, mais qui semble être essentiellement introuvable en un seul endroit. Je voudrais essayer de tout mettre ici pour demander si je fais quelque chose de mal et, sinon, pour aider toute personne qui a besoin de cette information.

Contexte: Le produit/service pour lequel j'essayais de sécuriser repose sur des services WCF sur un serveur Windows, accessibles uniquement via des applications client personnalisées sur un PC ou un iPad. Un serveur par client, pas d'accès par navigateur. Tout était déjà sécurisé par TLS avec authentification et autorisation à l'aide de mécanismes standard Windows et de certificats provenant d'une autorité de certification commerciale.

Pour restreindre davantage l'accès, des certificats client/serveur ont été implémentés pour la plate-forme Windows à l'aide de certificats auto-signés (les autorités de certification commerciales ne sont pas nécessaires dans les cas d'authentification mutuelle sans accès public/via le navigateur, malgré les affirmations contraires, et plus difficiles. à gérer).

Faire en sorte que tout cela fonctionne pour l'iPad était un cauchemar terriblement documenté avec une quantité incroyable de désinformation ou des recommandations partiellement correctes. Dans ce qui suit, j'ai essayé de faire le lien avec les meilleures sources, mais je m'excuse si j'ai raté une attribution par inadvertance. Veuillez commenter s'il y a quelque chose de mal/trompeur à propos de ce post.

Merci

8
saminpa

Les principales étapes sont les suivantes:

  1. Créer un système de génération de certificats (simple mais non trivial s’il s’agit d’un système de production)
  2. Transférez les certificats sur un iPad (PAS incorporé dans le bundle App Store!)
  3. Enregistrez toutes les informations d'identification reçues dans le trousseau de l'application (à laquelle Apple déclare appartenir)
  4. Récupérer les informations d'identification enregistrées du trousseau pour les utiliser avec NSURLConnections
  5. Authentifiez réellement le certificat du serveur et renvoyez les informations d'identification du client

Étape 1. Générer des certificats

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

Vous pouvez utiliser d'autres méthodes, mais OpenSSL pour Windows [ http://slproweb.com/products.html] est assez génial, sauf que l'interface standard est cmdline et que la documentation est difficile à suivre.

Ce que je souhaiterais que quelqu'un m'ait expliqué dès le départ est évidente, mais pas du tout: [a] L'application est installée dans un répertoire de niveau racine et comprend les fichiers de configuration utilisés par défaut pour les paramètres non spécifiés sur la ligne de commande les emplacements des fichiers intermédiaires et de sortie doivent être spécifiés dans les fichiers de configuration [c] certains fichiers doivent être créés manuellement avant d'exécuter les commandes [d]; vous devez créer une structure de fichiers/dossiers qui convient à ce que vous essayez de faire, puis personnaliser les fichiers cfg en conséquence.

Dans mon cas, cela signifiait un RootCA pour mon entreprise, un certificat intermédiaire par client (défini pour ne permettre de créer que des certificats clients), un certificat de serveur par client et des certificats clients, le cas échéant. (c’est la configuration minimale, n’utilisez jamais de paires CA/client, conservez la racine dans un lockbox) Voici la structure de mon fichier:

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

Dans le dossier de premier niveau sslcert

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

Dans le dossier racine

RootCA.cfg

Dans le dossier certs\template

IntermediateCA.cfg

Définissez le répertoire de travail et lancez OpenSSL cd\sslcert c:\OpenSSL-Win32\bin\openssl.exe

Créer une clé racine et un certificat en une étape

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

NOTE pour le débutant: les -extensions vous permettent de choisir d'appliquer une ou plusieurs sous-sections du même fichier cfg.

Clé de contrôle et cert (facultatif)

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

Demander un nouveau certificat intermédiaire

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

Signature du certificat intermédiaire à l'aide du certificat racine trouvé dans la configuration racine _

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

Clé de contrôle et cert (facultatif)

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

Créez le fichier de chaîne de certificats en concaténant les certificats intermédiaires et racine (il ne s'agit que d'un simple ajout à partir de la ligne de commande - la nouvelle chaîne sera ajoutée au paquet final p12)

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

Demander une nouvelle clé client et un nouveau certificat

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

Signer et tester le certificat client avec une autorité intermédiaire

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

Certificat client du package

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

Renommer des pkcs pour les importer dans iOS à partir d'un email/iTunes

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

Demander une nouvelle clé de serveur et un nouveau certificat

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

Signer et tester le certificat du serveur avec une autorité intermédiaire

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

Certificat de serveur de package

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

Voici les fichiers cfg: 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

Intermédiaire

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. Transférer des certificats sur un iPad

Ref: comment enregistrer l'application pour ouvrir le fichier pdf dans mon application dans ipad

Apple vous recommande d'enregistrer un nouveau type de fichier géré par votre application et de transférer un fichier p12 renommé avec la nouvelle extension personnalisée sur un périphérique (manuellement ou par courrier électronique) pour installer les certificats de client. Le fichier p12 doit inclure la chaîne de cert publique ainsi que les informations de cert du client telles que définies à l'étape 1 ci-dessus. Lorsque vous essayez d'ouvrir un tel fichier, le périphérique envoie au délégué de votre application un démarrage/réveil que vous devez gérer (pas dans didload car il pourrait s'agir d'un réveil).

Cela a un peu changé avec les versions 8 ou 9, mais je dois prendre en charge 7, il s'agit donc du gestionnaire obsolète. Même solution cependant et cela commence par l’ajout au fichier plist de l’application comme indiqué dans les captures d’écran ci-dessous.

Notez que vous aurez besoin de deux nouvelles icônes et d'une extension de fichier qui ne seront probablement pas revendiquées par une autre application.

 enter image description here

 enter image description here

Ensuite, vous avez besoin du délégué/gestionnaire qui devrait être explicite. Comme cette partie n'a rien à voir avec le flux de contrôle normal, je gère tout le traitement des délégués dans AppDelegate.m. (est-ce si faux?) Configurez les méthodes/variables selon vos besoins et ignorez la vérification supplémentaire paranoïde de l'existence du fichier ...

Réf.: 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. Enregistrer les informations d'identification reçues dans le trousseau d'applications

Maintenant que vous avez les données p12 brutes, il devrait être simple de savoir quoi faire ensuite ... NON. Toute la documentation semble être pour le nom/pwd de stockage et un nombre effrayant d’affiches suggèrent de sauvegarder le certificat du serveur dans le système de fichiers, ce qui est correct mais n’a aucun sens quand vous avez le trousseau et Apple dit que c’est à quoi cela sert. Enfin et surtout, comment faites-vous la distinction entre les certs stockés et comment les mettez-vous à jour?

En résumé, j’ai décidé de faire une suppression/réenregistrement complète après avoir essayé toutes sortes de choses qui ne fonctionnaient pas pour vérifier s’il s’agissait d’une mise à jour ou d’un chargement initial - c’est ce que je voulais faire en premier lieu car c’est mon travail. chaîne de l'application. Tout cela relève des FC et je n’utilise pas ARC car je refuse de porter tout ce que je n’ai pas à faire. Autant que je sache, tant que vous allouez des FC, des acteurs à NS et des CFRelease après utilisation, aucun avertissement n’est émis.

Ce sont des références clés:

Énumérer tous les éléments de trousseau de mon application iOS

[essentiel pour aider à visualiser à quoi ressemble votre trousseau]

Comment supprimer tous les éléments de trousseau accessibles à une application?

Qu'est-ce qui rend un élément de trousseau unique (sous iOS)?

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

[ https://developer.Apple.com/library/ios/samplecode/AdvancedURLConnections/Listings/Credentials_m.html , qui indique:

// 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.

Le résumé est que (duh) la chose la plus simple à faire est d’attribuer une étiquette au certificat afin que vous puissiez le rechercher de manière unique et vous rendre compte que si vous enregistrez une identité, elle sera automatiquement divisée en clé et en certificat, ce qui peut - non bien sûr - ont entraîné des difficultés de remplacement.

Le code (l'explication suit):

- (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"];
}

où kClientIdentityLabel et kServerCertificateLabel sont des étiquettes arbitraires.

Les fonctions kSec sont trop nombreuses/complexes pour être expliquées ici en détail. Il suffit de dire que tout est effacé, puis l'identité du client extraite est enregistrée, suivie de l'extraction de l'autorité de certification racine, qui est ensuite enregistrée séparément. Pourquoi la boucle? parce que je ne savais pas s'il était techniquement correct de supposer que la racine se trouvait à la fin de la chaîne, mais ce sera le cas si je génère la p12 afin que le code soit là pour l'instant.

Notez que les erreurs de kSec sont codées, ce site est donc indispensable: https://www.osstatus.com

4. Récupérez les informations d'identification enregistrées dans le trousseau

Une fois que les informations d'identification sont dans le trousseau, vous pouvez les extraire de cette façon (les modes de défaillance laissent à désirer):

- (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. Authentifiez le certificat du serveur et renvoyez les informations d'identification du client

Hoo boy. Ceci est une édition pour expliquer comment utiliser réellement les certificats récupérés (c'était supposé être la partie la plus évidente ...)

Premièrement, la documentation déjà douteuse d’Apple est obsolète par le nouveau cadre de sécurité du transport d’applications (voir par exemple: http://useyourloaf.com/blog/app-transport-security/ ). Je n'entrerai pas dans les détails, mais l'idée est de forcer tout le monde à toujours utiliser https et les certificats de confiance par défaut. Dans mon scénario, avec l'identification de certificat et l'authentification mutuelle entre des clients dédiés et un serveur privé, vous pouvez désactiver cette fonctionnalité en toute sécurité en ajoutant un dictionnaire à votre liste de plis, comme suit:

 enter image description here

Ensuite, à l'étape 4, vous disposiez déjà des informations d'identification du client pour répondre immédiatement à ce défi, mais le certificat de serveur flotte en tant que NSData au format DER créé par SecCertificateCopyData et il est difficile de savoir ce qui doit se passer lorsque ce défi arrive.

Il s'avère que ce que vous êtes censé faire est d'implémenter l'algorithme de la section 6 du "standard X.509" ( https://tools.ietf.org/html/rfc5280 ). Heureusement, cela est implémenté dans les coulisses par la fonction iOS SecTrustEvaluate mais il y a un échafaudage à construire et des éléments étranges à comprendre.

[Petit problème - manque d'espace !! Ajout d'une nouvelle question, y compris la fin de cette étape.]

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

[Poursuivant d'autres publications]

Alors c'est tout. Désolé pour la qualité de production pas assez, mais je voulais clouer cela ensemble alors qu'il était encore frais dans mon esprit. Je mettrai à jour le message si je trouve des erreurs.

J'espère que cela vous aidera et voici un dernier lien vers un très bon livre qui, entre autres choses, vous donnera la chair de poule pour faire confiance aux CA commerciaux ...

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

12
saminpa

[Je viens (!) De me rendre compte que je peux ajouter une autre réponse puisque le lien de continuation a été voté par défaut et fermé et qu'il y a deux demandes d'informations supplémentaires qui ne rentrent pas dans la liste ci-dessus. La réponse ci-dessous commence par la question du message supprimé.]

… La partie sur laquelle je ne comprends toujours pas, c'est pourquoi j'ai dû créer une nouvelle relation de confiance et une nouvelle stratégie pour mettre en œuvre un certificat d'ancrage et un épinglage.

Si j’ajoutais simplement l’ancre à la confiance reçue du serveur, je ne pouvais pas renvoyer correctement le pointeur sur le NSURLCredential reçu du serveur, il semblait avoir été modifié et rejeté par l’expéditeur (?).

La question est de savoir si ce traitement est vraiment approprié ou peut-il être condensé? Cela devient un peu fatiguant, mais je ne veux pas accepter quelque chose simplement parce que cela "fonctionne". Ma solution actuelle est indiquée ci-dessous.

À l'étape 4, vous disposiez déjà des informations d'identification du client pour répondre à ce type de défi sans manipulation, mais le certificat de serveur flotte en tant que NSData au format DER créé par SecCertificateCopyData et il est difficile de savoir ce qui devrait se passer lorsque ce défi arrive.

Il s'avère que ce que vous êtes censé faire est d'implémenter l'algorithme de la section 6 du "standard X.509" ( https://tools.ietf.org/html/rfc5280 ). Heureusement, cela est implémenté dans les coulisses par la fonction iOS SecTrustEvaluate mais il y a un échafaudage à construire et des éléments étranges à comprendre. Commencez par le code (en suivant le chapeau de la source d'origine):

SecTrustEvaluate renvoie toujours kSecTrustResultRecoverableTrustFailure avec SecPolicyCreateSSL

    - (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];
}

Donc, je vérifie d’abord si le certificat de serveur a été chargé (sinon, le processus est effectué via la méthode conventionnelle d’autorité de certification approuvée).

Ensuite, vous sélectionnez "l'objet de confiance" à évaluer. J'ai omis de le faire sans créer une copie de travail de l'objet de confiance que j'ai reçu du serveur, mais il a en quelque sorte foiré la référence 'NSURLCredential * credential = [NSURLCredential credentialForTrust: serverTrust]' si je l'ai utilisé directement. Il semble cependant, d'après les documents Apple vraiment horribles, qu'il s'agisse d'une approche casher (pour ma part, je recommande de survoler la RFC x.509 si vous voulez en comprendre une partie).

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-SECCUSTOMROOTho[2]

La confiance nécessite une "stratégie", la chaîne de certificats entrante à évaluer et un ou plusieurs "certificats d'ancrage" qui définissent fondamentalement l'origine dans un système de coordonnées arbitraires. Tout est validé en aval du point zéro, même s'il ne s'agit pas d'un certificat racine. .

Donc, vous chargez la chaîne entrante et le certificat stocké dans des tableaux à fournir à la nouvelle approbation et créez une nouvelle stratégie avec SecPolicyCreateSSL - ceci définit un indicateur pour indiquer que le certificat doit être vérifié pour avoir été émis pour serverAuth et que le serveur entrant nom doit être ignoré (pour permettre une certaine flexibilité de l’infrastructure).

Vous créez ensuite la nouvelle approbation à l'aide de la nouvelle stratégie et du tableau de certificats à authentifier. Ensuite, vous définissez l'ancre et assurez-vous que la chaîne ne sera évaluée que par rapport à votre cert d'ancrage, et pas uniquement dans le trousseau iOS.

Lorsque vous évaluez la confiance, il peut sembler étrange que vous acceptiez kSecTrustResultUnspecified et que vous ne procédiez pas ou que vous obteniez un résultat plus positif. En fait, continuer signifie que vous suivez les remplacements de l'interface utilisateur, ce qui est vraiment mauvais; non spécifié signifie simplement qu'il n'y a rien de mal selon la politique spécifiée.

Enfin, vous renvoyez les informations d'identification de l'objet de confiance entrant (pas le nouvel) et tout devrait être en or ...

0
saminpa