Skip to content

Commit

Permalink
Code refactoring, move general CBOR functions to MdocHelpers class
Browse files Browse the repository at this point in the history
  • Loading branch information
phisakel committed Oct 25, 2023
1 parent da3fa09 commit 2572b2c
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 474 deletions.
1 change: 0 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ let package = Package(
targets: ["MdocDataTransfer18013"]),
],
dependencies: [
//.package(path: "../MdocSecurity18013"),
.package(url: "https://github.com/eu-digital-identity-wallet/eudi-lib-ios-iso18013-security.git", branch: "develop"),
.package(url: "https://github.com/apple/swift-log.git", branch: "main"),
.package(url: "https://github.com/valpackett/SwiftCBOR.git", branch: "master"),
Expand Down
94 changes: 76 additions & 18 deletions Sources/MdocDataTransfer18013/BLETransfer/MdocGATTServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,39 +22,40 @@ import CoreBluetooth
import UIKit
#endif
import Logging
import ASN1Decoder
import MdocDataModel18013
import MdocSecurity18013

/// BLE Gatt server implementation of mdoc transfer manager
public class MdocGattServer: ObservableObject, MdocTransferManager {
public class MdocGattServer: ObservableObject {
var peripheralManager: CBPeripheralManager!
var bleDelegate: Delegate!
var remoteCentral: CBCentral!
var stateCharacteristic: CBMutableCharacteristic!
var server2ClientCharacteristic: CBMutableCharacteristic!
public var deviceEngagement: DeviceEngagement?
public var deviceRequest: DeviceRequest?
public var deviceResponseToSend: DeviceResponse?
public var validRequestItems: RequestItems?
public var errorRequestItems: RequestItems?
public var sessionEncryption: SessionEncryption?
public var docs: [DeviceResponse]!
public var iaca: [SecCertificate]!
public var devicePrivateKey: CoseKeyPrivate!
public var readerName: String?
@Published public var qrCodeImageData: Data?
public var qrCodeImageData: Data?
public weak var delegate: (any MdocOfflineDelegate)?
@Published public var advertising: Bool = false
@Published public var error: Error? = nil { willSet { handleErrorSet(newValue) }}
@Published public var status: TransferStatus = .initializing { willSet { handleStatusChange(newValue) }}
public var requireUserAccept = false
public var advertising: Bool = false
public var error: Error? = nil { willSet { handleErrorSet(newValue) }}
public var status: TransferStatus = .initializing { willSet { handleStatusChange(newValue) } }
var readBuffer = Data()
var sendBuffer = [Data]()
var numBlocks: Int = 0
var subscribeCount: Int = 0

public init(status: TransferStatus = .initializing) {
self.status = status
public init(parameters: [String: Any]) throws {
guard let (docs, devicePrivateKey, iaca) = MdocHelpers.initializeData(parameters: parameters) else {
throw Self.makeError(code: .documents_not_provided)
}
self.docs = docs; self.devicePrivateKey = devicePrivateKey; self.iaca = iaca
status = .initialized; handleStatusChange(status)
}

@objc(CBPeripheralManagerDelegate)
Expand Down Expand Up @@ -195,7 +196,6 @@ public class MdocGattServer: ObservableObject, MdocTransferManager {
peripheralManager.stopAdvertising()
deviceRequest = decodeRequestAndInformUser(requestData: readBuffer, devicePrivateKey: devicePrivateKey, readerKeyRawData: nil, handOver: BleTransferMode.QRHandover, handler: userSelected)
if deviceRequest == nil { error = Self.makeError(code: .requestDecodeError) }
if requireUserAccept == false /*|| _isDebugAssertConfiguration() */ { userSelected(true, nil) }
}
else if newValue == .initialized {
bleDelegate = Delegate(server: self)
Expand All @@ -218,12 +218,12 @@ public class MdocGattServer: ObservableObject, MdocTransferManager {
if !b { error = Self.makeError(code: .userRejected) }
if let items {
do {
try getDeviceResponseToSend(deviceRequest!, selectedItems: items, eReaderKey: sessionEncryption!.sessionKeys.publicKey, devicePrivateKey: devicePrivateKey)
guard let (docToSend, _, _) = try MdocHelpers.getDeviceResponseToSend(deviceRequest: deviceRequest!, deviceResponses: docs, selectedItems: items, sessionEncryption: sessionEncryption, eReaderKey: sessionEncryption!.sessionKeys.publicKey, devicePrivateKey: devicePrivateKey) else { error = Self.makeError(code: .noDocumentToReturn); return }
guard let bytes = getSessionDataToSend(docToSend: docToSend) else { error = Self.makeError(code: .noDocumentToReturn); return }
prepareDataToSend(bytes)
DispatchQueue.main.asyncAfter(deadline: .now()+0.2) { self.sendDataWithUpdates() }
} catch { self.error = error }
}
guard let bytes = getSessionDataToSend(deviceRequest!, eReaderKey: sessionEncryption!.sessionKeys.publicKey) else { error = Self.makeError(code: .noDocumentToReturn); return }
prepareDataToSend(bytes)
DispatchQueue.main.asyncAfter(deadline: .now()+0.2) { self.sendDataWithUpdates() }
}

func handleErrorSet(_ newValue: Error?) {
Expand All @@ -235,12 +235,12 @@ public class MdocGattServer: ObservableObject, MdocTransferManager {

func prepareDataToSend(_ msg: Data) {
let mbs = min(511, remoteCentral.maximumUpdateValueLength-1)
numBlocks = Helpers.CountNumBlocks(dataLength: msg.count, maxBlockSize: mbs)
numBlocks = MdocHelpers.CountNumBlocks(dataLength: msg.count, maxBlockSize: mbs)
logger.info("Sending response of total bytes \(msg.count) in \(numBlocks) blocks and block size: \(mbs)")
sendBuffer.removeAll()
// send blocks
for i in 0..<numBlocks {
let (block,bEnd) = Helpers.CreateBlockCommand(data: msg, blockId: i, maxBlockSize: mbs)
let (block,bEnd) = MdocHelpers.CreateBlockCommand(data: msg, blockId: i, maxBlockSize: mbs)
var blockWithHeader = Data()
blockWithHeader.append(contentsOf: !bEnd ? BleTransferMode.START_DATA : BleTransferMode.END_DATA)
// send actual data after header
Expand All @@ -253,10 +253,68 @@ public class MdocGattServer: ObservableObject, MdocTransferManager {
guard !isPreview && !isInErrorState else { return }
guard sendBuffer.count > 0 else {
status = .responseSent; logger.info("Finished sending BLE data")
stop()
return
}
let b = peripheralManager.updateValue(sendBuffer.first!, for: server2ClientCharacteristic, onSubscribedCentrals: [remoteCentral])
if b, sendBuffer.count > 0 { sendBuffer.removeFirst(); sendDataWithUpdates() }
}

public func getSessionDataToSend(docToSend: DeviceResponse) -> Data? {
do {
guard var sessionEncryption else { logger.error("Session Encryption not initialized"); return nil }
if docToSend.documents == nil { logger.error("Could not create documents to send") }
let cborToSend = docToSend.toCBOR(options: CBOROptions())
let clearBytesToSend = cborToSend.encode()
guard let cipherData = try sessionEncryption.encrypt(clearBytesToSend) else { return nil }
let sd = SessionData(cipher_data: status == .error ? nil : cipherData, status: status == .error ? 0 : 20)
return Data(sd.encode(options: CBOROptions()))
} catch { self.error = error}
return nil
}

/// Decrypt the contents of a data object and return a ``DeviceRequest`` object if the data represents a valid device request. If the data does not represent a valid device request, the function returns nil.
/// - Parameters:
/// - requestData: Request data passed to the mdoc holder
/// - handler: Handler to call with the accept/reject flag
/// - devicePrivateKey: Device private key
/// - readerKeyRawData: reader key cbor data (if reader engagement is used)
/// - Returns: A ``DeviceRequest`` object

public func decodeRequestAndInformUser(requestData: Data, devicePrivateKey: CoseKeyPrivate, readerKeyRawData: [UInt8]?, handOver: CBOR, handler: @escaping (Bool, RequestItems?) -> Void) -> DeviceRequest? {
do {
guard let seCbor = try CBOR.decode([UInt8](requestData)) else { logger.error("Request Data is not Cbor"); return nil }
guard var se = SessionEstablishment(cbor: seCbor) else { logger.error("Request Data cannot be decoded to session establisment"); return nil }
if se.eReaderKeyRawData == nil, let readerKeyRawData { se.eReaderKeyRawData = readerKeyRawData }
guard se.eReaderKey != nil else { logger.error("Reader key not available"); return nil }
let requestCipherData = se.data
guard let deviceEngagement else { logger.error("Device Engagement not initialized"); return nil }
// init session-encryption object from session establish message and device engagement, decrypt data
sessionEncryption = SessionEncryption(se: se, de: deviceEngagement, handOver: handOver)
guard var sessionEncryption else { logger.error("Session Encryption not initialized"); return nil }
guard let requestData = try sessionEncryption.decrypt(requestCipherData) else { logger.error("Request data cannot be decrypted"); return nil }
guard let deviceRequest = DeviceRequest(data: requestData) else { logger.error("Decrypted data cannot be decoded"); return nil }
guard let (_, validRequestItems, errorRequestItems) = try MdocHelpers.getDeviceResponseToSend(deviceRequest: deviceRequest, deviceResponses: docs, selectedItems: nil, sessionEncryption: sessionEncryption, eReaderKey: sessionEncryption.sessionKeys.publicKey, devicePrivateKey: devicePrivateKey) else { logger.error("Valid request items nil"); return nil }
var params: [String: Any] = [UserRequestKeys.valid_items_requested.rawValue: validRequestItems, UserRequestKeys.error_items_requested.rawValue: 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? X509Certificate(der: certData), let issName = x509.issuerDistinguishedName, let (b,reasonFailure) = try? mdocAuth.validateReaderAuth(readerAuthCBOR: readerAuthRawCBOR, readerAuthCertificate: certData, itemsRequestRawData: docR.itemsRequestRawData!, rootCerts: iaca) {
params[UserRequestKeys.reader_certificate_issuer.rawValue] = issName
params[UserRequestKeys.reader_auth_validated.rawValue] = b
if let reasonFailure { params[UserRequestKeys.reader_certificate_validation_message.rawValue] = reasonFailure }
}
}
self.deviceRequest = deviceRequest
delegate?.didReceiveRequest(params, handleSelected: handler)
return deviceRequest
} catch { self.error = error}
return nil
}

public static func makeError(code: ErrorCode, str: String? = nil) -> NSError {
let errorMessage = str ?? NSLocalizedString(code.description, comment: code.description)
logger.error(Logger.Message(unicodeScalarLiteral: errorMessage))
return NSError(domain: "\(MdocGattServer.self)", code: code.rawValue, userInfo: [NSLocalizedDescriptionKey: errorMessage])
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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
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,
Expand Down Expand Up @@ -60,3 +60,20 @@ public enum ErrorCode: Int, CustomStringConvertible {
}
}
}

/// String keys for the initialization dictionary
public enum InitializeKeys: String {
case document_json_data
case document_signup_response_data
case device_private_key
case trusted_certificates
}

/// String keys for the user request dictionary
public enum UserRequestKeys: String {
case valid_items_requested
case error_items_requested
case reader_certificate_issuer
case reader_auth_validated
case reader_certificate_validation_message
}
145 changes: 0 additions & 145 deletions Sources/MdocDataTransfer18013/Helpers.swift

This file was deleted.

Loading

0 comments on commit 2572b2c

Please sign in to comment.