How Secure Is Canada’s COVID Alert App? Evaluation of Android App v1.0.3

How Secure Is Canada’s COVID Alert App? A Closer Look at Android App v1.0.3

The Canadian government recently launched the COVID Alert app, a COVID-19 contact tracing mobile application, as a pilot project in Ontario to track and notify users if they have been exposed to anyone who has been diagnosed with COVID-19. Despite assurances from the government, many people have suspicions or misconceptions about what data the application is actually collecting and communicating. Based on the adage “trust but verify,” our consulting team performed an inspection of the Android version of the application downloaded from the Google Play store. 

Note: This inspection of the Canada COVID Alert app consisted solely of an analysis of publicly-available source code repositories and the Android application available on the Google Play store. Security Compass did not perform a penetration test of any kind of the COVID Alert app.

Based on the research conducted, the application does not collect or transmit any personal information and appears to strike a good balance between privacy and efficacy. A detailed analysis of the application and the evaluation follows below.

Analysis of Canada’s COVID-19 contact tracing mobile application

The Canada COVID Alert Android App v1.0.3 available from Google Play Store was analyzed for its overall security posture and to identify security gaps that could affect user privacy. A static and dynamic analysis was performed on the compiled Android Package (APK), and the following source code was also analyzed:

The Android version of the app uses Google Play Service’s Exposure Notifications API. More information about this API can be found here.

Android permissions

The app requests the following permissions to function on a user’s device:

  • android.hardware.bluetooth_le
  • android.permission.INTERNET
  • android.permission.BLUETOOTH
  • android.permission.ACCESS_NETWORK_STATE
  • android.permission.ACCESS_WIFI_STATE
  • android.permission.RECEIVE_BOOT_COMPLETED
  • android.permission.WAKE_LOCK
  • android.permission.GET_TASKS
  • android.permission.FOREGROUND_SERVICE

Bluetooth is utilized to scan surroundings to identify nearby devices (i.e. Android/iOS devices) that both have the COVID Alert app installed and are transmitting Random IDs for other devices to log.

The app neither logs nor tracks the user’s location, and it does not request Location Service information. However, the app does require that users enable Location Service along with Bluetooth. One of the key questions end-users have is “why must Location Service be enabled if the app does not trace/log a user’s location?” Location Service must be enabled because of changes made in Android 6.0+ devices. With the new changes in place, an app that uses Bluetooth Low Energy (BLE) requires the device to have Location Service enabled. More information about BLE permissions can be found here.

Network communication

Using an encrypted HTTPS connection, the app communicates over the Internet with two servers: “retrieval.covid-notification.alpha.canada.ca” and “submission.covid-notification.alpha.canada.ca”.

Figure 1: App's URLs defined in the "app.covidshield.BuildConfig" class

The “retrieval.covid-notification.alpha.canada.ca” server is used by the app to download a ZIP file containing two files: “export.bin” and “export.sig”. The “export.bin” file contains Temporary Exposure Keys (TEK) and metadata, which are compared against the locally stored keys to identify whether a user was exposed to a person diagnosed with COVID-19. More details regarding the “export.bin” file format can be found here. 

Here is a sample HTTPS request for the ZIP file download that contains the “export.bin” and “export.sig” files:

Figure 2: A HTTPS network call to download diagnosed TEKs ZIP file

The file is downloaded using the “writeFile()” method defined inside the “app.covidshield.module.CovidShieldModule” class. A screenshot of the decompiled code follows:

Figure 3: The decompiled code used for saving the downloaded TEKs file as a "keys.zip" file

A more readable version of the “writeFile()” method can be found on the GitHub repo.

Figure 4: Source code used to save the "keys.zip" file

The downloaded file is stored as a “keys.zip” file inside the app’s data directory inside a randomly generated directory name (e.g. /data/data/ca.gc.hcsc.canada.stopcovid/app_RANDOM-UUID/). Additionally, the “keys.zip” file’s permissions are set to private (i.e. the file can be read/written by the COVID Alert app) to ensure that it is not accessible to other apps on the device or by using ADB on non-rooted devices.

Figure 5: The "keys.zip" file is saved inside the app's directory with private file permissions

When a user is diagnosed with COVID-19 and needs a one-time key to let people know they have been exposed, the app allows the user to enter a 10-12-digit PIN, which is only provided by an authorized health worker. The app utilizes the “submission.covid-notification.alpha.canada.ca” server for the one-time key submission.

Figure 6: The HTTPS network call used when a one-time-key is submitted

Brute forcing the one-time key is prevented by the server-side application using an IP address ban of 1 hour after 50 sequential incorrect submissions.

Figure 7: An HTTPS network call made when an attempt is made to brute-force the one-time key

The COVID Alert app’s server-side code for the IP ban mechanism is available here.

Figure 8: Source code for IP-address identification when the one-time key is submitted

The “getIP()” method accepts an argument that is the raw HTTP request, which is sent by the user when a one-time-key is submitted. This method then checks for the presence of the “X-FORWARDED-FOR” HTTP request header. If this header is found, its value is split using a comma delimiter and the IP address is returned as a string.

This “getIP()” process seemed to have a security gap because an adversary could submit an HTTPS call with an X-FORWARDED-FOR request header set with any IP address or list of IP addresses separated by commas. The server-side code would then use the X-FORWARDED-FOR header’s value to identify the user’s IP address and could ban it for brute-forcing the one-time key. However, the original IP address of the user is concealed and not banned, allowing the adversary to continue the brute-force attack.

Despite this potential issue in the published source code, the issue did not persist in the production implementation on “submission.covid-notification.alpha.canada.ca”. Even if the X-FORWARDED-FOR HTTP request header is supplied with random IP addresses, the server uses the original IP address of the user and will ban them for too many one-time-key submissions.

Figure 9: An attempt to bypass the IP-address ban using the X-FORWARDED-FOR header

Analyzing the COVID Alert server-side source code determined that the app is not storing users’ IP addresses. A user’s IP address is stored when an incorrect one-time key is submitted, but it is purged when the user submits a correct one-time key. Because the actual production configuration cannot be viewed, it is not possible to be certain that the IP address of end-users are not logged at all.

Dynamic analysis

The Android app was analyzed during runtime using Frida on a rooted Android device. The app did not implement SSL Pinning, so it was possible to intercept HTTPS network communication. Additionally, the app did not implement root detection, so it was possible to install and run the app on a rooted Android device. The app is dependent upon Google Play Services with Exposure Notifications being enabled to function properly. If the app is tampered with or re-signed, the app's signature is changed, and the Google Play Services’ Exposure Notification (EN) API denies the app from using the EN API. The error message can be seen in the Android Debug (ADB) logcat:

Figure 10: The Exposure Notification API set to “Denied” in response to a re-signed APK file

Apart from the static analysis performed using decompiled code and file system analysis, the app’s classes and methods were hooked and monitored for any malicious activities, especially when the device was not configured with any network proxy or third-party CA certificate. The app was not observed performing any malicious activity or attempting to access/exfiltrate users’ information/identities/locations. Because the app did not implement debugger detection and runtime injections, it was possible to utilize Frida to perform runtime manipulations inside the app’s functionalities.

Figure 11: The "downloadDiagnosisJKeysFile()" method hooked for analysis

Figure 12: The "writeFile()" method hooked to identify calls sent to the function for saving "keys.zip" file

Objection was used to hook the Exposure Notification API calls to identify which methods are called by the COVID Alert app and what actions are performed. At the time of the security review, no sensitive information leakage or security gap affecting user privacy were identified.

Machine generated alternative text:
ca.gc.hcsc.canada. stopcovid on (asus: 7.1.1) [usb] # (agent) [hd948z9es4i] Called 
com . google . and roid . gms . nearby . exposurenotification . ExposureConfiguration$ExposureConfigurationBuilder . setMini 
mumRiskScore(int) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
(agent) 
[hd948z9es4i] 
[hd948z9es4i] 
[hd948z9es4i] 
[hd948z9es4i] 
[hd948z9es4i] 
[hd948z9es4i] 
[hd948z9es4i] 
[hd948z9es4i] 
[hd948z9es4i] 
[hd948z9es4i] 
[f14jaiuj3d] 
[f14jaiuj3d] 
[f14jaiuj3d] 
[f14jaiuj3d] 
[f14jaiuj3d] 
[f14jaiuj3d] 
[f14jaiuj3d] 
[f14jaiuj3d] 
[f14jaiuj3d] 
[f14jaiuj3d] 
[f14jaiuj3d] 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
Called 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
google. android. 
com. 
on (asus: 7.1.1) [usb] 
. nearby . exposurenotification . ExposureConfiguration$ExposureConfigurationBuilder . setAttenuationScores( [I 
. nearby . exposurenotification . ExposureConfiguration$ExposureConfigurationBuilder . setAttenuationWeight(int 
. nearby . exposurenotification . ExposureConfiguration$ExposureConfigurationBuilder . setDaysSinceLastExposureScores( [I 
. nearby . exposurenotification . ExposureConfiguration$ExposureConfigurationBuilder . setDaysSinceLastExposureWeight(int) 
. nearby . exposurenotification . ExposureConfiguration$ExposureConfigurationBuilder . setDurationScores( [I 
. nearby . exposurenotification . ExposureConfiguration$ExposureConfigurationBuilder . setDurationWeight(int) 
. nearby . exposurenotification . ExposureConfiguration$ExposureConfigurationBuilder . setDurationAtAttenuationThresh01ds( [I 
. nearby . exposurenotification . ExposureConfiguration$ExposureConfigurationBuilder . setTransmissionRiskScores( [I 
. nearby . exposurenotification . ExposureConfiguration$ExposureConfigurationBuilder . setTransmissionRiskWeight(int) 
. nearby . exposurenotification . ExposureConfiguration$ExposureConfigurationBuilder . build() 
. nearby. exposurenotification. ExposupeConfigupation.writeToparce1(android .os .parcel, int) 
. nearby . exposurenotification . Exposureconfiguration . getMinimumRiskScore() 
. nearby . exposurenoti fication . Exposureconfiguration . getAttenuationScores() 
. nearby . exposurenotification . Exposureconfiguration . getAttenuationWeight() 
. nearby . exposurenotification . Exposureconfiguration . getDaysSinceLastExposureScores() 
. nearby . exposurenotification . Exposureconfiguration . getDaysSinceLastExposureWeight() 
. nearby . exposurenoti fication . Exposureconfiguration . getDurationScores() 
. nearby . exposurenotification . Exposureconfiguration . getDurationWeight() 
. nearby . exposurenotification . Exposureconfiguration . getTransmissionRiskScores() 
. nearby . exposurenotification . Exposureconfiguration . getTransmissionRiskWeight() 
. nearby . exposurenotification . Exposureconfiguration . getDurationAtAttenuationThresh01ds() 
ca.gc . hcsc. canada. stopcovid

Figure 13: Hooked Exposure Notification API calls when the app was first run

Frida scripts were used to intercept the Exposure Notification API calls and the arguments passed to the API calls. At the time of security review, the analysis of the arguments and the API call behaviour did not disclose any malicious behavior.

Figure 14: Hooked Exposure Notification API calls and argument value of each API call

For further analysis, the network proxy was disabled and a third-party CA certificate (for HTTPS interception) was removed from the Android device. Then, all the app’s own classes and methods were hooked and left near other devices with the COVID Alert app running. This test was performed to identify whether the app was performing any suspicious actions, leaking sensitive information, or disclosing the user’s identity. At the time of security review, this test case did not indicate any suspicious activity or information leakage.

Additionally, the same test case was performed with HTTPS interception and network-level interception enabled to identify any HTTPS/TCP/UDP network communication used for suspicious activities or information leaks. Again, there were no indications of said activities or leaks.

What could not be reviewed?

Dynamic analysis of the app’s behavior after a user has submitted a valid one-time-key could not be performed because reviewers lacked a valid one-time key that is restricted to health workers and users diagnosed with COVID-19. However, the static analysis of the app did not disclose any suspicious behaviour or user-information leakage.

Conclusion

From the cursory review of the COVID Alert App, it seems that the app neither accesses nor discloses the user’s identity. If an app user is diagnosed with COVID-19, the app explicitly asks for the user’s consent to share the randomly generated keys on the user’s device. It is up to the user to share the keys or not. Additionally, at the time of security review, the app was neither observed attempting to access a user’s identity from the device nor did it share a user’s identity with a third party (including Google).

If a user is running Android 6.0+, the user must enable Location Service so that the Bluetooth scanning can be performed, but the app still does not log or track the user's location.

Source code analysis identified one security gap regarding how the server-side application identifies the user’s IP address. However, this issue was not found in the production instance used by the COVID Alert app.

As a final thought, the app seems to be well built and is not disclosing users’ identities or PII, instilling confidence in its use.

Interested in learning more about Security Compass Advisory Services? Explore how we can help solve your organization’s security challenges.

About the Author

Abhineet is a Principal Security Consultant at Security Compass. He has extensive experience in performing vulnerability assessments and penetration testing of web applications based on a variety of technologies, infrastructure, and Android & iOS mobile applications. He has utilized his technical skills and industry experience to manage multiple projects, advises clients on how to mitigate security risks, and develops action plans for complex client engagements. He enjoys contributing to open-source projects and is actively involved in building new tools and scripts to simplify different tasks within the information security domain.

More Content by Abhineet Jayaraj

No Previous Articles

Next Article
Scenario Planning to Manage Security in DevSecOps
Scenario Planning to Manage Security in DevSecOps

One of the biggest challenges that remain in DevSecOps today is alignment between teams. Read how scenario ...