วิธีใช้ NSURLConnection เพื่อเชื่อมต่อกับ SSL สำหรับใบรับรองที่ไม่น่าเชื่อถือ


300

ฉันมีรหัสง่าย ๆ ต่อไปนี้เพื่อเชื่อมต่อกับเว็บเพจ SSL

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

ยกเว้นจะให้ข้อผิดพลาดหากใบรับรองเป็นแบบเซ็นชื่อด้วยตนเองError Domain=NSURLErrorDomain Code=-1202 UserInfo=0xd29930 "untrusted server certificate".มีวิธีตั้งค่าให้ยอมรับการเชื่อมต่อหรือไม่ (เช่นเดียวกับในเบราว์เซอร์ที่คุณสามารถกดยอมรับได้) หรือวิธีเลี่ยงผ่านได้หรือไม่

คำตอบ:


415

มี API ที่รองรับสำหรับการทำสิ่งนี้ให้สำเร็จ! เพิ่มสิ่งนี้ลงในNSURLConnectionผู้รับมอบสิทธิ์ของคุณ:

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

โปรดทราบว่าconnection:didReceiveAuthenticationChallenge:สามารถส่งข้อความถึง challeng.sender (มาก) ในภายหลังหลังจากนำเสนอกล่องโต้ตอบให้กับผู้ใช้หากจำเป็นเป็นต้น


31
ขอบคุณมากมันทำงานได้อย่างสมบูรณ์ เพียงแค่ลบทั้งสอง ifs และเก็บเฉพาะส่วนการใช้งานปลายทางในการเรียกกลับ didReceiveAuthentificationChallenge หากคุณต้องการยอมรับไซต์ https ใด ๆ
yonel

19
โฮสท์ที่เชื่อถือได้คืออะไรโดยที่วัตถุมีการกำหนดอย่างไร
อามียา

7
Ameya มันจะเป็น NSArray ของวัตถุ NSString สตริงเป็นชื่อโฮสต์เช่น @ "google.com"
William Denniss

19
รหัสนี้ทำงานได้ดี แต่โปรดทราบว่าจุดทั้งหมดของการมีใบรับรองที่ถูกต้องคือการป้องกันการโจมตีจากคนกลาง ดังนั้นโปรดระวังหากคุณใช้รหัสนี้ใครบางคนสามารถหลอกที่เรียกว่า "โฮสต์ที่เชื่อถือได้" คุณยังคงได้รับคุณสมบัติการเข้ารหัสข้อมูลของ SSL แต่คุณจะสูญเสียโฮสต์ที่ระบุคุณสมบัติการตรวจสอบความถูกต้อง
William Denniss

42
วิธีการเหล่านี้ได้รับการพิจารณาว่าเลิกใช้แล้วตั้งแต่ iOS 5.0 และ Mac OS X 10.6 -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challengeวิธีการที่ควรจะนำมาใช้แทน
แอนดรูอาร์

36

หากคุณไม่เต็มใจ (หรือไม่) เพื่อใช้ API ส่วนตัวมีโอเพนซอร์ส (ใบอนุญาต BSD) ห้องสมุดเรียกASIHTTPRequestCFNetwork APIsที่ให้ห่อหุ้มรอบระดับที่ต่ำกว่า พวกเขาเพิ่งเปิดตัวความสามารถในการอนุญาตให้HTTPS connectionsใช้ใบรับรองที่ลงนามด้วยตนเองหรือไม่น่าเชื่อถือกับ-setValidatesSecureCertificate:API หากคุณไม่ต้องการดึงทั้งไลบรารีคุณสามารถใช้แหล่งที่มาเป็นข้อมูลอ้างอิงสำหรับการใช้งานฟังก์ชั่นเดียวกันได้ด้วยตนเอง


2
ทิมคุณอาจพบว่าตัวเองต้องการใช้ async ด้วยเหตุผลอื่น ๆ อยู่ดี (เช่นความสามารถในการแสดงแถบความคืบหน้า) ฉันค้นหาได้ทั้งหมด แต่คำขอที่เรียบง่ายที่สุดนั่นคือวิธีที่ฉันไป ดังนั้นบางทีคุณควรติดตั้ง Async ทันทีและช่วยลดความยุ่งยากในภายหลัง
William Denniss

ดูสิ่งนี้สำหรับการนำไปใช้ (แต่ใช้ [r setValidatesSecureCertificate: NO];): stackoverflow.com/questions/7657786/…
Sam Brodkin

ขออภัยที่ฉันนำหัวข้อนี้กลับมา แต่เนื่องจาก iOS 5 ได้เปิดตัวฟีเจอร์ ARC ฉันจะทำให้งานนี้ตอนนี้ได้อย่างไร
Melvin Lai

คุณช่วยกรุณาตรวจสอบสิ่งนี้ได้ที่: stackoverflow.com/q/56627757/1364053
nr5

33

ตามหลักการแล้วควรมีสองสถานการณ์เท่านั้นเมื่อแอปพลิเคชัน iOS จะต้องยอมรับใบรับรองที่ไม่น่าเชื่อถือ

สถานการณ์สมมติ A: คุณเชื่อมต่อกับสภาพแวดล้อมการทดสอบที่ใช้ใบรับรองแบบลงนามด้วยตนเอง

สถานการณ์ B: คุณเป็นทราฟHTTPSฟิกที่รับส่งข้อมูลโดยใช้MITM Proxy like Burp Suite, Fiddler, OWASP ZAP, etc.The Proxies จะส่งคืนใบรับรองที่ลงนามโดย CA ที่ลงชื่อด้วยตนเองเพื่อให้พร็อกซีสามารถรับHTTPSปริมาณข้อมูลได้

เจ้าภาพการผลิตไม่ควรใช้ใบรับรองยกเลิกที่เชื่อถือได้สำหรับเหตุผลที่ชัดเจน

หากคุณต้องการให้เครื่องจำลอง iOS ยอมรับใบรับรองที่ไม่น่าเชื่อถือสำหรับการทดสอบขอแนะนำอย่างยิ่งว่าคุณไม่ต้องเปลี่ยนตรรกะแอปพลิเคชันเพื่อปิดใช้งานการตรวจสอบความถูกต้องของใบรับรองที่จัดทำโดยNSURLConnectionAPIs หากแอปพลิเคชันเผยแพร่สู่สาธารณะโดยไม่ลบตรรกะนี้มันจะไวต่อการโจมตีจากคนกลาง

วิธีที่แนะนำให้ยอมรับใบรับรองที่ไม่น่าเชื่อถือสำหรับวัตถุประสงค์ในการทดสอบคือการนำเข้าใบรับรอง Certificate Authority (CA) ซึ่งลงนามใบรับรองไปยัง iOS Simulator หรืออุปกรณ์ iOS ของคุณ ฉันเขียนโพสต์บล็อกสั้น ๆ ซึ่งแสดงให้เห็นถึงวิธีการทำสิ่งนี้ซึ่ง iOS Simulator ที่:

การยอมรับใบรับรองที่ไม่น่าเชื่อถือโดยใช้เครื่องมือจำลอง iOS


1
ผู้ชายที่น่ากลัว ฉันเห็นด้วยมันง่ายมากที่จะลืมเกี่ยวกับการปิดใช้งานแอปพิเศษนี้เพื่อยอมรับใบรับรองที่ไม่น่าเชื่อถือ
Tomasz

"ในอุดมคติแล้วควรมีสองสถานการณ์เท่านั้นเมื่อแอปพลิเคชัน iOS จะต้องยอมรับใบรับรองที่ไม่น่าเชื่อถือ" - วิธีการปฏิเสธการรับรองที่ดี 'อ้างว่า' เมื่อปักหมุดใบรับรอง? มอบให้: Dignotar (pwn'd) และ Trustwave (ชื่อเสียง MitM)
jww

เห็นด้วยอย่างยิ่งกับคำชี้แจงของคุณเกี่ยวกับการลืมลบรหัส ประชดคือว่ามันง่ายกว่ามากที่จะทำการเปลี่ยนแปลงในรหัสกว่ารับจำลองที่จะยอมรับการเซ็นสัญญาด้วยตนเอง
devios1

12

NSURLRequestมีวิธีการส่วนตัวที่เรียกว่าsetAllowsAnyHTTPSCertificate:forHost:ซึ่งจะทำสิ่งที่คุณต้องการ คุณสามารถกำหนดallowsAnyHTTPSCertificateForHost:วิธีการNSURLRequestผ่านหมวดหมู่และตั้งค่าให้ส่งคืนYESสำหรับโฮสต์ที่คุณต้องการแทนที่


คำเตือนทั่วไปเกี่ยวกับ API ที่ไม่มีเอกสารใช้ ... แต่ดีที่รู้ว่าเป็นไปได้
สตีเฟ่น Darlington

ใช่อย่างแน่นอน ฉันได้เพิ่มคำตอบอื่นที่ไม่เกี่ยวข้องกับการใช้ API ส่วนตัว
Nathan de Vries

ใช้งานได้เมื่อคุณใช้ "NSURLConnection sendSynchronousRequest:" หรือไม่
ทิมBüthe

11

เพื่อเติมเต็มคำตอบที่ยอมรับเพื่อความปลอดภัยที่ดียิ่งขึ้นคุณสามารถเพิ่มใบรับรองเซิร์ฟเวอร์ของคุณหรือใบรับรอง CA หลักของคุณเองไปที่ keychain ( https://stackoverflow.com/a/9941559/1432048 ) แต่การทำเช่นนี้เพียงอย่างเดียวจะไม่ทำให้ NSURLConnection รับรองความถูกต้องของเซิร์ฟเวอร์ที่ลงชื่อด้วยตนเองโดยอัตโนมัติ คุณยังคงต้องเพิ่มรหัสด้านล่างลงในผู้รับมอบสิทธิ์ NSURLC การเชื่อมต่อของคุณมันถูกคัดลอกมาจากรหัสตัวอย่างของ Apple AdvancedURLConnectionsและคุณต้องเพิ่มไฟล์สองไฟล์ (Credentials.h, Credentials.m) จากตัวอย่างรหัส Apple ไปยังโครงการของคุณ

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

10

ฉันไม่สามารถใช้เครดิตนี้ได้แต่สิ่งนี้ฉันพบว่าทำงานได้ดีสำหรับความต้องการของฉัน shouldAllowSelfSignedCertเป็นBOOLตัวแปรของฉัน เพียงเพิ่มNSURLConnectionผู้รับมอบสิทธิ์ของคุณและคุณควรจะผ่านไปอย่างรวดเร็วสำหรับการเชื่อมต่อพื้นฐาน

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

10

ใน iOS 9 การเชื่อมต่อ SSL จะล้มเหลวสำหรับใบรับรองที่ไม่ถูกต้องหรือลงนามด้วยตนเองทั้งหมด นี่เป็นพฤติกรรมเริ่มต้นของฟีเจอร์ความปลอดภัยการขนส่งแอพใหม่ใน iOS 9.0 หรือใหม่กว่าและใน OS X 10.11 และใหม่กว่า

คุณสามารถลบล้างพฤติกรรมนี้ได้ในInfo.plist, โดยการตั้งค่าNSAllowsArbitraryLoadsเป็นYESในNSAppTransportSecurityพจนานุกรม อย่างไรก็ตามฉันขอแนะนำให้แทนที่การตั้งค่านี้เพื่อการทดสอบเท่านั้น

ป้อนคำอธิบายรูปภาพที่นี่

สำหรับข้อมูลดู App ขนส่ง Technote ที่นี่


ทางออกเดียวที่ใช้ได้สำหรับฉันฉันไม่มีวิธีที่จะเปลี่ยนกรอบ Firebase เพื่อตอบสนองความต้องการของฉันที่แก้ไขได้ขอบคุณ!
Yitzchak

ตอนนี้ฉันเห็นว่า Google ขอ NSAllowArbitraryLoads = YES สำหรับ Admob (ใน Firebase) firebase.google.com/docs/admob/ios/ios9
Yitzchak

6

วิธีแก้ปัญหาหมวดหมู่ที่โพสต์โดย Nathan de Vries จะผ่านการตรวจสอบ API ส่วนตัวของ AppStore และมีประโยชน์ในกรณีที่คุณไม่สามารถควบคุมNSUrlConnectionวัตถุได้ ตัวอย่างหนึ่งคือNSXMLParserซึ่งจะเปิด URL ที่คุณจัดหา แต่ไม่ได้เปิดเผยหรือNSURLRequestNSURLConnection

ใน iOS 4 วิธีแก้ปัญหายังคงใช้งานได้ แต่บนอุปกรณ์เท่านั้นตัวจำลองจะไม่เรียกใช้allowsAnyHTTPSCertificateForHost:วิธีการอีกต่อไป


6

คุณต้องใช้NSURLConnectionDelegateเพื่ออนุญาตการเชื่อมต่อ HTTPS และมีการเรียกกลับใหม่ด้วย iOS8

เลิกใช้:

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

คุณต้องประกาศ:

connectionShouldUseCredentialStorage: - ส่งเพื่อตรวจสอบว่าตัวโหลด URL ควรใช้ที่เก็บข้อมูลรับรองสำหรับการตรวจสอบความถูกต้องของการเชื่อมต่อหรือไม่

connection:willSendRequestForAuthenticationChallenge: - บอกผู้รับมอบสิทธิ์ว่าการเชื่อมต่อจะส่งการร้องขอเพื่อขอการรับรองความถูกต้อง

ด้วยwillSendRequestForAuthenticationChallengeคุณสามารถใช้challengeเช่นเดียวกับที่คุณทำกับวิธีการเลิกเช่น:

// Trusting and not trusting connection to host: Self-signed certificate
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];

คุณช่วยกรุณาตรวจสอบสิ่งนี้ได้ที่: stackoverflow.com/q/56627757/1364053
nr5

3

ฉันโพสต์บางส่วนของรหัส (ขึ้นอยู่กับงานของคนอื่นที่ฉันทราบ) ที่ให้คุณรับรองความถูกต้องกับใบรับรองที่สร้างขึ้นเอง (และวิธีรับใบรับรองฟรี - ดูความคิดเห็นด้านล่างของCocoanetics )

รหัสของฉันอยู่ที่นี่GitHub


คุณช่วยกรุณาตรวจสอบสิ่งนี้ได้ที่: stackoverflow.com/q/56627757/1364053
nr5

2

หากคุณต้องการใช้sendSynchronousRequest ต่อไปฉันจะทำงานในโซลูชันนี้:

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

คุณสามารถดูได้ที่นี่: Objective-C SSL Synchronous Connection


1

ด้วยAFNetworkingฉันใช้ https webservice ด้วยโค้ดด้านล่างได้สำเร็จ

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

1

คุณสามารถใช้รหัสนี้

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

ใช้-connection:willSendRequestForAuthenticationChallenge:แทนวิธีการที่เลิกใช้เหล่านี้

เลิกใช้:

-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace  
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
-(void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.