Skip to content

Commit

Permalink
[Nigori] Add classes representing Public-key & Public-private key-pair.
Browse files Browse the repository at this point in the history
Public-private cryptography will be leveraged for encrypting passwords outside the account boundary in the context of Password Sharing. The primitives that are used are based on the X25519 curve. The intended encryption protocol will be HPKE.

Bug: 1445056
Change-Id: I596640f95ab0b816e8a9a0171cfa5a11d0bee01f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4547322
Commit-Queue: Elias Khsheibun <eliaskh@chromium.org>
Auto-Submit: Elias Khsheibun <eliaskh@chromium.org>
Reviewed-by: David Benjamin <davidben@chromium.org>
Reviewed-by: Maksim Moskvitin <mmoskvitin@google.com>
Cr-Commit-Position: refs/heads/main@{#1149740}
  • Loading branch information
Elias Khsheibun authored and Chromium LUCI CQ committed May 26, 2023
1 parent 1c7dbda commit efddb51
Show file tree
Hide file tree
Showing 9 changed files with 367 additions and 0 deletions.
2 changes: 2 additions & 0 deletions components/sync/BUILD.gn
Expand Up @@ -217,6 +217,8 @@ source_set("unit_tests") {
"engine/net/http_bridge_unittest.cc",
"engine/net/sync_server_connection_manager_unittest.cc",
"engine/nigori/nigori_unittest.cc",
"engine/nigori/public_key_unittest.cc",
"engine/nigori/public_private_key_pair_unittest.cc",
"engine/sync_manager_impl_unittest.cc",
"engine/sync_scheduler_impl_unittest.cc",
"engine/syncer_proto_util_unittest.cc",
Expand Down
4 changes: 4 additions & 0 deletions components/sync/engine/BUILD.gn
Expand Up @@ -120,6 +120,10 @@ static_library("engine") {
"nigori/keystore_keys_handler.h",
"nigori/nigori.cc",
"nigori/nigori.h",
"nigori/public_key.cc",
"nigori/public_key.h",
"nigori/public_private_key_pair.cc",
"nigori/public_private_key_pair.h",
"nudge_handler.h",
"polling_constants.h",
"shutdown_reason.cc",
Expand Down
3 changes: 3 additions & 0 deletions components/sync/engine/nigori/DEPS
@@ -0,0 +1,3 @@
include_rules = [
"+third_party/boringssl/src/include/openssl",
]
48 changes: 48 additions & 0 deletions components/sync/engine/nigori/public_key.cc
@@ -0,0 +1,48 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/sync/engine/nigori/public_key.h"

#include <algorithm>
#include <array>
#include <utility>

#include "base/check.h"
#include "base/check_op.h"
#include "base/memory/ptr_util.h"

namespace syncer {

PublicKey::PublicKey(PublicKey&& other) = default;
PublicKey& PublicKey::operator=(PublicKey&& other) = default;
PublicKey::~PublicKey() = default;

PublicKey::PublicKey(base::span<const uint8_t> public_key) {
CHECK_EQ(static_cast<size_t>(X25519_PUBLIC_VALUE_LEN), public_key.size());

std::copy(public_key.begin(), public_key.end(), public_key_);
}

// static
absl::optional<PublicKey> PublicKey::CreateByImport(
base::span<const uint8_t> public_key) {
if (public_key.size() != X25519_PUBLIC_VALUE_LEN) {
return {};
}
return PublicKey(public_key);
}

std::array<uint8_t, X25519_PUBLIC_VALUE_LEN> PublicKey::GetRawPublicKey()
const {
std::array<uint8_t, X25519_PUBLIC_VALUE_LEN> raw_public_key;
std::copy(public_key_, public_key_ + X25519_PUBLIC_VALUE_LEN,
raw_public_key.begin());
return raw_public_key;
}

PublicKey PublicKey::Clone() const {
return PublicKey(GetRawPublicKey());
}

} // namespace syncer
43 changes: 43 additions & 0 deletions components/sync/engine/nigori/public_key.h
@@ -0,0 +1,43 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_SYNC_ENGINE_NIGORI_PUBLIC_KEY_H_
#define COMPONENTS_SYNC_ENGINE_NIGORI_PUBLIC_KEY_H_

#include <array>

#include "base/containers/span.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/boringssl/src/include/openssl/curve25519.h"

namespace syncer {

// A wrapper around a 32-byte X25519 public key.
class PublicKey {
public:
PublicKey(const PublicKey& other) = delete;
PublicKey(PublicKey&& other);
PublicKey& operator=(PublicKey&& other);
PublicKey& operator=(const PublicKey& other) = delete;
~PublicKey();

// Initialize the key using |public_key|.
static absl::optional<PublicKey> CreateByImport(
base::span<const uint8_t> public_key);

// Returns the raw public key.
std::array<uint8_t, X25519_PUBLIC_VALUE_LEN> GetRawPublicKey() const;

// Creates an exact clone.
PublicKey Clone() const;

private:
explicit PublicKey(base::span<const uint8_t> public_key);

uint8_t public_key_[X25519_PUBLIC_VALUE_LEN];
};

} // namespace syncer

#endif // COMPONENTS_SYNC_ENGINE_NIGORI_PUBLIC_KEY_H_
58 changes: 58 additions & 0 deletions components/sync/engine/nigori/public_key_unittest.cc
@@ -0,0 +1,58 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/sync/engine/nigori/public_key.h"

#include <vector>

#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace syncer {
namespace {

TEST(PublicKeyTest, GetRawPublicKeyShouldAlwaysSucceed) {
const std::vector<uint8_t> key(X25519_PUBLIC_VALUE_LEN, 0xDE);
const absl::optional<PublicKey> public_key = PublicKey::CreateByImport(key);

ASSERT_TRUE(public_key.has_value());

const std::array<uint8_t, X25519_PUBLIC_VALUE_LEN> raw_key =
public_key->GetRawPublicKey();
EXPECT_THAT(key, testing::ElementsAreArray(raw_key));
}

TEST(PublicKeyTest, CreateByImportShouldFailOnLongerKeys) {
const std::vector<uint8_t> key(X25519_PUBLIC_VALUE_LEN * 2);
const absl::optional<PublicKey> public_key = PublicKey::CreateByImport(key);

EXPECT_FALSE(public_key.has_value());
}

TEST(PublicKeyTest, CreateByImportShouldFailOnShorterKeys) {
const std::vector<uint8_t> key(X25519_PUBLIC_VALUE_LEN - 1);
const absl::optional<PublicKey> public_key = PublicKey::CreateByImport(key);

EXPECT_FALSE(public_key.has_value());
}

TEST(PublicKeyTest, CloneShouldAlwaysSucceed) {
const std::vector<uint8_t> key(X25519_PUBLIC_VALUE_LEN, 0xDE);
const absl::optional<PublicKey> public_key = PublicKey::CreateByImport(key);

absl::optional<PublicKey> clone = public_key->Clone();

ASSERT_TRUE(clone.has_value());

const std::array<uint8_t, X25519_PUBLIC_VALUE_LEN> raw_key =
public_key->GetRawPublicKey();
const std::array<uint8_t, X25519_PUBLIC_VALUE_LEN> clone_raw_key =
clone->GetRawPublicKey();

EXPECT_THAT(key, testing::ElementsAreArray(raw_key));
EXPECT_THAT(key, testing::ElementsAreArray(clone_raw_key));
}

} // namespace
} // namespace syncer
71 changes: 71 additions & 0 deletions components/sync/engine/nigori/public_private_key_pair.cc
@@ -0,0 +1,71 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/sync/engine/nigori/public_private_key_pair.h"

#include <algorithm>
#include <array>
#include <cstdint>
#include <iterator>
#include <string>
#include <utility>

#include "base/base64.h"
#include "base/check_op.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace syncer {

PublicPrivateKeyPair::PublicPrivateKeyPair(PublicPrivateKeyPair&& other) =
default;

PublicPrivateKeyPair& PublicPrivateKeyPair::operator=(
PublicPrivateKeyPair&& other) = default;

PublicPrivateKeyPair::~PublicPrivateKeyPair() = default;

// static
PublicPrivateKeyPair PublicPrivateKeyPair::GenerateNewKeyPair() {
return PublicPrivateKeyPair();
}

// static
absl::optional<PublicPrivateKeyPair> PublicPrivateKeyPair::CreateByImport(
base::span<uint8_t> private_key) {
if (private_key.size() != X25519_PRIVATE_KEY_LEN) {
return {};
}
return PublicPrivateKeyPair(std::move(private_key));
}

PublicPrivateKeyPair::PublicPrivateKeyPair(base::span<uint8_t> private_key) {
CHECK_EQ(static_cast<size_t>(X25519_PRIVATE_KEY_LEN), private_key.size());

std::copy(private_key.begin(), private_key.end(), private_key_);
X25519_public_from_private(public_key_, private_key_);
}

PublicPrivateKeyPair::PublicPrivateKeyPair() {
X25519_keypair(public_key_, private_key_);
}

std::array<uint8_t, X25519_PRIVATE_KEY_LEN>
PublicPrivateKeyPair::GetRawPrivateKey() const {
std::array<uint8_t, X25519_PRIVATE_KEY_LEN> raw_private_key;
std::copy(private_key_, private_key_ + X25519_PRIVATE_KEY_LEN,
raw_private_key.begin());
return raw_private_key;
}

std::array<uint8_t, X25519_PUBLIC_VALUE_LEN>
PublicPrivateKeyPair::GetRawPublicKey() const {
std::array<uint8_t, X25519_PUBLIC_VALUE_LEN> raw_public_key;
std::copy(public_key_, public_key_ + X25519_PUBLIC_VALUE_LEN,
raw_public_key.begin());
return raw_public_key;
}

} // namespace syncer
49 changes: 49 additions & 0 deletions components/sync/engine/nigori/public_private_key_pair.h
@@ -0,0 +1,49 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_SYNC_ENGINE_NIGORI_PUBLIC_PRIVATE_KEY_PAIR_H_
#define COMPONENTS_SYNC_ENGINE_NIGORI_PUBLIC_PRIVATE_KEY_PAIR_H_

#include <array>
#include <memory>
#include <string>

#include "base/containers/span.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/boringssl/src/include/openssl/curve25519.h"

namespace syncer {

// A wrapper around a 32-byte X25519 public-private key-pair.
class PublicPrivateKeyPair {
public:
// Generate a X25519 key pair.
static PublicPrivateKeyPair GenerateNewKeyPair();
// Initialize the Public-private key-pair using |private_key|.
static absl::optional<PublicPrivateKeyPair> CreateByImport(
base::span<uint8_t> private_key);

PublicPrivateKeyPair(const PublicPrivateKeyPair& other) = delete;
PublicPrivateKeyPair(PublicPrivateKeyPair&& other);
PublicPrivateKeyPair& operator=(PublicPrivateKeyPair&& other);
PublicPrivateKeyPair& operator=(const PublicPrivateKeyPair&) = delete;
~PublicPrivateKeyPair();

// Returns the raw private key.
std::array<uint8_t, X25519_PRIVATE_KEY_LEN> GetRawPrivateKey() const;

// Returns the raw public key.
std::array<uint8_t, X25519_PUBLIC_VALUE_LEN> GetRawPublicKey() const;

private:
PublicPrivateKeyPair();
explicit PublicPrivateKeyPair(base::span<uint8_t> private_key);

uint8_t private_key_[X25519_PRIVATE_KEY_LEN];
uint8_t public_key_[X25519_PUBLIC_VALUE_LEN];
};

} // namespace syncer

#endif // COMPONENTS_SYNC_ENGINE_NIGORI_PUBLIC_PRIVATE_KEY_PAIR_H_
89 changes: 89 additions & 0 deletions components/sync/engine/nigori/public_private_key_pair_unittest.cc
@@ -0,0 +1,89 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/sync/engine/nigori/public_private_key_pair.h"

#include <algorithm>
#include <vector>

#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/curve25519.h"

namespace syncer {
namespace {

TEST(PublicPrivateKeyPairTest, GenerateNewKeyPairShouldAlwaysSucceed) {
PublicPrivateKeyPair key = PublicPrivateKeyPair::GenerateNewKeyPair();

EXPECT_THAT(key.GetRawPrivateKey(), testing::SizeIs(X25519_PRIVATE_KEY_LEN));
EXPECT_THAT(key.GetRawPublicKey(), testing::SizeIs(X25519_PUBLIC_VALUE_LEN));
}

TEST(PublicPrivateKeyPairTest, GenerateNewKeyPairShouldGenerateDifferentKeys) {
PublicPrivateKeyPair key_1 = PublicPrivateKeyPair::GenerateNewKeyPair();
PublicPrivateKeyPair key_2 = PublicPrivateKeyPair::GenerateNewKeyPair();

EXPECT_NE(key_1.GetRawPrivateKey(), key_2.GetRawPrivateKey());
EXPECT_NE(key_1.GetRawPublicKey(), key_2.GetRawPublicKey());
}

TEST(PublicPrivateKeyPairTest,
GenerateNewKeyPairShouldGenerateDifferentPublicPrivateParts) {
PublicPrivateKeyPair key = PublicPrivateKeyPair::GenerateNewKeyPair();

EXPECT_NE(key.GetRawPrivateKey(), key.GetRawPublicKey());
}

TEST(PublicPrivateKeyPairTest, GeneratedPublicKeyShouldMatchX25519Derivation) {
PublicPrivateKeyPair key = PublicPrivateKeyPair::GenerateNewKeyPair();

const std::array<uint8_t, X25519_PRIVATE_KEY_LEN> raw_private_key =
key.GetRawPrivateKey();
const std::array<uint8_t, X25519_PUBLIC_VALUE_LEN> raw_public_key =
key.GetRawPublicKey();

uint8_t expected_public_key[X25519_PUBLIC_VALUE_LEN];
uint8_t expected_private_key_arr[X25519_PRIVATE_KEY_LEN];
std::copy(raw_private_key.begin(), raw_private_key.end(),
expected_private_key_arr);
X25519_public_from_private(expected_public_key, expected_private_key_arr);

EXPECT_THAT(raw_public_key, testing::ElementsAreArray(expected_public_key));
}

TEST(PublicPrivateKeyPairTest, CreateByImportShouldSucceed) {
std::vector<uint8_t> private_key(X25519_PRIVATE_KEY_LEN, 0xDE);

absl::optional<PublicPrivateKeyPair> key =
PublicPrivateKeyPair::CreateByImport(private_key);

ASSERT_TRUE(key.has_value());

std::array<uint8_t, X25519_PRIVATE_KEY_LEN> raw_private_key =
key->GetRawPrivateKey();

EXPECT_THAT(private_key, testing::ElementsAreArray(raw_private_key));
}

TEST(PublicPrivateKeyPairTest, CreateByImportShouldFailOnShorterKey) {
std::vector<uint8_t> private_key(X25519_PRIVATE_KEY_LEN - 1, 0xDE);

absl::optional<PublicPrivateKeyPair> key =
PublicPrivateKeyPair::CreateByImport(private_key);

EXPECT_FALSE(key.has_value());
}

TEST(PublicPrivateKeyPairTest, CreateByImportShouldFailOnLongerKey) {
std::vector<uint8_t> private_key(X25519_PRIVATE_KEY_LEN + 1, 0xDE);

absl::optional<PublicPrivateKeyPair> key =
PublicPrivateKeyPair::CreateByImport(private_key);

EXPECT_FALSE(key.has_value());
}

} // namespace
} // namespace syncer

0 comments on commit efddb51

Please sign in to comment.