Как получить 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-кодом самой последней версии. Надеюсь, это может кому-то помочь!

  1. получил сертификат в кодировке base64 зажатой между головой и хвостом, как это (формат PEM):

    -----BEGIN CERTIFICATE-----
    -----END CERTIFICATE-----
    
  2. обнажите вне заголовок и хвост, как

    // 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)  
    }
    
  3. декодировать строку base64 в NSData:

    let data = NSData(base64Encoded: cerStr, 
       options:NSData.Base64DecodingOptions.ignoreUnknownCharacters)!  
    
  4. преобразовать его из формата NSdata в SecCertificate:

    let cert = SecCertificateCreateWithData(kCFAllocatorDefault, data)
    
  5. теперь этот сертификат можно использовать для сравнения с сертификатом, полученным от доверия urlSession:

    certificateFromUrl = SecTrustGetCertificateAtIndex(...)
    if cert == certificate {
    }