-
Notifications
You must be signed in to change notification settings - Fork 299
Website API
End-To-End extension provides message encryption, decryption and signing services for third-party web applications through the Website API. By using the API web applications (e.g. webmail systems, instant messaging applications) can interact with End-To-End, giving them a secure, asynchronous way to encrypt/sign and verify arbitrary content. This page serves as a documentation for the API in its current version.
End-to-End extension allows the user to encrypt/decrypt/compose and verify messages by clicking on the extension page action button. In order to communicate with the web application in the current browser tab, the extension tries to establish a permanent HTML5 Channel Messaging connection with the Javascript code of the web application. Then API requests and responses are sent between the extension and the web application through the established channel. Responses to the requests are sent asynchronously.
If the channel cannot be established, the extension fallbacks to using standard DOM methods to extract currently selected/active objects and inject information back into the document.
- User clicks the extension page action button
- If content script has already been injected, go to 3. Otherwise, content script is injected into the current browser tab document.
- If API connection is already established or if DOM fallback is enabled, go to 8. Otherwise, content script detects API support in the web application. If support is not detected, enable DOM fallback and go to 8.
- For chosen origins (e.g. Gmail) appropriate stub script is injected, enabling API support.
- Content script sends a bootstrap message to the web application, passing a message port.
- Web application responds to a bootstrap message. If the response timeouts or indicates no API availability, enable DOM fallback and go to 8.
- Content script and web application attach event listeners to a message port
message
event. - If DOM fallback is enabled, call an appropriate DOM method in a content script. Otherwise, send the API request through the message port to the web application and forward the response back.
To minimise message noise, End-To-End extension only starts the API bootstrap procedure if the current browser tab document is enabled for the API support. As of now, the whitelist of origins with the API support is hardcoded in the extension. Please contact us to discuss adding Website API support for your web application.
In order to extract or inject messages into the current browser tab, the extension injects a content script into the current tab document, when the user first clicks on the page action button (for a given document). The content script is responsible for detecting if the Website API endpoint is provided by the web application in the current document and tries to establish a permanent connection with the API endpoint by sending a bootstrap message.
Bootstrap message is sent using HTML5 Cross-document messaging from the content script in the current window. The event that is triggered by sending the message will have the following properties:
event.data = {
api: 'e2e-init'
};
event.origin = window.location.origin; // The message is sent from a content script from the same origin
event.ports = [messagePort]; // The port through which the messages should be sent.
In order to establish the connection, the web application SHOULD send a following message through the provided messagePort
:
{
api: 'e2e-init',
version: 1
available: true|false
}
Value true
of the available
field signifies that the web application is ready to receive further API requests over the messagePort
. If the value of available
field is false, the extension will end the connection and fallback to the DOM API.
Example code that correctly responds to a bootstrap message can be found below:
/**
* Handles the bootstrap message coming from the extension.
* @param {MessageEvent} e bootstrap message sent from the extension.
*/
var bootstrapMessageHandler = function(e) {
if (e.origin == window.location.origin &&
e.data.api == 'e2e-init' &&
e.ports &&
e.ports.length == 1) {
e.ports[0].addEventListener('message', messageHandler, false);
e.ports[0].start();
e.ports[0].postMessage({
api: 'e2e-init',
version: 1,
available: true
});
window.removeEventListener('message', bootstrapMessageHandler);
}
};
window.addEventListener('message', bootstrapMessageHandler, false);
Further messages (requests and responses) are exchanged by peers over the port. Requests have the following format:
{
id: string, // Unique ID
call: string, // API function name to call
args: Object|null // Call parameters
}
If the request is successful, a peer SHOULD respond by sending the following result object:
{
requestId: string // ID of the request
result: * // Result
}
To notify the peer of an error condition, the following result object SHOULD be sent instead:
{
requestId: string // ID of the request
error: string // Error message
result: null
}
Exemplary function to handle requests coming from the extension is provided below:
/**
* Handles incoming E2E requests.
* @param {MessageEvent} event Event to handle.
*/
var messageHandler = function(event) {
var request = event.data;
var port = event.target;
var call = event.data.call;
var args = event.data.args;
var requestId = event.data.id;
// ...
if (error) {
port.postMessage({
requestId: requestId,
result: null,
error: 'An error occurred.'
});
} else {
port.postMessage({
requestId: requestId,
result: theResult
});
}
};
This function should be set up as an event listener for the message
event on the MessagePort
object during bootstrapping.
After the API connection has been bootstrapped, API requests can originate from either of the peer types (the extension or the web application). Different API calls are available for each of the peer types.
Notifies that extension is ready to accept requests from the web application.
- Request parameters: none
- Response format: response is ignored.
Called to retrieve the current message element to create a reading glass IFRAME for and/or fetch the message contents to decrypt.
-
Request parameters: none
-
Response format:
{ selector: string // document.querySelector-compatible selector of the element with the active message. body: string // Text contents of the active message, with HTML formatting removed. }
Called to check if the web application has a currently open draft message to fetch it contents to encrypt or decrypt.
-
Request parameters: none
-
Response format:
boolean // does the web application have the active draft message
###getActiveDraft
Called to fetch the currently opened message draft, to start an encrypt/decrypt/sign UI flow.
-
Request parameters: none
-
Response format:
{ to: Array<{address: string, name: string}> // Intended recipients of the currently active message draft. E-mails in the address fields. cc: Array<{address: string, name: string}> // Recipients to CC body: string // Text contents of the active draft, with HTML formatting removed. subject: string|undefined // Message subject. Optional }
Called to set the new (usually encrypted or signed) message body and recipients in the active message draft.
-
Request parameters:
{ to: Array<{address: string, name: string}> // Intended recipients of the currently active message draft. E-mails in the address fields. cc: Array<{address: string, name: string}> // Recipients to CC body: string // Text contents of the draft, with HTML formatting removed. subject: string|undefined // Message subject. Optional. }
-
Response format:
boolean // Was the draft updated successfully
As of now, no requests from web applications are defined. Available requests will be added in the future, provided they do not introduce additional threats described in our Threat model.
In order to provide better user experience, a timeout for responding to events is enforced. Currently the web application needs to respond to the bootstrap request in 1000 ms. Timeout for subsequent API requests is 100 ms.