How to implement the Certificate Pinning (SSL Pinning) on iOS

บทนำ (Overview)

เมื่อโปรแกรมต่างๆ ที่ถูกติดตั้งบน “iOS” จะต้องสื่อสารกับเครื่องให้บริการ (Web Server) เช่น “Facebook” “Line” หรือ “GMail” เพื่อป้องกันมิให้ “Hacker” “Malware” หรือโปรแกรมประเภท “Proxy” สามารถดักจับข้อมูลของได้นั้น ข้อมูลดังกล่าวจะต้องส่งผ่านช่องทางสื่อสารที่เข้ารหัส ตั้งแต่บนมือถือสิ้นสุดที่เครื่อง “Server” ซึ่งสามารถทำได้โดยใช้โปรโตรคอลที่เรียกว่า “SSL/TLS”

certificatepining1

แต่พวก Hacker ก็ไม่จบแค่นั้น เขาสามารถปลอม “Certificates” ของ “Server” (“Server” ที่ได้การลงทะเบียนไว้แล้วว่ามีอยู่จริง และจะถูกออกใบ “Certificate” โดย “CA”) ปลอมซะเหมือนเพื่อส่งให้ “Client” ทราบว่า ฉันนี้แหละคือ “Server” ตัวจริง หรือส่ง “Self Signed Certificate” ให้มือถือเกิดความเชื่อใจ (Trust) จากนั้นสามารถดับจับข้อมูลได้ (Intercept) ผ่านทางโปรแกรมประเภท “Proxy” เป็นต้น

certificatepining2

โดยปกติเมื่อเขียนโปรแกรมใช้งาน HTTPS ถ้าติดต่อผ่าน “Certificate” ที่ “Untrusted” โปรแกรมจะมี “Error” ออกมาของโปรแกรมอยู่แล้ว แต่บางครั้ง โปรแกรมเมอร์อาจจะข้ามการตรวจสอบตรงนี้จนอาจเกิดปัญหาด้านความปลอดภัยก็เป็นไป…

charlcertificate
Charles Proxy Certificate

เพราะฉนั้นจึงต้องลดความเสี่ยงนี้ด้วยการทำ “SSL pining” โดยสามารถตรวจสอบได้ว่า “Certificates” ที่ระบุเท่านั้นที่จะสามารถเข้ารหัสเพื่อส่งและรับข้อมูลจาก “Server” ดังกล่าว สามารถอธิบายได้ดังนี้

certificatepining3

ขั้นตอน (Steps)

  1. ซึ่งในขั้นตอนการเขียนโปรแกรม “iOS” นั้นมี “Function” ที่สำคัญสำหรับการทำ “CertificatePinning” ดังนี้
    + (BOOL)setupSSLPinsUsingDictionnary:(NSDictionary*)domainsAndCertificates {
    if (domainsAndCertificates == nil) {
    return NO;
    }
    
    // Serialize the dictionary to a plist
    NSError *error;
    NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:domainsAndCertificates
    format:NSPropertyListXMLFormat_v1_0
    options:0
    error:&error];
    if (plistData == nil) {
    NSLog(@"Error serializing plist: %@", error);
    return NO;
    }
    
    // Write the plist to a pre-defined location on the filesystem
    NSError *writeError;
    if ([plistData writeToFile:[@PINNED_KEYS_FILE_PATH stringByExpandingTildeInPath]
    options:NSDataWritingAtomic
    error:&writeError] == NO) {
    NSLog(@"Error saving plist to the filesystem: %@", writeError);
    return NO;
    }
    
    return YES;
    }
    
  2. จากนั้นเราจึงสามารถตรวจสอบ “Certificates” ที่ใช้งาน โดยเปรียบเทียบกับ “Certificates” ที่เก็บเอาไว้ในเครื่อง “Mobile” โดยใช้ฟังก์ชันดังนี้
    + (BOOL)verifyPinnedCertificateForTrust:(SecTrustRef)trust andDomain:(NSString*)domain {
        if ((trust == NULL) || (domain == nil)) {
            return NO;
        }
    
        // Deserialize the plist that contains our SSL pins
        NSDictionary *SSLPinsDict = [NSDictionary dictionaryWithContentsOfFile:[@PINNED_KEYS_FILE_PATH stringByExpandingTildeInPath]];
        if (SSLPinsDict == nil) {
            NSLog(@"Error accessing the SSL Pins plist at %@", @PINNED_KEYS_FILE_PATH);
            return NO;
        }
    
        // Do we have certificates pinned for this domain ?
        NSArray *trustedCertificates = [SSLPinsDict objectForKey:domain];
        if ((trustedCertificates == nil) || ([trustedCertificates count] < 1)) {
            return NO;
        }
    
        // For each pinned certificate, check if it is part of the server's cert trust chain
        // We only need one of the pinned certificates to be in the server's trust chain
        for (NSData *pinnedCertificate in trustedCertificates) {
    
            // Check each certificate in the server's trust chain (the trust object)
            // Unfortunately the anchor/CA certificate cannot be accessed this way
            CFIndex certsNb = SecTrustGetCertificateCount(trust);
            for(int i=0;i<certsNb;i++) {
    
                // Extract the certificate
                SecCertificateRef certificate = SecTrustGetCertificateAtIndex(trust, i);
                NSData* DERCertificate = (__bridge NSData*) SecCertificateCopyData(certificate);
    
                // Compare the two certificates
                if ([pinnedCertificate isEqualToData:DERCertificate]) {
                    return YES;
                }
            }
    
            // Check the anchor/CA certificate separately
            SecCertificateRef anchorCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(pinnedCertificate));
            if (anchorCertificate == NULL) {
                break;
            }
    
            NSArray *anchorArray = [NSArray arrayWithObject:(__bridge id)(anchorCertificate)];
            if (SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)(anchorArray)) != 0) {
                CFRelease(anchorCertificate);
                break;
            }
    
            SecTrustResultType trustResult;
            SecTrustEvaluate(trust, &trustResult);
            if (trustResult == kSecTrustResultUnspecified) {
                // The anchor certificate was pinned
                CFRelease(anchorCertificate);
                return YES;
            }
            CFRelease(anchorCertificate);
        }
    
        // If we get here, we didn't find any matching certificate in the chain
        return NO;
    }
    
  3. โดย Source-code ดังกล่าวสามารถดาวน์โหลดได้ที่ https://github.com/iSECPartners/ssl-conservatory โดยเรานำไปรวมอยู่ในโปรแกรมของเราและตรวจสอบการเรียกใช้ “API” หลังบ้านซึ่งต้องผ่าน “SSL” เมื่อตรวจสอบแล้วว่ามี “Certificate” ไม่เหมาะสมอาจจะไม่อนุญาติให้ใช้งาน หรือแจ้งเตือนข้อผิดพลาดเป็นต้น

ใส่ความเห็น