La sicurezza mobile è diventata un argomento caldo. Per qualsiasi app che comunica in remoto, è importante considerare la sicurezza delle informazioni dell'utente che vengono inviate attraverso una rete. In questo post, imparerai le migliori pratiche correnti per proteggere le comunicazioni della tua app iOS in Swift.
Quando sviluppi la tua app, considera di limitare le richieste di rete a quelle essenziali. Per quelle richieste, assicurati che siano realizzate su HTTPS e non su HTTP: questo aiuterà a proteggere i dati dell'utente da "man in the middle attack", dove un altro computer sulla rete funge da relay per la tua connessione, ma ascolta o cambia i dati che passa. La tendenza negli ultimi anni è di avere tutte le connessioni effettuate su HTTPS. Fortunatamente per noi, le nuove versioni di Xcode già lo applicano.
Per creare una semplice richiesta HTTPS su iOS, tutto ciò che dobbiamo fare è aggiungere "S
" al "http
"sezione dell'URL. Finché l'host supporta HTTPS e ha certificati validi, otterremo una connessione sicura. Funziona per API come URLSession
, NSURLConnection
, e CFNetwork, così come le popolari librerie di terze parti come AFNetworking.
Nel corso degli anni, HTTPS ha avuto diversi attacchi contro di esso. Poiché è importante che HTTPS sia configurato correttamente, Apple ha creato App Transport Security (ATS in breve). ATS garantisce che le connessioni di rete della tua app utilizzino protocolli standard del settore, in modo da non inviare accidentalmente dati utente in modo non sicuro. La buona notizia è che ATS è abilitato di default per le app create con le attuali versioni di Xcode.
ATS è disponibile da iOS 9 e OS X El Capitan. Le app attuali nello store non richiedono improvvisamente ATS, ma le app create contro le versioni più recenti di Xcode e i relativi SDK verranno abilitate per impostazione predefinita. Alcune delle best practice applicate da ATS includono l'utilizzo di TLS versione 1.2 o successiva, la segretezza di inoltro tramite lo scambio di chiavi ECDHE, la crittografia AES-128 e l'uso di almeno certificati SHA-2.
È importante notare che mentre ATS è abilitato automaticamente, non significa necessariamente che ATS venga applicato nella tua app. ATS funziona sulle classi base come URLSession
e NSURLConnection
e interfacce CFNetwork basate sul flusso. ATS non viene applicato su interfacce di rete di livello inferiore come socket non elaborati, socket CFNetwork o librerie di terze parti che utilizzano queste chiamate di livello inferiore. Pertanto, se si utilizzano reti di basso livello, è necessario fare attenzione a implementare le migliori pratiche di ATS manualmente.
Poiché ATS applica l'uso di HTTPS e altri protocolli sicuri, ci si potrebbe chiedere se sarà ancora possibile effettuare connessioni di rete che non supportano HTTPS, ad esempio quando si scaricano immagini da una cache CDN. Non preoccuparti, puoi controllare le impostazioni ATS per domini specifici nel file plist del progetto. In Xcode, trova il tuo info.plist file, fare clic con il tasto destro del mouse e scegliere Apri come> Codice sorgente.
Troverete una sezione chiamata NSAppTransportSecurity
. Se non è lì, puoi aggiungere tu stesso il codice; il formato è il seguente.
NSAppTransportSecurity NSExceptionDomains yourdomain.com NSIncludesSubdomains NSThirdPartyExceptionRequiresForwardSecrecy
Ciò consente di modificare le impostazioni ATS per tutte le connessioni di rete. Alcune delle impostazioni comuni sono le seguenti:
NSAllowsArbitraryLoads
: Disabilita ATS. Non usare questo! Le versioni future di Xcode rimuoveranno questa chiave.NSAllowsArbitraryLoadsForMedia
: Consente il caricamento di supporti senza restrizioni ATS per il framework AV Foundation. Si dovrebbero consentire solo carichi non sicuri se il proprio file multimediale è già crittografato con altri mezzi. (Disponibile su iOS 10 e macOS 10.12.)NSAllowsArbitraryLoadsInWebContent
: Può essere utilizzato per disattivare le restrizioni ATS dagli oggetti di visualizzazione Web nell'app. Pensaci prima di disattivarlo perché consente agli utenti di caricare contenuti insicuri arbitrari all'interno della tua app. (Disponibile su iOS 10 e macOS 10.12.)NSAllowsLocalNetworking
: Può essere utilizzato per consentire il caricamento delle risorse di rete locali senza restrizioni ATS. (Disponibile su iOS 10 e macOS 10.12.)Il NSExceptionDomains
dizionario ti consente di impostare le impostazioni per domini specifici. Ecco una descrizione di alcune delle chiavi utili che puoi utilizzare per il tuo dominio:
NSExceptionAllowsInsecureHTTPLoads
: Consente al dominio specifico di utilizzare connessioni non HTTPS.NSIncludesSubdomains
: Specifica se le regole correnti vengono passate ai sottodomini.NSExceptionMinimumTLSVersion
: Utilizzato per specificare versioni TLS meno recenti e meno sicure consentite.Sebbene il traffico crittografato sia illeggibile, potrebbe comunque essere memorizzato. Se la chiave privata utilizzata per crittografare quel traffico è compromessa in futuro, la chiave può essere utilizzata per leggere tutto il traffico precedentemente memorizzato.
Per evitare questo tipo di compromissione, Perfect Forward Secrecy (PFS) genera una chiave di sessionequesto è unico per ogni sessione di comunicazione. Se la chiave per una sessione specifica viene compromessa, non comprometterà i dati di altre sessioni. ATS implementa PFS per impostazione predefinita e puoi controllare questa funzione utilizzando la chiave Plist NSExceptionRequiresForwardSecrecy
. La disattivazione consentirà ai crittografi TLS che non supportano la perfetta segretezza in avanti.
Certificato La trasparenza è uno standard imminente progettato per essere in grado di controllare o verificare i certificati presentati durante l'installazione di una connessione HTTPS.
Quando l'host imposta un certificato HTTPS, viene emesso da ciò che viene chiamato Autorità di certificazione (CA). Certificato La trasparenza mira ad avvicinarsi al monitoraggio in tempo reale per scoprire se un certificato è stato rilasciato maliziosamente o è stato rilasciato da un'autorità di certificazione compromessa.
Quando viene emesso un certificato, l'autorità di certificazione deve inviare il certificato a un numero di registri di certificati solo append, che possono essere successivamente verificati dal cliente e controllati dal proprietario del dominio. Il certificato deve esistere in almeno due registri affinché il certificato sia valido.
Il tasto Plist per questa funzione è NSRequiresCertificateTransparency
. Attivandola applicherà la trasparenza del certificato. È disponibile su iOS 10 e macOS 10.12 e versioni successive.
Quando si acquista un certificato per utilizzare HTTPS sul proprio server, tale certificato è ritenuto legittimo poiché è firmato con un certificato da un'autorità di certificazione intermedia. Tale certificato utilizzato dall'autorità intermedia potrebbe a sua volta essere firmato da un'altra autorità intermedia e così via, a condizione che l'ultimo certificato sia firmato da un'autorità di certificazione di base attendibile.
Quando viene stabilita una connessione HTTPS, questi certificati vengono presentati al client. Questa catena di trust viene valutata per assicurarsi che i certificati siano firmati correttamente da un'autorità di certificazione già considerata affidabile da iOS. (Esistono modi per aggirare questo controllo e accettare il proprio certificato autofirmato per il test, ma non farlo in un ambiente di produzione.)
Se uno qualsiasi dei certificati nella catena di fiducia non è valido, l'intero certificato viene dichiarato non valido ei dati non verranno inviati sulla connessione non affidabile. Mentre questo è un buon sistema, non è infallibile. Esistono vari punti deboli che possono far sì che iOS si fidi del certificato di un utente malintenzionato anziché di un certificato firmato legittimamente.
Ad esempio, i proxy di intercettazione possono possedere un certificato intermedio di cui ci si può fidare. Un tecnico inverso può istruire manualmente iOS ad accettare il proprio certificato. Inoltre, la politica di una società potrebbe aver predisposto il dispositivo per accettare il proprio certificato. Tutto ciò porta alla capacità di eseguire un attacco "man in the middle" sul tuo traffico, consentendone la lettura. Tuttavia, il blocco dei certificati impedisce che vengano stabilite connessioni per tutti questi scenari.
Il blocco del certificato viene in soccorso controllando il certificato del server con una copia del certificato previsto.
Per implementare il blocco, è necessario implementare il seguente delegato. Per URLSession
, utilizza il seguente:
opzionale func urlSession (_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
O per NSURLConnection
, Puoi usare:
connessione func opzionale (_ connection: NSURLConnection, didReceive challenge: URLAuthenticationChallenge)
Entrambi i metodi ti permettono di ottenere un SecTrust
oggetto da challenge.protectionSpace.serverTrust
. Poiché stiamo ignorando i delegati di autenticazione, ora dobbiamo chiamare esplicitamente la funzione che esegue i controlli della catena di certificati standard che abbiamo appena discusso. Fallo chiamando il SecTrustEvaluate
funzione. Quindi possiamo confrontare il certificato del server con uno previsto.
Ecco un esempio di implementazione.
import Foundation import Security class URLSessionPinningDelegate: NSObject, URLSessionDelegate func urlSession (_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) var successo: Bool = false se let serverTrust = challenge.protectionSpace.serverTrust if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) // Imposta la politica per la convalida del criterio di let del dominio: SecPolicy = SecPolicyCreateSSL (true, "yourdomain.com" come CFString) lascia policy = NSArray. init (oggetto: policy) SecTrustSetPolicies (serverTrust, policies) let certificateCount: CFIndex = SecTrustGetCertificateCount (serverTrust) se certificateCount> 0 se let certificato = SecTrustGetCertificateAtIndex (serverTrust, 0) let serverCertificateData = SecCertificateCopyData (certificato) come NSData // per loop su un array che può contenere un certificato scaduto + imminente lasciare certFilenames: [S tring] = ["CertificateRenewed", "Certificate"] per filenameString: String in certFilenames let filePath = Bundle.main.path (forResource: filenameString, ofType: "cer") se let file = filePath se lasciato localCertData = NSData ( contentsOfFile: file) // Imposta anchor cert sul tuo server if let localCert: SecCertificate = SecCertificateCreateWithData (nil, localCertData) let certArray = [localCert] come CFArray SecTrustSetAnchorCertificates (serverTrust, certArray) // convalida un certificato verificandone il relativo firma più le firme dei certificati nella sua catena di certificati, fino al certificato di ancoraggio var result = SecTrustResultType.invalid SecTrustEvaluate (serverTrust, & result); let isValid: Bool = (result == SecTrustResultType.unspecified || result == SecTrustResultType.proceed) if (isValid) // Convalida il certificato host rispetto al certificato aggiunto. if serverCertificateData.isEqual (to: localCertData as Data) success = true completionHandler (.useCredential, URLCredential (trust: serverTrust)) break // ha trovato un certificato riuscito, non ha bisogno di continuare il ciclo // end if serverCertificateData.isEqual (to: localCertData as Data) // end if (isValid) // end if let localCertData = NSData (contentsOfFile: file) // end if let file = filePath // end per filenameString: String in certFilenames / / end if let certificate = SecTrustGetCertificateAtIndex (serverTrust, 0) // end if certificateCount> 0 // end if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) // end se let serverTrust = challenge.protectionSpace.serverTrust if ( success == false) completionHandler (.cancelAuthenticationChallenge, nil)
Per utilizzare questo codice, impostare il delegato di URLSession
quando crei la tua connessione.
se let url = NSURL (stringa: "https://yourdomain.com") let session = URLSession (configurazione: URLSessionConfiguration.ephemeral, delegate: URLSessionPinningDelegate (), delegateQueue: nil) let dataTask = session.dataTask (con: url come URL, completionHandler: (data, response, error) -> Void in // ...) dataTask.resume ()
Assicurati di includere il certificato nel pacchetto della tua app. Se il tuo certificato è un file .pem, dovrai convertirlo in un file .cer nel terminale macOS:
openssl x509 -inform PEM -in mycert.pem -outform DER -out certificate.cer
Ora, se il certificato viene modificato da un utente malintenzionato, la tua app lo rileva e rifiuta di effettuare la connessione.
Nota che alcune librerie di terze parti come AFNetworking supportano già il pinning.
Con tutte le protezioni finora, le tue connessioni dovrebbero essere abbastanza sicure contro gli attacchi degli uomini nel mezzo. Anche così, una regola importante per quanto riguarda le comunicazioni di rete non è mai quella di fidarsi ciecamente dei dati che stai ricevendo. In effetti, è buona pratica di programmazione design per contratto. Ilinput e output dei tuoi metodi hanno un contratto che definisce le specifiche aspettative dell'interfaccia; se l'interfaccia dice che restituirà un NSNumber
, allora dovrebbe farlo. Se il tuo server si aspetta una stringa di 24 caratteri o meno, assicurati che l'interfaccia restituisca solo 24 caratteri.
Questo aiuta a prevenire errori innocenti, ma soprattutto, può anche ridurre la probabilità di vari attacchi di corruzione di memoria e di iniezione. Parser comuni come il JSONSerialization
class convertirà il testo in tipi di dati Swift dove è possibile eseguire questi tipi di test.
se lascia dizionario = json come? [String: Any] if let count = dictionary ["count"] as? Int // ...
Altri parser potrebbero funzionare con oggetti equivalenti Objective-C. Ecco un modo per verificare che un oggetto sia del tipo previsto in Swift.
se someObject è NSArray
Prima di inviare un metodo delegato, assicurati che l'oggetto sia del tipo giusto in modo che risponda al metodo, altrimenti l'app si bloccherà con un errore "selettore non riconosciuto".
if someObject.responds (to: #selector (getter: NSNumber.intValue)
Inoltre, puoi vedere se un oggetto è conforme a un protocollo prima di provare a inviare messaggi ad esso:
se someObject.conforms (a: MyProtocol.self)
Oppure puoi verificare che corrisponda a un tipo di oggetto Core Foundation.
if CFGetTypeID (someObject)! = CFNullGetTypeID ()
È una buona idea scegliere con cura quali informazioni dal server l'utente può vedere. Ad esempio, è una cattiva idea visualizzare un avviso di errore che passa direttamente un messaggio dal server. I messaggi di errore potrebbero rivelare informazioni relative al debug e alla sicurezza. Una soluzione è di inviare al server specifici codici di errore che inducano il client a mostrare messaggi predefiniti.
Inoltre, assicurati di codificare gli URL in modo che contengano solo caratteri validi. NSString
'S stringByAddingPercentEscapesUsingEncoding
funzionerà. Non codifica alcuni caratteri come la e commerciale e segni più, ma il CFURLCreateStringByAddingPercentEscapes
la funzione consente la personalizzazione di ciò che verrà codificato.
Quando si inviano dati a un server, prestare estrema attenzione quando un input utente viene passato a comandi che verranno eseguiti da un server SQL o da un server che eseguirà il codice. Mentre proteggere un server contro tali attacchi va oltre lo scopo di questo articolo, come sviluppatori mobili possiamo fare la nostra parte rimuovendo i caratteri per la lingua che il server sta usando in modo che l'input non sia suscettibile di comandare attacchi di iniezione. Gli esempi potrebbero essere la rimozione di virgolette, punto e virgola e barre quando non sono necessari per l'input dell'utente specifico.
var mutableString: String = string mutableString = mutableString.replacingOccurrences (of: "%", with: "") mutableString = mutableString.replacingOccurrences (of: "\" ", con:" ") mutableString = mutableString.replacingOccurrences (di:" \ "", con: "") mutableString = mutableString.replacingOccurrences (di: "\ t", con: "") mutableString = mutableString.replacingOccurrences (di: "\ n", con: "")
È buona norma limitare la lunghezza dell'input dell'utente. Possiamo limitare il numero di caratteri digitati in un campo di testo impostando il UITextField
delegato e poi implementando il suo shouldChangeCharactersInRange
metodo delegato.
func textField (_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replaceString stringa: String) -> Bool let newLength: Int = textField.text! .characters.count + string.characters.count - range.length if newLength> maxSearchLength return false else return true
Per un UITextView, il metodo delegato per implementare questo è:
optional func textField (_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replaceString stringa: String) -> Bool
L'input dell'utente può essere ulteriormente convalidato in modo che l'input sia di un formato previsto. Ad esempio, se un utente deve inserire un indirizzo email, possiamo controllare un indirizzo valido:
class func validateEmail (da emailString: String, useStrictValidation isStrict: Bool) -> Bool var filterString: String? = nil se isStrict filterString = "[A-Z0-9a-z ._% + -] + @ [A-Za-z0-9 .-] + \\. [A-Za-z] 2,4 " else filterString =". + @. + \\. [A-Za-z] 2 [A-Za-z] * " let emailPredicate = NSPredicate (formato:" SELF MATCHES% @ ", filterString!) return emailPredicate.evaluate (con: emailString)
Se un utente sta caricando un'immagine sul server, possiamo verificare che si tratti di un'immagine valida. Ad esempio, per un file JPEG, i primi due byte e gli ultimi due byte sono sempre FF D8 e FF D9.
class func validateImageData (_ data: Data) -> Bool let totalBytes: Int = data.count se 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
L'elenco potrebbe continuare, ma solo tu, in quanto lo sviluppatore saprà quale dovrebbe essere l'input e l'output previsti, dati i requisiti di progettazione.
I dati inviati sulla rete hanno il potenziale per essere memorizzati nella cache e nella memoria del dispositivo. Puoi fare di tutto per proteggere le tue comunicazioni di rete, come abbiamo fatto, solo per scoprire che la comunicazione è in fase di memorizzazione.
Varie versioni di iOS hanno avuto un comportamento inaspettato quando si tratta delle impostazioni della cache, e alcune delle regole per ciò che viene memorizzato nella cache in iOS continuano a cambiare le versioni. Mentre il caching aiuta le prestazioni della rete riducendo il numero di richieste, disattivarlo per tutti i dati che ritieni altamente sensibili può essere una buona idea. Puoi rimuovere la cache condivisa in qualsiasi momento (ad esempio all'avvio dell'app) chiamando:
URLCache.shared.removeAllCachedResponses ()
Per disabilitare la memorizzazione nella cache a livello globale, utilizzare:
let theURLCache = URLCache (memoryCapacity: 0, diskCapacity: 0, diskPath: nil) URLCache.shared = theURLCache
E se stai usando URLSession
, puoi disabilitare la cache per la sessione in questo modo:
let configuration = URLSessionConfiguration.default configuration.requestCachePolicy = .reloadIgnoringLocalCacheData configuration.urlCache = nil let session = URLSession.init (configurazione: configurazione)
Se stai usando un NSURLConnection
oggetto con un delegato, è possibile disabilitare la cache per connessione con questo metodo delegato:
func connection (_ connection: NSURLConnection, willCacheResponse cachedResponse: CachedURLResponse) -> CachedURLResponse? return nil
E per creare una richiesta URL che non controlli la cache, usa:
var request = NSMutableURLRequest (url: theUrl, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: urlTimeoutTime)
Varie versioni di iOS 8 avevano alcuni bug in cui alcuni di questi metodi da soli non avrebbero fatto nulla. Ciò significa che è una buona idea implementare tutti del codice precedente per le connessioni sensibili, quando è necessario impedire in modo affidabile la memorizzazione nella cache delle richieste di rete.
È importante comprendere i limiti di HTTPS per proteggere le comunicazioni di rete.
Nella maggior parte dei casi, HTTPS si ferma sul server. Ad esempio, la mia connessione al server di una società potrebbe essere su HTTPS, ma una volta che il traffico colpisce il server, non è crittografato. Ciò significa che la società sarà in grado di vedere le informazioni che sono state inviate (nella maggior parte dei casi è necessario), e significa anche che la società potrebbe quindi proxy o passare di nuovo quelle informazioni non criptate.
Non riesco a completare questo articolo senza coprire un altro concetto che è una tendenza recente, quella che viene chiamata "crittografia end-to-end". Un buon esempio è un'app di chat crittografata in cui due dispositivi mobili comunicano tra loro tramite un server. I due dispositivi creano chiavi pubbliche e private: scambiano le chiavi pubbliche, mentre le loro chiavi private non abbandonano mai il dispositivo. I dati vengono comunque inviati tramite HTTPS attraverso il server, ma vengono prima crittografati dalla chiave pubblica dell'altra parte in modo tale che solo i dispositivi che detengono le chiavi private possano decrittografare i reciproci messaggi.
Come un'analogia per aiutarti a capire la crittografia end-to-end, immagina che voglio che qualcuno mi invii un messaggio in modo sicuro che solo io possa leggere. Quindi fornisco loro una scatola con un lucchetto aperto su di essa (la chiave pubblica) mentre tengo la chiave del lucchetto (chiave privata). L'utente scrive un messaggio, lo inserisce nella casella, blocca il lucchetto e me lo rimanda. Solo io posso leggere il messaggio perché sono l'unico con la chiave per sbloccare il lucchetto.
Con la crittografia end-to-end, il server fornisce un servizio per la comunicazione, ma non può leggere il contenuto della comunicazione - spedisce la casella bloccata, ma non ha la chiave per aprirlo. Sebbene i dettagli di implementazione vadano oltre lo scopo di questo articolo, è un concetto potente se si desidera consentire una comunicazione sicura tra gli utenti della tua app.
Se vuoi saperne di più su questo approccio, un punto di partenza è il repository GitHub per Open Whisper System, un progetto open source.
Quasi tutte le app mobili oggi comunicheranno attraverso una rete e la sicurezza è un aspetto di importanza critica, ma spesso trascurato, dello sviluppo di app per dispositivi mobili.
In questo articolo, abbiamo trattato alcune best practice sulla sicurezza, tra cui il semplice HTTPS, l'hardening delle comunicazioni di rete, la disinfezione dei dati e la crittografia end-to-end. Queste best practice dovrebbero servire come base per la sicurezza quando si codifica la tua app mobile.
E mentre sei qui, dai un'occhiata ad alcuni dei nostri tutorial e corsi più popolari su iOS!