Commit d92a618e authored by Peter E Conn's avatar Peter E Conn Committed by Commit Bot

🤝 Perform Origin Verification locally.

Previously Digital Asset Link verification contacted a Google Server.
This had the disadvantage that verification would only work with servers
that were publicly accessible.

By doing the verification locally it will work with non-publicly
accessible servers (such as company internal sites).

Bug: 905698
Change-Id: Iaf00ae3b0d6193349310359183f3154c7f1d84f6
Reviewed-on: https://chromium-review.googlesource.com/c/1338180
Commit-Queue: Peter Conn <peconn@chromium.org>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Reviewed-by: default avatarPeter Beverloo <peter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#609664}
parent 8a7844d5
......@@ -19,34 +19,81 @@
#include "services/network/public/cpp/resource_response.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/origin.h"
namespace {
const char kDigitalAssetLinksBaseURL[] =
"https://digitalassetlinks.googleapis.com";
const char kDigitalAssetLinksCheckAPI[] = "/v1/assetlinks:check?";
const char kTargetOriginParam[] = "source.web.site";
const char kSourcePackageNameParam[] = "target.androidApp.packageName";
const char kSourceFingerprintParam[] =
"target.androidApp.certificate.sha256Fingerprint";
const char kRelationshipParam[] = "relation";
GURL GetUrlForCheckingRelationship(const std::string& web_domain,
const std::string& package_name,
const std::string& fingerprint,
const std::string& relationship) {
GURL request_url =
GURL(kDigitalAssetLinksBaseURL).Resolve(kDigitalAssetLinksCheckAPI);
request_url =
net::AppendQueryParameter(request_url, kTargetOriginParam, web_domain);
request_url = net::AppendQueryParameter(request_url, kSourcePackageNameParam,
package_name);
request_url = net::AppendQueryParameter(request_url, kSourceFingerprintParam,
fingerprint);
request_url =
net::AppendQueryParameter(request_url, kRelationshipParam, relationship);
DCHECK(request_url.is_valid());
return request_url;
// Location on a website where the asset links file can be found, see
// https://developers.google.com/digital-asset-links/v1/getting-started.
const char kAssetLinksAbsolutePath[] = ".well-known/assetlinks.json";
GURL GetUrlForAssetLinks(const url::Origin& origin) {
return origin.GetURL().Resolve(kAssetLinksAbsolutePath);
}
// An example, well formed asset links file for reference:
// [{
// "relation": ["delegate_permission/common.handle_all_urls"],
// "target": {
// "namespace": "android_app",
// "package_name": "com.peter.trustedpetersactivity",
// "sha256_cert_fingerprints": [
// "FA:2A:03: ... :9D"
// ]
// }
// }, {
// "relation": ["delegate_permission/common.handle_all_urls"],
// "target": {
// "namespace": "android_app",
// "package_name": "com.example.firstapp",
// "sha256_cert_fingerprints": [
// "64:2F:D4: ... :C1"
// ]
// }
// }]
bool StatementHasMatchingRelationship(const base::Value& statement,
const std::string& target_relation) {
const base::Value* relations =
statement.FindKeyOfType("relation", base::Value::Type::LIST);
if (!relations)
return false;
for (const auto& relation : relations->GetList()) {
if (relation.is_string() && relation.GetString() == target_relation)
return true;
}
return false;
}
bool StatementHasMatchingPackage(const base::Value& statement,
const std::string& target_package) {
const base::Value* package = statement.FindPathOfType(
{"target", "package_name"}, base::Value::Type::STRING);
return package && package->GetString() == target_package;
}
bool StatementHasMatchingFingerprint(const base::Value& statement,
const std::string& target_fingerprint) {
const base::Value* fingerprints = statement.FindPathOfType(
{"target", "sha256_cert_fingerprints"}, base::Value::Type::LIST);
if (!fingerprints)
return false;
for (const auto& fingerprint : fingerprints->GetList()) {
if (fingerprint.is_string() &&
fingerprint.GetString() == target_fingerprint) {
return true;
}
}
return false;
}
} // namespace
namespace digital_asset_links {
......@@ -60,6 +107,9 @@ DigitalAssetLinksHandler::DigitalAssetLinksHandler(
DigitalAssetLinksHandler::~DigitalAssetLinksHandler() = default;
void DigitalAssetLinksHandler::OnURLLoadComplete(
const std::string& package,
const std::string& fingerprint,
const std::string& relationship,
std::unique_ptr<std::string> response_body) {
int response_code = -1;
if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers)
......@@ -84,7 +134,8 @@ void DigitalAssetLinksHandler::OnURLLoadComplete(
/* connector=*/nullptr, // Connector is unused on Android.
*response_body,
base::Bind(&DigitalAssetLinksHandler::OnJSONParseSucceeded,
weak_ptr_factory_.GetWeakPtr()),
weak_ptr_factory_.GetWeakPtr(), package, fingerprint,
relationship),
base::Bind(&DigitalAssetLinksHandler::OnJSONParseFailed,
weak_ptr_factory_.GetWeakPtr()));
......@@ -92,13 +143,26 @@ void DigitalAssetLinksHandler::OnURLLoadComplete(
}
void DigitalAssetLinksHandler::OnJSONParseSucceeded(
std::unique_ptr<base::Value> result) {
base::Value* success = result->FindKeyOfType(
kDigitalAssetLinksCheckResponseKeyLinked, base::Value::Type::BOOLEAN);
const std::string& package,
const std::string& fingerprint,
const std::string& relationship,
std::unique_ptr<base::Value> statement_list) {
if (!statement_list->is_list()) {
std::move(callback_).Run(RelationshipCheckResult::FAILURE);
return;
}
std::move(callback_).Run(success && success->GetBool()
? RelationshipCheckResult::SUCCESS
: RelationshipCheckResult::FAILURE);
for (const auto& statement : statement_list->GetList()) {
if (statement.is_dict() &&
StatementHasMatchingRelationship(statement, relationship) &&
StatementHasMatchingPackage(statement, package) &&
StatementHasMatchingFingerprint(statement, fingerprint)) {
std::move(callback_).Run(RelationshipCheckResult::SUCCESS);
return;
}
}
std::move(callback_).Run(RelationshipCheckResult::FAILURE);
}
void DigitalAssetLinksHandler::OnJSONParseFailed(
......@@ -113,11 +177,11 @@ void DigitalAssetLinksHandler::OnJSONParseFailed(
bool DigitalAssetLinksHandler::CheckDigitalAssetLinkRelationship(
RelationshipCheckResultCallback callback,
const std::string& web_domain,
const std::string& package_name,
const std::string& package,
const std::string& fingerprint,
const std::string& relationship) {
GURL request_url = GetUrlForCheckingRelationship(web_domain, package_name,
fingerprint, relationship);
// TODO(peconn): Propegate the use of url::Origin backwards to clients.
GURL request_url = GetUrlForAssetLinks(url::Origin::Create(GURL(web_domain)));
if (!request_url.is_valid())
return false;
......@@ -159,7 +223,8 @@ bool DigitalAssetLinksHandler::CheckDigitalAssetLinkRelationship(
url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
shared_url_loader_factory_.get(),
base::BindOnce(&DigitalAssetLinksHandler::OnURLLoadComplete,
weak_ptr_factory_.GetWeakPtr()));
weak_ptr_factory_.GetWeakPtr(), package, fingerprint,
relationship));
return true;
}
......
......@@ -42,9 +42,9 @@ class DigitalAssetLinksHandler {
// Checks whether the given "relationship" has been declared by the target
// |web_domain| for the source Android app which is uniquely defined by the
// |package_name| and SHA256 |fingerprint| (a string with 32 hexadecimals
// with : in between) given. Any error in the string params
// here will result in a bad request and a nullptr response to the callback.
// |package| and SHA256 |fingerprint| (a string with 32 hexadecimals with :
// between) given. Any error in the string params here will result in a bad
// request and a nullptr response to the callback.
//
// Calling this multiple times on the same handler will cancel the previous
// checks.
......@@ -54,15 +54,21 @@ class DigitalAssetLinksHandler {
bool CheckDigitalAssetLinkRelationship(
RelationshipCheckResultCallback callback,
const std::string& web_domain,
const std::string& package_name,
const std::string& package,
const std::string& fingerprint,
const std::string& relationship);
private:
void OnURLLoadComplete(std::unique_ptr<std::string> response_body);
void OnURLLoadComplete(const std::string& package,
const std::string& fingerprint,
const std::string& relationship,
std::unique_ptr<std::string> response_body);
// Callbacks for the SafeJsonParser.
void OnJSONParseSucceeded(std::unique_ptr<base::Value> result);
void OnJSONParseSucceeded(const std::string& package,
const std::string& fingerprint,
const std::string& relationship,
std::unique_ptr<base::Value> result);
void OnJSONParseFailed(const std::string& error_message);
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
......
......@@ -21,6 +21,39 @@
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const char kStatementList[] = R"(
[{
"relation": ["other_relationship"],
"target": {
"namespace": "android_app",
"package_name": "com.peter.trustedpetersactivity",
"sha256_cert_fingerprints": [
"FA:2A:03:CB:38:9C:F3:BE:28:E3:CA:7F:DA:2E:FA:4F:4A:96:F3:BC:45:2C:08:A2:16:A1:5D:FD:AB:46:BC:9D"
]
}
}, {
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.firstapp",
"sha256_cert_fingerprints": [
"64:2F:D4:BE:1C:4D:F8:36:2E:D3:50:C4:69:53:96:A1:3D:14:0A:23:AD:2F:BF:EB:6E:C6:E4:64:54:3B:34:C1"
]
}
}]
)";
const char kDomain[] = "https://www.example.com";
const char kValidPackage[] = "com.example.firstapp";
const char kValidRelation[] = "delegate_permission/common.handle_all_urls";
const char kValidFingerprint[] =
"64:2F:D4:BE:1C:4D:F8:36:2E:D3:50:C4:69:53:96:A1:3D:14:0A:23:AD:2F:BF:EB:"
"6E:C6:E4:64:54:3B:34:C1";
} // namespace
namespace digital_asset_links {
namespace {
......@@ -45,50 +78,37 @@ class DigitalAssetLinksHandlerTest : public ::testing::Test {
&test_url_loader_factory_);
}
void AddResponse(net::Error error, int response_code, bool linked) {
std::string response_string;
if (error == net::OK && response_code == net::HTTP_OK && linked) {
response_string =
R"({
"linked": true ,
"maxAge": "40.188652381s"
})";
} else if (error == net::OK && response_code == net::HTTP_OK) {
response_string =
R"({
"linked": false ,
"maxAge": "40.188652381s"
})";
} else if (error == net::OK && response_code == net::HTTP_BAD_REQUEST) {
response_string =
R"({
"code": 400 ,
"message": "Invalid statement query received."
"status": "INVALID_ARGUMENT"
})";
}
auto& url = test_url_loader_factory_.pending_requests()->at(0).request.url;
if (response_string.empty()) {
network::ResourceResponseHead response_head;
std::string status_line =
"HTTP/1.1 " + base::NumberToString(response_code) + " " +
net::GetHttpReasonPhrase(
static_cast<net::HttpStatusCode>(response_code));
response_head.headers =
base::MakeRefCounted<net::HttpResponseHeaders>(status_line);
test_url_loader_factory_.AddResponse(
url, response_head, response_string,
network::URLLoaderCompletionStatus(error));
} else {
test_url_loader_factory_.AddResponse(
url.spec(), response_string,
static_cast<net::HttpStatusCode>(response_code));
}
void AddErrorResponse(net::Error error, int response_code) {
request_url_ =
test_url_loader_factory_.pending_requests()->at(0).request.url;
network::ResourceResponseHead response_head;
std::string status_line =
"HTTP/1.1 " + base::NumberToString(response_code) + " " +
net::GetHttpReasonPhrase(
static_cast<net::HttpStatusCode>(response_code));
response_head.headers =
base::MakeRefCounted<net::HttpResponseHeaders>(status_line);
test_url_loader_factory_.AddResponse(
request_url_, response_head, "",
network::URLLoaderCompletionStatus(error));
base::RunLoop().RunUntilIdle();
}
void AddResponse(const std::string& response) {
request_url_ =
test_url_loader_factory_.pending_requests()->at(0).request.url;
test_url_loader_factory_.AddResponse(request_url_.spec(), response,
net::HTTP_OK);
base::RunLoop().RunUntilIdle();
}
int num_invocations_;
RelationshipCheckResult result_;
GURL request_url_;
private:
base::test::ScopedTaskEnvironment task_environment_;
......@@ -100,25 +120,122 @@ class DigitalAssetLinksHandlerTest : public ::testing::Test {
};
} // namespace
TEST_F(DigitalAssetLinksHandlerTest, CorrectAssetLinksUrl) {
DigitalAssetLinksHandler handler(GetSharedURLLoaderFactory());
handler.CheckDigitalAssetLinkRelationship(
base::BindOnce(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
kDomain, kValidPackage, kValidFingerprint, kValidRelation);
AddResponse("");
EXPECT_EQ(request_url_,
"https://www.example.com/.well-known/assetlinks.json");
}
TEST_F(DigitalAssetLinksHandlerTest, PositiveResponse) {
DigitalAssetLinksHandler handler(GetSharedURLLoaderFactory());
handler.CheckDigitalAssetLinkRelationship(
base::Bind(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
"", "", "", "");
AddResponse(net::OK, net::HTTP_OK, true);
base::BindOnce(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
kDomain, kValidPackage, kValidFingerprint, kValidRelation);
AddResponse(kStatementList);
EXPECT_EQ(1, num_invocations_);
EXPECT_EQ(result_, RelationshipCheckResult::SUCCESS);
}
TEST_F(DigitalAssetLinksHandlerTest, NegativeResponse) {
TEST_F(DigitalAssetLinksHandlerTest, PackageMismatch) {
DigitalAssetLinksHandler handler(GetSharedURLLoaderFactory());
handler.CheckDigitalAssetLinkRelationship(
base::BindOnce(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
kDomain, "evil.package", kValidFingerprint, kValidRelation);
AddResponse(kStatementList);
EXPECT_EQ(1, num_invocations_);
EXPECT_EQ(result_, RelationshipCheckResult::FAILURE);
}
TEST_F(DigitalAssetLinksHandlerTest, SignatureMismatch) {
DigitalAssetLinksHandler handler(GetSharedURLLoaderFactory());
handler.CheckDigitalAssetLinkRelationship(
base::BindOnce(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
kDomain, kValidPackage, "66:66:66:66:66:66", kValidRelation);
AddResponse(kStatementList);
EXPECT_EQ(1, num_invocations_);
EXPECT_EQ(result_, RelationshipCheckResult::FAILURE);
}
TEST_F(DigitalAssetLinksHandlerTest, RelationshipMismatch) {
DigitalAssetLinksHandler handler(GetSharedURLLoaderFactory());
handler.CheckDigitalAssetLinkRelationship(
base::BindOnce(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
kDomain, kValidPackage, kValidFingerprint, "take_firstborn_child");
AddResponse(kStatementList);
EXPECT_EQ(1, num_invocations_);
EXPECT_EQ(result_, RelationshipCheckResult::FAILURE);
}
TEST_F(DigitalAssetLinksHandlerTest, StatementIsolation) {
// Ensure we don't merge separate statements together.
DigitalAssetLinksHandler handler(GetSharedURLLoaderFactory());
handler.CheckDigitalAssetLinkRelationship(
base::BindOnce(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
kDomain, kValidPackage, kValidFingerprint, "other_relationship");
AddResponse(kStatementList);
EXPECT_EQ(1, num_invocations_);
EXPECT_EQ(result_, RelationshipCheckResult::FAILURE);
}
TEST_F(DigitalAssetLinksHandlerTest, BadAssetLinks_Empty) {
DigitalAssetLinksHandler handler(GetSharedURLLoaderFactory());
handler.CheckDigitalAssetLinkRelationship(
base::BindOnce(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
kDomain, kValidPackage, kValidFingerprint, kValidRelation);
AddResponse("");
EXPECT_EQ(1, num_invocations_);
EXPECT_EQ(result_, RelationshipCheckResult::FAILURE);
}
TEST_F(DigitalAssetLinksHandlerTest, BadAssetLinks_NotList) {
DigitalAssetLinksHandler handler(GetSharedURLLoaderFactory());
handler.CheckDigitalAssetLinkRelationship(
base::BindOnce(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
kDomain, kValidPackage, kValidFingerprint, kValidRelation);
AddResponse(R"({ "key": "value"})");
EXPECT_EQ(1, num_invocations_);
EXPECT_EQ(result_, RelationshipCheckResult::FAILURE);
}
TEST_F(DigitalAssetLinksHandlerTest, BadAssetLinks_StatementNotDict) {
DigitalAssetLinksHandler handler(GetSharedURLLoaderFactory());
handler.CheckDigitalAssetLinkRelationship(
base::BindOnce(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
kDomain, kValidPackage, kValidFingerprint, kValidRelation);
AddResponse(R"([ [], [] ])");
EXPECT_EQ(1, num_invocations_);
EXPECT_EQ(result_, RelationshipCheckResult::FAILURE);
}
TEST_F(DigitalAssetLinksHandlerTest, BadAssetLinks_MissingFields) {
DigitalAssetLinksHandler handler(GetSharedURLLoaderFactory());
handler.CheckDigitalAssetLinkRelationship(
base::Bind(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
"", "", "", "");
AddResponse(net::OK, net::HTTP_OK, false);
base::BindOnce(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
kDomain, kValidPackage, kValidFingerprint, kValidRelation);
AddResponse(R"([ { "target" : {} } ])");
EXPECT_EQ(1, num_invocations_);
EXPECT_EQ(result_, RelationshipCheckResult::FAILURE);
......@@ -127,10 +244,10 @@ TEST_F(DigitalAssetLinksHandlerTest, NegativeResponse) {
TEST_F(DigitalAssetLinksHandlerTest, BadRequest) {
DigitalAssetLinksHandler handler(GetSharedURLLoaderFactory());
handler.CheckDigitalAssetLinkRelationship(
base::Bind(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
"", "", "", "");
AddResponse(net::OK, net::HTTP_BAD_REQUEST, true);
base::BindOnce(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
kDomain, kValidPackage, kValidFingerprint, kValidRelation);
AddErrorResponse(net::OK, net::HTTP_BAD_REQUEST);
EXPECT_EQ(1, num_invocations_);
EXPECT_EQ(result_, RelationshipCheckResult::FAILURE);
......@@ -139,10 +256,10 @@ TEST_F(DigitalAssetLinksHandlerTest, BadRequest) {
TEST_F(DigitalAssetLinksHandlerTest, NetworkError) {
DigitalAssetLinksHandler handler(GetSharedURLLoaderFactory());
handler.CheckDigitalAssetLinkRelationship(
base::Bind(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
"", "", "", "");
AddResponse(net::ERR_ABORTED, net::HTTP_OK, true);
base::BindOnce(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
kDomain, kValidPackage, kValidFingerprint, kValidRelation);
AddErrorResponse(net::ERR_ABORTED, net::HTTP_OK);
EXPECT_EQ(1, num_invocations_);
EXPECT_EQ(result_, RelationshipCheckResult::FAILURE);
......@@ -151,10 +268,10 @@ TEST_F(DigitalAssetLinksHandlerTest, NetworkError) {
TEST_F(DigitalAssetLinksHandlerTest, NetworkDisconnected) {
DigitalAssetLinksHandler handler(GetSharedURLLoaderFactory());
handler.CheckDigitalAssetLinkRelationship(
base::Bind(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
"", "", "", "");
AddResponse(net::ERR_INTERNET_DISCONNECTED, net::HTTP_OK, true);
base::BindOnce(&DigitalAssetLinksHandlerTest::OnRelationshipCheckComplete,
base::Unretained(this)),
kDomain, kValidPackage, kValidFingerprint, kValidRelation);
AddErrorResponse(net::ERR_INTERNET_DISCONNECTED, net::HTTP_OK);
EXPECT_EQ(1, num_invocations_);
EXPECT_EQ(result_, RelationshipCheckResult::NO_CONNECTION);
......
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