【问题标题】:iOS: How to create PKCS12 (P12) keystore from private key and x509certificate in application programmatically?iOS:如何以编程方式从应用程序中的私钥和 x509 证书创建 PKCS12 (P12) 密钥库?
【发布时间】:2014-11-12 09:23:56
【问题描述】:

这个问题显然很相似,但没有任何答案:Programmatically create a x509 certificate for iPhone without using OpenSSL

在我们的应用程序(服务器、客户端)中,我们正在实现客户端身份验证(基于 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();

【问题讨论】:

  • 您是否有任何理由不在服务器上使用 API 来完成这项工作?
  • @remus 是的,P12 文件是根据客户端的私钥和客户端的证书创建的,客户端不必将其私钥/公钥发送到服务器如果没有必要。如果它脱离你的手只是为了你可以使用它就不是真正的私人,是吗:)
  • '`但是,要在 SSL 请求中使用此证书,必须将私钥和 X509Certificate 导出到 PKCS12 (P12) keystone。这根本不正确。您可以使用 Security 框架生成密钥对并将其存储在 keychain/NSURLCredentialStorage 中。绝对不建议将私钥作为文件存储在文件系统上!

标签: ios objective-c ssl cryptography authentication


【解决方案1】:

如果您使用openssl,您不必将完整的源代码复制到您的项目中,添加libs和headers就足够了,因此可以使用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;
}

【讨论】:

  • 谢谢!您已保存项目:D
  • 所以用未受保护的私钥将 pem 写入磁盘会使安全性付诸东流,对吧?
  • 这只是几个 PEM_write_PrivateKey 参数 :-) //X509_sign(x509, pkey, EVP_sha1());在我的情况下,必须取消注释 p12 才能在 OSX 上消化
  • 您是否将代码嵌入到swift项目中,如果有,您可以共享代码吗?
【解决方案2】:

非常感谢大家提供这么好的解决方案!

我将您的代码翻译成 Swift 3 并构建了以下函数,以使用 PEM 格式的签名 X509 证书和 RSA 私钥创建 P12 密钥库:

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 中与OpenSSL-for-iPhone 项目一起使用:

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

项目-桥接-Header.h:

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

【讨论】:

  • 我们必须导入什么库才能使用此代码?我们是否必须将 OpenSSL 库从 Object-C 桥接到 swift?
  • 你需要桥接原来的OpenSSL库。该库可以使用OpenSSL-for-iPhone 构建。我在回答中添加了一个简短的解释。
  • @sundance 如何在私有 pod 中使用它?
  • 对不起,我没有使用任何 pod,所以我不知道如何配置它们。
  • 我在 iOS 11 / XCode 9 中遇到了此代码的一些问题,当启用地址清理程序时,对 BIO_puts() 的调用会导致堆溢出。请参阅下面的答案。
【解决方案3】:

我的解决方案类似于 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
}

【讨论】:

  • 知道如何在混合中添加根 CA 证书吗?我正在使用 PEM_read_X509() 加载根证书,并在 PKCS12_create() 中使用它替换代码中的 nil 参数,但我得到了 EXC_BAD_ACCESS。
  • 对不起 - 这些天我不再在 iOS 上工作了,我现在在做 Android/Kotlin。很高兴您能找到解决方案。
【解决方案4】:

问题解决了!谢谢各位。

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];

保存P12文件是:

//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"];

谢谢!

【讨论】:

  • 有人知道“证书”是什么类型的吗?
  • @mr.sosa 我认为它是 X509。
  • 当我将私钥数据转换为 base64 字符串然后使用以下代码转换为 EVP_PKEY 时,我的 EVP_PKEY 为空
  • NSString *privateKey = [privateKeyData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; NSString *pemPrivateKey = [NSString stringWithFormat:@"-----BEGIN RSA PRIVATE KEY-----\n\%@\n-----END RSA PRIVATE KEY-----\n",privateKey ];生物 *publicBIO = NULL; EVP_PKEY *pkey = NULL; NSData *privateKeyData = [pemPrivateKey dataUsingEncoding:NSUTF8StringEncoding];
  • if ((publicBIO = BIO_new_mem_buf((unsigned char *)[privateKeyData bytes], [privateKeyData length])) == NO) { NSLog(@"BIO_new_mem_buf() failed!"); } pkey = PEM_read_bio_PrivateKey(publicBIO, NULL, NULL, NULL); //这里的pkey是null
【解决方案5】:

我的解决方案类似于 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
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-25
    • 2011-06-27
    • 2017-02-05
    • 1970-01-01
    • 2014-01-04
    • 2012-12-31
    • 2016-01-17
    相关资源
    最近更新 更多