iOS: Как создать хранилище ключей PKCS12 (P12) из закрытого ключа и x509certificate в приложении программно?

этот вопрос был, по-видимому, похож, но не имел никаких ответов:программно создайте сертификат x509 для iPhone без использования OpenSSL

в нашем приложении (сервер, клиент) мы реализуем аутентификацию клиента (SSL на основе X509Certificate). У нас уже есть способ создания keypair создать PKCS10 Certificate Signing Request, подпишите это self-signed CA и создать X509Certificate отправить это обратно. Однако для использования этого сертификата в SSL-запросах private key и X509Certificate должны быть экспортированы в PKCS12 (P12) keystore.

кто-нибудь знает что-нибудь о том, как это сделать, или даже если это возможно? Клиент и создать файл P12 (мы не хотим выдавать закрытый ключ), а клиент под iOS и мобильные устройства. Решение работало для Android с помощью BouncyCastle (губка), но мы ничего не нашли для iOS.

EDIT: в Java этот экспорт выполняется следующий:

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    KeyStore ks = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
    ks.load(null);
    ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(),
            new java.security.cert.Certificate[] { x509Certificate });
    ks.store(bos, password.toCharArray());
    bos.close();
    return bos.toByteArray();

4 ответов


Если вы используете openssl, вам не нужно копировать полный исходный код в свой проект, достаточно добавить библиотеки и заголовки, поэтому библиотеку openssl можно использовать без каких-либо проблем с размером. Вы можете создать ключ и сертификат, как это с openssl:

EVP_PKEY * pkey;
pkey = EVP_PKEY_new();

RSA * rsa;
rsa = RSA_generate_key(
        2048,   /* number of bits for the key - 2048 is a sensible value */
        RSA_F4, /* exponent - RSA_F4 is defined as 0x10001L */
        NULL,   /* callback - can be NULL if we aren't displaying progress */
        NULL    /* callback argument - not needed in this case */
);

EVP_PKEY_assign_RSA(pkey, rsa);

X509 * x509;
x509 = X509_new();

ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);

X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);

X509_set_pubkey(x509, pkey);

X509_NAME * name;
name = X509_get_subject_name(x509);

X509_NAME_add_entry_by_txt(name, "C",  MBSTRING_ASC,
        (unsigned char *)"CA", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O",  MBSTRING_ASC,
        (unsigned char *)"MyCompany Inc.", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
        (unsigned char *)"localhost", -1, -1, 0);

X509_set_issuer_name(x509, name);

//X509_sign(x509, pkey, EVP_sha1());

const EVP_CIPHER *aConst = EVP_des_ede3_cbc();

и вы можете записать это в формат pem с помощью следующих функций:

PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL);


PEM_write_X509(
            f,   /* write the certificate to the file we've opened */
            x509 /* our certificate */
);

после этого можно записать эти файлы в файл P12, источником здесь: https://github.com/luvit/openssl/blob/master/openssl/demos/pkcs12/pkwrite.c

/* pkwrite.c */

#include <stdio.h>
#include <stdlib.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>

/* Simple PKCS#12 file creator */
int main(int argc, char **argv)
{
    FILE *fp;
    EVP_PKEY *pkey;
    X509 *cert;
    PKCS12 *p12;
    if (argc != 5) {
        fprintf(stderr, "Usage: pkwrite infile password name p12file\n");
        exit(1);
    }
    SSLeay_add_all_algorithms();
    ERR_load_crypto_strings();
    if (!(fp = fopen(argv[1], "r"))) {
        fprintf(stderr, "Error opening file %s\n", argv[1]);
        exit(1);
    }
    cert = PEM_read_X509(fp, NULL, NULL, NULL);
    rewind(fp);
    pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
    fclose(fp);
    p12 = PKCS12_create(argv[2], argv[3], pkey, cert, NULL, 0,0,0,0,0);
    if(!p12) {
        fprintf(stderr, "Error creating PKCS#12 structure\n");
        ERR_print_errors_fp(stderr);
        exit(1);
    }
    if (!(fp = fopen(argv[4], "wb"))) {
        fprintf(stderr, "Error opening file %s\n", argv[1]);
        ERR_print_errors_fp(stderr);
        exit(1);
    }
    i2d_PKCS12_fp(fp, p12);
    PKCS12_free(p12);
    fclose(fp);
    return 0;
}

спасибо всем большое за это хорошее решение!

Я перевел ваш код на Swift 3 и построил следующую функцию для создания хранилища ключей P12 с использованием подписанного сертификата X509 и закрытого ключа RSA, как в формате PEM:

func createP12(pemCertificate: String, pemPrivateKey: String) {
    // Read certificate
    let buffer = BIO_new(BIO_s_mem())
    pemCertificate.data(using: .utf8)!.withUnsafeBytes({ (bytes: UnsafePointer<Int8>) -> Void in
        BIO_puts(buffer, bytes)
    })
    let certificate = PEM_read_bio_X509(buffer, nil, nil, nil)
    X509_print_fp(stdout, certificate)
    // Read private key
    let privateKeyBuffer = BIO_new(BIO_s_mem())
    pemPrivateKey.data(using: .utf8)!.withUnsafeBytes({ (bytes: UnsafePointer<Int8>) -> Void in
        BIO_puts(privateKeyBuffer, bytes)
    })
    let privateKey = PEM_read_bio_PrivateKey(privateKeyBuffer, nil, nil, nil)
    PEM_write_PrivateKey(stdout, privateKey, nil, nil, 0, nil, nil)
    // Check if private key matches certificate
    guard X509_check_private_key(certificate, privateKey) == 1 else {
        NSLog("Private key does not match certificate")
        return
    }
    // Set OpenSSL parameters
    OPENSSL_add_all_algorithms_noconf()
    ERR_load_crypto_strings()
    // Create P12 keystore
    let passPhrase = UnsafeMutablePointer(mutating: ("" as NSString).utf8String)
    let name = UnsafeMutablePointer(mutating: ("SSL Certificate" as NSString).utf8String)
    guard let p12 = PKCS12_create(passPhrase, name, privateKey, certificate, nil, 0, 0, 0, 0, 0) else {
        NSLog("Cannot create P12 keystore:")
        ERR_print_errors_fp(stderr)
        return
    }
    // Save P12 keystore
    let fileManager = FileManager.default
    let tempDirectory = NSTemporaryDirectory() as NSString
    let path = tempDirectory.appendingPathComponent("ssl.p12")
    fileManager.createFile(atPath: path, contents: nil, attributes: nil)
    guard let fileHandle = FileHandle(forWritingAtPath: path) else {
        NSLog("Cannot open file handle: \(path)")
        return
    }
    let p12File = fdopen(fileHandle.fileDescriptor, "w")
    i2d_PKCS12_fp(p12File, p12)
    fclose(p12File)
    fileHandle.closeFile()
}

EDIT:

OpenSSL можно использовать в iOS с OpenSSL-для-iPhone:

  1. Проверьте репозиторий
  2. создайте статические библиотеки с помощью ./build-libssl.sh
  3. добавить $(YOUR_PATH)/OpenSSL-for-iPhone/include в пути поиска заголовка
  4. добавить $(YOUR_PATH)/OpenSSL-for-iPhone/lib в пути поиска библиотеки
  5. добавить libcrypto.a и libssl.a к связанным фреймворкам и библиотекам
  6. добавьте следующие заголовки в заголовок моста:

Проект-Мост-Заголовок.h:

#import <openssl/err.h>
#import <openssl/pem.h>
#import <openssl/pkcs12.h>
#import <openssl/x509.h>

проблема решена! Спасибо, ребята.

файл p12 теперь создан правильно.

код теперь:

NSString *certPem = [certificate pemCertificate];
[certPem writeToFile:[self certFilePath] atomically:YES encoding:NSUTF8StringEncoding error:nil];

const char *cert_chars = [certPem cStringUsingEncoding:NSUTF8StringEncoding];

BIO *buffer = BIO_new(BIO_s_mem());
BIO_puts(buffer, cert_chars);

X509 *cert;
cert = PEM_read_bio_X509(buffer, NULL, 0, NULL);
if (cert == NULL) {
    NSLog(@"error");
}
X509_print_fp(stdout, cert);

if (!X509_check_private_key(cert, [certificate privateKey])) {
    NSLog(@"PK error");
}

PKCS12 *p12;

SSLeay_add_all_algorithms();
ERR_load_crypto_strings();


p12 = PKCS12_create("passPhrase", "iOSMobileCertificate", [certificate privateKey], cert, NULL, 0,0,0,0,0);
if(!p12) {
    fprintf(stderr, "Error creating PKCS#12 structure\n");
    ERR_print_errors_fp(stderr);
    exit(1);
}

[self saveP12File:p12];

saveP12File является:

//create empty file
NSString *p12FilePath = [self p12FilePath];
if (![[NSFileManager defaultManager] createFileAtPath:p12FilePath contents:nil attributes:nil])
{
    NSLog(@"Error creating file for P12");
    @throw [[NSException alloc] initWithName:@"Fail getP12File" reason:@"Fail Error creating file for P12" userInfo:nil];
}

//get a FILE struct for the P12 file
NSFileHandle *outputFileHandle = [NSFileHandle fileHandleForWritingAtPath:p12FilePath];
FILE *p12File = fdopen([outputFileHandle fileDescriptor], "w");

i2d_PKCS12_fp(p12File, p12);
PKCS12_free(p12);
fclose(p12File);

и p12FilePath это:

NSString *documentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
return [documentsFolder stringByAppendingPathComponent:@"CERT.p12"];

спасибо!


мое решение похоже на sundance, я переработал его, чтобы обойти проблемы с дезинфицирующим средством / переполнением кучи, возникающие в XCode 9.

func createP12(secCertificate: SecCertificate, secPrivateKeyBase64: String, p12FileName: String, _ p12Password: String = "") throws -> String {
    // Read certificate
    // Convert sec certificate to DER certificate
    let derCertificate = SecCertificateCopyData(secCertificate)

    // Create strange pointer to read DER certificate with OpenSSL
    // data must be a two-dimensional array containing the pointer to the DER certificate as single element at position [0][0]
    let certificatePointer = CFDataGetBytePtr(derCertificate)
    let certificateLength = CFDataGetLength(derCertificate)
    let certificateData = UnsafeMutablePointer<UnsafePointer<UInt8>?>.allocate(capacity: 1)
    certificateData.pointee = certificatePointer

    // Read DER certificate
    let certificate = d2i_X509(nil, certificateData, certificateLength)

    // Print certificate
    #if DEBUG
        X509_print_fp(stdout, certificate)
    #endif

    let pemPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n\(secPrivateKeyBase64)\n-----END RSA PRIVATE KEY-----\n"

    let p12Path = try pemPrivateKey.data(using: .utf8)!.withUnsafeBytes({ (bytes: UnsafePointer<Int8>) -> String in
        let privateKeyBuffer = BIO_new_mem_buf(bytes, Int32(pemPrivateKey.characters.count))
        let privateKey = PEM_read_bio_PrivateKey(privateKeyBuffer, nil, nil, nil)
        defer {
            BIO_free(privateKeyBuffer)
        }

        // Print private key
        #if DEBUG
            PEM_write_PrivateKey(stdout, privateKey, nil, nil, 0, nil, nil)
        #endif

        // Check if private key matches certificate
        guard X509_check_private_key(certificate, privateKey) == 1 else {
            throw X509Error.privateKeyDoesNotMatchCertificate
        }

        // Set OpenSSL parameters
        OPENSSL_add_all_algorithms_noconf()
        ERR_load_crypto_strings()

        // Create P12 keystore
        let passPhrase = UnsafeMutablePointer(mutating: (p12Password as NSString).utf8String)
        let name = UnsafeMutablePointer(mutating: ("SSL Certificate" as NSString).utf8String)
        guard let p12 = PKCS12_create(passPhrase, name, privateKey, certificate, nil, 0, 0, 0, 0, 0) else {
            ERR_print_errors_fp(stderr)
            throw X509Error.cannotCreateP12Keystore
        }

        // Save P12 keystore
        let fileManager = FileManager.default
        let documentsPathURL = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
        let path = documentsPathURL.appendingPathComponent(p12FileName).path
        fileManager.createFile(atPath: path, contents: nil, attributes: nil)
        guard let fileHandle = FileHandle(forWritingAtPath: path) else {
            NSLog("Cannot open file handle: \(path)")
            throw X509Error.cannotOpenFileHandles
        }
        let p12File = fdopen(fileHandle.fileDescriptor, "w")
        i2d_PKCS12_fp(p12File, p12)
        PKCS12_free(p12)
        fclose(p12File)
        fileHandle.closeFile()

        NSLog("Wrote P12 keystore to: \(path)")
        return path
    })

    return p12Path
}

enum X509Error: Error {
    case privateKeyDoesNotMatchCertificate
    case cannotCreateP12Keystore
    case cannotOpenFileHandles
}