Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

ຊ౰ʹͦ͏Ͱ͔͢ʁ

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

ରࡦ͸Ͱ͖ͳ͍͔ʁ

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

࣮૷ํ๏

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

URLSessionAdapter • SessionAdapterͷURLSession࣮૷ • URLSessionAdapterͷαϒΫϥεʹɺ
 ϐϯཹΊ༻ͷίʔϧόοΫΛ࣮૷͢Δ

Slide 39

Slide 39 text

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) { ... } ... }

Slide 40

Slide 40 text

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) { ... } ... }

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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αʔόʔূ໌ॻ

Slide 45

Slide 45 text

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ʹ

Slide 46

Slide 46 text

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ϑΝΠϧ

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

ϓϩδΣΫτʹ഑ஔ

Slide 49

Slide 49 text

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) } } σʔλΛൺֱ

Slide 50

Slide 50 text

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) } } ઀ଓࣦഊ

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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ϋογϡ

Slide 54

Slide 54 text

ϋογϡग़ྗ #!/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

Slide 55

Slide 55 text

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) } αʔόʔূ໌ॻ

Slide 56

Slide 56 text

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) } αʔόʔূ໌ॻ͔Β ެ։伴ΛऔΓग़͢

Slide 57

Slide 57 text

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ϋογϡ

Slide 58

Slide 58 text

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) } ެ։伴ϋογϡΛൺֱ

Slide 59

Slide 59 text

ϦΫΤετ 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)") } }

Slide 60

Slide 60 text

ϦΫΤετ 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Λ࡞੒

Slide 61

Slide 61 text

ϦΫΤετ 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ΠϯελϯεΛ࢖ͬͯ
 ϦΫΤετ

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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) ) } ূ໌ॻϐϯཹΊ

Slide 65

Slide 65 text

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) ) } ެ։伴ϐϯཹΊ

Slide 66

Slide 66 text

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Λ࡞੒

Slide 67

Slide 67 text

ϦΫΤετ 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ΠϯελϯεΛ࢖ͬͯ ϦΫΤετ

Slide 68

Slide 68 text

ϐϯཹΊͰ͖ͯΔ͔֬ೝ • Charles΍mitmproxyΛ࢖ͬͨͱ͖ʹɺ
 ௨৴ΤϥʔʹͳΕ͹OK

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

SSL Proxyingແޮ • HTTPSͳͷͰ௨৴಺༰͕
 ҉߸Խ͞Ε͍ͯΔ

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

SSLαʔόʔূ໌ॻͷӡ༻

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

ӡ༻ϑϩʔ αʔόʔ ΫϥΠΞϯτ ͜͜·Ͱʹ
 ߋ৽͠ͳ͍ͱࢮ͵ ূ໌ॻ ༗ޮظݶ

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

ࢀߟϦϯΫ • 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 ηΩϡϦςΟ਍அϨϙʔτ

Slide 92

Slide 92 text

Thanks!