Skip to content

Instantly share code, notes, and snippets.

@jdanthinne
Last active July 27, 2023 21:31
Show Gist options
  • Save jdanthinne/8a6557744ba5498de1952d176d293293 to your computer and use it in GitHub Desktop.
Save jdanthinne/8a6557744ba5498de1952d176d293293 to your computer and use it in GitHub Desktop.
Convert PEM certificate to P12 (PKCS#12)
import Foundation
import OpenSSL
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
}
enum X509Error: Error {
case cannotReadPEMCertificate
case privateKeyDoesNotMatchCertificate
case cannotCreateP12Keystore
case cannotOpenFileHandles
case cannotReadP12Certificate
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment