Как я могу получить SecKeyRef из файла DER/PEM

Мне нужно интегрировать приложение для iPhone с системой, и они требуют шифрования данных с помощью данного открытого ключа, есть 3 файла в 3 разных форматах.xml .der и.pem, я исследовал и нашел несколько статей о получении SecKeyRef из 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;

Проблема происходит в SecCertificateCreateWithData, он всегда возвращает ноль, даже если чтение файла в порядке. Кто-нибудь сделал это, пожалуйста, помогите мне, спасибо!

РЕДАКТИРОВАТЬ: файл сертификата был подпись MD5.

3 ответа

Решение

Я много боролся с той же проблемой и, наконец, нашел решение. Моя проблема заключалась в том, что мне нужно было использовать как внешний закрытый, так и открытый ключ для шифрования / дешифрования данных в приложении для iOS, и я не хотел использовать цепочку для ключей. Оказывается, вам также нужен подписанный сертификат для библиотеки безопасности iOS, чтобы можно было прочитать данные ключа, и, конечно, файлы должны быть в правильном формате. Процедура в основном следующая:

Допустим, у вас есть закрытый ключ в формате PEM (с маркерами -----BEGIN RSA PRIVATE KEY----- и -----END RSA PRIVATE KEY-----): 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

Теперь у вас есть два файла, которые совместимы с платформой безопасности iOS: rsaCert.der (открытый ключ) и rsaPrivate.p12 (закрытый ключ). Код ниже читает в открытом ключе, предполагая, что файл добавлен в ваш пакет:

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

Для чтения в закрытом ключе используйте следующий код:

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

Начиная с iOS 10, на самом деле можно импортировать закрытые ключи PEM без преобразования их в PKCS#12 (который является очень универсальным контейнерным форматом для всего, что связано с криптографией) и, таким образом, также без использования OpenSSL в командной строке или статически. связывание приложений с ним. В macOS это даже возможно, поскольку в 10.7 используется функция, отличная от упомянутых здесь (но пока она не существует для iOS). Точно так же, как описано ниже, будет работать и на MacOS 10.12 и более поздних версиях.

Для импорта сертификата достаточно просто лишить

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

а также

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

строк, затем выполнить декодирование base64 поверх оставшихся данных, в результате получается сертификат в стандартном формате DER, который можно просто передать SecCertificateCreateWithData() чтобы получить SecCertificateRef, Это всегда работало, также до iOS 10.

Чтобы импортировать закрытый ключ, может потребоваться дополнительная работа. Если закрытый ключ обернут

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

тогда это очень легко. Снова, первая и последняя строка должны быть удалены, остальные данные должны быть декодированы в base64, и в результате получается ключ RSA в формате PKCS#1. Этот формат может содержать только ключи RSA, и он доступен для чтения, просто передавайте декодированные данные в SecKeyCreateWithData() чтобы получить SecKeyRef, attributes Для словаря нужны только следующие пары ключ / значение:

  • kSecAttrKeyType: kSecAttrKeyTypeRSA
  • kSecAttrKeyClass: kSecAttrKeyClassPrivate
  • kSecAttrKeySizeInBits: CFNumberRef с таким количеством битов в ключе (например, 1024, 2048 и т. д.). Если не известно, эту информацию можно фактически прочитать из необработанных данных ключа, которые являются данными ASN.1 (это немного выходит за рамки этого ответа, но ниже я приведу несколько полезных ссылок о том, как разобрать этот формат). Это значение может быть необязательным! В моих тестах фактически не было необходимости устанавливать это значение; если он отсутствует, API сам определяет значение, и оно всегда устанавливается правильно позже.

Если закрытый ключ обернут -----BEGIN PRIVATE KEY----- тогда данные в кодировке base64 представлены не в формате PKCS#1, а в формате PKCS#8, однако это просто более общий контейнер, который также может содержать ключи не-RSA, но для ключей RSA внутренние данные этого контейнера равны для PKCS#1, так что можно сказать, что для ключей RSA PKCS#8 - это PKCS#1 с дополнительным заголовком, и все, что вам нужно сделать, это удалить этот дополнительный заголовок. Просто удалите первые 26 байтов декодированных данных base64, и вы снова получите PKCS#1. Да, это действительно так просто.

Чтобы узнать больше о форматах PKCS#x в кодировках PEM, загляните на этот сайт. Чтобы узнать больше о формате ASN.1, вот хороший сайт для этого. И если вам нужен простой, но мощный и интерактивный онлайн-анализатор ASN.1 для работы с различными форматами, который может напрямую считывать данные PEM, а также ASN.1 в base64 и hexdump, попробуйте этот сайт.

Очень важно: при добавлении закрытого ключа в цепочку для ключей, которую вы создали, как описано выше, имейте в виду, что такой закрытый ключ не содержит хеш-код открытого ключа, однако хеш-код открытого ключа важен для API-интерфейса цепочки ключей для формирования идентичности (SecIdentityRef), поскольку использование хэша открытого ключа - это то, как API находит правильный закрытый ключ, который принадлежит импортированному сертификату (SecIdentityRef это просто SecKeyRef закрытого ключа и SecCertificateRef сертификата, формирующего объединенный объект, и это хеш открытого ключа, который связывает их вместе). Поэтому, когда вы планируете добавить закрытый ключ в связку ключей, обязательно установите хэш открытого ключа вручную, иначе вы никогда не сможете получить его идентификатор, и без этого вы не сможете использовать API связки ключей для таких задач, как подписывание или дешифрование. данные. Хэш открытого ключа должен храниться в атрибуте с именем kSecAttrApplicationLabel (глупое имя, я знаю, но на самом деле это не ярлык, и пользователь ничего не увидит, ознакомьтесь с документацией). Например:

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

После долгих часов исследований в Интернете с помощью этого поста, я наконец-то начал работать идеально. Вот заметки с рабочим кодом Swift самой последней версии. Я надеюсь, что это может помочь кому-то!

  1. Получил сертификат в строке в кодировке base64, зажатой между заголовком и хвостом следующим образом (формат PEM):

    -----BEGIN CERTIFICATE-----
    -----END CERTIFICATE-----
    
  2. раздеть заголовок и хвост, такие как

    // 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. декодировать строку base64 в NSData:

    let data = NSData(base64Encoded: cerStr, 
       options:NSData.Base64DecodingOptions.ignoreUnknownCharacters)!  
    
  4. Преобразуйте его из формата NSdata в SecCertificate:

    let cert = SecCertificateCreateWithData(kCFAllocatorDefault, data)
    
  5. Теперь этот сертификат можно использовать для сравнения с сертификатом, полученным из доверия urlSession:

    certificateFromUrl = SecTrustGetCertificateAtIndex(...)
    if cert == certificate {
    }
    
Другие вопросы по тегам