Commit 5fcf81dd authored by Chris Lu's avatar Chris Lu Committed by Commit Bot

[ios] Split WebFrame JavaScript message payload

Separate function name and parameters from the rest of the Message payload.

This change encrypts the function name and its parameters in a separate payload
from the messageId and replyWithResult values. __gCrWeb.message.routeMessage() is
then passed two dictionaries, each containing an encrypted payload and its
initialization vector.

Both payloads are decrypted and their properties verified in executeMessage_().
This will allow for the function name and parameters to be dropped when
forwarding to a child frame, while still providing enough details for the
child frame to respond to the native side that it received the message.

Bug: 994968
Change-Id: I659d88b8119b2eba2b125d8c30cbea88485c88f1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1759204Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Reviewed-by: default avatarChris Palmer <palmer@chromium.org>
Reviewed-by: default avatarMike Dougherty <michaeldo@chromium.org>
Commit-Queue: Chris Lu <thegreenfrog@chromium.org>
Cr-Commit-Position: refs/heads/master@{#694992}
parent fd46f03c
......@@ -238,11 +238,17 @@ var callGCrWebFunction_ = function(functionPath, parameters) {
}
/**
* Decrypts and executes the function specified in |payload|.
* @param {!string} payload The encrypted message payload.
* @param {!string} iv The initialization vector used to encrypt the |payload|.
* Decrypts and executes the function specified in |functionPayload|.
* @param {Object} encryptedMessageDetails JSON containing encrypted
* information about the message and the initialization vector to decrypt
* the information.
* @param {!Object} encryptedFunctionDetails JSON containing encrypted
* information about the function and its parameters and the initialization
* vector to decrypt the information. If null, won't execute any call, but
* will respond to the native call if specified in |encryptedMessageDetails|.
*/
var executeMessage_ = function(payload, iv) {
var executeMessage_ = function(encryptedMessageDetails,
encryptedFunctionDetails) {
if (!frameSymmetricKey_) {
// Payload cannot be decrypted without a key. This message could be spam or
// sent by the native application by mistake.
......@@ -250,43 +256,84 @@ var executeMessage_ = function(payload, iv) {
}
// Decode the base64 payload.
var encryptedFunctionArray =
new Uint8Array(Array.from(atob(payload)).map(function(a) {
var encryptedMessageArray = new Uint8Array(Array.from(
atob(encryptedMessageDetails['payload'])).map(function(a) {
return a.charCodeAt(0);
}));
// Decode the base64 initialization buffer.
var ivbuf = new Uint8Array(Array.from(atob(iv)).map(function(a) {
var messageIvbuf = new Uint8Array(Array.from(
atob(encryptedMessageDetails['iv'])).map(function(a) {
return a.charCodeAt(0);
}));
var algorithm = {'name': 'AES-GCM', iv: ivbuf};
var messageAlgorithm = {'name': 'AES-GCM', iv: messageIvbuf};
getFrameSymmetricKey_(function(frameKey) {
window.crypto.subtle.decrypt(algorithm, frameKey, encryptedFunctionArray)
.then(function(decrypted) {
var callJSON = new TextDecoder().decode(new Uint8Array(decrypted));
var callDict = JSON.parse(callJSON);
window.crypto.subtle.decrypt(
messageAlgorithm, frameKey, encryptedMessageArray)
.then(function(decryptedMessagePayload) {
var messageJSONString = new TextDecoder().decode(
new Uint8Array(decryptedMessagePayload));
var messageDict = JSON.parse(messageJSONString);
// Verify that message id is valid.
if (!Number.isInteger(callDict['messageId']) ||
callDict['messageId'] <= lastReceivedMessageId_) {
if (!Number.isInteger(messageDict['messageId']) ||
messageDict['messageId'] <= lastReceivedMessageId_) {
return;
}
// Check that a function name and parameters are specified.
if (typeof callDict['functionName'] !== 'string' ||
callDict['functionName'].length < 1 ||
!Array.isArray(callDict['parameters'])) {
lastReceivedMessageId_ = messageDict['messageId'];
// Return early if the function payload was dropped.
if (!encryptedFunctionDetails) {
if (typeof messageDict['replyWithResult'] === 'boolean' &&
messageDict['replyWithResult']) {
replyWithResult_(messageDict['messageId'], null);
}
return;
}
lastReceivedMessageId_ = callDict['messageId'];
var result =
callGCrWebFunction_(callDict['functionName'], callDict['parameters']);
if (typeof callDict['replyWithResult'] === 'boolean' &&
callDict['replyWithResult']) {
replyWithResult_(callDict['messageId'], result);
}
// Decode the base64 payload.
var encryptedFunctionArray = new Uint8Array(Array.from(
atob(encryptedFunctionDetails['payload'])).map(
function(a) {
return a.charCodeAt(0);
}));
// Decode the base64 initialization buffer.
var functionIvbuf = new Uint8Array(Array.from(
atob(encryptedFunctionDetails['iv'])).map(
function(a) {
return a.charCodeAt(0);
}));
var additionalData = new Uint8Array(Array.from(
messageDict['messageId'].toString()).map(
function(a) {
return a.charCodeAt(0);
}));
var functionAlgorithm = {'name': 'AES-GCM',
iv: functionIvbuf,
additionalData: additionalData};
window.crypto.subtle.decrypt(
functionAlgorithm, frameKey, encryptedFunctionArray)
.then(function(decryptedFunctionPayload) {
var functionJSONPayload = new TextDecoder().decode(
new Uint8Array(decryptedFunctionPayload));
var functionDict = JSON.parse(functionJSONPayload);
let functionName = functionDict['functionName'];
let parameters = functionDict['parameters'];
var result = null;
if (typeof functionName === 'string' && functionName.length >= 1
&& Array.isArray(parameters)) {
result = callGCrWebFunction_(functionName, parameters);
}
if (typeof messageDict['replyWithResult'] === 'boolean'
&& messageDict['replyWithResult']) {
replyWithResult_(messageDict['messageId'], result);
}
});
});
});
};
......@@ -316,19 +363,24 @@ __gCrWeb.message['getFrameId'] = function() {
* Routes an encrypted message to the targeted frame. Once the target frame is
* found, the |payload| will be decrypted and executed. This function is called
* by the native code.
* @param {!string} payload The encrypted message payload.
* @param {!string} iv The initialization vector used to encrypt the |payload|.
* @param {!Object} encryptedMessageDetails JSON representing a dictionary
* containing encrypted information about the message and the initialization
* vector to decrypt the information.
* @param {!Object} encryptedFunctionDetails JSON representing a dictionary
* containing encrypted information about the function to call and the
* parameters to pass and the initialization vector to decrypt the information.
* @param {!string} target_frame_id The |frameId_| of the frame which should
* process the |payload|.
*/
__gCrWeb.message['routeMessage'] = function(payload, iv, target_frame_id) {
__gCrWeb.message['routeMessage'] = function(encryptedMessageDetails,
encryptedFunctionDetails, target_frame_id) {
if (!isFrameMessagingSupported_()) {
// API is unsupported.
return;
}
if (target_frame_id === __gCrWeb.message['getFrameId']()) {
executeMessage_(payload, iv);
executeMessage_(encryptedMessageDetails, encryptedFunctionDetails);
return;
}
......@@ -337,8 +389,8 @@ __gCrWeb.message['routeMessage'] = function(payload, iv, target_frame_id) {
window.frames[i].postMessage(
{
type: 'org.chromium.encryptedMessage',
payload: payload,
iv: iv,
message_payload: encryptedMessageDetails,
function_payload: encryptedFunctionDetails,
target_frame_id: target_frame_id
},
'*'
......
......@@ -37,12 +37,12 @@ window.addEventListener('message', function(message) {
__gCrWeb.message['getExistingFrames']();
} else if (payload.hasOwnProperty('type') &&
payload.type == 'org.chromium.encryptedMessage') {
if (payload.hasOwnProperty('payload') &&
payload.hasOwnProperty('iv') &&
if (payload.hasOwnProperty('message_payload') &&
payload.hasOwnProperty('function_payload') &&
payload.hasOwnProperty('target_frame_id')) {
__gCrWeb.message['routeMessage'](
payload['payload'],
payload['iv'],
payload['message_payload'],
payload['function_payload'],
payload['target_frame_id']
);
}
......
......@@ -70,6 +70,11 @@ class WebFrameImpl : public WebFrame, public web::WebStateObserver {
void DetachFromWebState();
// Returns the script command name to use for this WebFrame.
const std::string GetScriptCommandPrefix();
// Encrypts |payload| and returns a JSON string of a dictionary containing
// the encrypted metadata and its initialization vector. If encryption fails,
// an empty string will be returned.
const std::string EncryptPayload(base::DictionaryValue payload,
const std::string& additiona_data);
// A structure to store the callbacks associated with the
// |CallJavaScriptFunction| requests.
......
......@@ -9,6 +9,7 @@
#include "base/base64.h"
#include "base/bind.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
......@@ -85,6 +86,36 @@ bool WebFrameImpl::CanCallJavaScriptFunction() const {
return is_main_frame_ || frame_key_;
}
const std::string WebFrameImpl::EncryptPayload(
base::DictionaryValue payload,
const std::string& additiona_data) {
crypto::Aead aead(crypto::Aead::AES_256_GCM);
aead.Init(&frame_key_->key());
std::string payload_json;
base::JSONWriter::Write(payload, &payload_json);
std::string payload_iv;
crypto::RandBytes(base::WriteInto(&payload_iv, aead.NonceLength() + 1),
aead.NonceLength());
std::string payload_ciphertext;
if (!aead.Seal(payload_json, payload_iv, additiona_data,
&payload_ciphertext)) {
LOG(ERROR) << "Error sealing message payload for WebFrame.";
return std::string();
}
std::string encoded_payload_iv;
base::Base64Encode(payload_iv, &encoded_payload_iv);
std::string encoded_payload;
base::Base64Encode(payload_ciphertext, &encoded_payload);
std::string payload_string;
base::DictionaryValue payload_dict;
payload_dict.SetKey("payload", base::Value(encoded_payload));
payload_dict.SetKey("iv", base::Value(encoded_payload_iv));
base::JSONWriter::Write(payload_dict, &payload_string);
return payload_string;
}
bool WebFrameImpl::CallJavaScriptFunction(
const std::string& name,
const std::vector<base::Value>& parameters,
......@@ -101,36 +132,28 @@ bool WebFrameImpl::CallJavaScriptFunction(
reply_with_result);
}
base::DictionaryValue message;
message.SetKey("messageId", base::Value(message_id));
message.SetKey("replyWithResult", base::Value(reply_with_result));
message.SetKey("functionName", base::Value(name));
base::ListValue parameters_value(parameters);
message.SetKey("parameters", std::move(parameters_value));
std::string json;
base::JSONWriter::Write(message, &json);
base::DictionaryValue message_payload;
message_payload.SetKey("messageId", base::Value(message_id));
message_payload.SetKey("replyWithResult", base::Value(reply_with_result));
const std::string& encrypted_message_json =
EncryptPayload(std::move(message_payload), std::string());
crypto::Aead aead(crypto::Aead::AES_256_GCM);
aead.Init(&frame_key_->key());
std::string iv;
crypto::RandBytes(base::WriteInto(&iv, aead.NonceLength() + 1),
aead.NonceLength());
base::DictionaryValue function_payload;
function_payload.SetKey("functionName", base::Value(name));
base::ListValue parameters_value(parameters);
function_payload.SetKey("parameters", std::move(parameters_value));
const std::string& encrypted_function_json = EncryptPayload(
std::move(function_payload), base::NumberToString(message_id));
std::string ciphertext;
if (!aead.Seal(json, iv, /*additional_data=*/nullptr, &ciphertext)) {
LOG(ERROR) << "Error sealing message for WebFrame.";
if (encrypted_message_json.empty() || encrypted_function_json.empty()) {
// Sealing the payload failed.
return false;
}
std::string encoded_iv;
base::Base64Encode(iv, &encoded_iv);
std::string encoded_message;
base::Base64Encode(ciphertext, &encoded_message);
std::string script = base::StringPrintf(
"__gCrWeb.message.routeMessage('%s', '%s', '%s')",
encoded_message.c_str(), encoded_iv.c_str(), frame_id_.c_str());
std::string script =
base::StringPrintf("__gCrWeb.message.routeMessage(%s, %s, '%s')",
encrypted_message_json.c_str(),
encrypted_function_json.c_str(), frame_id_.c_str());
GetWebState()->ExecuteJavaScript(base::UTF8ToUTF16(script));
return true;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment