iOS:如何在应用程序中通过编程从私钥和x509证书创建PKCS12(P12)密钥库



这个问题显然很相似,但没有任何答案:在不使用OpenSSL 的情况下为iPhone编程创建x509证书

在我们的应用程序(服务器、客户端)中,我们正在实现客户端身份验证(基于X509Certificate的SSL)。我们已经有了生成keypair、创建PKCS10 Certificate Signing Request、由self-signed CA签名并创建X509Certificate的方法,并将其发回。但是,要在SSL请求中使用此证书,必须将private keyX509Certificate导出到PKCS12(P12)keystore

有人知道如何做到这一点吗,甚至知道这是否可能吗?客户端生成P12文件(我们不想泄露私钥),并且客户端运行iOS,是一个移动设备。该解决方案适用于使用BouncyCastle(SpongyCastle)的Android,但我们在iOS上一无所获。

编辑:在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();

如果您使用openssl,您不必将完整的源代码复制到您的项目中,只需添加libs和header就足够了,因此可以使用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 p12filen");
        exit(1);
    }
    SSLeay_add_all_algorithms();
    ERR_load_crypto_strings();
    if (!(fp = fopen(argv[1], "r"))) {
        fprintf(stderr, "Error opening file %sn", 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 structuren");
        ERR_print_errors_fp(stderr);
        exit(1);
    }
    if (!(fp = fopen(argv[4], "wb"))) {
        fprintf(stderr, "Error opening file %sn", argv[1]);
        ERR_print_errors_fp(stderr);
        exit(1);
    }
    i2d_PKCS12_fp(fp, p12);
    PKCS12_free(p12);
    fclose(fp);
    return 0;
}

非常感谢大家提供了这个不错的解决方案!

我将您的代码翻译成Swift 3,并构建了以下函数,以使用已签名的X509证书和RSA私钥创建P12密钥库,两者都是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()
}

编辑:

OpenSSL可以在iOS中与iPhone项目的OpenSSL一起使用:

  1. 签出存储库
  2. ./build-libssl.sh构建静态库
  3. $(YOUR_PATH)/OpenSSL-for-iPhone/include添加到标头搜索路径
  4. $(YOUR_PATH)/OpenSSL-for-iPhone/lib添加到库搜索路径
  5. libcrypto.alibssl.a添加到链接的框架和库中
  6. 将以下标头添加到桥接标头:

项目桥接头.h:

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

我的解决方案与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
}

我的解决方案与mbonness的类似,但使用Swift 5进行了重新设计,以抑制弃用警告,并采用了可选的rootCA证书。

static func pkcs12(fromPem pemCertificate: String,
                   withPrivateKey pemPrivateKey: String,
                   p12Password: String = "",
                   certificateAuthorityFileURL: URL? = nil) throws -> NSData {
    // Create sec certificates from PEM string
    let modifiedCert = pemCertificate
        .replacingOccurrences(of: "-----BEGIN CERTIFICATE-----", with: "")
        .replacingOccurrences(of: "-----END CERTIFICATE-----", with: "")
        .replacingOccurrences(of: "n", with: "")
        .trimmingCharacters(in: .whitespacesAndNewlines)
    guard let derCertificate = NSData(base64Encoded: modifiedCert, options: [])
    else {
        throw X509Error.cannotReadPEMCertificate
    }
    // 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)
    let p12Path = try pemPrivateKey.data(using: .utf8)!
        .withUnsafeBytes { bytes throws -> String in
            let privateKeyBuffer = BIO_new_mem_buf(bytes.baseAddress, Int32(pemPrivateKey.count))
            let privateKey = PEM_read_bio_PrivateKey(privateKeyBuffer, nil, nil, nil)
            defer {
                BIO_free(privateKeyBuffer)
            }
            // 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()
            ERR_load_CRYPTO_strings()
            // The CA cert needs to be in a stack of certs
            let certsStack = sk_X509_new_null()
            if let certificateAuthorityFileURL = certificateAuthorityFileURL {
                // Read root certiticate
                let rootCAFileHandle = try FileHandle(forReadingFrom: certificateAuthorityFileURL)
                let rootCAFile = fdopen(rootCAFileHandle.fileDescriptor, "r")
                let rootCA = PEM_read_X509(rootCAFile, nil, nil, nil)
                fclose(rootCAFile)
                rootCAFileHandle.closeFile()
                // Add certificate to the stack
                sk_X509_push(certsStack, rootCA)
            }
            // 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,
                                          certsStack,
                                          0,
                                          0,
                                          0,
                                          PKCS12_DEFAULT_ITER,
                                          0) else {
                ERR_print_errors_fp(stderr)
                throw X509Error.cannotCreateP12Keystore
            }
            // Save P12 keystore
            let fileManager = FileManager.default
            let path = fileManager
                .temporaryDirectory
                .appendingPathComponent(UUID().uuidString)
                .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()
            return path
        }
    // Read P12 Data
    guard let p12Data = NSData(contentsOfFile: p12Path) else {
        throw X509Error.cannotReadP12Certificate
    }
    // Remove temporary file
    try? FileManager.default.removeItem(atPath: p12Path)
    return p12Data
}

问题解决了!谢谢大家。

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 structuren");
    ERR_print_errors_fp(stderr);
    exit(1);
}
[self saveP12File:p12];

saveP12文件为:

//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);

p12文件路径为:

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

谢谢!

最新更新