Подтвердить что ты не робот

Как я могу получить 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.

4b9b3361

Ответ 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 {
    }