2012-05-14 22 views
17

Necesito integrar mi aplicación de iPhone con un sistema, y ​​ellos requieren encriptar datos por una clave pública determinada, hay 3 archivos en 3 formatos diferentes .xml .der y. Pem, investigué y encontré algunos artículos sobre cómo obtener SecKeyRef de DER/PEM, pero siempre son nulos. A continuación se muestra el código:¿Cómo puedo obtener SecKeyRef desde el archivo DER/PEM

NSString *pkFilePath = [[NSBundle mainBundle] pathForResource:@"PKFile" ofType:@"der"]; 
NSData *pkData = [NSData dataWithContentsOfFile:pkFilePath]; 

SecCertificateRef cert; 
cert = SecCertificateCreateWithData(NULL, (CFDataRef) pkData); 
assert(cert != NULL); 

OSStatus err; 

    if (cert != NULL) { 
     err = SecItemAdd(
         (CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys: 
              (id) kSecClassCertificate, kSecClass, 
              (id) cert,     kSecValueRef, 
              nil 
              ], 
         NULL 
         ); 
     if ((err == errSecSuccess) || (err == errSecDuplicateItem)) { 
      CFArrayRef certs = CFArrayCreate(kCFAllocatorDefault, (const void **) &cert, 1, NULL); 
      SecPolicyRef policy = SecPolicyCreateBasicX509(); 
      SecTrustRef trust; 
      SecTrustCreateWithCertificates(certs, policy, &trust); 
      SecTrustResultType trustResult; 
      SecTrustEvaluate(trust, &trustResult); 
      if (certs) { 
       CFRelease(certs); 
      } 
      if (trust) { 
       CFRelease(trust); 
      } 
      return SecTrustCopyPublicKey(trust); 
     } 
    } 
return NULL; 

problema sucede en SecCertificateCreateWithData, que siempre vuelven a cero, incluso a través de ficheros de lectura está bien. ¡Alguien ha hecho esto por favor ayúdenme, gracias!

EDITAR: El archivo cert fue la firma MD5.

+2

creo que encontrará su respuesta aquí: http://stackoverflow.com/questions/1595013/iphone-how-to-create-a-seckeyref-from-a-public-key-file-pem –

Respuesta

50

Luché mucho con el mismo problema y finalmente encontré una solución. Mi problema era que necesitaba usar una clave privada y pública externa para encriptar/descifrar datos en una aplicación de iOS y no quería usar el llavero. Resulta que también necesita un certificado firmado para que la biblioteca de seguridad de iOS pueda leer los datos clave y, por supuesto, los archivos deben estar en el formato correcto. El procedimiento es básicamente el siguiente:

Supongamos que tiene una clave privada en formato PEM (con ----- BEGIN RSA PRIVATE KEY ----- y ----- END RSA PRIVATE KEY-- --- marcadores): rsaPrivate.pem

//Create a certificate signing request with the private key 
openssl req -new -key rsaPrivate.pem -out rsaCertReq.csr 

//Create a self-signed certificate with the private key and signing request 
openssl x509 -req -days 3650 -in rsaCertReq.csr -signkey rsaPrivate.pem -out rsaCert.crt 

//Convert the certificate to DER format: the certificate contains the public key 
openssl x509 -outform der -in rsaCert.crt -out rsaCert.der 

//Export the private key and certificate to p12 file 
openssl pkcs12 -export -out rsaPrivate.p12 -inkey rsaPrivate.pem -in rsaCert.crt 

Ahora tiene dos archivos que son compatibles con el marco de seguridad de iOS: rsaCert.der (clave pública) y rsaPrivate.p12 (clave privada). El código siguiente se lee en la clave pública suponiendo que el archivo se añade a su paquete:

- (SecKeyRef)getPublicKeyRef { 

    NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"rsaCert" ofType:@"der"]; 
    NSData *certData = [NSData dataWithContentsOfFile:resourcePath]; 
    SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef)certData); 
    SecKeyRef key = NULL; 
    SecTrustRef trust = NULL; 
    SecPolicyRef policy = NULL; 

    if (cert != NULL) { 
     policy = SecPolicyCreateBasicX509(); 
     if (policy) { 
      if (SecTrustCreateWithCertificates((CFTypeRef)cert, policy, &trust) == noErr) { 
       SecTrustResultType result; 
       OSStatus res = SecTrustEvaluate(trust, &result); 

       //Check the result of the trust evaluation rather than the result of the API invocation. 
       if (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) { 
        key = SecTrustCopyPublicKey(trust); 
       } 
      } 
     } 
    } 
    if (policy) CFRelease(policy); 
    if (trust) CFRelease(trust); 
    if (cert) CFRelease(cert); 
    return key; 
} 

a leer en el uso de clave privada el siguiente código:

SecKeyRef getPrivateKeyRef() { 
    NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"rsaPrivate" ofType:@"p12"]; 
    NSData *p12Data = [NSData dataWithContentsOfFile:resourcePath]; 

    NSMutableDictionary * options = [[NSMutableDictionary alloc] init]; 

    SecKeyRef privateKeyRef = NULL; 

    //change to the actual password you used here 
    [options setObject:@"password_for_the_key" forKey:(id)kSecImportExportPassphrase]; 

    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); 

    OSStatus securityError = SecPKCS12Import((CFDataRef) p12Data, 
              (CFDictionaryRef)options, &items); 

    if (securityError == noErr && CFArrayGetCount(items) > 0) { 
     CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0); 
     SecIdentityRef identityApp = 
     (SecIdentityRef)CFDictionaryGetValue(identityDict, 
              kSecImportItemIdentity); 

     securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef); 
     if (securityError != noErr) { 
      privateKeyRef = NULL; 
     } 
    } 
    [options release]; 
    CFRelease(items); 
    return privateKeyRef; 
} 
+2

¡Gracias! ¡Salva mi día! – mientus

+0

Gracias, gracias, gracias !!! ¡Dios mío! –

+0

La primera solución que encontré que logra hacerlo sin usar el llavero. Excelente. – fishinear

3

A partir de IOS 10, se es posible importar claves privadas PEM sin convertirlas a PKCS # 12 (que es un formato de contenedor muy universal para todo lo relacionado con la criptografía) y por lo tanto también sin usar OpenSSL en la línea de comandos o vincular estáticamente aplicaciones con él . En macOS, incluso es posible desde 10.7 utilizando una función diferente a las mencionadas aquí (pero hasta ahora no existe para iOS). Exactamente del modo descrito a continuación también funcionará en macOS 10.12 y posteriores.

Para importar un certificado, es suficiente con sólo quitar los

-----BEGIN CERTIFICATE----- 

y

-----END CERTIFICATE----- 

líneas, a continuación, ejecutar la decodificación base 64 a través de los datos que quedan, el resultado es un certificado en formato DER norma , que solo se puede alimentar a SecCertificateCreateWithData() para obtener un SecCertificateRef. Esto siempre ha funcionado, también antes de iOS 10.

Para importar una clave privada, puede que se requiera un poco de trabajo adicional. Si la clave privada se envuelve con

-----BEGIN RSA PRIVATE KEY----- 

entonces es muy fácil. De nuevo, la primera y la última línea deben ser eliminadas, los datos restantes deben ser decodificados en base64 y el resultado es una clave RSA en formato PKCS # 1.Este formato solo puede contener claves RSA y es directamente legible, solo alimente los datos decodificados en SecKeyCreateWithData() para obtener un SecKeyRef. El diccionario attributes sólo necesitan los siguientes pares clave/valor:

  • kSecAttrKeyType: kSecAttrKeyTypeRSA
  • kSecAttrKeyClass: kSecAttrKeyClassPrivate
  • kSecAttrKeySizeInBits: CFNumberRef con el entonces número de bits en la clave (por ejemplo, 1024, 2048, etc.) Si no se conoce, esta información se puede leer a partir de los datos clave sin procesar, que son datos ASN.1 (está un poco fuera del alcance de esta respuesta, pero a continuación proporcionaré algunos enlaces útiles sobre cómo analizar ese formato). ¡Este valor es tal vez opcional! En mis pruebas, en realidad no fue necesario establecer este valor; si está ausente, la API determinó el valor por sí misma y siempre se configuró correctamente más adelante.

En caso de que la clave privada está envuelto por -----BEGIN PRIVATE KEY-----, los datos a continuación, los base64 no está en PKCS # 1 formato pero en PKCS # 8 formato, sin embargo, este es un sólo un contenedor más genérico que también llevar a cabo llaves no RSA pero para claves RSA los datos internos de ese recipiente es igual a PKCS # 1, por lo que se podría decir de claves RSA PKCS # 8 es PKCS # 1 con una cabecera adicional y todo lo que necesitas hacer es quitar ese encabezado extra. Simplemente elimine los primeros 26 bytes de los datos decodificados de base64 y tiene PKCS # 1 nuevamente. Sí, es así de simple.

Para obtener más información sobre los formatos PKCS # x en las codificaciones PEM, have a look at this site. Para obtener más información sobre el formato ASN.1, here's a good site for that. Y si necesita un analizador de ASN.1 en línea sencillo pero potente e interactivo para jugar con diferentes formatos, uno que pueda leer directamente datos de PEM, así como ASN.1 en base64 y hexdump, try this site.

Muy importante: Al agregar una clave privada al llavero que usted creó como se indica arriba, tenga en cuenta que dicha clave privada no contiene un hash de clave pública, pero un hash de clave pública es importante para el llavero API para formar una identidad (SecIdentityRef), ya que usar el hash de clave pública es la forma en que la API encuentra la clave privada correcta que pertenece a un certificado importado (un SecIdentityRef es solo SecKeyRef de una clave privada y SecCertificateRef de un certificado que forma una combinación objeto y es el hash de clave pública, que los une). Por lo tanto, cuando planee agregar la clave privada al llavero, asegúrese de configurar un hash de clave pública manualmente; de ​​lo contrario, nunca podrá obtener una identidad y sin eso no podrá usar la API de llavero para tareas como firmar o descifrar datos. El hash de clave pública debe almacenarse en un atributo llamado kSecAttrApplicationLabel (nombre estúpido, lo sé, pero en realidad no es una etiqueta y nada que el usuario pueda ver nunca, consulte la documentación). Por ejemplo:

OSStatus error = SecItemAdd(
    (__bridge CFDictionaryRef)@{ 
     (__bridge NSString *)kSecClass: 
      (__bridge NSString *)kSecClassKey, 
     (__bridge NSString *)kSecAttrApplicationLabel: 
      hashOfPublicKey, // hashOfPublicKey is NSData * 
#if TARGET_OS_IPHONE 
     (__bridge NSString *)kSecValueRef: 
      (__bridge id)privateKeyToAdd, // privateKeyToAdd is SecKeyRef 
#else 
     (__bridge NSString *)kSecUseItemList: 
       @[(__bridge id)privateKeyToAdd], // privateKeyToAdd is SecKeyRef 
#endif 
    }, 
    &outReference // Can also be NULL, 
        // otherwise reference to added keychain entry 
        // that must be released with CFRelease() 
); 
+0

Además de pelar ----- BEGIN y ----- las líneas END caracteres de nueva línea también deben eliminarse antes de la decodificación base64. – mbonness

+0

@mbonness Cada decodificador base64 de conformidad estándar debe ser capaz de ignorar las nuevas líneas como lo requiere el estándar, de lo contrario no podría decodificar su propia salida de codificación que muy a menudo contiene nuevas líneas. Al usar 'initWithBase64EncodedString: options:' simplemente use la opción 'NSDataBase64DecodingIgnoreUnknownCharacters'. La mayoría de los decodificadores de base64 de terceros ignoran las nuevas líneas de forma predeterminada y solo se pueden desactivar con una opción. – Mecki

2

Después de horas de esfuerzo investigando en línea con la ayuda de esta publicación, finalmente lo hago funcionar perfectamente. Aquí están las notas con el código Swift de trabajo de la versión más reciente. ¡Espero que pueda ayudar a alguien!

  1. recibido un certificado en los base64 cadena codificada intercalada entre cabecera y cola como este (formato PEM):

    -----BEGIN CERTIFICATE----- 
    -----END CERTIFICATE----- 
    
  2. tira hacia fuera de la cabecera y la cola, tal como

    // remove the header string 
    let offset = ("-----BEGIN CERTIFICATE-----").characters.count 
    let index = certStr.index(cerStr.startIndex, offsetBy: offset+1) 
    cerStr = cerStr.substring(from: index) 
    
    // remove the tail string 
    let tailWord = "-----END CERTIFICATE-----" 
    if let lowerBound = cerStr.range(of: tailWord)?.lowerBound { 
    cerStr = cerStr.substring(to: lowerBound) 
    } 
    
  3. decode base64 cadena a NSData:

    let data = NSData(base64Encoded: cerStr, 
        options:NSData.Base64DecodingOptions.ignoreUnknownCharacters)! 
    
  4. convertirlo de formato NSData a SecCertificate:

    let cert = SecCertificateCreateWithData(kCFAllocatorDefault, data) 
    
  5. Ahora, este certificado se puede utilizar para comparar con el certificado recibido la confianza urlSession:

    certificateFromUrl = SecTrustGetCertificateAtIndex(...) 
    if cert == certificate { 
    } 
    
Cuestiones relacionadas