La sécurité mobile est devenue un sujet brûlant. Pour toute application qui communique à distance, il est important de prendre en compte la sécurité des informations utilisateur envoyées sur un réseau. Dans cet article, vous apprendrez les meilleures pratiques actuelles en matière de sécurisation des communications de votre application iOS dans Swift..
Lors du développement de votre application, envisagez de limiter les demandes réseau à celles qui sont essentielles. Pour ces demandes, assurez-vous qu'elles sont effectuées via HTTPS et non via HTTP. Cela aidera à protéger les données de vos utilisateurs contre les "attaques entre deux personnes", lorsqu'un autre ordinateur du réseau sert de relais à votre connexion, mais écoute bien. ou modifie les données qu'il transmet. La tendance de ces dernières années est que toutes les connexions soient établies via HTTPS. Heureusement pour nous, les nouvelles versions de Xcode appliquent déjà cette.
Pour créer une simple requête HTTPS sur iOS, il suffit d’ajouter "s
" au "http
"section de l'URL. Tant que l'hôte prend en charge HTTPS et possède des certificats valides, nous obtiendrons une connexion sécurisée. Cela fonctionne pour les API telles que URLSession
, NSURLConnection
, et CFNetwork, ainsi que des bibliothèques tierces populaires telles que AFNetworking.
Au fil des ans, HTTPS a eu plusieurs attaques contre lui. Comme il est important de configurer correctement le protocole HTTPS, Apple a créé App Transport Security (ATS). ATS veille à ce que les connexions réseau de votre application utilisent des protocoles standard, afin d'éviter toute transmission accidentelle des données utilisateur. La bonne nouvelle est que ATS est activé par défaut pour les applications construites avec les versions actuelles de Xcode..
ATS est disponible à partir de iOS 9 et OS X El Capitan. Les applications actuelles dans le magasin ne nécessiteront pas soudainement ATS, mais les applications conçues pour les versions plus récentes de Xcode et de ses SDK l'activeront par défaut. Parmi les meilleures pratiques appliquées par ATS, citons l'utilisation de TLS version 1.2 ou ultérieure, la confidentialité du transfert via l'échange de clé ECDHE, le cryptage AES-128 et l'utilisation d'au moins des certificats SHA-2..
Il est important de noter que si ATS est activé automatiquement, cela ne signifie pas nécessairement que ATS est appliqué dans votre application. ATS travaille sur les classes de base telles que URLSession
et NSURLConnection
et des interfaces CFNetwork basées sur les flux. ATS n'est pas appliqué aux interfaces réseau de niveau inférieur telles que les sockets brutes, les sockets CFNetwork ou les bibliothèques tierces qui utiliseraient ces appels de niveau inférieur. Donc, si vous utilisez un réseau de bas niveau, vous devrez faire attention à mettre en œuvre manuellement les meilleures pratiques d'ATS..
Comme ATS impose l'utilisation de HTTPS et d'autres protocoles sécurisés, vous pouvez vous demander si vous pourrez toujours établir des connexions réseau qui ne peuvent pas prendre en charge HTTPS, par exemple lorsque vous téléchargez des images à partir d'un cache CDN. Ne vous inquiétez pas, vous pouvez contrôler les paramètres ATS pour des domaines spécifiques dans le fichier Plist de votre projet. Dans Xcode, trouvez votre info.plist fichier, faites un clic droit dessus et choisissez Ouvrir en tant que> Code source.
Vous trouverez une section intitulée NSAppTransportSecurity
. Si ce n'est pas le cas, vous pouvez ajouter le code vous-même; le format est le suivant.
NSAppTransportSecurity NSExceptionDomains votredomaine.com NSIncludesSubdomains NSThirdPartyExceptionRequiresForwardSecrecy
Cela vous permet de modifier les paramètres ATS pour toutes les connexions réseau. Certains des paramètres communs sont les suivants:
NSAllowsArbitraryLoads
: Désactive ATS. Ne l'utilisez pas! Les futures versions de Xcode vont supprimer cette clé.NSAllowsArbitraryLoadsForMedia
: Permet le chargement de médias sans restrictions ATS pour le cadre AV Foundation. Vous ne devez autoriser les chargements non sécurisés que si votre média est déjà crypté par un autre moyen. (Disponible sur iOS 10 et macOS 10.12.)NSAllowsArbitraryLoadsInWebContent
: Peut être utilisé pour désactiver les restrictions ATS des objets de vue Web dans votre application. Réfléchissez bien avant de désactiver cette option, car elle permet aux utilisateurs de charger du contenu arbitraire et non sécurisé dans votre application. (Disponible sur iOS 10 et macOS 10.12.)NSAllowsLocalNetworking
: Ceci peut être utilisé pour permettre le chargement des ressources du réseau local sans restrictions ATS. (Disponible sur iOS 10 et macOS 10.12.)le NSExceptionDomains
dictionnaire vous permet de définir des paramètres pour des domaines spécifiques. Voici une description de certaines des clés utiles que vous pouvez utiliser pour votre domaine:
NSExceptionAllowsInsecureHTTPLoads
: Autorise le domaine spécifique à utiliser des connexions non HTTPS.NSIncludesSubdomains
: Spécifie si les règles actuelles sont transmises aux sous-domaines.NSExceptionMinimumTLSVersion
: Utilisé pour spécifier les anciennes versions de TLS moins sécurisées autorisées.Bien que le trafic crypté soit illisible, il peut toujours être stocké. Si la clé privée utilisée pour chiffrer ce trafic est compromise à l'avenir, elle peut être utilisée pour lire tout le trafic précédemment stocké..
Pour éviter ce type de compromission, Perfect Forward Secrecy (PFS) génère une clé de session.qui est unique pour chaque session de communication. Si la clé d'une session spécifique est compromise, les données des autres sessions ne seront pas compromises. ATS implémente PFS par défaut et vous pouvez contrôler cette fonctionnalité à l'aide de la touche plist NSExceptionRequiresForwardSecrecy
. Le désactiver permet aux chiffrements TLS qui ne prennent pas en charge le secret de transfert parfait..
La transparence des certificats est une norme à venir conçue pour pouvoir vérifier ou auditer les certificats présentés lors de la configuration d'une connexion HTTPS..
Lorsque votre hôte configure un certificat HTTPS, celui-ci est émis par ce que l'on appelle une autorité de certification. La transparence des certificats vise à permettre une surveillance presque en temps réel pour déterminer si un certificat a été émis de manière malveillante ou a été délivré par une autorité de certification compromise..
Lorsqu'un certificat est émis, l'autorité de certification doit soumettre le certificat à un certain nombre de journaux de certificats ajoutés uniquement, qui peuvent ensuite être vérifiés par le client et examinés par le propriétaire du domaine. Le certificat doit exister dans au moins deux journaux pour que le certificat soit valide..
La touche pliste pour cette fonctionnalité est NSRequireCertificateTransparency
. L'activation de cette option appliquera la transparence du certificat. Ceci est disponible sur iOS 10 et macOS 10.12 et versions ultérieures..
Lorsque vous achetez un certificat pour utiliser HTTPS sur votre serveur, ce certificat est dit légitime car il est signé avec un certificat d'une autorité de certification intermédiaire. Ce certificat utilisé par l'autorité intermédiaire peut à son tour être signé par une autre autorité intermédiaire, et ainsi de suite, à condition que le dernier certificat soit signé par une autorité de certification racine de confiance..
Lorsqu'une connexion HTTPS est établie, ces certificats sont présentés au client. Cette chaîne de confiance est évaluée pour vérifier que les certificats sont correctement signés par une autorité de certification déjà approuvée par iOS. (Il existe des moyens de contourner cette vérification et d'accepter votre propre certificat auto-signé à des fins de test, mais ne le faites pas dans un environnement de production.)
Si l'un des certificats de la chaîne de confiance n'est pas valide, l'intégralité du certificat est dite invalide et vos données ne seront pas envoyées via la connexion non approuvée. Bien que ce soit un bon système, il n'est pas infaillible. Diverses faiblesses existent qui peuvent amener iOS à faire confiance au certificat d'un attaquant au lieu d'un certificat légitimement signé.
Par exemple, les mandataires d'interception peuvent posséder un certificat intermédiaire approuvé. Un reverse engineering peut ordonner manuellement à iOS d'accepter son propre certificat. En outre, une stratégie d'entreprise peut avoir configuré le périphérique pour accepter son propre certificat. Tout cela conduit à la possibilité de lancer une attaque «homme au milieu» sur votre trafic, lui permettant d'être lu. Mais l’épinglage de certificat empêchera l’établissement de connexions pour tous ces scénarios..
L'épinglage de certificat vient à la rescousse en vérifiant le certificat du serveur par rapport à une copie du certificat attendu.
Pour implémenter l'épinglage, le délégué suivant doit être implémenté. Pour URLSession
, utilisez le suivant:
func facultatif urlSession (_ session: URLSession, défi DidReceive: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Annulé)
Ou pour NSURLConnection
, vous pouvez utiliser:
func connexion optionnelle (_ connection: NSURLConnection, défi didReceive: URLAuthenticationChallenge)
Les deux méthodes vous permettent d’obtenir un SecTrust
objet de challenge.protectionSpace.serverTrust
. Comme nous substituons les délégués d'authentification, nous devons maintenant appeler explicitement la fonction qui effectue les vérifications standard de la chaîne de certificats dont nous venons de parler. Faites cela en appelant le SecTrustEvaluate
une fonction. Ensuite, nous pouvons comparer le certificat du serveur avec celui attendu.
Voici un exemple d'implémentation.
import Foundation import Classe de sécurité URLSessionPinningDelegate: NSObject, URLSessionDelegate func urlSession (_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCred?) let serverTrust = challenge.protectionSpace.serverTrust if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) // Définir une politique pour valider le domaine let policy: SecPolicy = SecPolicyCreateSSL (true, "yourdomain.com", comme le spécifie la politique). init (objet: politique) SecTrustSetPolicies (serverTrust, politiques) let certificateCount: CFIndex = SecTrustGetCertificateCount (serverTrust) si certificateCount> 0 if let certificate = SecTrustGetCertificateAtIndex (serverTrust, 0) let serverCert sur tableau qui peut contenir expiré + certificat à venir laisser certFilenames: [S tring] = ["CertificateRenewed", "Certificate"] pour filenameString: Chaîne dans certFilenames let filePath = Bundle.main.path (forResource: filenameString, ofType: "cer") si let file = filePath si let localCertData = NSData ( contentsOfFile: file) // Définissez le certificat d'ancrage sur votre propre serveur si laissez localCert: SecCertificate = SecCertificateCreateWithData (nil, localCertData) let certArray = [localCert] en tant que CFArray SecTrustSetAnchorCertificates (serverTrust, certArray) // validate un certificat signature plus les signatures des certificats dans sa chaîne de certificats, jusqu'au certificat d'ancre var result = SecTrustResultType.invalid SecTrustEvaluate (serverTrust, & result); let isValid: Bool = (result == SecTrustResultType.unspecified || result == SecTrustResultType.proceed) if (isValid) // Valide le certificat d'hôte contre le certificat épinglé. Si serverCertificateData.isEqual (to: localCertData as Data) succès = true completionHandler (.useCredential, URLCredential (trust: serverTrust)) break // a trouvé un certificat réussi, il n'est pas nécessaire de continuer à boucler // end si serverCertificateDataData.isEqual (to: localCertData as Data) // end if (isValid) // end si let localCertData = NSData (contentsOfFile: fichier) // end si let file = cheminFichier // end pour filenameString: String dans certFilenames / / end if let certificate = SecTrustGetCertificateAtIndex (serverTrust, 0) // end si CertificateCount> 0 // end if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) // fin si let serverTrust = challenge.protectionSpaceServer success == false) completionHandler (.cancelAuthenticationChallenge, nil)
Pour utiliser ce code, définissez le délégué du URLSession
lors de la création de votre connexion.
si let url = NSURL (string: "https://yourdomain.com") let session = URLSession (configuration: URLSessionConfiguration.ephemeral, délégué: URLSessionPinningDelegate (), delegateQueue: nil) let dataTask = session.dataTask (avec: url comme URL, completionHandler: (données, réponse, erreur) -> Annuler dans //…) dataTask.resume ()
Assurez-vous d'inclure le certificat dans votre bundle d'applications. Si votre certificat est un fichier .pem, vous devrez le convertir en fichier .cer dans le terminal macOS:
openssl x509 -inform PEM -in mycert.pem -outform DER -out certificate.cer
Désormais, si le certificat est modifié par un attaquant, votre application le détecte et refuse d'établir la connexion..
Notez que certaines bibliothèques tierces telles que AFNetworking prennent en charge l’épinglage déjà.
Avec toutes les protections jusqu'à présent, vos connexions devraient être assez sécurisées contre les attaques de type "homme au milieu". Néanmoins, une règle importante en matière de communication réseau est de ne jamais faire aveuglément confiance aux données que vous recevez. En fait, c’est une bonne pratique de programmation pour conception par contrat. leles entrées et les sorties de vos méthodes ont un contrat qui définit les attentes spécifiques de l'interface; si l'interface dit qu'il va retourner un NSNumber
, alors il devrait le faire. Si votre serveur attend une chaîne de 24 caractères ou moins, assurez-vous que l'interface ne renvoie que 24 caractères..
Cela permet d'éviter des erreurs innocentes, mais plus important encore, cela peut également réduire le risque d'attaques diverses par injection et corruption de mémoire. Analyseurs communs tels que le JSONSérialisation
la classe convertira le texte en types de données Swift où ces types de test peuvent être effectués.
si laisser dictionnaire = json comme? [String: Any] si let count = dictionary ["count"] as? Int //…
D'autres analyseurs peuvent fonctionner avec des objets équivalents à Objective-C. Voici un moyen de valider qu'un objet est du type attendu dans Swift.
si someObject est NSArray
Avant d'envoyer une méthode à un délégué, assurez-vous que l'objet est du type approprié pour pouvoir y répondre. Dans le cas contraire, l'application se planterait avec une erreur «sélecteur non reconnu»..
si someObject.responds (to: #selector (getter: NSNumber.intValue)
De plus, vous pouvez voir si un objet est conforme à un protocole avant d'essayer de lui envoyer des messages:
si someObject.conforms (à: MyProtocol.self)
Ou vous pouvez vérifier qu'il correspond à un type d'objet Core Foundation.
si CFGetTypeID (someObject)! = CFNullGetTypeID ()
C'est une bonne idée de choisir avec soin les informations du serveur que l'utilisateur peut voir. Par exemple, il est déconseillé d’afficher une alerte d’erreur qui transmet directement un message du serveur. Les messages d'erreur peuvent révéler des informations de débogage et relatives à la sécurité. Une solution consiste à ce que le serveur envoie des codes d'erreur spécifiques qui amènent le client à afficher des messages prédéfinis..
Assurez-vous également de coder vos URL afin qu’elles ne contiennent que des caractères valides.. NSString
de stringByAddingPercentEscapesUsingEncoding
marchera. Il ne code pas certains caractères tels que les esperluettes et les signes plus, mais le CFURLCreateStringByAddingPercentEscapes
fonction permet la personnalisation de ce qui sera encodé.
Lors de l'envoi de données à un serveur, soyez extrêmement prudent lorsque vous saisissez une entrée utilisateur dans des commandes qui seront exécutées par un serveur SQL ou par un serveur qui exécutera du code. Bien que sécuriser un serveur contre de telles attaques dépasse le cadre de cet article, en tant que développeurs mobiles, nous pouvons faire notre part en supprimant les caractères de la langue utilisée par le serveur afin que l'entrée ne soit pas soumise aux attaques par injection de commande. Des exemples peuvent être les guillemets, les points-virgules et les barres obliques, s'ils ne sont pas nécessaires pour une entrée utilisateur spécifique.
var mutableString: String = chaîne mutableString = mutableString.replacingOccurrences (de: "%", avec: "") mutableString = mutableString = mutableString.replacingOccurrences (de: "%", avec: "") \ '", avec:" "mutableString = mutableString.RemplaceOccurrences (de:" \ t ", avec:" ") mutableString = mutableString.RemplaceOccurrences (de:" \ t ", avec:" ") mutableString = mutableString.remplacementOccurrences (de:" \ t ", avec:" "
Il est recommandé de limiter la durée de saisie de l'utilisateur. Nous pouvons limiter le nombre de caractères saisis dans un champ de texte en définissant le UITextField
le délégué et la mise en œuvre de son shouldChangeCharactersInRange
méthode déléguée.
func textField (_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool let newLength: Int = textField.text! .characters.count + string.characters.count - range.length if newLength> maxSearchLength return false else return true
Pour une UITextView, la méthode déléguée pour l'implémenter est la suivante:
func facultatif textField (_ textField: UITextField, shouldChangeCharactersIn gamme: NSRange, replacementString string: String) -> Bool
La saisie de l’utilisateur peut être ensuite validée pour que la saisie ait le format attendu. Par exemple, si un utilisateur doit entrer une adresse électronique, nous pouvons vérifier une adresse valide:
class func validateEmail (from emailString: String, useStrictValidation isStrict: Bool) -> Bool var filterString: String? = nil si isStrict filterString = "[A-Z0-9a-z ._% + -] + @ [A-Za-z0-9 .-] + \\. [A-Za-z] 2,4 " else filterString =". + @. + \\. [AZZ] 2 [AZZ] * " laisser emailPredicate = NSPredicate (format:" SELF MATCHES% @ ", filterString!) renvoie emailPredicate.evaluate (avec: emailString)
Si un utilisateur télécharge une image sur le serveur, nous pouvons vérifier qu'il s'agit d'une image valide. Par exemple, pour un fichier JPEG, les deux premiers octets et les deux derniers octets sont toujours FF D8 et FF D9..
class func validateImageData (_ data: Data) -> Bool let totalBytes: Int = data.count si 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
La liste est longue, mais vous seul, en tant que développeur, saurez ce que l'entrée et la sortie attendus devraient être, étant donné les exigences de conception..
Les données que vous envoyez sur le réseau peuvent potentiellement être mises en cache dans la mémoire et sur le stockage de l'appareil. Comme nous l’avons fait, vous pouvez faire tout ce qui est en votre pouvoir pour protéger les communications de votre réseau, mais seulement pour savoir que la communication est en cours de stockage..
Diverses versions d'iOS ont eu un comportement inattendu en ce qui concerne les paramètres de cache, et certaines des règles pour ce qui est mis en cache dans iOS changent constamment d'une version à l'autre. Bien que la mise en cache améliore les performances du réseau en réduisant le nombre de demandes, il peut être judicieux de la désactiver pour toutes les données que vous jugez très sensibles. Vous pouvez supprimer le cache partagé à tout moment (par exemple, au démarrage de l'application) en appelant:
URLCache.shared.removeAllCachedResponses ()
Pour désactiver la mise en cache au niveau global, utilisez:
let theURLCache = URLCache (memoryCapacity: 0, diskCapacity: 0, diskPath: nil) URLCache.shared = theURLCache
Et si vous utilisez URLSession
, vous pouvez désactiver le cache pour la session comme ceci:
let configuration = URLSessionConfiguration.default configuration.requestCachePolicy = .reloadIgnoringLocalCacheData configuration.urlCache = nil let session = URLSession.init (configuration: configuration)
Si vous utilisez un NSURLConnection
objet avec un délégué, vous pouvez désactiver le cache par connexion avec cette méthode de délégué:
func connection (_ connection: NSURLConnection, willCacheResponse cachedResponse: CachedURLResponse) -> CachedURLResponse? return nil
Et pour créer une demande d'URL qui ne vérifiera pas le cache, utilisez:
var request = NSMutableURLRequest (url: theUrl, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: urlTimeoutTime)
Différentes versions d'iOS 8 présentaient des bogues où certaines de ces méthodes ne feraient rien. Cela signifie que c'est une bonne idée de mettre en œuvre tout du code ci-dessus pour les connexions sensibles, lorsque vous devez empêcher de manière fiable la mise en cache des requêtes réseau.
Il est important de comprendre les limites du protocole HTTPS pour la protection des communications réseau..
Dans la plupart des cas, HTTPS s’arrête sur le serveur. Par exemple, ma connexion au serveur d'une entreprise peut être via HTTPS, mais une fois que le trafic atteint le serveur, il est non crypté. Cela signifie que la société sera en mesure de voir les informations qui ont été envoyées (dans la plupart des cas, il le faut), mais cela signifie également que la société peut alors utiliser un proxy ou transmettre à nouveau ces informations sans les chiffrer..
Je ne peux pas terminer cet article sans aborder un autre concept qui correspond à une tendance récente, à savoir le "cryptage de bout en bout". Un bon exemple est une application de discussion cryptée dans laquelle deux appareils mobiles communiquent l'un avec l'autre via un serveur. Les deux appareils créent des clés publiques et privées. Ils échangent des clés publiques, tandis que leurs clés privées ne quittent jamais l'appareil. Les données sont toujours envoyées via HTTPS via le serveur, mais elles sont d'abord cryptées par la clé publique de l'autre partie de manière à ce que seuls les périphériques possédant les clés privées puissent décoder leurs messages..
Comme analogie pour vous aider à comprendre le cryptage de bout en bout, imaginez que je veuille que quelqu'un m'envoie un message en toute sécurité que je ne peux que lire. Je leur fournis donc une boîte avec un cadenas ouvert (la clé publique) tandis que je garde la clé du cadenas (clé privée). L'utilisateur écrit un message, le met dans la boîte, verrouille le cadenas et me le renvoie. Seulement je peux lire le message parce que je suis le seul à avoir la clé pour ouvrir le cadenas.
Avec le cryptage de bout en bout, le serveur fournit un service de communication, mais il ne peut pas lire le contenu de la communication. Il expédie la boîte verrouillée, mais n'a pas la clé pour l'ouvrir. Bien que les détails de la mise en œuvre dépassent le cadre de cet article, il s'agit d'un concept puissant si vous souhaitez permettre une communication sécurisée entre les utilisateurs de votre application..
Si vous souhaitez en savoir plus sur cette approche, vous devez commencer par le référentiel GitHub pour Open Whisper System, un projet open source..
Aujourd'hui, presque toutes les applications mobiles communiquent sur un réseau et la sécurité est un aspect extrêmement important mais souvent négligé du développement d'applications mobiles..
Dans cet article, nous avons abordé certaines des meilleures pratiques de sécurité, notamment le protocole HTTPS simple, le renforcement des applications de communication réseau, la désinfection des données et le cryptage de bout en bout. Ces meilleures pratiques devraient servir de base à la sécurité lors du codage de votre application mobile..
Et pendant que vous êtes ici, consultez certains de nos autres tutoriels et cours populaires sur les applications iOS.!