Mobilsikkerhet har blitt et varmt emne. For alle apper som kommuniserer eksternt, er det viktig å vurdere sikkerheten til brukerinformasjon som sendes over et nettverk. I dette innlegget lærer du dagens beste praksis for å sikre kommunikasjonen til iOS-appen din i Swift.
Når du utvikler appen din, bør du vurdere å begrense nettverksforespørsler til de som er avgjørende. For disse forespørsler må du sørge for at de er laget over HTTPS og ikke over HTTP. Dette vil bidra til å beskytte brukerens data fra «mann i mellomangrepene», der en annen datamaskin på nettverket fungerer som et relé for tilkoblingen din, men lytter inn eller endrer dataene den passerer sammen. Trenden i de siste årene er å ha alle tilkoblinger over HTTPS. Heldigvis for oss, håndhever nyere versjoner av Xcode dette allerede.
For å lage en enkel HTTPS-forespørsel på iOS, er alt vi trenger å gjøre ved å legge til "s
" til "http
"delen av nettadressen. Så lenge verten støtter HTTPS og har gyldige sertifikater, får vi en sikker tilkobling. Dette fungerer for APIer som for eksempel URLSession
, NSURLConnection
, og CFNetwork, samt populære tredjepartsbiblioteker som AFNetworking.
Gjennom årene har HTTPS hatt flere angrep mot det. Siden det er viktig å ha HTTPS konfigurert riktig, har Apple opprettet App Transport Security (ATS for kort). ATS sikrer at appens nettverksforbindelser bruker industristandardprotokoller, slik at du ikke ved et uhell sender brukerdata usikkert. Den gode nyheten er at ATS er aktivert som standard for apper som er bygget med nåværende versjoner av Xcode.
ATS er tilgjengelig fra iOS 9 og OS X El Capitan. Nåværende apps i butikken vil ikke plutselig kreve ATS, men apps som er bygget opp mot nyere versjoner av Xcode og dets SDKer, vil ha den aktivert som standard. Noen av de beste praksisene som håndheves av ATS, inkluderer bruk av TLS versjon 1.2 eller høyere, fremoverhemmelighet gjennom ECDHE-nøkkelutveksling, AES-128-kryptering, og bruken av minst SHA-2-sertifikater.
Det er viktig å merke seg at mens ATS er aktivert automatisk, betyr det ikke nødvendigvis at ATS blir håndhevet i appen din. ATS arbeider på grunnlagsklassene som URLSession
og NSURLConnection
og strømbaserte CFNetwork-grensesnitt. ATS blir ikke håndhevet på nettverksgrensesnitt på lavere nivå, for eksempel røde stikkontakter, CFNetwork-stikkontakter eller andre tredjepartsbiblioteker som vil bruke disse lavere nivåanropene. Så hvis du bruker lavnettet nettverk, må du være forsiktig med å implementere ATS beste praksis manuelt.
Siden ATS styrker bruken av HTTPS og andre sikre protokoller, kan du kanskje lure på om du fortsatt vil kunne lage nettverkstilkoblinger som ikke støtter HTTPS, for eksempel når du laster ned bilder fra en CDN-buffer. Ikke bekymre deg, du kan kontrollere ATS-innstillinger for bestemte domener i prosjektets plistfil. I Xcode, finn din Info.plist fil, høyreklikk den og velg Åpne som> Kildekode.
Du finner en seksjon som heter NSAppTransportSecurity
. Hvis det ikke er der, kan du selv legge til koden; formatet er som følger.
NSAppTransportSecurity NSExceptionDomains yourdomain.com NSIncludesSubdomains NSThirdPartyExceptionRequiresForwardSecrecy
Dette lar deg endre ATS-innstillinger for alle nettverksforbindelser. Noen av de vanlige innstillingene er som følger:
NSAllowsArbitraryLoads
: Deaktiverer ATS. Ikke bruk dette! Fremtidige versjoner av Xcode vil fjerne denne nøkkelen.NSAllowsArbitraryLoadsForMedia
: Tillater lasting av media uten ATS-begrensninger for AV Foundation-rammeverket. Du bør bare tillate usikre laster hvis mediet ditt allerede er kryptert på en annen måte. (Tilgjengelig på iOS 10 og MacOS 10.12.)NSAllowsArbitraryLoadsInWebContent
: Kan brukes til å slå av ATS-begrensningene fra webvisning i objekter. Tenk først før du slår av dette, da det lar brukerne laste inn vilkårlig usikkert innhold i appen din. (Tilgjengelig på iOS 10 og MacOS 10.12.)NSAllowsLocalNetworking
: Dette kan brukes til å la lokale nettverksressurser lastes uten ATS-restriksjoner. (Tilgjengelig på iOS 10 og MacOS 10.12.)De NSExceptionDomains
Ordboken lar deg angi innstillinger for bestemte domener. Her er en beskrivelse av noen av de nyttige nøklene du kan bruke for domenet ditt:
NSExceptionAllowsInsecureHTTPLoads
: Tillater at det bestemte domenet bruker ikke-HTTPS-tilkoblinger.NSIncludesSubdomains
: Angir om de nåværende reglene går ned til underdomener.NSExceptionMinimumTLSVersion
: Brukes til å angi eldre, mindre sikre TLS-versjoner som er tillatt.Mens kryptert trafikk er ulæselig, kan det fortsatt bli lagret. Hvis den private nøkkelen som brukes til å kryptere den trafikken, blir kompromittert i fremtiden, kan nøkkelen brukes til å lese all tidligere lagret trafikk.
For å forhindre denne typen kompromiss genererer Perfect Forward Secrecy (PFS) en øktnøkkelsom er unikt for hver kommunikasjonssesjon. Hvis nøkkelen for en bestemt økt er kompromittert, vil det ikke kompromittere data fra andre økter. ATS implementerer PFS som standard, og du kan kontrollere denne funksjonen ved hjelp av plistasten NSExceptionRequiresForwardSecrecy
. Slå av dette vil tillate TLS-cifre som ikke støtter perfekt fremoverhemmelighet.
Certificate Transparency er en kommende standard designet for å kunne kontrollere eller revidere sertifikatene som ble presentert under oppsettet av en HTTPS-tilkobling.
Når verten oppretter et HTTPS-sertifikat, utstedes det av det som kalles en sertifikatmyndighet (CA). Certificate Transparency har som mål å ha nær sanntids overvåkning for å finne ut om et sertifikat ble utstedt skadelig eller har blitt utstedt av en kompromittert sertifikatmyndighet.
Når et sertifikat er utstedt, må sertifiseringsinstansen sende inn sertifikatet til en rekke loggbokmer for vedlegg-bare, som senere kan krysscheckes av kunden og undersøkes av eieren av domenet. Sertifikatet må eksistere i minst to logger for at sertifikatet skal være gyldig.
Plistnøkkelen for denne funksjonen er NSRequiresCertificateTransparency
. Slå på dette vil håndheve Certificate Transparency. Dette er tilgjengelig på iOS 10 og MacOS 10.12 og senere.
Når du kjøper et sertifikat for å bruke HTTPS på serveren din, sies det at sertifikatet er legitimt fordi det er signert med et sertifikat fra en mellomliggende sertifikatmyndighet. Det sertifikatet som brukes av den mellomliggende autoriteten, kan i sin tur bli signert av en annen mellomliggende autoritet, og så videre, så lenge det siste sertifikatet er signert av en rotteattestmyndighet som er klarert.
Når en HTTPS-forbindelse er opprettet, presenteres disse sertifikatene for klienten. Denne kjeden av tillit vurderes for å sikre at sertifikatene er riktig signert av en sertifikatmyndighet som allerede er klarert av iOS. (Det finnes måter å kringgå denne sjekken og akseptere ditt eget selvsignerte sertifikat for testing, men gjør ikke dette i et produksjonsmiljø.)
Hvis noen av sertifikatene i tillitskoden ikke er gyldige, er hele sertifikatet sagt ugyldig, og dataene dine vil ikke bli sendt ut over den usikre forbindelsen. Selv om dette er et godt system, er det ikke idiotsikkert. Det finnes ulike svakheter som kan gjøre IOS tillit til angriperens sertifikat i stedet for et legitimt signert sertifikat.
For eksempel kan avlytningsmusikere ha et mellomliggende sertifikat som er klarert. En omvendt ingeniør kan manuelt instruere iOS for å godta sitt eget sertifikat. I tillegg kan et selskaps policy ha gjort det mulig for enheten å godta sitt eget sertifikat. Alt dette fører til evnen til å utføre et "mann i midten" angrep på trafikken din, slik at den kan leses. Men sertifikatpinne vil forhindre at tilkoblinger blir etablert for alle disse scenariene.
Sertifikatpinne blir reddet ved å sjekke serverens sertifikat mot en kopi av det forventede sertifikatet.
For å implementere pinning, må følgende delegat implementeres. Til URLSession
, bruk følgende:
valgfri func urlSession (_ session: URLSession, didReceive utfordring: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Feid)
Eller for NSURLConnection
, du kan bruke:
valgfri func-forbindelse (_ forbindelse: NSURLConnection, didReceive utfordring: URLAuthenticationChallenge)
Begge metodene tillater deg å skaffe en SecTrust
objekt fra challenge.protectionSpace.serverTrust
. Fordi vi overstyrer godkjenningsdelegatene, må vi nå koble den funksjonen som utfører standard sertifikatkjedekontrollene som vi nettopp har diskutert. Gjør dette ved å ringe SecTrustEvaluate
funksjon. Da kan vi sammenligne serverens sertifikat med en forventet.
Her er et eksempel implementering.
Import Foundation Import Sikkerhetsklasse URLSessionPinningDelegate: NSObject, URLSessionDelegate func urlSession (_session: URLSession, didReceive utfordring: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) var suksess: Bool = la serverTrust = challenge.protectionSpace.serverTrust if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) // Angi policy for å validere domenet la policy: SecPolicy = SecPolicyCreateSSL (true, "yourdomain.com" som CFString) la policies = NSArray. init (objekt: policy) SecTrustSetPolicies (serverTrust, policy) la certificateCount: CFIndex = SecTrustGetCertificateCount (serverTrust) hvis certificateCount> 0 hvis la sertifikat = SecTrustGetCertificateAtIndex (serverTrust, 0) la serverCertificateData = SecCertificateCopyData (sertifikat) som NSData // for loop over array som kan inneholde utgått + kommende sertifikat la certFilenames: [S tring] = ["CertificateRenewed", "Certificate"] for filenameString: String i certFilenames let filePath = Bundle.main.path (forResource: filenameString, ofType: "cer") hvis la filen = filePath hvis la localCertData = NSData contentOfFile: file) // Sett ankercert til din egen server hvis la localCert: SecCertificate = SecCertificateCreateWithData (nil, localCertData) la certArray = [localCert] som CFArray SecTrustSetAnchorCertificates (serverTrust, certArray) // validerer et sertifikat ved å verifisere dets signatur pluss signaturene til sertifikatene i sertifikatkjeden, opp til ankercertifikatet var result = SecTrustResultType.invalid SecTrustEvaluate (serverTrust, & result); la isValid: Bool = (resultat == SecTrustResultType.unspecified || result == SecTrustResultType.proceed) hvis (isValid) // Validér vertscertifikat mot pinned sertifikat. hvis serverCertificateData.isEqual (til: localCertData as Data) suksess = sann fullføringHandler (.useCredential, URLCredential (trust: serverTrust)) break // funnet et vellykket sertifikat, trenger ikke å fortsette looping // end hvis serverCertificateData.isEqual (til: localCertData som data) // // hvis (isValid) // slutt hvis la localCertData = NSData (contentOfFile: file) // slutt hvis la filen = filePath // end for filenameString: String i certFilenames / / avslutte hvis la sertifikat = SecTrustGetCertificateAtIndex (serverTrust, 0) // ende hvis certificateCount> 0 // end hvis (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) / / slutt hvis la serverTrust = challenge.protectionSpace.serverTrust if suksess == false) completionHandler (.cancelAuthenticationChallenge, null)
For å bruke denne koden, angi delegaten til URLSession
når du oppretter tilkoblingen din.
hvis la url = NSURL (streng: "https://yourdomain.com") let session = URLSession (konfigurasjon: URLSessionConfiguration.ephemeral, delegere: URLSessionPinningDelegate (), delegateQueue: null) la dataTask = session.dataTask (med: url som URL, completionHandler: (data, svar, feil) -> Opphevet i // ...) dataTask.resume ()
Pass på å inkludere sertifikatet i apppakken din. Hvis sertifikatet ditt er en .pem-fil, må du konvertere den til en .cer-fil i macOS-terminalen:
openssl x509 -inform PEM-i mycert.pem -outform DER -out certificate.cer
Nå, hvis sertifikatet er endret av en angriper, vil appen oppdage det og nekte å opprette forbindelsen.
Vær oppmerksom på at noen tredjepartsbiblioteker, for eksempel AFNetworking, støtter allerede.
Med alle beskyttelsene så langt, må forbindelsene dine være ganske sikre mot mennesket i midtangrepene. Likevel er en viktig regel om nettverkskommunikasjon aldri å blinde tillit til dataene du mottar. Faktisk er det bra programmeringsøvelse til design på kontrakt. Deinnganger og utdata av metodene dine har en kontrakt som definerer spesifikke grensesnittforventninger; hvis grensesnittet sier at det vil returnere en NSNumber
, da bør det gjøre det. Hvis serveren din forventer en streng på 24 tegn eller færre, må du kontrollere at grensesnittet bare gir opptil 24 tegn.
Dette bidrar til å forhindre uskyldige feil, men enda viktigere, det kan også redusere sannsynligheten for ulike injeksjon og minne korrupsjon angrep. Vanlige parsere som JSONSerialization
klassen vil konvertere tekst til swift datatyper der denne typen test kan gjøres.
hvis la ordboken = json som? [String: Any] hvis la telle = ordbok ["telle"] som? Int // ...
Andre parsere kan fungere med Objective-C tilsvarende objekter. Her er en måte å validere at et objekt er av den forventede typen i Swift.
hvis noenObject er NSArray
Før du sender en delegat en metode, må du kontrollere at objektet er av riktig type, slik at det vil svare på metoden, ellers vil appen krasje med en feil som ikke er kjennetegnet.
hvis noenObject.responds (til: #selector (getter: NSNumber.intValue)
I tillegg kan du se om et objekt samsvarer med en protokoll før du prøver å sende meldinger til det:
hvis noenObject.conforms (til: MyProtocol.self)
Eller du kan sjekke at den samsvarer med en Core Foundation-objekttype.
hvis CFGetTypeID (someObject)! = CFNullGetTypeID ()
Det er en god ide å nøye velge hvilken informasjon fra serveren brukeren kan se. For eksempel er det en dårlig ide å vise et feilvarsel som direkte sender en melding fra serveren. Feilmeldinger kan avsløre debugging og sikkerhetsrelatert informasjon. En løsning er at serveren skal sende bestemte feilkoder som gjør at klienten kan vise forhåndsdefinerte meldinger.
Sørg også for at du koder nettadressene dine slik at de bare inneholder gyldige tegn. NSString
's stringByAddingPercentEscapesUsingEncoding
skal jobbe. Det koder ikke noen tegn som ampersands og pluss tegn, men CFURLCreateStringByAddingPercentEscapes
funksjonen tillater tilpasning av det som skal kodes.
Når du sender data til en server, vær ekstremt forsiktig når noen brukerinngang blir sendt inn i kommandoer som vil bli utført av en SQL-server eller en server som vil kjøre kode. Mens sikring av en server mot slike angrep er utenfor anvendelsesområdet for denne artikkelen, kan vi som mobilutviklere gjøre vår del ved å fjerne tegn for språket som serveren bruker, slik at inngangen ikke er mottakelig for injeksjonsangrep. Eksempler kan være å strippe anførselstegn, semikolon og skråstreker når de ikke er nødvendige for den spesifikke brukerinngangen.
var mutableString: String = streng mutableString = mutableString.replacingOccurrences (av: "%", med: "" mutableString = mutableString.replacingOccurrences (av: "\" "med:" ") mutableString = mutableString.replacingOccurrences \ "" med: "") mutableString = mutableString.replacingOccurrences (av: "\ t" med: "") mutableString = mutableString.replacingOccurrences (av: "\ n" med:
Det er god praksis å begrense lengden på brukerinngang. Vi kan begrense antall tegn som er skrevet i et tekstfelt ved å sette inn UITextField
s delegert og deretter implementere sin shouldChangeCharactersInRange
delegere metode.
func textField (_ textField: UITextField, shouldChangeCharactersIn rekkevidde: NSRange, replacementString streng: String) -> Bool la newLength: Int = textField.text! .characters.count + string.characters.count - range.length hvis newLength> maxSearchLength return false else return true
For en UITextView er delegatemetoden for å implementere dette:
valgfri func textField (_ textField: UITextField, shouldChangeCharactersIn rekkevidde: NSRange, erstatningString streng: String) -> Bool
Brukerinngang kan videre valideres slik at inngangen er av et forventet format. Hvis en bruker for eksempel skal skrive inn en e-postadresse, kan vi for eksempel sjekke om en gyldig adresse:
class func validateEmail (fra emailString: String, useStrictValidation isStrict: Bool) -> Bool var filterString: String? = null hvis isStrict filterString = "[A-Z0-9a-z ._% + -] + @ [A-Za-z0-9 .-] + \\. [A-Za-z] 2,4 " annet filterString =". + @. + \\. [A-Za-z] 2 [A-Za-z] * "la emailPredicate = NSPredicate (format:" SELF MATCHES% @ " filterString!) returnere emailPredicate.evaluate (med: emailString)
Hvis en bruker laster opp et bilde til serveren, kan vi kontrollere at det er et gyldig bilde. For eksempel, for en JPEG-fil, er de første to byte og de siste to byte alltid FF D8 og FF D9.
klasse func validateImageData (_ data: Data) -> Bool la totalBytes: Int = data.count hvis totalBytes < 12 return false let bytes = [UInt8](data) let isValid: Bool = (bytes[0] == UInt8(0xff) && bytes[1] == UInt8(0xd8) && bytes[totalBytes - 2] == UInt8(0xff) && bytes[totalBytes - 1] == UInt8(0xd9)) return isValid
Listen fortsetter, men bare deg som utvikler vil vite hva forventet inngang og utgang skal være, gitt designkravene.
Dataene du sender over nettverket, har potensial til å bli lagret i minnet og på lagring av enheter. Du kan gå langt for å beskytte nettverkskommunikasjonen, slik vi har gjort, for bare å finne ut at kommunikasjonen blir lagret.
Ulike versjoner av iOS har hatt uventet oppførsel når det gjelder hurtigbufferinnstillingene, og noen av reglene for hva som blir cached i iOS, fortsetter å skifte over versjonene. Mens caching hjelper nettverksytelsen ved å redusere antall forespørsler, kan det være en god ide å slå den av for data du synes er svært følsom. Du kan når som helst fjerne den delte cachen (for eksempel ved oppstart av appen) ved å ringe:
URLCache.shared.removeAllCachedResponses ()
For å deaktivere caching på globalt nivå, bruk:
la theURLCache = URLCache (memoryCapacity: 0, diskCapacity: 0, diskPath: null) URLCache.shared = theURLCache
Og hvis du bruker URLSession
, Du kan deaktivere hurtigbufferen for økten som denne:
la konfigurasjon = URLSessionConfiguration.default configuration.requestCachePolicy = .reloadIgnoringLocalCacheData configuration.urlCache = null la session = URLSession.init (konfigurasjon: konfigurasjon)
Hvis du bruker en NSURLConnection
objekt med en delegat, kan du deaktivere hurtigbufferen per forbindelse med denne delegatemetoden:
func forbindelse (_ forbindelse: NSURLConnection, willCacheResponse cachedResponse: CachedURLResponse) -> CachedURLResponse? return nil
Og for å opprette en URL-forespørsel som ikke vil kontrollere bufferen, bruker du:
var request = NSMutableURLRequest (url: theUrl, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: urlTimeoutTime)
Ulike versjoner av iOS 8 hadde noen feil der noen av disse metodene i seg selv ikke ville gjøre noe. Det betyr at det er en god ide å implementere alle av den ovennevnte koden for følsomme forbindelser, når du trenger pålitelig hindring av caching av nettverksforespørsler.
Det er viktig å forstå grensene for HTTPS for å beskytte nettverkskommunikasjon.
I de fleste tilfeller stopper HTTPS på serveren. For eksempel kan forbindelsen til et selskaps server være over HTTPS, men når trafikken treffer serveren, er den ukryptert. Dette innebærer at selskapet vil kunne se informasjonen som ble sendt (i de fleste tilfeller må den), og det betyr også at selskapet kan da proxy eller sende ut informasjonen igjen ukryptert.
Jeg kan ikke fullføre denne artikkelen uten å dekke et nytt konsept som er en ny trend - det kalles "end-to-end-kryptering". Et godt eksempel er en kryptert chat app hvor to mobile enheter kommuniserer med hverandre via en server. De to enhetene lager offentlige og private nøkler. De bytter offentlige nøkler, mens de private nøklene aldri forlater enheten. Dataene sendes fortsatt over HTTPS via serveren, men den blir først kryptert av den andre partens offentlige nøkkel på en slik måte at bare enhetene som har private nøkler kan dekryptere hverandres meldinger.
Som en analogi for å hjelpe deg med å forstå end-to-end kryptering, tenk at jeg vil at noen skal sende meg en melding sikkert som jeg bare kan lese. Så jeg gir dem en boks med en åpen hengelås på den (den offentlige nøkkelen) mens jeg holder hengelåsen (privat nøkkel). Brukeren skriver en melding, legger den i esken, låser hengelåsen og sender den tilbake til meg. Bare jeg kan lese hva meldingen er fordi jeg er den eneste med nøkkelen til å låse opp hengelåsen.
Med end-to-end kryptering tilbyr serveren en tjeneste for kommunikasjon, men den kan ikke lese innholdet i kommunikasjonen - de sender den låste boksen, men de har ikke nøkkelen til å åpne den. Selv om implementeringsdetaljer er utenfor rammen av denne artikkelen, er det et kraftig konsept hvis du vil tillate sikker kommunikasjon mellom brukere av appen din.
Hvis du vil lære mer om denne tilnærmingen, er et sted å starte GitHub-repo for Open Whisper System, et open source-prosjekt.
Nesten alle mobilappene i dag kommuniserer over et nettverk, og sikkerhet er et kritisk viktig, men ofte forsømt aspekt av mobilapputvikling.
I denne artikkelen har vi dekket noen best practices for sikkerhet, inkludert enkle HTTPS, programherding av nettverkskommunikasjon, datasanitisering og end-to-end-kryptering. Disse beste rutene bør tjene som grunnlag for sikkerhet når du koder mobilappen din.
Og mens du er her, sjekk ut noen av våre andre populære iOS app opplæringsprogrammer og kurs!