Securitatea mobilă a devenit un subiect fierbinte. Pentru orice aplicație care comunică de la distanță, este important să se ia în considerare securitatea informațiilor despre utilizator care sunt trimise într-o rețea. În acest post, veți afla cele mai bune practici curente pentru securizarea comunicațiilor aplicației dvs. iOS în Swift.
La dezvoltarea aplicației, luați în considerare limitarea solicitărilor de rețea la cele care sunt esențiale. Pentru aceste solicitări, asigurați-vă că acestea sunt realizate pe HTTPS și nu pe HTTP - aceasta va ajuta la protejarea datelor utilizatorului de la "om în atacurile de mijloc", unde un alt computer din rețea acționează ca un releu pentru conexiunea dvs., dar ascultă în sau modifică datele pe care le transmite. Tendința în ultimii ani este de a avea toate conexiunile realizate prin HTTPS. Din fericire pentru noi, versiuni mai noi de Xcode deja aplica acest lucru.
Pentru a crea o solicitare HTTPS simplă pe iOS, tot ce trebuie să faceți este să adăugați "s
"la"http
"din URL-ul dvs. Atât timp cât gazda acceptă HTTPS și are certificate valide, vom obține o conexiune securizată. URLSession
, NSURLConnection
, și CFNetwork, precum și biblioteci populare terțe, cum ar fi AFNetworking.
De-a lungul anilor, HTTPS a avut mai multe atacuri împotriva sa. Deoarece este important să configurați corect HTTPS, Apple a creat App Transport Security (ATS pe scurt). ATS asigură că conexiunile de rețea ale aplicației dvs. utilizează protocoale standard pentru industrie, astfel încât să nu trimiteți în mod accidental date despre utilizatori. Vestea bună este că ATS este activat în mod implicit pentru aplicațiile construite cu versiunile curente de Xcode.
ATS este disponibil începând cu iOS 9 și OS X El Capitan. Aplicațiile curente din magazin nu vor necesita brusc ATS, dar aplicațiile construite împotriva versiunilor mai noi ale Xcode și SDK-urilor vor fi activate în mod implicit. Unele dintre cele mai bune practici impuse de ATS includ utilizarea TLS versiunea 1.2 sau o versiune superioară, secretizarea înainte prin intermediul schimbului de chei ECDHE, criptarea AES-128 și utilizarea cel puțin a certificatelor SHA-2.
Este important să rețineți că, în timp ce serviciul ATS este activat automat, aceasta nu înseamnă neapărat că ATS este aplicată în aplicația dvs. ATS funcționează pe clasele de fundație cum ar fi URLSession
și NSURLConnection
și interfețe CFNetwork bazate pe fluxuri. ATS nu este implementată pe interfețe de rețea de nivel inferior, cum ar fi prize de tip raw, prize CFNetwork sau orice biblioteci terțe care ar folosi aceste apeluri la nivel inferior. Deci, dacă utilizați rețele de nivel scăzut, va trebui să aveți grijă să implementați manual cele mai bune practici ale ATS.
Deoarece ATS impune utilizarea HTTPS și a altor protocoale securizate, s-ar putea să vă întrebați dacă veți putea totuși să faceți conexiuni de rețea care nu pot suporta HTTPS, cum ar fi atunci când descărcați imagini dintr-o memorie cache CDN. Nu vă faceți griji, puteți controla setările ATS pentru anumite domenii în fișierul plist al proiectului. În Xcode, găsiți-vă info.plist fișier, faceți clic dreapta pe el și alegeți Deschideți As> Cod sursa.
Veți găsi o secțiune numită NSAppTransportSecurity
. Dacă nu există, puteți adăuga singur codul; formatul este după cum urmează.
NSAppTransportSecurity NSExceptionDomains yourdomain.com NSIncludesSubdomains NSThirdPartyExceptionRequiresForwardSecrecy
Aceasta vă permite să modificați setările ATS pentru toate conexiunile de rețea. Unele dintre setările comune sunt după cum urmează:
NSAllowsArbitraryLoads
: Dezactivează ATS. Nu folosi asta! Viitoarele versiuni ale Xcode vor elimina această cheie.NSAllowsArbitraryLoadsForMedia
: Permite încărcarea conținutului media fără restricții ATS pentru cadrul Foundation Foundation. Ar trebui să permiteți sarcini nesigure numai dacă suportul dvs. media este deja criptat prin alte mijloace. (Disponibil pe iOS 10 și macOS 10.12.)NSAllowsArbitraryLoadsInWebContent
: Poate fi folosit pentru a dezactiva restricțiile ATS din obiectele de vizualizare web din aplicația dvs. Gândiți-vă mai întâi înainte de a dezactiva acest lucru, deoarece permite utilizatorilor să încarce un conținut nesigur arbitrar în aplicația dvs. (Disponibil pe iOS 10 și macOS 10.12.)NSAllowsLocalNetworking
: Aceasta poate fi utilizată pentru a permite încărcarea resurselor locale de rețea fără restricții ATS. (Disponibil pe iOS 10 și macOS 10.12.) NSExceptionDomains
dicționar vă permite să setați setările pentru anumite domenii. Iată o descriere a câtorva dintre cheile utile pe care le puteți utiliza pentru domeniul dvs.:
NSExceptionAllowsInsecureHTTPLoads
: Permite domeniului specific să utilizeze conexiuni non-HTTPS.NSIncludesSubdomains
: Specifică dacă regulile actuale sunt transmise în subdomenii.NSExceptionMinimumTLSVersion
: Utilizat pentru a specifica versiunile mai vechi, mai puțin sigure TLS care sunt permise.În timp ce traficul criptat este necitit, poate fi stocat în continuare. Dacă cheia privată utilizată pentru a cripta traficul este compromisă în viitor, cheia poate fi utilizată pentru a citi tot traficul stocat anterior.
Pentru a preveni acest tip de compromis, Perfect Forward Secrecy (PFS) generează o cheie de sesiunecare este unic pentru fiecare sesiune de comunicare. Dacă cheia pentru o anumită sesiune este compromisă, aceasta nu va compromite datele din alte sesiuni. ATS implementează în mod prestabilit PFS și puteți controla această funcție utilizând tasta plistă NSExceptionRequiresForwardSecrecy
. Dacă dezactivați această opțiune, veți permite cititoarele TLS care nu suportă secretul perfect înainte.
Transparența certificatelor este un standard viitoare conceput pentru a putea verifica sau a verifica certificatele prezentate în timpul configurării unei conexiuni HTTPS.
Când gazda dvs. configurează un certificat HTTPS, acesta este emis de ceea ce se numește Autoritatea de certificare (CA). Transparența certificatelor urmărește apropierea de monitorizarea în timp real pentru a afla dacă un certificat a fost emis în mod necorespunzător sau a fost eliberat de o autoritate certificată compromisă.
Atunci când se eliberează un certificat, autoritatea de certificare trebuie să trimită certificatul la un număr de jurnale de certificat numai în anexă, care ulterior pot fi verificate de client și verificate de proprietarul domeniului. Certificatul trebuie să existe în cel puțin două jurnale, pentru ca certificatul să fie valabil.
Cheia plist pentru această caracteristică este NSRequiresCertificateTransparency
. Activarea acestei funcții va impune transparența certificatelor. Acest lucru este disponibil în versiunile iOS 10 și macOS 10.12 și versiunile ulterioare.
Când cumpărați un certificat pentru a utiliza HTTPS pe serverul dvs., acest certificat se consideră legitim, deoarece este semnat cu un certificat de la o autoritate de certificare intermediară. Acest certificat utilizat de autoritatea intermediară poate fi, la rândul său, semnat de o altă autoritate intermediară și așa mai departe, atâta timp cât ultimul certificat este semnat de o autoritate de certificare rădăcină care este de încredere.
Atunci când se stabilește o conexiune HTTPS, aceste certificate sunt prezentate clientului. Acest lanț de încredere este evaluat pentru a vă asigura că certificatele sunt semnate corect de o autoritate de certificare care este deja de încredere de iOS. (Există modalități de a ocoli acest control și de a accepta propriul certificat auto-semnat pentru testare, dar nu faceți acest lucru într-un mediu de producție.)
Dacă oricare dintre certificatele din lanțul de încredere nu este validă, atunci întregul certificat este declarat nevalabil, iar datele dvs. nu vor fi trimise prin conexiunea neautentificată. În timp ce acesta este un sistem bun, nu este rezistent. Există diferite slăbiciuni care pot face ca iOS să aibă încredere în certificatul unui atacator în locul unui certificat semnat în mod legitim.
De exemplu, proxy-urile de interceptare pot avea un certificat intermediar care este de încredere. Un inginer invers poate instrui iOS manual să-și accepte propriul certificat. În plus, politica unei companii poate să fi prevăzut dispozitivul să accepte propriul certificat. Toate acestea conduc la capacitatea de a efectua un atac "om în mijloc" asupra traficului dvs., permițându-l să fie citit. Însă, fixarea certificatelor va împiedica stabilirea conexiunilor pentru toate aceste scenarii.
Certificarea de certificare vine la salvare prin verificarea certificatului serverului împotriva unei copii a certificatului așteptat.
Pentru a implementa fixarea, trebuie implementat următorul delegat. Pentru URLSession
, foloseste urmatoarele:
opțional func urlSession (_ sesiune: URLSession, provocareDescriere: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
Sau pentru NSURLConnection
, poți să folosești:
opțional func conexiune (_ conexiune: NSURLConnection, didReceive provocare: URLAuthenticationChallenge)
Ambele metode vă permit să obțineți a SecTrust
obiect de la challenge.protectionSpace.serverTrust
. Deoarece eliminăm delegațiile de autentificare, trebuie să apelăm în mod explicit funcția care efectuează verificările lanțului certificat standard pe care tocmai am discutat-o. Faceți acest lucru sunând la SecTrustEvaluate
funcţie. Apoi, putem compara certificatul serverului cu unul așteptat.
Iată un exemplu de implementare.
Importul fundației import Clasa de securitate URLSessionPinningDelegate: NSObject, URLSessionDelegate func urSession, _Session: URLSession, provocareRecepție: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDispoziție, URLCredential?) -> Swift.Void) var success: (//) Setarea politicii pentru validarea politicii de acceptare a domeniului: SecPolicy = SecPolicyCreateSSL (true, "yourdomain.com" ca CFString) let policies = NSArray. init (object: policy) SecTrustSetPolicies (serverTrust, policies) permite certificateCount: CFIndex = SecTrustGetCertificateCount (serverTrust) dacă certificateCount> 0 if let certificate = SecTrustGetCertificateAtIndex (serverTrust, 0) enableCertificateData = SecCertificateCopyData peste matrice care poate conține un certificat expirat + viit certFilenames: [S trd] = ["CertificateRenewed", "Certificate"] pentru numele fișieruluiString: String în certFilenames let filePath = Bundle.main.path (pentruResource: filenameString, ofType: cer) (/ / cercArray = [localCert] ca CFArray SecTrustSetAnchorCertificates (serverTrust, certArray) // validează un certificat prin verificarea lui semnătura plus semnăturile certificatelor din lanțul certificatului, până la certificatul de ancorare var rezultat = SecTrustResultType.invalid SecTrustEvaluate (serverTrust, & rezultat); let isValid: Bool = (rezultat == SecTrustResultType.unspecified || result == SecTrustResultType.proceed) dacă (isValid) // Validați certificatul gazdă împotriva certificatului fixat. dacă serverCertificateData.isEqual (la: localCertData as Data) succes = true completionHandler (.useCredential, URLCredential (încredere: serverTrust)) break // a găsit un certificat de succes, nu trebuie să continuați cu buclă // end if serverCertificateData.isEqual (la: localCertData ca date) / / end if (isValid) / end if let localCertData = NSData (contentsOfFile: file) // end if leave file = filePath // end for filenameString: String in certFilenames / / end dacă certificatul = SecTrustGetCertificateAtIndex (serverTrust, 0) // se termină dacă certificateCount> 0 // se termină dacă (provocare.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) / end if leave serverTrust = challenge.protectionSpace.serverTrust if succes == false) completionHandler (.cancelAuthenticationChallenge, nil)
Pentru a utiliza acest cod, setați delegatul URLSession
atunci când creați conexiunea.
dacă permiteți url = NSURL (șir: "https://yourdomain.com") permiteți sesiune = URLSession (configurație: URLSessionConfiguration.ephemeral, delegate: URLSessionPinningDelegate (), delegateQueue: nil) let dataTask = session.dataTask ca adresă URL, completareManager: (date, răspuns, eroare) -> Void în // ...) dataTask.resume ()
Asigurați-vă că includeți certificatul în pachetul de aplicații. Dacă certificatul dvs. este un fișier .pem, va trebui să îl convertiți într-un fișier .cer din terminalul macOS:
openssl x509 - informații PEM - în mycert.pem -outform DER -out certificate.cer
Acum, dacă certificatul este modificat de un atacator, aplicația îl va detecta și va refuza conectarea.
Rețineți că unele biblioteci terțe, cum ar fi AFNetworking, susțin deja suportul.
Cu toate protecțiile de până acum, conexiunile ar trebui să fie destul de sigure împotriva omului în atacurile de mijloc. Chiar și așa, o regulă importantă privind comunicațiile în rețea nu este niciodată să ai încredere orb în datele pe care le primești. De fapt, este o practică bună de programare proiectare prin contract. intrările și ieșirile metodelor dvs. au un contract care definește așteptările interfeței specifice; dacă interfața spune că va reveni NSNumber
, atunci ar trebui să facă acest lucru. Dacă serverul dvs. așteaptă un șir de 24 de caractere sau mai puține, asigurați-vă că interfața va returna doar până la 24 de caractere.
Acest lucru ajută la prevenirea erorilor nevinovate, dar, mai important, poate reduce și probabilitatea diferitelor atacuri de injectare și corupere a memoriei. Parserii obișnuiți, cum ar fi JSONSerialization
clasa va converti textul în tipuri de date Swift în cazul în care aceste tipuri de testare se poate face.
dacă dați dicționarul = json ca? [String: Oricare] dacă lăsați count = dictionary ["count"] ca? Int // ...
Alți parseri pot funcționa cu obiecte echivalente obiectivului C. Iată o modalitate de a valida faptul că un obiect este de tipul așteptat în Swift.
dacă someObject este NSArray
Înainte de a trimite o delegată o metodă, asigurați-vă că obiectul este de tipul corect, astfel încât acesta să răspundă la metodă - altfel aplicația se va prăbuși cu o eroare de "selector nerecunoscut".
dacă someObject.responds (la: #selector (getter: NSNumber.intValue)
În plus, puteți vedea dacă un obiect se conformează unui protocol înainte de a încerca să-i trimiteți mesaje:
dacă someObject.conforms (la: MyProtocol.self)
Sau puteți verifica dacă se potrivește cu un tip de obiect Core Foundation.
dacă CFGetTypeID (someObject)! = CFNullGetTypeID ()
Este o idee bună să alegeți cu atenție informațiile pe care serverul le poate vedea. De exemplu, este o idee proastă să afișați o alertă de eroare care transmite direct un mesaj de la server. Mesajele de eroare pot dezvălui informații legate de depanare și de securitate. O soluție este ca serverul să trimită coduri de eroare specifice care îi determină pe client să afișeze mesaje predefinite.
De asemenea, asigurați-vă că codificați adresele URL astfel încât să conțină numai caractere valide. NSString
„s stringByAddingPercentEscapesUsingEncoding
va functiona. Nu codifică unele caractere, cum ar fi ampersanduri și semne plus, dar CFURLCreateStringByAddingPercentEscapes
funcția permite personalizarea a ceea ce va fi codificat.
Atunci când trimiteți date către un server, fiți foarte atenți atunci când orice intrare a utilizatorului este transmisă în comenzi care vor fi executate de către un server SQL sau un server care va rula codul. În timp ce securizarea unui server împotriva unor astfel de atacuri depășește domeniul de aplicare al acestui articol, ca dezvoltatori de dispozitive mobile, putem face parte prin eliminarea caracterelor pentru limba pe care serverul o folosește astfel încât intrarea să nu fie susceptibilă la atacurile de comandă. Exemple ar putea fi citatele de stripare, punct și virgulă și tăieturile în cazul în care nu sunt necesare pentru intrarea specifică a utilizatorului.
var mutableString: String = șir mutableString = mutableString.replacingOccurrences (de: "%", cu: ") mutableString = mutableString.replacingOccurrences mutableString = mutableString.replacingOccurrences \ n ", cu:" "), mutableString = mutableString.replacingOccurrences (de:" \ t ", cu:" ") mutableString = mutableString.replacingOccurrences
Este o practică bună să limitați lungimea intrărilor de utilizatori. Putem limita numărul de caractere introduse într-un câmp de text prin setarea UITextField
delegat și apoi implementarea acestuia shouldChangeCharactersInRange
metoda delegată.
func textField (_ textField: UITextField, ar trebui să înlocuiascăCharactersIn intervalul: NSRange, replaceString șir: String) -> Bool lasă nouăLength: Int = textField.text! .characters.count + string.characters.count - range.length if newLength> maxSearchLength return false altceva return true
Pentru o metodă UITextView, metoda delegat pentru a implementa acest lucru este:
opțional func textField (_ textField: UITextField, ar trebui să înlocuiascăCharactersIn intervalul: NSRange, înlocuireString șir: String) -> Bool
Intrarea utilizatorilor poate fi validată în continuare, astfel încât intrarea să aibă un format așteptat. De exemplu, dacă un utilizator trebuie să introducă o adresă de e-mail, putem verifica o adresă validă:
class func validateEmail (din emailString: String, useStrictValidation esteStrict: Bool) -> Bool var filterString: String? = zero dacă esteStrict filterString = "[A-Z0-9a-z ._% + -] + @ [A-Za-z0-9 .-] + \ altceva filterString = ". + @. + \\. A-Za-z] 2 [A-Za-z] *" emailPredicate = NSPredicate (format "SELF MATCHES% @" filterString!) retur e-mailPredicate.evaluate (cu: emailString)
Dacă un utilizator încarcă o imagine pe server, putem verifica dacă este o imagine validă. De exemplu, pentru un fișier JPEG, primii doi octeți și ultimii doi octeți sunt întotdeauna FF D8 și FF D9.
class func validateImageData (date: date) -> Bool let totalBytes: Int = data.count dacă 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
Lista continuă, dar numai dvs. ca dezvoltator veți ști ce ar trebui să fie intrarea și ieșirea așteptate, având în vedere cerințele de proiectare.
Datele pe care le trimiteți prin rețea au potențialul de a fi stocate în cache în memorie și în memoria dispozitivului. Puteți merge la distanțe mari pentru a vă proteja comunicațiile de rețea, așa cum am făcut, doar pentru a afla că comunicarea este stocată.
Diferitele versiuni ale iOS au avut un comportament neașteptat când vine vorba de setările cache-ului, iar unele dintre regulile pentru ceea ce se stochează în cache-ul iOS continuă să se schimbe peste versiuni. În timp ce cachingul ajută la performanța rețelei prin reducerea numărului de solicitări, dezactivarea acestuia pentru orice date despre care credeți că este extrem de sensibilă poate fi o idee bună. Puteți să eliminați memoria cache partajată în orice moment (cum ar fi pornirea aplicației) sunând:
URLCache.shared.removeAllCachedResponses ()
Pentru a dezactiva caching-ul la nivel global, folosiți:
permitețiURLCache = URLCache (memoryCapacity: 0, diskCapacity: 0, diskPath: nil) URLCache.shared =URLCache
Și dacă utilizați URLSession
, puteți dezactiva cache-ul pentru sesiunea de mai jos:
lasă configurația = URLSessionConfiguration.default configuration.requestCachePolicy = .reloadIgnoringLocalCacheData configuration.urlCache = nu permite sesiune = URLSession.init (configurare: configurare)
Dacă utilizați un NSURLConnection
obiect cu un delegat, puteți dezactiva cache-ul pentru fiecare conexiune cu această metodă de delegat:
func conexiune (_ conexiune: NSURLConnection, willCacheResponse cachedResponse: CachedURLResponse) -> CachedURLResponse? return nil
Și pentru a crea o solicitare de adresă URL care nu va verifica memoria cache, utilizați:
var request = NSMutableURLRequest (url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: urlTimeoutTime)
Diferitele versiuni ale iOS 8 au avut unele bug-uri, unde unele dintre aceste metode nu ar face nimic. Asta înseamnă că este o idee bună de a implementa toate din codul de mai sus pentru conexiuni sensibile, când trebuie să preveniți în mod fiabil încorporarea cererilor de rețea.
Este important să înțelegeți limitele HTTPS pentru protejarea comunicațiilor de rețea.
În majoritatea cazurilor, HTTPS se oprește la server. De exemplu, conexiunea mea cu serverul unei corporații poate fi peste HTTPS, dar odată ce traficul atinge serverul, acesta este necriptat. Acest lucru înseamnă că societatea va putea vedea informațiile trimise (în cele mai multe cazuri are nevoie) și înseamnă, de asemenea, că acea companie ar putea atunci să proxy sau să transmită acea informație din nou necriptată.
Nu pot finaliza acest articol fără a acoperi încă un concept care este o tendință recentă - ceea ce se numește criptare end-to-end. Un bun exemplu este o aplicație de chat criptată în care două dispozitive mobile comunică între ele printr-un server. Cele două dispozitive creează chei publice și private - schimbă cheile publice, în timp ce cheile lor private nu părăsesc niciodată dispozitivul. Datele sunt încă transmise prin HTTPS prin server, dar sunt mai întâi criptate de cheia publică a celeilalte părți astfel încât numai dispozitivele care dețin cheile private pot decripta reciproc mesajele.
Ca o analogie care vă ajută să înțelegeți criptarea end-to-end, imaginați-vă că vreau ca cineva să-mi trimită un mesaj în siguranță, pe care îl pot citi numai. Deci le ofer o cutie cu un lacăt deschis (cheia publică) în timp ce păstrez cheia lacătului (cheia privată). Utilizatorul scrie un mesaj, îl pune în cutie, blochează lacătul și o trimite înapoi la mine. Numai eu pot citi ce este mesajul pentru că eu sunt singurul cu cheia pentru a debloca lacătul.
Cu criptarea de la capăt la celălalt, serverul oferă un serviciu de comunicații, dar nu poate citi conținutul comunicării - livrează cutia încuiată, dar nu are cheia pentru ao deschide. Deși detaliile implementării depășesc sfera de aplicare a acestui articol, este un concept puternic dacă doriți să permiteți o comunicare sigură între utilizatorii aplicației dvs..
Dacă doriți să aflați mai multe despre această abordare, un loc pentru a începe este GitHub repo pentru Open Whisper System, un proiect open-source.
Aproape toate aplicațiile mobile de astăzi vor comunica într-o rețea, iar securitatea este un aspect important, dar adesea neglijat, al dezvoltării aplicațiilor mobile.
În acest articol, am abordat câteva dintre cele mai bune practici de securitate, inclusiv HTTPS simplu, întărirea aplicațiilor de comunicații în rețea, dezinfectarea datelor și criptarea end-to-end. Aceste cele mai bune practici ar trebui să servească drept bază pentru securitate atunci când codați aplicația dvs. mobilă.
Și în timp ce sunteți aici, verificați câteva dintre cele mai populare tutoriale și cursuri pentru aplicații iOS!