In recent weeks, Confide, a secure instant messaging application, has gained popularity in some circles. This article presents a quick assessment of the security of this application. The official website boasts the confidentiality provided by the product through three qualifiers: encrypted, ephemeral and screenshot protected. Each of these aspects will be studied.
The encryption protocol will be particularly detailed because it is tagged as battle tested, military grade cryptography. We already knew about military grade cryptography, which seems to be a synonym of put AES-256 somewhere, no matter how you use it in many applications, but we had never heard of battle tested cryptography. This article is an opportunity to present this technology.
Developing properly end-to-end communication systems is complex. As we have seen in the past with iMessage, even if cryptographic primitives are correctly implemented and encryption keys are correctly generated and protected, the design is critical to forbid the service operator from being able to eavesdrop messages.
TL;DR: Confide server can read your messages by performing a man-in-the-middle attack. Other security features (screenshot prevention, message deletion) can be defeated and will be detailed in a future article.
Setting Up the Test Environment
This first part presents quickly the modifications made to the client to analyze the Confide protocol. It allows the reader to reproduce the attacks described in this article.
Readers familiar with MitM can skip this part.
Retrieving the Application Code
Confide was developed with the Electron framework. This is a node.js framework for developing desktop applications with web technologies. The article presents the results of the study of Confide 1.4.2 and 1.4.3 under Windows. The code for the macOS application is probably very close. The one from iOS and Android was not analyzed.
The application code is located in an asar archive. This archive contains the client, written in JavaScript, as well as node.js modules in JavaScript and native code. The client code is minified but remains very understandable.
Analyzing the Traffic
All the communications exchanged between two clients pass through the Confide server (https://confideapi.com). Communications are protected by TLS. The first step required to analyze the traffic is to retrieve plaintext communications. A web proxy such as Fiddler can be used.
Fiddler registers as a system proxy when it is started, and thus intercepts all communications. Confide connections are made using the node request module, which does not respect the system proxy settings under Windows. However, it is possible to specify a proxy using environment variables, as indicated in the module documentation:
The following environment variables are respected by request:
HTTP_PROXY / http_proxy
Communications are then intercepted by Fiddler, but the TLS handshake between Confide and Fiddler fails. This is because Confide tells the request module to allow only valid TLS certificates. This behavior can be changed using the strictSSL parameter:
strictSSL - If true, requires SSL certificates be valid. Note: to use your own certificate authority, you need to specify an agent that was created with that CA as an option.
The strictSSL parameter can be replaced in the Confide code. It is initially set to !0 (true), and can be modified to !1 (false). Thus, connections with self-signed certificates will be accepted.
return request.defaults({headers: headers, gzip: !0, agentOptions: {maxCachedSessions: 0}, strictSSL: !0})
The TLS connection now succeeds. However, Confide refuses to start and displays an error message, as can be seen in the following screenshot:
HTTP Public Key Pinning. Confide refuses to start.
The server public key is pinned: the SHA-256 of the public key of the certificate returned by the server is compared to a hash hardcoded in the application:
certificate = c.getPeerCertificate();
if (!certificate || "yFyxMmEJG6xo94BRydog8B+XAocb6cCmPIEZKSa79pM=" !== computeFingerprint(certificate))
This can be circumvented by simply removing the check or by replacing the logical or by a logical and. The communications are now correctly captured and decrypted by Fiddler, and the application is fully functional. Let us now detail the protocol.
Now I can speak confidentially while the traffic is intercepted by the proxy.
Protocol analysis
The protocol is not fully described here: only the steps that seemed necessary to us to analyze the end-to-end encryption are detailed.
Account Creation
A user account must be created to use Confide. A valid phone number or e-mail is required. The user is then prompted to enter his / her last name, first name and password.
These pieces of information are sent to the server using a POST request on /users/create:
phone=%2B33601020304&password=supersecret&firstName=Lou&lastName=Tausk&language=fr
The server returns JSON data containing the profile of the created account: one may find in particular the last name and first name of the user, a UserId and a random alphanumeric username, probably used internally. This profile can then be retrieved with a GET request on /users/me once authenticated.
An e-mail or SMS containing a link to follow to activate the account is then sent to the user.
The user authenticates (see below), and generates an ECDH key for the P-256 curve. It transmits the public part to the server in an unconventional way: it is sent in an HTTP header named Public-Key with all the requests of the user.
A POST request to /installations/set sends to the server a UUID corresponding to the device on which Confide is installed. The server then returns the UserId, its public key and an InstallationId. This InstallationId, as we will see later, is used by Confide for synchronization on several devices.
Authentication
After the account is created, the user must authenticate to the server. The user email and password are transmitted to the server through a POST request. The server returns some JSON data containing an authentication token.
The data sent by the client has the following form:
grant_type=password&username=<email>&password=<password>
The most surprising part of the authentication request is the presence of an authorization HTTP header although no secret has been exchanged so far:
authorization: Basic RnVvSDBpZW...
The value of this field is actually fixed: the user name and the password of the Basic authorization field are stored in a slightly obfuscated and encrypted form in the cc proprietary node module. This mechanism is probably aimed at preventing an alternative client from communicating with the Confide server.
After authentication, fixed identifiers are no longer used: in all other types of queries made by the client, the authorization field contains the token returned by the server.
Notifications
When a message is sent to a client, it is notified through a WebSocket which is opened when logging on. The client then retrieves the list of unread threads with a GET request on /v1/threads?v=2. Each thread consists of a list of "participants" and a list of unread messages.
These messages can be encrypted or not: in fact, the demo message sent by Confide after registration is not encrypted. All other messages (at least all the messages we have sent) are encrypted. The encryption mechanism is now detailed.
End-to-end Encryption
The Encryption paragraph on the application website stipulates that:
Confide uses battle tested, military grade cryptography to keep your messages safe and secure. We combine this with a simple and intuitive user experience to provide superior security, with no configuration required.
All messages between Confide users are end-to-end encrypted. Encryption keys are generated locally on each device and the private key never leaves the device, ensuring that only the intended recipients can read your messages.
All communication goes through Transport Layer Security (TLS), preventing any possible man-in-the-middle attack and providing yet another layer of security, privacy and data integrity.
In this section, we show that the protection provided by Confide heavily relies on TLS and the security provided by this end-to-end encryption is very low. In particular, Confide can read user messages.
Each contact has a public key, which is included in the contact profile (recall that the public key is an ECDH key on P-256). The profile of each contact can be retrieved by searching the server. This search can be made by phone number, e-mail address or "username". We did not manage to make the "Search by username" work during our tests. It might be disabled.
Sending a Message
When sending a message to one or more recipients, the client:
Generates an AES-128 encryption key K and a 128-bit initialization vector IV.
Creates an empty array conversationKeys.
- For each recipient i:
Computes a shared secret Si with ECDH using the sender private key and the recipient public key.
Generates a 128-bit initialization vector IVi.
Generates an AES-128 key Ki = SHA1(Si). Only the first 128 bits are retained.
Encrypt K with Ki and IVi using AES-128-CBC and add the result to conversationKeys.
The client gets a conversationKeys array containing the key K encrypted for each of the recipients.
The content of the message is then processed. The client:
Encrypts each attachment (picture, video...) of the message with K and IV using AES-128-CBC.
Transmits each encrypted file to the server, which returns a unique file identifier for each file.
Encrypts the text of the message with K and IV using AES-128-CBC.
Sends the encrypted message, attachment IDs, and conversationKeys to the server.
The text and attachments are therefore encrypted with the same pair (K, IV). No cryptographic integrity mechanism is present. The cryptographic protocol does not involve authentication, which hence relies on the Confide server.
Receiving a Message
When a client is notified that a new message is being received, it requests the list of its unread threads. The server sends the threads, which include the list of the participants as well as the encrypted message addressed to the recipients and the sender public key:
"Threads": [{
"Participants": [{
"UserId": 828258,
"Username": "bkqb5bq9",
"FirstName": "Nadine",
"LastName": "Alcover",
"LastMessageSent": {
"DateSent": "2017-03-01T15:39:34Z"
},
"LastMessageReceived": {
"DateRead": "2017-03-01T15:13:50Z"
}
}, {
"UserId": 828569,
"Username": "rk727f79",
"FirstName": "Lou",
"LastName": "Tausk",
"LastMessageSent": {
"MessageId": 97795492,
"DateSent": "2017-03-01T15:13:36Z"
}
}
],
"UnreadMessages": [{
"MessageId": 97800395,
"DateSent": "2017-03-01T15:39:34Z",
"SenderPublicKey": "0324274E88A29962D48DAC6916C49BA0EA51466738F90015420CFA54AD94D0B13A",
"EncryptedMessageKey": "BpOjb8KZfaQR/qHEWW66ZddUsZR/qKQiIbkVfDRy1ZYJqNcFG4VWtRQRoieexTR/17DQREIgQAegaPj2+TRSog==",
"TextContent": "j2RDcxLYPRVyB/OSKWJknA==",
"SenderUserId": 828258,
"FormatVersion": 1
}
]
}
]
The recipient has no way to verify the origin of the message. It cannot check the sender's public key authenticity either.
It decrypts the message encryption key with its private key and the SenderPublicKey field, and then decrypts the message content. The message integrity, again, cannot be verified.
Man-in-the-middle from the server
The most obvious problem is not the reuse of the IV or the lack of integrity mechanism, it is linked to the fact that the encrypted message origin and the authenticity of the public encryption key transmitted by the server can in no way be verified by the client.
Thus, the Confide server could generate its own key pair and transmit the public part to a client when the latter requests the public key of a recipient (we only note that Confide is able to do so, not that it does so). This client then unknowingly encrypts a message that can be decrypted by the server. Finally, when the server sends the message to the recipient, it is able to re-encrypt the message with its own key for the actual recipient.
This is a classic man-in-the-middle attack against Diffie-Hellman based protocols, which works by design of the Confide protocol. Confidentiality of the communications is in fact provided by TLS, and not by the end-to-end encryption mechanism provided by Confide. This limits the possibilities of MitM to the Confide server, which acts here as a trusted third party.
We will now see that an even more important problem is present in the protocol.
Synchronization
Confide is available for Windows, macOS, iOS and Android. The website boasts, let us recall, a simple and intuitive user experience to provide superior security, with no configuration required.
This translates in the ability to use the same account on multiple devices. To do this, a user installs the application, authenticates himself, and can then access his messages while the private key has not been copied.
How is it possible? There is actually no key synchronization mechanism. Instead, when a user authenticates with a new device, a new installation is created and a new public key is sent to the server. It is added to the user's key set, and does not replace the previous one. A user can thus have several keys in his set:
"Installations": [{
"InstallationId": 1042078,
"PublicKey": "0324274E88A29962D48DAC6916C49BA0EA51466738F90015420CFA54AD94D0B13A"
}, {
"InstallationId": 1042218,
"PublicKey": "0335BCA15666320BFA3A21610D627EA7EF8A7DDD0BDC7A32724E2250FBB75BA4BE"
}
]
Then, when a message is sent to this user, the message key is encrypted for all of the user's public keys.
Confide provides no ways for the user to revoke or check his key set.
We can draw two conclusions:
The Confide server can add a key when it returns a user's profile. The sender of a message then protects the message key with the recipient real public key and the one added by the server. There is no way to detect this.
The end-to-end encryption mechanism provides a very low protection: the overall security of communications is based on TLS. Without this layer, the compromise of communications would be trivial. An attacker in the middle man position could inject a public key when sending a user's profile to the server, and decrypt messages sent on the fly.
A proof of concept has been developed. Instead of writing a full Confide server, we wrote a script that performs a man-in-the-middle attack on the TLS connection, adds a rogue recipient key and decrypts messages on the fly when they are sent. Hence anybody able to inject a public key in the TLS session can read all messages without having the user knowing it.
Currently, at least Confide or attackers who compromised the Confide servers are technically able to do so by design.
Without access to the Confide servers, the attack would require to defeat the TLS layer and the HTTP public key pinning (which has been disabled in our client as previously detailed).
Here is a quick demonstration showing the script intercepting messages:
Conclusion
The end-to-end encryption used in Confide is far from reaching the state of the art. Building a secure instant messaging is not easy, but when claiming it, some strong mechanisms should really be enforced since the beginning.
The confidentiality of the exchanged messages depends on the robustness of TLS. Confide can technically read all the messages that pass through its servers. End-to-end encryption, as it is implemented, solely relies on the server through which the messages pass.
Confide is not just an encrypted messenger. It provides other interesting security features:
Screenshot prevention: Received messages can theoretically not be copied by a user. As the astute reader may have noticed, the previous paragraphs present screenshots of the application.
Message deletion: Once a user reads a message, it is deleted from the client and from the server. Is it possible to prevent message deletion?
Secrets protection: Confide handle secrets, like private keys required to decrypt messages. Are these keys correctly protected?
These interrogations (and other components) will be discussed in a future article. All the issues have been reported to Confide, and they are working on fixing them. In the meantime, do not consider your conversations to be so well concealed.