Como usar o NSURLConnection para se conectar ao SSL para um certificado não confiável?

300

Eu tenho o seguinte código simples para conectar-me a uma página SSL

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

Exceto se houver um erro se o certificado for autoassinado. Error Domain=NSURLErrorDomain Code=-1202 UserInfo=0xd29930 "untrusted server certificate".Existe uma maneira de configurá-lo para aceitar conexões de qualquer maneira (como em um navegador que você pode pressionar em aceitar) ou uma maneira de ignorá-lo?

erotsppa
fonte

Respostas:

415

Há uma API suportada para fazer isso! Adicione algo assim ao seu NSURLConnectiondelegado:

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

Observe que connection:didReceiveAuthenticationChallenge:pode enviar sua mensagem para challenge.sender (muito) posteriormente, depois de apresentar uma caixa de diálogo ao usuário, se necessário, etc.

Gordon Henriksen
fonte
31
Muito obrigado, funciona perfeitamente. Apenas remova os dois ifs e mantenha apenas a parte useCendential no retorno de chamada didReceiveAuthentificationChallenge se desejar aceitar qualquer site https.
Página
19
o que é um TrustedHosts, onde n, como é o objeto definido
Ameya
7
Ameya, seria um NSArray de objetos NSString. As strings são os nomes de host como @ "google.com".
William Denniss
19
Esse código funciona bem. Mas observe que o objetivo de ter certificados válidos é impedir ataques do tipo intermediário. Portanto, esteja ciente de que, se você usar esse código, alguém poderá falsificar o chamado "host confiável". Você ainda obtém os recursos de criptografia de dados do SSL, mas perde o host para identificar os recursos de validação.
precisa
42
Agora, esses métodos são considerados obsoletos no iOS 5.0 e no Mac OS X 10.6. O -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challengemétodo deve ser usado em seu lugar.
Andrew R.
36

Se você não estiver disposto (ou não puder) a usar APIs privadas, há uma biblioteca de código aberto (licença BSD) chamada ASIHTTPRequest que fornece um wrapper em torno do nível inferior CFNetwork APIs. Eles recentemente introduziram a capacidade de permitir o HTTPS connectionsuso de certificados autoassinados ou não confiáveis ​​com a -setValidatesSecureCertificate:API. Se você não deseja extrair a biblioteca inteira, pode usar a fonte como referência para implementar a mesma funcionalidade.

Nathan de Vries
fonte
2
Tim, você pode querer usar o assíncrono por outros motivos de qualquer maneira (como poder mostrar uma barra de progresso), acho que para todos, exceto os pedidos mais simples, é assim que eu vou. Então, talvez você deva implementar o Async agora e salvar os problemas mais tarde.
precisa
Veja isso para a implementação (mas use [r setValidatesSecureCertificate: NO];): stackoverflow.com/questions/7657786/…
Sam Brodkin
Desculpe por trazer esse tópico de volta. Mas desde que o iOS 5 introduziu os recursos do ARC. Como posso fazer isso funcionar agora?
Melvin Lai
Poderia, por favor verifique o seguinte: stackoverflow.com/q/56627757/1364053
NR 5
33

Idealmente, deve haver apenas dois cenários em que um aplicativo iOS precisaria aceitar um certificado não confiável.

Cenário A: Você está conectado a um ambiente de teste que está usando um certificado autoassinado.

Cenário B: você está proxy de HTTPStráfego usando um proxy MITM Proxy like Burp Suite, Fiddler, OWASP ZAP, etc.que retornará um certificado assinado por uma CA autoassinada para que o proxy possa capturar HTTPStráfego.

Os hosts de produção nunca devem usar certificados não confiáveis ​​por razões óbvias .

Se você precisar que o simulador do iOS aceite um certificado não confiável para fins de teste, é altamente recomendável que você não altere a lógica do aplicativo para desativar a validação de certificado interna fornecida pelas NSURLConnectionAPIs. Se o aplicativo for lançado ao público sem remover essa lógica, ele estará suscetível a ataques do tipo man-in-the-middle.

A maneira recomendada de aceitar certificados não confiáveis ​​para fins de teste é importar o certificado da Autoridade de Certificação (CA) que assinou o certificado no seu Simulador iOS ou dispositivo iOS. Eu escrevi um post rápido no blog que demonstra como fazer isso no iOS Simulator:

aceitando certificados não confiáveis ​​usando o simulador ios

user890103
fonte
1
Homem material incrível. Eu concordo, é tão fácil esquecer a desativação dessa lógica de aplicativo especial para aceitar qualquer certificado não confiável.
Tomasz
"Idealmente, deve haver apenas dois cenários de quando um aplicativo iOS precisaria aceitar um certificado não confiável". - Que tal rejeitar um bom certificado 'reivindicado' ao fixá-lo? Conferência: Dignotar (pwn'd) e Trustwave (fama do MitM).
JWW
Concordo totalmente com a sua declaração sobre o esquecimento de remover o código. A ironia é que é muito mais fácil fazer essa alteração no código do que fazer com que o simulador aceite certificados autoassinados.
Devios1 17/05
12

NSURLRequesttem um método particular chamado setAllowsAnyHTTPSCertificate:forHost:, que fará exatamente o que você deseja. Você pode definir o allowsAnyHTTPSCertificateForHost:método por NSURLRequestmeio de uma categoria e configurá-lo para retornar YESpara o host que você deseja substituir.

Nathan de Vries
fonte
Advertências comuns sobre APIs não documentadas se aplicam ... mas é bom saber que é possível.
Stephen Darlington
Sim absolutamente. Adicionei outra resposta que não envolve o uso de APIs privadas.
Nathan de Vries
Isso funciona quando você usa "NSURLConnection sendSynchronousRequest:"?
Tim Büthe
11

Para complementar a resposta aceita, para uma segurança muito melhor, você pode adicionar seu certificado de servidor ou seu próprio certificado CA raiz às chaves ( https://stackoverflow.com/a/9941559/1432048 ), mas fazer isso sozinho não fará com que NSURLConnection autentique seu servidor autoassinado automaticamente. Você ainda precisa adicionar o código abaixo ao seu representante do NSURLConnection, ele é copiado do código de exemplo da Apple AdvancedURLConnections e você precisa adicionar dois arquivos (Credentials.h, Credentials.m) do código de exemplo da Apple aos seus projetos.

- (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
fonte
10

Não posso aceitar nenhum crédito por isso, mas este que achei funcionou muito bem para minhas necessidades. shouldAllowSelfSignedCerté minha BOOLvariável Basta adicionar ao seu NSURLConnectiondelegado e você deve estar agitado para um desvio rápido por conexão.

- (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
fonte
10

No iOS 9, as conexões SSL falharão para todos os certificados inválidos ou autoassinados. Esse é o comportamento padrão do novo recurso App Transport Security no iOS 9.0 ou posterior e no OS X 10.11 e posterior.

Você pode substituir esse comportamento no Info.plist, definindo NSAllowsArbitraryLoadspara YESno NSAppTransportSecuritydicionário. No entanto, recomendo substituir essa configuração apenas para fins de teste.

insira a descrição da imagem aqui

Para obter informações, consulte App Transport Technote aqui .

johnnieb
fonte
A única solução funcionou para mim, não tenho como alterar a estrutura do Firebase para atender às minhas necessidades, e isso resolveu, obrigado!
Yitzchak
Agora vi que o Google solicita NSAllowArbitraryLoads = YES para Admob (no Firebase). firebase.google.com/docs/admob/ios/ios9
Yitzchak
6

A solução alternativa da categoria publicada por Nathan de Vries passará nas verificações de API privadas da AppStore e é útil nos casos em que você não tem controle do NSUrlConnectionobjeto. Um exemplo é o NSXMLParserque abrirá o URL que você fornece, mas não expõe o NSURLRequestou NSURLConnection.

No iOS 4, a solução alternativa ainda parece funcionar, mas apenas no dispositivo, o Simulador não invoca mais o allowsAnyHTTPSCertificateForHost:método.

Alex Suzuki
fonte
6

Você precisa usar NSURLConnectionDelegatepara permitir conexões HTTPS e há novos retornos de chamada no iOS8.

Descontinuada:

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

Em vez disso, você precisa declarar:

connectionShouldUseCredentialStorage: - Enviado para determinar se o carregador de URL deve usar o armazenamento de credenciais para autenticar a conexão.

connection:willSendRequestForAuthenticationChallenge: - Informa ao delegado que a conexão enviará uma solicitação para um desafio de autenticação.

Com willSendRequestForAuthenticationChallengevocê pode usar challengecomo você fez com os métodos preteridos, por exemplo:

// 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
fonte
Poderia, por favor verifique o seguinte: stackoverflow.com/q/56627757/1364053
NR 5
3

Publiquei um código essencial (com base no trabalho de outra pessoa que observo) que permite que você se autentique adequadamente em um certificado auto-gerado (e como obter um certificado gratuito - veja os comentários na parte inferior do Cocoanetics )

Meu código está aqui github

David H
fonte
Poderia, por favor verifique o seguinte: stackoverflow.com/q/56627757/1364053
NR 5
2

Se você quiser continuar usando o sendSynchronousRequest, eu trabalho nesta solução:

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

Você pode vê-lo aqui: Conexão síncrona SSL do Objective-C

jgorozco
fonte
1

Com o AFNetworking , consumi com sucesso o https webservice com o código abaixo,

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
fonte
1

Você pode usar este código

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

Use em -connection:willSendRequestForAuthenticationChallenge:vez desses métodos preteridos

Descontinuada:

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