Skip to content

Commit

Permalink
Enhance RequestItem and UserRequestInfo structures to include display…
Browse files Browse the repository at this point in the history
… names and document type mappings
  • Loading branch information
phisakel committed Dec 19, 2024
1 parent 02938f1 commit 4cb0958
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class MdocGattServer: @unchecked Sendable, ObservableObject {
public var deviceRequest: DeviceRequest?
public var sessionEncryption: SessionEncryption?
public var docs: [String: IssuerSigned]!
public var docDisplayNames: [String: [String: [String: String]]?]!
public var iaca: [SecCertificate]!
public var devicePrivateKeys: [String: CoseKeyPrivate]!
public var dauthMethod: DeviceAuthMethod
Expand All @@ -55,6 +56,7 @@ public class MdocGattServer: @unchecked Sendable, ObservableObject {
public init(parameters: InitializeTransferData) throws {
let objs = parameters.toInitializeTransferInfo()
self.docs = objs.documentObjects.mapValues { IssuerSigned(data: $0.bytes) }.compactMapValues { $0 }
docDisplayNames = objs.docDisplayNames
self.devicePrivateKeys = objs.privateKeyObjects
self.iaca = objs.iaca
self.dauthMethod = objs.deviceAuthMethod
Expand Down Expand Up @@ -217,7 +219,7 @@ public class MdocGattServer: @unchecked Sendable, ObservableObject {
delegate?.didChangeStatus(newValue)
if newValue == .requestReceived {
peripheralManager.stopAdvertising()
let decodedRes = await MdocHelpers.decodeRequestAndInformUser(deviceEngagement: deviceEngagement, docs: docs, iaca: iaca, requestData: readBuffer, devicePrivateKeys: devicePrivateKeys, dauthMethod: dauthMethod, unlockData: unlockData, readerKeyRawData: nil, handOver: BleTransferMode.QRHandover)
let decodedRes = await MdocHelpers.decodeRequestAndInformUser(deviceEngagement: deviceEngagement, docs: docs, docDisplayNames: docDisplayNames, iaca: iaca, requestData: readBuffer, devicePrivateKeys: devicePrivateKeys, dauthMethod: dauthMethod, unlockData: unlockData, readerKeyRawData: nil, handOver: BleTransferMode.QRHandover)
switch decodedRes {
case .success(let decoded):
self.deviceRequest = decoded.deviceRequest
Expand Down Expand Up @@ -261,7 +263,7 @@ public class MdocGattServer: @unchecked Sendable, ObservableObject {
if let items {
do {
let docTypeReq = deviceRequest?.docRequests.first?.itemsRequest.docType ?? ""
guard let (drToSend, _, _) = try await MdocHelpers.getDeviceResponseToSend(deviceRequest: deviceRequest!, issuerSigned: docs, selectedItems: items, sessionEncryption: sessionEncryption, eReaderKey: sessionEncryption!.sessionKeys.publicKey, devicePrivateKeys: devicePrivateKeys, dauthMethod: dauthMethod, unlockData: unlockData) else {
guard let (drToSend, _, _) = try await MdocHelpers.getDeviceResponseToSend(deviceRequest: deviceRequest!, issuerSigned: docs, docDisplayNames: docDisplayNames, selectedItems: items, sessionEncryption: sessionEncryption, eReaderKey: sessionEncryption!.sessionKeys.publicKey, devicePrivateKeys: devicePrivateKeys, dauthMethod: dauthMethod, unlockData: unlockData) else {
errorToSend = MdocHelpers.getErrorNoDocuments(docTypeReq); return
}
guard let dts = drToSend.documents, !dts.isEmpty else { errorToSend = MdocHelpers.getErrorNoDocuments(docTypeReq); return }
Expand Down
17 changes: 11 additions & 6 deletions Sources/MdocDataTransfer18013/InitializeTransferInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,29 @@ import MdocSecurity18013

public struct InitializeTransferData: Sendable {

public init(dataFormats: [String : String], documentData: [String : Data], privateKeyData: [String : String], trustedCertificates: [Data], deviceAuthMethod: String, docTypesToΙds: [String : String]) {
public init(dataFormats: [String : String], documentData: [String : Data], docDisplayNames: [String: [String: [String: String]]?], privateKeyData: [String : String], trustedCertificates: [Data], deviceAuthMethod: String, idsToDocTypes: [String : String]) {
self.dataFormats = dataFormats
self.documentData = documentData
self.docDisplayNames = docDisplayNames
self.privateKeyData = privateKeyData
self.trustedCertificates = trustedCertificates
self.deviceAuthMethod = deviceAuthMethod
self.docTypesToΙds = docTypesToΙds
self.idsToDocTypes = idsToDocTypes
}

public let dataFormats: [String: String]
/// doc-id to document data
public let documentData: [String: Data]
// doc-id to doc.fields display names
public let docDisplayNames: [String: [String: [String: String]]?]
/// doc-id to private key secure area name
public let privateKeyData: [String: String]
/// trusted certificates
public let trustedCertificates: [Data]
/// device auth method
public let deviceAuthMethod: String
// document id to document type map
public let docTypesToΙds: [String: String]
public let idsToDocTypes: [String: String]

public func toInitializeTransferInfo() -> InitializeTransferInfo {
// filter data and private keys by format
Expand All @@ -32,7 +35,7 @@ public struct InitializeTransferData: Sendable {
let privateKeyObjects = Dictionary.init(uniqueKeysWithValues: privateKeyData.map { k,v in (k, CoseKeyPrivate(privateKeyId: k, secureArea: SecureAreaRegistry.shared.get(name: v))) })
let iaca = trustedCertificates.map { SecCertificateCreateWithData(nil, $0 as CFData)! }
let deviceAuthMethod = DeviceAuthMethod(rawValue: deviceAuthMethod) ?? .deviceMac
return InitializeTransferInfo(dataFormats: dataFormats, documentObjects: documentObjects, privateKeyObjects: privateKeyObjects, iaca: iaca, deviceAuthMethod: deviceAuthMethod, docTypesToΙds: docTypesToΙds)
return InitializeTransferInfo(dataFormats: dataFormats, documentObjects: documentObjects, docDisplayNames: docDisplayNames, privateKeyObjects: privateKeyObjects, iaca: iaca, deviceAuthMethod: deviceAuthMethod, idsToDocTypes: idsToDocTypes)
}
}

Expand All @@ -41,12 +44,14 @@ public struct InitializeTransferInfo {
public let dataFormats: [String: DocDataFormat]
/// doc-id to document objects
public let documentObjects: [String: Data]
// doc-id to doc.fields display names
public let docDisplayNames: [String: [String: [String: String]]?]
/// doc-id to private key objects
public let privateKeyObjects: [String: CoseKeyPrivate]
/// trusted certificates
public let iaca: [SecCertificate]
/// device auth method
public let deviceAuthMethod: DeviceAuthMethod
// document id to document type map
public let docTypesToΙds: [String: String]
}
public let idsToDocTypes: [String: String]
}
17 changes: 10 additions & 7 deletions Sources/MdocDataTransfer18013/MdocHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import SwiftCBOR
import Logging
import X509

public typealias RequestItems = [String: [String: [RequestItem]]]
public typealias RequestItems = [String: [NameSpace: [RequestItem]]]

/// Helper methods
public class MdocHelpers {
Expand Down Expand Up @@ -64,7 +64,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, unlockData: [String: Data], readerKeyRawData: [UInt8]?, handOver: CBOR) async -> Result<(sessionEncryption: SessionEncryption, deviceRequest: DeviceRequest, userRequestInfo: UserRequestInfo, isValidRequest: Bool), Error> {
public static func decodeRequestAndInformUser(deviceEngagement: DeviceEngagement?, docs: [String: IssuerSigned], docDisplayNames: [String: [String: [String: String]]?], iaca: [SecCertificate], requestData: Data, devicePrivateKeys: [String: CoseKeyPrivate], dauthMethod: DeviceAuthMethod, unlockData: [String: Data], readerKeyRawData: [UInt8]?, handOver: CBOR) async -> 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)) }
Expand All @@ -77,9 +77,9 @@ public class MdocHelpers {
guard var sessionEncryption else { logger.error("Session Encryption not initialized"); return .failure(Self.makeError(code: .sessionEncryptionNotInitialized)) }
guard let requestData = try await sessionEncryption.decrypt(requestCipherData) else { logger.error("Request data cannot be decrypted"); return .failure(Self.makeError(code: .requestDecodeError)) }
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 await Self.getDeviceResponseToSend(deviceRequest: deviceRequest, issuerSigned: docs, selectedItems: nil, sessionEncryption: sessionEncryption, eReaderKey: sessionEncryption.sessionKeys.publicKey, devicePrivateKeys: devicePrivateKeys, dauthMethod: dauthMethod, unlockData: unlockData) else { logger.error("Valid request items nil"); return .failure(Self.makeError(code: .requestDecodeError)) }
guard let (drTest, validRequestItems, errorRequestItems) = try await Self.getDeviceResponseToSend(deviceRequest: deviceRequest, issuerSigned: docs, docDisplayNames: docDisplayNames, selectedItems: nil, sessionEncryption: sessionEncryption, eReaderKey: sessionEncryption.sessionKeys.publicKey, devicePrivateKeys: devicePrivateKeys, dauthMethod: dauthMethod, unlockData: unlockData) else { logger.error("Valid request items nil"); return .failure(Self.makeError(code: .requestDecodeError)) }
let bInvalidReq = (drTest.documents == nil)
var userRequestInfo = UserRequestInfo(validItemsRequested: validRequestItems, errorItemsRequested: errorRequestItems)
var userRequestInfo = UserRequestInfo(docDataFormats: docs.mapValues { _ in .cbor }, validItemsRequested: validRequestItems, errorItemsRequested: errorRequestItems)
if let docR = deviceRequest.docRequests.first {
let mdocAuth = MdocReaderAuthentication(transcript: sessionEncryption.transcript)
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) {
Expand All @@ -103,27 +103,30 @@ public class MdocHelpers {
/// - sessionTranscript: Session Transcript object
/// - dauthMethod: Mdoc Authentication method
/// - Returns: (Device response object, valid requested items, error request items) tuple
public static func getDeviceResponseToSend(deviceRequest: DeviceRequest?, issuerSigned: [String: IssuerSigned], selectedItems: RequestItems? = nil, sessionEncryption: SessionEncryption? = nil, eReaderKey: CoseKey? = nil, devicePrivateKeys: [String: CoseKeyPrivate], sessionTranscript: SessionTranscript? = nil, dauthMethod: DeviceAuthMethod, unlockData: [String: Data]) async throws -> (deviceResponse: DeviceResponse, validRequestItems: RequestItems, errorRequestItems: RequestItems)? {
public static func getDeviceResponseToSend(deviceRequest: DeviceRequest?, issuerSigned: [String: IssuerSigned], docDisplayNames: [String: [String: [String: String]]?], selectedItems: RequestItems? = nil, sessionEncryption: SessionEncryption? = nil, eReaderKey: CoseKey? = nil, devicePrivateKeys: [String: CoseKeyPrivate], sessionTranscript: SessionTranscript? = nil, dauthMethod: DeviceAuthMethod, unlockData: [String: Data]) async throws -> (deviceResponse: DeviceResponse, validRequestItems: RequestItems, errorRequestItems: RequestItems)? {
var docFiltered = [Document](); var docErrors = [[DocType: UInt64]]()
var validReqItemsDocDict = RequestItems(); var errorReqItemsDocDict = RequestItems()
guard deviceRequest != nil || selectedItems != nil else { fatalError("Invalid call") }
let haveSelectedItems = selectedItems != nil
// doc.id's (if have selected items), otherwise doc.types
let reqDocIdsOrDocTypes = if haveSelectedItems { Array(selectedItems!.keys) } else { deviceRequest!.docRequests.map(\.itemsRequest.docType) }
var docId: String?
for reqDocIdOrDocType in reqDocIdsOrDocTypes {
var docReq: DocRequest? // if selected items is null
if haveSelectedItems == false {
docReq = deviceRequest?.docRequests.findDoc(name: reqDocIdOrDocType)
guard let (_, _) = Array(issuerSigned.values).findDoc(name: reqDocIdOrDocType) else {
guard let pair = issuerSigned.first(where: { $1.issuerAuth.mso.docType == reqDocIdOrDocType}) else {
docErrors.append([reqDocIdOrDocType: UInt64(0)])
errorReqItemsDocDict[reqDocIdOrDocType] = [:]
continue
}
docId = pair.key
} else {
guard issuerSigned[reqDocIdOrDocType] != nil else { continue }
}
let devicePrivateKey = devicePrivateKeys[reqDocIdOrDocType] // used only if doc.id
let doc = if haveSelectedItems { issuerSigned[reqDocIdOrDocType]! } else { Array(issuerSigned.values).findDoc(name: reqDocIdOrDocType)!.0 }
let displayNames = if haveSelectedItems { docDisplayNames[reqDocIdOrDocType] } else { docDisplayNames[docId!] }
// Document's data must be in CBOR bytes that has the IssuerSigned structure according to ISO 23220-4
// Currently, the library does not support IssuerSigned structure without the nameSpaces field.
guard let issuerNs = doc.issuerNameSpaces else { logger.error("Document does not contain issuer namespaces"); return nil }
Expand All @@ -148,7 +151,7 @@ public class MdocHelpers {
}
if itemsToAdd.count > 0 {
nsItemsToAdd[reqNamespace] = itemsToAdd
validReqItemsNsDict[reqNamespace] = itemsToAdd.map { RequestItem(elementIdentifier: $0.elementIdentifier, intentToRetain: docReq?.itemsRequest.requestNameSpaces.nameSpaces[reqNamespace]?.dataElements[$0.elementIdentifier], isOptional: nil) }
validReqItemsNsDict[reqNamespace] = itemsToAdd.map { RequestItem(elementIdentifier: $0.elementIdentifier, displayName: displayNames??[reqNamespace]?[$0.elementIdentifier], intentToRetain: docReq?.itemsRequest.requestNameSpaces.nameSpaces[reqNamespace]?.dataElements[$0.elementIdentifier], isOptional: nil) }
}
let errorItemsSet = itemsReqSet.subtracting(itemsSet)
if errorItemsSet.count > 0 {
Expand Down
18 changes: 16 additions & 2 deletions Sources/MdocDataTransfer18013/RequestItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,38 @@ limitations under the License.
import Foundation

/// A structure representing a request item for data transfer.
public struct RequestItem: Equatable, Sendable {
public init(elementIdentifier: String, intentToRetain: Bool? = nil, isOptional: Bool? = nil) {
public struct RequestItem: Equatable, Hashable, Sendable {
public init(elementIdentifier: String, displayName: String?, intentToRetain: Bool? = nil, isOptional: Bool? = nil) {
self.elementIdentifier = elementIdentifier
self.displayName = displayName
self.intentToRetain = intentToRetain
self.isOptional = isOptional
}

public init(elementIdentifier: String) {
self.elementIdentifier = elementIdentifier
self.displayName = nil
self.intentToRetain = nil
self.isOptional = nil
}

/// A unique identifier for the data element.
/// This identifier is used to distinguish between different elements within the data transfer process.
public let elementIdentifier: String
// display name
public let displayName: String?
/// Indicates whether the mdoc verifier intends to retain the received data element
public let intentToRetain: Bool?
/// Indicates whether the data element is optional.
/// false or nil value of the property indicates the field is required
public let isOptional: Bool?

//implementation of Equatable and Hashable
public static func == (lhs: RequestItem, rhs: RequestItem) -> Bool {
return lhs.elementIdentifier == rhs.elementIdentifier
}

public func hash(into hasher: inout Hasher) {
hasher.combine(elementIdentifier)
}
}
14 changes: 12 additions & 2 deletions Sources/MdocDataTransfer18013/UserRequestInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,31 @@ 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.
*/
import Foundation
import MdocDataModel18013

public struct UserRequestInfo : Sendable {
public init(validItemsRequested: RequestItems, errorItemsRequested: RequestItems? = nil, readerAuthValidated: Bool? = nil, readerCertificateIssuer: String? = nil, readerCertificateValidationMessage: String? = nil, readerLegalName: String? = nil) {
public init(docDataFormats: [String: DocDataFormat], validItemsRequested: RequestItems, errorItemsRequested: RequestItems? = nil, readerAuthValidated: Bool? = nil, readerCertificateIssuer: String? = nil, readerCertificateValidationMessage: String? = nil, readerLegalName: String? = nil) {
self.docDataFormats = docDataFormats
self.validItemsRequested = validItemsRequested
self.errorItemsRequested = errorItemsRequested
self.readerAuthValidated = readerAuthValidated
self.readerCertificateIssuer = readerCertificateIssuer
self.readerCertificateValidationMessage = readerCertificateValidationMessage
self.readerLegalName = readerLegalName
}

/// docType to format map
public var docDataFormats: [String: DocDataFormat]
/// valid items requested
public var validItemsRequested: RequestItems
/// error items requested
public var errorItemsRequested: RequestItems?
/// reader authentication from verifer validated
public var readerAuthValidated: Bool?
/// reader certificate issuer
public var readerCertificateIssuer: String?
/// reader certificate validation message
public var readerCertificateValidationMessage: String?
/// reader legal name
public var readerLegalName: String?
}

0 comments on commit 4cb0958

Please sign in to comment.