published on

Protonmail API: a peek under the hood

The Protonmail Android applications uses the Protonmail API to authenticate, list, read, and send messages. We discovered, however, that it does not use any sort of certificate pinning. This allows us to have a look at how the API looks like, the login process and the metadata that could potentially leak to an attacker armed with a fake SSL certificate from a rogue (but trusted) CA.

Certificate pinning in mobile applications is a method to make sure that the X509 certificates presented by a website (say protonmail.ch), are actually the expected ones and have not been replaced by a malicious actor.

While Android and iOS check by default that certificates are signed by a trusted authority, there have been numerous cases where one of those trusted authorities (out of the hundreds included by default) has been compromised and misused to issue rogue certificates. This would allow a malicious actor (like a nasty government) to decrypt all the traffic to the server on the fly, without the user suspecting anything.

Protonmail is a privacy-focused application. Its main feature is that all encrypt and decrypt operations are handled client-side, on the user’s device. Thus, it would not be possible to recover the text of email messages if the servers are compromised, and Protonmail’s employees do not have the power to access them even if they wanted or are requested to do so.

As we will see, in the case of a Man-In-The-Middle attack with a fake SSL certificate, the attackers would not be able to learn the content of the messages (as they travel already encrypted), but they would be able to learn a lot of other details.

Protonmail weaknesses

On our assessment of Protonmail Android application we quickly realized that it did not make any use of certificate pinning, so it was very easy to add a fake CA to our device and use it to inspect traffic with sniffing tools like Mitmproxy.

We also saw that Protonmail uses HTTP Public Key Pinning, which aims to address the same issue in browsers. HPKP works by inserting a header in every request to protonmail.ch which contains the fingerprints of the certificates used by that server. This allows browsers to remember those fingerprints and throw warnings when they suddently do not correspond to the certificates presented by the server.

However, Protonmail uses HPKP with a trick. The headers presented are:

Public-Key-Pins: pin-sha256=“cGJ8GCze8d6sddNq+ANgp4yJZu7/JR6weg1kDjgWr4M=”; pin-sha256=“tYkfFN27P1GUjH5ME128BCg302dL2iwOYhz5wwFJb50=”; pin-256=“j9ESw8g3DxR9XM06fYZeuN1UB4O6xp/GAIjjdD/zM3g=”; pin-sha256=“njSJ38BhLoq85xOvt2uSoP5ngQQgAIbAG4nXDB7rMss=”; max-age=300;

max-age=300 basically means that the browser will forget about the provided fingerprints in just 5 minutes. However, the recommended value for this setting is two months.

Setting such a low maximum age renders HPKP almost useless, as browsers will not be able to detect any unexpected certificate change unless the users have visited the site in the last 5 minutes. By including those headers, Protonmail.com can however get a better rating in SSL-checking tools, giving the impression that they are taking advantage of the security offered by HPKP.

API Layout

The Android application (which we have used to run tests), talks to api.protonmail.ch.

From there we can immediately learn about some of the endpoints provided by the API:

  • /messages/unread: returns label IDs and counts for unread messages.

  • /messages/total: returns total counts and label IDs.

  • /messages?Label=0&Order=time&Begin=&End= returns a list of messages, which includes metadata like subject, Label IDs, Starred status, Sender addresses and conversation IDs.

  • /device/delete: is used on logout.

  • /users: returns information about all the addresses used by protonmail, including encrypted private GPG keys and public keys for each address.

  • /messages/<messageID>: returns information about a single message. The response contains all the message metadata in plain text (including recipients, headers, number of attachments and their names and sizes), with exception of the message’s body, which is a PGP encrypted message.

  • /attachments/<attachmentID>: returns a message attachment. The contents of the response is by all means cyphertext.

The login process

It is also interesting to observe how the login process works, as it involves receiving the necessary PGP keys to encrypt and decrypt Protonmail messages.

It starts by performing a POST request to /auth/info:

{"ClientID":"Android","ClientSecret":"<secret_hex>","Username":"<user>"}

The server responds with:

{
    "Code": 1000,
    "Modulus": "\\n-----BEGIN PGP SIGNED MESSAGE-----\\nHash: SHA256\\n\\<message>\\n-----BEGIN PGP SIGNATURE-----\\nVersion: OpenPGP.js v1.2.0\\nComment: http://openpgpjs.org\\n\\n\\n=twTO\\n-----END PGP SIGNATURE-----\\n",
    "ServerEphemeral": "<base64_encoded_string>"",
    "Version": 4,
    "Salt": "<salt>",
    "SRPSession": "<hex_encoded_session>",
    "TwoFactor": 1
}

This must be followed by a POST request to auth:

{
    "ClientEphemeral": "<base64 string>",
    "ClientID": "Android",
    "ClientProof": "<base64 string>",
    "ClientSecret": "<same as above>",
    "SRPSession": "<same as above>",
    "TwoFactorCode": "<2facode>",
    "Username": "<username>"
}

Which comes back with:

{
    "AccessToken": "<PGP message encrypted with EncPrivateKey>",
    "Code": 1000,
    "EncPrivateKey": "<gpg private key>",
    "ExpiresIn": 864000,
    "KeySalt": "<base64 string>",
    "PasswordMode": 1,
    "PrivateKey": "<gpg private key for the main email address",
    "RefreshToken": "<hex string>",
    "Scope": "full mail self paid keys payments organization",
    "ServerProof": "<base64 string>",
    "TokenType": "Bearer",
    "Uid": "<hex uid>"
}

What we see here is that Protonmail provides the user’s device with all the necessary key material to encrypt and decrypt messages, without the need of ever revealing them to the Protonmail service. Since the user password does not seem to be transmitted out of the device (instead a proof of user’s identity is), the Protonmail staff cannot learn enough information to make use of the encrypted private keys that they store.

The private keys are themselves encrypted and need a passphrase to be used. We imagine that the passphrase of our keys is probably constructed as a combination of the user-password and the salt provided by the server. Once the device has decrypted the keys, it can use them to obtain the AccessToken and make further use of the API.

The passphrase can be easily obtained accessing Protonmail with a regular browser and using the developpers tools to set the right break-points in the openpgp.js library file (decrypt function). It seems to be a 31-character password containing letters and numbers. With it we have been able to verify that the AccessToken corresponds to the Bearer authentication header used by the application in subsequent requests to the API.

Conclusion: Leaked metadata and fixes upcoming from Protonmail

Thus, we can safely say that, in an scenario where the Protonmail communications can be successfully MiTM’ed, the attacker would learn:

  • Number of messages
  • Subject of the messages
  • Presence of any attachments
  • Full headers of the messages
  • Date and size of the messages
  • Sender and recipients names and addresses
  • Labels and names
  • Encrypted GPG private keys

That is a lot of information! Therefore, you should take extra pre-cautions if you handled sensistive communications using Protonmail in scenarios where the internet traffic might be monitored. While it is easy to inspect a website’s certificate when using a regular browser, it becomes very difficult to do the same while using mobile applications.

The Protonmail security team was informed of these findings (their lack of certificate pinning in the Android application and the misconfiguration of HPKP headers) in early June 2017, and after a very long thread explaining what certificate pinning is and why these things should be important for a privacy-first email provider, they replied that This is on our roadmap and we hope to get to it this summer.