verificar spark solucion servidor para mail imposible identidad correo ios swift encryption certificate

ios - solucion - spark mail para windows



Certificados e identidades de clientes en iOS (2)

He generado una clave privada y una clave pública para mi aplicación iOS basada en Swift usando la función SecKeyGeneratePair .
Luego, generé una solicitud de firma de certificado utilizando la generación CSR de iOS y mi servidor respondió con la cadena de certificados en formato PEM.
Convertí el certificado PEM al formato DER usando el siguiente código:

var modifiedCert = certJson.replacingOccurrences(of: "-----BEGIN CERTIFICATE-----", with: "") modifiedCert = modifiedCert.replacingOccurrences(of: "-----END CERTIFICATE-----", with: "") modifiedCert = modifiedCert.replacingOccurrences(of: "/n", with: "") let dataDecoded = NSData(base64Encoded: modifiedCert, options: [])

Ahora, debería crear un certificado a partir de DER-data usando let certificate = SecCertificateCreateWithData(nil, certDer)

Mi pregunta es la siguiente: ¿Cómo puedo conectar el certificado con la clave privada que he creado al principio y obtener la identidad a la que pertenecen estas (claves y certificado)?
¿Tal vez, agregar un certificado al llavero y obtener la identidad usando SecItemCopyMatching ? He seguido el procedimiento presentado en la pregunta SecIdentityRef.

Editar:

Al agregar el certificado a keychain, obtengo la respuesta de estado 0, lo que creo que significa que el certificado se agregó a keychain.

let certificate: SecCertificate? = SecCertificateCreateWithData(nil, certDer) if certificate != nil{ let params : [String: Any] = [ kSecClass as String : kSecClassCertificate, kSecValueRef as String : certificate! ] let status = SecItemAdd(params as CFDictionary, &certRef) print(status) }

Ahora, cuando intento obtener la identidad, obtengo el estado -25300 (errSecItemNotFound). El siguiente código se utiliza para obtener la identidad. etiqueta es la etiqueta de clave privada que he usado para generar clave privada / pública.

let query: [String: Any] = [ kSecClass as String : kSecClassIdentity, kSecAttrApplicationTag as String : tag, kSecReturnRef as String: true ] var retrievedData: SecIdentity? var extractedData: AnyObject? let status = SecItemCopyMatching(query as NSDictionary, &extractedData) if (status == errSecSuccess) { retrievedData = extractedData as! SecIdentity? }

Puedo obtener la clave privada, la clave pública y el certificado del llavero usando SecItemCopyMatching y agregar el certificado al llavero, pero consultar la SecIdentity no funciona. ¿Es posible que mi certificado no coincida con mis claves? ¿Cómo se comprueba eso?

Imprimí la clave pública de iOS en formato base64. Se imprimió lo siguiente:

MIIBCgKCAQEAo/MRST9oZpO3nTl243o+ocJfFCyKLtPgO/QiO9apb2sWq4kqexHy 58jIehBcz4uGJLyKYi6JHx/NgxdSRKE3PcjU2sopdMN35LeO6jZ34auH37gX41Sl 4HWkpMOB9v/OZvMoKrQJ9b6/qmBVZXYsrSJONbr+74/mI/m1VNtLOM2FIzewVYcL HHsM38XOg/kjSUsHEUKET/FfJkozgp76r0r3E0khcbxwU70qc77YPgeJHglHcZKF ZHFbvNz4E9qUy1mWJvoCmAEItWnyvuw+N9svD1Rri3t5qlaBwaIN/AtayHwJWoWA /HF+Jg87eVvEErqeT1wARzJL2xv5V1O4ZwIDAQAB

Luego de la solicitud de firma de certificado extraje la clave pública usando openssl (openssl req -in ios.csr -pubkey -noout). La siguiente respuesta fue impresa:

-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo/MRST9oZpO3nTl243o+ ocJfFCyKLtPgO/QiO9apb2sWq4kqexHy58jIehBcz4uGJLyKYi6JHx/NgxdSRKE3 PcjU2sopdMN35LeO6jZ34auH37gX41Sl4HWkpMOB9v/OZvMoKrQJ9b6/qmBVZXYs rSJONbr+74/mI/m1VNtLOM2FIzewVYcLHHsM38XOg/kjSUsHEUKET/FfJkozgp76 r0r3E0khcbxwU70qc77YPgeJHglHcZKFZHFbvNz4E9qUy1mWJvoCmAEItWnyvuw+ N9svD1Rri3t5qlaBwaIN/AtayHwJWoWA/HF+Jg87eVvEErqeT1wARzJL2xv5V1O4 ZwIDAQAB -----END PUBLIC KEY----

Parece que hay una pequeña diferencia en el comienzo de la clave generada a partir de la CSR. (MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A). Sobre la base del cifrado RSA de la pregunta, parece que MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A es un identificador con formato base64 para el cifrado RSA "1.2.840.113549.1.1.1". Así que supongo que la clave pública podría estar bien?


La forma habitual de generar certificados SSL es que la clave privada se utiliza para generar la CSR, información de solicitud de firma de certificado. De hecho, está ocultando información de la compañía, correo electrónico, etc. con esa firma clave. Con esa CSR, entonces, usted firma su certificado, por lo que se asociará con su clave privada y la información almacenada en la CSR, sin importar la clave pública. Actualmente no puedo ver en el proyecto de Generación de CSR de IOS, donde puede pasar su clave generada: me parece que la CSR generada con el proyecto de Generación de CSR de IOS está utilizando su propia clave generada, o ninguna clave privada. Eso tendrá lógica con el hecho de que no puede extraer la clave privada de CER o DER, porque no está allí.


No utilizamos el mismo método de CSR, pero tenemos una cosa equivalente en la que hacemos lo siguiente:

  1. Generar par de claves
  2. Enviar la clave pública al servidor remoto
  3. El servidor remoto genera un certificado de cliente firmado utilizando la clave pública
  4. Enviar el certificado de cliente de nuevo al dispositivo iOS
  5. Añadir el certificado de cliente al llavero.
  6. Más adelante, utilice el certificado de cliente en una NSURLSession o similar.

Como parece haber descubierto, iOS necesita esta cosa adicional llamada "identidad" para vincular el certificado del cliente.

También descubrimos que iOS tiene una cosa rara en la que debe BORRAR la clave pública del llavero antes de agregar el certificado de cliente y su identidad, de lo contrario, la identidad no parece ubicar correctamente el certificado del cliente. Elegimos volver a agregar la clave pública, pero como una "contraseña genérica" ​​(es decir, datos de usuario arbitrarios): solo hacemos esto porque iOS no tiene una API sensata para extraer una clave pública de un certificado sobre la marcha, y Necesitamos la clave pública para otras cosas extrañas que estamos haciendo.

Si solo está realizando la autenticación del certificado de cliente TLS, una vez que tenga el certificado, no necesitará una copia explícita de la clave pública, por lo que puede simplificar el proceso simplemente eliminándolo y omitiendo "agregar-volver-en-como -generic-password "bit

Por favor, disculpe la pila gigante de código, las cosas criptográficas siempre parecen requerir mucho trabajo.

Aquí hay bits de código para realizar las tareas anteriores:

Generar el par de llaves y eliminar / volver a guardar la clave pública

/// Returns the public key binary data in ASN1 format (DER encoded without the key usage header) static func generateKeyPairWithPublicKeyAsGenericPassword(privateKeyTag: String, publicKeyAccount: String, publicKeyService: String) throws -> Data { let tempPublicKeyTag = "TMPPUBLICKEY:/(privateKeyTag)" // we delete this public key and replace it with a generic password, but it needs a tag during the transition let privateKeyAttr: [NSString: Any] = [ kSecAttrApplicationTag: privateKeyTag.data(using: .utf8)!, kSecAttrAccessible: kSecAttrAccessibleAlwaysThisDeviceOnly, kSecAttrIsPermanent: true ] let publicKeyAttr: [NSString: Any] = [ kSecAttrApplicationTag: tempPublicKeyTag.data(using: .utf8)!, kSecAttrAccessible: kSecAttrAccessibleAlwaysThisDeviceOnly, kSecAttrIsPermanent: true ] let keyPairAttr: [NSString: Any] = [ kSecAttrKeyType: kSecAttrKeyTypeRSA, kSecAttrKeySizeInBits: 2048, kSecPrivateKeyAttrs: privateKeyAttr, kSecPublicKeyAttrs: publicKeyAttr ] var publicKey: SecKey?, privateKey: SecKey? let genKeyPairStatus = SecKeyGeneratePair(keyPairAttr as CFDictionary, &publicKey, &privateKey) guard genKeyPairStatus == errSecSuccess else { log.error("Generation of key pair failed. Error = /(genKeyPairStatus)") throw KeychainError.generateKeyPairFailed(genKeyPairStatus) } // Would need CFRelease(publicKey and privateKey) here but swift does it for us // we store the public key in the keychain as a "generic password" so that it doesn''t interfere with retrieving certificates // The keychain will normally only store the private key and the certificate // As we want to keep a reference to the public key itself without having to ASN.1 parse it out of the certificate // we can stick it in the keychain as a "generic password" for convenience let findPubKeyArgs: [NSString: Any] = [ kSecClass: kSecClassKey, kSecValueRef: publicKey!, kSecAttrKeyType: kSecAttrKeyTypeRSA, kSecReturnData: true ] var resultRef:AnyObject? let status = SecItemCopyMatching(findPubKeyArgs as CFDictionary, &resultRef) guard status == errSecSuccess, let publicKeyData = resultRef as? Data else { log.error("Public Key not found: /(status))") throw KeychainError.publicKeyNotFound(status) } // now we have the public key data, add it in as a generic password let attrs: [NSString: Any] = [ kSecClass: kSecClassGenericPassword, kSecAttrAccessible: kSecAttrAccessibleAlwaysThisDeviceOnly, kSecAttrService: publicKeyService, kSecAttrAccount: publicKeyAccount, kSecValueData: publicKeyData ] var result: AnyObject? let addStatus = SecItemAdd(attrs as CFDictionary, &result) if addStatus != errSecSuccess { log.error("Adding public key to keychain failed. Error = /(addStatus)") throw KeychainError.cannotAddPublicKeyToKeychain(addStatus) } // delete the "public key" representation of the public key from the keychain or it interferes with looking up the certificate let pkattrs: [NSString: Any] = [ kSecClass: kSecClassKey, kSecValueRef: publicKey! ] let deleteStatus = SecItemDelete(pkattrs as CFDictionary) if deleteStatus != errSecSuccess { log.error("Deletion of public key from keychain failed. Error = /(deleteStatus)") throw KeychainError.cannotDeletePublicKeyFromKeychain(addStatus) } // no need to CFRelease, swift does this. return publicKeyData }

TENGA EN CUENTA que publicKeyData no está estrictamente en formato DER, está en formato "DER con los primeros 24 bytes eliminados". No estoy seguro de cómo se llama esto oficialmente, pero microsoft y apple parecen usarlo como formato en bruto para las claves públicas. Si su servidor es uno de Microsoft que ejecuta .NET (escritorio o núcleo), entonces probablemente estará satisfecho con los bytes de clave pública tal como están. Si es Java y espera DER, es posible que deba generar el encabezado DER: esta es una secuencia fija de 24 bytes en la que probablemente pueda concatenar.

Agregando el certificado del cliente al llavero, generando una Identidad.

static func addIdentity(clientCertificate: Data, label: String) throws { log.info("Adding client certificate to keychain with label /(label)") guard let certificateRef = SecCertificateCreateWithData(kCFAllocatorDefault, clientCertificate as CFData) else { log.error("Could not create certificate, data was not valid DER encoded X509 cert") throw KeychainError.invalidX509Data } // Add the client certificate to the keychain to create the identity let addArgs: [NSString: Any] = [ kSecClass: kSecClassCertificate, kSecAttrAccessible: kSecAttrAccessibleAlwaysThisDeviceOnly, kSecAttrLabel: label, kSecValueRef: certificateRef, kSecReturnAttributes: true ] var resultRef: AnyObject? let addStatus = SecItemAdd(addArgs as CFDictionary, &resultRef) guard addStatus == errSecSuccess, let certAttrs = resultRef as? [NSString: Any] else { log.error("Failed to add certificate to keychain, error: /(addStatus)") throw KeychainError.cannotAddCertificateToKeychain(addStatus) } // Retrieve the client certificate issuer and serial number which will be used to retrieve the identity let issuer = certAttrs[kSecAttrIssuer] as! Data let serialNumber = certAttrs[kSecAttrSerialNumber] as! Data // Retrieve a persistent reference to the identity consisting of the client certificate and the pre-existing private key let copyArgs: [NSString: Any] = [ kSecClass: kSecClassIdentity, kSecAttrIssuer: issuer, kSecAttrSerialNumber: serialNumber, kSecReturnPersistentRef: true] // we need returnPersistentRef here or the keychain makes a temporary identity that doesn''t stick around, even though we don''t use the persistentRef let copyStatus = SecItemCopyMatching(copyArgs as CFDictionary, &resultRef); guard copyStatus == errSecSuccess, let _ = resultRef as? Data else { log.error("Identity not found, error: /(copyStatus) - returned attributes were /(certAttrs)") throw KeychainError.cannotCreateIdentityPersistentRef(addStatus) } // no CFRelease(identityRef) due to swift }

En nuestro código, elegimos devolver una etiqueta y luego buscar la identidad según sea necesario mediante la etiqueta y el siguiente código. También puede optar por devolver la referencia de identidad de la función anterior en lugar de la etiqueta. Aquí está nuestra función getIdentity de todos modos

Obteniendo la identidad más adelante

// Remember any OBJECTIVE-C code that calls this method needs to call CFRetain static func getIdentity(label: String) -> SecIdentity? { let copyArgs: [NSString: Any] = [ kSecClass: kSecClassIdentity, kSecAttrLabel: label, kSecReturnRef: true ] var resultRef: AnyObject? let copyStatus = SecItemCopyMatching(copyArgs as CFDictionary, &resultRef) guard copyStatus == errSecSuccess else { log.error("Identity not found, error: /(copyStatus)") return nil } // back when this function was all ObjC we would __bridge_transfer into ARC, but swift can''t do that // It wants to manage CF types on it''s own which is fine, except they release when we return them out // back into ObjC code. return (resultRef as! SecIdentity) } // Remember any OBJECTIVE-C code that calls this method needs to call CFRetain static func getCertificate(label: String) -> SecCertificate? { let copyArgs: [NSString: Any] = [ kSecClass: kSecClassCertificate, kSecAttrLabel: label, kSecReturnRef: true] var resultRef: AnyObject? let copyStatus = SecItemCopyMatching(copyArgs as CFDictionary, &resultRef) guard copyStatus == errSecSuccess else { log.error("Identity not found, error: /(copyStatus)") return nil } // back when this function was all ObjC we would __bridge_transfer into ARC, but swift can''t do that // It wants to manage CF types on it''s own which is fine, except they release when we return them out // back into ObjC code. return (resultRef as! SecCertificate) }

Y finalmente

Usando la identidad para autenticar contra un servidor

Este bit está en objc porque así es como funciona nuestra aplicación, pero entiendes la idea:

SecIdentityRef _clientIdentity = [XYZ getClientIdentityWithLabel: certLabel]; if(_clientIdentity) { CFRetain(_clientIdentity); } SecCertificateRef _clientCertificate = [XYZ getClientCertificateWithLabel:certLabel]; if(_clientCertificate) { CFRetain(_clientCertificate); } ... - (void)URLSession:(nullable NSURLSession *)session task:(nullable NSURLSessionTask *)task didReceiveChallenge:(nullable NSURLAuthenticationChallenge *)challenge completionHandler:(nullable void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate) { // supply the appropriate client certificate id bridgedCert = (__bridge id)_clientCertificate; NSArray* certificates = bridgedCert ? @[bridgedCert] : @[]; NSURLCredential* credential = [NSURLCredential credentialWithIdentity:identity certificates:certificates persistence:NSURLCredentialPersistenceForSession]; completionHandler(NSURLSessionAuthChallengeUseCredential, credential); } }

Este código tomó mucho tiempo para hacerlo bien. El material del certificado de iOS está extremadamente mal documentado, espero que esto ayude.