Skip to content

Commit 7a8ed05

Browse files
committed
support external jwts and authentication
1 parent f407620 commit 7a8ed05

File tree

5 files changed

+184
-53
lines changed

5 files changed

+184
-53
lines changed

lib/Ziti.swift

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ import CZitiPrivate
330330
}
331331

332332
// Store certificates
333-
let certs = dropFirst("pem:", resp.id.cert)
333+
let certs = dropFirst("pem:", resp.id.cert!)
334334
_ = zkc.deleteCertificate(silent: true)
335335
// storeCertificate only stores the first (leaf) certificate in the pem. that's ok - the full chain of certs is stored in the .zid
336336
// file. only the leaf/pubkey needs to be in the keychain.
@@ -348,7 +348,28 @@ import CZitiPrivate
348348
}
349349

350350
let zid = ZitiIdentity(id: subj, ztAPIs: resp.ztAPIs, certs: certs, ca: ca)
351-
log.info("Enrolled id:\(subj) with controller: \(zid.ztAPI), cert: \(resp.id.cert)", function:"enroll()")
351+
log.info("Enrolled id:\(subj) with controller: \(zid.ztAPI), cert: \(resp.id.cert!)", function:"enroll()")
352+
353+
enrollCallback(zid, nil)
354+
}
355+
}
356+
357+
@objc public static func enroll(controllerURL:String, _ enrollCallback: @escaping EnrollmentCallback) {
358+
ZitiEnroller.enroll(url: controllerURL) { resp, _, zErr in
359+
guard let resp = resp, zErr == nil else {
360+
log.error(String(describing: zErr), function:"enroll()")
361+
enrollCallback(nil, zErr)
362+
return
363+
}
364+
365+
// Grab CA if specified
366+
var ca = resp.id.ca
367+
if let idCa = resp.id.ca {
368+
ca = dropFirst("pem:", idCa)
369+
}
370+
371+
let zid = ZitiIdentity(id: controllerURL, ztAPIs: resp.ztAPIs, ca: ca)
372+
log.info("Enrolled id:\(zid.id) with controller: \(zid.ztAPI)", function:"enroll()")
352373

353374
enrollCallback(zid, nil)
354375
}
@@ -383,24 +404,35 @@ import CZitiPrivate
383404
/// - See also:
384405
/// - `runAsync(_:)`
385406
@objc public func run(_ postureChecks:ZitiPostureChecks?, _ initCallback: @escaping InitCallback) {
386-
// Get certificate
407+
// Get certificates. There won't be any if this identity uses external authentication.
387408
let zkc = ZitiKeychain(tag: id.id)
388-
guard let certPEM = id.getCertificates(zkc) else {
389-
let errStr = "unable to retrieve certificates"
390-
log.error(errStr)
391-
initCallback(ZitiError(errStr))
392-
return
409+
var certPEMPtr:UnsafeMutablePointer<Int8>? = nil
410+
let certPEM = id.getCertificates(zkc)
411+
if (certPEM != nil) {
412+
certPEMPtr = UnsafeMutablePointer<Int8>.allocate(capacity: certPEM!.count + 1)
413+
certPEMPtr!.initialize(from: certPEM!, count: certPEM!.count + 1)
414+
} else {
415+
// is there any way to know at this point that ext auth is needed? maybe check ext auth signers? add "extAuthEnabled" field?
416+
//let errStr = "unable to retrieve certificates"
417+
//log.error(errStr)
418+
//initCallback(ZitiError(errStr))
419+
//return
393420
}
394421

395422
// Get private key
396-
guard let privKey = zkc.getPrivateKey() else {
397-
let errStr = "unable to retrieve private key from keychain"
398-
log.error(errStr)
399-
initCallback(ZitiError(errStr))
400-
return
423+
var privKeyPEMPtr: UnsafeMutablePointer<Int8>? = nil
424+
if let privKey = zkc.getPrivateKey() {
425+
let privKeyPEM = zkc.getKeyPEM(privKey)
426+
privKeyPEMPtr = UnsafeMutablePointer<Int8>.allocate(capacity: privKeyPEM.count + 1)
427+
privKeyPEMPtr!.initialize(from: privKeyPEM, count: privKeyPEM.count + 1)
428+
} else {
429+
// check if we should have a key (same deal as with cert above)
430+
//let errStr = "unable to retrieve private key from keychain"
431+
//log.error(errStr)
432+
//initCallback(ZitiError(errStr))
433+
//return
401434
}
402-
let privKeyPEM = zkc.getKeyPEM(privKey)
403-
435+
404436
// init ziti
405437
self.initCallback = initCallback
406438
self.postureChecks = postureChecks
@@ -416,12 +448,6 @@ import CZitiPrivate
416448
let ctrlPtr = UnsafeMutablePointer<Int8>.allocate(capacity: id.ztAPI.count + 1)
417449
ctrlPtr.initialize(from: id.ztAPI, count: id.ztAPI.count + 1)
418450

419-
let certPEMPtr = UnsafeMutablePointer<Int8>.allocate(capacity: certPEM.count + 1)
420-
certPEMPtr.initialize(from: certPEM, count: certPEM.count + 1)
421-
422-
let privKeyPEMPtr = UnsafeMutablePointer<Int8>.allocate(capacity: privKeyPEM.count + 1)
423-
privKeyPEMPtr.initialize(from: privKeyPEM, count: privKeyPEM.count + 1)
424-
425451
var caPEMPtr:UnsafeMutablePointer<Int8>? = nil // todo empty string
426452
if (id.ca != nil) {
427453
caPEMPtr = UnsafeMutablePointer<Int8>.allocate(capacity: id.ca!.count + 1)
@@ -451,8 +477,8 @@ import CZitiPrivate
451477
var zitiStatus = ziti_context_init(&self.ztx, &zitiCfg)
452478

453479
ctrlPtr.deallocate()
454-
certPEMPtr.deallocate()
455-
privKeyPEMPtr.deallocate()
480+
if let certPEMPtr = certPEMPtr { certPEMPtr.deallocate() }
481+
if let privKeyPEMPtr = privKeyPEMPtr { privKeyPEMPtr.deallocate() }
456482
caPEMPtr?.deallocate()
457483

458484
withUnsafeMutablePointer(to: &ctrls) { ctrlListPtr in
@@ -826,7 +852,18 @@ import CZitiPrivate
826852
mfaAuthResponseStatusCallback = cb
827853
ziti_mfa_auth(ztx, code.cString(using: .utf8), Ziti.onMfaAuthResponseStatus, self.toVoidPtr())
828854
}
829-
855+
856+
/// Type definition of callback method for external authentication
857+
public typealias ExtAuthCallback = (_ ziti:Ziti, _ url:String, _ ctx:UnsafeMutableRawPointer?) -> Void
858+
private var extAuthStatusCallback:ExtAuthCallback?
859+
public func extAuth(_ provider:String, _ cb: @escaping ExtAuthCallback) {
860+
extAuthStatusCallback = cb
861+
let cProvider = provider.cString(using: .utf8)
862+
let cProviderCpy = copyString(cProvider) // grr
863+
ziti_use_ext_jwt_signer(ztx, cProviderCpy)
864+
ziti_ext_auth(ztx, Ziti.onExtAuthStatus, self.toVoidPtr())
865+
freeString(cProviderCpy)
866+
}
830867
// MARK: - Static C Callbacks
831868

832869
static private let onMfaAuthResponseStatus:ziti_mfa_cb = { ztx, status, ctx in
@@ -1126,6 +1163,14 @@ import CZitiPrivate
11261163
}
11271164
}
11281165

1166+
static private let onExtAuthStatus:ziti_ext_auth_launch_cb = { ztx, url, ctx in
1167+
guard let mySelf = zitiUnretained(Ziti.self, ctx) else {
1168+
log.wtf("invalid context")
1169+
return
1170+
}
1171+
mySelf.extAuthStatusCallback?(mySelf, String(cString:url!), ctx)
1172+
}
1173+
11291174
// MARK: - Helpers
11301175

11311176
private static func dropFirst(_ drop:String, _ str:String) -> String {

lib/ZitiEnroller.swift

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,16 @@ import CZitiPrivate
3737
@objc public class EnrollmentResponse : NSObject, Codable {
3838
/// Identity portion of successful enrollment attempt
3939
public class Identity : Codable {
40-
///locally generated private key used for generating CSR as part of enrollmen
40+
///locally generated private key used for generating CSR as part of enrollment
4141
public var key:String?,
4242

43-
/// signed certificate created as part of CSR process
44-
cert:String,
43+
/// signed certificate created as part of CSR process. will be nill for url enrollments
44+
cert:String?,
4545

4646
/// root certificates for trusting the Ziti Controller
4747
ca:String?
4848

49-
init(cert:String, key:String?, ca:String?) {
49+
init(cert:String?, key:String?, ca:String?) {
5050
self.cert = cert
5151
self.key = key
5252
self.ca = ca
@@ -92,10 +92,12 @@ import CZitiPrivate
9292
var enrollmentCallback:EnrollmentCallback?
9393
var jwtFile_c:UnsafeMutablePointer<Int8>?
9494
var privatePem_c:UnsafeMutablePointer<Int8>?
95+
var url_c:UnsafeMutablePointer<Int8>?
9596

9697
deinit {
9798
jwtFile_c?.deallocate()
9899
privatePem_c?.deallocate()
100+
url_c?.deallocate()
99101
}
100102
}
101103

@@ -158,16 +160,48 @@ import CZitiPrivate
158160
}
159161
}
160162

163+
static func enroll(withLoop loop:UnsafeMutablePointer<uv_loop_t>?,
164+
controllerURL:String,
165+
cb:@escaping EnrollmentCallback) {
166+
let enrollData = UnsafeMutablePointer<EnrollmentRequestData>.allocate(capacity: 1)
167+
enrollData.initialize(to: EnrollmentRequestData())
168+
enrollData.pointee.enrollmentCallback = cb
169+
enrollData.pointee.url_c = UnsafeMutablePointer<Int8>.allocate(capacity: controllerURL.count + 1)
170+
enrollData.pointee.url_c!.initialize(from: controllerURL.cString(using: .utf8)!, count: controllerURL.count + 1)
171+
172+
var enroll_opts = ziti_enroll_opts(url: enrollData.pointee.url_c, token: nil, key: nil,
173+
cert: nil, name: nil, use_keychain: false)
174+
let status = ziti_enroll(&enroll_opts, loop, ZitiEnroller.on_enroll, enrollData)
175+
guard status == ZITI_OK else {
176+
let errStr = String(cString: ziti_errorstr(status))
177+
log.error(errStr)
178+
cb(nil, nil, ZitiError(errStr, errorCode: Int(status)))
179+
return
180+
}
181+
}
182+
183+
@objc public static func enroll(url:String, cb:@escaping EnrollmentCallback) {
184+
self.enroll(withLoop: ZitiEnroller.loop, controllerURL: url, cb: cb)
185+
186+
let runStatus = uv_run(ZitiEnroller.loop, UV_RUN_DEFAULT)
187+
guard runStatus == 0 else {
188+
let errStr = String(cString: uv_strerror(runStatus))
189+
log.error(errStr)
190+
cb(nil, nil, ZitiError(errStr, errorCode: Int(runStatus)))
191+
return
192+
}
193+
}
194+
161195
/**
162-
* Extract the sub (id) field from HWT file
196+
* Extract the sub (id) field from JWT file
163197
*/
164198
@objc public func getSubj() -> String? {
165199
return getClaims()?.sub
166200
}
167201

168202
/**
169203
* Retreive the claims in the JWT file
170-
* - returns: The claims in the file or nil if enable to decode
204+
* - returns: The claims in the file or nil if unable to decode
171205
*/
172206
public func getClaims() -> ZitiClaims? {
173207
do {
@@ -226,13 +260,14 @@ import CZitiPrivate
226260
log.wtf("invalid config", function:"on_enroll()")
227261
return
228262
}
229-
guard let cert = String(cString: zc.id.cert, encoding: .utf8) else {
230-
let errStr = "Unable to convert cert to string"
231-
log.error(errStr, function:"on_enroll()")
232-
let ze = ZitiError(errStr, errorCode: -1)
233-
enrollData.pointee.enrollmentCallback?(nil, nil, ze)
234-
return
235-
}
263+
// todo only do this if not using url.
264+
//guard let cert = String(cString: zc.id.cert, encoding: .utf8) else {
265+
// let errStr = "Unable to convert cert to string"
266+
// log.error(errStr, function:"on_enroll()")
267+
// let ze = ZitiError(errStr, errorCode: -1)
268+
// enrollData.pointee.enrollmentCallback?(nil, nil, ze)
269+
// return
270+
//}
236271

237272
var controllers:[String] = []
238273
var ctrlList = zc.controllers
@@ -255,9 +290,14 @@ import CZitiPrivate
255290
return
256291
}
257292

258-
let id = EnrollmentResponse.Identity(cert: cert,
259-
key: String(cString: zc.id.key, encoding: .utf8),
260-
ca: String(cString: zc.id.ca, encoding: .utf8))
293+
let cert:String? = zc.id.cert != nil ? String(cString: zc.id.cert, encoding: .utf8)! : nil
294+
let key:String? = zc.id.key != nil ? String(cString: zc.id.key, encoding: .utf8)! : nil
295+
let ca:String? = zc.id.ca != nil ? String(cString: zc.id.ca, encoding: .utf8)! : nil
296+
297+
let id = EnrollmentResponse.Identity(cert: cert, key: key, ca: ca)
298+
//let id = EnrollmentResponse.Identity(cert: cert,
299+
// key: String(cString: zc.id.key, encoding: .utf8),
300+
// ca: String(cString: zc.id.ca, encoding: .utf8))
261301
let enrollResp = EnrollmentResponse(ztAPIs: controllers, id: id)
262302
enrollData.pointee.enrollmentCallback?(enrollResp, enrollData.pointee.subj, nil)
263303
}

lib/ZitiEvent.swift

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ import CZitiPrivate
167167

168168
/// Enumeration of possible authentication actions
169169
@objc public enum AuthAction : UInt32 {
170+
/// Authentication flow is stuck, most likely due to (external auth) configuration
171+
case CannotContinue
172+
170173
/// Request for MFA code
171174
case PromptTotp
172175

@@ -180,21 +183,24 @@ import CZitiPrivate
180183

181184
init(_ action:ziti_auth_action) {
182185
switch action {
183-
case ziti_auth_prompt_totp: self = .PromptTotp
184-
case ziti_auth_prompt_pin: self = .PromptPin
185-
case ziti_auth_login_external: self = .LoginExternal
186+
case ziti_auth_cannot_continue: self = .CannotContinue
187+
case ziti_auth_prompt_totp: self = .PromptTotp
188+
case ziti_auth_prompt_pin: self = .PromptPin
189+
case ziti_auth_login_external: self = .LoginExternal
190+
case ziti_auth_select_external: self = .LoginExternal
186191
default: self = .Unknown
187192
}
188193
}
189194

190195
/// Returns string representation of AuthAction
191196
public var debug: String {
192197
switch self {
193-
case .PromptTotp: return ".PromptTotp"
194-
case .PromptPin: return ".PromptPin"
195-
case .LoginExternal: return ".LoginExternal"
196-
case .Unknown: return ".Unknown"
197-
@unknown default: return "unknown \(self.rawValue)"
198+
case .CannotContinue: return ".CannotContinue"
199+
case .PromptTotp: return ".PromptTotp"
200+
case .PromptPin: return ".PromptPin"
201+
case .LoginExternal: return ".LoginExternal"
202+
case .Unknown: return ".Unknown"
203+
@unknown default: return "unknown \(self.rawValue)"
198204
}
199205
}
200206
}

lib/ZitiTunnel.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,9 @@ public class ZitiTunnel : NSObject, ZitiUnretained {
361361
}
362362
// pass event to application
363363
mySelf.tunnelProvider?.tunnelEventCallback(event)
364+
case TunnelEvents.ExtJWTEvent.rawValue:
365+
var cExtJWTEvent = UnsafeRawPointer(cEvent).bindMemory(to: ext_signer_event.self, capacity: 1)
366+
mySelf.tunnelProvider?.tunnelEventCallback(ZitiTunnelExtJWTEvent(ziti, cExtJWTEvent))
364367
default:
365368
log.warn("Unrecognized event type \(cEvent.pointee.event_type.rawValue)")
366369
return

lib/ZitiTunnelEvent.swift

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ import CZitiPrivate
2727
self.ziti = ziti
2828
}
2929

30-
func toStr(_ cStr:UnsafePointer<CChar>?) -> String {
31-
if let cStr = cStr { return String(cString: cStr) }
32-
return ""
33-
}
34-
3530
/// Provide a debug description of the event
3631
/// - returns: String containing the debug description
3732
public override var debugDescription: String {
@@ -263,3 +258,45 @@ import CZitiPrivate
263258
" cert: \(certPEM)"
264259
}
265260
}
261+
262+
@objc public class JWTProvider : NSObject, Codable {
263+
public var name:String = ""
264+
public var issuer:String = ""
265+
266+
init(provider_c: UnsafeMutablePointer<jwt_provider>) {
267+
self.name = toStr(provider_c.pointee.name)
268+
self.issuer = toStr(provider_c.pointee.issuer)
269+
}
270+
}
271+
272+
/// Class encapsulating Ziti Tunnel SDK C External JWT Event
273+
@objc public class ZitiTunnelExtJWTEvent : ZitiTunnelEvent {
274+
275+
/// Authentication status
276+
public var status:String = ""
277+
278+
/// JWT providers
279+
public var providers:[JWTProvider] = []
280+
281+
init(_ ziti:Ziti, _ evt:UnsafePointer<ext_signer_event>) {
282+
super.init(ziti)
283+
status = toStr(evt.pointee.status)
284+
var providerList = evt.pointee.providers
285+
withUnsafeMutablePointer(to: &providerList) { providerListPtr in
286+
var i = model_list_iterator(providerListPtr)
287+
while i != nil {
288+
let providerPtr = model_list_it_element(i)
289+
if let provider_c = UnsafeMutablePointer<jwt_provider>(OpaquePointer(providerPtr)) {
290+
let provider = JWTProvider(provider_c: provider_c)
291+
providers.append(provider)
292+
}
293+
i = model_list_it_next(i)
294+
}
295+
}
296+
}
297+
}
298+
299+
func toStr(_ cStr:UnsafePointer<CChar>?) -> String {
300+
if let cStr = cStr { return String(cString: cStr) }
301+
return ""
302+
}

0 commit comments

Comments
 (0)