Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 85 additions & 79 deletions ios/MqttClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import CocoaMQTT
import os
import Security

typealias SecKeyPerformBlock = (SecKey) -> ()

func loadX509Certificate(fromPem: String) -> SecCertificate? {
let pemContents = fromPem
.replacingOccurrences(of: "-----BEGIN CERTIFICATE-----", with: "")
Expand All @@ -13,22 +15,6 @@ func loadX509Certificate(fromPem: String) -> SecCertificate? {
return SecCertificateCreateWithData(nil, data)
}

func loadPrivateKey(fromPem: String) -> SecKey? {
let pemContents = fromPem
.replacingOccurrences(of: "-----BEGIN RSA PRIVATE KEY-----", with: "")
.replacingOccurrences(of: "-----END RSA PRIVATE KEY-----", with: "")
guard let data = NSData.init(base64Encoded: pemContents, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) else
{
return nil
}
let options: [String: Any] = [
kSecAttrType as String: kSecAttrKeyTypeRSA,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits as String: 2048 // supposes that the private key has 2048 bits
]
return SecKeyCreateWithData(data, options as CFDictionary, nil)
}

@objc(MqttClient)
class MqttClient : RCTEventEmitter {
static let DEFAULT_KEY_APPLICATION_TAG = "com.github.emoto-kc-ak.react-native-mqtt-client"
Expand Down Expand Up @@ -69,12 +55,36 @@ class MqttClient : RCTEventEmitter {
self.hasListeners = false
}

func loadPrivateKeyFromKeychain(keyTag: String, block: SecKeyPerformBlock){
var query: [String: AnyObject] = [
String(kSecClass) : kSecClassKey,
String(kSecAttrApplicationTag): keyTag as AnyObject,
String(kSecReturnRef) : true as AnyObject
]

if #available(iOS 10, *) {
query[String(kSecAttrKeyType)] = kSecAttrKeyTypeECSECPrimeRandom
} else {
// Fallback on earlier versions
query[String(kSecAttrKeyType)] = kSecAttrKeyTypeEC
}

var result : AnyObject?

let status = SecItemCopyMatching(query as CFDictionary, &result)

if status == errSecSuccess {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have an error here, do we manage to reject the parent promise?

print("\(keyTag) Key existed!")
block((result as! SecKey?)!)
}
}

@objc(setIdentity:resolve:reject:)
func setIdentity(params: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void
{
let caCertPem: String = RCTConvert.nsString(params["caCertPem"])
let certPem: String = RCTConvert.nsString(params["certPem"])
let keyPem: String = RCTConvert.nsString(params["keyPem"])
let keyTag: String = RCTConvert.nsString(params["keyTag"])
let keyStoreOptions = RCTConvert.nsDictionary(params["keyStoreOptions"])
let caCertLabel: String = RCTConvert.nsString(keyStoreOptions?["caCertLabel"]) ?? Self.DEFAULT_CA_CERT_LABEL
let certLabel: String = RCTConvert.nsString(keyStoreOptions?["certLabel"]) ?? Self.DEFAULT_CERT_LABEL
Expand All @@ -87,75 +97,71 @@ class MqttClient : RCTEventEmitter {
reject("RANGE_ERROR", "invalid certificate", nil)
return
}
//guard let key = loadPrivateKey(fromPem: keyPem) else {
// reject("RANGE_ERROR", "invalid private key", nil)
// return
//}
do {
guard let key = try ECPrivateKey(key: keyPem).nativeKey else {
reject("RANGE_ERROR", "invalid private key", nil)

let block: SecKeyPerformBlock = { privateKey in
do {
// adds the private key to the keychain
let addKeyAttrs: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecValueRef as String: privateKey,
kSecAttrLabel as String: "Private key that signed an MQTT client certificate",
kSecAttrApplicationTag as String: keyApplicationTag
]
let err = SecItemAdd(addKeyAttrs as CFDictionary, nil)
guard err == errSecSuccess || err == errSecDuplicateItem else {
reject("INVALID_IDENTITY", "failed to add the private key to the keychain: \(err)", nil)
return
}
}
catch let error {
reject("RANGE_ERROR", error.localizedDescription, nil)
}
// adds the certificate to the keychain
let addCertAttrs: [String: Any] = [
kSecClass as String: kSecClassCertificate,
kSecValueRef as String: cert,
kSecAttrLabel as String: certLabel
]
var err = SecItemAdd(addCertAttrs as CFDictionary, nil)
guard err == errSecSuccess || err == errSecDuplicateItem else {
reject("INVALID_IDENTITY", "failed to add the certificate to the keychain: \(err)", nil)
return
}

// adds the private key to the keychain
let addKeyAttrs: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecValueRef as String: key,
kSecAttrLabel as String: "Private key that signed an MQTT client certificate",
kSecAttrApplicationTag as String: keyApplicationTag
// adds the root certificate to the keychain
// TODO: root certificate may be stored in other place,
// because it is public information.
let addCaCertAttrs: [String: Any] = [
kSecClass as String: kSecClassCertificate,
kSecValueRef as String: caCert,
kSecAttrLabel as String: caCertLabel
]
let err = SecItemAdd(addKeyAttrs as CFDictionary, nil)
err = SecItemAdd(addCaCertAttrs as CFDictionary, nil)
guard err == errSecSuccess || err == errSecDuplicateItem else {
reject("INVALID_IDENTITY", "failed to add the private key to the keychain: \(err)", nil)
reject("INVALID_IDENTITY", "failed to add the root certificate to the keychain: \(err)", nil)
return
}
// obtains the identity
let queryIdentityAttrs: [String: Any] = [
kSecClass as String: kSecClassIdentity,
kSecAttrApplicationTag as String: keyApplicationTag,
kSecReturnRef as String: true
]
var identity: CFTypeRef?
err = SecItemCopyMatching(queryIdentityAttrs as CFDictionary, &identity)
guard err == errSecSuccess else {
reject("INVALID_IDENTITY", "failed to query the keychain for the identity: \(err)", nil)
return
}
guard CFGetTypeID(identity) == SecIdentityGetTypeID() else {
reject("INVALID_IDENTITY", "failed to query the keychain for the identity: type ID mismatch", nil)
return
}
// remembers the identity and the CA certificate
self.certArray = [identity!, caCert] as CFArray
resolve(nil)
}
catch let error {
reject("RANGE_ERROR", error.localizedDescription, nil)
}
// adds the certificate to the keychain
let addCertAttrs: [String: Any] = [
kSecClass as String: kSecClassCertificate,
kSecValueRef as String: cert,
kSecAttrLabel as String: certLabel
]
var err = SecItemAdd(addCertAttrs as CFDictionary, nil)
guard err == errSecSuccess || err == errSecDuplicateItem else {
reject("INVALID_IDENTITY", "failed to add the certificate to the keychain: \(err)", nil)
return
}
// adds the root certificate to the keychain
// TODO: root certificate may be stored in other place,
// because it is public information.
let addCaCertAttrs: [String: Any] = [
kSecClass as String: kSecClassCertificate,
kSecValueRef as String: caCert,
kSecAttrLabel as String: caCertLabel
]
err = SecItemAdd(addCaCertAttrs as CFDictionary, nil)
guard err == errSecSuccess || err == errSecDuplicateItem else {
reject("INVALID_IDENTITY", "failed to add the root certificate to the keychain: \(err)", nil)
return
}
// obtains the identity
let queryIdentityAttrs: [String: Any] = [
kSecClass as String: kSecClassIdentity,
kSecAttrApplicationTag as String: keyApplicationTag,
kSecReturnRef as String: true
]
var identity: CFTypeRef?
err = SecItemCopyMatching(queryIdentityAttrs as CFDictionary, &identity)
guard err == errSecSuccess else {
reject("INVALID_IDENTITY", "failed to query the keychain for the identity: \(err)", nil)
return
}
guard CFGetTypeID(identity) == SecIdentityGetTypeID() else {
reject("INVALID_IDENTITY", "failed to query the keychain for the identity: type ID mismatch", nil)
return
}
// remembers the identity and the CA certificate
self.certArray = [identity!, caCert] as CFArray
resolve(nil)

self.loadPrivateKeyFromKeychain(keyTag: keyTag, block: block)
}

@objc(loadIdentity:resolve:reject:)
Expand Down