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

18

Мне нужно интегрировать приложение для iPhone с системой, и они требуют шифрования данных с помощью данного открытого ключа, есть 3 файла в 3 различных форматах .xml .der и .pem, я исследовал и нашел несколько статей о получении SecKeyRef из DER / PEM, но они всегда возвращают ноль. Ниже мой код:

<code>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;
</code>

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

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

Я думаю, что вы найдете ответ здесь:stackoverflow.com/questions/1595013/… Little Green Viper

Ваш Ответ

3   ответа
53

наконец, нашел решение. Моя проблема заключалась в том, что мне нужно было использовать как внешний закрытый, так и открытый ключ для шифрования / дешифрования данных в приложении для 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 9.3 теперь поддерживает непосредственный ввод ключей PEM. Хотя это может потребовать использования цепочки для ключей, вы можете немедленно удалить ее из цепочки для ключей.
@CommaToast - Как это сделать с 9.3?
Спасибо Спасибо спасибо!!! ты бог!
2

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

Received a certificate in the base64 encoded string sandwiched between header and tail like this (PEM format):

-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----

strip out the header and the tail, such as

// 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)  
}

decode base64 string to NSData:

let data = NSData(base64Encoded: cerStr, 
   options:NSData.Base64DecodingOptions.ignoreUnknownCharacters)!  

Convert it from NSdata format to SecCertificate:

let cert = SecCertificateCreateWithData(kCFAllocatorDefault, data)

Now, this cert can be used to compare with the certificate received from the urlSession trust:

certificateFromUrl = SecTrustGetCertificateAtIndex(...)
if cert == certificate {
}
Что, если PEM представляет собой цепочку из нескольких ключей, то есть нескольких «BEGIN / END»?
8

на самом деле можно импортировать закрытые ключи 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 with then number of bits in the key (e.g. 1024, 2048, etc.) If not known, this information can actually be read from the raw key data, which is ASN.1 data (it's a bit beyond the scope of this answer, but I will provide some helpful links below about how to parse that format). This value is maybe optional! In my tests it was actually not necessary to set this value; if absent, the API determined the value on its own and it was always set correctly later on.

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

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

Very important: При добавлении закрытого ключа в цепочку для ключей, которую вы создали, как описано выше, имейте в виду, что такой закрытый ключ не содержит хеш-код открытого ключа, однако хеш-код открытого ключа важен для 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
              // @[ ... ] wraps it into a NSArray object,
              // as kSecUseItemList expects an array of items
#endif
     },
     &outReference // Can also be NULL,
                   // otherwise reference to added keychain entry
                   // that must be released with CFRelease()
);
@mbonness. Каждый декодер base64, соответствующий стандарту, должен иметь возможность игнорировать переводы строки, как того требует стандарт, в противном случае он даже не сможет декодировать собственные выходные данные кодирования, которые очень часто содержат переводы строки. Когда используешьinitWithBase64EncodedString:options: просто используйте опциюNSDataBase64DecodingIgnoreUnknownCharacters, Большинство сторонних декодеров base64 по умолчанию игнорируют переводы строки, и это может быть отключено только с помощью опции.
Помимо удаления строк ----- BEGIN и ----- END, символы новой строки также должны быть удалены перед декодированием base64.

Похожие вопросы