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

Add class to compute key proofs for CryptAuth v2 Enrollment

This class is used for the batch computation of key proofs needed for
CryptAuth v2 Enrollment. The lone method ComputeKeyProofs() takes a list
of key-payload pairs and returns a list of key proof strings, ordered
consistent with the input list. The key proofs are used by CryptAuth to
verify that the client has the appropriate key material.

The CryptAuth v2 Enrollment protocol requires the following key proof
formats:
  - Symmetric keys: The HMAC-SHA256 of the payload, using a key derived
    from the symmetric key. Specifically,
        HMAC(HKDF(|key|, salt="CryptAuth Key Proof", info=|key_handle|),
             |payload|)

  - Asymmetric keys: A signed, unencrypted, and serialized SecureMessage
    proto, with the following parameters:
        - signature_scheme = ECDSA_P256_SHA256,
        - encryption_scheme = NONE,
        - verification_key_id = |key_handle|,
        - body = |payload|,
    signed with the private key material.

The protocol also demands that the |random_session_id|, sent by the
CryptAuth server via the SyncKeysResponse, be used as the payload for
key proofs. In the future, key crossproofs might be employed, where the
payload will consist of other key proofs.

Requirements:
  - An instance of this class should only be used once.
  - The input list, |key_payload_pairs|, cannot be empty.
  - Currently, the only supported key types are RAW128 and RAW256 for
    symmetric keys and P256 for asymmetric keys.

Bug: 899080
Change-Id: I3db6dfedd15ef7a51b4ef8953dfb4fc84c5bd550
Reviewed-on: https://chromium-review.googlesource.com/c/1460310
Commit-Queue: Josh Nohle <nohle@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#630601}
parent 1751ba7d
......@@ -40,6 +40,9 @@ static_library("device_sync") {
"cryptauth_key.h",
"cryptauth_key_bundle.cc",
"cryptauth_key_bundle.h",
"cryptauth_key_proof_computer.h",
"cryptauth_key_proof_computer_impl.cc",
"cryptauth_key_proof_computer_impl.h",
"cryptauth_key_registry.cc",
"cryptauth_key_registry.h",
"cryptauth_key_registry_impl.cc",
......@@ -160,6 +163,7 @@ source_set("unit_tests") {
"cryptauth_enrollment_manager_impl_unittest.cc",
"cryptauth_gcm_manager_impl_unittest.cc",
"cryptauth_key_bundle_unittest.cc",
"cryptauth_key_proof_computer_impl_unittest.cc",
"cryptauth_key_registry_impl_unittest.cc",
"cryptauth_key_unittest.cc",
"device_sync_service_unittest.cc",
......
// 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_KEY_PROOF_COMPUTER_H_
#define CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_KEY_PROOF_COMPUTER_H_
#include <string>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "base/macros.h"
#include "chromeos/services/device_sync/cryptauth_key.h"
namespace chromeos {
namespace device_sync {
// The lone method ComputeKeyProofs() takes a list of key-payload pairs and
// returns a list of key proof strings, ordered consistent with the
// input list. The key proofs are used by CryptAuth to verify that the client
// has the appropriate key material.
//
// The CryptAuth v2 Enrollment protocol requires the following key proof
// formats:
// - Symmetric keys: The HMAC-SHA256 of the payload, using a key derived from
// the symmetric key. Specifically,
// HMAC(HKDF(|key|, salt="CryptAuth Key Proof", info=|key_handle|),
// |payload|)
//
// - Asymmetric keys: A signed, unencrypted, and serialized SecureMessage
// proto, with the following parameters:
// - signature_scheme = ECDSA_P256_SHA256,
// - encryption_scheme = NONE,
// - verification_key_id = |key_handle|,
// - body = |payload|,
// signed with the private key material.
//
// The protocol also demands that the |random_session_id|, sent by the CryptAuth
// server via the SyncKeysResponse, be used as the payload for key proofs. In
// the future, key crossproofs might be employed, where the payload will
// consist of other key proofs.
//
// Requirements:
// - An instance of this class should only be used once.
// - The input list, |key_payload_pairs|, cannot be empty.
// - Currently, the only supported key types are RAW128 and RAW256 for
// symmetric keys and P256 for asymmetric keys.
class CryptAuthKeyProofComputer {
public:
CryptAuthKeyProofComputer() = default;
virtual ~CryptAuthKeyProofComputer() = default;
using ComputeKeyProofsCallback =
base::OnceCallback<void(const std::vector<std::string>& /* key_proofs*/)>;
virtual void ComputeKeyProofs(
const std::vector<std::pair<CryptAuthKey, std::string>>&
key_payload_pairs,
ComputeKeyProofsCallback compute_key_proofs_callback) = 0;
DISALLOW_COPY_AND_ASSIGN(CryptAuthKeyProofComputer);
};
} // namespace device_sync
} // namespace chromeos
#endif // CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_KEY_PROOF_COMPUTER_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.
#include "chromeos/services/device_sync/cryptauth_key_proof_computer_impl.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "chromeos/components/multidevice/secure_message_delegate.h"
#include "chromeos/components/multidevice/secure_message_delegate_impl.h"
#include "chromeos/services/device_sync/cryptauth_key.h"
#include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
#include "crypto/hkdf.h"
#include "crypto/hmac.h"
namespace chromeos {
namespace device_sync {
namespace {
// The salt used for HKDF in symmetric key proofs. This value is part of the
// CryptAuth v2 Enrollment specifications.
const char kSymmetricKeyProofSalt[] = "CryptAuth Key Proof";
size_t NumBytesForSymmetricKeyType(cryptauthv2::KeyType key_type) {
switch (key_type) {
case (cryptauthv2::KeyType::RAW128):
return 16u;
case (cryptauthv2::KeyType::RAW256):
return 32u;
default:
NOTREACHED();
return 0u;
}
}
bool IsValidAsymmetricKey(const CryptAuthKey& key) {
return key.IsAsymmetricKey() && !key.private_key().empty() &&
key.type() == cryptauthv2::KeyType::P256;
}
} // namespace
// static
CryptAuthKeyProofComputerImpl::Factory*
CryptAuthKeyProofComputerImpl::Factory::test_factory_ = nullptr;
// static
CryptAuthKeyProofComputerImpl::Factory*
CryptAuthKeyProofComputerImpl::Factory::Get() {
if (test_factory_)
return test_factory_;
static base::NoDestructor<CryptAuthKeyProofComputerImpl::Factory> factory;
return factory.get();
}
// static
void CryptAuthKeyProofComputerImpl::Factory::SetFactoryForTesting(
Factory* test_factory) {
test_factory_ = test_factory;
}
CryptAuthKeyProofComputerImpl::Factory::~Factory() = default;
std::unique_ptr<CryptAuthKeyProofComputer>
CryptAuthKeyProofComputerImpl::Factory::BuildInstance() {
return base::WrapUnique(new CryptAuthKeyProofComputerImpl());
}
CryptAuthKeyProofComputerImpl::CryptAuthKeyProofComputerImpl()
: secure_message_delegate_(
multidevice::SecureMessageDelegateImpl::Factory::NewInstance()) {}
CryptAuthKeyProofComputerImpl::~CryptAuthKeyProofComputerImpl() = default;
void CryptAuthKeyProofComputerImpl::ComputeKeyProofs(
const std::vector<std::pair<CryptAuthKey, std::string>>& key_payload_pairs,
ComputeKeyProofsCallback compute_key_proofs_callback) {
DCHECK(!key_payload_pairs.empty());
// Fail if ComputeKeyProofs() has already been called.
DCHECK(num_key_proofs_to_compute_ == 0 && key_payload_pairs_.empty() &&
output_key_proofs_.empty() && !compute_key_proofs_callback_);
num_key_proofs_to_compute_ = key_payload_pairs.size();
key_payload_pairs_ = key_payload_pairs;
output_key_proofs_.resize(num_key_proofs_to_compute_);
compute_key_proofs_callback_ = std::move(compute_key_proofs_callback);
for (size_t i = 0; i < key_payload_pairs_.size(); ++i) {
if (key_payload_pairs_[i].first.IsSymmetricKey()) {
ComputeSymmetricKeyProof(i, key_payload_pairs_[i].first,
key_payload_pairs_[i].second);
} else {
DCHECK(IsValidAsymmetricKey(key_payload_pairs_[i].first));
ComputeAsymmetricKeyProof(i, key_payload_pairs_[i].first,
key_payload_pairs_[i].second);
}
}
}
void CryptAuthKeyProofComputerImpl::ComputeSymmetricKeyProof(
const size_t index,
const CryptAuthKey& symmetric_key,
const std::string& payload) {
std::string derived_symmetric_key_material =
crypto::HkdfSha256(symmetric_key.symmetric_key(), kSymmetricKeyProofSalt,
symmetric_key.handle(),
NumBytesForSymmetricKeyType(symmetric_key.type()));
crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
std::vector<unsigned char> signed_payload(hmac.DigestLength());
bool success =
hmac.Init(derived_symmetric_key_material) &&
hmac.Sign(payload, signed_payload.data(), signed_payload.size());
DCHECK(success);
OnKeyProofComputed(index,
std::string(signed_payload.begin(), signed_payload.end()));
}
void CryptAuthKeyProofComputerImpl::ComputeAsymmetricKeyProof(
const size_t index,
const CryptAuthKey& asymmetric_key,
const std::string& payload) {
multidevice::SecureMessageDelegate::CreateOptions options;
options.encryption_scheme = securemessage::EncScheme::NONE;
options.signature_scheme = securemessage::SigScheme::ECDSA_P256_SHA256;
options.verification_key_id = asymmetric_key.handle();
secure_message_delegate_->CreateSecureMessage(
payload, asymmetric_key.private_key(), options,
base::Bind(&CryptAuthKeyProofComputerImpl::OnKeyProofComputed,
base::Unretained(this), index));
}
void CryptAuthKeyProofComputerImpl::OnKeyProofComputed(
const size_t index,
const std::string& key_proof) {
DCHECK(index < output_key_proofs_.size());
output_key_proofs_[index] = key_proof;
--num_key_proofs_to_compute_;
if (!num_key_proofs_to_compute_)
std::move(compute_key_proofs_callback_).Run(output_key_proofs_);
}
} // 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_KEY_PROOF_COMPUTER_IMPL_H_
#define CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_KEY_PROOF_COMPUTER_IMPL_H_
#include "chromeos/services/device_sync/cryptauth_key_proof_computer.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "base/macros.h"
#include "chromeos/services/device_sync/cryptauth_key.h"
#include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
namespace chromeos {
namespace multidevice {
class SecureMessageDelegate;
} // namespace multidevice
namespace device_sync {
class CryptAuthKeyProofComputerImpl : public CryptAuthKeyProofComputer {
public:
class Factory {
public:
static Factory* Get();
static void SetFactoryForTesting(Factory* test_factory);
virtual ~Factory();
virtual std::unique_ptr<CryptAuthKeyProofComputer> BuildInstance();
private:
static Factory* test_factory_;
};
~CryptAuthKeyProofComputerImpl() override;
// CryptAuthKeyProofComputer:
void ComputeKeyProofs(
const std::vector<std::pair<CryptAuthKey, std::string>>&
key_payload_pairs,
ComputeKeyProofsCallback compute_key_proofs_callback) override;
private:
CryptAuthKeyProofComputerImpl();
void ComputeSymmetricKeyProof(const size_t index,
const CryptAuthKey& symmetric_key,
const std::string& payload);
void ComputeAsymmetricKeyProof(const size_t index,
const CryptAuthKey& asymmetric_key,
const std::string& payload);
void OnKeyProofComputed(const size_t index,
const std::string& single_key_proof);
size_t num_key_proofs_to_compute_ = 0;
std::vector<std::pair<CryptAuthKey, std::string>> key_payload_pairs_;
std::vector<std::string> output_key_proofs_;
ComputeKeyProofsCallback compute_key_proofs_callback_;
std::unique_ptr<multidevice::SecureMessageDelegate> secure_message_delegate_;
DISALLOW_COPY_AND_ASSIGN(CryptAuthKeyProofComputerImpl);
};
} // namespace device_sync
} // namespace chromeos
#endif // CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_KEY_PROOF_COMPUTER_IMPL_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.
#include "chromeos/services/device_sync/cryptauth_key_proof_computer_impl.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/macros.h"
#include "chromeos/components/multidevice/fake_secure_message_delegate.h"
#include "chromeos/components/multidevice/secure_message_delegate_impl.h"
#include "chromeos/services/device_sync/cryptauth_key.h"
#include "chromeos/services/device_sync/cryptauth_key_proof_computer.h"
#include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
#include "crypto/hkdf.h"
#include "crypto/hmac.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace device_sync {
namespace {
// The salt used for HKDF in symmetric key proofs. This value is part of the
// CryptAuth v2 Enrollment specifications.
const char kSymmetricKeyProofSalt[] = "CryptAuth Key Proof";
const char kFakePublicKeyMaterial[] = "public_key";
const char kFakePrivateKeyMaterial[] = "private_key";
const char kFakePayloadAsymmetricKey[] = "payload_asymmetric_key";
const char kFakeSymmetricKey256Material[] = "symmetric_key_256";
const char kFakePayloadSymmetricKey256[] = "payload_symmetric_key_256";
const char kFakeSymmetricKey128Material[] = "symmetric_key_128";
const char kFakePayloadSymmetricKey128[] = "payload_symmetric_key_128";
void VerifyKeyProofComputationCallback(
const std::vector<std::string>& expected_key_proofs,
const std::vector<std::string>& key_proofs) {
EXPECT_EQ(expected_key_proofs, key_proofs);
}
class FakeSecureMessageDelegateFactory
: public multidevice::SecureMessageDelegateImpl::Factory {
public:
FakeSecureMessageDelegateFactory() = default;
~FakeSecureMessageDelegateFactory() override = default;
multidevice::FakeSecureMessageDelegate* instance() { return instance_; }
private:
// multidevice::SecureMessageDelegateImpl::Factory:
std::unique_ptr<multidevice::SecureMessageDelegate> BuildInstance() override {
auto instance = std::make_unique<multidevice::FakeSecureMessageDelegate>();
instance_ = instance.get();
return instance;
}
multidevice::FakeSecureMessageDelegate* instance_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(FakeSecureMessageDelegateFactory);
};
} // namespace
class CryptAuthKeyProofComputerImplTest : public testing::Test {
protected:
CryptAuthKeyProofComputerImplTest() = default;
~CryptAuthKeyProofComputerImplTest() override = default;
void SetUp() override {
fake_secure_message_delegate_factory_ =
std::make_unique<FakeSecureMessageDelegateFactory>();
multidevice::SecureMessageDelegateImpl::Factory::SetInstanceForTesting(
fake_secure_message_delegate_factory_.get());
key_proof_computer_ =
CryptAuthKeyProofComputerImpl::Factory::Get()->BuildInstance();
}
void TearDown() override {
multidevice::SecureMessageDelegateImpl::Factory::SetInstanceForTesting(
nullptr);
}
std::string ComputeSymmetricKeyProof(const CryptAuthKey& symmetric_key,
const std::string& payload) {
size_t num_bytes =
symmetric_key.type() == cryptauthv2::KeyType::RAW256 ? 32u : 16u;
std::string derived_symmetric_key_material = crypto::HkdfSha256(
symmetric_key.symmetric_key(), kSymmetricKeyProofSalt,
symmetric_key.handle(), num_bytes);
crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
std::vector<unsigned char> signed_payload(hmac.DigestLength());
bool success =
hmac.Init(derived_symmetric_key_material) &&
hmac.Sign(payload, signed_payload.data(), signed_payload.size());
if (!success)
return "";
return std::string(signed_payload.begin(), signed_payload.end());
}
std::string ComputeAsymmetricKeyProof(const CryptAuthKey& asymmetric_key,
const std::string& payload) {
multidevice::SecureMessageDelegate::CreateOptions options;
options.encryption_scheme = securemessage::EncScheme::NONE;
options.signature_scheme = securemessage::SigScheme::ECDSA_P256_SHA256;
options.verification_key_id = asymmetric_key.handle();
std::string key_proof;
fake_secure_message_delegate()->CreateSecureMessage(
payload, asymmetric_key.private_key(), options,
base::Bind(
[](std::string* key_proof, const std::string& secure_message) {
*key_proof = secure_message;
},
&key_proof));
return key_proof;
}
CryptAuthKeyProofComputer* key_proof_computer() {
return key_proof_computer_.get();
}
multidevice::FakeSecureMessageDelegate* fake_secure_message_delegate() {
return fake_secure_message_delegate_factory_->instance();
}
private:
std::unique_ptr<FakeSecureMessageDelegateFactory>
fake_secure_message_delegate_factory_;
std::unique_ptr<CryptAuthKeyProofComputer> key_proof_computer_;
DISALLOW_COPY_AND_ASSIGN(CryptAuthKeyProofComputerImplTest);
};
TEST_F(CryptAuthKeyProofComputerImplTest, SuccessfulKeyProofComputation) {
std::vector<std::pair<CryptAuthKey, std::string>> key_payload_pairs = {
{CryptAuthKey(kFakePublicKeyMaterial, kFakePrivateKeyMaterial,
CryptAuthKey::Status::kActive, cryptauthv2::KeyType::P256),
kFakePayloadAsymmetricKey},
{CryptAuthKey(kFakeSymmetricKey256Material, CryptAuthKey::Status::kActive,
cryptauthv2::KeyType::RAW256),
kFakePayloadSymmetricKey256},
{CryptAuthKey(kFakeSymmetricKey128Material, CryptAuthKey::Status::kActive,
cryptauthv2::KeyType::RAW128),
kFakePayloadSymmetricKey128}};
std::vector<std::string> expected_key_proofs = {
ComputeAsymmetricKeyProof(key_payload_pairs[0].first,
key_payload_pairs[0].second),
ComputeSymmetricKeyProof(key_payload_pairs[1].first,
key_payload_pairs[1].second),
ComputeSymmetricKeyProof(key_payload_pairs[2].first,
key_payload_pairs[2].second)};
key_proof_computer()->ComputeKeyProofs(
key_payload_pairs,
base::BindOnce(&VerifyKeyProofComputationCallback, expected_key_proofs));
}
} // namespace device_sync
} // namespace chromeos
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