iOS:如何以编程方式在应用程序中从私钥和x509certificate创建PKCS12(P12)密钥库?

时间:2014-11-12 09:23:57

标签: ios objective-c ssl cryptography authentication

这个问题显然相似但没有任何答案: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();

4 个答案:

答案 0 :(得分:9)

如果使用openssl,则不必将完整的源代码复制到项目中,只需添加libs和头文件即可,因此可以使用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;
}

答案 1 :(得分:7)

非常感谢你们这个很好的解决方案!

我将您的代码翻译为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中与OpenSSL-for-iPhone项目一起使用:

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

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

答案 2 :(得分:2)

问题解决了!谢谢你们,伙计们。

现在可以正确创建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"];

谢谢!

答案 3 :(得分:2)

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