Как я могу получить 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, она всегда возвращает nil даже через файл чтения в порядке.
Кто-нибудь сделал это, пожалуйста, помогите мне, спасибо!
EDIT: файл cert был подписи MD5.
Ответы
Ответ 1
Я много боролся с той же проблемой и, наконец, нашел решение. Моя проблема заключалась в том, что мне нужно было использовать как внешний закрытый, так и открытый ключ для шифрования/дешифрования данных в приложении 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;
}
Ответ 2
Начиная с 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-интерфейс keychain для формирования идентификатора (SecIdentityRef
), так как использование хэша открытого ключа - это то, как API находит правильный закрытый ключ, принадлежащий импортированному сертификату (a SecIdentityRef
- это просто SecKeyRef
частного ключ и SecCertificateRef
сертификата, образующего объединенный объект, и это хэш-ключ открытого ключа, который связывает их вместе). Поэтому, когда вы планируете добавить закрытый ключ в цепочку ключей, обязательно установите хэш-ключ открытого ключа вручную, иначе вы никогда не сможете получить идентификатор для него, и без этого вы не сможете использовать 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()
);
Ответ 3
После нескольких часов усилий, исследующих онлайн с помощью этого сообщения, я, наконец, полностью работаю. Вот заметки с рабочим кодом 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 {
}