123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- // Copyright 2015 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 "components/gcm_driver/crypto/gcm_encryption_provider.h"
- #include <memory>
- #include "base/base64.h"
- #include "base/big_endian.h"
- #include "base/bind.h"
- #include "base/logging.h"
- #include "base/strings/strcat.h"
- #include "components/gcm_driver/common/gcm_message.h"
- #include "components/gcm_driver/crypto/encryption_header_parsers.h"
- #include "components/gcm_driver/crypto/gcm_decryption_result.h"
- #include "components/gcm_driver/crypto/gcm_encryption_result.h"
- #include "components/gcm_driver/crypto/gcm_key_store.h"
- #include "components/gcm_driver/crypto/gcm_message_cryptographer.h"
- #include "components/gcm_driver/crypto/message_payload_parser.h"
- #include "components/gcm_driver/crypto/p256_key_util.h"
- #include "components/gcm_driver/crypto/proto/gcm_encryption_data.pb.h"
- #include "crypto/ec_private_key.h"
- #include "crypto/random.h"
- namespace gcm {
- namespace {
- const char kEncryptionProperty[] = "encryption";
- const char kCryptoKeyProperty[] = "crypto-key";
- const char kInternalRawData[] = "_googRawData";
- // Directory in the GCM Store in which the encryption database will be stored.
- const base::FilePath::CharType kEncryptionDirectoryName[] =
- FILE_PATH_LITERAL("Encryption");
- IncomingMessage CreateMessageWithId(const std::string& message_id) {
- IncomingMessage message;
- message.message_id = message_id;
- return message;
- }
- } // namespace
- GCMEncryptionProvider::GCMEncryptionProvider() {}
- GCMEncryptionProvider::~GCMEncryptionProvider() = default;
- // static
- const char GCMEncryptionProvider::kContentEncodingProperty[] =
- "content-encoding";
- // static
- const char GCMEncryptionProvider::kContentCodingAes128Gcm[] = "aes128gcm";
- void GCMEncryptionProvider::Init(
- const base::FilePath& store_path,
- const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner) {
- DCHECK(!key_store_);
- base::FilePath encryption_store_path = store_path;
- // |store_path| can be empty in tests, which means that the database should
- // be created in memory rather than on-disk.
- if (!store_path.empty())
- encryption_store_path = store_path.Append(kEncryptionDirectoryName);
- key_store_ = std::make_unique<GCMKeyStore>(encryption_store_path,
- blocking_task_runner);
- }
- void GCMEncryptionProvider::GetEncryptionInfo(
- const std::string& app_id,
- const std::string& authorized_entity,
- EncryptionInfoCallback callback) {
- DCHECK(key_store_);
- key_store_->GetKeys(
- app_id, authorized_entity,
- false /* fallback_to_empty_authorized_entity */,
- base::BindOnce(&GCMEncryptionProvider::DidGetEncryptionInfo,
- weak_ptr_factory_.GetWeakPtr(), app_id, authorized_entity,
- std::move(callback)));
- }
- void GCMEncryptionProvider::DidGetEncryptionInfo(
- const std::string& app_id,
- const std::string& authorized_entity,
- EncryptionInfoCallback callback,
- std::unique_ptr<crypto::ECPrivateKey> key,
- const std::string& auth_secret) {
- if (!key) {
- key_store_->CreateKeys(
- app_id, authorized_entity,
- base::BindOnce(&GCMEncryptionProvider::DidCreateEncryptionInfo,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- return;
- }
- std::string public_key;
- const bool success = GetRawPublicKey(*key, &public_key);
- DCHECK(success);
- std::move(callback).Run(public_key, auth_secret);
- }
- void GCMEncryptionProvider::RemoveEncryptionInfo(
- const std::string& app_id,
- const std::string& authorized_entity,
- base::OnceClosure callback) {
- DCHECK(key_store_);
- key_store_->RemoveKeys(app_id, authorized_entity, std::move(callback));
- }
- bool GCMEncryptionProvider::IsEncryptedMessage(
- const IncomingMessage& message) const {
- // Messages that explicitly specify their content coding to be "aes128gcm"
- // indicate that they use draft-ietf-webpush-encryption-08.
- auto content_encoding_iter = message.data.find(kContentEncodingProperty);
- if (content_encoding_iter != message.data.end() &&
- content_encoding_iter->second == kContentCodingAes128Gcm) {
- return true;
- }
- // The Web Push protocol requires the encryption and crypto-key properties to
- // be set, and the raw_data field to be populated with the payload.
- if (message.data.find(kEncryptionProperty) == message.data.end() ||
- message.data.find(kCryptoKeyProperty) == message.data.end())
- return false;
- return message.raw_data.size() > 0;
- }
- void GCMEncryptionProvider::DecryptMessage(const std::string& app_id,
- const IncomingMessage& message,
- DecryptMessageCallback callback) {
- DCHECK(key_store_);
- if (!IsEncryptedMessage(message)) {
- std::move(callback).Run(GCMDecryptionResult::UNENCRYPTED, message);
- return;
- }
- std::string salt, public_key, ciphertext;
- GCMMessageCryptographer::Version version;
- uint32_t record_size;
- auto content_encoding_iter = message.data.find(kContentEncodingProperty);
- if (content_encoding_iter != message.data.end() &&
- content_encoding_iter->second == kContentCodingAes128Gcm) {
- // The message follows encryption per draft-ietf-webpush-encryption-08. Use
- // the binary header of the message to derive the values.
- auto parser = std::make_unique<MessagePayloadParser>(message.raw_data);
- if (!parser->IsValid()) {
- // Attempt to parse base64 encoded internal raw data.
- auto raw_data_iter = message.data.find(kInternalRawData);
- std::string raw_data;
- if (raw_data_iter == message.data.end() ||
- !base::Base64Decode(raw_data_iter->second, &raw_data) ||
- !(parser = std::make_unique<MessagePayloadParser>(raw_data))
- ->IsValid()) {
- DLOG(ERROR) << "Unable to parse the message's binary header";
- std::move(callback).Run(parser->GetFailureReason(),
- CreateMessageWithId(message.message_id));
- return;
- }
- }
- salt = parser->salt();
- public_key = parser->public_key();
- record_size = parser->record_size();
- ciphertext = parser->ciphertext();
- version = GCMMessageCryptographer::Version::DRAFT_08;
- } else {
- // The message follows encryption per draft-ietf-webpush-encryption-03. Use
- // the Encryption and Crypto-Key header values to derive the values.
- const auto& encryption_header = message.data.find(kEncryptionProperty);
- DCHECK(encryption_header != message.data.end());
- const auto& crypto_key_header = message.data.find(kCryptoKeyProperty);
- DCHECK(crypto_key_header != message.data.end());
- EncryptionHeaderIterator encryption_header_iterator(
- encryption_header->second.begin(), encryption_header->second.end());
- if (!encryption_header_iterator.GetNext()) {
- DLOG(ERROR) << "Unable to parse the value of the Encryption header";
- std::move(callback).Run(GCMDecryptionResult::INVALID_ENCRYPTION_HEADER,
- CreateMessageWithId(message.message_id));
- return;
- }
- if (encryption_header_iterator.salt().size() !=
- GCMMessageCryptographer::kSaltSize) {
- DLOG(ERROR) << "Invalid values supplied in the Encryption header";
- std::move(callback).Run(GCMDecryptionResult::INVALID_ENCRYPTION_HEADER,
- CreateMessageWithId(message.message_id));
- return;
- }
- salt = encryption_header_iterator.salt();
- record_size = encryption_header_iterator.rs();
- CryptoKeyHeaderIterator crypto_key_header_iterator(
- crypto_key_header->second.begin(), crypto_key_header->second.end());
- if (!crypto_key_header_iterator.GetNext()) {
- DLOG(ERROR) << "Unable to parse the value of the Crypto-Key header";
- std::move(callback).Run(GCMDecryptionResult::INVALID_CRYPTO_KEY_HEADER,
- CreateMessageWithId(message.message_id));
- return;
- }
- // Ignore values that don't include the "dh" property. When using VAPID, it
- // is valid for the application server to supply multiple values.
- while (crypto_key_header_iterator.dh().empty() &&
- crypto_key_header_iterator.GetNext()) {
- }
- bool valid_crypto_key_header = false;
- if (!crypto_key_header_iterator.dh().empty()) {
- public_key = crypto_key_header_iterator.dh();
- valid_crypto_key_header = true;
- // Guard against the "dh" property being included more than once.
- while (crypto_key_header_iterator.GetNext()) {
- if (crypto_key_header_iterator.dh().empty())
- continue;
- valid_crypto_key_header = false;
- break;
- }
- }
- if (!valid_crypto_key_header) {
- DLOG(ERROR) << "Invalid values supplied in the Crypto-Key header";
- std::move(callback).Run(GCMDecryptionResult::INVALID_CRYPTO_KEY_HEADER,
- CreateMessageWithId(message.message_id));
- return;
- }
- ciphertext = message.raw_data;
- version = GCMMessageCryptographer::Version::DRAFT_03;
- }
- // Use |fallback_to_empty_authorized_entity|, since this message might have
- // been sent to either an InstanceID token or a non-InstanceID registration.
- key_store_->GetKeys(
- app_id, message.sender_id /* authorized_entity */,
- true /* fallback_to_empty_authorized_entity */,
- base::BindOnce(&GCMEncryptionProvider::DecryptMessageWithKey,
- weak_ptr_factory_.GetWeakPtr(), message.message_id,
- message.collapse_key, message.sender_id, std::move(salt),
- std::move(public_key), record_size, std::move(ciphertext),
- version, std::move(callback)));
- } // namespace gcm
- void GCMEncryptionProvider::EncryptMessage(const std::string& app_id,
- const std::string& authorized_entity,
- const std::string& p256dh,
- const std::string& auth_secret,
- const std::string& message,
- EncryptMessageCallback callback) {
- DCHECK(key_store_);
- key_store_->GetKeys(
- app_id, authorized_entity,
- false /* fallback_to_empty_authorized_entity */,
- base::BindOnce(&GCMEncryptionProvider::EncryptMessageWithKey,
- weak_ptr_factory_.GetWeakPtr(), app_id, authorized_entity,
- p256dh, auth_secret, message, std::move(callback)));
- }
- void GCMEncryptionProvider::DidCreateEncryptionInfo(
- EncryptionInfoCallback callback,
- std::unique_ptr<crypto::ECPrivateKey> key,
- const std::string& auth_secret) {
- if (!key) {
- std::move(callback).Run(std::string() /* p256dh */,
- std::string() /* auth_secret */);
- return;
- }
- std::string public_key;
- const bool success = GetRawPublicKey(*key, &public_key);
- DCHECK(success);
- std::move(callback).Run(public_key, auth_secret);
- }
- void GCMEncryptionProvider::DecryptMessageWithKey(
- const std::string& message_id,
- const std::string& collapse_key,
- const std::string& sender_id,
- const std::string& salt,
- const std::string& public_key,
- uint32_t record_size,
- const std::string& ciphertext,
- GCMMessageCryptographer::Version version,
- DecryptMessageCallback callback,
- std::unique_ptr<crypto::ECPrivateKey> key,
- const std::string& auth_secret) {
- if (!key) {
- DLOG(ERROR) << "Unable to retrieve the keys for the incoming message.";
- std::move(callback).Run(GCMDecryptionResult::NO_KEYS,
- CreateMessageWithId(message_id));
- return;
- }
- std::string shared_secret;
- if (!ComputeSharedP256Secret(*key, public_key, &shared_secret)) {
- DLOG(ERROR) << "Unable to calculate the shared secret.";
- std::move(callback).Run(GCMDecryptionResult::INVALID_SHARED_SECRET,
- CreateMessageWithId(message_id));
- return;
- }
- std::string plaintext;
- GCMMessageCryptographer cryptographer(version);
- std::string exported_public_key;
- const bool success = GetRawPublicKey(*key, &exported_public_key);
- DCHECK(success);
- if (!cryptographer.Decrypt(exported_public_key, public_key, shared_secret,
- auth_secret, salt, ciphertext, record_size,
- &plaintext)) {
- DLOG(ERROR) << "Unable to decrypt the incoming data.";
- std::move(callback).Run(GCMDecryptionResult::INVALID_PAYLOAD,
- CreateMessageWithId(message_id));
- return;
- }
- IncomingMessage decrypted_message;
- decrypted_message.message_id = message_id;
- decrypted_message.collapse_key = collapse_key;
- decrypted_message.sender_id = sender_id;
- decrypted_message.raw_data.swap(plaintext);
- decrypted_message.decrypted = true;
- // There must be no data associated with the decrypted message at this point,
- // to make sure that we don't end up in an infinite decryption loop.
- DCHECK_EQ(0u, decrypted_message.data.size());
- std::move(callback).Run(version == GCMMessageCryptographer::Version::DRAFT_03
- ? GCMDecryptionResult::DECRYPTED_DRAFT_03
- : GCMDecryptionResult::DECRYPTED_DRAFT_08,
- std::move(decrypted_message));
- }
- void GCMEncryptionProvider::EncryptMessageWithKey(
- const std::string& app_id,
- const std::string& authorized_entity,
- const std::string& p256dh,
- const std::string& auth_secret,
- const std::string& message,
- EncryptMessageCallback callback,
- std::unique_ptr<crypto::ECPrivateKey> key,
- const std::string& sender_auth_secret) {
- if (!key) {
- DLOG(ERROR) << "Unable to retrieve the keys for the outgoing message.";
- std::move(callback).Run(GCMEncryptionResult::NO_KEYS, std::string());
- return;
- }
- // Creates a cryptographically secure salt of |salt_size| octets in size,
- // and calculate the shared secret for the message.
- std::string salt;
- crypto::RandBytes(base::WriteInto(&salt, 16 + 1), 16);
- std::string shared_secret;
- if (!ComputeSharedP256Secret(*key, p256dh, &shared_secret)) {
- DLOG(ERROR) << "Unable to calculate the shared secret.";
- std::move(callback).Run(GCMEncryptionResult::INVALID_SHARED_SECRET,
- std::string());
- return;
- }
- size_t record_size;
- std::string ciphertext;
- GCMMessageCryptographer cryptographer(
- GCMMessageCryptographer::Version::DRAFT_08);
- std::string sender_public_key;
- bool success = GetRawPublicKey(*key, &sender_public_key);
- DCHECK(success);
- if (!cryptographer.Encrypt(p256dh, sender_public_key, shared_secret,
- auth_secret, salt, message, &record_size,
- &ciphertext)) {
- DLOG(ERROR) << "Unable to encrypt the incoming data.";
- std::move(callback).Run(GCMEncryptionResult::ENCRYPTION_FAILED,
- std::string());
- return;
- }
- // Construct encryption header.
- uint32_t rs = record_size;
- char rs_buf[sizeof(rs)];
- base::WriteBigEndian(rs_buf, rs);
- std::string rs_str(std::begin(rs_buf), std::end(rs_buf));
- uint8_t key_length = sender_public_key.size();
- char key_length_buf[sizeof(key_length)];
- base::WriteBigEndian(key_length_buf, key_length);
- std::string key_length_str(std::begin(key_length_buf),
- std::end(key_length_buf));
- std::string payload = base::StrCat(
- {salt, rs_str, key_length_str, sender_public_key, ciphertext});
- std::move(callback).Run(GCMEncryptionResult::ENCRYPTED_DRAFT_08,
- std::move(payload));
- }
- } // namespace gcm
|