2012-05-14 10 views
17

Ho bisogno di integrare la mia app iPhone con un sistema e richiedono di crittografare i dati con una determinata chiave pubblica, ci sono 3 file in 3 diversi formati .xml .der e. pem, ho ricercato e trovato alcuni articoli su come ottenere SecKeyRef da DER/PEM, ma sono sempre di ritorno nil. Qui di seguito è il mio codice:Come posso ottenere SecKeyRef dal file 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 si verifica a SecCertificateCreateWithData, si ritorna sempre pari a zero anche attraverso file letto è ok. Chiunque ha fatto questo per favore aiutami, grazie!

MODIFICA: il file cert era una firma MD5.

+2

penso che troverai la tua risposta qui: http://stackoverflow.com/questions/1595013/iphone-how-to-create-a-seckeyref-from-a-public-key-file-pem –

risposta

50

Ho faticato molto con lo stesso problema e alla fine ho trovato una soluzione. Il mio problema era che dovevo usare sia una chiave privata e pubblica esterna per crittografare/decodificare i dati in un'app per iOS e non volevo usare il portachiavi. È inoltre necessario disporre di un certificato firmato per la libreria di sicurezza iOS per poter leggere i dati chiave e, naturalmente, i file devono essere nel formato corretto. La procedura è fondamentalmente la seguente:

Supponiamo che tu abbia una chiave privata in formato PEM (con il ----- INIZIA A RSA PRIVATE KEY ----- e----- END RSA PRIVATE KEY-- --- marcatori): 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 

Ora avete due file che sono compatibili con il quadro di sicurezza di iOS: rsaCert.der (chiave pubblica) e rsaPrivate.p12 (chiave privata). Il codice sotto si legge nella chiave pubblica assumendo il file viene aggiunto al tuo bundle:

- (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; 
} 

Per leggere nell'uso chiave privata il seguente codice:

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

Grazie! Salva la mia giornata! – mientus

+0

Grazie, grazie, grazie !!! Dio mio! –

+0

La prima soluzione che ho trovato che riesce a farlo senza usare il portachiavi. Eccellente. – fishinear

3

partire con iOS 10, si è in realtà possibile importare chiavi private PEM senza convertirle in PKCS # 12 (che è un formato contenitore universale per tutto ciò che riguarda la crittografia) e quindi anche senza usare OpenSSL sulla linea di comando o collegando staticamente le app con esso . Su macOS è persino possibile dal 10.7 utilizzando una funzione diversa da quelle menzionate qui (ma finora non esiste per iOS). Esattamente come descritto di seguito funzionerà anche su macOS 10.12 e successivi.

Per importare un certificato, è abbastanza per mettere a nudo solo le

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

e

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

linee, quindi eseguire la decodifica base64 sopra i dati rimasti, il risultato è un certificato in formato Der Standard , che può essere semplicemente inviato a SecCertificateCreateWithData() per ottenere un SecCertificateRef. Funziona sempre, anche prima di iOS 10.

Per importare una chiave privata, potrebbe essere necessario un po 'di lavoro extra. Se la chiave privata è incapsulata con

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

quindi è molto facile. Anche in questo caso, la prima e l'ultima riga devono essere rimosse, i dati rimanenti devono essere decodificati in base64 e il risultato è una chiave RSA nel formato PKCS # 1.Questo formato può contenere solo chiavi RSA ed è direttamente leggibile, basta inserire i dati decodificati in SecKeyCreateWithData() per ottenere un SecKeyRef. Il dizionario attributes solo bisogno le seguenti coppie chiave/valore:

  • kSecAttrKeyType: kSecAttrKeyTypeRSA
  • kSecAttrKeyClass: kSecAttrKeyClassPrivate
  • kSecAttrKeySizeInBits: CFNumberRef con allora il numero di bit nella chiave (ad esempio 1024, 2048, etc.) Se non si sa, questa informazione può essere letta dai dati della chiave grezza, che sono dati ASN.1 (è un po 'oltre lo scopo di questa risposta, ma fornirò alcuni link utili di seguito su come analizzare tale formato). Questo valore è forse facoltativo! Nei miei test non era effettivamente necessario impostare questo valore; se assente, l'API ha determinato il valore da solo ed è stato sempre impostato correttamente in seguito.

Nel caso la chiave privata è avvolto da -----BEGIN PRIVATE KEY-----, dati quindi le base64 codificato non è in PKCS # 1 formato ma in PKCS # 8 formato, tuttavia, questo è solo un container più generico che può anche contenere chiavi non RSA ma per chiavi RSA i dati interni di tale contenitore è uguale PKCS # 1, quindi si può dire per le chiavi RSA PKCS # 8 è PKCS # 1 con un'intestazione supplementare e tutto ciò che devi fare è rimuovere l'intestazione aggiuntiva. Basta rimuovere i primi 26 byte dei dati decodificati di base64 e hai di nuovo PKCS # 1. Sì, è davvero così semplice.

Per ulteriori informazioni sui formati PKCS # x nelle codifiche PEM, have a look at this site. Per ulteriori informazioni sul formato ASN.1, here's a good site for that. E se hai bisogno di un parser ASN.1 semplice ma potente e interattivo per giocare con diversi formati, uno che può leggere direttamente i dati PEM, così come ASN.1 in base64 e hexdump, try this site.

Molto importante: Quando si aggiunge una chiave privata al portachiavi, che è stato creato come sopra, si prega di essere consapevole del fatto che una chiave privata come non contiene un hash chiave pubblica, ma un hash chiave pubblica è importante perché portachiavi API per creare un'identità (SecIdentityRef), poiché l'utilizzo dell'hash della chiave pubblica è il modo in cui l'API trova la chiave privata corretta appartenente a un certificato importato (uno SecIdentityRef è solo un SecKeyRef di una chiave privata e un SecCertificateRef di un cert che forma una combinazione oggetto ed è l'hash della chiave pubblica, che li lega insieme). Pertanto, quando pianifichi di aggiungere la chiave privata al portachiavi, assicurati di impostare manualmente un hash della chiave pubblica, altrimenti non sarai mai in grado di ottenere un'identità e senza di essa non puoi utilizzare l'API portachiavi per attività come la firma o la decrittografia dati. L'hash della chiave pubblica deve essere memorizzato in un attributo denominato kSecAttrApplicationLabel (nome stupido, lo so, ma in realtà non è un'etichetta e nulla che l'utente possa mai vedere, controlla la documentazione). Ad es .:

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

Oltre allo stripping ----- BEGIN e ----- le linee END i caratteri di nuova riga dovrebbero essere eliminati prima della decodifica di base64. – mbonness

+0

@mbonness Ogni decodificatore base64 conforme allo standard deve essere in grado di ignorare le nuove righe come richiesto dallo standard, altrimenti non potrebbe nemmeno decodificare il proprio output di codifica che molto spesso contiene newline. Quando si utilizza 'initWithBase64EncodedString: options:' usa semplicemente l'opzione 'NSDataBase64DecodingIgnoreUnknownCharacters'. La maggior parte dei decodificatori base64 di terze parti ignorano le nuove righe per impostazione predefinita e possono essere disattivate solo con un'opzione. – Mecki

2

Dopo ore di sforzi di ricerca online con l'aiuto di questo post, finalmente riesco a farlo funzionare perfettamente. Ecco le note con il codice Swift funzionante della versione più recente. Spero possa aiutare qualcuno!

  1. ricevuto un certificato in base64 stringa codificata inserita tra intestazione e coda simili (formato PEM):

    -----BEGIN CERTIFICATE----- 
    -----END CERTIFICATE----- 
    
  2. striscia fuori l'intestazione e la coda, come

    // 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. decodificare stringa Base64 per NSData:

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

    let cert = SecCertificateCreateWithData(kCFAllocatorDefault, data) 
    
  5. Ora, questo cert può essere utilizzato per confrontare con il certificato ricevuto dal urlSession fiducia:

    certificateFromUrl = SecTrustGetCertificateAtIndex(...) 
    if cert == certificate { 
    } 
    
Problemi correlati