Le code qui fait tourner l'application sur les téléphones est maintenant divulgué pour s'assurer qu'il n'y a pas de vulnérabilités.
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | package au.gov.health.covidsafe import android.app.Application import android.content.Context import android.os.Build import com.atlassian.mobilekit.module.feedback.FeedbackModule import au.gov.health.covidsafe.logging.CentralLog import au.gov.health.covidsafe.services.BluetoothMonitoringService import au.gov.health.covidsafe.streetpass.CentralDevice import au.gov.health.covidsafe.streetpass.PeripheralDevice class TracerApp : Application() { override fun onCreate() { super.onCreate() AppContext = applicationContext FeedbackModule.init(this) } companion object { private const val TAG = "TracerApp" const val ORG = BuildConfig.ORG const val protocolVersion = BuildConfig.PROTOCOL_VERSION lateinit var AppContext: Context fun thisDeviceMsg(): String { BluetoothMonitoringService.broadcastMessage?.let { CentralLog.i(TAG, "Retrieved BM for storage: $it") return it } CentralLog.e(TAG, "No local Broadcast Message") return BluetoothMonitoringService.broadcastMessage!! } fun asPeripheralDevice(): PeripheralDevice { return PeripheralDevice(Build.MODEL, "SELF") } fun asCentralDevice(): CentralDevice { return CentralDevice(Build.MODEL, "SELF") } } } |
Version Android de l’application de suivi développée en langage Kotlin
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | // // CentralController.swift // CovidSafe // // Copyright © 2020 Australian Government. All rights reserved. // import Foundation import CoreData import CoreBluetooth import UIKit struct CentralWriteData: Codable { var modelC: String // phone model of central var rssi: Double var txPower: Double? var msg: String // tempID var org: String var v: Int } class CentralController: NSObject { enum CentralError: Error { case centralAlreadyOn case centralAlreadyOff } var centralDidUpdateStateCallback: ((CBManagerState) -> Void)? var characteristicDidReadValue: ((EncounterRecord) -> Void)? private let restoreIdentifierKey = "com.joelkek.tracer.central" private var central: CBCentralManager? private var recoveredPeripherals: [CBPeripheral] = [] private var queue: DispatchQueue // This dict is to keep track of discovered android devices, so that i do not connect to the same android device multiple times within the same BluetraceConfig.CentralScanInterval private var discoveredAndroidPeriManufacturerToUUIDMap = [Data: UUID]() // This dict has 2 purpose // 1. To store all the EncounterRecord, because the RSSI and TxPower is gotten at the didDiscoverPeripheral delegate, but the characterstic value is gotten at didUpdateValueForCharacteristic delegate // 2. Use to check for duplicated iphones peripheral being discovered, so that i dont connect to the same iphone again in the same scan window private var scannedPeripherals = [UUID: (peripheral: CBPeripheral, encounter: EncounterRecord)]() // stores the peripherals encountered within one scan interval var timerForScanning: Timer? public init(queue: DispatchQueue) { self.queue = queue super.init() } func turnOn() { DLog("CC requested to be turnOn") guard central == nil else { return } central = CBCentralManager(delegate: self, queue: self.queue, options: [CBCentralManagerOptionRestoreIdentifierKey: restoreIdentifierKey, CBCentralManagerOptionShowPowerAlertKey: 1]) } func turnOff() { DLog("CC turnOff") guard central != nil else { return } central?.stopScan() central = nil } public func getState() -> CBManagerState? { return central?.state } public func getDiscoveredPeripheralsCount() -> Int { let COUNT_NOT_FOUND = -1 guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return COUNT_NOT_FOUND } let managedContext = appDelegate.persistentContainer.viewContext let fetchRequest = NSFetchRequest<Encounter>(entityName: "Encounter") let sortByDate = NSSortDescriptor(key: "timestamp", ascending: false) fetchRequest.sortDescriptors = [sortByDate] let fetchedResultsController = NSFetchedResultsController<Encounter>(fetchRequest: fetchRequest, managedObjectContext: managedContext, sectionNameKeyPath: nil, cacheName: nil) do { try fetchedResultsController.performFetch() return fetchedResultsController.fetchedObjects?.count ?? COUNT_NOT_FOUND } catch let error as NSError { print("Could not perform fetch. \(error), \(error.userInfo)") return COUNT_NOT_FOUND } } } extension CentralController: CBCentralManagerDelegate { func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) { } func centralManagerDidUpdateState(_ central: CBCentralManager) { centralDidUpdateStateCallback?(central.state) switch central.state { case .poweredOn: DispatchQueue.main.async { self.timerForScanning = Timer.scheduledTimer(withTimeInterval: TimeInterval(BluetraceConfig.CentralScanInterval), repeats: true) { _ in DLog("CC Starting a scan") Encounter.timestamp(for: .scanningStarted) // for all peripherals that are not disconnected, disconnect them self.scannedPeripherals.forEach { (scannedPeri) in central.cancelPeripheralConnection(scannedPeri.value.peripheral) } // clear all peripherals, such that a new scan window can take place self.scannedPeripherals = [UUID: (CBPeripheral, EncounterRecord)]() self.discoveredAndroidPeriManufacturerToUUIDMap = [Data: UUID]() central.scanForPeripherals(withServices: [BluetraceConfig.BluetoothServiceID]) DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(BluetraceConfig.CentralScanDuration)) { DLog("CC Stopping a scan") central.stopScan() Encounter.timestamp(for: .scanningStopped) } } self.timerForScanning?.fire() } default: timerForScanning?.invalidate() } } func handlePeripheralOfUncertainStatus(_ peripheral: CBPeripheral) { // If not connected to Peripheral, attempt connection and exit if peripheral.state != .connected { DLog("CC handlePeripheralOfUncertainStatus not connected") central?.connect(peripheral) return } // If don't know about Peripheral's services, discover services and exit if peripheral.services == nil { DLog("CC handlePeripheralOfUncertainStatus unknown services") peripheral.discoverServices([BluetraceConfig.BluetoothServiceID]) return } // If Peripheral's services don't contain targetID, disconnect and remove, then exit. // If it does contain targetID, discover characteristics for service guard let service = peripheral.services?.first(where: { $0.uuid == BluetraceConfig.BluetoothServiceID }) else { DLog("CC handlePeripheralOfUncertainStatus no matching Services") central?.cancelPeripheralConnection(peripheral) return } DLog("CC handlePeripheralOfUncertainStatus discoverCharacteristics") peripheral.discoverCharacteristics([BluetraceConfig.BluetoothServiceID], for: service) // If Peripheral's service's characteristics don't contain targetID, disconnect and remove, then exit. // If it does contain targetID, read value for characteristic guard let characteristic = service.characteristics?.first(where: { $0.uuid == BluetraceConfig.BluetoothServiceID}) else { DLog("CC handlePeripheralOfUncertainStatus no matching Characteristics") central?.cancelPeripheralConnection(peripheral) return } DLog("CC handlePeripheralOfUncertainStatus readValue") peripheral.readValue(for: characteristic) return } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { let debugLogs = ["CentralState": BluetraceUtils.centralStateToString(central.state), "peripheral": peripheral, "advertisments": advertisementData as AnyObject] as AnyObject DLog("\(debugLogs)") // iphones will "mask" the peripheral's identifier for android devices, resulting in the same android device being discovered multiple times with different peripheral identifier. Hence Android is using use CBAdvertisementDataServiceDataKey data for identifying an android pheripheral if let manuData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data { let androidIdentifierData = manuData.subdata(in: 2..<manuData.count) if discoveredAndroidPeriManufacturerToUUIDMap.keys.contains(androidIdentifierData) { DLog("Android Peripheral \(peripheral) has been discovered already in this window, will not attempt to connect to it again") return } else { peripheral.delegate = self discoveredAndroidPeriManufacturerToUUIDMap.updateValue(peripheral.identifier, forKey: androidIdentifierData) scannedPeripherals.updateValue((peripheral, EncounterRecord(rssi: RSSI.doubleValue, txPower: advertisementData[CBAdvertisementDataTxPowerLevelKey] as? Double)), forKey: peripheral.identifier) central.connect(peripheral) } } else { // Means not android device, i will check if the peripheral.identifier exist in the scannedPeripherals DLog("CBAdvertisementDataManufacturerDataKey Data not found. Peripheral is likely not android") if scannedPeripherals[peripheral.identifier] == nil { peripheral.delegate = self scannedPeripherals.updateValue((peripheral, EncounterRecord(rssi: RSSI.doubleValue, txPower: advertisementData[CBAdvertisementDataTxPowerLevelKey] as? Double)), forKey: peripheral.identifier) central.connect(peripheral) } else { DLog("iOS Peripheral \(peripheral) has been discovered already in this window, will not attempt to connect to it again") } } } func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { let peripheralStateString = BluetraceUtils.peripheralStateToString(peripheral.state) DLog("CC didConnect peripheral peripheralCentral state: \(BluetraceUtils.centralStateToString(central.state)), Peripheral state: \(peripheralStateString)") peripheral.delegate = self peripheral.discoverServices([BluetraceConfig.BluetoothServiceID]) } func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { DLog("CC didDisconnectPeripheral \(peripheral) , \(error != nil ? "error: \(error.debugDescription)" : "" )") } func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { DLog("CC didFailToConnect peripheral \(error != nil ? "error: \(error.debugDescription)" : "" )") } } extension CentralController: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { if let err = error { DLog("error: \(err)") } guard let service = peripheral.services?.first(where: { $0.uuid == BluetraceConfig.BluetoothServiceID }) else { return } peripheral.discoverCharacteristics([BluetraceConfig.BluetoothServiceID], for: service) } func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { if let err = error { DLog("error: \(err)") } guard let characteristic = service.characteristics?.first(where: { $0.uuid == BluetraceConfig.BluetoothServiceID}) else { return } peripheral.readValue(for: characteristic) // Do not need to wait for a successful read before writing, because no data from the read is needed in the write if let currEncounter = scannedPeripherals[peripheral.identifier] { EncounterMessageManager.shared.getTempId { (result) in guard let tempId = result else { DLog("broadcast msg not present") return } guard let rssi = currEncounter.encounter.rssi else { DLog("rssi should be present in \(currEncounter.encounter)") return } let dataToWrite = CentralWriteData(modelC: DeviceIdentifier.getModel(), rssi: rssi, txPower: currEncounter.encounter.txPower, msg: tempId, org: BluetraceConfig.OrgID, v: BluetraceConfig.ProtocolVersion) do { let encodedData = try JSONEncoder().encode(dataToWrite) peripheral.writeValue(encodedData, for: characteristic, type: .withResponse) } catch { DLog("Error: \(error)") } } } } func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { let debugLogs = ["characteristic": characteristic as AnyObject, "encounter": scannedPeripherals[peripheral.identifier] as AnyObject] as AnyObject DLog("\(debugLogs)") if error == nil { if let scannedPeri = scannedPeripherals[peripheral.identifier], let characteristicValue = characteristic.value { do { let peripheralCharData = try JSONDecoder().decode(PeripheralCharacteristicsData.self, from: characteristicValue) var encounterStruct = scannedPeri.encounter encounterStruct.msg = peripheralCharData.msg encounterStruct.update(modelP: peripheralCharData.modelP) encounterStruct.org = peripheralCharData.org encounterStruct.v = peripheralCharData.v scannedPeripherals.updateValue((scannedPeri.peripheral, encounterStruct), forKey: peripheral.identifier) encounterStruct.saveToCoreData() } catch { DLog("Error: \(error). CharacteristicValue is \(characteristicValue)") } } else { DLog("Error: scannedPeripherals[peripheral.identifier] is \(String(describing: scannedPeripherals[peripheral.identifier])), characteristic.value is \(String(describing: characteristic.value))") } } else { DLog("Error: \(error!)") } } func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { DLog("didWriteValueFor to peripheral: \(peripheral), for characteristics: \(characteristic). \(error != nil ? "error: \(error.debugDescription)" : "" )") central?.cancelPeripheralConnection(peripheral) } } |
Code source du contrôleur central de l’application iOS développée en Swift
CovidSafe fonctionne grâce au bluetooth : lorsqu’une personne en croise une autre ayant également téléchargé l’application, alors son smartphone va enregistrer ces données. Si l’un des individus avec lesquels cette personne est entrée en contact est testé positif, un professionnel de santé la contactera afin de lui indiquer la marche à suivre.
Quelques heures après sa publication sur l'App Store et Google Play, plus de 5 millions d'Australiens avaient téléchargé Covidsafe. Problème, cette application ne fonctionne pas de la même manière sous Android et sous iOS. Randall Brugeaud, directeur général de la Digital Transformation Agency (DTA), l'agence en charge du déploiement de CovidSafe, reconnaît certaines limites techniques : « ce que nous pouvons dire, c’est que la qualité de la connectivité bluetooth pour les téléphones sur lesquels l’application est installée et tourne au premier plan est très bonne. Puis, elle se détériore progressivement et la qualité de la connexion est moins bonne lorsque l’application tourne en arrière-plan ».
Dans la pratique, les utilisateurs des iPhone doivent toujours avoir leur smartphone ouvert sur l’application s’ils veulent que cette dernière enregistre chaque personne qu’ils ont croisée. Ce bug est encore plus prononcé s’il s’agit d’un ancien iPhone. Les possesseurs de téléphones Android, eux, n’ont qu’à avoir l’application en arrière-plan pour que cette dernière soit efficace.
Une mise à jour de l’application est prévue dans les jours à venir, afin de rectifier le tir. Selon Randall Brugeaud, il faudra attendre l'intégration du protocole développé en commun par Apple et Google pour que le fonctionnement de l'application soit amélioré. Ce sera fait dès que possible, le responsable annonçant que l'Australie sera l'un des premiers pays au monde à intégrer ce protocole.
L'application stockera pendant 21 jours les données des personnes avec lesquelles l’utilisateur aura été en contact, et celles-ci seront conservées sur son téléphone pendant cette période. Ces informations sont stockées de manière chiffrée sur un serveur gouvernemental, puis transmises aux autorités sanitaires de l'État et du territoire dans le cas où une personne avec laquelle vous avez été en contact a été testée positive.
C'est Amazon Web Services qui a été sélectionné pour gérer ces données dans le cadre d'un premier contrat à 700 000 dollars. Ce sont des serveurs d’Amazon qui stockent les données récoltées par l’application, suscitant des craintes de la part des citoyens australiens de voir leurs informations personnelles entre les mains du gouvernement américain.
Sources : GitHub, DTA
Et vous ?
Que pensez-vous du fonctionnement de CovidSafe ?
Que pensez-vous du choix d'Amazon Web Services pour héberger les données de CovidSafe ?
Voir aussi :
Apple et Google lancent un outil commun de traçage du COVID-19 pour iOS et Android, il se traduira d'abord par une API, puis un outil intégré nativement aux deux systèmes
Le Contrôleur européen de la protection des données appelle à la mise en oeuvre d'une application mobile paneuropéenne pour suivre le COVID-19. L'initiative devrait lancer sa plateforme cette semaine
Covid-19 : faut-il pister les téléphones pour cibler les lieux de rassemblements ? Le PM canadien n'écarte pas l'idée d'aller vers un « totalitarisme sanitaire », d'après les défenseurs des libertés
StopCovid : la France travaille sur une application qui va tracer l'historique des contacts avec les malades, mais la piste de la géolocalisation est écartée