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

这个问题显然是相似的,但没有任何答案:以编程方式为iPhone创build一个x509证书,而不使用OpenSSL

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

有没有人知道如何做到这一点,或者即使有可能? 客户端必须生成P12文件(我们不想泄露私钥),客户端运行的是iOS,并且是移动设备。 该解决scheme使用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,则不必将完整的源代码复制到项目中,只需添加库和头文件即可,因此openssl库可以在没有任何大小的情况下使用。 你可以使用openssl生成一个key和一个cert:

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

非常感谢你这个很好的解决scheme!

我将你的代码翻译成了Swift 3,并且使用签名的X509证书和RSA私钥build立了以下函数来创build一个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 for iPhone项目中,可以在iOS中使用OpenSSL :

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

项目桥接-Header.h:

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

问题解决了! 感谢你们。

现在p12文件被正确创build。

现在的代码是:

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

谢谢!

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