web-dev-qa-db-fra.com

Comment accepter un certificat SSL auto-signé à l'aide de NSURLSession d'iOS 7 et de sa famille de méthodes de délégation à des fins de développement?

Je développe une application iPhone. Pendant le développement, je dois me connecter à un serveur utilisant un certificat SSL auto-signé. Je suis à peu près certain que - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler est pour moi l'occasion d'écrire un code d'exception pour permettre cela. Cependant, je ne trouve aucune ressource qui me dise comment faire cela. Je peux voir l'erreur suivante dans le journal:

NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)

En plus de cela, quand je NSLog(@"error = %@", error); de la méthode déléguée ci-dessus, je reçois:

Error Domain = NSURLErrorDomain Code = -1202 "Le certificat de Ce serveur est invalide. Vous vous connectez peut-être à un serveur qui Se fait passer pour" api.mydevelopmenturl.com ", ce qui pourrait rendre votre .__ confidentiel. information à risque. " UserInfo = 0x10cbdbcf0 {NSUnderlyingError = 0x112ec9730 "Le certificat de ce serveur est Invalide. Vous vous connectez peut-être à un serveur prétendant être “ Api.mydevelopmenturl.com ”qui pourrait mettre en péril vos informations confidentielles ", NSErrorFailingURLStringKey = https://api.mydevelopmenturl.com/posts , NSErrorFailingURLKey = https://api.mydevelopmenturl.com/posts , NSLocalizedRecoverySuggestion = Voulez-vous vous connecter au le serveur quand même ?, NSURLErrorFailingURLPeerTrustErrorKey =, NSLocalizedDescription = Le certificat pour ce serveur n'est pas valide . Vous vous connectez peut-être à un serveur prétendant être “Api.mydevelopmenturl.com” qui pourrait mettre votre confidentialité information à risque.}

Des idées sur la façon de résoudre ce problème? S'il vous plaît, postez le code car j'ai lu les documents conceptuels et je ne les comprends pas. En voici un exemple qui me dépasse: https://developer.Apple.com/library/content/technotes/tn2232/_index.html

54
John Erck

Cela fonctionne pour moi:

NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:Nil];
...
...
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{
  if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
    if([challenge.protectionSpace.Host isEqualToString:@"mydomain.com"]){
      NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
      completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
    }
  }
}
105
friherd

Apple a une note technique 2232 qui est très informative et explique en détail évaluation de la confiance du serveur HTTPS .

Dans ce cas, l'erreur -1202 dans le domaine NSURLErrorDomain est NSURLErrorServerCertificateUntrusted, ce qui signifie que l'évaluation de la confiance du serveur a échoué. Vous pourriez également recevoir une variété d'autres erreurs; Annexe A: Erreurs courantes dans l'évaluation de la confiance du serveur } répertorie les plus courantes.

De la note technique:

Dans la plupart des cas, le meilleur moyen de résoudre une évaluation de confiance du serveur l'échec est de réparer le serveur. Cela a deux avantages: il offre le meilleure sécurité et cela réduit la quantité de code que vous devez écrire. Le Le reste de cette note technique décrit comment vous pouvez diagnostiquer la confiance du serveur échecs d’évaluation et, s’il n’est pas possible de réparer le serveur, comment vous pouvez personnaliser l'évaluation de la confiance du serveur pour autoriser votre connexion à procéder sans nuire complètement à la sécurité de l'utilisateur.

La partie relative à cette question est la section sur évaluation de la confiance du serveur NSURLSession :

NSURLSession vous permet de personnaliser l'évaluation de la confiance du serveur HTTPS par implémenter le -URLSession:didReceiveChallenge:completionHandler: méthode déléguée. Pour personnaliser l'évaluation de la confiance du serveur HTTPS, recherchez un défi dont l’espace de protection dispose d’une méthode d’authentification de NSURLAuthenticationMethodServerTrust. Pour ces défis, résolvez comme décrit ci-dessous. Pour d'autres défis, ceux que vous n'avez pas Attention, appelez le bloc de gestion d’achèvement avec le NSURLSessionAuthChallengePerformDefaultHandling disposition et NULL accréditation.

Lorsque vous traitez avec NSURLAuthenticationMethodServerTrust défi d'authentification, vous pouvez obtenir l'objet de confiance à partir du fichier contester l’espace de protection en appelant la méthode -serverTrust. Après utiliser l'objet de confiance pour créer votre propre confiance de serveur HTTPS personnalisée évaluation, vous devez résoudre le problème de deux manières:

Si vous souhaitez refuser la connexion, appelez le bloc du gestionnaire d'achèvement avec la disposition NSURLSessionAuthChallengeCancelAuthenticationChallenge et un identifiant NULL.

Si vous souhaitez autoriser la connexion, créez une information d'identification à partir de votre objet de confiance (en utilisant +[NSURLCredential credentialForTrust:]) et appelez le bloc du gestionnaire d'achèvement avec cette information d'identification et le NSURLSessionAuthChallengeUseCredential disposition.

Le résultat de tout cela est que si vous implémentez la méthode de délégation suivante, vous pouvez remplacer la confiance du serveur pour un serveur particulier:

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
    if([challenge.protectionSpace.authenticationMethod
                           isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        if([challenge.protectionSpace.Host
                           isEqualToString:@"domaintoverride.com"])
        {
            NSURLCredential *credential = 
                          [NSURLCredential credentialForTrust:
                                          challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
        }
        else
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}

Notez que vous devez gérer à la fois le cas de l'hôte correspondant à celui que vous souhaitez remplacer par et dans tous les autres cas. Si vous ne gérez pas la partie "tous les autres cas", le résultat du comportement n'est pas défini. 

24
memmons

Recherchez en ligne une autorité de certification SSL de confiance offrant un essai gratuit de 90 jours pour les nouveaux certificats. Installez le certificat sur votre serveur. Vous avez maintenant 90 jours pour développer votre application à un point tel que vous pouvez décider si cela vaut la peine de payer de l'argent pour "renouveler" le certificat. C'est la meilleure réponse pour moi car ma décision d'utiliser le certificat auto-signé était motivée par des considérations financières et que 90 jours me laissaient suffisamment de temps pour développer mon application à un point tel que je peux décider s'il vaut la peine de dépenser de l'argent pour un certificat SSL . Cette approche évite d'avoir à gérer les implications en termes de sécurité liées à l'exécution d'une base de code modifiée pour accepter les certificats auto-signés. Sucré! Yay pour le démarrage!

11
John Erck

Faites-vous une énorme faveur et ne le faites pas.

Commencez par lire le document le code le plus dangereux au monde: validation des certificats SSL dans des logiciels autres que des navigateurs} , en particulier la section 10, "Interruption ou désactivation de la validation des certificats". Il appelle spécifiquement un blog relatif à Cocoa qui décrit spécifiquement comment faire ce que vous demandez.

Mais non. Désactiver la vérification des certificats SSL revient à introduire une bombe à retardement dans votre application. Parfois, un jour, il restera accidentellement activé, et une construction entrera dans la nature. Et ce jour-là, vos utilisateurs seront sérieusement menacés.

Vous devez plutôt utiliser un certificat, signé avec un certificat intermédiaire que vous pouvez installer et faire confiance sur ce périphérique spécifique, ce qui permettra à la validation SSL de réussir sans mettre en danger un autre périphérique que le vôtre (et seulement de manière temporaire).

9
Shaggy Frog

Identique à la solution de friherd mais dans Swift:

func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust{
        let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
        completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential);
    }
}
6
eric

Pour Swift 3.0/4

Si vous souhaitez autoriser n'importe quel type de certificat auto-signé, vous pouvez utiliser l'approche suivante pour implémenter un URLSessionDelegate . Apple fournit des informations supplémentaires sur l'utilisation de l'URLSessionDelegate pour tous les types de méthodes d'authentification: https : //developer.Apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/AuthenticationChallenges.html

Dans un premier temps, implémentez la méthode delegate et assignez un délégué correspondant:

let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
let task = urlSession.dataTask(with: urlRequest).resume()

Maintenant, implémentez la méthode du délégué https://developer.Apple.com/documentation/foundation/nsurlsessiondelegate/1409308-urlsession?language=objc

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

    guard challenge.previousFailureCount == 0 else {
        challenge.sender?.cancel(challenge)
        // Inform the user that the user name and password are incorrect
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }

    // Within your authentication handler delegate method, you should check to see if the challenge protection space has an authentication type of NSURLAuthenticationMethodServerTrust
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
       // and if so, obtain the serverTrust information from that protection space.
       && challenge.protectionSpace.serverTrust != nil
       && challenge.protectionSpace.Host == "yourdomain.com" {
        let proposedCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
        completionHandler(URLSession.AuthChallengeDisposition.useCredential, proposedCredential)
    }
}

Néanmoins, vous pouvez adapter l'acceptation de tout certificat auto-signé pour votre domaine fourni à un nom très spécifique. Assurez-vous que vous avez déjà ajouté ce certificat à votre groupe de cibles de construction. Je l'ai nommé ici "cert.cer"

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

    guard challenge.previousFailureCount == 0 else {
        challenge.sender?.cancel(challenge)
        // Inform the user that the user name and password are incorrect
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }

    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
       && challenge.protectionSpace.serverTrust != nil
       && challenge.protectionSpace.Host == "yourdomain.com" {

        if let trust = challenge.protectionSpace.serverTrust,
           let pem = Bundle.main.url(forResource:"cert", withExtension: "cer"),
           let data = NSData(contentsOf: pem),
           let cert = SecCertificateCreateWithData(nil, data) {
            let certs = [cert]
            SecTrustSetAnchorCertificates(trust, certs as CFArray)
            var result=SecTrustResultType.invalid
            if SecTrustEvaluate(trust,&result)==errSecSuccess {
              if result==SecTrustResultType.proceed || result==SecTrustResultType.unspecified {
                let proposedCredential = URLCredential(trust: trust)
                completionHandler(.useCredential,proposedCredential)
                return
              }
            }

        }
    }
    completionHandler(.performDefaultHandling, nil)
}
3
Lepidopteron

il suffit d’ajouter .cer à SecTrust et il passe sur ATS

class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate {

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

        if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
            if let trust = challenge.protectionSpace.serverTrust,
               let pem = Bundle.main.path(forResource: "https", ofType: "cer"),
               let data = NSData(contentsOfFile: pem),
               let cert = SecCertificateCreateWithData(nil, data) {
                let certs = [cert]
                SecTrustSetAnchorCertificates(trust, certs as CFArray)

                completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: trust))
                return
            }
        }

        // Pinning failed
        completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
}
1
yycking

Voici la solution qui a fonctionné pour moi . Vous devez accepter la connexion via le délégué de la connexion, y compris les deux messages:

- (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])
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

Veuillez noter que cela ne vérifie pas la fiabilité du certificat. Par conséquent, seul le cryptage SSL de la connexion HTTPS est intéressant, mais le pouvoir de signature n'est pas pris en compte ici, ce qui peut réduire la sécurité.

0
Maxime T

Cela fonctionne bien pour moi de passer auto-signé:

Delegate : NSURLSessionDelegate

- (void)URLSession:(NSURLSession *)session **task**:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
0
Riyaz shaik riyaz

mise à jour xcode 9

    var result:(message:String, data:Data?) = (message: "Fail", data: nil)
    var request = URLRequest(url: url)

    let sessionDelegate = SessionDelegate()
    let session = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil)
    let task = session.dataTask(with: request){(data, response, error) in


    }
    task.resume()

la tâche déléguée

    class SessionDelegate:NSObject, URLSessionDelegate
    {

        func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
            if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust)
            {
                print(challenge.protectionSpace.Host) 
                if(challenge.protectionSpace.Host == "111.11.11.11")
                {
                    let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
                   completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential)
                }
            }

        }
    }
0
luhuiya

Une meilleure solution consiste peut-être à donner à l'utilisateur la possibilité d'accepter le certificat confirmant (visuellement) que l'URL est exacte pour le service utilisé. Par exemple, si l'hôte est entré dans un paramètre d'application, testez-le à l'entrée de l'utilisateur et laissez-le décider à cet endroit. 

Considérez que cette tactique de "confirmation de l'utilisateur" est utilisée par Safari, donc approuvée par Apple, il serait logique qu'elle soit utilisée logiquement pour d'autres applications. 

Suggérez de creuser dans NSErrorRecoveryAttempting (je ne le fais pas moi-même) http://Apple.co/22Au1GR

Obtenez l'hôte confirmé, puis prenez le chemin d'exclusion d'URL individuel mentionné ci-après. En fonction de la mise en œuvre, il peut également être judicieux de stocker l'hôte en tant qu'exclusion pour pouvoir le consulter ultérieurement. 

Cela semble être quelque chose qu'Apple aurait implémenté par nature dans Cocoa, mais pour l'instant, je n'ai pas trouvé de «bouton facile». Aurait aimé un indicateur "kLetUserDecide" sur quelque chose dans NSURL ou NSURLSession au lieu que tout le monde ait à implémenter la méthode déléguée ainsi que le protocole NSErrorRecoveryAttempting.

0
William Cerniuk