Как я могу получить 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 самой последней версии. Я надеюсь, что это может помочь кому-то!
Получил сертификат в строке в кодировке base64, зажатой между заголовком и хвостом следующим образом (формат PEM):
-----BEGIN CERTIFICATE----- -----END CERTIFICATE-----
раздеть заголовок и хвост, такие как
// 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) }
декодировать строку base64 в NSData:
let data = NSData(base64Encoded: cerStr, options:NSData.Base64DecodingOptions.ignoreUnknownCharacters)!
Преобразуйте его из формата NSdata в SecCertificate:
let cert = SecCertificateCreateWithData(kCFAllocatorDefault, data)
Теперь этот сертификат можно использовать для сравнения с сертификатом, полученным из доверия urlSession:
certificateFromUrl = SecTrustGetCertificateAtIndex(...) if cert == certificate { }