There are two issues in the mobile COVIDSafe apps that allow for Unique IDs / TempIDs to be broadcast after they expire.
The first issue relates to how the iOS app obtains new TempIDs.
The second issue identifies the parts of the codebase where the apps continue to use TempIDs that it already knows have expired.
The use of TempIDs beyond their 2-hour lifespan may make it easier for third-parties to track COVIDSafe app users.
Obtaining new TempIDs on iOS
When requesting a TempID from the National COVIDSafe Data Store, the response contains three fields:
- The TempID itself.
- A refresh time, which is 1 hour from the current time, after which the application should attempt to obtain a new TempID.
- An expiry time, which is 2 hours from the current time, after which the application must cease to use this TempID.
This interpretation of these fields is supported by the Android codebase, which includes the comment
// Only attempt to write BM back to peripheral if it is still valid in StreetPassWorker.kt#L586, as well as the various logging messages in both the iOS and Android codebase.
In the Android app, the app appears to set up a timer to obtain a new TempID after the refresh time is reached. However, the iOS app does not do this, and only attempts to obtain a new TempID once the expiry time is reached.
This means that if there are any intermittent communications issues, such as a loss of network connectivity or server outage, the Android app has a whole hour as a grace window to attempt to re-establish communications and obtain a new TempID. However, the iOS app has no such grace window, and immediately has only two choices - broadcast an expired TempID, or broadcast nothing.
Using Expired TempIDs
In both these codebases, the app will continue to broadcast an expired TempID after it has expired, if a new one cannot be obtained.
In the Android codebase, this is due to the function
bmValid, which is supposed to check the validity of a Broadcast Message (BM), i.e. a TempID, always returning true in Utils.kt#L245, even if it has determined just two lines above that the TempID has indeed already expired. Therefore, if the app determines that the TempID has expired, it will nevertheless continue to re-use the old TempID indefinitely.
In the iOS codebase, this is due to the function
fetchTempIdFromApi returning the previously-acquired TempID if any error is encountered talking to the National COVIDSafe Data Store. This behaviour is defined in EncounterMessageManager.swift#L97-107. This function is called by
getAdvertisementPayload when the TempID has expired, and can therefore return a "new" token whose expiry is never re-evaluated. Therefore, if the app determines that the TempID has expired, and fails to obtain a new TempID, it will nevertheless continue to re-use the old TempID indefinitely.
Combining these two issues means that:
- The Android app has only an hour tolerance for intermittent networking or client-server communication, and if the issue persists beyond the expiry then it will continue to broadcast an expired TempID indefinitely. If the issue is resolved between the refresh and expiry times then the app still performs as expected.
- The iOS app has zero tolerance for intermittent networking or client-server communication, if the issue persists beyond the expiry then it will continue to broadcast an expired TempID indefinitely.
An encrypted user ID will be created every 2 hours. This will be logged in the National COVIDSafe data store (data store), operated by the Digital Transformation Agency, in case you need to be identified for contact tracing.
Quite clearly, from the above behaviours, an encrypted user ID may not necessarily be created every 2 hours, such as users on iPods or iPads away from their home Wi-Fi, users with Airplane Mode in cinemas, users in shopping centers or other building that have weak cellular signals due to thick walls or underground levels, server outages, connections pending captive portals, etc.
This also appears to run somewhat contrary to the Privacy Impact Assessment, which states:
8.17 We understand that the National COVIDSafe Data Store will automatically generate new Unique IDs for each User every two hours and send these new Unique IDs to the User’s App.
8.18 The App will only accept the new Unique IDs if it is open and running. If the App successfully accepts the new Unique ID, an automatic message will be generated and sent back to the National COVIDSafe Data Store. This message will only effectively indicate a "yes (new Unique ID successfully delivered)" response to the National COVIDSafe Data Store. If the App is not open and running, it will not be able to accept a new Unique ID. It will continue to store the previous Unique ID and use this when the App is opened, until a new Unique ID is generated and accepted.
As has been interpreted from the code and has also been observed from performing functional testing on a running app:
- There are a range of actual and potential scenarios in which the app is open and running, but is still not able to accept a new Unique ID.
- The iOS app does not even make a best-effort attempt to obtain new Unique IDs before the old one has expired.
- Both the iOS and Android apps can (and do) broadcast expired TempIDs.
- In these scenarios, users broadcasting TempIDs longer than their expiry become quite easy to track with third-party tools, which may amount to a breach of privacy.
- Prevent the broadcast of expired TempIDs,
- Display a warning to the user to connect to the internet if the TempID has expired and the app is unable to obtain a fresh one,
- Restore the original Singapore (OpenTrace) behaviour of downloading a large batch of TempIDs and cycling through them, instead of downloading them one at a time, and
- On iOS, make use of the window between the TempID refresh time and its expiry time to obtain a new TempID.
- 17th May 2020: Reported to email@example.com
- 18th May 2020: Response confirming receipt and that this has been passed on to the development team.
- 26th May 2020: iOS app updated with fix.
- 26th May 2020: Updated source code for both iOS and Android published.
- 01 Jun 2020: Android app updated with fix.