Asegurando las comunicaciones en iOS

La seguridad móvil se ha convertido en un tema candente. Para cualquier aplicación que se comunique de forma remota, es importante tener en cuenta la seguridad de la información del usuario que se envía a través de una red. En esta publicación, conocerá las mejores prácticas actuales para proteger las comunicaciones de su aplicación iOS en Swift. 

Utilizar HTTPS

Al desarrollar su aplicación, considere limitar las solicitudes de red a aquellas que son esenciales. Para esas solicitudes, asegúrese de que se realicen a través de HTTPS y no a través de HTTP; esto ayudará a proteger los datos de su usuario de "ataques del hombre en el medio", donde otra computadora en la red actúa como un retransmisor para su conexión, pero escucha. o cambia los datos que pasa a lo largo. La tendencia en los últimos años es tener todas las conexiones hechas a través de HTTPS. Afortunadamente para nosotros, las nuevas versiones de Xcode ya lo hacen cumplir.

Para crear una solicitud HTTPS simple en iOS, todo lo que tenemos que hacer es adjuntar "s" al "http"en la sección de la URL. Mientras el host admita HTTPS y tenga certificados válidos, obtendremos una conexión segura. Esto funciona para API como URLSession, NSURConexión, y CFNetwork, así como las bibliotecas de terceros populares como AFNetworking.

Aplicación de seguridad de transporte

A lo largo de los años, HTTPS ha tenido varios ataques contra él. Dado que es importante tener HTTPS configurado correctamente, Apple ha creado la Seguridad de transporte de aplicaciones (ATS para abreviar). ATS se asegura de que las conexiones de red de su aplicación estén utilizando protocolos estándar de la industria, para que no envíe datos de usuarios de forma insegura por accidente. La buena noticia es que ATS está habilitado de forma predeterminada para aplicaciones creadas con versiones actuales de Xcode.

ATS está disponible a partir de iOS 9 y OS X El Capitán. Las aplicaciones actuales en la tienda no requerirán repentinamente ATS, pero las aplicaciones creadas contra versiones más recientes de Xcode y sus SDK lo tendrán habilitado de forma predeterminada. Algunas de las mejores prácticas aplicadas por ATS incluyen el uso de TLS versión 1.2 o superior, el secreto hacia adelante a través del intercambio de claves ECDHE, el cifrado AES-128 y el uso de al menos certificados SHA-2.

Es importante tener en cuenta que si bien ATS se habilita automáticamente, no significa necesariamente que ATS se aplique en su aplicación. ATS trabaja en las clases de cimentación como URLSession y NSURConexión y las interfaces CFNetwork basadas en flujo. ATS no se aplica en las interfaces de red de nivel inferior, como sockets sin procesar, sockets de red de red o cualquier otra biblioteca de terceros que utilice estas llamadas de nivel inferior. Entonces, si está utilizando redes de bajo nivel, tendrá que tener cuidado de implementar las mejores prácticas de ATS manualmente..

Excepciones ATS

Dado que ATS impone el uso de HTTPS y otros protocolos seguros, puede preguntarse si todavía podrá hacer conexiones de red que no sean compatibles con HTTPS, como cuando descarga imágenes desde un caché CDN. No se preocupe, puede controlar la configuración ATS para dominios específicos en el archivo plist de su proyecto. En Xcode, encuentra tu info.plist Archivo, haga clic derecho sobre él, y elija Abrir como> Código fuente.

Encontrarás una sección llamada NSAppTransportSecurity. Si no está allí, puede agregar el código usted mismo; el formato es el siguiente. 

NSAppTransportSecurity  NSExceptionDomains  tudominio.com  NSIncluyeSubdominios  NSThirdPartyExceptionRequiresForwardSecrecy    

Esto le permite cambiar la configuración ATS para todas las conexiones de red. Algunos de los ajustes comunes son los siguientes:

  • NSAllowsArbitraryLoads: Desactiva ATS. ¡No uses esto! Las futuras versiones de Xcode eliminarán esta clave..
  • NSAllowsArbitraryLoadsForMedia: Permite la carga de medios sin restricciones ATS para el marco de AV Foundation. Solo debe permitir cargas inseguras si su medio ya está cifrado por otros medios. (Disponible en iOS 10 y macOS 10.12.)
  • NSAllowsArbitraryLoadsInWebContent: Se puede usar para desactivar las restricciones ATS de los objetos de vista web en su aplicación. Piense primero antes de desactivar esto, ya que les permite a los usuarios cargar contenido inseguro arbitrario dentro de su aplicación. (Disponible en iOS 10 y macOS 10.12.)
  • NSAllowsLocalNetworking: Esto se puede usar para permitir que los recursos de la red local se carguen sin restricciones ATS. (Disponible en iOS 10 y macOS 10.12.)

los NSExceptionDomains diccionario le permite establecer la configuración de dominios específicos. Aquí hay una descripción de algunas de las claves útiles que puede usar para su dominio:

  • NSExceptionAllowsInsecureHTTPLoads: Permite que el dominio específico utilice conexiones no HTTPS.
  • NSIncluyeSubdominios: Especifica si las reglas actuales se transfieren a subdominios.
  • NSExceptionMinimumTLSVersion: Se utiliza para especificar versiones TLS más antiguas y menos seguras que están permitidas.

Perfecto secreto hacia adelante

Si bien el tráfico cifrado es ilegible, aún puede almacenarse. Si la clave privada utilizada para cifrar ese tráfico se ve comprometida en el futuro, la clave se puede usar para leer todo el tráfico almacenado previamente. 

Para evitar este tipo de compromiso, Perfect Forward Secrecy (PFS) genera una clave de sesiónEso es único para cada sesión de comunicación. Si la clave para una sesión específica está comprometida, no comprometerá los datos de ninguna otra sesión. ATS implementa PFS de forma predeterminada, y puede controlar esta función utilizando la tecla plist NSExceptionRequiresForwardSecrecy. Desactivar esto permitirá que los cifrados TLS no admitan el secreto hacia adelante perfecto.

Transparencia del certificado

La transparencia del certificado es un próximo estándar diseñado para poder verificar o auditar los certificados presentados durante la configuración de una conexión HTTPS. 

Cuando su host configura un certificado HTTPS, es emitido por lo que se llama una Autoridad de Certificación (CA). La transparencia del certificado tiene como objetivo tener un monitoreo cercano al tiempo real para averiguar si un certificado fue emitido de manera malintencionada o si fue emitido por una autoridad de certificación comprometida.. 

Cuando se emite un certificado, la autoridad de certificación debe enviar el certificado a un número de registros de certificados solo para anexos, que luego pueden ser verificados por el cliente y analizados por el propietario del dominio. El certificado debe existir en al menos dos registros para que el certificado sea válido.

La clave de plist para esta característica es NSRequiresCertificateTransparency. Al activar esto se aplicará la transparencia del certificado. Está disponible en iOS 10 y macOS 10.12 y posteriores..

Certificado y establecimiento de claves públicas

Cuando compra un certificado para usar HTTPS en su servidor, se dice que ese certificado es legítimo porque está firmado con un certificado de una autoridad de certificación intermedia. El certificado utilizado por la autoridad intermedia puede a su vez estar firmado por otra autoridad intermedia, y así sucesivamente, siempre que el último certificado esté firmado por una autoridad de certificación raíz de confianza.. 

Cuando se establece una conexión HTTPS, estos certificados se presentan al cliente. Esta cadena de confianza se evalúa para asegurarse de que los certificados están firmados correctamente por una autoridad de certificación en la que ya es confiable iOS. (Hay formas de omitir esta comprobación y de aceptar su propio certificado autofirmado para realizar pruebas, pero no lo haga en un entorno de producción). 

Si alguno de los certificados en la cadena de confianza no es válido, entonces se dice que el certificado completo no es válido y sus datos no se enviarán a través de la conexión no confiable. Si bien este es un buen sistema, no es infalible. Existen varios puntos débiles que pueden hacer que iOS confíe en el certificado de un atacante en lugar de un certificado firmado legítimamente. 

Por ejemplo, los proxies de interceptación pueden poseer un certificado intermedio que sea confiable. Un ingeniero inverso puede ordenar manualmente a iOS que acepte su propio certificado. Además, la política de una corporación puede haber provisto el dispositivo para aceptar su propio certificado. Todo esto lleva a la capacidad de realizar un ataque de "hombre en el medio" en su tráfico, lo que le permite leerlo. Pero la fijación de certificados evitará que se establezcan conexiones para todos estos escenarios.

La fijación de certificados llega al rescate al comparar el certificado del servidor con una copia del certificado esperado.

Para implementar la fijación, se debe implementar el siguiente delegado. por URLSession, usa lo siguiente:

función opcional urlSession (_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completedHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)

O por NSURConexión, puedes usar:

conexión de función opcional (_ conexión: NSURLConnection, didReceive challenge: URLAuthenticationChallenge)

Ambos métodos le permiten obtener una SecTrust objeto de challenge.protectionSpace.serverTrust. Debido a que estamos anulando a los delegados de autenticación, ahora debemos llamar explícitamente a la función que realiza las comprobaciones de cadena de certificados estándar que acabamos de analizar. Haga esto llamando al SecTrustEvaluate función. Entonces podemos comparar el certificado del servidor con uno esperado.

Aquí hay una implementación de ejemplo..

Importar clase Importar clase de seguridad URLSessionPinningDelegate: NSObject, URLSessionDelegate func urlSession (_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completedHandler: @escap. let serverTrust = challenge.protectionSpace.serverTrust if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) // Establezca una política para validar el dominio de la política: SecPolicy = SecPolicyCreateSSL (true, "yourdomain.com" como CFS). init (objeto: política) SecTrustSetPolicies (serverTrust, políticas) permiten a certificateCount: CFIndex = SecTrustGetCertificateCount (serverTrust) if certificateCount> 0 if let certificate = SecTrustGetCertificateCtaificateActualidad) sobre la matriz, que puede contener caducado + próximo certificado, deje certFilenames: [S tring] = ["CertificateRenewed", "Certificate"] para filenameString: String in certFilenames let filePath = Bundle.main.path (forResource: filenameString, ofType: "cer") si let file = filePath if let localCertData = NSData ( contentsOfFile: file) // Establezca el ancla cert en su propio servidor si deja localCert: SecCertificate = SecCertificateCreateWithData (nil, localCertData) let certArray = [localCert] como CFArray SecTrustSetAnchorCertificates (serverTrust, certArray) // valida un certificado firma más las firmas de los certificados en su cadena de certificados, hasta el certificado de anclaje var result = SecTrustResultType.invalid SecTrustEvaluate (serverTrust, & result); let isValid: Bool = (result == SecTrustResultType.unspecified || result == SecTrustResultType.proceed) if (isValid) // Valide el certificado del host contra el certificado anclado. Si serverCertificateData.isEqual (to: localCertData as Data) success = true completedHandler (.useCredential, URLCredential (trust: serverTrust)) rompe // encuentra un certificado exitoso, no es necesario que continúe en bucle // finaliza si serverCertificateData.isEqual (a: localCertData as Data) // end if if (isValid) // end si permite que localCertData = NSData (contentsOfFile: file) // end if let file = filePath // end for filenameString: String in certFilenames / / end if let certificate = SecTrustGetCertificateAtIndex (serverTrust, 0) // end si certificateCount> 0 // end si (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) // end si let serverTrust = challenge.protectionSpace.server.servidorTrust if éxito == falso) completedHandler (.cancelAuthenticationChallenge, nil)

Para utilizar este código, configure el delegado de la URLSession al crear tu conexión.

if let url = NSURL (cadena: "https://yourdomain.com") let session = URLSession (configuración: URLSessionConfiguration.ephemeral, delegate: URLSessionPinningDelegate (), delegateQueue: nil) let dataTask = session.dataTask (con: url como URL, completedHandler: (data, response, error) -> Void in //…) dataTask.resume ()

Asegúrese de incluir el certificado en su paquete de aplicaciones. Si su certificado es un archivo .pem, deberá convertirlo en un archivo .cer en el terminal macOS:

openssl x509 -inform PEM -en mycert.pem -outform DER -out certificate.cer

Ahora, si un atacante cambia el certificado, su aplicación lo detectará y se negará a establecer la conexión..

Tenga en cuenta que algunas bibliotecas de terceros, como AFNetworking, ya admiten el anclaje..

Desinfección y Validación

Con todas las protecciones hasta el momento, tus conexiones deben ser bastante seguras contra los ataques del hombre en el medio. Aun así, una regla importante con respecto a las comunicaciones de red es nunca confiar ciegamente en los datos que está recibiendo. De hecho, es una buena práctica de programación diseño por contrato. loslas entradas y salidas de sus métodos tienen un contrato que define las expectativas específicas de la interfaz; Si la interfaz dice que devolverá un NSNumber, entonces debería hacerlo. Si su servidor espera una cadena de 24 caracteres o menos, asegúrese de que la interfaz solo devolverá hasta 24 caracteres. 

Esto ayuda a prevenir errores inocentes, pero lo que es más importante, también puede reducir la probabilidad de varios ataques de corrupción de memoria e inyección. Analizadores comunes como el JSONSerialización clase convertirá texto en tipos de datos Swift donde se pueden realizar este tipo de pruebas.

si let dictionary = json as? [Cadena: Cualquiera] si vamos a contar = diccionario ["contar"] como? En t  //… 

Otros analizadores pueden trabajar con objetos equivalentes en Objective-C. Aquí hay una forma de validar que un objeto es del tipo esperado en Swift.

si someObject es NSArray

Antes de enviar un método a un delegado, asegúrese de que el objeto sea del tipo correcto para que responda al método; de lo contrario, la aplicación se bloqueará con un error de "selector no reconocido".

si someObject.responds (a: #selector (getter: NSNumber.intValue)

Además, puede ver si un objeto se ajusta a un protocolo antes de intentar enviarle mensajes:

si someObject.conforms (to: MyProtocol.self)

O puedes verificar que coincida con un tipo de objeto Core Foundation.

if CFGetTypeID (someObject)! = CFNullGetTypeID ()

Es una buena idea elegir cuidadosamente qué información del servidor puede ver el usuario. Por ejemplo, es una mala idea mostrar una alerta de error que pasa directamente un mensaje del servidor. Los mensajes de error pueden revelar información de depuración y relacionada con la seguridad. Una solución es hacer que el servidor envíe códigos de error específicos que hacen que el cliente muestre mensajes predefinidos.

Además, asegúrese de codificar sus URL para que solo contengan caracteres válidos. NSStringes stringByAddingPercentEscapesUsingEncoding trabajará. No codifica algunos caracteres como signos y signos más, pero el CFURLCreateStringByAddingPercentEscapes La función permite la personalización de lo que será codificado..

Desinfección de datos de usuario

Al enviar datos a un servidor, tenga mucho cuidado cuando cualquier entrada del usuario se transfiera a los comandos que ejecutará un servidor SQL o un servidor que ejecutará el código. Si bien la protección de un servidor contra dichos ataques está fuera del alcance de este artículo, como desarrolladores móviles podemos hacer nuestra parte eliminando los caracteres del idioma que usa el servidor para que la entrada no sea susceptible de comandos de ataques de inyección. Los ejemplos pueden ser quitar comillas, puntos y comas y barras diagonales cuando no son necesarios para la entrada específica del usuario.

var mutableString: String = string mutableString = mutableString.replacingOccurrences (of: "%", with: "") mutableString = mutableString.replacingOccurrences (/: "", " \ '", con:" ") mutableString = mutableString.replacingOccurrences (of:" \ t ", with:" ") mutableString = mutableString.replacingOcurrences (of:" \ n ", with:" ")

Es una buena práctica limitar la duración de la entrada del usuario. Podemos limitar el número de caracteres escritos en un campo de texto configurando UITextFielddelegado y luego implementando su shouldChangeCharactersInRange método de delegado.

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

Para un UITextView, el método delegado para implementar esto es:

opcional func textField (_ textField: UITextField, shouldChangeCharactersIn rango: NSRange, replacementString string: String) -> Bool

La entrada del usuario se puede validar aún más para que la entrada tenga el formato esperado. Por ejemplo, si un usuario ingresa una dirección de correo electrónico, podemos verificar una dirección válida:

class func validateEmail (de 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 =". + @. + \\. [A-Za-z] 2 [A-Za-z] * " let emailPredicate = NSPredicate (formato:" SELF MATCHES% @ ", filterString!) return emailPredicate.evaluate (con: emailString)

Si un usuario está cargando una imagen en el servidor, podemos verificar que sea una imagen válida. Por ejemplo, para un archivo JPEG, los dos primeros bytes y los dos últimos bytes son siempre FF D8 y 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 lista continúa, pero solo usted como desarrollador sabrá cuáles deben ser las entradas y salidas esperadas, dados los requisitos de diseño..

URLCache

Los datos que envía a través de la red pueden almacenarse en la memoria caché y en el almacenamiento del dispositivo. Puede hacer todo lo posible para proteger sus comunicaciones de red, como hemos estado haciendo, solo para descubrir que la comunicación se está almacenando. 

Varias versiones de iOS han tenido algún comportamiento inesperado cuando se trata de la configuración de la memoria caché, y algunas de las reglas para lo que se almacena en la memoria caché en iOS siguen cambiando sobre las versiones. Si bien el almacenamiento en caché ayuda al rendimiento de la red al reducir la cantidad de solicitudes, puede ser una buena idea desactivarlo para cualquier información que considere altamente sensible. Puede eliminar el caché compartido en cualquier momento (como en el inicio de la aplicación) llamando a:

URLCache.shared.removeAllCachedResponses ()

Para deshabilitar el almacenamiento en caché en un nivel global, use:

deje theURLCache = URLCache (memoryCapacity: 0, diskCapacity: 0, diskPath: nil) URLCache.shared = theURLCache

Y si estas usando URLSession, Puedes deshabilitar el caché para la sesión de esta manera:

let configuration = URLSessionConfiguration.default configuration.requestCachePolicy = .reloadIgnoringLocalCacheData configuration.urlCache = nil let session = URLSession.init (configuración: configuración)

Si está utilizando un NSURConexión con un delegado, puede deshabilitar el caché por conexión con este método de delegado:

conexión de la función (_ conexión: NSURLConnection, willCacheResponse cachedResponse: CachedURLResponse) -> CachedURLResponse? return nil

Y para crear una solicitud de URL que no compruebe el caché, use:

var request = NSMutableURLRequest (url: theUrl, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: urlTimeoutTime)

Varias versiones de iOS 8 tenían algunos errores donde algunos de estos métodos por sí solos no harían nada. Eso significa que es una buena idea implementar todos del código anterior para conexiones confidenciales, cuando necesita prevenir de manera confiable el almacenamiento en caché de las solicitudes de red.

El futuro

Es importante comprender los límites de HTTPS para proteger las comunicaciones de red. 

En la mayoría de los casos, HTTPS se detiene en el servidor. Por ejemplo, mi conexión al servidor de una corporación puede ser a través de HTTPS, pero una vez que el tráfico llega al servidor, no está encriptado. Esto significa que la corporación podrá ver la información que se envió (en la mayoría de los casos debe hacerlo), y también significa que la compañía podría presentar o pasar esa información nuevamente sin cifrar.. 

No puedo terminar este artículo sin cubrir un concepto más que es una tendencia reciente, lo que se denomina "cifrado de extremo a extremo". Un buen ejemplo es una aplicación de chat encriptada donde dos dispositivos móviles se comunican entre sí a través de un servidor. Los dos dispositivos crean claves públicas y privadas: intercambian claves públicas, mientras que sus claves privadas nunca abandonan el dispositivo. Los datos aún se envían a través de HTTPS a través del servidor, pero primero se encriptan mediante la clave pública de la otra parte de tal manera que solo los dispositivos que contienen las claves privadas pueden descifrar los mensajes de cada uno.. 

Como una analogía para ayudarlo a comprender el cifrado de extremo a extremo, imagine que quiero que alguien me envíe un mensaje de forma segura que solo yo pueda leer. Así que les proporciono una caja con un candado abierto (la clave pública) mientras mantengo la llave del candado (clave privada). El usuario escribe un mensaje, lo coloca en el cuadro, bloquea el candado y me lo envía. Solo puedo leer el mensaje porque soy el único con la llave para desbloquear el candado. 

Con el cifrado de extremo a extremo, el servidor proporciona un servicio para la comunicación, pero no puede leer el contenido de la comunicación; envían la caja bloqueada, pero no tienen la clave para abrirla. Si bien los detalles de la implementación están fuera del alcance de este artículo, es un concepto poderoso si desea permitir una comunicación segura entre los usuarios de su aplicación.. 

Si desea obtener más información sobre este enfoque, un punto de partida es el repositorio de GitHub para Open Whisper System, un proyecto de código abierto..

Conclusión

Hoy en día, casi todas las aplicaciones móviles se comunicarán a través de una red, y la seguridad es un aspecto de importancia crítica pero a menudo descuidado en el desarrollo de aplicaciones móviles.. 

En este artículo, hemos cubierto algunas de las mejores prácticas de seguridad, que incluyen HTTPS simple, el endurecimiento de las aplicaciones de las comunicaciones de red, el saneamiento de datos y el cifrado de extremo a extremo. Estas mejores prácticas deben servir como base para la seguridad al codificar su aplicación móvil.

Y mientras esté aquí, consulte algunos de nuestros otros tutoriales y cursos de aplicaciones iOS populares!