Commit a3609ba7 authored by Mike Dougherty's avatar Mike Dougherty Committed by Commit Bot

Add support for WebFrames without an encryption key.

Bug: 881816
Cq-Include-Trybots: luci.chromium.try:ios-simulator-cronet;luci.chromium.try:ios-simulator-full-configs
Change-Id: I695dac7e9aa0b2c6e08d5e661d56afda12d140b8
Reviewed-on: https://chromium-review.googlesource.com/1215042
Commit-Queue: Mike Dougherty <michaeldo@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Cr-Commit-Position: refs/heads/master@{#590197}
parent 98d93c3a
......@@ -2485,25 +2485,28 @@ registerLoadRequestForURL:(const GURL&)requestURL
web::WebFramesManagerImpl::FromWebState([self webState]);
std::string frameID = base::SysNSStringToUTF8(message.body[@"crwFrameId"]);
std::string encodedFrameKeyString =
base::SysNSStringToUTF8(message.body[@"crwFrameKey"]);
NSNumber* lastSentMessageID = message.body[@"crwFrameLastReceivedMessageId"];
if (!framesManager->GetFrameWithId(frameID)) {
GURL messageFrameOrigin =
web::GURLOriginWithWKSecurityOrigin(message.frameInfo.securityOrigin);
std::string decodedFrameKeyString;
std::string encodedFrameKeyString =
base::SysNSStringToUTF8(message.body[@"crwFrameKey"]);
base::Base64Decode(encodedFrameKeyString, &decodedFrameKeyString);
std::unique_ptr<crypto::SymmetricKey> frameKey =
crypto::SymmetricKey::Import(crypto::SymmetricKey::Algorithm::AES,
decodedFrameKeyString);
if (frameKey) {
int initialMessageID = lastSentMessageID.intValue == INT_MAX
? 0
: lastSentMessageID.intValue + 1;
auto newFrame = std::make_unique<web::WebFrameImpl>(
frameID, std::move(frameKey), initialMessageID,
message.frameInfo.mainFrame, messageFrameOrigin, self.webState);
frameID, message.frameInfo.mainFrame, messageFrameOrigin,
self.webState);
newFrame->SetEncryptionKey(std::move(frameKey));
NSNumber* lastSentMessageID =
message.body[@"crwFrameLastReceivedMessageId"];
int nextMessageID = std::max(0, lastSentMessageID.intValue + 1);
newFrame->SetNextMessageId(nextMessageID);
framesManager->AddFrame(std::move(newFrame));
_webStateImpl->OnWebFrameAvailable(
framesManager->GetFrameWithId(frameID));
......
......@@ -27,13 +27,15 @@ class WebFrameImpl : public WebFrame, public web::WebStateObserver {
// of the next message sent to the frame with the |CallJavaScriptFunction|
// API.
WebFrameImpl(const std::string& frame_id,
std::unique_ptr<crypto::SymmetricKey> frame_key,
int initial_message_id,
bool is_main_frame,
GURL security_origin,
web::WebState* web_state);
~WebFrameImpl() override;
// Sets the value to use for the next message ID.
void SetNextMessageId(int message_id);
// Sets the key to use for message encryption.
void SetEncryptionKey(std::unique_ptr<crypto::SymmetricKey> frame_key);
// The associated web state.
WebState* GetWebState();
......@@ -79,6 +81,27 @@ class WebFrameImpl : public WebFrame, public web::WebStateObserver {
std::unique_ptr<TimeoutCallback> timeout_callback;
};
// Calls the JavaScript function |name| in the web state (main frame). If
// |reply_with_result| is true, the return value of executing the function
// will be sent back to the receiver. This function is only used if the
// receiver does not have an encryption key. The JavaScript function is called
// directly and thus only works on the main frame. (Encryption is not required
// to securely communicate with the main frame because evaluating JavaScript
// on the WebState is already secure.)
bool ExecuteJavaScriptFunction(const std::string& name,
const std::vector<base::Value>& parameters,
int message_id,
bool reply_with_result);
// Runs the request associated with the message with id |message_id|. The
// completion callback, if any, associated with |message_id| will be called
// with |result|.
void CompleteRequest(int message_id, const base::Value* result);
// Calls the completion block of |request_callbacks| with |result| value and
// removes the callbacks from |pending_requests|.
void CompleteRequest(std::unique_ptr<RequestCallbacks> request_callbacks,
const base::Value* result);
// Cancels the request associated with the message with id |message_id|. The
// completion callback, if any, associated with |message_id| will be called
// with a null result value. Note that the JavaScript will still run to
......@@ -87,10 +110,6 @@ class WebFrameImpl : public WebFrame, public web::WebStateObserver {
// Performs |CancelRequest| on all outstanding request callbacks in
// |pending_requests_|.
void CancelPendingRequests();
// Calls the completion block of |request_callbacks| with a null value to
// represent the request was cancelled.
void CancelRequestWithCallbacks(
std::unique_ptr<RequestCallbacks> request_callbacks);
// Handles message from JavaScript with result of executing the function
// specified in CallJavaScriptFunction.
......
......@@ -4,9 +4,12 @@
#include "ios/web/web_state/web_frame_impl.h"
#import <Foundation/Foundation.h>
#include "base/base64.h"
#include "base/json/json_writer.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/values.h"
......@@ -14,7 +17,6 @@
#include "crypto/random.h"
#import "ios/web/public/web_state/web_state.h"
#include "ios/web/public/web_task_traits.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
......@@ -28,18 +30,13 @@ const char kJavaScriptReplyCommandPrefix[] = "frameMessaging_";
namespace web {
WebFrameImpl::WebFrameImpl(const std::string& frame_id,
std::unique_ptr<crypto::SymmetricKey> frame_key,
int initial_message_id,
bool is_main_frame,
GURL security_origin,
web::WebState* web_state)
: frame_id_(frame_id),
frame_key_(std::move(frame_key)),
next_message_id_(initial_message_id),
is_main_frame_(is_main_frame),
security_origin_(security_origin),
web_state_(web_state) {
DCHECK(frame_key_);
DCHECK(web_state);
web_state->AddObserver(this);
......@@ -54,6 +51,15 @@ WebFrameImpl::~WebFrameImpl() {
DetachFromWebState();
}
void WebFrameImpl::SetEncryptionKey(
std::unique_ptr<crypto::SymmetricKey> frame_key) {
frame_key_ = std::move(frame_key);
}
void WebFrameImpl::SetNextMessageId(int message_id) {
next_message_id_ = message_id;
}
WebState* WebFrameImpl::GetWebState() {
return web_state_;
}
......@@ -77,6 +83,11 @@ bool WebFrameImpl::CallJavaScriptFunction(
int message_id = next_message_id_;
next_message_id_++;
if (!frame_key_) {
return ExecuteJavaScriptFunction(name, parameters, message_id,
reply_with_result);
}
base::DictionaryValue message;
message.SetKey("messageId", base::Value(message_id));
message.SetKey("replyWithResult", base::Value(reply_with_result));
......@@ -134,31 +145,73 @@ bool WebFrameImpl::CallJavaScriptFunction(
base::PostDelayedTaskWithTraits(FROM_HERE, {web::WebThread::UI},
timeout_callback_ptr->callback(), timeout);
return CallJavaScriptFunction(name, parameters, /*reply_with_result=*/true);
bool called =
CallJavaScriptFunction(name, parameters, /*reply_with_result=*/true);
if (!called) {
// Remove callbacks if the call failed.
auto request = pending_requests_.find(message_id);
if (request != pending_requests_.end()) {
pending_requests_.erase(request);
}
}
return called;
}
void WebFrameImpl::CancelRequest(int message_id) {
bool WebFrameImpl::ExecuteJavaScriptFunction(
const std::string& name,
const std::vector<base::Value>& parameters,
int message_id,
bool reply_with_result) {
if (!IsMainFrame()) {
return false;
}
NSMutableArray* parameter_strings = [[NSMutableArray alloc] init];
// std::string parameters_string;
for (const auto& value : parameters) {
std::string string_value;
base::JSONWriter::Write(value, &string_value);
[parameter_strings addObject:base::SysUTF8ToNSString(string_value)];
}
NSString* script = [NSString
stringWithFormat:@"__gCrWeb.%s(%@)", name.c_str(),
[parameter_strings componentsJoinedByString:@","]];
GetWebState()->ExecuteJavaScript(base::SysNSStringToUTF16(script),
base::BindOnce(^(const base::Value* result) {
CompleteRequest(message_id, result);
}));
return true;
}
void WebFrameImpl::CompleteRequest(int message_id, const base::Value* result) {
auto request = pending_requests_.find(message_id);
if (request == pending_requests_.end()) {
return;
}
CancelRequestWithCallbacks(std::move(request->second));
CompleteRequest(std::move(request->second), result);
pending_requests_.erase(request);
}
void WebFrameImpl::CompleteRequest(
std::unique_ptr<RequestCallbacks> request_callbacks,
const base::Value* result) {
request_callbacks->timeout_callback->Cancel();
std::move(request_callbacks->completion).Run(result);
}
void WebFrameImpl::CancelRequest(int message_id) {
CompleteRequest(message_id, /*result=*/nullptr);
}
void WebFrameImpl::CancelPendingRequests() {
for (auto& it : pending_requests_) {
CancelRequestWithCallbacks(std::move(it.second));
CompleteRequest(std::move(it.second), /*result=*/nullptr);
}
pending_requests_.clear();
}
void WebFrameImpl::CancelRequestWithCallbacks(
std::unique_ptr<RequestCallbacks> request_callbacks) {
request_callbacks->timeout_callback->Cancel();
std::move(request_callbacks->completion).Run(nullptr);
}
bool WebFrameImpl::OnJavaScriptReply(web::WebState* web_state,
const base::DictionaryValue& command_json,
const GURL& page_url,
......
......@@ -79,8 +79,7 @@ typedef web::WebTest WebFrameImplTest;
TEST_F(WebFrameImplTest, CreateWebFrameForMainFrame) {
TestWebState test_web_state;
GURL security_origin;
WebFrameImpl web_frame(kFrameId, CreateKey(), /*initial_message_id=*/0,
/*is_main_frame=*/true, security_origin,
WebFrameImpl web_frame(kFrameId, /*is_main_frame=*/true, security_origin,
&test_web_state);
EXPECT_EQ(&test_web_state, web_frame.GetWebState());
......@@ -93,8 +92,7 @@ TEST_F(WebFrameImplTest, CreateWebFrameForMainFrame) {
TEST_F(WebFrameImplTest, CreateWebFrameForIFrame) {
TestWebState test_web_state;
GURL security_origin;
WebFrameImpl web_frame(kFrameId, CreateKey(), /*initial_message_id=*/0,
/*is_main_frame=*/false, security_origin,
WebFrameImpl web_frame(kFrameId, /*is_main_frame=*/false, security_origin,
&test_web_state);
EXPECT_EQ(&test_web_state, web_frame.GetWebState());
......@@ -108,13 +106,14 @@ TEST_F(WebFrameImplTest, CreateWebFrameForIFrame) {
TEST_F(WebFrameImplTest, CallJavaScriptFunction) {
TestWebState test_web_state;
GURL security_origin;
WebFrameImpl web_frame(kFrameId, CreateKey(), /*initial_message_id=*/0,
/*is_main_frame=*/false, security_origin,
WebFrameImpl web_frame(kFrameId, /*is_main_frame=*/false, security_origin,
&test_web_state);
web_frame.SetEncryptionKey(CreateKey());
std::vector<base::Value> function_params;
function_params.push_back(base::Value("plaintextParam"));
web_frame.CallJavaScriptFunction("functionName", function_params);
EXPECT_TRUE(
web_frame.CallJavaScriptFunction("functionName", function_params));
NSString* last_script =
base::SysUTF16ToNSString(test_web_state.GetLastExecutedJavascript());
......@@ -151,13 +150,14 @@ TEST_F(WebFrameImplTest, CallJavaScriptFunction) {
TEST_F(WebFrameImplTest, CallJavaScriptFunctionUniqueInitializationVector) {
TestWebState test_web_state;
GURL security_origin;
WebFrameImpl web_frame(kFrameId, CreateKey(), /*initial_message_id=*/0,
/*is_main_frame=*/false, security_origin,
WebFrameImpl web_frame(kFrameId, /*is_main_frame=*/false, security_origin,
&test_web_state);
web_frame.SetEncryptionKey(CreateKey());
std::vector<base::Value> function_params;
function_params.push_back(base::Value("plaintextParam"));
web_frame.CallJavaScriptFunction("functionName", function_params);
EXPECT_TRUE(
web_frame.CallJavaScriptFunction("functionName", function_params));
NSString* last_script1 =
base::SysUTF16ToNSString(test_web_state.GetLastExecutedJavascript());
......@@ -166,7 +166,8 @@ TEST_F(WebFrameImplTest, CallJavaScriptFunctionUniqueInitializationVector) {
// Call JavaScript Function again to verify that the same initialization
// vector is not reused and that the ciphertext is different.
web_frame.CallJavaScriptFunction("functionName", function_params);
EXPECT_TRUE(
web_frame.CallJavaScriptFunction("functionName", function_params));
NSString* last_script2 =
base::SysUTF16ToNSString(test_web_state.GetLastExecutedJavascript());
RouteMessageParameters params2 =
......@@ -187,14 +188,16 @@ TEST_F(WebFrameImplTest, CallJavaScriptFunctionMessageProperlyEncoded) {
TestWebState test_web_state;
GURL security_origin;
WebFrameImpl web_frame(kFrameId, std::move(key), initial_message_id,
/*is_main_frame=*/false, security_origin,
WebFrameImpl web_frame(kFrameId, /*is_main_frame=*/false, security_origin,
&test_web_state);
web_frame.SetEncryptionKey(std::move(key));
web_frame.SetNextMessageId(initial_message_id);
std::vector<base::Value> function_params;
std::string plaintext_param("plaintextParam");
function_params.push_back(base::Value(plaintext_param));
web_frame.CallJavaScriptFunction("functionName", function_params);
EXPECT_TRUE(
web_frame.CallJavaScriptFunction("functionName", function_params));
NSString* last_script =
base::SysUTF16ToNSString(test_web_state.GetLastExecutedJavascript());
......@@ -253,17 +256,19 @@ TEST_F(WebFrameImplTest, CallJavaScriptFunctionRespondWithResult) {
TestWebState test_web_state;
GURL security_origin;
WebFrameImpl web_frame(kFrameId, std::move(key), initial_message_id,
/*is_main_frame=*/false, security_origin,
WebFrameImpl web_frame(kFrameId, /*is_main_frame=*/false, security_origin,
&test_web_state);
web_frame.SetEncryptionKey(std::move(key));
web_frame.SetNextMessageId(initial_message_id);
std::vector<base::Value> function_params;
std::string plaintext_param("plaintextParam");
function_params.push_back(base::Value(plaintext_param));
web_frame.CallJavaScriptFunction("functionName", function_params,
base::BindOnce(^(const base::Value* value){
}),
base::TimeDelta::FromSeconds(5));
EXPECT_TRUE(web_frame.CallJavaScriptFunction(
"functionName", function_params,
base::BindOnce(^(const base::Value* value){
}),
base::TimeDelta::FromSeconds(5)));
NSString* last_script =
base::SysUTF16ToNSString(test_web_state.GetLastExecutedJavascript());
......@@ -298,4 +303,55 @@ TEST_F(WebFrameImplTest, CallJavaScriptFunctionRespondWithResult) {
EXPECT_TRUE(decrypted_respond_with_result);
}
// Tests that the WebFrame properly creates JavaScript for the main frame when
// there is no encryption key.
TEST_F(WebFrameImplTest, CallJavaScriptFunctionMainFrameWithoutKey) {
TestWebState test_web_state;
GURL security_origin;
WebFrameImpl web_frame(kFrameId, /*is_main_frame=*/true, security_origin,
&test_web_state);
std::vector<base::Value> function_params;
EXPECT_TRUE(
web_frame.CallJavaScriptFunction("functionName", function_params));
NSString* last_script =
base::SysUTF16ToNSString(test_web_state.GetLastExecutedJavascript());
EXPECT_NSEQ(@"__gCrWeb.functionName()", last_script);
function_params.push_back(base::Value("param1"));
EXPECT_TRUE(
web_frame.CallJavaScriptFunction("functionName", function_params));
last_script =
base::SysUTF16ToNSString(test_web_state.GetLastExecutedJavascript());
EXPECT_NSEQ(@"__gCrWeb.functionName(\"param1\")", last_script);
function_params.push_back(base::Value(true));
function_params.push_back(base::Value(27));
function_params.push_back(base::Value(3.14));
EXPECT_TRUE(
web_frame.CallJavaScriptFunction("functionName", function_params));
last_script =
base::SysUTF16ToNSString(test_web_state.GetLastExecutedJavascript());
EXPECT_NSEQ(@"__gCrWeb.functionName(\"param1\",true,27,3.14)", last_script);
}
// Tests that the WebFrame does not create JavaScript for an iframe when there
// is no encryption key.
TEST_F(WebFrameImplTest, CallJavaScriptFunctionIFrameFrameWithoutKey) {
TestWebState test_web_state;
GURL security_origin;
WebFrameImpl web_frame(kFrameId, /*is_main_frame=*/false, security_origin,
&test_web_state);
std::vector<base::Value> function_params;
function_params.push_back(base::Value("plaintextParam"));
EXPECT_FALSE(
web_frame.CallJavaScriptFunction("functionName", function_params));
NSString* last_script =
base::SysUTF16ToNSString(test_web_state.GetLastExecutedJavascript());
EXPECT_EQ(last_script.length, 0ul);
}
} // namespace web
......@@ -4,7 +4,6 @@
#include "ios/web/web_state/web_frames_manager_impl.h"
#include "base/base64.h"
#import "ios/web/public/test/fakes/test_web_state.h"
#include "ios/web/web_state/web_frame_impl.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -14,27 +13,14 @@
#error "This file requires ARC support."
#endif
using crypto::SymmetricKey;
namespace {
// A base64 encoded sample key.
const char kFrameKey[] = "d1uJzdvOFIUT5kEpK4o+x5JCaSlYT/a45ISU7S9EzTo=";
// Returns true if |web_frame| is contained in |frames|.
bool ContainsWebFrame(std::set<web::WebFrame*> frames,
web::WebFrame* web_frame) {
return frames.end() != std::find(frames.begin(), frames.end(), web_frame);
}
// Returns a key which can be used to create a WebFrame.
std::unique_ptr<SymmetricKey> CreateKey() {
std::string decoded_frame_key_string;
base::Base64Decode(kFrameKey, &decoded_frame_key_string);
return crypto::SymmetricKey::Import(crypto::SymmetricKey::Algorithm::AES,
decoded_frame_key_string);
}
} // namespace
namespace web {
......@@ -54,8 +40,7 @@ class WebFramesManagerImplTest : public PlatformTest {
TEST_F(WebFramesManagerImplTest, GetMainWebFrame) {
GURL security_origin;
auto web_frame = std::make_unique<WebFrameImpl>(
"web_frame", CreateKey(), /*initial_message_id=*/0,
/*is_main_frame=*/true, security_origin, &test_web_state_);
"web_frame", /*is_main_frame=*/true, security_origin, &test_web_state_);
WebFrameImpl* web_frame_ptr = web_frame.get();
frames_manager_->AddFrame(std::move(web_frame));
......@@ -72,9 +57,9 @@ TEST_F(WebFramesManagerImplTest, NoMainWebFrame) {
GURL security_origin;
const std::string web_frame_frame_id = "web_frame";
auto web_frame = std::make_unique<WebFrameImpl>(
web_frame_frame_id, CreateKey(), /*initial_message_id=*/0,
/*is_main_frame=*/true, security_origin, &test_web_state_);
auto web_frame =
std::make_unique<WebFrameImpl>(web_frame_frame_id, /*is_main_frame=*/true,
security_origin, &test_web_state_);
frames_manager_->AddFrame(std::move(web_frame));
frames_manager_->RemoveFrameWithId(web_frame_frame_id);
......@@ -88,13 +73,13 @@ TEST_F(WebFramesManagerImplTest, NoMainWebFrame) {
TEST_F(WebFramesManagerImplTest, AddFrames) {
GURL security_origin;
auto main_web_frame = std::make_unique<WebFrameImpl>(
"main_web_frame", CreateKey(), /*initial_message_id=*/0,
"main_web_frame",
/*is_main_frame=*/true, security_origin, &test_web_state_);
WebFrameImpl* main_web_frame_ptr = main_web_frame.get();
frames_manager_->AddFrame(std::move(main_web_frame));
auto child_web_frame = std::make_unique<WebFrameImpl>(
"child_web_frame", CreateKey(), /*initial_message_id=*/0,
"child_web_frame",
/*is_main_frame=*/false, security_origin, &test_web_state_);
WebFrameImpl* child_web_frame_ptr = child_web_frame.get();
frames_manager_->AddFrame(std::move(child_web_frame));
......@@ -109,19 +94,19 @@ TEST_F(WebFramesManagerImplTest, AddFrames) {
TEST_F(WebFramesManagerImplTest, RemoveFrame) {
GURL security_origin;
auto main_web_frame = std::make_unique<WebFrameImpl>(
"main_web_frame", CreateKey(), /*initial_message_id=*/0,
"main_web_frame",
/*is_main_frame=*/true, security_origin, &test_web_state_);
WebFrameImpl* main_web_frame_ptr = main_web_frame.get();
frames_manager_->AddFrame(std::move(main_web_frame));
const std::string child_web_frame_1_frame_id = "child_web_frame_1_frame_id";
auto child_web_frame_1 = std::make_unique<WebFrameImpl>(
child_web_frame_1_frame_id, CreateKey(), /*initial_message_id=*/0,
child_web_frame_1_frame_id,
/*is_main_frame=*/false, security_origin, &test_web_state_);
frames_manager_->AddFrame(std::move(child_web_frame_1));
auto child_web_frame_2 = std::make_unique<WebFrameImpl>(
"child_web_frame_2", CreateKey(), /*initial_message_id=*/0,
"child_web_frame_2",
/*is_main_frame=*/false, security_origin, &test_web_state_);
WebFrameImpl* child_web_frame_2_ptr = child_web_frame_2.get();
frames_manager_->AddFrame(std::move(child_web_frame_2));
......@@ -138,10 +123,10 @@ TEST_F(WebFramesManagerImplTest, RemoveFrame) {
TEST_F(WebFramesManagerImplTest, RemoveAllFrames) {
GURL security_origin;
frames_manager_->AddFrame(std::make_unique<WebFrameImpl>(
"main_web_frame", CreateKey(), /*initial_message_id=*/0,
"main_web_frame",
/*is_main_frame=*/true, security_origin, &test_web_state_));
frames_manager_->AddFrame(std::make_unique<WebFrameImpl>(
"web_frame", CreateKey(), /*initial_message_id=*/0,
"web_frame",
/*is_main_frame=*/false, security_origin, &test_web_state_));
ASSERT_EQ(2ul, frames_manager_->GetAllWebFrames().size());
......@@ -156,7 +141,7 @@ TEST_F(WebFramesManagerImplTest, RemoveNonexistantFrame) {
GURL security_origin;
const std::string main_web_frame_frame_id = "main_web_frame";
auto main_web_frame = std::make_unique<WebFrameImpl>(
main_web_frame_frame_id, CreateKey(), /*initial_message_id=*/0,
main_web_frame_frame_id,
/*is_main_frame=*/true, security_origin, &test_web_state_);
WebFrameImpl* main_web_frame_ptr = main_web_frame.get();
......@@ -177,7 +162,7 @@ TEST_F(WebFramesManagerImplTest, GetFrameWithId) {
const std::string web_frame_frame_id = "web_frame_frame_id";
auto web_frame = std::make_unique<WebFrameImpl>(
web_frame_frame_id, CreateKey(), /*initial_message_id=*/0,
web_frame_frame_id,
/*is_main_frame=*/false, security_origin, &test_web_state_);
WebFrameImpl* web_frame_ptr = web_frame.get();
frames_manager_->AddFrame(std::move(web_frame));
......
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