Цель C: Не удалось получить SecKeyRef из личного ключа PEM

Я новичок в программировании Objective C и iOS.

Я использую простые общедоступные/закрытые ключи (формат PEM), сгенерированные с помощью openssl, для шифрования и дешифрования данных, которые необходимо обменивать между сервером и клиентом. Я успешно работал в Java Server и Client.

Проблема началась, когда я шифровал данные с помощью открытого ключа в Java и расшифровывал их с помощью закрытого ключа в Objective C/iOS. Я просмотрел несколько примеров и скрепил код, но я получаю сообщение об ошибке -25300, когда я все время вызываю SecItemCopyMatching как часть создания SecKeyRef из закрытого ключа.

Кстати, здесь нет сертификатов, и это простые ключи. Вот что я делаю:

  • Прочитайте закрытый ключ PEM и декодирование Base64.
  • Сгенерируйте SecKeyRef из декодированной строки с помощью SecItemCopyMatching.
  • Расшифровывать с помощью SecKeyDecrypt.

Моя проблема - это шаг №2, который возвращает статус -25300 (errSecItemNotFound -25300
Элемент не найден. Доступно в iOS 2.0 и более поздних версиях.)

Вот мой код для генерации SecKeyRef:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSString *challenge = @"2KFqc46DNSWrizzv69lJN25o62xEYQw/QLcMiT2V1XLER9uJbOu+xH2qgTuNWa1HZ9SW3Lq+HovtkhFmjmf08QkVQohHmxCJXVyCgVhPBleScAgQ8AoP3tmV0RqGb2mJrb19ybeYP7uZ2piVtF4cRwU1gO3VTooCUK3cX4wS7Tc=";
NSLog(@"challenge, %@", challenge);

NSData *incomingData = [self base64DataFromString:challenge];
uint8_t *challengeBuffer = (uint8_t*)[incomingData bytes];
NSLog(@"challengeBuffer: %s", challengeBuffer);

[self decryptWithPrivateKey:challengeBuffer];

free(challengeBuffer);

return YES;
}

// Generate a SecKeyRef from the private key in the private.pem file.
- (SecKeyRef)getPrivateKeyRef {
NSString *startPrivateKey = @"-----BEGIN RSA PRIVATE KEY-----";
NSString *endPrivateKey = @"-----END RSA PRIVATE KEY-----";
NSString* path = [[NSBundle mainBundle] pathForResource:@"private"
                                                 ofType:@"pem"];
NSString* content = [NSString stringWithContentsOfFile:path
                                              encoding:NSUTF8StringEncoding
                                                 error:NULL];
NSLog(@"Private Key: %@", content);

NSString *privateKey;
NSScanner *scanner = [NSScanner scannerWithString:content];
[scanner scanUpToString:startPrivateKey intoString:nil];
[scanner scanString:startPrivateKey intoString:nil];
[scanner scanUpToString:endPrivateKey intoString:&privateKey];

NSData *privateTag = [self dataWithBase64EncodedString:privateKey];
NSLog(@"Decoded String: %@", privateTag);

OSStatus status = noErr;
SecKeyRef privateKeyReference = NULL;

NSMutableDictionary * queryPrivateKey = [[NSMutableDictionary alloc] init];

// Set the private key query dictionary.
[queryPrivateKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[queryPrivateKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
[queryPrivateKey setObject:privateTag forKey:(__bridge id)kSecAttrApplicationTag];
[queryPrivateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
//[queryPrivateKey setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnRef];


// Get the key.
status = SecItemCopyMatching((__bridge CFDictionaryRef)queryPrivateKey, (CFTypeRef *)&privateKeyReference);
NSLog(@"status: %ld", status);

if(status != noErr)
{
    privateKeyReference = NULL;
}

return privateKeyReference;
}

// Decrypt data
- (void)decryptWithPrivateKey:(uint8_t *)cipherBuffer {
OSStatus status = noErr;

SecKeyRef privateKeyRef = [self getPrivateKeyRef];

size_t plainBufferSize = SecKeyGetBlockSize(privateKeyRef);
uint8_t *plainBuffer = malloc(plainBufferSize);

size_t cipherBufferSize = strlen((char *)cipherBuffer);
NSLog(@"decryptWithPrivateKey: length of input: %lu", cipherBufferSize);

//  Error handling
status = SecKeyDecrypt(privateKeyRef,
                       PADDING,
                       cipherBuffer,
                       cipherBufferSize,
                       &plainBuffer[0],
                       &plainBufferSize
                       );
NSLog(@"decryption result code: %ld (size: %lu)", status, plainBufferSize);
NSLog(@"FINAL decrypted text: %s", plainBuffer);
}

Я уже пару дней ломаю голову, и мне показалось, что мне нужна помощь. Любой какой-нибудь указатель? Я мог бы потратить больше времени на получение знаний и поддержки домена Crypto, предоставляемых iOS, но я вообще не занимаюсь программированием на iOS, и это одно время.

Мне просто нужно какое-то направление, и я могу изо всех сил пытаться заставить его работать.

ТИА.

Ответ 1

К сожалению, инфраструктура безопасности в iOS требует, чтобы закрытые ключи находились в формате PKCS12 с парольной фразой. Открытые ключи могут быть в бронированном DER X509 или PKCS12, но для ключей PKCS12 должны использоваться закрытые ключи. Частным ключом, который вы пытаетесь использовать, является ключ RSA в формате PEM.

Если у вас есть доступ к ключу, он может быть преобразован с помощью команд командной строки openssl:

openssl pkcs12 -export -nocerts -inkey privatekey.pem -out privatekey.p12

Это создаст файл PKCS12 с закрытым ключом и потребует кодовую фразу. Если у вас нет контроля за закрытым ключом (например, если он исходит от внешнего источника, такого как сервер), вам не повезло.

Но предположим, что вы смогли выполнить описанные выше шаги, чтобы преобразовать этот секретный закрытый ключ PEM RSA в PKCS12. Извлечение секретного ключа из данных PKCS12 не слишком сложно:

  • Загрузите PKCS12 как NSData. Вы можете сделать это, используя dataWithContentsOfURL:, если это ресурс в файловой системе.
  • Используйте SecPKCS12Import для импорта данных PKCS12 с парольной фразой.
  • Извлеките SecIdentityRef из импортированных элементов.
  • Скопируйте закрытый ключ из SecIdentityRef

Функция для этого:

OSStatus    SecKeyPrivatePKCS12Import(CFDataRef keyData, CFStringRef passphrase, SecKeyRef *privateKey){
    OSStatus        status              = errSecSuccess;
    CFDictionaryRef secImportOptions    = NULL;
    CFArrayRef      secImportItems      = NULL;

    if ((keyData != NULL) && (CFStringGetLength(passphrase) > 0) ){
        const void *keys[] = { kSecImportExportPassphrase };
        const void *values[] = { passphrase };

        secImportOptions = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);

        status = SecPKCS12Import((CFDataRef) keyData, (CFDictionaryRef)secImportOptions, &secImportItems);
        if (CFArrayGetCount(secImportItems) > 0){
            CFDictionaryRef identityDict = CFArrayGetValueAtIndex(secImportItems, 0);
            SecIdentityRef identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
            SecIdentityCopyPrivateKey(identityApp, privateKey);
        }
    }

    return status;
}

Вызов из Objective-C будет выглядеть так:

OSStatus status = errSecSuccess;

status = SecKeyPrivatePKCS12Import((_bridge CFDataRef)data, (_bridge CFStringRef)passphrase, &privateKey);
if (privateKey == NULL){
   // Check the status value for why it failed
}

Предполагая, что "данные" представляют собой экземпляр NSData, который содержит данные PKCS12, а "passphrase" - это экземпляр NSString, представляющий кодовую фразу. При успехе "privateKey" заполняется личным ключом, импортированным из данных PKCS12.

Ответ 2

У меня возникла такая же проблема, когда я работал с java-сервером и iPhone-приложением, и моя работа была такой, как показано ниже.

  • Сгенерировать p12 на java-сервере. Не забывайте записывать пароль.]
  • Преобразование необработанных байтов файла p12 в строку базы 64.
  • Отправьте эти данные в приложение iOS независимо от того, как вы хотите.

    3.1 Вы можете поместить базу 64 в текстовый файл и отправить ее в iOS. [Безопасный путь и работа в моем случае.]

    3.2 Вы можете использовать строку JSON для отправки этой строки. [Это может привести к повреждению ваших данных.]

  • Как только вы получите данные в приложении iPhone, переведите базовую строку 64 в NSData. NSData + Base64
  • Используйте следующий метод для получения секретного ключа SecKeyRef.

    - (SecKeyRef)getPrivateKeyFromData:(NSData *)p12Data withPassword:(NSString *)password {
        NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
        SecKeyRef privateKey = NULL;
        [options setObject:password forKey:(__bridge id)kSecImportExportPassphrase];
        CFArrayRef items = NULL;// = CFArrayCreate(NULL, 0, 0, NULL);
        OSStatus securityError = SecPKCS12Import((__bridge CFDataRef)p12Data,
                                              (__bridge CFDictionaryRef)options, &items);
        if (securityError == noErr && CFArrayGetCount(items) > 0) {
             CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
             SecIdentityRef identityApp =
             (SecIdentityRef)CFDictionaryGetValue(identityDict,
                                          kSecImportItemIdentity);
             securityError = SecIdentityCopyPrivateKey(identityApp, &privateKey);
        if (securityError != noErr) {
                privateKey = NULL;
            }
        }
        //NSLog(@"-------------------- Private Key Error %d",(int)securityError);
        CFRelease(items);
        options = nil;
        p12Data = nil;
        password = nil;
        return privateKey;
    }
    

Надеюсь, это поможет!!!!!

Ответ 3

У вас есть секретный ключ и сертификат в цепочке ключей. В противном случае SecItemCopyMatching ничего не сделает. Вам нужно импортировать только один раз.

/* importing client identity (private key) */
NSData* certificateData = ... ; // decoded pkcs21 certificate from base64 pem
NSString* passcode = @"passphrased used to encrypt the private key";
CFDictionaryRef optionsDictionary = (__bridge CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys: passcode,  kSecImportExportPassphrase, nil];
CFArrayRef certificates;
OSStatus error = SecPKCS12Import((__bridge CFDataRef) certificateData, optionsDictionary, &certificates);
CFDictionaryRef myIDs = CFArrayGetValueAtIndex(certificates, 0); 

SecIdentityRef identity = (SecIdentityRef) CFDictionaryGetValue(myIDs, kSecImportItemIdentity);

NSDictionary* clientCertificateQuery = @{(__bridge id)kSecValueRef        : identity,
                                         (__bridge id)kSecAttrLabel       : @"some label you can use to find the item again with SecItemCopyMatching"};
OSStatus err = SecItemAdd((__bridge CFDictionaryRef) clientCertificateQuery, NULL);

Затем вы можете использовать SecItemCopyMatching, чтобы получить идентификатор и SecIdentityCopyPrivateKey, чтобы получить закрытый ключ.

NSDictionary* clientCertificateQuery = @{(__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitAll,
                                         (__bridge id)kSecClass      : (__bridge id)kSecClassIdentity,
                                         (__bridge id)kSecReturnRef   : (__bridge id)kCFBooleanTrue};
SecIdentityRef identity = NULL;
OSStatus errorCode = SecItemCopyMatching((__bridge CFDictionaryRef) clientCertificateQuery, &identity);
SecKeyRef privateKeyRef;
OSStatus err = SecIdentityCopyPrivateKey (identity, &privateKeyRef);

Всегда проверяйте ошибки OSStatus, поскольку вы обязательно столкнетесь с errSecDuplicateItem.

Обязательно прочитайте Apple Сертификат, ключ и ссылка на службы доверия.