Commit efc6c37a authored by Josh Nohle's avatar Josh Nohle Committed by Commit Bot

[DeviceSync v2] Add CryptAuthMetadataSyncer class

Handles the SyncMetadata portion of the CryptAuth v2 DeviceSync
protocol, which consists of one or two SyncMetadata request/response
interactions with the CryptAuth servers.

The output of a successful flow is:
- A map from Instance ID to DeviceMetadataPacket, which contains device
  metadata encrypted with the group public key.
- (Optional) A new group public and private key created by the client or
  a new group public key provided by CryptAuth.
- (Optional) A group private key, encrypted with the local device's
  CryptAuthKeyBunde::kDeviceSyncBetterTogether public key.

Neither the device nor key registries change; all relevant data is sent
as callback parameters.

Verified 100% code coverage aside from different flavors of network
request errors.

Bug: 951969
Change-Id: I54c4a3afd72d751e7dc95545ac64ce9f811d939b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1703070Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Commit-Queue: Josh Nohle <nohle@chromium.org>
Cr-Commit-Position: refs/heads/master@{#678879}
parent 8bb0adac
...@@ -65,6 +65,10 @@ static_library("device_sync") { ...@@ -65,6 +65,10 @@ static_library("device_sync") {
"cryptauth_key_registry.h", "cryptauth_key_registry.h",
"cryptauth_key_registry_impl.cc", "cryptauth_key_registry_impl.cc",
"cryptauth_key_registry_impl.h", "cryptauth_key_registry_impl.h",
"cryptauth_metadata_syncer.cc",
"cryptauth_metadata_syncer.h",
"cryptauth_metadata_syncer_impl.cc",
"cryptauth_metadata_syncer_impl.h",
"cryptauth_scheduler.cc", "cryptauth_scheduler.cc",
"cryptauth_scheduler.h", "cryptauth_scheduler.h",
"cryptauth_scheduler_impl.cc", "cryptauth_scheduler_impl.cc",
...@@ -214,6 +218,7 @@ source_set("unit_tests") { ...@@ -214,6 +218,7 @@ source_set("unit_tests") {
"cryptauth_key_proof_computer_impl_unittest.cc", "cryptauth_key_proof_computer_impl_unittest.cc",
"cryptauth_key_registry_impl_unittest.cc", "cryptauth_key_registry_impl_unittest.cc",
"cryptauth_key_unittest.cc", "cryptauth_key_unittest.cc",
"cryptauth_metadata_syncer_impl_unittest.cc",
"cryptauth_scheduler_impl_unittest.cc", "cryptauth_scheduler_impl_unittest.cc",
"cryptauth_v2_enroller_impl_unittest.cc", "cryptauth_v2_enroller_impl_unittest.cc",
"cryptauth_v2_enrollment_manager_impl_unittest.cc", "cryptauth_v2_enrollment_manager_impl_unittest.cc",
......
...@@ -48,6 +48,9 @@ std::ostream& operator<<( ...@@ -48,6 +48,9 @@ std::ostream& operator<<(
case ResultCode::kSuccess: case ResultCode::kSuccess:
stream << "[Success]"; stream << "[Success]";
break; break;
case ResultCode::kFinishedWithNonFatalErrors:
stream << "[Finished with non-fatal errors]";
break;
case ResultCode::kErrorMissingUserKeyPair: case ResultCode::kErrorMissingUserKeyPair:
stream << "[Error: No user key pair in registry]"; stream << "[Error: No user key pair in registry]";
break; break;
...@@ -60,12 +63,9 @@ std::ostream& operator<<( ...@@ -60,12 +63,9 @@ std::ostream& operator<<(
case ResultCode::kErrorNoMetadataInResponse: case ResultCode::kErrorNoMetadataInResponse:
stream << "[Error: No encrypted metadata in SyncMetadata response]"; stream << "[Error: No encrypted metadata in SyncMetadata response]";
break; break;
case ResultCode::kErrorInvalidMetadataInResponse: case ResultCode::kErrorAllResponseMetadataInvalid:
stream << "[Error: Invalid DeviceMetadataPacket in SyncMetadata " stream << "[Error: All DeviceMetadataPackets in SyncMetadata "
<< "response]"; << "response are invalid]";
break;
case ResultCode::kErrorDuplicateDeviceIdsInResponse:
stream << "[Error: Duplicate device IDs in SyncMetadata response]";
break; break;
case ResultCode::kErrorNoLocalDeviceMetadataInResponse: case ResultCode::kErrorNoLocalDeviceMetadataInResponse:
stream << "[Error: No local device metadata in SyncMetadata response]"; stream << "[Error: No local device metadata in SyncMetadata response]";
......
...@@ -24,12 +24,12 @@ class CryptAuthDeviceSyncResult { ...@@ -24,12 +24,12 @@ class CryptAuthDeviceSyncResult {
// TODO(nohle): Add numeric values. // TODO(nohle): Add numeric values.
enum class ResultCode { enum class ResultCode {
kSuccess, kSuccess,
kFinishedWithNonFatalErrors,
kErrorMissingUserKeyPair, kErrorMissingUserKeyPair,
kErrorEncryptingDeviceMetadata, kErrorEncryptingDeviceMetadata,
kErrorEstablishingGroupPublicKey, kErrorEstablishingGroupPublicKey,
kErrorNoMetadataInResponse, kErrorNoMetadataInResponse,
kErrorInvalidMetadataInResponse, kErrorAllResponseMetadataInvalid,
kErrorDuplicateDeviceIdsInResponse,
kErrorNoLocalDeviceMetadataInResponse, kErrorNoLocalDeviceMetadataInResponse,
kErrorMissingFeatureStatuses, kErrorMissingFeatureStatuses,
kErrorMissingLocalDeviceSyncBetterTogetherKey, kErrorMissingLocalDeviceSyncBetterTogetherKey,
......
// Copyright 2019 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 "chromeos/services/device_sync/cryptauth_metadata_syncer.h"
#include <utility>
#include "chromeos/services/device_sync/cryptauth_device_sync_result.h"
namespace chromeos {
namespace device_sync {
CryptAuthMetadataSyncer::CryptAuthMetadataSyncer() = default;
CryptAuthMetadataSyncer::~CryptAuthMetadataSyncer() = default;
void CryptAuthMetadataSyncer::SyncMetadata(
const cryptauthv2::RequestContext& request_context,
const cryptauthv2::BetterTogetherDeviceMetadata& local_device_metadata,
const CryptAuthKey* initial_group_key,
SyncMetadataAttemptFinishedCallback callback) {
// Enforce that SyncMetadata() can only be called once.
DCHECK(!was_sync_metadata_called_);
was_sync_metadata_called_ = true;
callback_ = std::move(callback);
OnAttemptStarted(request_context, local_device_metadata, initial_group_key);
}
void CryptAuthMetadataSyncer::OnAttemptFinished(
const IdToDeviceMetadataPacketMap& id_to_device_metadata_packet_map,
std::unique_ptr<CryptAuthKey> new_group_key,
const base::Optional<cryptauthv2::EncryptedGroupPrivateKey>&
encrypted_group_private_key,
const CryptAuthDeviceSyncResult& device_sync_result) {
DCHECK(callback_);
std::move(callback_).Run(id_to_device_metadata_packet_map,
std::move(new_group_key),
encrypted_group_private_key, device_sync_result);
}
} // namespace device_sync
} // namespace chromeos
// Copyright 2019 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 CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_METADATA_SYNCER_H_
#define CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_METADATA_SYNCER_H_
#include <memory>
#include "base/callback.h"
#include "base/containers/flat_map.h"
#include "base/macros.h"
#include "base/optional.h"
#include "chromeos/services/device_sync/cryptauth_key.h"
#include "chromeos/services/device_sync/proto/cryptauth_devicesync.pb.h"
namespace cryptauthv2 {
class BetterTogetherDeviceMetadata;
} // namespace cryptauthv2
namespace chromeos {
namespace device_sync {
class CryptAuthDeviceSyncResult;
// Handles the SyncMetadata portion of the CryptAuth v2 DeviceSync protocol,
// which consists of one or two SyncMetadata request/response interactions with
// the CryptAuth servers:
//
// 1a) First SyncMetadataRequest: Contains what this device believes to be the
// group public key. This could be a newly generated key or one from local
// storage. The device's metadata is encrypted with this key and included in
// the request.
//
// 1b) First SyncMetadataResponse: The response from CryptAuth possibly includes
// the correct group key pair, where the private key is encrypted with this
// device's CryptAuthKeyBunde::kDeviceSyncBetterTogether public key. If the
// group public key is not set, the server is indicating that a new group
// key pair must be created by this device. In this case or if the group
// public key in the response differs from the one sent in the request,
// another SyncMetadataRequest is made. Otherwise, the second
// SyncMetadataRequest is skipped and the encrypted remote device metadata
// is decrypted using the group private key if available. Note: The remote
// devices are only those registered in the DeviceSync:BetterTogether group,
// in other words, those using v2 DeviceSync.
//
// 2a) (Possible) Second SyncMetadataRequest: Not invoked if the group public
// key from the first SyncMetadataResponse (1b) agrees with the one sent in
// the first SyncMetadataRequest (1a). The client has the correct group
// public key at this point.
//
// 2b) (Possible) Second SyncMetadataResponse: The included remote device
// metadata can be decrypted if a group private key is sent. If no group
// private key is returned, the client must wait for a GCM message
// requesting a DeviceSync when the key becomes available.
//
// The output of a successful flow is:
// - A map from Instance ID to DeviceMetadataPacket, which contains device
// metadata encrypted with the group public key.
// - (Optional) A new group public and private key created by the client or a
// new group public key provided by CryptAuth. Null if no new group key was
// created.
// - (Optional) A group private key, encrypted with this device's
// CryptAuthKeyBunde::kDeviceSyncBetterTogether public key.
//
// A CryptAuthMetadataSyncer object is designed to be used for only one
// SyncMetadata() call. For a new DeviceSync attempt, a new object should be
// created.
class CryptAuthMetadataSyncer {
public:
using IdToDeviceMetadataPacketMap =
base::flat_map<std::string, cryptauthv2::DeviceMetadataPacket>;
using SyncMetadataAttemptFinishedCallback = base::OnceCallback<void(
const IdToDeviceMetadataPacketMap&,
std::unique_ptr<CryptAuthKey>,
const base::Optional<cryptauthv2::EncryptedGroupPrivateKey>&,
const CryptAuthDeviceSyncResult&)>;
virtual ~CryptAuthMetadataSyncer();
// Starts the SyncMetadata portion of the CryptAuth v2 DeviceSync flow.
void SyncMetadata(
const cryptauthv2::RequestContext& request_context,
const cryptauthv2::BetterTogetherDeviceMetadata& local_device_metadata,
const CryptAuthKey* initial_group_key,
SyncMetadataAttemptFinishedCallback callback);
protected:
CryptAuthMetadataSyncer();
virtual void OnAttemptStarted(
const cryptauthv2::RequestContext& request_context,
const cryptauthv2::BetterTogetherDeviceMetadata& local_device_metadata,
const CryptAuthKey* initial_group_key) = 0;
void OnAttemptFinished(
const IdToDeviceMetadataPacketMap& id_to_device_metadata_packet_map,
std::unique_ptr<CryptAuthKey> new_group_key,
const base::Optional<cryptauthv2::EncryptedGroupPrivateKey>&
encrypted_group_private_key,
const CryptAuthDeviceSyncResult& device_sync_result);
private:
SyncMetadataAttemptFinishedCallback callback_;
bool was_sync_metadata_called_ = false;
DISALLOW_COPY_AND_ASSIGN(CryptAuthMetadataSyncer);
};
} // namespace device_sync
} // namespace chromeos
#endif // CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_METADATA_SYNCER_H_
// Copyright 2019 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 CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_METADATA_SYNCER_IMPL_H_
#define CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_METADATA_SYNCER_IMPL_H_
#include <memory>
#include <ostream>
#include <string>
#include "base/containers/flat_map.h"
#include "base/macros.h"
#include "base/timer/timer.h"
#include "chromeos/services/device_sync/cryptauth_device_sync_result.h"
#include "chromeos/services/device_sync/cryptauth_key.h"
#include "chromeos/services/device_sync/cryptauth_key_bundle.h"
#include "chromeos/services/device_sync/cryptauth_metadata_syncer.h"
#include "chromeos/services/device_sync/network_request_error.h"
#include "chromeos/services/device_sync/proto/cryptauth_better_together_device_metadata.pb.h"
#include "chromeos/services/device_sync/proto/cryptauth_devicesync.pb.h"
#include "chromeos/services/device_sync/proto/cryptauth_directive.pb.h"
namespace chromeos {
namespace device_sync {
class CryptAuthClient;
class CryptAuthClientFactory;
class CryptAuthEciesEncryptor;
class CryptAuthKeyCreator;
// An implementation of CryptAuthMetadataSyncer, using instances of
// CryptAuthClient to make the SyncMetadata API calls to CryptAuth. Timeouts are
// handled internally, so ShareGroupPrivateKey() is always guaranteed to return.
//
// All returned DeviceMetadataPackets are guaranteed to have a nontrivial device
// ID, device name, and device public key.
class CryptAuthMetadataSyncerImpl : public CryptAuthMetadataSyncer {
public:
class Factory {
public:
static Factory* Get();
static void SetFactoryForTesting(Factory* test_factory);
virtual ~Factory();
virtual std::unique_ptr<CryptAuthMetadataSyncer> BuildInstance(
CryptAuthClientFactory* client_factory,
std::unique_ptr<base::OneShotTimer> timer =
std::make_unique<base::OneShotTimer>());
private:
static Factory* test_factory_;
};
~CryptAuthMetadataSyncerImpl() override;
private:
enum class State {
kNotStarted,
kWaitingForGroupKeyCreation,
kWaitingForLocalDeviceMetadataEncryption,
kWaitingForFirstSyncMetadataResponse,
kWaitingForSecondSyncMetadataResponse,
kFinished
};
// kKeyExistsButNotConfirmedWithCryptAuth: A local group public key exists but
// CryptAuth has yet to confirm or deny that it is the correct group key.
// kNewKeyNeedsToBeCreate: Either a local group public key does not exist or
// an empty group_public_key field in the first SyncMetadataResponse is a
// signal from CryptAuth that we should generate a new group key pair and
// make another SyncMetadataRequest.
// kNewKeyReceivedFromCryptAuth: If our local group public key differs from
// the one received from CryptAuth, replace the local key and make another
// SyncMetadataRequest. The group private key will be provided in the next
// SyncMetadataResponse if available.
// kEstablished: Our local group public key agrees with the group_public_key
// field sent in the latest SyncMetadataResponse. It should take at most
// two SyncMetadata calls to establish the group public key.
enum class GroupPublicKeyState {
kUndetermined,
kKeyExistsButNotConfirmedWithCryptAuth,
kNewKeyNeedsToBeCreated,
kNewKeyReceivedFromCryptAuth,
kEstablished
};
friend std::ostream& operator<<(std::ostream& stream, const State& state);
static base::Optional<base::TimeDelta> GetTimeoutForState(State state);
static base::Optional<CryptAuthDeviceSyncResult::ResultCode>
ResultCodeErrorFromTimeoutDuringState(State state);
// CryptAuthMetadataSyncer:
void OnAttemptStarted(
const cryptauthv2::RequestContext& request_context,
const cryptauthv2::BetterTogetherDeviceMetadata& local_device_metadata,
const CryptAuthKey* initial_group_key) override;
CryptAuthMetadataSyncerImpl(CryptAuthClientFactory* client_factory,
std::unique_ptr<base::OneShotTimer> timer);
void SetState(State state);
void OnTimeout();
const CryptAuthKey* GetGroupKey();
GroupPublicKeyState GetGroupPublicKeyState();
void AttemptNextStep();
void EncryptLocalDeviceMetadata();
void CreateGroupKey();
void OnGroupKeyCreated(
const base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey>& new_keys,
const base::Optional<CryptAuthKey>& client_ephemeral_dh);
void OnLocalDeviceMetadataEncrypted(
const base::Optional<std::string>& encrypted_metadata);
void MakeSyncMetadataCall();
void OnSyncMetadataSuccess(const cryptauthv2::SyncMetadataResponse& response);
void OnSyncMetadataFailure(NetworkRequestError error);
void FilterMetadataAndFinishAttempt();
void FinishAttempt(const CryptAuthDeviceSyncResult::ResultCode& result_code);
size_t num_sync_metadata_calls_ = 0;
cryptauthv2::RequestContext request_context_;
cryptauthv2::BetterTogetherDeviceMetadata local_device_metadata_;
base::Optional<std::string> encrypted_local_device_metadata_;
base::Optional<cryptauthv2::SyncMetadataResponse> sync_metadata_response_;
// The filtered map of DeviceMetadataPackets from the SyncMetadataResponse,
// keyed by device ID. All DeviceMetadataPackets are guaranteed to have a
// nontrivial device ID, device name, and device public key.
base::flat_map<std::string, cryptauthv2::DeviceMetadataPacket>
id_to_device_metadata_packet_map_;
// Non-null if a new group key is created or if CryptAuth sends a new group
// public key during the SyncMetadata flow. This value is returned in a
// callback when the attempt finishes.
std::unique_ptr<CryptAuthKey> new_group_key_;
// The CryptAuthClient for the latest CryptAuth request. The client can only
// be used for one call; therefore, for each API call, a new client needs to
// be generated from |client_factory_|.
std::unique_ptr<CryptAuthClient> cryptauth_client_;
// Used to generate the group key pair if necessary.
std::unique_ptr<CryptAuthKeyCreator> key_creator_;
// The CryptAuthEciesEncryptor for the latest encryption/decryption. An
// instance can only be used for one method call; therefore, for each
// encryption/decryption, a new encryptor needs to be generated.
std::unique_ptr<CryptAuthEciesEncryptor> encryptor_;
State state_ = State::kNotStarted;
const CryptAuthKey* initial_group_key_;
CryptAuthClientFactory* client_factory_ = nullptr;
std::unique_ptr<base::OneShotTimer> timer_;
DISALLOW_COPY_AND_ASSIGN(CryptAuthMetadataSyncerImpl);
};
} // namespace device_sync
} // namespace chromeos
#endif // CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_METADATA_SYNCER_IMPL_H_
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