Comment utiliser NSURLConnection pour se connecter avec SSL pour un certificat non approuvé?

300

J'ai le code simple suivant pour me connecter à une page Web SSL

NSMutableURLRequest *urlRequest=[NSMutableURLRequest requestWithURL:url];
[ NSURLConnection sendSynchronousRequest: urlRequest returningResponse: nil error: &error ];

Sauf qu'il donne une erreur si le certificat est auto-signé Error Domain=NSURLErrorDomain Code=-1202 UserInfo=0xd29930 "untrusted server certificate".Existe-t-il un moyen de le configurer pour accepter les connexions de toute façon (tout comme dans un navigateur, vous pouvez appuyer sur Accepter) ou un moyen de le contourner?

erotsppa
la source

Réponses:

415

Il existe une API prise en charge pour cela! Ajoutez quelque chose comme ça à votre NSURLConnectiondélégué:

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
  return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
  if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    if ([trustedHosts containsObject:challenge.protectionSpace.host])
      [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

  [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

Notez que connection:didReceiveAuthenticationChallenge:peut envoyer son message à challenge.sender (beaucoup) plus tard, après avoir présenté une boîte de dialogue à l'utilisateur si nécessaire, etc.

Gordon Henriksen
la source
31
Merci beaucoup, cela fonctionne parfaitement. Supprimez simplement les deux ifs et ne conservez que la partie useCendential dans le rappel didReceiveAuthentificationChallenge si vous souhaitez accepter un site https.
yonel
19
qu'est-ce qu'un trustHosts, où n comment l'objet est-il défini
Ameya
7
Ameya, ce serait un NSArray d'objets NSString. Les chaînes sont les noms d'hôte comme @ "google.com".
William Denniss
19
Ce code fonctionne bien. Mais notez que l'intérêt d'avoir des certificats valides est d'empêcher les attaques de l'homme du milieu. Soyez donc conscient que si vous utilisez ce code, quelqu'un peut usurper le soi-disant "hôte de confiance". Vous bénéficiez toujours des fonctionnalités de cryptage des données de SSL, mais vous perdez les fonctionnalités de validation d'identification de l'hôte.
William Denniss
42
Ces méthodes sont désormais considérées comme obsolètes depuis iOS 5.0 et Mac OS X 10.6. La -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challengeméthode doit être utilisée à la place.
Andrew R.
36

Si vous n'êtes pas disposé (ou incapable) d'utiliser des API privées, il existe une bibliothèque open source (licence BSD) appelée ASIHTTPRequest qui fournit un wrapper autour du niveau inférieur CFNetwork APIs. Ils ont récemment introduit la possibilité d'autoriser l' HTTPS connectionsutilisation de certificats auto-signés ou non approuvés avec l' -setValidatesSecureCertificate:API. Si vous ne voulez pas récupérer toute la bibliothèque, vous pouvez utiliser la source comme référence pour implémenter vous-même la même fonctionnalité.

Nathan de Vries
la source
2
Tim, vous pouvez vous retrouver à vouloir utiliser async pour d'autres raisons de toute façon (comme être en mesure d'afficher une barre de progression), je trouve pour toutes les demandes, sauf la plus simple, c'est ma façon de procéder. Donc, vous devriez peut-être simplement implémenter Async maintenant et éviter les tracas plus tard.
William Denniss
Voir ceci pour l'implémentation (mais utilisez [r setValidatesSecureCertificate: NO];): stackoverflow.com/questions/7657786/…
Sam Brodkin
Désolé d'avoir ramené ce sujet. Mais depuis que l'iOS 5 a introduit les fonctionnalités ARC. Comment puis-je faire en sorte que cela fonctionne maintenant?
Melvin Lai
Pourriez-vous s'il vous plaît vérifier ceci: stackoverflow.com/q/56627757/1364053
nr5
33

Idéalement, il ne devrait y avoir que deux scénarios dans lesquels une application iOS devrait accepter un certificat non approuvé.

Scénario A: vous êtes connecté à un environnement de test qui utilise un certificat auto-signé.

Scénario B: vous utilisez le HTTPStrafic proxy à l'aide d'un MITM Proxy like Burp Suite, Fiddler, OWASP ZAP, etc.proxy Les proxy renverront un certificat signé par une autorité de certification auto-signée afin que le proxy puisse capturer le HTTPStrafic.

Les hôtes de production ne doivent jamais utiliser de certificats non approuvés pour des raisons évidentes .

Si vous avez besoin que le simulateur iOS accepte un certificat non approuvé à des fins de test, il est fortement recommandé de ne pas modifier la logique d'application afin de désactiver la validation de certificat intégrée fournie par le NSURLConnection API. Si l'application est mise à la disposition du public sans supprimer cette logique, elle sera sensible aux attaques de l'homme du milieu.

La méthode recommandée pour accepter des certificats non approuvés à des fins de test consiste à importer le certificat de l'autorité de certification (CA) qui a signé le certificat sur votre simulateur iOS ou votre appareil iOS. J'ai rédigé un petit article de blog qui montre comment faire cela sur un simulateur iOS:

acceptation de certificats non approuvés à l'aide du simulateur ios

user890103
la source
1
Homme de trucs génial. Je suis d'accord, il est si facile d'oublier de désactiver cette logique d'application spéciale pour accepter tout certificat non approuvé.
Tomasz
"Idéalement, il ne devrait y avoir que deux scénarios où une application iOS devrait accepter un certificat non approuvé." - Que diriez-vous de rejeter un bon certificat «réclamé» lors de l'épinglage d'un certificat? Confer: Dignotar (pwn'd) et Trustwave (renommée MitM).
jww
Entièrement d'accord avec votre déclaration sur l'oubli de supprimer le code. L'ironie est qu'il est beaucoup plus facile de faire ce changement dans le code que d'amener le simulateur à accepter des certificats auto-signés.
devios1
12

NSURLRequesta une méthode privée appelée setAllowsAnyHTTPSCertificate:forHost:, qui fera exactement ce que vous souhaitez. Vous pouvez définir la allowsAnyHTTPSCertificateForHost:méthode NSURLRequestvia une catégorie et la définir pour renvoyer YESl'hôte que vous souhaitez remplacer.

Nathan de Vries
la source
Les mises en garde habituelles concernant les API non documentées s'appliquent ... mais bon de savoir que c'est possible.
Stephen Darlington
Ouais, absolument. J'ai ajouté une autre réponse qui n'implique pas l'utilisation d'API privées.
Nathan de Vries
Est-ce que cela fonctionne lorsque vous utilisez "NSURLConnection sendSynchronousRequest:"?
Tim Büthe
11

Pour compléter la réponse acceptée, pour une bien meilleure sécurité, vous pouvez ajouter votre certificat de serveur ou votre propre certificat d'autorité de certification racine au trousseau ( https://stackoverflow.com/a/9941559/1432048 ), mais cela ne suffira pas à rendre NSURLConnection authentifiez votre serveur auto-signé automatiquement. Vous devez toujours ajouter le code ci-dessous à votre délégué NSURLConnection, il est copié à partir de l'exemple de code Apple AdvancedURLConnections , et vous devez ajouter deux fichiers (Credentials.h, Credentials.m) à partir de l'exemple de code Apple à vos projets.

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//        if ([trustedHosts containsObject:challenge.protectionSpace.host])

    OSStatus                err;
    NSURLProtectionSpace *  protectionSpace;
    SecTrustRef             trust;
    SecTrustResultType      trustResult;
    BOOL                    trusted;

    protectionSpace = [challenge protectionSpace];
    assert(protectionSpace != nil);

    trust = [protectionSpace serverTrust];
    assert(trust != NULL);
    err = SecTrustEvaluate(trust, &trustResult);
    trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified));

    // If that fails, apply our certificates as anchors and see if that helps.
    //
    // It's perfectly acceptable to apply all of our certificates to the SecTrust
    // object, and let the SecTrust object sort out the mess.  Of course, this assumes
    // that the user trusts all certificates equally in all situations, which is implicit
    // in our user interface; you could provide a more sophisticated user interface
    // to allow the user to trust certain certificates for certain sites and so on).

    if ( ! trusted ) {
        err = SecTrustSetAnchorCertificates(trust, (CFArrayRef) [Credentials sharedCredentials].certificates);
        if (err == noErr) {
            err = SecTrustEvaluate(trust, &trustResult);
        }
        trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified));
    }
    if(trusted)
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}

[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
Xiang
la source
10

Je ne peux pas prendre de crédit pour cela, mais celui que j'ai trouvé fonctionnait très bien pour mes besoins. shouldAllowSelfSignedCertest ma BOOLvariable. Ajoutez simplement à votre NSURLConnectiondélégué et vous devriez être rockin pour un contournement rapide sur une base par connexion.

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)space {
     if([[space authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust]) {
          if(shouldAllowSelfSignedCert) {
               return YES; // Self-signed cert will be accepted
          } else {
               return NO;  // Self-signed cert will be rejected
          }
          // Note: it doesn't seem to matter what you return for a proper SSL cert
          //       only self-signed certs
     }
     // If no other authentication is required, return NO for everything else
     // Otherwise maybe YES for NSURLAuthenticationMethodDefault and etc.
     return NO;
}
Ryna
la source
10

Dans iOS 9, les connexions SSL échoueront pour tous les certificats non valides ou auto-signés. Il s'agit du comportement par défaut de la nouvelle fonctionnalité App Transport Security dans iOS 9.0 ou version ultérieure, et sur OS X 10.11 et version ultérieure.

Vous pouvez remplacer ce comportement dans le Info.plist, en définissant NSAllowsArbitraryLoadssur YESdans le NSAppTransportSecuritydictionnaire. Cependant, je recommande de remplacer ce paramètre à des fins de test uniquement.

entrez la description de l'image ici

Pour plus d'informations, voir App Transport Technote ici .

johnnieb
la source
La seule solution a fonctionné pour moi, je n'ai aucun moyen de changer le cadre Firebase pour répondre à mes besoins, cela l'a résolu, merci!
Yitzchak
Maintenant, j'ai vu que Google demandait NSAllowArbitraryLoads = YES pour Admob (dans Firebase). firebase.google.com/docs/admob/ios/ios9
Yitzchak
6

La solution de contournement de catégorie publiée par Nathan de Vries passera les vérifications de l'API privée AppStore et est utile dans les cas où vous n'avez pas le contrôle de l' NSUrlConnectionobjet. Un exemple est celui NSXMLParserqui ouvrira l'URL que vous fournissez, mais n'expose pas le NSURLRequestou NSURLConnection.

Dans iOS 4, la solution de contournement semble toujours fonctionner, mais uniquement sur l'appareil, le simulateur n'invoque plus la allowsAnyHTTPSCertificateForHost:méthode.

Alex Suzuki
la source
6

Vous devez utiliser NSURLConnectionDelegatepour autoriser les connexions HTTPS et il y a de nouveaux rappels avec iOS8.

Obsolète:

connection:canAuthenticateAgainstProtectionSpace:
connection:didCancelAuthenticationChallenge:
connection:didReceiveAuthenticationChallenge:

Au lieu de cela, vous devez déclarer:

connectionShouldUseCredentialStorage: - Envoyé pour déterminer si le chargeur d'URL doit utiliser le stockage des informations d'identification pour authentifier la connexion.

connection:willSendRequestForAuthenticationChallenge: - Indique au délégué que la connexion enverra une demande de défi d'authentification.

Avec, willSendRequestForAuthenticationChallengevous pouvez utiliser challengecomme vous l'avez fait avec les méthodes obsolètes, par exemple:

// Trusting and not trusting connection to host: Self-signed certificate
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
ricardopereira
la source
Pourriez-vous s'il vous plaît vérifier ceci: stackoverflow.com/q/56627757/1364053
nr5
3

J'ai posté un code essentiel (basé sur le travail de quelqu'un d'autre que je note) qui vous permet de vous authentifier correctement contre un certificat auto-généré (et comment obtenir un certificat gratuit - voir les commentaires en bas de Cocoanetics )

Mon code est ici github

David H
la source
Pourriez-vous s'il vous plaît vérifier ceci: stackoverflow.com/q/56627757/1364053
nr5
2

Si vous souhaitez continuer à utiliser sendSynchronousRequest, je travaille dans cette solution:

FailCertificateDelegate *fcd=[[FailCertificateDelegate alloc] init];

NSURLConnection *c=[[NSURLConnection alloc] initWithRequest:request delegate:fcd startImmediately:NO];
[c setDelegateQueue:[[NSOperationQueue alloc] init]];
[c start];    
NSData *d=[fcd getData];

vous pouvez le voir ici: Connexion Synchrone SSL Objective-C

jgorozco
la source
1

Avec AFNetworking, j'ai utilisé avec succès le service Web https avec le code ci-dessous,

NSString *aStrServerUrl = WS_URL;

// Initialize AFHTTPRequestOperationManager...
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];

[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
manager.securityPolicy.allowInvalidCertificates = YES; 
[manager POST:aStrServerUrl parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
{
    successBlock(operation, responseObject);

} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
    errorBlock(operation, error);
}];
cjd
la source
1

Vous pouvez utiliser ce code

-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
     if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodServerTrust)
     {
         [[challenge sender] useCredential:[NSURLCredential credentialForTrust:[[challenge protectionSpace] serverTrust]] forAuthenticationChallenge:challenge];
     }
}

Utiliser à la -connection:willSendRequestForAuthenticationChallenge:place de ces méthodes obsolètes

Obsolète:

-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace  
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
-(void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
Vaibhav Sharma
la source