Index: components/gcm_driver/crypto/gcm_encryption_provider.cc |
diff --git a/components/gcm_driver/crypto/gcm_encryption_provider.cc b/components/gcm_driver/crypto/gcm_encryption_provider.cc |
index 2b775e4c8ee56b0d6e2d1029e63736f7282bff5f..9c53a6e915c0a24be5dddca845fcf45699aefd9d 100644 |
--- a/components/gcm_driver/crypto/gcm_encryption_provider.cc |
+++ b/components/gcm_driver/crypto/gcm_encryption_provider.cc |
@@ -4,13 +4,23 @@ |
#include "components/gcm_driver/crypto/gcm_encryption_provider.h" |
+#include "base/base64.h" |
#include "base/bind.h" |
#include "base/logging.h" |
+#include "base/strings/string_util.h" |
+#include "components/gcm_driver/common/gcm_messages.h" |
+#include "components/gcm_driver/crypto/encryption_header_parsers.h" |
#include "components/gcm_driver/crypto/gcm_key_store.h" |
+#include "components/gcm_driver/crypto/gcm_message_cryptographer.h" |
#include "components/gcm_driver/crypto/proto/gcm_encryption_data.pb.h" |
+#include "crypto/curve25519.h" |
namespace gcm { |
+const char kEncryptionProperty[] = "encryption"; |
jianli
2015/08/04 00:49:30
All these constants, including the previously adde
Peter Beverloo
2015/09/25 16:37:35
Done for consistency, but please do note that cons
|
+const char kEncryptionKeyProperty[] = "encryption_key"; |
+const char kEncryptionDataProperty[] = "encryption_data"; |
+ |
// Directory in the GCM Store in which the encryption database will be stored. |
const base::FilePath::CharType kEncryptionDirectoryName[] = |
FILE_PATH_LITERAL("Encryption"); |
@@ -71,5 +81,127 @@ void GCMEncryptionProvider::DidCreatePublicKey( |
DCHECK_EQ(KeyPair::ECDH_CURVE_25519, pair.type()); |
callback.Run(pair.public_key()); |
} |
+bool GCMEncryptionProvider::IsEncryptedMessage(const IncomingMessage& message) |
+ const { |
+ // Both the proprietary GCM protocol and the Web Push protocol require the |
+ // encryption and encryption_key properties to be set. |
+ if (message.data.find(kEncryptionProperty) == message.data.end() || |
+ message.data.find(kEncryptionKeyProperty) == message.data.end()) |
+ return false; |
+ |
+ // The proprietary GCM protocol will store the encrypted payload in an app |
+ // data field called "encryption_data". |
+ if (message.data.find(kEncryptionDataProperty) != message.data.end()) |
+ return true; |
+ |
+ // The Web Push protocol uses the raw message data for the encrypted payload. |
+ return message.raw_data.size() > 0; |
+} |
+ |
+void GCMEncryptionProvider::DecryptMessage(const std::string& app_id, |
+ const IncomingMessage& message, |
+ const MessageCallback& callback) { |
+ DCHECK(key_store_); |
+ |
+ const auto& encryption_header = message.data.find(kEncryptionProperty); |
+ const auto& encryption_key_header = message.data.find(kEncryptionKeyProperty); |
+ |
+ // Callers are expected to call IsEncryptedMessage() prior to this method. |
+ DCHECK(encryption_header != message.data.end()); |
+ DCHECK(encryption_key_header != message.data.end()); |
+ |
+ std::vector<EncryptionHeaderValue> encryption_headers; |
+ if (!ParseEncryptionHeader(encryption_header->second, &encryption_headers)) { |
+ DLOG(ERROR) << "Unable to parse the value of the Encryption header"; |
+ callback.Run(IncomingMessage(), false /* success */); |
+ return; |
+ } |
+ |
+ std::vector<EncryptionKeyHeaderValue> encryption_key_headers; |
+ if (!ParseEncryptionKeyHeader(encryption_key_header->second, |
+ &encryption_key_headers)) { |
+ DLOG(ERROR) << "Unable to parse the value of the Encryption-Key header"; |
+ callback.Run(IncomingMessage(), false /* success */); |
+ return; |
+ } |
+ |
+ // Note: The HTTP Encrypted Content specification defines that multiple rounds |
+ // of encryption are allowed. Chrome's Web Push protocol implementation does |
+ // not currently support this, due to not using the "keyid" parameter. |
+ if (encryption_headers.size() != 1 || encryption_key_headers.size() != 1) { |
+ DLOG(ERROR) << "Only a single encryption key is currently supported"; |
+ callback.Run(IncomingMessage(), false /* success */); |
+ return; |
+ } |
+ |
+ std::string ciphertext; |
+ |
+ // Determine the payload. For the proprietary GCM protocol the payload is part |
+ // of the app data, base64 URL encoded. For the Web Push protocol, it's |
+ // included as raw data for the incoming message. |
+ const auto& encryption_data_iter = message.data.find(kEncryptionDataProperty); |
+ if (encryption_data_iter != message.data.end()) { |
+ if (!Base64DecodeUrlSafe(encryption_data_iter->second, &ciphertext)) { |
+ DLOG(ERROR) << "Unable to URL-safe base64 decode the encrypted data"; |
+ callback.Run(IncomingMessage(), false /* success */); |
+ return; |
+ } |
+ } else { |
+ DCHECK(message.raw_data.size()); |
+ ciphertext = message.raw_data; |
+ } |
+ |
+ key_store_->GetKeys( |
+ app_id, base::Bind(&GCMEncryptionProvider::DecryptMessageWithKey, |
+ weak_ptr_factory_.GetWeakPtr(), message, callback, |
+ encryption_headers[0], encryption_key_headers[0], |
+ ciphertext)); |
+} |
+ |
+void GCMEncryptionProvider::DecryptMessageWithKey( |
+ const IncomingMessage& message, |
+ const MessageCallback& callback, |
+ const EncryptionHeaderValue& encryption_header, |
+ const EncryptionKeyHeaderValue& encryption_key_header, |
+ const std::string& ciphertext, |
+ const KeyPair& pair) { |
+ if (!pair.IsInitialized()) { |
+ DLOG(ERROR) << "Unable to retrieve the keys for the incoming message."; |
+ callback.Run(IncomingMessage(), false /* success */); |
+ return; |
+ } |
+ |
+ DCHECK_EQ(KeyPair::ECDH_CURVE_25519, pair.type()); |
+ |
+ // Calculate the shared secret between the client's private key and the |
+ // server's public key. |
+ uint8_t shared_key[crypto::curve25519::kBytes]; |
+ crypto::curve25519::ScalarMult( |
+ reinterpret_cast<const unsigned char*>(pair.private_key().data()), |
+ reinterpret_cast<const unsigned char*>(encryption_key_header.dh.data()), |
+ shared_key); |
+ |
+ const std::string shared_key_string(shared_key, |
+ shared_key + sizeof(shared_key)); |
+ |
+ std::string plaintext; |
+ |
+ // TODO(peter): Support explicit keys for the decryption (don't use HKDF). |
+ |
+ GCMMessageCryptographer cryptographer; |
+ if (!cryptographer.Decrypt(ciphertext, shared_key_string, |
+ encryption_header.salt, encryption_header.rs, |
+ &plaintext)) { |
+ DLOG(ERROR) << "Unable to decrypt the incoming data."; |
+ callback.Run(IncomingMessage(), false /* success */); |
+ return; |
+ } |
+ |
+ IncomingMessage decrypted_message; |
+ decrypted_message.collapse_key = message.collapse_key; |
+ decrypted_message.raw_data = plaintext; |
+ |
+ callback.Run(decrypted_message, true /* success */); |
+} |
} // namespace gcm |