wulkanowy-ios/sdk/Sources/Sdk/X509.swift
2021-02-17 21:57:26 +01:00

192 lines
5.9 KiB
Swift

//
// File.swift
//
//
// Created by Tomasz (copied from rrroyal/vulcan) on 14/02/2021.
//
import Foundation
import CryptoKit
import OpenSSL
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
public class X509: ObservableObject {
enum X509Error: Error {
case errorGeneratingPKEY
}
public enum KeyFormat {
case PEM
case DER
}
let certificate: OpaquePointer
let pkey: OpaquePointer
public init(serialNumber: Int, certificateEntries: [String: String]) throws {
let x509: OpaquePointer = X509_new()
// serial number
ASN1_INTEGER_set(X509_get_serialNumber(x509), serialNumber)
// version
X509_set_version(x509, 0x2) // v3
// validity date
X509_gmtime_adj(X509_getm_notBefore(x509), 0)
X509_gmtime_adj(X509_getm_notAfter(x509), 60 * 60 * 24 * 365 * 10) // 60 seconds * 60 minutes * 24 hours * 365 days * 10 years
// key
guard let pkey = EVP_PKEY_new() else {
throw X509Error.errorGeneratingPKEY
}
let exponent = BN_new()
BN_set_word(exponent, 0x10001)
let rsa = RSA_new()
RSA_generate_key_ex(rsa, 2048, exponent, nil)
EVP_PKEY_set1_RSA(pkey, rsa)
X509_set_pubkey(x509, pkey)
self.pkey = pkey
// issuer
let subjectName = X509_get_subject_name(x509)
for (key, value) in certificateEntries {
X509_NAME_add_entry_by_txt(subjectName, key, MBSTRING_ASC, value, -1, -1, 0)
}
X509_set_issuer_name(x509, subjectName)
// sign the certificate
X509_sign(x509, pkey, EVP_sha256())
self.certificate = x509
}
/// Gets the private key used to sign the certificate data.
/// - Parameter format: Format of the returned key
/// - Returns: Private key data
public func getPrivateKeyData(format: KeyFormat) -> Data? {
let bio = BIO_new(BIO_s_mem())
switch format {
case .PEM: PEM_write_bio_PrivateKey(bio, self.pkey, nil, nil, 0, nil, nil)
case .DER: PEM_write_bio_PrivateKey_traditional(bio, self.pkey, nil, nil, 0, nil, nil)
}
var pointer: UnsafeMutableRawPointer?
let len = BIO_ctrl(bio, BIO_CTRL_INFO, 0, &pointer)
guard let nonEmptyPointer = pointer else {
return nil
}
let data = Data(bytes: nonEmptyPointer, count: len)
BIO_vfree(bio)
return data
}
/// Gets the public key used to sign the certificate data.
/// - Returns: Public key data
public func getPublicKeyData() -> Data? {
let bio = BIO_new(BIO_s_mem())
PEM_write_bio_PUBKEY(bio, self.pkey)
var pointer: UnsafeMutableRawPointer?
let len = BIO_ctrl(bio, BIO_CTRL_INFO, 0, &pointer)
guard let nonEmptyPointer = pointer else {
return nil
}
let data = Data(bytes: nonEmptyPointer, count: len)
BIO_vfree(bio)
return data
}
/// Gets the generated certificate data.
/// - Returns: Certificate data
public func getCertificateData() -> Data? {
let bio = BIO_new(BIO_s_mem())
PEM_write_bio_X509(bio, self.certificate)
var pointer: UnsafeMutableRawPointer?
let len = BIO_ctrl(bio, BIO_CTRL_INFO, 0, &pointer)
guard let nonEmptyPointer = pointer else {
return nil
}
let data = Data(bytes: nonEmptyPointer, count: len)
BIO_vfree(bio)
return data
}
/// Get certificate thumbrint.
/// - Returns: Certificate fingerprint
public func getCertificateFingerprint() -> String {
let md: UnsafeMutablePointer<UInt8> = .allocate(capacity: Int(EVP_MAX_MD_SIZE))
var n: UInt32 = 0
X509_digest(self.certificate, EVP_sha1(), md, &n)
return UnsafeMutableBufferPointer(start: md, count: Int(EVP_MAX_MD_SIZE))
.prefix(Int(n))
.makeIterator()
.map {
let string = String($0, radix: 16)
return ($0 < 16 ? "0" + string : string)
}
.joined(separator: ":")
.uppercased()
}
/// Get public key fingerprint
/// - Returns: Public key fingerprint
public func getPublicKeyFingerprint() -> String? {
guard let keyData = self.getPublicKeyData(),
let rawKeyB64 = String(data: keyData, encoding: .utf8) else {
return nil
}
let keyB64 = rawKeyB64
.split(separator: "\n") // Split by newline
.dropFirst() // Drop prefix
.dropLast() // Drop suffix
.joined() // Combine
guard let data = Data(base64Encoded: keyB64) else {
return nil
}
let hash = Insecure.MD5.hash(data: data)
return hash.map { String(format: "%02hhx", $0) }.joined()
}
/// Get private key fingerprint
/// - Parameter format: Format of the returned key
/// - Returns: Private key fingerprint
public func getPrivateKeyFingerprint(format: KeyFormat) -> String? {
guard let keyData = self.getPrivateKeyData(format: format),
let rawKeyB64 = String(data: keyData, encoding: .utf8) else {
return nil
}
let keyB64 = rawKeyB64
.split(separator: "\n") // Split by newline
.dropFirst() // Drop prefix
.dropLast() // Drop suffix
.joined() // Combine
guard let data = Data(base64Encoded: keyB64) else {
return nil
}
let hash = Insecure.MD5.hash(data: data)
return hash.map { String(format: "%02hhx", $0) }.joined()
}
}