From 101eaa944d1bd17b9e0769ace84ad1424d13d121 Mon Sep 17 00:00:00 2001 From: Tiago Nascimento Date: Thu, 22 Aug 2024 14:27:54 -0300 Subject: [PATCH 1/5] Add OID4VCI functions wrapper Signed-off-by: Tiago Nascimento --- Sources/MobileSdk/OID4VCI.swift | 160 ++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 Sources/MobileSdk/OID4VCI.swift diff --git a/Sources/MobileSdk/OID4VCI.swift b/Sources/MobileSdk/OID4VCI.swift new file mode 100644 index 0000000..e2a1af6 --- /dev/null +++ b/Sources/MobileSdk/OID4VCI.swift @@ -0,0 +1,160 @@ +import Foundation + +import SpruceIDMobileSdkRs + +public class Oid4vciDefaultHttpClient : SpruceIDMobileSdkRs.HttpClient { + public func httpClient(request: HttpRequest) throws -> HttpResponse { + guard let url = URL(string: request.url) else { + throw HttpClientError.Other(error: "failed to construct URL") + } + + let session = URLSession.shared + var req = URLRequest(url: url, + cachePolicy: .useProtocolCachePolicy, + timeoutInterval: 60) + req.httpMethod = request.method + req.httpBody = request.body + req.allHTTPHeaderFields = request.headers + + let semaphore = DispatchSemaphore(value: 0) + + var data: Data? + var response: URLResponse? + var error: Error? + + let dataTask = session.dataTask(with: req) { + data = $0 + response = $1 + error = $2 + + semaphore.signal() + } + dataTask.resume() + + _ = semaphore.wait(timeout: .distantFuture) + + if let error { + throw HttpClientError.Other(error: "failed to execute request: \(error)") + } + + guard let response = response as? HTTPURLResponse else { + throw HttpClientError.Other(error: "failed to parse response") + } + + guard let data = data else { + throw HttpClientError.Other(error: "failed to parse response data") + } + + guard let statusCode = UInt16(exactly: response.statusCode) else { + throw HttpClientError.Other(error: "failed to parse response status code") + } + + let headers = try response.allHeaderFields.map({ (k, v) in + guard let k = k as? String else { + throw HttpClientError.HeaderParse + } + + guard let v = v as? String else { + throw HttpClientError.HeaderParse + } + + return (k, v) + }) + + return HttpResponse( + statusCode: statusCode, + headers: Dictionary(uniqueKeysWithValues: headers), + body: data) + } +} + +public class Oid4vci { + let httpClient: SpruceIDMobileSdkRs.HttpClient; + + public init(httpClient: SpruceIDMobileSdkRs.HttpClient) { + self.httpClient = httpClient + } + + public convenience init() { + self.init(httpClient: Oid4vciDefaultHttpClient()) + } + + public func getMetadata( + session: Oid4vciSession + ) async throws -> Oid4vciMetadata { + try await SpruceIDMobileSdkRs.oid4vciGetMetadata( + session: session) + } + + public func initiateWithOffer( + credentialOffer: String, + clientId: String, + redirectUrl: String + ) async throws -> Oid4vciSession { + try await SpruceIDMobileSdkRs.oid4vciInitiateWithOffer( + credentialOffer: credentialOffer, + clientId: clientId, + redirectUrl: redirectUrl, + httpClient: self.httpClient) + } + + public func initiate( + baseUrl: String, + clientId: String, + redirectUrl: String + ) async throws -> Oid4vciSession { + try await SpruceIDMobileSdkRs.oid4vciInitiate( + baseUrl: baseUrl, + clientId: clientId, + redirectUrl: redirectUrl, + httpClient: self.httpClient) + } + + public func exchangeToken( + session: Oid4vciSession + ) throws -> String? { + try SpruceIDMobileSdkRs.oid4vciExchangeToken( + session: session, + httpClient: self.httpClient) + } + + public func exchangeCredential( + session: Oid4vciSession, + proofsOfPossession: [String] + ) async throws -> [SpruceIDMobileSdkRs.CredentialResponse] { + try await SpruceIDMobileSdkRs.oid4vciExchangeCredential( + session: session, + proofsOfPossession: proofsOfPossession, + httpClient: self.httpClient) + } + + public func generatePopPrepare( + audience: String, + issuer: String, + nonce: String?, + vm: String, + publicJwk: String, + durationInSecs: Int64? + ) throws -> [UInt8] { + let prepare = try SpruceIDMobileSdkRs.generatePopPrepare( + audience: audience, + issuer: issuer, + nonce: nonce, + vm: vm, + publicJwk: publicJwk, + durationInSecs: durationInSecs) + + return [UInt8](prepare) + } + + public func generatePopComplete( + signingInput: [UInt8], + signature: [UInt8] + ) throws -> String { + let complete = try SpruceIDMobileSdkRs.generatePopComplete( + signingInput: Data(signingInput), + signature: Data(signature)) + + return complete + } +} From 50a5ab6e1ad512bb2795351337f54b896a4eab6a Mon Sep 17 00:00:00 2001 From: Tiago Nascimento Date: Mon, 26 Aug 2024 13:45:43 -0300 Subject: [PATCH 2/5] Remove oid4vci wrapper; Add async client interface impl Signed-off-by: Tiago Nascimento --- Sources/MobileSdk/OID4VCI.swift | 132 +++++++++++--------------------- 1 file changed, 45 insertions(+), 87 deletions(-) diff --git a/Sources/MobileSdk/OID4VCI.swift b/Sources/MobileSdk/OID4VCI.swift index e2a1af6..720dea6 100644 --- a/Sources/MobileSdk/OID4VCI.swift +++ b/Sources/MobileSdk/OID4VCI.swift @@ -2,7 +2,7 @@ import Foundation import SpruceIDMobileSdkRs -public class Oid4vciDefaultHttpClient : SpruceIDMobileSdkRs.HttpClient { +public class Oid4vciSyncHttpClient : SpruceIDMobileSdkRs.HttpClient { public func httpClient(request: HttpRequest) throws -> HttpResponse { guard let url = URL(string: request.url) else { throw HttpClientError.Other(error: "failed to construct URL") @@ -68,93 +68,51 @@ public class Oid4vciDefaultHttpClient : SpruceIDMobileSdkRs.HttpClient { } } -public class Oid4vci { - let httpClient: SpruceIDMobileSdkRs.HttpClient; - - public init(httpClient: SpruceIDMobileSdkRs.HttpClient) { - self.httpClient = httpClient - } - - public convenience init() { - self.init(httpClient: Oid4vciDefaultHttpClient()) - } - - public func getMetadata( - session: Oid4vciSession - ) async throws -> Oid4vciMetadata { - try await SpruceIDMobileSdkRs.oid4vciGetMetadata( - session: session) - } - - public func initiateWithOffer( - credentialOffer: String, - clientId: String, - redirectUrl: String - ) async throws -> Oid4vciSession { - try await SpruceIDMobileSdkRs.oid4vciInitiateWithOffer( - credentialOffer: credentialOffer, - clientId: clientId, - redirectUrl: redirectUrl, - httpClient: self.httpClient) - } - - public func initiate( - baseUrl: String, - clientId: String, - redirectUrl: String - ) async throws -> Oid4vciSession { - try await SpruceIDMobileSdkRs.oid4vciInitiate( - baseUrl: baseUrl, - clientId: clientId, - redirectUrl: redirectUrl, - httpClient: self.httpClient) - } - - public func exchangeToken( - session: Oid4vciSession - ) throws -> String? { - try SpruceIDMobileSdkRs.oid4vciExchangeToken( - session: session, - httpClient: self.httpClient) - } - - public func exchangeCredential( - session: Oid4vciSession, - proofsOfPossession: [String] - ) async throws -> [SpruceIDMobileSdkRs.CredentialResponse] { - try await SpruceIDMobileSdkRs.oid4vciExchangeCredential( - session: session, - proofsOfPossession: proofsOfPossession, - httpClient: self.httpClient) - } - - public func generatePopPrepare( - audience: String, - issuer: String, - nonce: String?, - vm: String, - publicJwk: String, - durationInSecs: Int64? - ) throws -> [UInt8] { - let prepare = try SpruceIDMobileSdkRs.generatePopPrepare( - audience: audience, - issuer: issuer, - nonce: nonce, - vm: vm, - publicJwk: publicJwk, - durationInSecs: durationInSecs) +public class Oid4vciAsyncHttpClient : SpruceIDMobileSdkRs.AsyncHttpClient { + public func httpClient(request: HttpRequest) async throws -> HttpResponse { + guard let url = URL(string: request.url) else { + throw HttpClientError.Other(error: "failed to construct URL") + } - return [UInt8](prepare) - } - - public func generatePopComplete( - signingInput: [UInt8], - signature: [UInt8] - ) throws -> String { - let complete = try SpruceIDMobileSdkRs.generatePopComplete( - signingInput: Data(signingInput), - signature: Data(signature)) + let session = URLSession.shared + var req = URLRequest(url: url, + cachePolicy: .useProtocolCachePolicy, + timeoutInterval: 60) + req.httpMethod = request.method + req.httpBody = request.body + req.allHTTPHeaderFields = request.headers + + let data: Data + let response: URLResponse + do { + (data, response) = try await session.data(for: req) + } catch { + throw HttpClientError.Other(error: "failed to execute request: \(error)") + } - return complete + guard let response = response as? HTTPURLResponse else { + throw HttpClientError.Other(error: "failed to parse response") + } + + guard let statusCode = UInt16(exactly: response.statusCode) else { + throw HttpClientError.Other(error: "failed to parse response status code") + } + + let headers = try response.allHeaderFields.map({ (k, v) in + guard let k = k as? String else { + throw HttpClientError.HeaderParse + } + + guard let v = v as? String else { + throw HttpClientError.HeaderParse + } + + return (k, v) + }) + + return HttpResponse( + statusCode: statusCode, + headers: Dictionary(uniqueKeysWithValues: headers), + body: data) } } From 4ccf9f68196e9da2e1b1101cbad7e00c58bff943 Mon Sep 17 00:00:00 2001 From: Tiago Nascimento Date: Tue, 3 Sep 2024 11:51:32 -0300 Subject: [PATCH 3/5] Update interface names; Add comments about semaphore usage Signed-off-by: Tiago Nascimento --- Sources/MobileSdk/OID4VCI.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Sources/MobileSdk/OID4VCI.swift b/Sources/MobileSdk/OID4VCI.swift index 720dea6..1fc1fbb 100644 --- a/Sources/MobileSdk/OID4VCI.swift +++ b/Sources/MobileSdk/OID4VCI.swift @@ -2,7 +2,7 @@ import Foundation import SpruceIDMobileSdkRs -public class Oid4vciSyncHttpClient : SpruceIDMobileSdkRs.HttpClient { +public class Oid4vciSyncHttpClient : SyncHttpClient { public func httpClient(request: HttpRequest) throws -> HttpResponse { guard let url = URL(string: request.url) else { throw HttpClientError.Other(error: "failed to construct URL") @@ -16,6 +16,7 @@ public class Oid4vciSyncHttpClient : SpruceIDMobileSdkRs.HttpClient { req.httpBody = request.body req.allHTTPHeaderFields = request.headers + // Semaphore used to wait for the callback to complete let semaphore = DispatchSemaphore(value: 0) var data: Data? @@ -27,10 +28,14 @@ public class Oid4vciSyncHttpClient : SpruceIDMobileSdkRs.HttpClient { response = $1 error = $2 + // Signaling from inside the callback will let the outside function + // know that `data`, `response` and `error` are ready to be read. semaphore.signal() } + // Initiate execution of the http request dataTask.resume() + // Blocking wait for the callback to signal back _ = semaphore.wait(timeout: .distantFuture) if let error { @@ -68,7 +73,7 @@ public class Oid4vciSyncHttpClient : SpruceIDMobileSdkRs.HttpClient { } } -public class Oid4vciAsyncHttpClient : SpruceIDMobileSdkRs.AsyncHttpClient { +public class Oid4vciAsyncHttpClient : AsyncHttpClient { public func httpClient(request: HttpRequest) async throws -> HttpResponse { guard let url = URL(string: request.url) else { throw HttpClientError.Other(error: "failed to construct URL") From b0f5c02ecc75fcf114b9f847977588afdc4cc763 Mon Sep 17 00:00:00 2001 From: Tiago Nascimento Date: Wed, 11 Sep 2024 12:56:48 -0300 Subject: [PATCH 4/5] Update mobile-sdk-rs version Signed-off-by: Tiago Nascimento --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 798581f..69c6613 100644 --- a/Package.swift +++ b/Package.swift @@ -14,7 +14,7 @@ let package = Package( targets: ["SpruceIDMobileSdk"]) ], dependencies: [ - .package(url: "https://github.com/spruceid/mobile-sdk-rs.git", from: "0.0.29"), + .package(url: "https://github.com/spruceid/mobile-sdk-rs.git", from: "0.0.30"), // .package(path: "../mobile-sdk-rs"), .package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0") ], From 7234d134697a84d202bcfb53e8a5b17ce64b3dd9 Mon Sep 17 00:00:00 2001 From: Tiago Nascimento Date: Wed, 11 Sep 2024 13:14:45 -0300 Subject: [PATCH 5/5] Format/lint files Signed-off-by: Tiago Nascimento --- Package.resolved | 4 +-- Sources/MobileSdk/OID4VCI.swift | 58 ++++++++++++++++----------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Package.resolved b/Package.resolved index 82f273c..54421ad 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/spruceid/mobile-sdk-rs.git", "state" : { - "revision" : "e8a1b7056989e4cb471281b464c1aeac298be97f", - "version" : "0.0.29" + "revision" : "b1e729b2adc97415019925c873b976648a2331cb", + "version" : "0.0.30" } }, { diff --git a/Sources/MobileSdk/OID4VCI.swift b/Sources/MobileSdk/OID4VCI.swift index 1fc1fbb..ef06ab0 100644 --- a/Sources/MobileSdk/OID4VCI.swift +++ b/Sources/MobileSdk/OID4VCI.swift @@ -2,12 +2,12 @@ import Foundation import SpruceIDMobileSdkRs -public class Oid4vciSyncHttpClient : SyncHttpClient { +public class Oid4vciSyncHttpClient: SyncHttpClient { public func httpClient(request: HttpRequest) throws -> HttpResponse { guard let url = URL(string: request.url) else { throw HttpClientError.Other(error: "failed to construct URL") } - + let session = URLSession.shared var req = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, @@ -15,14 +15,14 @@ public class Oid4vciSyncHttpClient : SyncHttpClient { req.httpMethod = request.method req.httpBody = request.body req.allHTTPHeaderFields = request.headers - + // Semaphore used to wait for the callback to complete let semaphore = DispatchSemaphore(value: 0) var data: Data? var response: URLResponse? var error: Error? - + let dataTask = session.dataTask(with: req) { data = $0 response = $1 @@ -37,35 +37,35 @@ public class Oid4vciSyncHttpClient : SyncHttpClient { // Blocking wait for the callback to signal back _ = semaphore.wait(timeout: .distantFuture) - + if let error { throw HttpClientError.Other(error: "failed to execute request: \(error)") } - + guard let response = response as? HTTPURLResponse else { throw HttpClientError.Other(error: "failed to parse response") } - + guard let data = data else { throw HttpClientError.Other(error: "failed to parse response data") } - + guard let statusCode = UInt16(exactly: response.statusCode) else { throw HttpClientError.Other(error: "failed to parse response status code") } - - let headers = try response.allHeaderFields.map({ (k, v) in - guard let k = k as? String else { + + let headers = try response.allHeaderFields.map({ (key, value) in + guard let key = key as? String else { throw HttpClientError.HeaderParse } - - guard let v = v as? String else { + + guard let value = value as? String else { throw HttpClientError.HeaderParse } - - return (k, v) + + return (key, value) }) - + return HttpResponse( statusCode: statusCode, headers: Dictionary(uniqueKeysWithValues: headers), @@ -73,12 +73,12 @@ public class Oid4vciSyncHttpClient : SyncHttpClient { } } -public class Oid4vciAsyncHttpClient : AsyncHttpClient { +public class Oid4vciAsyncHttpClient: AsyncHttpClient { public func httpClient(request: HttpRequest) async throws -> HttpResponse { guard let url = URL(string: request.url) else { throw HttpClientError.Other(error: "failed to construct URL") } - + let session = URLSession.shared var req = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, @@ -86,7 +86,7 @@ public class Oid4vciAsyncHttpClient : AsyncHttpClient { req.httpMethod = request.method req.httpBody = request.body req.allHTTPHeaderFields = request.headers - + let data: Data let response: URLResponse do { @@ -94,27 +94,27 @@ public class Oid4vciAsyncHttpClient : AsyncHttpClient { } catch { throw HttpClientError.Other(error: "failed to execute request: \(error)") } - + guard let response = response as? HTTPURLResponse else { throw HttpClientError.Other(error: "failed to parse response") } - + guard let statusCode = UInt16(exactly: response.statusCode) else { throw HttpClientError.Other(error: "failed to parse response status code") } - - let headers = try response.allHeaderFields.map({ (k, v) in - guard let k = k as? String else { + + let headers = try response.allHeaderFields.map({ (key, value) in + guard let key = key as? String else { throw HttpClientError.HeaderParse } - - guard let v = v as? String else { + + guard let value = value as? String else { throw HttpClientError.HeaderParse } - - return (k, v) + + return (key, value) }) - + return HttpResponse( statusCode: statusCode, headers: Dictionary(uniqueKeysWithValues: headers),