diff --git a/CZiti.xcodeproj/project.pbxproj b/CZiti.xcodeproj/project.pbxproj index 6469d7b6..adc69908 100644 --- a/CZiti.xcodeproj/project.pbxproj +++ b/CZiti.xcodeproj/project.pbxproj @@ -1057,6 +1057,7 @@ OTHER_SWIFT_FLAGS = ""; SWIFT_INCLUDE_PATHS = "$(SRCROOT)/lib/ProjectModule $(PROJECT_DIR)/deps/ziti-tunnel-sdk-c/build-$(PLATFORM_NAME)-$(CURRENT_ARCH)/_deps/tlsuv-src/include $(PROJECT_DIR)/deps/ziti-tunnel-sdk-c/build-$(PLATFORM_NAME)-$(CURRENT_ARCH)/_deps/tlsuv-src/deps/uv_link_t/include $(PROJECT_DIR)/deps/ziti-tunnel-sdk-c/build-$(PLATFORM_NAME)-$(CURRENT_ARCH)/_deps/ziti-sdk-c-src/includes $(PROJECT_DIR)/deps/ziti-tunnel-sdk-c/build-$(PLATFORM_NAME)-$(CURRENT_ARCH)/vcpkg_installed/$(VCPKG_ARCH)-$(VCPKG_OS)/include $(PROJECT_DIR)/deps/ziti-tunnel-sdk-c/include $(PROJECT_DIR)/deps/ziti-tunnel-sdk-c/lib/ziti-tunnel/include $(PROJECT_DIR)/deps/ziti-tunnel-sdk-c/lib/ziti-tunnel-cbs/include $(PROJECT_DIR)/deps/ziti-tunnel-sdk-c/lib/ziti-tunnel/lwip $(PROJECT_DIR)/deps/ziti-tunnel-sdk-c/build-$(PLATFORM_NAME)-$(CURRENT_ARCH)/_deps/lwip-src/src/include $(PROJECT_DIR)/deps/ziti-tunnel-sdk-c/build-$(PLATFORM_NAME)-$(CURRENT_ARCH)/_deps/lwip-contrib-src/ports/unix/port/include"; SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; diff --git a/deps/ziti-tunnel-sdk-c b/deps/ziti-tunnel-sdk-c index 60c6ed96..e3132315 160000 --- a/deps/ziti-tunnel-sdk-c +++ b/deps/ziti-tunnel-sdk-c @@ -1 +1 @@ -Subproject commit 60c6ed969cccef4ebc62d6a7758cfb7f4c08eaeb +Subproject commit e31323155065843610bdce7249bf5b8d27930ac0 diff --git a/lib/Ziti.swift b/lib/Ziti.swift index bc874acc..1cf7203a 100644 --- a/lib/Ziti.swift +++ b/lib/Ziti.swift @@ -329,11 +329,12 @@ import CZitiPrivate return } - // Store certificate - let cert = dropFirst("pem:", resp.id.cert) + // Store certificates + let certs = dropFirst("pem:", resp.id.cert) _ = zkc.deleteCertificate(silent: true) - let (err, cns) = zkc.storeCertificates(cert) - guard err == nil else { + // storeCertificate only stores the first (leaf) certificate in the pem. that's ok - the full chain of certs is stored in the .zid + // file. only the leaf/pubkey needs to be in the keychain. + guard zkc.storeCertificate(fromPem: certs) == nil else { let errStr = "Unable to store certificate\n" log.error(errStr, function:"enroll()") enrollCallback(nil, ZitiError(errStr)) @@ -346,8 +347,8 @@ import CZitiPrivate ca = dropFirst("pem:", idCa) } - let zid = ZitiIdentity(id: subj, ztAPIs: resp.ztAPIs, certCNs: cns, ca: ca) - log.info("Enrolled id:\(subj) with controller: \(zid.ztAPI) with cns: \(zid.getCertCNs())", function:"enroll()") + let zid = ZitiIdentity(id: subj, ztAPIs: resp.ztAPIs, certs: certs, ca: ca) + log.info("Enrolled id:\(subj) with controller: \(zid.ztAPI)", function:"enroll()") enrollCallback(zid, nil) } @@ -384,14 +385,12 @@ import CZitiPrivate @objc public func run(_ postureChecks:ZitiPostureChecks?, _ initCallback: @escaping InitCallback) { // Get certificate let zkc = ZitiKeychain(tag: id.id) - let (maybeCerts, zErr) = zkc.getCertificates(id.getCertCNs()) - guard let certs = maybeCerts, zErr == nil else { - let errStr = zErr != nil ? zErr!.localizedDescription : "unable to retrieve certificates from keychain" + guard let certPEM = id.getCertificates(zkc) else { + let errStr = "unable to retrieve certificates" log.error(errStr) - initCallback(zErr ?? ZitiError(errStr)) + initCallback(ZitiError(errStr)) return } - let certPEM = zkc.convertToPEM("CERTIFICATE", ders: certs) // Get private key guard let privKey = zkc.getPrivateKey() else { @@ -418,6 +417,7 @@ import CZitiPrivate model_list_append(&ctrls, c.cstring) } + // ziti_context_init copies strings (strdup) for its own use, so it's ok to use references to swift strings here. var zitiCfg = ziti_config( controller_url: id.ztAPI.cstring, controllers: ctrls, @@ -445,7 +445,7 @@ import CZitiPrivate pq_domain_cb: postureChecks?.domainQuery != nil ? Ziti.onDomainQuery : nil, app_ctx: self.toVoidPtr(), events: ZitiContextEvent.rawValue | ZitiRouterEvent.rawValue | ZitiServiceEvent.rawValue | ZitiAuthEvent.rawValue | ZitiConfigEvent.rawValue, - event_cb: Ziti.onEvent, cert_extension_window: 0) + event_cb: Ziti.onEvent, cert_extension_window: 30) zitiStatus = ziti_context_set_options(self.ztx, &zitiOpts) guard zitiStatus == Ziti.ZITI_OK else { @@ -901,8 +901,20 @@ import CZitiPrivate // update ourself if event.type == ZitiEvent.EventType.ConfigEvent { - mySelf.id.ztAPI = event.configEvent!.controllerUrl - mySelf.id.ca = event.configEvent!.caBundle + let cfgEvent = event.configEvent! + if !cfgEvent.controllerUrl.isEmpty { mySelf.id.ztAPI = cfgEvent.controllerUrl } + if !cfgEvent.controllers.isEmpty { mySelf.id.ztAPIs = cfgEvent.controllers } + if !cfgEvent.cert.isEmpty { + mySelf.id.certs = cfgEvent.cert + let zkc = ZitiKeychain(tag: mySelf.id.id) + _ = zkc.deleteCertificate() + // store the first/leaf certificate in the keychain so it can be used in a key pair. + let zErr = zkc.storeCertificate(fromPem: event.configEvent!.cert) + if zErr != nil { + log.warn("failed to store certificate: \(zErr!.localizedDescription)", function:"onEvent()") + } + } + if !cfgEvent.caBundle.isEmpty { mySelf.id.ca = cfgEvent.caBundle } } mySelf.eventCallbacksLock.lock() @@ -1125,7 +1137,14 @@ func scan< } extension String { + // use only when scope of c string matches scope of swift string. var cstring: UnsafePointer { (self as NSString).cString(using: String.Encoding.utf8.rawValue)! } + // use when c string needs to outlive swift string. caller must deallocate() the returned buffer when no longer needed. + var allocatedcString: UnsafeMutablePointer { + let buf = UnsafeMutablePointer.allocate(capacity: self.count + 1) + buf.initialize(from: self, count: self.count + 1) + return buf + } } diff --git a/lib/ZitiEvent.swift b/lib/ZitiEvent.swift index 43f6e095..963de12a 100644 --- a/lib/ZitiEvent.swift +++ b/lib/ZitiEvent.swift @@ -271,8 +271,8 @@ import CZitiPrivate @objc public let controllerUrl:String @objc public let controllers:[String] @objc public let cfgSource:String - - @objc public let caBundle:String // todo encapsulate ziti_id_cfg_s? + @objc public let cert:String + @objc public let caBundle:String init( _ cEvent:ziti_config_event) { var str = "" @@ -295,6 +295,12 @@ import CZitiPrivate caStr = String(cString: cStr) } caBundle = caStr + + var certStr = "" + if let cStr = cEvent.config.pointee.id.cert { + certStr = String(cString: cStr) + } + cert = certStr var ctrlsArray:[String] = [] var ctrlList = cEvent.config.pointee.controllers @@ -383,6 +389,7 @@ import CZitiPrivate str += " controllers: \(e.controllers))\n" str += " cfgSource: \(e.cfgSource)\n" str += " caBundle: \(e.caBundle)\n" + str += " cert: \(e.cert)\n" } return str } diff --git a/lib/ZitiIdentity.swift b/lib/ZitiIdentity.swift index f2ebd2d5..de8db430 100644 --- a/lib/ZitiIdentity.swift +++ b/lib/ZitiIdentity.swift @@ -34,12 +34,12 @@ import Foundation /// Initially the `sub` field from the one-time enrollment JWT. Used by `Ziti` to store and retrieve identity-related items in the Keychain @objc public let id:String - /// Certificate CNs - /// - /// Common names for the certificates in this identities certificate chain. The first element will always match `id`. - /// Used by `Ziti` to retrieve certificates from the Keychain. - @objc public var certCNs:[String]? - + /// Common names for the certificates in this identities certificate chain. + /// This is only set when the identity is decoded from an older representation. When `certs` is not set, this value can be used to + /// retrieve the identity's certificates from the Keychain. + @available(*, deprecated, message: "store certificates in `certs` instead") + @objc private var certCNs:[String]? + /// scheme, host, and port used to communicate with Ziti controller @objc public var ztAPI:String @@ -51,6 +51,9 @@ import Foundation /// Note that this name is unknown until a session with Ziti is active @objc public var name:String? + /// Certificates (PEM) + @objc public var certs:String? + /// CA pool verified as part of enrollment that can be used to establish trust with of the Ziti controller @objc public var ca:String? @@ -65,23 +68,29 @@ import Foundation /// - certCNs: common names of certififcates /// - name: name currently configured for this identity /// - ca: CA pool that can be used to verify trust of the Ziti controller - @objc public init(id:String, ztAPIs:[String], certCNs:[String]?=nil, name:String?=nil, ca:String?=nil) { + @objc public init(id:String, ztAPIs:[String], name:String?=nil, certs:String?=nil, ca:String?=nil) { self.id = id - self.certCNs = [ id ] - if let certCNs { - for cn in certCNs { - if cn == id { continue } - self.certCNs?.append(cn) - } - } self.ztAPI = ztAPIs.first ?? "" self.ztAPIs = ztAPIs self.name = name + self.certs = certs self.ca = ca } - @objc public func getCertCNs() -> [String] { - return certCNs ?? [self.id] + @objc public func getCertificates(_ zkc:ZitiKeychain?) -> String? { + if let certs = self.certs { + log.debug("returning certificates from identity") + return certs + } + // get certs using certCNs and the keychain if available. + let certCNs = self.certCNs ?? [self.id] + if let ders = zkc?.getCertificates(certCNs) { + log.debug("returning certfificates from keychain CNs=\(certCNs)") + self.certs = zkc?.convertToPEM("CERTIFICATE", ders: ders) + self.certCNs = nil + return self.certs + } + return nil } /// Save this object to a JSON file diff --git a/lib/ZitiKeychain.swift b/lib/ZitiKeychain.swift index 22e172a8..c81cf98c 100644 --- a/lib/ZitiKeychain.swift +++ b/lib/ZitiKeychain.swift @@ -257,8 +257,8 @@ public class ZitiKeychain : NSObject { return sceStatus } #endif - - @available(*, deprecated, message: "This function only stores the first certificate in `pem`. Use storeCertificates(pem:String) to store all certificates.") + + /// This function only stores the first certificate in `pem`. use storeCertificates(pem:String) if you need to store all certificates in the keychain. func storeCertificate(fromPem pem:String) -> ZitiError? { let (_, zErr) = storeCertificate(fromDer: convertToDER(pem)) if zErr != nil { @@ -267,7 +267,6 @@ public class ZitiKeychain : NSObject { return zErr } - @available(*, deprecated) func storeCertificate(fromDer der:Data) -> (SecCertificate?, ZitiError?) { guard let certificate = SecCertificateCreateWithData(nil, der as CFData) else { let errStr = "Unable to create certificate from data for \(tag)" @@ -316,17 +315,17 @@ public class ZitiKeychain : NSObject { return (certData, nil) } - func getCertificates(_ certCNs:[String]) -> ([Data]?, ZitiError?) { + func getCertificates(_ certCNs:[String]) -> [Data]? { var certs:[Data] = [] for cn in certCNs { let (cert, err) = getCertificate(cn) guard let cert = cert, err == nil else { - return (nil, err) + return nil } certs.append(cert) } - return (certs, nil) + return certs } func deleteCertificate(silent:Bool=false) -> ZitiError? { @@ -362,12 +361,13 @@ public class ZitiKeychain : NSObject { return nil } - func convertToPEM(_ type:String, ders:[Data]) -> String { + func convertToPEM(_ type:String, ders:[Data]) -> String? { var pem = "" ders.forEach { der in - pem.append(convertToPEM("CERTIFICATE", der: der)) + pem.append(convertToPEM(type, der: der)) } - return pem + if pem.count > 0 { return pem } + return nil } func convertToPEM(_ type:String, der:Data) -> String { diff --git a/lib/ZitiTunnel.swift b/lib/ZitiTunnel.swift index 64597e64..226176eb 100644 --- a/lib/ZitiTunnel.swift +++ b/lib/ZitiTunnel.swift @@ -349,6 +349,16 @@ public class ZitiTunnel : NSObject, ZitiUnretained { if !event.caBundle.isEmpty { ziti.id.ca = event.caBundle } + if !event.certPEM.isEmpty { + ziti.id.certs = event.certPEM + // store the first/leaf certificate in the keychain so it can be used in a key pair. + let zkc = ZitiKeychain(tag: ziti.id.id) + _ = zkc.deleteCertificate() + let zErr = zkc.storeCertificate(fromPem: event.certPEM) + if zErr != nil { + log.warn("failed to store certificates: \(zErr!.localizedDescription)", function:"onEventCallback()") + } + } // pass event to application mySelf.tunnelProvider?.tunnelEventCallback(event) default: diff --git a/lib/ZitiTunnelEvent.swift b/lib/ZitiTunnelEvent.swift index 70808838..1b72cc5b 100644 --- a/lib/ZitiTunnelEvent.swift +++ b/lib/ZitiTunnelEvent.swift @@ -220,6 +220,9 @@ import CZitiPrivate /// CA bundle public var caBundle:String = "" + /// Certificte PEM (possibly multiple certificates) + public var certPEM:String = "" + init(_ ziti:Ziti, _ evt:UnsafePointer) { super.init(ziti) var ziti_cfg_ptr:UnsafeMutablePointer? @@ -239,6 +242,7 @@ import CZitiPrivate } } self.caBundle = toStr(ziti_cfg_ptr?.pointee.id.ca) + self.certPEM = toStr(ziti_cfg_ptr?.pointee.id.cert) } /// Debug description @@ -247,6 +251,7 @@ import CZitiPrivate return super.debugDescription + "\n" + " controller_url: \(controllerUrl)\n" + " contrlollers: \(controllers)\n" + - " caBundle: \(caBundle)" + " caBundle: \(caBundle)\n" + + " cert: \(certPEM)" } }