Commit 8f9edb1a authored by Josh Nohle's avatar Josh Nohle Committed by Commit Bot

[Nearby Share] Add private and public certificate classes

The private and public certificates store encrypted user and device
metadata. Private certificates are for the local device and are
only stored locally. Public certificates are uploaded to the Nearby
Share server and distributed to user's whitelisted contacts.
Certificates are used to authenticate a channel between the local device
and a remote device before the actual payload is exchanged.

These classes handle all of the crypto operations surrounding
certificates:
  * Private cert: signing a token
  * Private cert: encrypting the metadata key before advertising it
  * Private cert: encrypting the device metadata
  * Private cert: converting to a public certificate
  * Private cert: managing the set of consumed salts
  * Public cert: verifying a signed token
  * Public cert: decrypting an advertised metadata key
  * Public cert: decrypting its metadata

See http://go/nearby-chrome-cert for more details. The test vectors from
that document are used in the unit tests here, as well as in the GmsCore
implementation, to ensure cross-platform consistency.

Fixed: b/154862766
Change-Id: Ifd24decf4e896667797add3c292c69ebd312f232
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2157285
Commit-Queue: Josh Nohle <nohle@chromium.org>
Reviewed-by: default avatarDavid Benjamin <davidben@chromium.org>
Reviewed-by: default avatarAlex Chau <alexchau@chromium.org>
Reviewed-by: default avatarJames Vecore <vecore@google.com>
Cr-Commit-Position: refs/heads/master@{#782118}
parent 044395d1
......@@ -3694,6 +3694,7 @@ static_library("browser") {
"//base/util/memory_pressure",
"//base/util/timer",
"//chrome/app/vector_icons",
"//chrome/browser/nearby_sharing/certificates",
"//chrome/browser/nearby_sharing/logging",
"//chrome/browser/nearby_sharing/proto",
"//chrome/browser/policy:path_parser",
......
# Copyright (c) 2020 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.
source_set("certificates") {
sources = [
"common.cc",
"common.h",
"constants.cc",
"constants.h",
"nearby_share_decrypted_public_certificate.cc",
"nearby_share_decrypted_public_certificate.h",
"nearby_share_encrypted_metadata_key.cc",
"nearby_share_encrypted_metadata_key.h",
"nearby_share_private_certificate.cc",
"nearby_share_private_certificate.h",
"nearby_share_visibility.h",
]
deps = [
"//base",
"//chrome/browser/nearby_sharing/proto",
"//crypto",
]
}
source_set("test_support") {
testonly = true
sources = [
"test_util.cc",
"test_util.h",
]
deps = [
":certificates",
"//base",
"//chrome/browser/nearby_sharing/proto",
"//crypto",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"common_unittest.cc",
"nearby_share_decrypted_public_certificate_unittest.cc",
"nearby_share_private_certificate_unittest.cc",
]
deps = [
":certificates",
":test_support",
"//base",
"//base/test:test_support",
"//chrome/browser/nearby_sharing/proto",
"//crypto",
"//testing/gtest",
]
}
// Copyright 2020 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 "chrome/browser/nearby_sharing/certificates/common.h"
#include "base/logging.h"
#include "chrome/browser/nearby_sharing/certificates/constants.h"
#include "crypto/encryptor.h"
#include "crypto/hkdf.h"
#include "crypto/sha2.h"
#include "crypto/symmetric_key.h"
bool IsNearbyShareCertificateExpired(base::Time current_time,
base::Time not_after,
bool use_public_certificate_tolerance) {
base::TimeDelta tolerance =
use_public_certificate_tolerance
? kNearbySharePublicCertificateValidityBoundOffsetTolerance
: base::TimeDelta::FromSeconds(0);
return current_time >= not_after + tolerance;
}
bool IsNearbyShareCertificateWithinValidityPeriod(
base::Time current_time,
base::Time not_before,
base::Time not_after,
bool use_public_certificate_tolerance) {
base::TimeDelta tolerance =
use_public_certificate_tolerance
? kNearbySharePublicCertificateValidityBoundOffsetTolerance
: base::TimeDelta::FromSeconds(0);
return current_time >= not_before - tolerance &&
!IsNearbyShareCertificateExpired(current_time, not_after,
use_public_certificate_tolerance);
}
std::vector<uint8_t> DeriveNearbyShareKey(base::span<const uint8_t> key,
size_t new_num_bytes) {
return crypto::HkdfSha256(key,
/*salt=*/base::span<const uint8_t>(),
/*info=*/base::span<const uint8_t>(),
new_num_bytes);
}
std::unique_ptr<crypto::Encryptor> CreateNearbyShareCtrEncryptor(
const crypto::SymmetricKey* secret_key,
base::span<const uint8_t> salt) {
DCHECK(secret_key);
DCHECK_EQ(kNearbyShareNumBytesSecretKey, secret_key->key().size());
DCHECK_EQ(kNearbyShareNumBytesMetadataEncryptionKeySalt, salt.size());
std::unique_ptr<crypto::Encryptor> encryptor =
std::make_unique<crypto::Encryptor>();
// For CTR mode, the iv input to Init() must be empty. Instead, the iv is
// set via SetCounter().
if (!encryptor->Init(secret_key, crypto::Encryptor::Mode::CTR,
/*iv=*/base::span<const uint8_t>())) {
LOG(ERROR) << "Encryptor could not be initialized.";
return nullptr;
}
std::vector<uint8_t> iv =
DeriveNearbyShareKey(salt, kNearbyShareNumBytesAesCtrIv);
if (!encryptor->SetCounter(iv)) {
LOG(ERROR) << "Could not set encryptor counter.";
return nullptr;
}
return encryptor;
}
// Copyright 2020 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 CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_COMMON_H_
#define CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_COMMON_H_
#include <memory>
#include <vector>
#include "base/containers/span.h"
#include "base/time/time.h"
namespace crypto {
class Encryptor;
class SymmetricKey;
} // namespace crypto
// Returns true if the |current_time| exceeds |not_after| by more than the
// public certificate clock-skew tolerance if applicable.
bool IsNearbyShareCertificateExpired(base::Time current_time,
base::Time not_after,
bool use_public_certificate_tolerance);
// Returns true if the |current_time| is in the interval
// [|not_before| - tolerance, |not_after| + tolerance), where a clock-skew
// tolerance is only non-zero if |use_public_certificate_tolerance| is true.
bool IsNearbyShareCertificateWithinValidityPeriod(
base::Time current_time,
base::Time not_before,
base::Time not_after,
bool use_public_certificate_tolerance);
// Uses HKDF to generate a new key of length |new_num_bytes| from |key|. To
// conform with the GmsCore implementation, trivial salt and info are used.
std::vector<uint8_t> DeriveNearbyShareKey(base::span<const uint8_t> key,
size_t new_num_bytes);
// Creates a CTR encryptor used for metadata key encryption/decryption.
std::unique_ptr<crypto::Encryptor> CreateNearbyShareCtrEncryptor(
const crypto::SymmetricKey* secret_key,
base::span<const uint8_t> salt);
#endif // CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_COMMON_H_
// Copyright 2020 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 "chrome/browser/nearby_sharing/certificates/common.h"
#include "chrome/browser/nearby_sharing/certificates/constants.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_decrypted_public_certificate.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_private_certificate.h"
#include "chrome/browser/nearby_sharing/certificates/test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
TEST(NearbyShareCertificatesCommonTest, ValidityPeriod_PrivateCertificate) {
NearbySharePrivateCertificate cert = GetNearbyShareTestPrivateCertificate();
const bool use_public_certificate_tolerance = false;
// Set time before validity period.
base::Time now = cert.not_before() - base::TimeDelta::FromMilliseconds(1);
EXPECT_FALSE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_FALSE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
// Set time at inclusive lower bound of validity period.
now = cert.not_before();
EXPECT_FALSE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
// Set time in the middle of the validity period.
now = cert.not_before() + (cert.not_after() - cert.not_before()) / 2;
EXPECT_FALSE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
// Set time at non-inclusive upper bound of validity period.
now = cert.not_after();
EXPECT_TRUE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_FALSE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
// Set time after validity period.
now = cert.not_after() + base::TimeDelta::FromMilliseconds(1);
EXPECT_TRUE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_FALSE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
}
TEST(NearbyShareCertificatesCommonTest, ValidityPeriod_PublicCertificate) {
NearbyShareDecryptedPublicCertificate cert =
*NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
GetNearbyShareTestPublicCertificate(),
GetNearbyShareTestEncryptedMetadataKey());
const bool use_public_certificate_tolerance = true;
// Set time before validity period, outside of tolerance.
base::Time now = cert.not_before() -
kNearbySharePublicCertificateValidityBoundOffsetTolerance -
base::TimeDelta::FromMilliseconds(1);
EXPECT_FALSE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_FALSE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
// Set time before validity period, at inclusive bound with tolerance.
now = cert.not_before() -
kNearbySharePublicCertificateValidityBoundOffsetTolerance;
EXPECT_FALSE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
// Set time before validity period, inside of tolerance.
now = cert.not_before() -
kNearbySharePublicCertificateValidityBoundOffsetTolerance / 2;
EXPECT_FALSE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
// Set time at inclusive lower bound of validity period.
now = cert.not_before();
EXPECT_FALSE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
// Set time in the middle of the validity period.
now = cert.not_before() + (cert.not_after() - cert.not_before()) / 2;
EXPECT_FALSE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
// Set time at upper bound of validity period.
now = cert.not_after();
EXPECT_FALSE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
// Set time after validity period, inside of tolerance.
now = cert.not_after() +
kNearbySharePublicCertificateValidityBoundOffsetTolerance / 2;
EXPECT_FALSE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_TRUE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
// Set time after validity period, at non-inclusive tolerance bound.
now = cert.not_after() +
kNearbySharePublicCertificateValidityBoundOffsetTolerance;
EXPECT_TRUE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_FALSE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
// Set time after validity period, outside of tolerance.
now = cert.not_after() +
kNearbySharePublicCertificateValidityBoundOffsetTolerance +
base::TimeDelta::FromMilliseconds(1);
EXPECT_TRUE(IsNearbyShareCertificateExpired(
now, cert.not_after(), use_public_certificate_tolerance));
EXPECT_FALSE(IsNearbyShareCertificateWithinValidityPeriod(
now, cert.not_before(), cert.not_after(),
use_public_certificate_tolerance));
}
// Copyright 2020 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 "chrome/browser/nearby_sharing/certificates/constants.h"
const base::TimeDelta kNearbyShareCertificateValidityPeriod =
base::TimeDelta::FromDays(3);
const base::TimeDelta kNearbyShareMaxPrivateCertificateValidityBoundOffset =
base::TimeDelta::FromHours(2);
const base::TimeDelta
kNearbySharePublicCertificateValidityBoundOffsetTolerance =
base::TimeDelta::FromMinutes(30);
const size_t kNearbyShareNumPrivateCertificates = 3;
const size_t kNearbyShareNumBytesAuthenticationTokenHash = 6;
const size_t kNearbyShareNumBytesAesGcmKey = 32;
const size_t kNearbyShareNumBytesAesGcmIv = 12;
const size_t kNearbyShareNumBytesAesCtrIv = 16;
const size_t kNearbyShareNumBytesSecretKey = 32;
const size_t kNearbyShareNumBytesMetadataEncryptionKey = 14;
const size_t kNearbyShareNumBytesMetadataEncryptionKeySalt = 2;
const size_t kNearbyShareNumBytesMetadataEncryptionKeyTag = 32;
const size_t kNearbyShareNumBytesCertificateId = 32;
const size_t kNearbyShareMaxNumMetadataEncryptionKeySalts = 32768;
const size_t kNearbyShareMaxNumMetadataEncryptionKeySaltGenerationRetries = 128;
const char kNearbyShareSenderVerificationPrefix = 0x01;
const char kNearbyShareReceiverVerificationPrefix = 0x02;
// Copyright 2020 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 CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_CONSTANTS_H_
#define CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_CONSTANTS_H_
#include "base/time/time.h"
// The number of days a certificate is valid.
extern const base::TimeDelta kNearbyShareCertificateValidityPeriod;
// The maximum offset for obfuscating a private certificate's not before/after
// timestamps when converting to a public certificate.
extern const base::TimeDelta
kNearbyShareMaxPrivateCertificateValidityBoundOffset;
// To account for clock skew between the local device and remote devices, public
// certificates will be considered valid if the current time is within the
// bounds [not-before - tolerance, not-after + tolerance).
extern const base::TimeDelta
kNearbySharePublicCertificateValidityBoundOffsetTolerance;
// The number of private certificates for a given visibility to be stored and
// rotated on the local device.
extern const size_t kNearbyShareNumPrivateCertificates;
// The number of bytes that
// HMAC(/*key=*/|secret_key|, /*message=*/|authentication_token|)
// is converted to via HKDF before advertising.
extern const size_t kNearbyShareNumBytesAuthenticationTokenHash;
// Length of key in bytes required by AES-GCM encryption.
extern const size_t kNearbyShareNumBytesAesGcmKey;
// Length of salt in bytes required by AES-GCM encryption.
extern const size_t kNearbyShareNumBytesAesGcmIv;
// Length of salt in bytes required by AES-CTR encryption.
extern const size_t kNearbyShareNumBytesAesCtrIv;
// The number of bytes of the AES secret key used to encrypt/decrypt the
// metadata encryption key.
extern const size_t kNearbyShareNumBytesSecretKey;
// The number of the bytes of the AES key used to encryption personal info
// metadata, for example, name and picture data. These bytes are broadcast in an
// advertisement to other devices, thus the smaller byte size.
extern const size_t kNearbyShareNumBytesMetadataEncryptionKey;
// The number of bytes for the salt used for encryption of the metadata
// encryption key. These bytes are broadcast in the advertisement to other
// devices.
extern const size_t kNearbyShareNumBytesMetadataEncryptionKeySalt;
// The number of bytes used for the hash of the metadata encryption key.
extern const size_t kNearbyShareNumBytesMetadataEncryptionKeyTag;
// The number of bytes in a certificate's identifier.
extern const size_t kNearbyShareNumBytesCertificateId;
// Half of the possible 2-byte salt values.
//
// Note: Static identifiers can be tracked over time by setting up persistent
// scanners at known locations (eg. at different isles within a supermarket). As
// the scanners’ location is already known, anyone who walks past the scanner
// has their location recorded too. This can be used for heuristics (eg. number
// of customers in a store, customers who prefer product X also prefer product
// Y, dwell time), or can be attached to an identity (eg. rewards card when
// checking out at the cashier). By rotating our identifiers, we prevent
// inadvertently leaking location. However, even rotations can be tracked as we
// get closer to running out of salts. If tracked over a long enough time, the
// device that avoids salts that you’ve seen in the past is statistically likely
// to be the device you’re tracking. Therefore, we only use half of the
// available 2-byte salts.
extern const size_t kNearbyShareMaxNumMetadataEncryptionKeySalts;
// The max number of retries allowed to generate a salt. This is a sanity check
// that will never be hit.
extern const size_t
kNearbyShareMaxNumMetadataEncryptionKeySaltGenerationRetries;
// The prefix prepended to the UKEY2 authentication token by the sender before
// signing.
extern const char kNearbyShareSenderVerificationPrefix;
// The prefix prepended to the UKEY2 authentication token by the receiver before
// signing.
extern const char kNearbyShareReceiverVerificationPrefix;
#endif // CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_CONSTANTS_H_
// Copyright 2020 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 "chrome/browser/nearby_sharing/certificates/nearby_share_decrypted_public_certificate.h"
#include <utility>
#include "base/logging.h"
#include "chrome/browser/nearby_sharing/certificates/common.h"
#include "chrome/browser/nearby_sharing/certificates/constants.h"
#include "chrome/browser/nearby_sharing/proto/timestamp.pb.h"
#include "crypto/aead.h"
#include "crypto/encryptor.h"
#include "crypto/hmac.h"
#include "crypto/signature_verifier.h"
#include "crypto/symmetric_key.h"
namespace {
bool IsDataValid(base::Time not_before,
base::Time not_after,
base::span<const uint8_t> public_key,
crypto::SymmetricKey* secret_key,
base::span<const uint8_t> id,
base::span<const uint8_t> encrypted_metadata,
base::span<const uint8_t> metadata_encryption_key_tag) {
return not_before < not_after && !public_key.empty() && secret_key &&
secret_key->key().size() == kNearbyShareNumBytesSecretKey &&
id.size() == kNearbyShareNumBytesCertificateId &&
!encrypted_metadata.empty() &&
metadata_encryption_key_tag.size() ==
kNearbyShareNumBytesMetadataEncryptionKeyTag;
}
// Attempts to decrypt |encrypted_metadata_key| using the |secret_key|.
// Return base::nullopt if the decryption was unsuccessful.
base::Optional<std::vector<uint8_t>> DecryptMetadataKey(
const NearbyShareEncryptedMetadataKey& encrypted_metadata_key,
const crypto::SymmetricKey* secret_key) {
std::unique_ptr<crypto::Encryptor> encryptor =
CreateNearbyShareCtrEncryptor(secret_key, encrypted_metadata_key.salt());
if (!encryptor) {
LOG(ERROR)
<< "Cannot decrypt metadata key: Could not create CTR encryptor.";
return base::nullopt;
}
std::vector<uint8_t> decrypted_metadata_key;
if (!encryptor->Decrypt(base::as_bytes(base::make_span(
encrypted_metadata_key.encrypted_key())),
&decrypted_metadata_key)) {
return base::nullopt;
}
return decrypted_metadata_key;
}
// Attempts to decrypt |encrypted_metadata| with |metadata_encryption_key|,
// using |authentication_key| as the IV. Returns base::nullopt if the decryption
// was unsuccessful.
base::Optional<std::vector<uint8_t>> DecryptMetadataPayload(
base::span<const uint8_t> encrypted_metadata,
base::span<const uint8_t> metadata_encryption_key,
const crypto::SymmetricKey* secret_key) {
// Init() keeps a reference to the input key, so that reference must outlive
// the lifetime of |aead|.
std::vector<uint8_t> derived_key = DeriveNearbyShareKey(
metadata_encryption_key, kNearbyShareNumBytesAesGcmKey);
crypto::Aead aead(crypto::Aead::AeadAlgorithm::AES_256_GCM);
aead.Init(derived_key);
return aead.Open(
encrypted_metadata,
/*nonce=*/
DeriveNearbyShareKey(base::as_bytes(base::make_span(secret_key->key())),
kNearbyShareNumBytesAesGcmIv),
/*additional_data=*/base::span<const uint8_t>());
}
// Returns true if the HMAC of |decrypted_metadata_key| is
// |metadata_encryption_key_tag|.
bool VerifyMetadataEncryptionKeyTag(
base::span<const uint8_t> decrypted_metadata_key,
base::span<const uint8_t> metadata_encryption_key_tag) {
// This array of 0x00 is used to conform with the GmsCore implementation.
std::vector<uint8_t> key(kNearbyShareNumBytesMetadataEncryptionKeyTag, 0x00);
std::vector<uint8_t> result(kNearbyShareNumBytesMetadataEncryptionKeyTag);
crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
return hmac.Init(key) &&
hmac.Verify(decrypted_metadata_key, metadata_encryption_key_tag);
}
} // namespace
// static
base::Optional<NearbyShareDecryptedPublicCertificate>
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
const nearbyshare::proto::PublicCertificate& public_certificate,
const NearbyShareEncryptedMetadataKey& encrypted_metadata_key) {
// Note: The PublicCertificate.metadata_encryption_key and
// PublicCertificate.for_selected_contacts are not returned from the server
// for remote devices.
base::Time not_before = base::Time::FromJavaTime(
public_certificate.start_time().seconds() * 1000);
base::Time not_after =
base::Time::FromJavaTime(public_certificate.end_time().seconds() * 1000);
std::vector<uint8_t> public_key(public_certificate.public_key().begin(),
public_certificate.public_key().end());
std::unique_ptr<crypto::SymmetricKey> secret_key =
crypto::SymmetricKey::Import(crypto::SymmetricKey::Algorithm::AES,
public_certificate.secret_key());
std::vector<uint8_t> id(public_certificate.secret_id().begin(),
public_certificate.secret_id().end());
std::vector<uint8_t> encrypted_metadata(
public_certificate.encrypted_metadata_bytes().begin(),
public_certificate.encrypted_metadata_bytes().end());
std::vector<uint8_t> metadata_encryption_key_tag(
public_certificate.metadata_encryption_key_tag().begin(),
public_certificate.metadata_encryption_key_tag().end());
if (!IsDataValid(not_before, not_after, public_key, secret_key.get(), id,
encrypted_metadata, metadata_encryption_key_tag)) {
return base::nullopt;
}
// Note: Failure to decrypt the metadata key should not log an error. When
// another device advertises their encrypted metadata key, we do not know what
// public certificate that corresponds too. So, we will potentially be calling
// DecryptPublicCertificate() on all of our public certificates with the same
// encrypted metadata key until we find the correct one.
base::Optional<std::vector<uint8_t>> decrypted_metadata_key =
DecryptMetadataKey(encrypted_metadata_key, secret_key.get());
if (!decrypted_metadata_key)
return base::nullopt;
// Confirm that the decrypted metadata key agrees with key commitment tag.
if (!VerifyMetadataEncryptionKeyTag(*decrypted_metadata_key,
metadata_encryption_key_tag)) {
LOG(ERROR) << "Metadata decryption failed: Failed to verify metadata "
<< "encryption key tag.";
return base::nullopt;
}
// If the key was able to be decrypted, we expect the metadata to be able to
// be decrypted.
base::Optional<std::vector<uint8_t>> decrypted_metadata_bytes =
DecryptMetadataPayload(encrypted_metadata, *decrypted_metadata_key,
secret_key.get());
if (!decrypted_metadata_bytes) {
LOG(ERROR) << "Metadata decryption failed: Failed to decrypt metadata "
<< "payload.";
return base::nullopt;
}
nearbyshare::proto::EncryptedMetadata unencrypted_metadata;
if (!unencrypted_metadata.ParseFromArray(decrypted_metadata_bytes->data(),
decrypted_metadata_bytes->size())) {
LOG(ERROR) << "Metadata decryption failed: Failed to parse decrypted "
<< "metadata payload.";
return base::nullopt;
}
return NearbyShareDecryptedPublicCertificate(
not_before, not_after, std::move(public_key), std::move(id),
std::move(unencrypted_metadata));
}
NearbyShareDecryptedPublicCertificate::NearbyShareDecryptedPublicCertificate(
base::Time not_before,
base::Time not_after,
std::vector<uint8_t> public_key,
std::vector<uint8_t> id,
nearbyshare::proto::EncryptedMetadata unencrypted_metadata)
: not_before_(not_before),
not_after_(not_after),
public_key_(std::move(public_key)),
id_(std::move(id)),
unencrypted_metadata_(std::move(unencrypted_metadata)) {}
NearbyShareDecryptedPublicCertificate::NearbyShareDecryptedPublicCertificate(
NearbyShareDecryptedPublicCertificate&&) = default;
NearbyShareDecryptedPublicCertificate::
~NearbyShareDecryptedPublicCertificate() = default;
bool NearbyShareDecryptedPublicCertificate::VerifySignature(
base::span<const uint8_t> payload,
base::span<const uint8_t> signature) const {
crypto::SignatureVerifier verifier;
if (!verifier.VerifyInit(crypto::SignatureVerifier::ECDSA_SHA256, signature,
public_key_)) {
LOG(ERROR) << "Verification failed: Initialization unsuccessful.";
return false;
}
verifier.VerifyUpdate(payload);
return verifier.VerifyFinal();
}
// Copyright 2020 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 CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_DECRYPTED_PUBLIC_CERTIFICATE_H_
#define CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_DECRYPTED_PUBLIC_CERTIFICATE_H_
#include <memory>
#include <vector>
#include "base/containers/span.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_encrypted_metadata_key.h"
#include "chrome/browser/nearby_sharing/proto/encrypted_metadata.pb.h"
#include "chrome/browser/nearby_sharing/proto/rpc_resources.pb.h"
// Stores decrypted metadata and crypto keys for the remote device that uploaded
// this certificate to the Nearby Share server. Use DecryptPublicCertificate()
// to generate an instance. This class provides a method for verifying a signed
// payload during the authentication flow.
class NearbyShareDecryptedPublicCertificate {
public:
// Attempts to decrypt the encrypted metadata of the PublicCertificate proto
// by first decrypting the |encrypted_metadata_key| using the secret key
// then using the decrypted key to decrypt the metadata. Returns base::nullopt
// if the metadata was not successfully decrypted or if the proto data is
// invalid.
static base::Optional<NearbyShareDecryptedPublicCertificate>
DecryptPublicCertificate(
const nearbyshare::proto::PublicCertificate& public_certificate,
const NearbyShareEncryptedMetadataKey& encrypted_metadata_key);
NearbyShareDecryptedPublicCertificate(
NearbyShareDecryptedPublicCertificate&&);
virtual ~NearbyShareDecryptedPublicCertificate();
const std::vector<uint8_t>& id() const { return id_; }
base::Time not_before() const { return not_before_; }
base::Time not_after() const { return not_after_; }
const nearbyshare::proto::EncryptedMetadata& unencrypted_metadata() const {
return unencrypted_metadata_;
}
// Verifies the |signature| of the signed |payload| using |public_key_|.
// Returns true if verification was successful.
bool VerifySignature(base::span<const uint8_t> payload,
base::span<const uint8_t> signature) const;
private:
NearbyShareDecryptedPublicCertificate(
base::Time not_before,
base::Time not_after,
std::vector<uint8_t> public_key,
std::vector<uint8_t> id,
nearbyshare::proto::EncryptedMetadata unencrypted_metadata);
// The begin/end times of the certificate's validity period. To avoid issues
// with clock skew, these time might be offset compared to the corresponding
// private certificate.
base::Time not_before_;
base::Time not_after_;
// A P-256 public key used for verification. The bytes comprise a DER-encoded
// ASN.1 SubjectPublicKeyInfo from the X.509 specification (RFC 5280).
std::vector<uint8_t> public_key_;
// An ID for the certificate, most likely generated from the secret key.
std::vector<uint8_t> id_;
// Unencrypted device metadata. The proto name is misleading; it holds data
// that was previously serialized and encrypted.
nearbyshare::proto::EncryptedMetadata unencrypted_metadata_;
};
#endif // CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_DECRYPTED_PUBLIC_CERTIFICATE_H_
// Copyright 2020 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 "chrome/browser/nearby_sharing/certificates/nearby_share_decrypted_public_certificate.h"
#include "base/optional.h"
#include "chrome/browser/nearby_sharing/certificates/constants.h"
#include "chrome/browser/nearby_sharing/certificates/test_util.h"
#include "chrome/browser/nearby_sharing/proto/rpc_resources.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
TEST(NearbyShareDecryptedPublicCertificateTest, Decrypt) {
base::Optional<NearbyShareDecryptedPublicCertificate> cert =
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
GetNearbyShareTestPublicCertificate(),
GetNearbyShareTestEncryptedMetadataKey());
EXPECT_TRUE(cert);
EXPECT_EQ(
base::Time::FromJavaTime(
GetNearbyShareTestPublicCertificate().start_time().seconds() * 1000),
cert->not_before());
EXPECT_EQ(
base::Time::FromJavaTime(
GetNearbyShareTestPublicCertificate().end_time().seconds() * 1000),
cert->not_after());
EXPECT_EQ(std::vector<uint8_t>(
GetNearbyShareTestPublicCertificate().secret_id().begin(),
GetNearbyShareTestPublicCertificate().secret_id().end()),
cert->id());
EXPECT_EQ(GetNearbyShareTestMetadata().SerializeAsString(),
cert->unencrypted_metadata().SerializeAsString());
}
TEST(NearbyShareDecryptedPublicCertificateTest, Decrypt_IncorrectKeyFailure) {
// Input incorrect metadata encryption key.
EXPECT_FALSE(NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
GetNearbyShareTestPublicCertificate(),
NearbyShareEncryptedMetadataKey(
std::vector<uint8_t>(kNearbyShareNumBytesMetadataEncryptionKey, 0x00),
std::vector<uint8_t>(kNearbyShareNumBytesMetadataEncryptionKeySalt,
0x00))));
}
TEST(NearbyShareDecryptedPublicCertificateTest,
Decrypt_MetadataDecryptionFailure) {
// Use metadata that cannot be decrypted with the given key.
nearbyshare::proto::PublicCertificate proto_cert =
GetNearbyShareTestPublicCertificate();
proto_cert.set_encrypted_metadata_bytes("invalid metadata");
EXPECT_FALSE(NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
proto_cert, GetNearbyShareTestEncryptedMetadataKey()));
}
TEST(NearbyShareDecryptedPublicCertificateTest, Decrypt_InvalidDataFailure) {
// Do not accept the input PublicCertificate because the validity period does
// not make sense.
nearbyshare::proto::PublicCertificate proto_cert =
GetNearbyShareTestPublicCertificate();
proto_cert.mutable_end_time()->set_seconds(proto_cert.start_time().seconds() -
1);
EXPECT_FALSE(NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
proto_cert, GetNearbyShareTestEncryptedMetadataKey()));
}
TEST(NearbySharePublicCertificateTest, Verify) {
base::Optional<NearbyShareDecryptedPublicCertificate> cert =
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
GetNearbyShareTestPublicCertificate(),
GetNearbyShareTestEncryptedMetadataKey());
EXPECT_TRUE(cert->VerifySignature(GetNearbyShareTestPayloadToSign(),
GetNearbyShareTestSampleSignature()));
}
TEST(NearbyShareDecryptedPublicCertificateTest, Verify_InitFailure) {
// Public key has invalid SubjectPublicKeyInfo format.
nearbyshare::proto::PublicCertificate proto_cert =
GetNearbyShareTestPublicCertificate();
proto_cert.set_public_key("invalid public key");
base::Optional<NearbyShareDecryptedPublicCertificate> cert =
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
proto_cert, GetNearbyShareTestEncryptedMetadataKey());
ASSERT_TRUE(cert);
EXPECT_FALSE(cert->VerifySignature(GetNearbyShareTestPayloadToSign(),
GetNearbyShareTestSampleSignature()));
}
TEST(NearbyShareDecryptedPublicCertificateTest, Verify_WrongSignature) {
base::Optional<NearbyShareDecryptedPublicCertificate> cert =
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
GetNearbyShareTestPublicCertificate(),
GetNearbyShareTestEncryptedMetadataKey());
EXPECT_FALSE(
cert->VerifySignature(GetNearbyShareTestPayloadToSign(),
/*signature=*/base::span<const uint8_t>()));
}
// Copyright 2020 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 "chrome/browser/nearby_sharing/certificates/nearby_share_encrypted_metadata_key.h"
#include <utility>
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/nearby_sharing/certificates/constants.h"
NearbyShareEncryptedMetadataKey::NearbyShareEncryptedMetadataKey(
std::vector<uint8_t> encrypted_key,
std::vector<uint8_t> salt)
: encrypted_key_(std::move(encrypted_key)), salt_(std::move(salt)) {
DCHECK_EQ(kNearbyShareNumBytesMetadataEncryptionKey, encrypted_key_.size());
DCHECK_EQ(kNearbyShareNumBytesMetadataEncryptionKeySalt, salt_.size());
}
NearbyShareEncryptedMetadataKey::NearbyShareEncryptedMetadataKey(
NearbyShareEncryptedMetadataKey&&) = default;
NearbyShareEncryptedMetadataKey::~NearbyShareEncryptedMetadataKey() = default;
// Copyright 2020 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 CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_ENCRYPTED_METADATA_KEY_H_
#define CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_ENCRYPTED_METADATA_KEY_H_
#include <vector>
// Holds the encrypted symmetric key--the key used to encrypt user/device
// metatdata--as well as the salt used to encrypt the key.
struct NearbyShareEncryptedMetadataKey {
public:
NearbyShareEncryptedMetadataKey(std::vector<uint8_t> encrypted_key,
std::vector<uint8_t> salt);
NearbyShareEncryptedMetadataKey(NearbyShareEncryptedMetadataKey&&);
~NearbyShareEncryptedMetadataKey();
const std::vector<uint8_t>& encrypted_key() const { return encrypted_key_; }
const std::vector<uint8_t>& salt() const { return salt_; }
private:
std::vector<uint8_t> encrypted_key_;
std::vector<uint8_t> salt_;
};
#endif // CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_ENCRYPTED_METADATA_KEY_H_
// Copyright 2020 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 "chrome/browser/nearby_sharing/certificates/nearby_share_private_certificate.h"
#include <utility>
#include "base/logging.h"
#include "base/rand_util.h"
#include "chrome/browser/nearby_sharing/certificates/common.h"
#include "chrome/browser/nearby_sharing/certificates/constants.h"
#include "chrome/browser/nearby_sharing/proto/timestamp.pb.h"
#include "crypto/aead.h"
#include "crypto/ec_private_key.h"
#include "crypto/ec_signature_creator.h"
#include "crypto/encryptor.h"
#include "crypto/hmac.h"
#include "crypto/random.h"
#include "crypto/sha2.h"
#include "crypto/symmetric_key.h"
namespace {
std::vector<uint8_t> GenerateRandomBytes(size_t num_bytes) {
std::vector<uint8_t> bytes(num_bytes);
crypto::RandBytes(bytes);
return bytes;
}
// Generates a random validity bound offset in the interval
// [0, kNearbyShareMaxPrivateCertificateValidityBoundOffset).
base::TimeDelta GenerateRandomOffset() {
return base::TimeDelta::FromMicroseconds(base::RandGenerator(
kNearbyShareMaxPrivateCertificateValidityBoundOffset.InMicroseconds()));
}
// Generates a certificate identifier by hashing the input secret |key|.
std::vector<uint8_t> CreateCertificateIdFromSecretKey(
const crypto::SymmetricKey& key) {
DCHECK_EQ(crypto::kSHA256Length, kNearbyShareNumBytesCertificateId);
std::vector<uint8_t> id(kNearbyShareNumBytesCertificateId);
crypto::SHA256HashString(key.key(), id.data(), id.size());
return id;
}
// Creates an HMAC from |metadata_encryption_key| to be used as a key commitment
// in certificates.
base::Optional<std::vector<uint8_t>> CreateMetadataEncryptionKeyTag(
base::span<const uint8_t> metadata_encryption_key) {
// This array of 0x00 is used to conform with the GmsCore implementation.
std::vector<uint8_t> key(kNearbyShareNumBytesMetadataEncryptionKeyTag, 0x00);
std::vector<uint8_t> result(kNearbyShareNumBytesMetadataEncryptionKeyTag);
crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
if (!hmac.Init(key) || !hmac.Sign(metadata_encryption_key, result))
return base::nullopt;
return result;
}
} // namespace
NearbySharePrivateCertificate::NearbySharePrivateCertificate(
NearbyShareVisibility visibility,
base::Time not_before,
nearbyshare::proto::EncryptedMetadata unencrypted_metadata)
: visibility_(visibility),
not_before_(not_before),
not_after_(not_before_ + kNearbyShareCertificateValidityPeriod),
key_pair_(crypto::ECPrivateKey::Create()),
secret_key_(crypto::SymmetricKey::GenerateRandomKey(
crypto::SymmetricKey::Algorithm::AES,
/*key_size_in_bits=*/8 * kNearbyShareNumBytesSecretKey)),
metadata_encryption_key_(
GenerateRandomBytes(kNearbyShareNumBytesMetadataEncryptionKey)),
id_(CreateCertificateIdFromSecretKey(*secret_key_)),
unencrypted_metadata_(std::move(unencrypted_metadata)) {
DCHECK_NE(visibility, NearbyShareVisibility::kNoOne);
}
NearbySharePrivateCertificate::NearbySharePrivateCertificate(
NearbyShareVisibility visibility,
base::Time not_before,
base::Time not_after,
std::unique_ptr<crypto::ECPrivateKey> key_pair,
std::unique_ptr<crypto::SymmetricKey> secret_key,
std::vector<uint8_t> metadata_encryption_key,
std::vector<uint8_t> id,
nearbyshare::proto::EncryptedMetadata unencrypted_metadata,
std::set<std::vector<uint8_t>> consumed_salts)
: visibility_(visibility),
not_before_(not_before),
not_after_(not_after),
key_pair_(std::move(key_pair)),
secret_key_(std::move(secret_key)),
metadata_encryption_key_(std::move(metadata_encryption_key)),
id_(std::move(id)),
unencrypted_metadata_(std::move(unencrypted_metadata)),
consumed_salts_(std::move(consumed_salts)) {
DCHECK_NE(visibility, NearbyShareVisibility::kNoOne);
}
NearbySharePrivateCertificate::NearbySharePrivateCertificate(
NearbySharePrivateCertificate&&) = default;
NearbySharePrivateCertificate::~NearbySharePrivateCertificate() = default;
base::Optional<NearbyShareEncryptedMetadataKey>
NearbySharePrivateCertificate::EncryptMetadataKey() {
base::Optional<std::vector<uint8_t>> salt = GenerateUnusedSalt();
if (!salt) {
LOG(ERROR) << "Encryption failed: Salt generation unsuccessful.";
return base::nullopt;
}
std::unique_ptr<crypto::Encryptor> encryptor =
CreateNearbyShareCtrEncryptor(secret_key_.get(), *salt);
if (!encryptor) {
LOG(ERROR) << "Encryption failed: Could not create CTR encryptor.";
return base::nullopt;
}
DCHECK_EQ(kNearbyShareNumBytesMetadataEncryptionKey,
metadata_encryption_key_.size());
std::vector<uint8_t> encrypted_metadata_key;
if (!encryptor->Encrypt(metadata_encryption_key_, &encrypted_metadata_key)) {
LOG(ERROR) << "Encryption failed: Could not encrypt metadata key.";
return base::nullopt;
}
return NearbyShareEncryptedMetadataKey(encrypted_metadata_key, *salt);
}
base::Optional<std::vector<uint8_t>> NearbySharePrivateCertificate::Sign(
base::span<const uint8_t> payload) const {
std::unique_ptr<crypto::ECSignatureCreator> signer(
crypto::ECSignatureCreator::Create(key_pair_.get()));
std::vector<uint8_t> signature;
if (!signer->Sign(payload.data(), payload.size(), &signature)) {
LOG(ERROR) << "Signing failed.";
return base::nullopt;
}
return signature;
}
base::Optional<nearbyshare::proto::PublicCertificate>
NearbySharePrivateCertificate::ToPublicCertificate() {
std::vector<uint8_t> public_key;
if (!key_pair_->ExportPublicKey(&public_key)) {
LOG(ERROR) << "Failed to export public key.";
return base::nullopt;
}
base::Optional<std::vector<uint8_t>> encrypted_metadata_bytes =
EncryptMetadata();
if (!encrypted_metadata_bytes) {
LOG(ERROR) << "Failed to encrypt metadata.";
return base::nullopt;
}
base::Optional<std::vector<uint8_t>> metadata_encryption_key_tag =
CreateMetadataEncryptionKeyTag(metadata_encryption_key_);
if (!metadata_encryption_key_tag) {
LOG(ERROR) << "Failed to compute metadata encryption key tag.";
return base::nullopt;
}
base::TimeDelta not_before_offset =
offset_for_testing_.value_or(GenerateRandomOffset());
base::TimeDelta not_after_offset =
offset_for_testing_.value_or(GenerateRandomOffset());
nearbyshare::proto::PublicCertificate public_certificate;
public_certificate.set_secret_id(std::string(id_.begin(), id_.end()));
public_certificate.set_secret_key(secret_key_->key());
public_certificate.set_public_key(
std::string(public_key.begin(), public_key.end()));
public_certificate.mutable_start_time()->set_seconds(
(not_before_ - not_before_offset).ToJavaTime() / 1000);
public_certificate.mutable_end_time()->set_seconds(
(not_after_ + not_after_offset).ToJavaTime() / 1000);
public_certificate.set_for_selected_contacts(
visibility_ == NearbyShareVisibility::kSelectedContacts);
public_certificate.set_metadata_encryption_key(std::string(
metadata_encryption_key_.begin(), metadata_encryption_key_.end()));
public_certificate.set_encrypted_metadata_bytes(std::string(
encrypted_metadata_bytes->begin(), encrypted_metadata_bytes->end()));
public_certificate.set_metadata_encryption_key_tag(
std::string(metadata_encryption_key_tag->begin(),
metadata_encryption_key_tag->end()));
return public_certificate;
}
base::Optional<std::vector<uint8_t>>
NearbySharePrivateCertificate::GenerateUnusedSalt() {
if (consumed_salts_.size() >= kNearbyShareMaxNumMetadataEncryptionKeySalts) {
LOG(ERROR) << "All salts exhausted for certificate.";
return base::nullopt;
}
for (size_t attempt = 0;
attempt < kNearbyShareMaxNumMetadataEncryptionKeySaltGenerationRetries;
++attempt) {
std::vector<uint8_t> salt;
if (next_salts_for_testing_.empty()) {
salt = GenerateRandomBytes(2u);
} else {
salt = next_salts_for_testing_.front();
next_salts_for_testing_.pop();
}
DCHECK_EQ(2u, salt.size());
if (!base::Contains(consumed_salts_, salt)) {
consumed_salts_.insert(salt);
return salt;
}
}
LOG(ERROR) << "Salt generation exceeded max number of retries. This is "
"highly improbable.";
return base::nullopt;
}
base::Optional<std::vector<uint8_t>>
NearbySharePrivateCertificate::EncryptMetadata() {
// Init() keeps a reference to the input key, so that reference must outlive
// the lifetime of |aead|.
std::vector<uint8_t> derived_key = DeriveNearbyShareKey(
metadata_encryption_key_, kNearbyShareNumBytesAesGcmKey);
crypto::Aead aead(crypto::Aead::AeadAlgorithm::AES_256_GCM);
aead.Init(derived_key);
std::vector<uint8_t> metadata_array(unencrypted_metadata_.ByteSizeLong());
unencrypted_metadata_.SerializeToArray(metadata_array.data(),
metadata_array.size());
return aead.Seal(
metadata_array,
/*nonce=*/
DeriveNearbyShareKey(base::as_bytes(base::make_span(secret_key_->key())),
kNearbyShareNumBytesAesGcmIv),
/*additional_data=*/base::span<const uint8_t>());
}
// Copyright 2020 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 CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_PRIVATE_CERTIFICATE_H_
#define CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_PRIVATE_CERTIFICATE_H_
#include <memory>
#include <set>
#include <vector>
#include "base/containers/queue.h"
#include "base/containers/span.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_encrypted_metadata_key.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_visibility.h"
#include "chrome/browser/nearby_sharing/proto/encrypted_metadata.pb.h"
#include "chrome/browser/nearby_sharing/proto/rpc_resources.pb.h"
namespace crypto {
class ECPrivateKey;
class SymmetricKey;
} // namespace crypto
// Stores metadata and crypto keys for the local device. This certificate
// can be converted to a public certificate and sent to select contacts, who
// will then use the certificate for authenticating the local device before
// transferring data. Provides method for signing a payload during
// authentication with a remote device. Provides method for encrypting the
// metadata encryption key, which can then be advertised.
class NearbySharePrivateCertificate {
public:
// Generates a random EC key pair, secret key, and metadata encryption
// key. Derives the certificate ID from the secret key. Derives the
// not-after time from |not_before| and the certificate validity period fixed
// by the Nearby Share protocol. Visibility cannot be "no one".
NearbySharePrivateCertificate(
NearbyShareVisibility visibility,
base::Time not_before,
nearbyshare::proto::EncryptedMetadata unencrypted_metadata);
NearbySharePrivateCertificate(
NearbyShareVisibility visibility,
base::Time not_before,
base::Time not_after,
std::unique_ptr<crypto::ECPrivateKey> key_pair,
std::unique_ptr<crypto::SymmetricKey> secret_key,
std::vector<uint8_t> metadata_encryption_key,
std::vector<uint8_t> id,
nearbyshare::proto::EncryptedMetadata unencrypted_metadata,
std::set<std::vector<uint8_t>> consumed_salts);
NearbySharePrivateCertificate(NearbySharePrivateCertificate&&);
virtual ~NearbySharePrivateCertificate();
const std::vector<uint8_t>& id() const { return id_; }
NearbyShareVisibility visibility() const { return visibility_; }
base::Time not_before() const { return not_before_; }
base::Time not_after() const { return not_after_; }
const nearbyshare::proto::EncryptedMetadata& unencrypted_metadata() const {
return unencrypted_metadata_;
}
// Encrypts |metadata_encryption_key_| with the |secret_key_|, using a
// randomly generated 2-byte salt that has not already been consumed. Returns
// base::nullopt if the encryption failed or if there are no remaining salts.
// Note: Due to the generation and storage of an unconsumed salt, this method
// is not thread safe.
base::Optional<NearbyShareEncryptedMetadataKey> EncryptMetadataKey();
// Signs the input |payload| with the private key from |key_pair_|. Returns
// base::nullopt if the signing was unsuccessful.
base::Optional<std::vector<uint8_t>> Sign(
base::span<const uint8_t> payload) const;
// Converts this private certificate to a public certificate proto that can be
// shared with select contacts. Returns base::nullopt if the conversion was
// unsuccessful.
base::Optional<nearbyshare::proto::PublicCertificate> ToPublicCertificate();
// For testing only.
base::queue<std::vector<uint8_t>>& next_salts_for_testing() {
return next_salts_for_testing_;
}
base::Optional<base::TimeDelta>& offset_for_testing() {
return offset_for_testing_;
}
private:
// Generates a random 2-byte salt used for encrypting the metadata encryption
// key. Adds returned salt to |consumed_salts_|. Returns base::nullopt if the
// maximum number of salts have been exhausted or if an unconsumed salt cannot
// be found in a fixed number of attempts, though this is highly improbably.
// Note: This function is not thread safe.
base::Optional<std::vector<uint8_t>> GenerateUnusedSalt();
// Encrypts |unencrypted_metadata_| with the |metadata_encryption_key_|, using
// the |secret_key_| as salt.
base::Optional<std::vector<uint8_t>> EncryptMetadata();
// Specifies which contacts can receive the public certificate corresponding
// to this private certificate.
NearbyShareVisibility visibility_;
// The begin/end times of the certificate's validity period. Note: An offset
// is not yet applied to these values. To avoid issues with clock skew,
// offsets should be applied during conversion to a public certificate.
base::Time not_before_;
base::Time not_after_;
// The public/private P-256 key pair used for verification/signing to ensure
// secret of public certificates. The public key is included in the
// public certificate, but the private key will never leave the device.
std::unique_ptr<crypto::ECPrivateKey> key_pair_;
// A 32-byte AES key used, along with a salt, to encrypt the
// |metadata_encryption_key_|, after which it can be safely advertised.
// Included in the public certificate.
std::unique_ptr<crypto::SymmetricKey> secret_key_;
// A 14-byte symmetric key used to encrypt |unencrypted_metadata_|. Not
// included in public certificate.
std::vector<uint8_t> metadata_encryption_key_;
// An ID for the certificate, generated from the secret key.
std::vector<uint8_t> id_;
// Unencrypted device metadata. The proto name is misleading; it holds data
// that will eventually be serialized and encrypted.
nearbyshare::proto::EncryptedMetadata unencrypted_metadata_;
// The set of 2-byte salts already used to encrypt the metadata key.
std::set<std::vector<uint8_t>> consumed_salts_;
// For testing only.
base::queue<std::vector<uint8_t>> next_salts_for_testing_;
base::Optional<base::TimeDelta> offset_for_testing_;
};
#endif // CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_PRIVATE_CERTIFICATE_H_
// Copyright 2020 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 <string>
#include "base/optional.h"
#include "chrome/browser/nearby_sharing/certificates/constants.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_decrypted_public_certificate.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_encrypted_metadata_key.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_private_certificate.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_visibility.h"
#include "chrome/browser/nearby_sharing/certificates/test_util.h"
#include "chrome/browser/nearby_sharing/proto/rpc_resources.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
TEST(NearbySharePrivateCertificateTest, Construction) {
NearbySharePrivateCertificate private_certificate(
NearbyShareVisibility::kAllContacts, GetNearbyShareTestNotBefore(),
GetNearbyShareTestMetadata());
EXPECT_EQ(kNearbyShareNumBytesCertificateId, private_certificate.id().size());
EXPECT_EQ(NearbyShareVisibility::kAllContacts,
private_certificate.visibility());
EXPECT_EQ(GetNearbyShareTestNotBefore(), private_certificate.not_before());
EXPECT_EQ(
GetNearbyShareTestNotBefore() + kNearbyShareCertificateValidityPeriod,
private_certificate.not_after());
EXPECT_EQ(GetNearbyShareTestMetadata().SerializeAsString(),
private_certificate.unencrypted_metadata().SerializeAsString());
}
TEST(NearbySharePrivateCertificateTest, EncryptMetadataKey) {
NearbySharePrivateCertificate private_certificate(
NearbyShareVisibility::kAllContacts, GetNearbyShareTestNotBefore(),
GetNearbyShareTestMetadata());
base::Optional<NearbyShareEncryptedMetadataKey> encrypted_metadata_key =
private_certificate.EncryptMetadataKey();
ASSERT_TRUE(encrypted_metadata_key);
EXPECT_EQ(kNearbyShareNumBytesMetadataEncryptionKey,
encrypted_metadata_key->encrypted_key().size());
EXPECT_EQ(kNearbyShareNumBytesMetadataEncryptionKeySalt,
encrypted_metadata_key->salt().size());
}
TEST(NearbySharePrivateCertificateTest, EncryptMetadataKey_FixedData) {
NearbySharePrivateCertificate private_certificate =
GetNearbyShareTestPrivateCertificate();
base::Optional<NearbyShareEncryptedMetadataKey> encrypted_metadata_key =
private_certificate.EncryptMetadataKey();
EXPECT_EQ(GetNearbyShareTestEncryptedMetadataKey().encrypted_key(),
encrypted_metadata_key->encrypted_key());
EXPECT_EQ(GetNearbyShareTestEncryptedMetadataKey().salt(),
encrypted_metadata_key->salt());
}
TEST(NearbySharePrivateCertificateTest,
EncryptMetadataKey_SaltsExhaustedFailure) {
NearbySharePrivateCertificate private_certificate =
GetNearbyShareTestPrivateCertificate();
for (size_t i = 0; i < kNearbyShareMaxNumMetadataEncryptionKeySalts; ++i) {
EXPECT_TRUE(private_certificate.EncryptMetadataKey());
}
EXPECT_FALSE(private_certificate.EncryptMetadataKey());
}
TEST(NearbySharePrivateCertificateTest,
EncryptMetadataKey_TooManySaltGenerationRetriesFailure) {
NearbySharePrivateCertificate private_certificate =
GetNearbyShareTestPrivateCertificate();
EXPECT_TRUE(private_certificate.EncryptMetadataKey());
while (private_certificate.next_salts_for_testing().size() <
kNearbyShareMaxNumMetadataEncryptionKeySaltGenerationRetries) {
private_certificate.next_salts_for_testing().push(GetNearbyShareTestSalt());
}
EXPECT_FALSE(private_certificate.EncryptMetadataKey());
}
TEST(NearbySharePrivateCertificateTest, PublicCertificateConversion) {
NearbySharePrivateCertificate private_certificate =
GetNearbyShareTestPrivateCertificate();
private_certificate.offset_for_testing() = GetNearbyShareTestValidityOffset();
base::Optional<nearbyshare::proto::PublicCertificate> public_certificate =
private_certificate.ToPublicCertificate();
ASSERT_TRUE(public_certificate);
EXPECT_EQ(GetNearbyShareTestPublicCertificate().SerializeAsString(),
public_certificate->SerializeAsString());
}
TEST(NearbySharePrivateCertificateTest, EncryptDecryptRoundtrip) {
NearbySharePrivateCertificate private_certificate =
GetNearbyShareTestPrivateCertificate();
base::Optional<NearbyShareDecryptedPublicCertificate>
decrypted_public_certificate =
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
*private_certificate.ToPublicCertificate(),
*private_certificate.EncryptMetadataKey());
ASSERT_TRUE(decrypted_public_certificate);
EXPECT_EQ(
private_certificate.unencrypted_metadata().SerializeAsString(),
decrypted_public_certificate->unencrypted_metadata().SerializeAsString());
}
TEST(NearbySharePrivateCertificateTest, SignVerifyRoundtrip) {
NearbySharePrivateCertificate private_certificate =
GetNearbyShareTestPrivateCertificate();
base::Optional<std::vector<uint8_t>> signature =
private_certificate.Sign(GetNearbyShareTestPayloadToSign());
ASSERT_TRUE(signature);
base::Optional<NearbyShareDecryptedPublicCertificate>
decrypted_public_certificate =
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
*private_certificate.ToPublicCertificate(),
*private_certificate.EncryptMetadataKey());
EXPECT_TRUE(decrypted_public_certificate->VerifySignature(
GetNearbyShareTestPayloadToSign(), *signature));
}
// Copyright 2020 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 CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_VISIBILITY_H_
#define CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_VISIBILITY_H_
// Specifies which contacts can receive the public certificate corresponding
// to the local device's private certificate.
enum class NearbyShareVisibility { kNoOne, kAllContacts, kSelectedContacts };
#endif // CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_VISIBILITY_H_
This diff is collapsed.
// Copyright 2020 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 CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_TEST_UTIL_H_
#define CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_TEST_UTIL_H_
#include <memory>
#include <vector>
#include "base/time/time.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_encrypted_metadata_key.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_private_certificate.h"
#include "chrome/browser/nearby_sharing/proto/encrypted_metadata.pb.h"
#include "chrome/browser/nearby_sharing/proto/rpc_resources.pb.h"
#include "crypto/ec_private_key.h"
#include "crypto/symmetric_key.h"
std::unique_ptr<crypto::ECPrivateKey> GetNearbyShareTestP256KeyPair();
const std::vector<uint8_t>& GetNearbyShareTestP256PublicKey();
std::unique_ptr<crypto::SymmetricKey> GetNearbyShareTestSecretKey();
const std::vector<uint8_t>& GetNearbyShareTestCertificateId();
const std::vector<uint8_t>& GetNearbyShareTestMetadataEncryptionKey();
const std::vector<uint8_t>& GetNearbyShareTestMetadataEncryptionKeyTag();
const std::vector<uint8_t>& GetNearbyShareTestSalt();
const NearbyShareEncryptedMetadataKey& GetNearbyShareTestEncryptedMetadataKey();
base::Time GetNearbyShareTestNotBefore();
base::TimeDelta GetNearbyShareTestValidityOffset();
const nearbyshare::proto::EncryptedMetadata& GetNearbyShareTestMetadata();
const std::vector<uint8_t>& GetNearbyShareTestEncryptedMetadata();
const std::vector<uint8_t>& GetNearbyShareTestPayloadToSign();
const std::vector<uint8_t>& GetNearbyShareTestSampleSignature();
NearbySharePrivateCertificate GetNearbyShareTestPrivateCertificate();
const nearbyshare::proto::PublicCertificate&
GetNearbyShareTestPublicCertificate();
#endif // CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_TEST_UTIL_H_
......@@ -9,6 +9,7 @@ proto_library("proto") {
"certificate_rpc.proto",
"contact_rpc.proto",
"device_rpc.proto",
"encrypted_metadata.proto",
"field_mask.proto",
"rpc_resources.proto",
"timestamp.proto",
......
// Copyright 2020 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.
// Keep in sync with
// http://google3/location/nearby/sharing/proto/contact_certificates.proto.
// Messages not used in Chrome have been omitted. Last copied at cl/310853364.
syntax = "proto2";
package nearbyshare.proto;
option optimize_for = LITE_RUNTIME;
message EncryptedMetadata {
// The name of the local device when certificate is created.
optional string device_name = 1;
// The name of the user whose device created the certificate.
optional string full_name = 2;
// The icon url of the user whose device created the certificate.
optional string icon_url = 3;
// The Bluetooth MAC address of the device which created the certificate.
optional bytes bluetooth_mac_address = 4;
// The obfuscated Gaia ID of the account which created the certificate.
optional string obfuscated_gaia_id = 5;
}
......@@ -4320,7 +4320,9 @@ test("unit_tests") {
}
deps += [
"//chrome/browser/nearby_sharing/certificates:unit_tests",
"//chrome/browser/nearby_sharing/logging:unit_tests",
"//chrome/browser/nearby_sharing/proto",
"//chrome/browser/resource_coordinator:tab_metrics_event_proto",
"//chrome/browser/resource_coordinator/tab_ranker:tab_features_test_helper",
"//chrome/services/sharing:unit_tests",
......
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