Commit 4a5ac609 authored by tguilbert's avatar tguilbert Committed by Commit bot

Add base::UnguessableToken

cc::SurfaceId, gpu::Mailbox and ScopedSurfaceRequestManager need an
unguessable identifier. Security recommends using 128 bits to make sure
an ID is unguessable. However, there is no conveniently serializable
way to represent 128 bits.

This change introduces base::UnguessableToken, a 128 bit class with a
cryptographically strong Create() function. UnguessableToken can be
used by themselves, or as part of an aggregate ID.

An empty UnguessableToken is a valid value. It is however illegal to
send empty UnguessableToken across processes (because the resource that
is supposed to be protected by the token would now be guessable).
Sending empty tokens across processes is a security issue, and should
be handled as such.

This change also introduces the appropriate code to send tokens
over IPC and Mojo. base::Optional should be used in cases where it may
be valid to send no token (rather than sending an empty token).

TEST=Added unittests. Also tested in a prototype that uses IPC and Mojo.
BUG=643857

Review-Url: https://codereview.chromium.org/2333443002
Cr-Commit-Position: refs/heads/master@{#419550}
parent 3b73138b
......@@ -969,6 +969,8 @@ component("base") {
"tracking_info.cc",
"tracking_info.h",
"tuple.h",
"unguessable_token.cc",
"unguessable_token.h",
"value_conversions.cc",
"value_conversions.h",
"values.cc",
......@@ -1987,6 +1989,7 @@ test("base_unittests") {
"trace_event/trace_event_unittest.cc",
"tracked_objects_unittest.cc",
"tuple_unittest.cc",
"unguessable_token_unittest.cc",
"values_unittest.cc",
"version_unittest.cc",
"vlog_unittest.cc",
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/unguessable_token.h"
#include "base/format_macros.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
namespace base {
UnguessableToken::UnguessableToken(uint64_t high, uint64_t low)
: high_(high), low_(low) {}
std::string UnguessableToken::ToString() const {
return base::StringPrintf("(%08" PRIX64 "%08" PRIX64 ")", high_, low_);
}
// static
UnguessableToken UnguessableToken::Create() {
UnguessableToken token;
// Use base::RandBytes instead of crypto::RandBytes, because crypto calls the
// base version directly, and to prevent the dependency from base/ to crypto/.
base::RandBytes(&token, sizeof(token));
return token;
}
// static
UnguessableToken UnguessableToken::Deserialize(uint64_t high, uint64_t low) {
// Receiving a zeroed out UnguessableToken from another process means that it
// was never initialized via Create(). Treat this case as a security issue.
DCHECK(!(high == 0 && low == 0));
return UnguessableToken(high, low);
}
} // namespace base
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_UNGUESSABLE_TOKEN_H_
#define BASE_UNGUESSABLE_TOKEN_H_
#include <stdint.h>
#include <string.h>
#include <tuple>
#include "base/base_export.h"
#include "base/hash.h"
#include "base/logging.h"
namespace base {
struct UnguessableTokenHash;
// A UnguessableToken is an 128-bit token generated from a cryptographically
// strong random source.
//
// UnguessableToken should be used when a sensitive ID needs to be unguessable,
// and is shared across processes. It can be used as part of a larger aggregate
// type, or as an ID in and of itself.
//
// Use Create() for creating new UnguessableTokens.
//
// NOTE: It is illegal to send empty UnguessableTokens across processes, and
// sending/receiving empty tokens should be treated as a security issue.
// If there is a valid scenario for sending "no token" across processes,
// base::Optional should be used instead of an empty token.
class BASE_EXPORT UnguessableToken {
public:
// Create a unique UnguessableToken.
static UnguessableToken Create();
// Return a UnguessableToken built from the high/low bytes provided.
// It should only be used in deserialization scenarios.
//
// NOTE: If the deserialized token is empty, it means that it was never
// initialized via Create(). This is a security issue, and should be handled.
static UnguessableToken Deserialize(uint64_t high, uint64_t low);
// Creates an empty UnguessableToken.
// Assign to it with Create() before using it.
UnguessableToken() = default;
// NOTE: Serializing an empty UnguessableToken is an illegal operation.
uint64_t GetHighForSerialization() const {
DCHECK(!is_empty());
return high_;
};
// NOTE: Serializing an empty UnguessableToken is an illegal operation.
uint64_t GetLowForSerialization() const {
DCHECK(!is_empty());
return low_;
}
bool is_empty() const { return high_ == 0 && low_ == 0; }
std::string ToString() const;
explicit operator bool() const { return !is_empty(); }
bool operator<(const UnguessableToken& other) const {
return std::tie(high_, low_) < std::tie(other.high_, other.low_);
}
bool operator==(const UnguessableToken& other) const {
return high_ == other.high_ && low_ == other.low_;
}
bool operator!=(const UnguessableToken& other) const {
return !(*this == other);
}
private:
friend struct UnguessableTokenHash;
UnguessableToken(uint64_t high, uint64_t low);
// Note: Two uint64_t are used instead of uint8_t[16], in order to have a
// simpler ToString() and is_empty().
uint64_t high_ = 0;
uint64_t low_ = 0;
};
// For use in std::unordered_map.
struct UnguessableTokenHash {
size_t operator()(const base::UnguessableToken& token) const {
DCHECK(token);
return base::HashInts64(token.high_, token.low_);
}
};
} // namespace base
#endif // BASE_UNGUESSABLE_TOKEN_H_
// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/unguessable_token.h"
#include <type_traits>
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
void TestSmallerThanOperator(const UnguessableToken& a,
const UnguessableToken& b) {
EXPECT_TRUE(a < b);
EXPECT_FALSE(b < a);
}
TEST(UnguessableTokenTest, VerifyEqualityOperators) {
// Deserialize is used for testing purposes.
// Use UnguessableToken::Create() in production code instead.
UnguessableToken token = UnguessableToken::Deserialize(1, 2);
UnguessableToken same_token = UnguessableToken::Deserialize(1, 2);
UnguessableToken diff_token = UnguessableToken::Deserialize(1, 3);
EXPECT_TRUE(token == token);
EXPECT_FALSE(token != token);
EXPECT_TRUE(token == same_token);
EXPECT_FALSE(token != same_token);
EXPECT_FALSE(token == diff_token);
EXPECT_FALSE(diff_token == token);
EXPECT_TRUE(token != diff_token);
EXPECT_TRUE(diff_token != token);
}
TEST(UnguessableTokenTest, VerifyConstructors) {
UnguessableToken token = UnguessableToken::Create();
EXPECT_FALSE(token.is_empty());
EXPECT_TRUE(token);
UnguessableToken copied_token(token);
EXPECT_TRUE(copied_token);
EXPECT_EQ(token, copied_token);
UnguessableToken uninitialized;
EXPECT_TRUE(uninitialized.is_empty());
EXPECT_FALSE(uninitialized);
EXPECT_TRUE(UnguessableToken().is_empty());
EXPECT_FALSE(UnguessableToken());
}
TEST(UnguessableTokenTest, VerifySerialization) {
UnguessableToken token = UnguessableToken::Create();
uint64_t high = token.GetHighForSerialization();
uint64_t low = token.GetLowForSerialization();
EXPECT_TRUE(high);
EXPECT_TRUE(low);
UnguessableToken Deserialized = UnguessableToken::Deserialize(high, low);
EXPECT_EQ(token, Deserialized);
}
TEST(UnguessableTokenTest, VerifyToString) {
UnguessableToken token = UnguessableToken::Deserialize(0x123, 0xABC);
std::string expected = "(0000012300000ABC)";
EXPECT_EQ(expected, token.ToString());
}
TEST(UnguessableTokenTest, VerifySmallerThanOperator) {
// Deserialize is used for testing purposes.
// Use UnguessableToken::Create() in production code instead.
{
SCOPED_TRACE("a.low < b.low and a.high == b.high.");
TestSmallerThanOperator(UnguessableToken::Deserialize(0, 1),
UnguessableToken::Deserialize(0, 5));
}
{
SCOPED_TRACE("a.low == b.low and a.high < b.high.");
TestSmallerThanOperator(UnguessableToken::Deserialize(1, 0),
UnguessableToken::Deserialize(5, 0));
}
{
SCOPED_TRACE("a.low < b.low and a.high < b.high.");
TestSmallerThanOperator(UnguessableToken::Deserialize(1, 1),
UnguessableToken::Deserialize(5, 5));
}
{
SCOPED_TRACE("a.low > b.low and a.high < b.high.");
TestSmallerThanOperator(UnguessableToken::Deserialize(1, 10),
UnguessableToken::Deserialize(10, 1));
}
}
TEST(UnguessableTokenTest, VerifyHash) {
UnguessableToken token = UnguessableToken::Create();
EXPECT_EQ(base::HashInts64(token.GetHighForSerialization(),
token.GetLowForSerialization()),
UnguessableTokenHash()(token));
}
TEST(UnguessableTokenTest, VerifyBasicUniqueness) {
EXPECT_NE(UnguessableToken::Create(), UnguessableToken::Create());
UnguessableToken token = UnguessableToken::Create();
EXPECT_NE(token.GetHighForSerialization(), token.GetLowForSerialization());
}
}
......@@ -13,6 +13,7 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "base/values.h"
#include "build/build_config.h"
#include "ipc/ipc_channel_handle.h"
......@@ -998,6 +999,45 @@ void ParamTraits<base::TimeTicks>::Log(const param_type& p, std::string* l) {
ParamTraits<int64_t>::Log(p.ToInternalValue(), l);
}
// If base::UnguessableToken is no longer 128 bits, the IPC serialization logic
// below should be updated.
static_assert(sizeof(base::UnguessableToken) == 2 * sizeof(uint64_t),
"base::UnguessableToken should be of size 2 * sizeof(uint64_t).");
void ParamTraits<base::UnguessableToken>::GetSize(base::PickleSizer* sizer,
const param_type& p) {
sizer->AddBytes(2 * sizeof(uint64_t));
}
void ParamTraits<base::UnguessableToken>::Write(base::Pickle* m,
const param_type& p) {
DCHECK(!p.is_empty());
ParamTraits<uint64_t>::Write(m, p.GetHighForSerialization());
ParamTraits<uint64_t>::Write(m, p.GetLowForSerialization());
}
bool ParamTraits<base::UnguessableToken>::Read(const base::Pickle* m,
base::PickleIterator* iter,
param_type* r) {
uint64_t high, low;
if (!ParamTraits<uint64_t>::Read(m, iter, &high) ||
!ParamTraits<uint64_t>::Read(m, iter, &low))
return false;
// Receiving a zeroed UnguessableToken is a security issue.
if (high == 0 && low == 0)
return false;
*r = base::UnguessableToken::Deserialize(high, low);
return true;
}
void ParamTraits<base::UnguessableToken>::Log(const param_type& p,
std::string* l) {
l->append(p.ToString());
}
void ParamTraits<IPC::ChannelHandle>::GetSize(base::PickleSizer* sizer,
const param_type& p) {
GetParamSize(sizer, p.name);
......
......@@ -40,6 +40,7 @@ class NullableString16;
class Time;
class TimeDelta;
class TimeTicks;
class UnguessableToken;
struct FileDescriptor;
#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN)
......@@ -666,6 +667,17 @@ struct IPC_EXPORT ParamTraits<base::TimeTicks> {
static void Log(const param_type& p, std::string* l);
};
template <>
struct IPC_EXPORT ParamTraits<base::UnguessableToken> {
typedef base::UnguessableToken param_type;
static void GetSize(base::PickleSizer* sizer, const param_type& p);
static void Write(base::Pickle* m, const param_type& p);
static bool Read(const base::Pickle* m,
base::PickleIterator* iter,
param_type* r);
static void Log(const param_type& p, std::string* l);
};
template <>
struct ParamTraits<std::tuple<>> {
typedef std::tuple<> param_type;
......
......@@ -10,6 +10,7 @@
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/unguessable_token.h"
#include "ipc/ipc_channel_handle.h"
#include "ipc/ipc_message.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -199,5 +200,25 @@ TEST(IPCMessageUtilsTest, OptionalSet) {
EXPECT_EQ(opt.value(), unserialized_opt.value());
}
TEST(IPCMessageUtilsTest, UnguessableTokenTest) {
base::UnguessableToken token = base::UnguessableToken::Create();
base::Pickle pickle;
IPC::WriteParam(&pickle, token);
base::PickleSizer sizer;
IPC::GetParamSize(&sizer, token);
EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
std::string log;
IPC::LogParam(token, &log);
EXPECT_EQ(token.ToString(), log);
base::UnguessableToken deserialized_token;
base::PickleIterator iter(pickle);
EXPECT_TRUE(IPC::ReadParam(&pickle, &iter, &deserialized_token));
EXPECT_EQ(token, deserialized_token);
}
} // namespace
} // namespace IPC
......@@ -26,6 +26,12 @@ struct TimeTicks;
[Native]
struct String16;
// Corresponds to |base::UnguessableToken| in base/unguessable_token.h
struct UnguessableToken {
uint64 high;
uint64 low;
};
// Corresponds to |base::Version| in base/version.h
struct Version {
array<uint32> components;
......
......@@ -7,6 +7,7 @@ public_headers = [
"//base/files/file_path.h",
"//base/strings/string16.h",
"//base/time/time.h",
"//base/unguessable_token.h",
"//base/values.h",
"//base/version.h",
]
......@@ -14,17 +15,16 @@ traits_headers = [
"//ipc/ipc_message_utils.h",
"//mojo/common/common_custom_types_struct_traits.h",
]
sources = [
"//mojo/common:struct_traits",
]
public_deps = [
"//ipc",
"//mojo/common:struct_traits",
]
type_mappings = [
"mojo.common.mojom.FilePath=base::FilePath",
"mojo.common.mojom.DictionaryValue=base::DictionaryValue",
"mojo.common.mojom.ListValue=base::ListValue",
"mojo.common.mojom.UnguessableToken=base::UnguessableToken",
"mojo.common.mojom.String16=base::string16",
"mojo.common.mojom.Time=base::Time[copyable_pass_by_value]",
"mojo.common.mojom.TimeDelta=base::TimeDelta[copyable_pass_by_value]",
......
......@@ -27,4 +27,20 @@ bool StructTraits<mojo::common::mojom::VersionDataView, base::Version>::Read(
return out->IsValid();
}
// static
bool StructTraits<mojo::common::mojom::UnguessableTokenDataView,
base::UnguessableToken>::
Read(mojo::common::mojom::UnguessableTokenDataView data,
base::UnguessableToken* out) {
uint64_t high = data.high();
uint64_t low = data.low();
// Receiving a zeroed UnguessableToken is a security issue.
if (high == 0 && low == 0)
return false;
*out = base::UnguessableToken::Deserialize(high, low);
return true;
}
} // namespace mojo
......@@ -5,8 +5,10 @@
#ifndef MOJO_COMMON_COMMON_CUSTOM_TYPES_STRUCT_TRAITS_H_
#define MOJO_COMMON_COMMON_CUSTOM_TYPES_STRUCT_TRAITS_H_
#include "base/unguessable_token.h"
#include "base/version.h"
#include "mojo/common/common_custom_types.mojom-shared.h"
#include "mojo/common/mojo_common_export.h"
namespace mojo {
......@@ -23,6 +25,26 @@ struct StructTraits<mojo::common::mojom::VersionDataView, base::Version> {
base::Version* out);
};
// If base::UnguessableToken is no longer 128 bits, the logic below and the
// mojom::UnguessableToken type should be updated.
static_assert(sizeof(base::UnguessableToken) == 2 * sizeof(uint64_t),
"base::UnguessableToken should be of size 2 * sizeof(uint64_t).");
template <>
struct StructTraits<mojo::common::mojom::UnguessableTokenDataView,
base::UnguessableToken> {
static uint64_t high(const base::UnguessableToken& token) {
return token.GetHighForSerialization();
}
static uint64_t low(const base::UnguessableToken& token) {
return token.GetLowForSerialization();
}
static bool Read(mojo::common::mojom::UnguessableTokenDataView data,
base::UnguessableToken* out);
};
template <>
struct StructTraits<mojo::common::mojom::TimeDeltaDataView, base::TimeDelta> {
static int64_t microseconds(const base::TimeDelta& delta) {
......
......@@ -90,6 +90,21 @@ class TestFilePathImpl : public TestFilePath {
mojo::Binding<TestFilePath> binding_;
};
class TestUnguessableTokenImpl : public TestUnguessableToken {
public:
explicit TestUnguessableTokenImpl(TestUnguessableTokenRequest request)
: binding_(this, std::move(request)) {}
// TestUnguessableToken implementation:
void BounceNonce(const base::UnguessableToken& in,
const BounceNonceCallback& callback) override {
callback.Run(in);
}
private:
mojo::Binding<TestUnguessableToken> binding_;
};
class TestTimeImpl : public TestTime {
public:
explicit TestTimeImpl(TestTimeRequest request)
......@@ -176,6 +191,19 @@ TEST_F(CommonCustomTypesTest, FilePath) {
run_loop.Run();
}
TEST_F(CommonCustomTypesTest, UnguessableToken) {
base::RunLoop run_loop;
TestUnguessableTokenPtr ptr;
TestUnguessableTokenImpl impl(GetProxy(&ptr));
base::UnguessableToken token = base::UnguessableToken::Create();
ptr->BounceNonce(token, ExpectResponse(&token, run_loop.QuitClosure()));
run_loop.Run();
}
TEST_F(CommonCustomTypesTest, Time) {
base::RunLoop run_loop;
......
......@@ -11,6 +11,11 @@ interface TestFilePath {
=> (mojo.common.mojom.FilePath out);
};
interface TestUnguessableToken {
BounceNonce(mojo.common.mojom.UnguessableToken in)
=> (mojo.common.mojom.UnguessableToken out);
};
interface TestTime {
BounceTime(mojo.common.mojom.Time time) => (mojo.common.mojom.Time time);
BounceTimeDelta(mojo.common.mojom.TimeDelta time_delta)
......
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