Как получить SecKeyRef из файла DER/PEM
Мне нужно интегрировать мое приложение iPhone с системой, и они требуют шифрования данных с помощью данного открытого ключа, есть 3 файла в 3 разных форматах .XML. дер и .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, он всегда возвращает ноль даже через файл чтения в порядке. Кто сделал это, пожалуйста, помогите мне, спасибо!
EDIT: файл сертификата был подписью MD5.
3 ответов
Я много боролся с той же проблемой и, наконец, нашел решение. Моя проблема заключалась в том, что мне нужно было использовать как внешний закрытый, так и открытый ключ для шифрования/дешифрования данных в приложении iOS и не хотел использовать брелок. Получается, вам также понадобится сертификат библиотека безопасности iOS, чтобы иметь возможность читать ключевые сведения и, конечно, файлы должны быть в правильном формате. Процедура в основном выглядит следующим образом:
скажем, у вас есть закрытый ключ в PEM формат (С - - - - - - начать закрытый ключ RSA - - - - - и - - - - - конец закрытый ключ RSA - - - - - маркеры): rsaPrivate.Пем!--4-->
//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 Security framework: rsaCert.der (открытый ключ) и rsaPrivate.Р12 (закрытый ключ). Код ниже читается в открытом ключе, предполагая, что файл добавлен в ваш пакет:
- (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
. The 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 is PKCS#1 с дополнительным заголовком и все тебе нужно снять лишний заголовок. Просто удалите первые 26 байтов декодированных данных base64, и у вас есть PKCS#1 снова. Да, все очень просто.
чтобы узнать больше о форматах PKCS#x в кодировках PEM,взгляните на этот сайт. Чтобы узнать больше об ASN.Формат 1, вот хороший сайт для этого. И если вам нужен простой, но мощный и интерактивный онлайн-АСН.1 парсер для игры с различными форматами, тот, который смогите сразу прочитать данные по PEM, так же, как ASN.1 в base64 и hexdump, попробуйте этот сайт.
очень важно: при добавлении закрытого ключа в keychain, который вы создали, как указано выше, имейте в виду, что такой закрытый ключ не содержит хэша открытого ключа, но хэш открытого ключа важен для них keychain API для формирования идентификатора (SecIdentityRef
), поскольку использование хэша открытого ключа-это то, как API находит правильный закрытый ключ, который принадлежит импортированному сертификату (a SecIdentityRef
это просто SecKeyRef
закрытого ключа и SecCertificateRef
сертификата, образующего комбинированный объект, и это хэш открытого ключа, который связывает их вместе). Поэтому, когда вы планируете добавить закрытый ключ в keychain, обязательно установите хэш открытого ключа вручную, иначе вы никогда не сможете получить удостоверение для него, и без этого вы не можете использовать API keychain для таких задач, как подписание или дешифрование данных. Хэш открытого ключа должен храниться в атрибуте с именем 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 { }