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) { ...@@ -238,11 +238,17 @@ var callGCrWebFunction_ = function(functionPath, parameters) {
} }
/** /**
* Decrypts and executes the function specified in |payload|. * Decrypts and executes the function specified in |functionPayload|.
* @param {!string} payload The encrypted message payload. * @param {Object} encryptedMessageDetails JSON containing encrypted
* @param {!string} iv The initialization vector used to encrypt the |payload|. * 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_) { if (!frameSymmetricKey_) {
// Payload cannot be decrypted without a key. This message could be spam or // Payload cannot be decrypted without a key. This message could be spam or
// sent by the native application by mistake. // sent by the native application by mistake.
...@@ -250,43 +256,84 @@ var executeMessage_ = function(payload, iv) { ...@@ -250,43 +256,84 @@ var executeMessage_ = function(payload, iv) {
} }
// Decode the base64 payload. // Decode the base64 payload.
var encryptedFunctionArray = var encryptedMessageArray = new Uint8Array(Array.from(
new Uint8Array(Array.from(atob(payload)).map(function(a) { atob(encryptedMessageDetails['payload'])).map(function(a) {
return a.charCodeAt(0); return a.charCodeAt(0);
})); }));
// Decode the base64 initialization buffer. // 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); return a.charCodeAt(0);
})); }));
var messageAlgorithm = {'name': 'AES-GCM', iv: messageIvbuf};
var algorithm = {'name': 'AES-GCM', iv: ivbuf};
getFrameSymmetricKey_(function(frameKey) { getFrameSymmetricKey_(function(frameKey) {
window.crypto.subtle.decrypt(algorithm, frameKey, encryptedFunctionArray) window.crypto.subtle.decrypt(
.then(function(decrypted) { messageAlgorithm, frameKey, encryptedMessageArray)
var callJSON = new TextDecoder().decode(new Uint8Array(decrypted)); .then(function(decryptedMessagePayload) {
var callDict = JSON.parse(callJSON); var messageJSONString = new TextDecoder().decode(
new Uint8Array(decryptedMessagePayload));
var messageDict = JSON.parse(messageJSONString);
// Verify that message id is valid. // Verify that message id is valid.
if (!Number.isInteger(callDict['messageId']) || if (!Number.isInteger(messageDict['messageId']) ||
callDict['messageId'] <= lastReceivedMessageId_) { messageDict['messageId'] <= lastReceivedMessageId_) {
return; return;
} }
// Check that a function name and parameters are specified. lastReceivedMessageId_ = messageDict['messageId'];
if (typeof callDict['functionName'] !== 'string' ||
callDict['functionName'].length < 1 || // Return early if the function payload was dropped.
!Array.isArray(callDict['parameters'])) { if (!encryptedFunctionDetails) {
if (typeof messageDict['replyWithResult'] === 'boolean' &&
messageDict['replyWithResult']) {
replyWithResult_(messageDict['messageId'], null);
}
return; return;
} }
lastReceivedMessageId_ = callDict['messageId']; // Decode the base64 payload.
var result = var encryptedFunctionArray = new Uint8Array(Array.from(
callGCrWebFunction_(callDict['functionName'], callDict['parameters']); atob(encryptedFunctionDetails['payload'])).map(
if (typeof callDict['replyWithResult'] === 'boolean' && function(a) {
callDict['replyWithResult']) { return a.charCodeAt(0);
replyWithResult_(callDict['messageId'], result); }));
}
// 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() { ...@@ -316,19 +363,24 @@ __gCrWeb.message['getFrameId'] = function() {
* Routes an encrypted message to the targeted frame. Once the target frame is * 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 * found, the |payload| will be decrypted and executed. This function is called
* by the native code. * by the native code.
* @param {!string} payload The encrypted message payload. * @param {!Object} encryptedMessageDetails JSON representing a dictionary
* @param {!string} iv The initialization vector used to encrypt the |payload|. * 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 * @param {!string} target_frame_id The |frameId_| of the frame which should
* process the |payload|. * process the |payload|.
*/ */
__gCrWeb.message['routeMessage'] = function(payload, iv, target_frame_id) { __gCrWeb.message['routeMessage'] = function(encryptedMessageDetails,
encryptedFunctionDetails, target_frame_id) {
if (!isFrameMessagingSupported_()) { if (!isFrameMessagingSupported_()) {
// API is unsupported. // API is unsupported.
return; return;
} }
if (target_frame_id === __gCrWeb.message['getFrameId']()) { if (target_frame_id === __gCrWeb.message['getFrameId']()) {
executeMessage_(payload, iv); executeMessage_(encryptedMessageDetails, encryptedFunctionDetails);
return; return;
} }
...@@ -337,8 +389,8 @@ __gCrWeb.message['routeMessage'] = function(payload, iv, target_frame_id) { ...@@ -337,8 +389,8 @@ __gCrWeb.message['routeMessage'] = function(payload, iv, target_frame_id) {
window.frames[i].postMessage( window.frames[i].postMessage(
{ {
type: 'org.chromium.encryptedMessage', type: 'org.chromium.encryptedMessage',
payload: payload, message_payload: encryptedMessageDetails,
iv: iv, function_payload: encryptedFunctionDetails,
target_frame_id: target_frame_id target_frame_id: target_frame_id
}, },
'*' '*'
......
...@@ -37,12 +37,12 @@ window.addEventListener('message', function(message) { ...@@ -37,12 +37,12 @@ window.addEventListener('message', function(message) {
__gCrWeb.message['getExistingFrames'](); __gCrWeb.message['getExistingFrames']();
} else if (payload.hasOwnProperty('type') && } else if (payload.hasOwnProperty('type') &&
payload.type == 'org.chromium.encryptedMessage') { payload.type == 'org.chromium.encryptedMessage') {
if (payload.hasOwnProperty('payload') && if (payload.hasOwnProperty('message_payload') &&
payload.hasOwnProperty('iv') && payload.hasOwnProperty('function_payload') &&
payload.hasOwnProperty('target_frame_id')) { payload.hasOwnProperty('target_frame_id')) {
__gCrWeb.message['routeMessage']( __gCrWeb.message['routeMessage'](
payload['payload'], payload['message_payload'],
payload['iv'], payload['function_payload'],
payload['target_frame_id'] payload['target_frame_id']
); );
} }
......
...@@ -70,6 +70,11 @@ class WebFrameImpl : public WebFrame, public web::WebStateObserver { ...@@ -70,6 +70,11 @@ class WebFrameImpl : public WebFrame, public web::WebStateObserver {
void DetachFromWebState(); void DetachFromWebState();
// Returns the script command name to use for this WebFrame. // Returns the script command name to use for this WebFrame.
const std::string GetScriptCommandPrefix(); 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 // A structure to store the callbacks associated with the
// |CallJavaScriptFunction| requests. // |CallJavaScriptFunction| requests.
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/base64.h" #include "base/base64.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/json/json_writer.h" #include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
...@@ -85,6 +86,36 @@ bool WebFrameImpl::CanCallJavaScriptFunction() const { ...@@ -85,6 +86,36 @@ bool WebFrameImpl::CanCallJavaScriptFunction() const {
return is_main_frame_ || frame_key_; 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( bool WebFrameImpl::CallJavaScriptFunction(
const std::string& name, const std::string& name,
const std::vector<base::Value>& parameters, const std::vector<base::Value>& parameters,
...@@ -101,36 +132,28 @@ bool WebFrameImpl::CallJavaScriptFunction( ...@@ -101,36 +132,28 @@ bool WebFrameImpl::CallJavaScriptFunction(
reply_with_result); reply_with_result);
} }
base::DictionaryValue message; base::DictionaryValue message_payload;
message.SetKey("messageId", base::Value(message_id)); message_payload.SetKey("messageId", base::Value(message_id));
message.SetKey("replyWithResult", base::Value(reply_with_result)); message_payload.SetKey("replyWithResult", base::Value(reply_with_result));
message.SetKey("functionName", base::Value(name)); const std::string& encrypted_message_json =
base::ListValue parameters_value(parameters); EncryptPayload(std::move(message_payload), std::string());
message.SetKey("parameters", std::move(parameters_value));
std::string json;
base::JSONWriter::Write(message, &json);
crypto::Aead aead(crypto::Aead::AES_256_GCM); base::DictionaryValue function_payload;
aead.Init(&frame_key_->key()); function_payload.SetKey("functionName", base::Value(name));
base::ListValue parameters_value(parameters);
std::string iv; function_payload.SetKey("parameters", std::move(parameters_value));
crypto::RandBytes(base::WriteInto(&iv, aead.NonceLength() + 1), const std::string& encrypted_function_json = EncryptPayload(
aead.NonceLength()); std::move(function_payload), base::NumberToString(message_id));
std::string ciphertext; if (encrypted_message_json.empty() || encrypted_function_json.empty()) {
if (!aead.Seal(json, iv, /*additional_data=*/nullptr, &ciphertext)) { // Sealing the payload failed.
LOG(ERROR) << "Error sealing message for WebFrame.";
return false; return false;
} }
std::string encoded_iv; std::string script =
base::Base64Encode(iv, &encoded_iv); base::StringPrintf("__gCrWeb.message.routeMessage(%s, %s, '%s')",
std::string encoded_message; encrypted_message_json.c_str(),
base::Base64Encode(ciphertext, &encoded_message); encrypted_function_json.c_str(), frame_id_.c_str());
std::string script = base::StringPrintf(
"__gCrWeb.message.routeMessage('%s', '%s', '%s')",
encoded_message.c_str(), encoded_iv.c_str(), frame_id_.c_str());
GetWebState()->ExecuteJavaScript(base::UTF8ToUTF16(script)); GetWebState()->ExecuteJavaScript(base::UTF8ToUTF16(script));
return true; 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