This question was apparently similar but had no answers of any kind: Programmatically create a x509 certificate for iPhone without using OpenSSL
In our application (server, client), we are implementing client authentication (SSL based on X509Certificate). We already have a way to generate a keypair
, create a PKCS10 Certificate Signing Request
, have this signed by the self-signed CA
and create a X509Certificate
, send this back. However, to use this certificate in SSL requests, the private key
and the X509Certificate
have to be exported to a PKCS12
(P12) keystore
.
Does anyone know anything about how to do this, or even if it's possible? The client has to generate the P12 file (we don't want to give out the private key), and the client is running iOS, and is a mobile device. The solution worked for Android using BouncyCastle (SpongyCastle), but we found nothing for iOS.
EDIT: In Java, this export is done by the following:
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();
My solution is similar to mbonness', but reworked to suppress deprecation warning with Swift 5, and taking an optional rootCA certificate.
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With