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での質問と回答を追記しました

85cab5fdf09afe3ee78ce3667681915a?s=128

Keisuke Kobayashi

August 31, 2018
Tweet

Transcript

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

    14:20~ @Track A
  2. ࣗݾ঺հ • Keisuke Kobayashi • Twitter: kobakei122 • GitHub: kobakei

    • Kyash, Inc • Android / iOS / Engineering Manager
  3. ͍ͭ࠷ۙϒϩά͕όζͬͨ

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

    ຊ౰ʹϐϯཹΊ͢΂͖͔
  6. ???ʮαʔόʔͱͷ௨৴͸શ෦ HTTPSԽͯ͠Δ͔Β҆શͰ͠ ΐʂʯ

  7. ຊ౰ʹͦ͏Ͱ͔͢ʁ

  8. HTTPSԽ͍ͯͯ͠΋
 ϦεΫ͸ଘࡏ͢Δ • αʔόʔূ໌ॻݕূͷόΠύεΛ͏͔ͬΓ
 ຊ൪ʹग़ͯ͠͠·͏ • தؒऀ߈ܸ

  9. αʔόʔূ໌ॻݕূͷόΠύε • ओʹ։ൃ࣌ʹࣗݾॺ໊ূ໌ॻΛ࢖͏ͨΊʹݕূΛແޮ Խ͢Δ͜ͱ͕͋Δ • #ifdef DEBUG Ͱ։ൃ൛͚ͩόΠύε͢Δ • ͜Ε͕͏͔ͬΓຊ൪ʹग़ͯ͠·͏ࣄނ

    • ແྉ or ͍҆ূ໌ॻ΋͋ΔͷͰɺ։ൃ؀ڥʹ΋ͪΌΜͱ ͨ͠ূ໌ॻΛஔ͍ͯৗʹόΠύε͠ͳ͍ͷ͕͓͢͢Ί
  10. தؒऀ߈ܸ • 2ऀͷ௨৴ͷؒʹ߈ܸऀ͕հࡏ͢Δ͜ͱͰɺ௨৴ͷ౪ ௌ΍վ͟ΜΛߦ͏߈ܸ • ӳޠͰ͸ Man in the middle

    (MITM) • ࠷΋Ұൠతͳͷ͸ɺෆਖ਼ͳΞΫηεϙΠϯτΛઃஔ͢ Δ͜ͱ • ྫɿո͍͠Free WiFiʹͭͳ͍ͩΒ৘ใ͕ൈ͔Εͨʂ
  11. SSL௨৴ ᶃ઀ଓཁٻ ΫϥΠΞϯτ αʔόʔ

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

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

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

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

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

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

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

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

    ΫϥΠΞϯτ αʔόʔ ߈ܸऀ
  20. ิ଍: 
 ߈ܸऀͷূ໌ॻΛ৴༻ͯ͠͠·͏ʁ • ϧʔτূ໌ॻΛ୺຤ʹΠϯετʔϧ͍ͯ͠Δ ৔߹ • ߈ܸऀ͕ೝূہ͔Βِ଄ূ໌ॻΛऔಘͨ͠৔ ߹ ※Ask

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

    • 2011: DigiNotar ※Ask The SpeakerͰ࣭໰͞ΕͨͷͰ௥ه
  22. Ҿ༻: http://www.security-next.com/094624

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

  24. ରࡦ͸Ͱ͖ͳ͍͔ʁ

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

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

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

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

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

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

  31. ূ໌ॻ or ެ։伴ͷϐϯཹΊ • ূ໌ॻͷϐϯཹΊ • SSLαʔόʔূ໌ॻͦͷ΋ͷ͕Ұக͢Δ͔ݕূ • ߋ৽ස౓͕ߴ͍ =

    ΞϓϦΛසൟʹߋ৽ඞཁ • ެ։伴ͷϐϯཹΊ • ূ໌ॻͷݩʹͳͬͨެ։伴͕Ұக͢Δ͔ݕূ • ߋ৽ස౓͸௿͍
  32. ࢀߟ: Android 7Ҏ߱ • ެࣜͷωοτϫʔΫηΩϡϦςΟߏ੒ػೳ • ެ։伴ͷϐϯཹΊΛαϙʔτ • https://developer.android.com/training/ articles/security-config

  33. ࢀߟ: OkHttp • Square͕։ൃ͢ΔAndroid༻HTTPΫϥΠΞϯτ • σϑΝΫτελϯμʔυ • ެ։伴ͷϐϯཹΊͷΈαϙʔτ͍ͯ͠Δ • “certificate

    pinning”ͱυΩϡϝϯτʹ͸͋Δ ͕ɺ಺෦͸ެ։伴ϐϯཹΊ
  34. ࣮૷ํ๏

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

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

  37. SessionAdapter public protocol SessionAdapter { func createTask(with URLRequest: URLRequest, handler:

    @escaping (Data?, URLResponse?, Error?) -> Void) -> SessionTask func getTasks(with handler: @escaping ([SessionTask]) -> Void) }
  38. URLSessionAdapter • SessionAdapterͷURLSession࣮૷ • URLSessionAdapterͷαϒΫϥεʹɺ
 ϐϯཹΊ༻ͷίʔϧόοΫΛ࣮૷͢Δ

  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) { ... } ... }
  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) { ... } ... }
  41. MyURLSessionAdapter • URLSessionAdapterͷαϒΫϥεΛ࡞੒ • ূ໌ॻϐϯཹΊ • ެ։伴ϐϯཹΊ

  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) } }
  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) } }
  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αʔόʔূ໌ॻ
  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ʹ
  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ϑΝΠϧ
  47. ΫϥΠΞϯτଆͷূ໌ॻ • OpenSSLͰαʔόʔͱ઀ଓͯ͠ΫϥΠΞϯτ ༻ͷূ໌ॻΛ࡞੒ openssl s_client -connect api.github.com:443 -showcerts <

    /dev/null | openssl x509 -outform DER > github.der
  48. ϓϩδΣΫτʹ഑ஔ

  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) } } σʔλΛൺֱ
  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) } } ઀ଓࣦഊ
  51. MyURLSessionAdapter • URLSessionAdapterͷαϒΫϥεΛ࡞੒ • ূ໌ॻϐϯཹΊ • ެ։伴ϐϯཹΊ

  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) }
  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ϋογϡ
  54. ϋογϡग़ྗ #!/bin/bash certs=`openssl s_client -servername $1 -host $1 -port 443

    -showcerts </dev/null 2>/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
  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) } αʔόʔূ໌ॻ
  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) } αʔόʔূ໌ॻ͔Β ެ։伴ΛऔΓग़͢
  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ϋογϡ
  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) } ެ։伴ϋογϡΛൺֱ
  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)") } }
  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Λ࡞੒
  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ΠϯελϯεΛ࢖ͬͯ
 ϦΫΤετ
  62. Alamofire • ඪ४Ͱαϙʔτ͞Ε͍ͯΔ • Kyash͸ͬͪ͜

  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) ) }
  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) ) } ূ໌ॻϐϯཹΊ
  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) ) } ެ։伴ϐϯཹΊ
  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Λ࡞੒
  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<String>) in // do something } SessionManagerΠϯελϯεΛ࢖ͬͯ ϦΫΤετ
  68. ϐϯཹΊͰ͖ͯΔ͔֬ೝ • Charles΍mitmproxyΛ࢖ͬͨͱ͖ʹɺ
 ௨৴ΤϥʔʹͳΕ͹OK

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

    PC൛/iOS൛͕͋Δ
  70. SSL Proxyingແޮ • HTTPSͳͷͰ௨৴಺༰͕
 ҉߸Խ͞Ε͍ͯΔ

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

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

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

    Facebook/Flipper (Sonar)
  74. SSLαʔόʔূ໌ॻͷӡ༻

  75. SSLαʔόʔূ໌ॻͷӡ༻ • αʔόʔνʔϜʢ·ͨ͸ΠϯϑϥνʔϜʣͱ ࿈ܞͯ͠ߋ৽࡞ۀΛ͢Δ • SSLαʔόʔূ໌ॻ or ެ։伴ͷߋ৽࣌ʹɺ
 ΫϥΠΞϯτͷڧ੍Ξοϓσʔτ͕ඞཁ •

    ๨ΕΔͱݹ͍ΫϥΠΞϯτͰ௨৴͕શ໓͢ Δ
  76. ӡ༻ϑϩʔ αʔόʔ ΫϥΠΞϯτ ͜͜·Ͱʹ
 ߋ৽͠ͳ͍ͱࢮ͵ ূ໌ॻ ༗ޮظݶ

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

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

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

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

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

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

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

  84. ճආࡦ • ڧ੍ΞοϓσʔτͷνΣοΫAPIΛ࡞੒͠ɺ
 ϐϯཹΊର৅Ͱ͸ͳ͍ผͷϗετʹஔ͘ • ผͷϗετΛཱͯΔʢαϒυϝΠϯͰΑ͍ʣ • Firebaseͱ͔Ͱ୅༻͢Δ

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

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

    Δ • ӡ༻ίετͱϦεΫͷόϥϯε
  87. Jailbreak • Jailbreak͞Εͨ୺຤Ͱ͸ϐϯཹΊΛഁΕΔ • ϐϯཹΊ͢Δ৔߹ɺJailbreak͞Εͨ୺຤΋஄ ͘΂͖͔ʁ

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

  89. ଞࣾ͸ϐϯཹΊ͍ͯ͠Δ͔ʁ • CharlesͰ֬ೝͰ͖Δ • Twitter͸ϐϯཹΊ͍ͯ͠Δ • ւ֎ͷۜߦ͸΄ͱΜͲ͍ͯ͠ΔʢΒ͍͠ʣ • ೔ຊͷۜߦ΍FinTechΞϓϦ͸͋·Γ΍ͬͯͳ͍ʁ •

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

  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 ηΩϡϦςΟ਍அϨϙʔτ
  92. Thanks!