diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 355a97b..8276154 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -11,13 +11,12 @@ jobs: runs-on: macos-latest steps: - - uses: swift-actions/setup-swift@v1 + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '16.0' - name: Get swift version - run: swift --version # Swift 5.8 - - uses: actions/checkout@v3 - - name: Fix Up Private GitHub URLs - # Add personal access token to all private repo URLs - run: find . -type f \( -name 'Package.swift' -o -name 'Package.resolved' \) -exec sed -i '' "s/https:\/\/github.com\/eu-digital-identity-wallet/https:\/\/${{ secrets.USER_NAME }}:${{ secrets.USER_GITHUB_TOKEN }}@github.com\/eu-digital-identity-wallet/g" {} \; + run: swift --version + - uses: actions/checkout@v4 - name: Build run: swift build - name: Run tests diff --git a/Package.resolved b/Package.resolved index 88c5d60..56dcc00 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,12 +1,13 @@ { + "originHash" : "fc60708f41996a66ff09913bb5bc0934ff3d47779d3ec833bf24adbed0bb2ad4", "pins" : [ { "identity" : "eudi-lib-ios-iso18013-data-model", "kind" : "remoteSourceControl", "location" : "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-model.git", "state" : { - "revision" : "12314daf45d637a1afeda321d605f328d422fe8d", - "version" : "0.2.6" + "revision" : "c1b4383d6fc3387a8ed4c79177548624c4e34e3a", + "version" : "0.3.3" } }, { @@ -14,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-security.git", "state" : { - "revision" : "41a7ba66ff5d614c9ef7d50607ef0a9af7ebb577", - "version" : "0.2.1" + "revision" : "13d65a1010ee9e6219f8bccbab6eb32f67405d86", + "version" : "0.2.6" } }, { @@ -41,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "ee97538f5b81ae89698fd95938896dec5217b148", - "version" : "1.1.1" + "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", + "version" : "1.1.4" } }, { @@ -50,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto.git", "state" : { - "revision" : "bc1c29221f6dfeb0ebbfbc98eb95cd3d4967868e", - "version" : "3.4.0" + "revision" : "46072478ca365fe48370993833cb22de9b41567f", + "version" : "3.5.2" } }, { @@ -77,8 +78,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5", - "version" : "1.5.4" + "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", + "version" : "1.6.1" } }, { @@ -86,10 +87,10 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/niscy-eudiw/SwiftCBOR.git", "state" : { - "revision" : "310dbc3975a5653237fed304d88a6dd59d04dd30", - "version" : "0.5.7" + "revision" : "2c8c55273d4c4aae21bb46c2afbae79ee072eff4", + "version" : "0.6.2" } } ], - "version" : 2 + "version" : 3 } diff --git a/Package.swift b/Package.swift index 71ef8d9..6dac242 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.8 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -13,9 +13,8 @@ let package = Package( targets: ["MdocDataTransfer18013"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), - .package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-model.git", .upToNextMajor(from: "0.2.6")), - .package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-security.git", .upToNextMajor(from: "0.2.1")), + .package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-model.git", exact: "0.3.3"), + .package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-security.git", exact: "0.2.6"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. diff --git a/README.md b/README.md index b2aa300..fd43924 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ The delegate object must be an instance of a class conforming to the ``MdocOffli public protocol MdocOfflineDelegate: AnyObject { func didChangeStatus(_ newStatus: TransferStatus) func didFinishedWithError(_ error: Error) - func didReceiveRequest(_ request: [String: Any], handleAccept: @escaping (Bool) -> Void) + func didReceiveRequest(_ request: UserRequestInfo, handleAccept: @escaping (Bool) -> Void) } ``` @@ -59,14 +59,6 @@ bleServerTransfer.performDeviceEngagement() ``` The QR code payload can be obtained from the property ``qrCodePayload`` when the ``status`` has the value ``TransferStatus.qrEngagementReady``. When user (holder) acceptance is required, the app should show the request items and the reader certificate details (if reader auth is used). -The request dictionary in ``didReceiveRequest`` delegate method has the following parameters: - -|Key | Value| -|--- | ---| -|items_requested|A dictionary of mdoc-types to array of requested items| -|reader_certificate_issuer|Reader certificate issuer| -|reader_auth_validated|Reader auth signature validated| -|reader_certificate_validation_message|Validation message for the reader certificate| The BLE server will send the requested if the user accepts. In the case the client app must call the `handleAccept` callback with `true`. diff --git a/Sources/MdocDataTransfer18013/BLETransfer/MdocGATTServer.swift b/Sources/MdocDataTransfer18013/BLETransfer/MdocGATTServer.swift index 20b1103..7665f32 100644 --- a/Sources/MdocDataTransfer18013/BLETransfer/MdocGATTServer.swift +++ b/Sources/MdocDataTransfer18013/BLETransfer/MdocGATTServer.swift @@ -26,7 +26,7 @@ import MdocDataModel18013 import MdocSecurity18013 /// BLE Gatt server implementation of mdoc transfer manager -public class MdocGattServer: ObservableObject { +public class MdocGattServer: @unchecked Sendable, ObservableObject { var peripheralManager: CBPeripheralManager! var bleDelegate: Delegate! var remoteCentral: CBCentral! @@ -65,7 +65,7 @@ public class MdocGattServer: ObservableObject { } @objc(CBPeripheralManagerDelegate) - class Delegate: NSObject, CBPeripheralManagerDelegate { + class Delegate: NSObject, @preconcurrency CBPeripheralManagerDelegate { unowned var server: MdocGattServer init(server: MdocGattServer) { @@ -214,7 +214,7 @@ public class MdocGattServer: ObservableObject { self.deviceRequest = decoded.deviceRequest sessionEncryption = decoded.sessionEncryption if decoded.isValidRequest { - delegate?.didReceiveRequest(decoded.params, handleSelected: userSelected) + delegate?.didReceiveRequest(decoded.userRequestInfo, handleSelected: userSelected) } else { userSelected(false, nil) } @@ -249,7 +249,6 @@ public class MdocGattServer: ObservableObject { prepareDataToSend(bytesToSend) DispatchQueue.main.asyncAfter(deadline: .now()+0.2) { self.sendDataWithUpdates() - self.error = errorToSend } } if !b { errorToSend = MdocHelpers.makeError(code: .userRejected) } diff --git a/Sources/MdocDataTransfer18013/BLETransfer/MdocOfflineDelegate.swift b/Sources/MdocDataTransfer18013/BLETransfer/MdocOfflineDelegate.swift index e9f74c7..703bc5c 100644 --- a/Sources/MdocDataTransfer18013/BLETransfer/MdocOfflineDelegate.swift +++ b/Sources/MdocDataTransfer18013/BLETransfer/MdocOfflineDelegate.swift @@ -9,7 +9,7 @@ import MdocSecurity18013 public protocol MdocOfflineDelegate: AnyObject { func didChangeStatus(_ newStatus: TransferStatus) func didFinishedWithError(_ error: Error) - func didReceiveRequest(_ request: [String: Any], handleSelected: @escaping (Bool, RequestItems?) -> Void) + func didReceiveRequest(_ request: UserRequestInfo, handleSelected: @escaping (Bool, RequestItems?) -> Void) } diff --git a/Sources/MdocDataTransfer18013/BLETransfer/ServiceCharacteristics.swift b/Sources/MdocDataTransfer18013/BLETransfer/ServiceCharacteristics.swift index 914a56a..d3d4e7c 100644 --- a/Sources/MdocDataTransfer18013/BLETransfer/ServiceCharacteristics.swift +++ b/Sources/MdocDataTransfer18013/BLETransfer/ServiceCharacteristics.swift @@ -22,20 +22,20 @@ import SwiftCBOR /// The enum BleTransferMode defines the two roles in the communication, which can be a server or a client. /// /// The four static variables are used to signal the start and the end of the communication. This is done by sending the bytes 0x01 and 0x02 for the start and end of the communication, respectively. For the start and end of the data transmission, the bytes 0x01 and 0x00 are used. -public enum BleTransferMode { +public enum BleTransferMode: Sendable { case server case client // signals for coordination - static var START_REQUEST: [UInt8] = [0x01] - static var END_REQUEST: [UInt8] = [0x02] - static var START_DATA: [UInt8] = [0x01] - static var END_DATA: [UInt8] = [0x00] + static let START_REQUEST: [UInt8] = [0x01] + static let END_REQUEST: [UInt8] = [0x02] + static let START_DATA: [UInt8] = [0x01] + static let END_DATA: [UInt8] = [0x00] public static let BASE_UUID_SUFFIX_SERVICE = "-0000-1000-8000-00805F9B34FB" public static let QRHandover = CBOR.null } /// mdoc service characteristic definitions (mdoc is the GATT server) -public enum MdocServiceCharacteristic: String { +public enum MdocServiceCharacteristic: String, Sendable { case state = "00000001-A123-48CE-896B-4C76973373E6" case client2Server = "00000002-A123-48CE-896B-4C76973373E6" case server2Client = "00000003-A123-48CE-896B-4C76973373E6" diff --git a/Sources/MdocDataTransfer18013/Enumerations.swift b/Sources/MdocDataTransfer18013/Enumerations.swift index 27e7ea4..9b29c92 100644 --- a/Sources/MdocDataTransfer18013/Enumerations.swift +++ b/Sources/MdocDataTransfer18013/Enumerations.swift @@ -19,7 +19,7 @@ limitations under the License. import Foundation /// Transfer status enumeration -public enum TransferStatus: String { +public enum TransferStatus: String, Sendable { case initializing case initialized case qrEngagementReady @@ -33,7 +33,7 @@ public enum TransferStatus: String { } /// Possible error codes -public enum ErrorCode: Int, CustomStringConvertible { +public enum ErrorCode: Int, CustomStringConvertible, Sendable { case documents_not_provided case invalidInputDocument case invalidUrl @@ -68,7 +68,7 @@ public enum ErrorCode: Int, CustomStringConvertible { } /// String keys for the initialization dictionary -public enum InitializeKeys: String { +public enum InitializeKeys: String, Sendable { case document_json_data case document_signup_issuer_signed_data case document_signup_issuer_signed_obj @@ -79,7 +79,7 @@ public enum InitializeKeys: String { } /// String keys for the user request dictionary -public enum UserRequestKeys: String { +public enum UserRequestKeys: String, Sendable { case valid_items_requested case error_items_requested case reader_certificate_issuer diff --git a/Sources/MdocDataTransfer18013/MdocHelpers.swift b/Sources/MdocDataTransfer18013/MdocHelpers.swift index 3da08cb..f154e32 100644 --- a/Sources/MdocDataTransfer18013/MdocHelpers.swift +++ b/Sources/MdocDataTransfer18013/MdocHelpers.swift @@ -92,7 +92,7 @@ public class MdocHelpers { /// - handOver: handOver structure /// - Returns: A ``DeviceRequest`` object - public static func decodeRequestAndInformUser(deviceEngagement: DeviceEngagement?, docs: [String: IssuerSigned], iaca: [SecCertificate], requestData: Data, devicePrivateKeys: [String: CoseKeyPrivate], dauthMethod: DeviceAuthMethod, readerKeyRawData: [UInt8]?, handOver: CBOR) -> Result<(sessionEncryption: SessionEncryption, deviceRequest: DeviceRequest, params: [String: Any], isValidRequest: Bool), Error> { + public static func decodeRequestAndInformUser(deviceEngagement: DeviceEngagement?, docs: [String: IssuerSigned], iaca: [SecCertificate], requestData: Data, devicePrivateKeys: [String: CoseKeyPrivate], dauthMethod: DeviceAuthMethod, readerKeyRawData: [UInt8]?, handOver: CBOR) -> Result<(sessionEncryption: SessionEncryption, deviceRequest: DeviceRequest, userRequestInfo: UserRequestInfo, isValidRequest: Bool), Error> { do { guard let seCbor = try CBOR.decode([UInt8](requestData)) else { logger.error("Request Data is not Cbor"); return .failure(Self.makeError(code: .requestDecodeError)) } guard var se = SessionEstablishment(cbor: seCbor) else { logger.error("Request Data cannot be decoded to session establisment"); return .failure(Self.makeError(code: .requestDecodeError)) } @@ -107,16 +107,16 @@ public class MdocHelpers { guard let deviceRequest = DeviceRequest(data: requestData) else { logger.error("Decrypted data cannot be decoded"); return .failure(Self.makeError(code: .requestDecodeError)) } guard let (drTest, validRequestItems, errorRequestItems) = try Self.getDeviceResponseToSend(deviceRequest: deviceRequest, issuerSigned: docs, selectedItems: nil, sessionEncryption: sessionEncryption, eReaderKey: sessionEncryption.sessionKeys.publicKey, devicePrivateKeys: devicePrivateKeys, dauthMethod: dauthMethod) else { logger.error("Valid request items nil"); return .failure(Self.makeError(code: .requestDecodeError)) } let bInvalidReq = (drTest.documents == nil) - var params: [String: Any] = [UserRequestKeys.valid_items_requested.rawValue: validRequestItems, UserRequestKeys.error_items_requested.rawValue: errorRequestItems] + var userRequestInfo = UserRequestInfo(validItemsRequested: validRequestItems, errorItemsRequested: errorRequestItems) if let docR = deviceRequest.docRequests.first { let mdocAuth = MdocReaderAuthentication(transcript: sessionEncryption.transcript) - if let readerAuthRawCBOR = docR.readerAuthRawCBOR, let certData = docR.readerCertificate, let x509 = try? X509.Certificate(derEncoded: [UInt8](certData)), let (b,reasonFailure) = try? mdocAuth.validateReaderAuth(readerAuthCBOR: readerAuthRawCBOR, readerAuthCertificate: certData, itemsRequestRawData: docR.itemsRequestRawData!, rootCerts: iaca) { - params[UserRequestKeys.reader_certificate_issuer.rawValue] = MdocHelpers.getCN(from: x509.subject.description) - params[UserRequestKeys.reader_auth_validated.rawValue] = b - if let reasonFailure { params[UserRequestKeys.reader_certificate_validation_message.rawValue] = reasonFailure } + if let readerAuthRawCBOR = docR.readerAuthRawCBOR, case let certData = docR.readerCertificates, certData.count > 0, let x509 = try? X509.Certificate(derEncoded: [UInt8](certData.first!)), let (b,reasonFailure) = try? mdocAuth.validateReaderAuth(readerAuthCBOR: readerAuthRawCBOR, readerAuthX5c: certData, itemsRequestRawData: docR.itemsRequestRawData!, rootCerts: iaca) { + userRequestInfo.readerCertificateIssuer = MdocHelpers.getCN(from: x509.subject.description) + userRequestInfo.readerAuthValidated = b + if let reasonFailure { userRequestInfo.readerCertificateValidationMessage = reasonFailure } } } - return .success((sessionEncryption: sessionEncryption, deviceRequest: deviceRequest, params: params, isValidRequest: !bInvalidReq)) + return .success((sessionEncryption: sessionEncryption, deviceRequest: deviceRequest, userRequestInfo: userRequestInfo, isValidRequest: !bInvalidReq)) } catch { return .failure(error) } } @@ -142,7 +142,7 @@ public class MdocHelpers { var docReq: DocRequest? // if selected items is null if haveSelectedItems == false { docReq = deviceRequest?.docRequests.findDoc(name: reqDocIdOrDocType) - guard let (doc, _) = Array(issuerSigned.values).findDoc(name: reqDocIdOrDocType) else { + guard let (_, _) = Array(issuerSigned.values).findDoc(name: reqDocIdOrDocType) else { docErrors.append([reqDocIdOrDocType: UInt64(0)]) errorReqItemsDocDict[reqDocIdOrDocType] = [:] continue @@ -254,6 +254,7 @@ public class MdocHelpers { /// - Parameters: /// - vc: The view controller that will present the settings /// - action: The action to perform + @MainActor public static func checkBleAccess(_ vc: UIViewController, action: @escaping ()->Void) { switch CBManager.authorization { case .denied: @@ -275,6 +276,7 @@ public class MdocHelpers { /// - Parameters: /// - vc: The view controller that will present the settings /// - action: The action to perform + @MainActor public static func checkCameraAccess(_ vc: UIViewController, action: @escaping ()->Void) { switch AVCaptureDevice.authorizationStatus(for: .video) { case .denied: @@ -302,6 +304,7 @@ public class MdocHelpers { /// - Parameters: /// - vc: The view controller that will present the settings /// - msg: The message to show + @MainActor public static func presentSettings(_ vc: UIViewController, msg: String) { let alertController = UIAlertController(title: NSLocalizedString("error", comment: ""), message: msg, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: NSLocalizedString("cancel", comment: ""), style: .default)) @@ -316,6 +319,7 @@ public class MdocHelpers { } /// Finds the top view controller in the view hierarchy of the app. It is used to present a new view controller on top of any existing view controllers. + @MainActor public static func getTopViewController(base: UIViewController? = UIApplication.shared.windows.first { $0.isKeyWindow }?.rootViewController) -> UIViewController? { if let nav = base as? UINavigationController { return getTopViewController(base: nav.visibleViewController) diff --git a/Sources/MdocDataTransfer18013/UserRequestInfo.swift b/Sources/MdocDataTransfer18013/UserRequestInfo.swift new file mode 100644 index 0000000..4db1039 --- /dev/null +++ b/Sources/MdocDataTransfer18013/UserRequestInfo.swift @@ -0,0 +1,33 @@ +/* +Copyright (c) 2023 European Commission + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +public struct UserRequestInfo : Sendable { + public init(validItemsRequested: RequestItems, errorItemsRequested: RequestItems? = nil, readerAuthValidated: Bool? = nil, readerCertificateIssuer: String? = nil, readerCertificateValidationMessage: String? = nil, readerLegalName: String? = nil) { + self.validItemsRequested = validItemsRequested + self.errorItemsRequested = errorItemsRequested + self.readerAuthValidated = readerAuthValidated + self.readerCertificateIssuer = readerCertificateIssuer + self.readerCertificateValidationMessage = readerCertificateValidationMessage + self.readerLegalName = readerLegalName + } + + public var validItemsRequested: RequestItems + public var errorItemsRequested: RequestItems? + public var readerAuthValidated: Bool? + public var readerCertificateIssuer: String? + public var readerCertificateValidationMessage: String? + public var readerLegalName: String? +} diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..df8e75a --- /dev/null +++ b/changelog.md @@ -0,0 +1,27 @@ +- Update PGP Key link ([#32](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/32)) via [@mgiakkou](https://github.com/mgiakkou) +- Update eudi-lib-ios-iso18013-data-model dependency to version 0.2.6 ([#31](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/31)) via [@phisakel](https://github.com/phisakel) +- Support multiple documents with the same doc-type ([#30](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/30)) via [@phisakel](https://github.com/phisakel) +- Generate DeviceResponse to present (BLE, Web) from IssuerSigned cbor data ([#27](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/issues/27)) via [@phisakel](https://github.com/phisakel) +- Add parameter sessionTranscript to getDeviceResponseToSend ([#26](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/26)) via [@phisakel](https://github.com/phisakel) +- Chore: Update eudi-lib-ios-iso18013-data-model dependency to version 0.2.4 ([#25](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/25)) via [@phisakel](https://github.com/phisakel) +- Return the QR code to the device engagement in string representation ([#24](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/24)) via [@akarabashov](https://github.com/akarabashov) +- Centralization of sec workflows ([#18](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/18)) via [@christosservosNCIN](https://github.com/christosservosNCIN) +- Update dependencies and import statements ([#23](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/23)) via [@phisakel](https://github.com/phisakel) +- Update devicePrivateKey to devicePrivateKeys ([#22](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/22)) via [@phisakel](https://github.com/phisakel) +- Update README.md ([#21](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/21)) via [@vkanellopoulos](https://github.com/vkanellopoulos) +- Update dependencies in Package.resolved and Package.swift ([#20](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/20)) via [@phisakel](https://github.com/phisakel) +- Update SECURITY.md ([#19](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/19)) via [@vkanellopoulos](https://github.com/vkanellopoulos) +- Use subjectDistinguishedName for reader common name ([#17](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/17)) via [@phisakel](https://github.com/phisakel) +- Integrate changes from dependent packages ([#16](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/16)) via [@phisakel](https://github.com/phisakel) +- Code formatting changes and addition of new error codes and helper functions ([#15](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/15)) via [@phisakel](https://github.com/phisakel) +- Update README and SECURITY files ([#14](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/14)) via [@phisakel](https://github.com/phisakel) +- Develop: Fix bug with reject (no response) ([#13](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/13)) via [@phisakel](https://github.com/phisakel) +- Develop ([#12](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/12)) via [@phisakel](https://github.com/phisakel) +- Develop ([#11](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/11)) via [@phisakel](https://github.com/phisakel) +- Develop ([#9](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/9)) via [@phisakel](https://github.com/phisakel) +- Develop: Refactoring of helper functions to be used in OpenID4VP integration ([#8](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/8)) via [@phisakel](https://github.com/phisakel) +- Develop ([#7](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/7)) via [@phisakel](https://github.com/phisakel) +- Merge pull request #5 from eu-digital-identity-wallet/develop ([#6](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/6)) via [@phisakel](https://github.com/phisakel) +- Use device private key corresponding to device key in mso ([#5](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/5)) via [@phisakel](https://github.com/phisakel) +- Additional changes ([#4](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/4)) via [@phisakel](https://github.com/phisakel) +- Merge Develop BLE transfer to main ([#1](https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-data-transfer/pull/1)) via [@phisakel](https://github.com/phisakel) \ No newline at end of file