$30 off During Our Annual Pro Sale. View Details »

iOSアプリ内で不正なSSL証明書を検知する / SSL Pinning for iOS apps

iOSアプリ内で不正なSSL証明書を検知する / SSL Pinning for iOS apps

iOSDC Japan 2018
8/31 14:20~14:50 @Track A

Ask The Speakerでの質問と回答を追記しました

Keisuke Kobayashi

August 31, 2018
Tweet

More Decks by Keisuke Kobayashi

Other Decks in Programming

Transcript

  1. iOSΞϓϦ಺Ͱෆਖ਼ͳ
    SSLূ໌ॻΛݕ஌͢Δ
    Keisuke Kobayashi (kobakei)
    iOSDC Japan 2018
    8/31 (Fri) 14:20~ @Track A

    View Slide

  2. ࣗݾ঺հ
    • Keisuke Kobayashi
    • Twitter: kobakei122
    • GitHub: kobakei
    • Kyash, Inc
    • Android / iOS / Engineering Manager

    View Slide

  3. ͍ͭ࠷ۙϒϩά͕όζͬͨ

    View Slide

  4. View Slide

  5. ΞδΣϯμ
    • தؒऀ߈ܸͱ͸
    • SSLূ໌ॻͷϐϯཹΊ (SSL Pinning)
    • ࣮ࡍͷӡ༻ɾϋϚΓ͕ͪͳ᠘
    • ຊ౰ʹϐϯཹΊ͢΂͖͔

    View Slide

  6. ???ʮαʔόʔͱͷ௨৴͸શ෦
    HTTPSԽͯ͠Δ͔Β҆શͰ͠
    ΐʂʯ

    View Slide

  7. ຊ౰ʹͦ͏Ͱ͔͢ʁ

    View Slide

  8. HTTPSԽ͍ͯͯ͠΋

    ϦεΫ͸ଘࡏ͢Δ
    • αʔόʔূ໌ॻݕূͷόΠύεΛ͏͔ͬΓ

    ຊ൪ʹग़ͯ͠͠·͏
    • தؒऀ߈ܸ

    View Slide

  9. αʔόʔূ໌ॻݕূͷόΠύε
    • ओʹ։ൃ࣌ʹࣗݾॺ໊ূ໌ॻΛ࢖͏ͨΊʹݕূΛແޮ
    Խ͢Δ͜ͱ͕͋Δ
    • #ifdef DEBUG Ͱ։ൃ൛͚ͩόΠύε͢Δ
    • ͜Ε͕͏͔ͬΓຊ൪ʹग़ͯ͠·͏ࣄނ
    • ແྉ or ͍҆ূ໌ॻ΋͋ΔͷͰɺ։ൃ؀ڥʹ΋ͪΌΜͱ
    ͨ͠ূ໌ॻΛஔ͍ͯৗʹόΠύε͠ͳ͍ͷ͕͓͢͢Ί

    View Slide

  10. தؒऀ߈ܸ
    • 2ऀͷ௨৴ͷؒʹ߈ܸऀ͕հࡏ͢Δ͜ͱͰɺ௨৴ͷ౪
    ௌ΍վ͟ΜΛߦ͏߈ܸ
    • ӳޠͰ͸ Man in the middle (MITM)
    • ࠷΋Ұൠతͳͷ͸ɺෆਖ਼ͳΞΫηεϙΠϯτΛઃஔ͢
    Δ͜ͱ
    • ྫɿո͍͠Free WiFiʹͭͳ͍ͩΒ৘ใ͕ൈ͔Εͨʂ

    View Slide

  11. SSL௨৴
    ᶃ઀ଓཁٻ
    ΫϥΠΞϯτ αʔόʔ

    View Slide

  12. SSL௨৴
    ᶃ઀ଓཁٻ
    ᶄެ։伴ͷૹ৴
    ΫϥΠΞϯτ αʔόʔ

    View Slide

  13. SSL௨৴
    ᶃ઀ଓཁٻ
    ᶄެ։伴ͷૹ৴
    ᶅ҉߸Խͨ͠σʔλૹ৴
    ※ຊདྷ͸ᶄͱᶅͷؒʹڞ௨伴ͷ҉߸Խ͕ೖΓ·͢
    ΫϥΠΞϯτ αʔόʔ

    View Slide

  14. தؒऀ߈ܸ
    ᶃ઀ଓཁٻ
    ΫϥΠΞϯτ αʔόʔ
    ߈ܸऀ

    View Slide

  15. தؒऀ߈ܸ
    ᶃ઀ଓཁٻ ᶄ઀ଓཁٻ
    ΫϥΠΞϯτ αʔόʔ
    ߈ܸऀ

    View Slide

  16. தؒऀ߈ܸ
    ᶃ઀ଓཁٻ ᶄ઀ଓཁٻ
    ᶅެ։伴ૹ৴
    ΫϥΠΞϯτ αʔόʔ
    ߈ܸऀ

    View Slide

  17. தؒऀ߈ܸ
    ᶃ઀ଓཁٻ ᶄ઀ଓཁٻ
    ᶅެ։伴ૹ৴
    ᶆެ։伴ૹ৴
    ΫϥΠΞϯτ αʔόʔ
    ߈ܸऀ

    View Slide

  18. தؒऀ߈ܸ
    ᶃ઀ଓཁٻ ᶄ઀ଓཁٻ
    ᶅެ։伴ૹ৴
    ᶆެ։伴ૹ৴
    ᶇσʔλૹ৴
    ΫϥΠΞϯτ αʔόʔ
    ߈ܸऀ

    View Slide

  19. தؒऀ߈ܸ
    ᶃ઀ଓཁٻ ᶄ઀ଓཁٻ
    ᶅެ։伴ૹ৴
    ᶆެ։伴ૹ৴
    ᶇσʔλૹ৴
    ِͷ伴Ͱ҉߸Խ͞Ε͍ͯΔͷͰ
    ෮߸Մೳ → ౪ௌ͞ΕΔʂ
    ΫϥΠΞϯτ αʔόʔ
    ߈ܸऀ

    View Slide

  20. ิ଍: 

    ߈ܸऀͷূ໌ॻΛ৴༻ͯ͠͠·͏ʁ
    • ϧʔτূ໌ॻΛ୺຤ʹΠϯετʔϧ͍ͯ͠Δ
    ৔߹
    • ߈ܸऀ͕ೝূہ͔Βِ଄ূ໌ॻΛऔಘͨ͠৔
    ߹
    ※Ask The SpeakerͰ࣭໰͞ΕͨͷͰ௥ه

    View Slide

  21. ิ଍: ِ଄ূ໌ॻͷൃߦͱ͸ʁ
    • ೝূہ͕ϋοΩϯά͞Εͨ৔߹ʹෆਖ਼ʹൃߦ
    ͞ΕΔ͜ͱ͕͋Δ
    • ༗໊ͳೝূہͷϋοΩϯάࣄྫ
    • 2011: Comodo
    • 2011: DigiNotar
    ※Ask The SpeakerͰ࣭໰͞ΕͨͷͰ௥ه

    View Slide

  22. Ҿ༻: http://www.security-next.com/094624

    View Slide

  23. Ҿ༻: https://linecorp.com/ja/security/article/135

    View Slide

  24. ରࡦ͸Ͱ͖ͳ͍͔ʁ

    View Slide

  25. ϐϯཹΊ (SSL Pinning)
    • SSLαʔόʔূ໌ॻ͕ΫϥΠΞϯτ͕ظ଴ͯ͠
    ͍Δ΋ͷͱಉҰ͔Λݕূ͢Δ

    View Slide

  26. ϐϯཹΊ
    ΫϥΠΞϯτ αʔόʔ
    ߈ܸऀ
    ɹ͕དྷΔ͸ͣʜ

    View Slide

  27. ϐϯཹΊ
    ᶃ઀ଓཁٻ ᶄ઀ଓཁٻ
    ᶅެ։伴ૹ৴
    ΫϥΠΞϯτ αʔόʔ
    ߈ܸऀ
    ɹ͕དྷΔ͸ͣʜ

    View Slide

  28. ϐϯཹΊ
    ᶃ઀ଓཁٻ ᶄ઀ଓཁٻ
    ᶅެ։伴ૹ৴
    ᶆެ։伴ૹ৴
    ΫϥΠΞϯτ αʔόʔ
    ߈ܸऀ
    ɹ͡Όͳ͍ͧʂ

    View Slide

  29. ϐϯཹΊ (SSL Pinning)
    • ҎԼͷ2͕ͭ͋Δ
    • ূ໌ॻϐϯཹΊ (certificate pinning)
    • ެ։伴ϐϯཹΊ (public key pinning)

    View Slide

  30. ͦ΋ͦ΋SSLαʔόʔূ໌ॻͱ͸ʁ
    • ެ։伴ͱॴ༗ऀ৘ใΛ΋ͱʹɺ৴པ͞Εͨೝ
    ূہ͕ൃߦ͢Δ
    • υϝΠϯॴ༗ݖɺاۀͷ࣮ࡏੑͷ֬ೝ

    View Slide

  31. ূ໌ॻ or ެ։伴ͷϐϯཹΊ
    • ূ໌ॻͷϐϯཹΊ
    • SSLαʔόʔূ໌ॻͦͷ΋ͷ͕Ұக͢Δ͔ݕূ
    • ߋ৽ස౓͕ߴ͍ = ΞϓϦΛසൟʹߋ৽ඞཁ
    • ެ։伴ͷϐϯཹΊ
    • ূ໌ॻͷݩʹͳͬͨެ։伴͕Ұக͢Δ͔ݕূ
    • ߋ৽ස౓͸௿͍

    View Slide

  32. ࢀߟ: Android 7Ҏ߱
    • ެࣜͷωοτϫʔΫηΩϡϦςΟߏ੒ػೳ
    • ެ։伴ͷϐϯཹΊΛαϙʔτ
    • https://developer.android.com/training/
    articles/security-config

    View Slide

  33. ࢀߟ: OkHttp
    • Square͕։ൃ͢ΔAndroid༻HTTPΫϥΠΞϯτ
    • σϑΝΫτελϯμʔυ
    • ެ։伴ͷϐϯཹΊͷΈαϙʔτ͍ͯ͠Δ
    • “certificate pinning”ͱυΩϡϝϯτʹ͸͋Δ
    ͕ɺ಺෦͸ެ։伴ϐϯཹΊ

    View Slide

  34. ࣮૷ํ๏

    View Slide

  35. ࣮૷
    • ͦΕͧΕͷ௨৴ϥΠϒϥϦ͝ͱʹ঺հ͠·͢
    • APIKit
    • Alamofire

    View Slide

  36. APIKit
    • ඪ४ͰϐϯཹΊ͸αϙʔτ͍ͯ͠ͳ͍
    • SessionAdapterʹద߹ͨ͠ΫϥεΛ࣮૷͢Δ
    ͜ͱͰɺ௨৴࣌ͷ֤छϋϯυϥΛΧελϚΠ
    ζͰ͖Δ

    View Slide

  37. SessionAdapter
    public protocol SessionAdapter {
    func createTask(with URLRequest: URLRequest, handler: @escaping (Data?,
    URLResponse?, Error?) -> Void) -> SessionTask
    func getTasks(with handler: @escaping ([SessionTask]) -> Void)
    }

    View Slide

  38. URLSessionAdapter
    • SessionAdapterͷURLSession࣮૷
    • URLSessionAdapterͷαϒΫϥεʹɺ

    ϐϯཹΊ༻ͷίʔϧόοΫΛ࣮૷͢Δ

    View Slide

  39. URLSessionAdapter
    open class URLSessionAdapter: NSObject, SessionAdapter, URLSessionDelegate,
    URLSessionTaskDelegate, URLSessionDataDelegate {
    open var urlSession: URLSession!
    public init(configuration: URLSessionConfiguration) {
    super.init()
    self.urlSession = URLSession(configuration: configuration, delegate: self,
    delegateQueue: nil)
    }
    open func createTask(with URLRequest: URLRequest, handler: @escaping (Data?,
    URLResponse?, Error?) -> Void) -> SessionTask {
    ...
    }
    open func getTasks(with handler: @escaping ([SessionTask]) -> Void) {
    ...
    }
    ...
    }

    View Slide

  40. URLSessionAdapter
    open class URLSessionAdapter: NSObject, SessionAdapter, URLSessionDelegate,
    URLSessionTaskDelegate, URLSessionDataDelegate {
    open var urlSession: URLSession!
    public init(configuration: URLSessionConfiguration) {
    super.init()
    self.urlSession = URLSession(configuration: configuration, delegate: self,
    delegateQueue: nil)
    }
    open func createTask(with URLRequest: URLRequest, handler: @escaping (Data?,
    URLResponse?, Error?) -> Void) -> SessionTask {
    ...
    }
    open func getTasks(with handler: @escaping ([SessionTask]) -> Void) {
    ...
    }
    ...
    }

    View Slide

  41. MyURLSessionAdapter
    • URLSessionAdapterͷαϒΫϥεΛ࡞੒
    • ূ໌ॻϐϯཹΊ
    • ެ։伴ϐϯཹΊ

    View Slide

  42. class MyURLSessionAdapter: URLSessionAdapter {
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->
    Swift.Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
    if let serverTrust = challenge.protectionSpace.serverTrust {
    var secresult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secresult)
    if errSecSuccess == status {
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
    let serverCertificateData = SecCertificateCopyData(serverCertificate)
    let data = CFDataGetBytePtr(serverCertificateData);
    let size = CFDataGetLength(serverCertificateData);
    let cert1 = NSData(bytes: data, length: size)
    let fileDer = Bundle.main.path(forResource: "github", ofType: "der")
    if let file = fileDer {
    if let cert2 = NSData(contentsOfFile: file) {
    if cert1.isEqual(to: cert2 as Data) {
    completionHandler(URLSession.AuthChallengeDisposition.useCredential,
    URLCredential(trust:serverTrust))
    return
    }
    }
    }
    }
    }
    }
    }
    // Pinning failed
    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
    }

    View Slide

  43. class MyURLSessionAdapter: URLSessionAdapter {
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->
    Swift.Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
    if let serverTrust = challenge.protectionSpace.serverTrust {
    var secresult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secresult)
    if errSecSuccess == status {
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
    let serverCertificateData = SecCertificateCopyData(serverCertificate)
    let data = CFDataGetBytePtr(serverCertificateData);
    let size = CFDataGetLength(serverCertificateData);
    let cert1 = NSData(bytes: data, length: size)
    let fileDer = Bundle.main.path(forResource: "github", ofType: "der")
    if let file = fileDer {
    if let cert2 = NSData(contentsOfFile: file) {
    if cert1.isEqual(to: cert2 as Data) {
    completionHandler(URLSession.AuthChallengeDisposition.useCredential,
    URLCredential(trust:serverTrust))
    return
    }
    }
    }
    }
    }
    }
    }
    // Pinning failed
    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
    }

    View Slide

  44. class MyURLSessionAdapter: URLSessionAdapter {
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->
    Swift.Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
    if let serverTrust = challenge.protectionSpace.serverTrust {
    var secresult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secresult)
    if errSecSuccess == status {
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
    let serverCertificateData = SecCertificateCopyData(serverCertificate)
    let data = CFDataGetBytePtr(serverCertificateData);
    let size = CFDataGetLength(serverCertificateData);
    let cert1 = NSData(bytes: data, length: size)
    let fileDer = Bundle.main.path(forResource: "github", ofType: "der")
    if let file = fileDer {
    if let cert2 = NSData(contentsOfFile: file) {
    if cert1.isEqual(to: cert2 as Data) {
    completionHandler(URLSession.AuthChallengeDisposition.useCredential,
    URLCredential(trust:serverTrust))
    return
    }
    }
    }
    }
    }
    }
    }
    // Pinning failed
    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
    }
    SSLαʔόʔূ໌ॻ

    View Slide

  45. class MyURLSessionAdapter: URLSessionAdapter {
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->
    Swift.Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
    if let serverTrust = challenge.protectionSpace.serverTrust {
    var secresult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secresult)
    if errSecSuccess == status {
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
    let serverCertificateData = SecCertificateCopyData(serverCertificate)
    let data = CFDataGetBytePtr(serverCertificateData);
    let size = CFDataGetLength(serverCertificateData);
    let cert1 = NSData(bytes: data, length: size)
    let fileDer = Bundle.main.path(forResource: "github", ofType: "der")
    if let file = fileDer {
    if let cert2 = NSData(contentsOfFile: file) {
    if cert1.isEqual(to: cert2 as Data) {
    completionHandler(URLSession.AuthChallengeDisposition.useCredential,
    URLCredential(trust:serverTrust))
    return
    }
    }
    }
    }
    }
    }
    }
    // Pinning failed
    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
    }
    αʔόʔূ໌ॻΛNSDataʹ

    View Slide

  46. class MyURLSessionAdapter: URLSessionAdapter {
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->
    Swift.Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
    if let serverTrust = challenge.protectionSpace.serverTrust {
    var secresult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secresult)
    if errSecSuccess == status {
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
    let serverCertificateData = SecCertificateCopyData(serverCertificate)
    let data = CFDataGetBytePtr(serverCertificateData);
    let size = CFDataGetLength(serverCertificateData);
    let cert1 = NSData(bytes: data, length: size)
    let fileDer = Bundle.main.path(forResource: "github", ofType: "der")
    if let file = fileDer {
    if let cert2 = NSData(contentsOfFile: file) {
    if cert1.isEqual(to: cert2 as Data) {
    completionHandler(URLSession.AuthChallengeDisposition.useCredential,
    URLCredential(trust:serverTrust))
    return
    }
    }
    }
    }
    }
    }
    }
    // Pinning failed
    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
    }
    ΫϥΠΞϯτͷDERϑΝΠϧ

    View Slide

  47. ΫϥΠΞϯτଆͷূ໌ॻ
    • OpenSSLͰαʔόʔͱ઀ଓͯ͠ΫϥΠΞϯτ
    ༻ͷূ໌ॻΛ࡞੒
    openssl s_client -connect api.github.com:443 -showcerts
    < /dev/null | openssl x509 -outform DER > github.der

    View Slide

  48. ϓϩδΣΫτʹ഑ஔ

    View Slide

  49. class MyURLSessionAdapter: URLSessionAdapter {
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->
    Swift.Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
    if let serverTrust = challenge.protectionSpace.serverTrust {
    var secresult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secresult)
    if errSecSuccess == status {
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
    let serverCertificateData = SecCertificateCopyData(serverCertificate)
    let data = CFDataGetBytePtr(serverCertificateData);
    let size = CFDataGetLength(serverCertificateData);
    let cert1 = NSData(bytes: data, length: size)
    let fileDer = Bundle.main.path(forResource: "github", ofType: "der")
    if let file = fileDer {
    if let cert2 = NSData(contentsOfFile: file) {
    if cert1.isEqual(to: cert2 as Data) {
    completionHandler(URLSession.AuthChallengeDisposition.useCredential,
    URLCredential(trust:serverTrust))
    return
    }
    }
    }
    }
    }
    }
    }
    // Pinning failed
    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
    }
    σʔλΛൺֱ

    View Slide

  50. class MyURLSessionAdapter: URLSessionAdapter {
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->
    Swift.Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
    if let serverTrust = challenge.protectionSpace.serverTrust {
    var secresult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secresult)
    if errSecSuccess == status {
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
    let serverCertificateData = SecCertificateCopyData(serverCertificate)
    let data = CFDataGetBytePtr(serverCertificateData);
    let size = CFDataGetLength(serverCertificateData);
    let cert1 = NSData(bytes: data, length: size)
    let fileDer = Bundle.main.path(forResource: "github", ofType: "der")
    if let file = fileDer {
    if let cert2 = NSData(contentsOfFile: file) {
    if cert1.isEqual(to: cert2 as Data) {
    completionHandler(URLSession.AuthChallengeDisposition.useCredential,
    URLCredential(trust:serverTrust))
    return
    }
    }
    }
    }
    }
    }
    }
    // Pinning failed
    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
    }
    ઀ଓࣦഊ

    View Slide

  51. MyURLSessionAdapter
    • URLSessionAdapterͷαϒΫϥεΛ࡞੒
    • ূ໌ॻϐϯཹΊ
    • ެ։伴ϐϯཹΊ

    View Slide

  52. let pinnedPublicKeyHash = "y2HhTRXXLdmAF1esYBb/muQUl3BIBdmEB8jUvMrGc28="
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->
    Swift.Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
    if let serverTrust = challenge.protectionSpace.serverTrust {
    var secresult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secresult)
    if errSecSuccess == status {
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
    let serverPublicKey = SecCertificateCopyPublicKey(serverCertificate)
    let serverPublicKeyData: NSData =
    SecKeyCopyExternalRepresentation(serverPublicKey!, nil )!
    let keyHash = sha256(data: serverPublicKeyData as Data)
    if (keyHash == pinnedPublicKeyHash) {
    completionHandler(.useCredential, URLCredential(trust:serverTrust))
    return
    }
    }
    }
    }
    }
    // Pinning failed
    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }

    View Slide

  53. let pinnedPublicKeyHash = "y2HhTRXXLdmAF1esYBb/muQUl3BIBdmEB8jUvMrGc28="
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->
    Swift.Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
    if let serverTrust = challenge.protectionSpace.serverTrust {
    var secresult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secresult)
    if errSecSuccess == status {
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
    let serverPublicKey = SecCertificateCopyPublicKey(serverCertificate)
    let serverPublicKeyData: NSData =
    SecKeyCopyExternalRepresentation(serverPublicKey!, nil )!
    let keyHash = sha256(data: serverPublicKeyData as Data)
    if (keyHash == pinnedPublicKeyHash) {
    completionHandler(.useCredential, URLCredential(trust:serverTrust))
    return
    }
    }
    }
    }
    }
    // Pinning failed
    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
    ެ։伴ͷSHA256ϋογϡ

    View Slide

  54. ϋογϡग़ྗ
    #!/bin/bash
    certs=`openssl s_client -servername $1 -host $1 -port 443 -showcerts /dev/null | sed -n '/
    Certificate chain/,/Server certificate/p'`
    rest=$certs
    while [[ "$rest" =~ '-----BEGIN CERTIFICATE-----' ]]
    do
    cert="${rest%%-----END CERTIFICATE-----*}-----END CERTIFICATE-----"
    rest=${rest#*-----END CERTIFICATE-----}
    echo `echo "$cert" | grep 's:' | sed 's/.*s:\(.*\)/\1/'`
    echo "$cert" | openssl x509 -pubkey -noout |
    openssl rsa -pubin -outform der 2>/dev/null |
    openssl dgst -sha256 -binary | openssl enc -base64
    done
    Ҿ༻: https://medium.com/@appmattus/android-security-ssl-pinning-1db8acb6621e

    View Slide

  55. let pinnedPublicKeyHash = "y2HhTRXXLdmAF1esYBb/muQUl3BIBdmEB8jUvMrGc28="
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->
    Swift.Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
    if let serverTrust = challenge.protectionSpace.serverTrust {
    var secresult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secresult)
    if errSecSuccess == status {
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
    let serverPublicKey = SecCertificateCopyPublicKey(serverCertificate)
    let serverPublicKeyData: NSData =
    SecKeyCopyExternalRepresentation(serverPublicKey!, nil )!
    let keyHash = sha256(data: serverPublicKeyData as Data)
    if (keyHash == pinnedPublicKeyHash) {
    completionHandler(.useCredential, URLCredential(trust:serverTrust))
    return
    }
    }
    }
    }
    }
    // Pinning failed
    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
    αʔόʔূ໌ॻ

    View Slide

  56. let pinnedPublicKeyHash = "y2HhTRXXLdmAF1esYBb/muQUl3BIBdmEB8jUvMrGc28="
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->
    Swift.Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
    if let serverTrust = challenge.protectionSpace.serverTrust {
    var secresult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secresult)
    if errSecSuccess == status {
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
    let serverPublicKey = SecCertificateCopyPublicKey(serverCertificate)
    let serverPublicKeyData: NSData =
    SecKeyCopyExternalRepresentation(serverPublicKey!, nil )!
    let keyHash = sha256(data: serverPublicKeyData as Data)
    if (keyHash == pinnedPublicKeyHash) {
    completionHandler(.useCredential, URLCredential(trust:serverTrust))
    return
    }
    }
    }
    }
    }
    // Pinning failed
    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
    αʔόʔূ໌ॻ͔Β
    ެ։伴ΛऔΓग़͢

    View Slide

  57. let pinnedPublicKeyHash = "y2HhTRXXLdmAF1esYBb/muQUl3BIBdmEB8jUvMrGc28="
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->
    Swift.Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
    if let serverTrust = challenge.protectionSpace.serverTrust {
    var secresult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secresult)
    if errSecSuccess == status {
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
    let serverPublicKey = SecCertificateCopyPublicKey(serverCertificate)
    let serverPublicKeyData: NSData =
    SecKeyCopyExternalRepresentation(serverPublicKey!, nil )!
    let keyHash = sha256(data: serverPublicKeyData as Data)
    if (keyHash == pinnedPublicKeyHash) {
    completionHandler(.useCredential, URLCredential(trust:serverTrust))
    return
    }
    }
    }
    }
    }
    // Pinning failed
    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
    ެ։伴ͷSHA256ϋογϡ

    View Slide

  58. let pinnedPublicKeyHash = "y2HhTRXXLdmAF1esYBb/muQUl3BIBdmEB8jUvMrGc28="
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) ->
    Swift.Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
    if let serverTrust = challenge.protectionSpace.serverTrust {
    var secresult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secresult)
    if errSecSuccess == status {
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
    let serverPublicKey = SecCertificateCopyPublicKey(serverCertificate)
    let serverPublicKeyData: NSData =
    SecKeyCopyExternalRepresentation(serverPublicKey!, nil )!
    let keyHash = sha256(data: serverPublicKeyData as Data)
    if (keyHash == pinnedPublicKeyHash) {
    completionHandler(.useCredential, URLCredential(trust:serverTrust))
    return
    }
    }
    }
    }
    }
    // Pinning failed
    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
    ެ։伴ϋογϡΛൺֱ

    View Slide

  59. ϦΫΤετ
    self.adapter = MyURLSessionAdapter(configuration: URLSessionConfiguration.default)
    self.session = Session(adapter: adapter)
    let request = RateLimitRequest()
    self.session.send(request) { result in
    switch result {
    case .success(let rateLimit):
    print("limit: \(rateLimit.limit)")
    print("remaining: \(rateLimit.remaining)")
    case .failure(let error):
    print("error: \(error)")
    }
    }

    View Slide

  60. ϦΫΤετ
    self.adapter = MyURLSessionAdapter(configuration: URLSessionConfiguration.default)
    self.session = Session(adapter: adapter)
    let request = RateLimitRequest()
    self.session.send(request) { result in
    switch result {
    case .success(let rateLimit):
    print("limit: \(rateLimit.limit)")
    print("remaining: \(rateLimit.remaining)")
    case .failure(let error):
    print("error: \(error)")
    }
    }
    MyURLSessionAdapter͔Β
    SessionΛ࡞੒

    View Slide

  61. ϦΫΤετ
    self.adapter = MyURLSessionAdapter(configuration: URLSessionConfiguration.default)
    self.session = Session(adapter: adapter)
    let request = RateLimitRequest()
    self.session.send(request) { result in
    switch result {
    case .success(let rateLimit):
    print("limit: \(rateLimit.limit)")
    print("remaining: \(rateLimit.remaining)")
    case .failure(let error):
    print("error: \(error)")
    }
    }
    SessionΠϯελϯεΛ࢖ͬͯ

    ϦΫΤετ

    View Slide

  62. Alamofire
    • ඪ४Ͱαϙʔτ͞Ε͍ͯΔ
    • Kyash͸ͬͪ͜

    View Slide

  63. SessionManager
    override func viewDidLoad() {
    super.viewDidLoad()
    // ϐϯཹΊ
    let serverTrustPolicies: [String: ServerTrustPolicy] = [
    "api.github.com": .pinCertificates(
    certificates: ServerTrustPolicy.certificates(),
    validateCertificateChain: true,
    validateHost: true
    ),
    "insecure.expired-apis.com": .disableEvaluation
    ]
    self.sessionManager = SessionManager(
    serverTrustPolicyManager: ServerTrustPolicyManager(policies:
    serverTrustPolicies)
    )
    }

    View Slide

  64. SessionManager
    override func viewDidLoad() {
    super.viewDidLoad()
    // ϐϯཹΊ
    let serverTrustPolicies: [String: ServerTrustPolicy] = [
    "api.github.com": .pinCertificates(
    certificates: ServerTrustPolicy.certificates(),
    validateCertificateChain: true,
    validateHost: true
    ),
    "insecure.expired-apis.com": .disableEvaluation
    ]
    self.sessionManager = SessionManager(
    serverTrustPolicyManager: ServerTrustPolicyManager(policies:
    serverTrustPolicies)
    )
    }
    ূ໌ॻϐϯཹΊ

    View Slide

  65. SessionManager
    override func viewDidLoad() {
    super.viewDidLoad()
    // ϐϯཹΊ
    let serverTrustPolicies: [String: ServerTrustPolicy] = [
    "api.github.com": .pinPublicKeys(
    publicKeys: ServerTrustPolicy.publicKeys(),
    validateCertificateChain: true,
    validateHost: true
    ),
    "insecure.expired-apis.com": .disableEvaluation
    ]
    self.sessionManager = SessionManager(
    serverTrustPolicyManager: ServerTrustPolicyManager(policies:
    serverTrustPolicies)
    )
    }
    ެ։伴ϐϯཹΊ

    View Slide

  66. SessionManager
    override func viewDidLoad() {
    super.viewDidLoad()
    // ϐϯཹΊ
    let serverTrustPolicies: [String: ServerTrustPolicy] = [
    "api.github.com": .pinPublicKeys(
    publicKeys: ServerTrustPolicy.publicKeys(),
    validateCertificateChain: true,
    validateHost: true
    ),
    "insecure.expired-apis.com": .disableEvaluation
    ]
    self.sessionManager = SessionManager(
    serverTrustPolicyManager: ServerTrustPolicyManager(policies:
    serverTrustPolicies)
    )
    }
    ϙϦγʔ͔Β
    SessionManagerΛ࡞੒

    View Slide

  67. ϦΫΤετ
    let url = "https://api.github.com/users/kobakei/repos"
    sessionManager.request(url, method: .get,
    parameters: nil,
    encoding: URLEncoding.default,
    headers: nil)
    .responseString { (response: DataResponse) in
    // do something
    }
    SessionManagerΠϯελϯεΛ࢖ͬͯ
    ϦΫΤετ

    View Slide

  68. ϐϯཹΊͰ͖ͯΔ͔֬ೝ
    • Charles΍mitmproxyΛ࢖ͬͨͱ͖ʹɺ

    ௨৴ΤϥʔʹͳΕ͹OK

    View Slide

  69. Charles
    • ΢Σϒ։ൃऀ༻ͷσόοάϓϩΩγ
    • HTTPS௨৴ͷத਎͕ݟ͑Δ
    • தؒऀ߈ܸͱಉ͜͡ͱΛ͍ͯ͠Δ
    • ϐϯཹΊ͍ͯ͠Δͱ࢖͑ͳ͍
    • PC൛/iOS൛͕͋Δ

    View Slide

  70. SSL Proxyingແޮ
    • HTTPSͳͷͰ௨৴಺༰͕

    ҉߸Խ͞Ε͍ͯΔ

    View Slide

  71. SSL Proxying༗ޮ
    • ϐϯཹΊ͍ͯ͠ͳ͍৔߹
    • ௨৴͸੒ޭ
    • JSONؙ͕ݟ͑

    View Slide

  72. SSL Proxying༗ޮ
    • ϐϯཹΊͨ͠৔߹
    • ઀ଓ͕Ωϟϯηϧ͞ΕΔ
    • දࣔ

    View Slide

  73. ։ൃ࣌ʹCharles࢖͑ͳ͍ʁ
    • ։ൃ͚࣌ͩϐϯཹΊΛແޮԽ
    • ࣄނΓ΍͍͢ͷͰݸਓతʹ͸Φεεϝ͠ͳ͍
    • ผ్ϥΠϒϥϦΛೖΕΔ
    • Flipboard/FLEX
    • Facebook/Flipper (Sonar)

    View Slide

  74. SSLαʔόʔূ໌ॻͷӡ༻

    View Slide

  75. SSLαʔόʔূ໌ॻͷӡ༻
    • αʔόʔνʔϜʢ·ͨ͸ΠϯϑϥνʔϜʣͱ
    ࿈ܞͯ͠ߋ৽࡞ۀΛ͢Δ
    • SSLαʔόʔূ໌ॻ or ެ։伴ͷߋ৽࣌ʹɺ

    ΫϥΠΞϯτͷڧ੍Ξοϓσʔτ͕ඞཁ
    • ๨ΕΔͱݹ͍ΫϥΠΞϯτͰ௨৴͕શ໓͢
    Δ

    View Slide

  76. ӡ༻ϑϩʔ
    αʔόʔ
    ΫϥΠΞϯτ
    ͜͜·Ͱʹ

    ߋ৽͠ͳ͍ͱࢮ͵
    ূ໌ॻ
    ༗ޮظݶ

    View Slide

  77. ӡ༻ϑϩʔ
    αʔόʔ
    ΫϥΠΞϯτ
    ূ໌ॻ
    ༗ޮظݶ
    ৽͍͠
    ূ໌ॻʹ
    ߋ৽

    View Slide

  78. ӡ༻ϑϩʔ
    αʔόʔ
    ΫϥΠΞϯτ
    ূ໌ॻ
    ༗ޮظݶ
    ৽͍͠
    ূ໌ॻʹ
    ߋ৽
    ݹ͍ূ໌ॻͱϐϯཹΊ͍ͯ͠ΔͷͰ

    ઀ଓͰ͖ͳ͘ͳΔ

    View Slide

  79. ӡ༻ϑϩʔ
    αʔόʔ
    ΫϥΠΞϯτ
    ূ໌ॻ
    ༗ޮظݶ
    ৽چͷূ໌ॻʹରԠͨ͠

    όʔδϣϯ΁Ξοϓσʔτ
    ৽ΞϓϦ
    چΞϓϦ

    View Slide

  80. ӡ༻ϑϩʔ
    αʔόʔ
    ΫϥΠΞϯτ
    ূ໌ॻ
    ༗ޮظݶ
    ڧ੍ΞοϓσʔτͰ
    ৽ΞϓϦʹ༠ಋ
    ৽ΞϓϦ
    چΞϓϦ

    View Slide

  81. ϋϚΓ͕ͪͳ᠘
    • ڧ੍Ξοϓσʔτͷ࣮૷ํ๏

    View Slide

  82. Α͋͘Δڧ੍Ξοϓσʔτ
    • ϦΫΤετͷϔομʔʹΞϓϦͷόʔδϣϯ
    Λ٧ΊΔ
    • αʔόʔͰϔομʔ͔Βݹ͍ΫϥΠΞϯτ͔
    ൑ఆ͠ɺݹ͍ΫϥΠΞϯτͷͱ͖ʹΤϥʔΛ
    ฦ͢

    View Slide

  83. Կ͕໰୊͔ʁ
    • ϐϯཹΊΛ࣮૷͢ΔͱɺϦΫΤετͷલʹ઀
    ଓ͕Ωϟϯηϧ͞ΕΔ
    • ͭ·Γαʔόʔ͔Βڧ੍ΞοϓσʔτͷΤϥ
    ʔ͕ฦͬͯ͜ͳ͍

    View Slide

  84. ճආࡦ
    • ڧ੍ΞοϓσʔτͷνΣοΫAPIΛ࡞੒͠ɺ

    ϐϯཹΊର৅Ͱ͸ͳ͍ผͷϗετʹஔ͘
    • ผͷϗετΛཱͯΔʢαϒυϝΠϯͰΑ͍ʣ
    • Firebaseͱ͔Ͱ୅༻͢Δ

    View Slide

  85. ຊ౰ʹϐϯཹΊͬͯඞཁʁ

    View Slide

  86. ຊ౰ʹϐϯཹΊ͢΂͖͔ʁ
    • ࣄۀɾϓϩμΫτʹΑΔ
    • Ұ཯ʹ͠ͳ͍ͱ͍͚ͳ͍ʗ͠ͳͯ͘΋͍͍ͱ
    ͸ݴ͑ͳ͍
    • αʔόʔ & ΫϥΠΞϯτશମͰ҆શΛ୲อ͢
    Δ
    • ӡ༻ίετͱϦεΫͷόϥϯε

    View Slide

  87. Jailbreak
    • Jailbreak͞Εͨ୺຤Ͱ͸ϐϯཹΊΛഁΕΔ
    • ϐϯཹΊ͢Δ৔߹ɺJailbreak͞Εͨ୺຤΋஄
    ͘΂͖͔ʁ

    View Slide

  88. େࣄͳ͜ͱ
    • ࣗ෼Ͱ൑அ͠ͳ͍͜ͱʂ
    • ηΩϡϦςΟΤϯδχΞʹ૬ஊ͠Α͏

    View Slide

  89. ଞࣾ͸ϐϯཹΊ͍ͯ͠Δ͔ʁ
    • CharlesͰ֬ೝͰ͖Δ
    • Twitter͸ϐϯཹΊ͍ͯ͠Δ
    • ւ֎ͷۜߦ͸΄ͱΜͲ͍ͯ͠ΔʢΒ͍͠ʣ
    • ೔ຊͷۜߦ΍FinTechΞϓϦ͸͋·Γ΍ͬͯͳ͍ʁ
    • গͳ͘ͱ΋ࣗ෼͕ΞΧ΢ϯτΛ͍࣋ͬͯΔͱ͜Ζ͸…

    View Slide

  90. ·ͱΊ
    • தؒऀ߈ܸରࡦͱͯ͠ɺSSLূ໌ॻͷϐϯཹΊ
    ͷ࣮૷ํ๏Λ঺հ͠·ͨ͠
    • ࣄۀϦεΫͱӡ༻ίετΛߟྀͯ͠ɺηΩϡ
    ϦςΟͷઐ໳ՈͱҰॹʹ΍Δɾ΍Βͳ͍Λ൑
    அ͠·͠ΐ͏

    View Slide

  91. ࢀߟϦϯΫ
    • How to make your iOS apps more secure with SSL pinning
    • https://infinum.co/the-capsized-eight/how-to-make-your-ios-apps-more-secure-with-ssl-pinning
    • iOS certificate pinning with Swift and NSURLSession | StackOverflow
    • https://stackoverflow.com/questions/34223291/ios-certificate-pinning-with-swift-and-nsurlsession
    • Difference between Certificate pinning and public key pinning
    • https://security.stackexchange.com/questions/85209/difference-between-certificate-pinning-and-public-key-
    pinning
    • Why is SSL certificate pinning required?
    • https://stackoverflow.com/questions/45699036/why-is-ssl-certificate-pinning-required
    • iOSΞϓϦͷηΩϡΞίʔσΟϯάೖ໳
    • https://www.jssec.org/dl/20160323_Ikuya_Fukumoto.pdf
    • LAC ηΩϡϦςΟ਍அϨϙʔτ

    View Slide

  92. Thanks!

    View Slide