diff options
5 files changed, 278 insertions, 66 deletions
diff --git a/components/gcm_driver/crypto/gcm_encryption_provider.cc b/components/gcm_driver/crypto/gcm_encryption_provider.cc index 5ed56e4..3378c61 100644 --- a/components/gcm_driver/crypto/gcm_encryption_provider.cc +++ b/components/gcm_driver/crypto/gcm_encryption_provider.cc @@ -177,7 +177,8 @@ void GCMEncryptionProvider::DecryptMessageWithKey( std::string plaintext; - GCMMessageCryptographer cryptographer; + GCMMessageCryptographer cryptographer( + GCMMessageCryptographer::Label::P256, pair.public_key(), dh); if (!cryptographer.Decrypt(message.raw_data, shared_secret, salt, rs, &plaintext)) { DLOG(ERROR) << "Unable to decrypt the incoming data."; diff --git a/components/gcm_driver/crypto/gcm_encryption_provider_unittest.cc b/components/gcm_driver/crypto/gcm_encryption_provider_unittest.cc index c8ad5dd..edb0bae 100644 --- a/components/gcm_driver/crypto/gcm_encryption_provider_unittest.cc +++ b/components/gcm_driver/crypto/gcm_encryption_provider_unittest.cc @@ -276,7 +276,10 @@ TEST_F(GCMEncryptionProviderTest, EncryptionRoundTrip) { // Encrypts the |kExampleMessage| using the generated shared key and the // random |salt|, storing the result in |record_size| and the message. - GCMMessageCryptographer cryptographer; + GCMMessageCryptographer cryptographer( + GCMMessageCryptographer::Label::P256, pair.public_key(), + server_pair.public_key()); + ASSERT_TRUE(cryptographer.Encrypt(kExampleMessage, shared_secret, salt, &record_size, &message.raw_data)); diff --git a/components/gcm_driver/crypto/gcm_message_cryptographer.cc b/components/gcm_driver/crypto/gcm_message_cryptographer.cc index a3bcfad..1daf579 100644 --- a/components/gcm_driver/crypto/gcm_message_cryptographer.cc +++ b/components/gcm_driver/crypto/gcm_message_cryptographer.cc @@ -5,9 +5,10 @@ #include "components/gcm_driver/crypto/gcm_message_cryptographer.h" #include <algorithm> +#include <sstream> -#include "base/big_endian.h" #include "base/logging.h" +#include "base/sys_byteorder.h" #include "crypto/hkdf.h" namespace gcm { @@ -17,18 +18,68 @@ namespace { // of a uint64_t, which is used to indicate the record sequence number. const uint64_t kNonceSize = 12; -// The default record size as defined by draft-thomson-http-encryption-01. +// The default record size as defined by draft-thomson-http-encryption. const size_t kDefaultRecordSize = 4096; // Key size, in bytes, of a valid AEAD_AES_128_GCM key. const size_t kContentEncryptionKeySize = 16; +// Creates the info parameter for an HKDF value for the given |content_encoding| +// in accordance with draft-thomson-http-encryption. +// +// cek_info = "Content-Encoding: aesgcm128" || 0x00 || context +// nonce_info = "Content-Encoding: nonce" || 0x00 || context +// +// context = label || 0x00 || +// length(recipient_public) || recipient_public || +// length(sender_public) || sender_public +// +// The length of the public keys must be written as a two octet unsigned integer +// in network byte order (big endian). +std::string InfoForContentEncoding( + const char* content_encoding, + GCMMessageCryptographer::Label label, + const base::StringPiece& recipient_public_key, + const base::StringPiece& sender_public_key) { + DCHECK(GCMMessageCryptographer::Label::P256 == label); + DCHECK_EQ(recipient_public_key.size(), 65u); + DCHECK_EQ(sender_public_key.size(), 65u); + + std::stringstream info_stream; + info_stream << "Content-Encoding: " << content_encoding << '\x00'; + + switch (label) { + case GCMMessageCryptographer::Label::P256: + info_stream << "P-256" << '\x00'; + break; + } + + uint16_t local_len = base::HostToNet16(recipient_public_key.size()); + info_stream.write(reinterpret_cast<char*>(&local_len), sizeof(local_len)); + info_stream << recipient_public_key; + + uint16_t peer_len = base::HostToNet16(sender_public_key.size()); + info_stream.write(reinterpret_cast<char*>(&peer_len), sizeof(peer_len)); + info_stream << sender_public_key; + + return info_stream.str(); +} + } // namespace const size_t GCMMessageCryptographer::kAuthenticationTagBytes = 16; const size_t GCMMessageCryptographer::kSaltSize = 16; -GCMMessageCryptographer::GCMMessageCryptographer() {} +GCMMessageCryptographer::GCMMessageCryptographer( + Label label, + const base::StringPiece& recipient_public_key, + const base::StringPiece& sender_public_key) + : content_encryption_key_info_( + InfoForContentEncoding("aesgcm128", label, recipient_public_key, + sender_public_key)), + nonce_info_( + InfoForContentEncoding("nonce", label, recipient_public_key, + sender_public_key)) {} GCMMessageCryptographer::~GCMMessageCryptographer() {} @@ -46,10 +97,10 @@ bool GCMMessageCryptographer::Encrypt(const base::StringPiece& plaintext, std::string content_encryption_key = DeriveContentEncryptionKey(key, salt); std::string nonce = DeriveNonce(key, salt); - // draft-nottingham-http-encryption-encoding-00 allows between 0 and 255 - // octets of padding to be inserted before the enciphered content, with the - // length of the padding stored in the first octet of the payload. Since - // there is no necessity for payloads to contain padding, don't add any. + // draft-thomson-http-encryption allows between 0 and 255 octets of padding to + // be inserted before the enciphered content, with the length of the padding + // stored in the first octet of the payload. Since there is no necessity for + // payloads to contain padding, don't add any. std::string record; record.reserve(plaintext.size() + 1); record.append(1, '\0'); @@ -82,8 +133,8 @@ bool GCMMessageCryptographer::Decrypt( // The |ciphertext| must be at least kAuthenticationTagBytes + 1 bytes, which // would be used for an empty message. Per - // https://tools.ietf.org/html/draft-thomson-webpush-encryption-01#section-3, - // the |record_size| parameter must be large enough to use only one record. + // https://tools.ietf.org/html/draft-thomson-http-encryption-02#section-3, the + // |record_size| parameter must be large enough to use only one record. if (ciphertext.size() < kAuthenticationTagBytes + 1 || ciphertext.size() >= record_size + kAuthenticationTagBytes + 1) { return false; @@ -125,7 +176,7 @@ std::string GCMMessageCryptographer::DeriveContentEncryptionKey( const base::StringPiece& key, const base::StringPiece& salt) const { crypto::HKDF hkdf(key, salt, - "Content-Encoding: aesgcm128", + content_encryption_key_info_, kContentEncryptionKeySize, 0, /* iv_bytes_to_generate */ 0 /* subkey_secret_bytes_to_generate */); @@ -137,14 +188,14 @@ std::string GCMMessageCryptographer::DeriveNonce( const base::StringPiece& key, const base::StringPiece& salt) const { crypto::HKDF hkdf(key, salt, - "Content-Encoding: nonce", + nonce_info_, kNonceSize, 0, /* iv_bytes_to_generate */ 0 /* subkey_secret_bytes_to_generate */); - // draft-thomson-http-encryption-01 defines that the result should be XOR'ed - // with the record's sequence number, but because Web Push encryption is - // limited to a single record we do not have to do that. + // draft-thomson-http-encryption defines that the result should be XOR'ed with + // the record's sequence number, however, Web Push encryption is limited to a + // single record per draft-ietf-webpush-encryption. return hkdf.client_write_key().as_string(); } diff --git a/components/gcm_driver/crypto/gcm_message_cryptographer.h b/components/gcm_driver/crypto/gcm_message_cryptographer.h index e2b299e..0ee275d 100644 --- a/components/gcm_driver/crypto/gcm_message_cryptographer.h +++ b/components/gcm_driver/crypto/gcm_message_cryptographer.h @@ -15,16 +15,16 @@ namespace gcm { // Messages delivered through GCM may be encrypted according to the IETF Web -// Push protocol, as described in draft-thomson-webpush-encryption-01: +// Push protocol, as described in draft-ietf-webpush-encryption: // -// https://tools.ietf.org/html/draft-thomson-webpush-encryption-01 +// https://tools.ietf.org/html/draft-ietf-webpush-encryption // // This class implements the ability to encrypt or decrypt such messages using // AEAD_AES_128_GCM with a 16-octet authentication tag. The encrypted payload // will be stored in a single record as described in -// draft-thomson-http-encryption-01: +// draft-thomson-http-encryption: // -// https://tools.ietf.org/html/draft-thomson-http-encryption-01 +// https://tools.ietf.org/html/draft-thomson-http-encryption // // Note that while this class is not responsible for creating or storing the // actual keys, it uses a key derivation function for the actual message @@ -36,7 +36,17 @@ class GCMMessageCryptographer { // unique content encryption key for a given message. static const size_t kSaltSize; - GCMMessageCryptographer(); + // Label of the encryption group used to calculate the shared secret. + enum class Label { + P256 + }; + + // Creates a new cryptographer with |label|, identifying the group used for + // the key agreement, and the public keys of both the recipient and sender. + GCMMessageCryptographer(Label label, + const base::StringPiece& recipient_public_key, + const base::StringPiece& sender_public_key); + ~GCMMessageCryptographer(); // Encrypts |plaintext| using the |key| and the |salt|, both of which must be @@ -83,6 +93,12 @@ class GCMMessageCryptographer { // Derives the nonce from |key| and |salt|. std::string DeriveNonce(const base::StringPiece& key, const base::StringPiece& salt) const; + + // The info parameters to the HKDFs used for deriving the content encryption + // key and the nonce. These contain the label of the used curve, as well as + // the sender and recipient's public keys. + std::string content_encryption_key_info_; + std::string nonce_info_; }; } // namespace gcm diff --git a/components/gcm_driver/crypto/gcm_message_cryptographer_unittest.cc b/components/gcm_driver/crypto/gcm_message_cryptographer_unittest.cc index a1a0280..b54e5fc 100644 --- a/components/gcm_driver/crypto/gcm_message_cryptographer_unittest.cc +++ b/components/gcm_driver/crypto/gcm_message_cryptographer_unittest.cc @@ -4,9 +4,10 @@ #include "components/gcm_driver/crypto/gcm_message_cryptographer.h" -#include "base/base64.h" +#include "base/base64url.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string_util.h" +#include "components/gcm_driver/crypto/p256_key_util.h" #include "crypto/random.h" #include "crypto/symmetric_key.h" #include "testing/gtest/include/gtest/gtest.h" @@ -21,6 +22,15 @@ const size_t kKeySizeBits = 128; // Example plaintext data to use in the tests. const char kExamplePlaintext[] = "Example plaintext"; +// Fixed local and peer public keys must be used to get consistent results. +const char kLocalPublicKeyCommon[] = + "BIXzEKOFquzVlr_1tS1bhmobZU3IJq2bswDflMJsizixqd_HFSvCJaCAotNjBw6A-iKQk7FshA" + "jdAA-T9Rh1a7U"; + +const char kPeerPublicKeyCommon[] = + "BAuzSrdIyKZsHnuOhqklkIKi6fl65V9OdPy6nFwI2SywL5-6I5SkkDtfIL9y7NkoEE345jv2Eo" + "5n4NIbLJIBjTM"; + // A test vector contains the information necessary to either encrypt or decrypt // a message. These vectors were created using a JavaScript implementation of // the same RFCs that the GCMMessageCryptographer implements. @@ -35,22 +45,22 @@ struct TestVector { const TestVector kEncryptionTestVectors[] = { // Simple message. { "Hello, world!", - "AhA6n2oFYPWIh+cXwyv1m2C0JvmjHB4ZkXj8QylESXU=", - "tsJYqAGvFDk6lDEv7daecw==", + "AhA6n2oFYPWIh-cXwyv1m2C0JvmjHB4ZkXj8QylESXU", + "tsJYqAGvFDk6lDEv7daecw", 4096, - "KNXRqBR9VKhtajeMaeKR/rHYIiORcyeFpUwWFGyS" + "Vhe0Wn01NnZ_AFDgdvD4rthl2Pz9u7-WX-BAHsGX" }, // Empty message. { "", - "lMyvTong4VR053jfCpWmMDGW5dEDAqiTZUIU+inhTjU=", - "wH3uvZqcN6oey9whiGpn1A==", + "lMyvTong4VR053jfCpWmMDGW5dEDAqiTZUIU-inhTjU", + "wH3uvZqcN6oey9whiGpn1A", 4096, - "Mnfr+AU5o7D30gjFdVOTFtw=" + "l81ck9Au-SbeTqQdPx-ky7Y" }, // Message with an invalid salt size. { "Hello, world!", - "CcdxzkR6z1EY9vSrM7/IxYVxDxu46hV638EZQTPd7XI=", - "aRr1fI1YSGVi5XU=", + "CcdxzkR6z1EY9vSrM7_IxYVxDxu46hV638EZQTPd7XI", + "aRr1fI1YSGVi5XU", 4096, nullptr // expected to fail } @@ -58,52 +68,45 @@ const TestVector kEncryptionTestVectors[] = { const TestVector kDecryptionTestVectors[] = { // Simple message. - { "avAFNhdbQohzizu+ORbU4XHhHSaXzw9lTN7UzB/j", - "47ZytAw9qHlm+Q8g+7rH81rUPzaCgGcoFvlS1qxQtQk=", - "EuR7EVetcaWpndXd/dKeyA==", + { "bkXjWrS4fIVFJOnBgUG6ZY9Kgip6bDX7QYHueYVc", + "47ZytAw9qHlm-Q8g-7rH81rUPzaCgGcoFvlS1qxQtQk", + "EuR7EVetcaWpndXd_dKeyA", 4096, "Hello, world!" }, // Simple message with 16 bytes of padding. - { "0198n7ZJ/ZPMnl4ZU2l9Lma5ktKbuzXCiJEXyYtROmWTP8RSiZd8sUd48xpk6Q==", - "MYSsNybwrTzRIzQYUq/yFPc6ugcTrJdEZJDM4NswvUg=", - "8sEAMQYnufo2UkKl80cUGQ==", + { "IkCA6-h14nYSJHsCy0iC7BlkXuO4bc_dyNo_D_d5R1fVaG7jggdhdO29H-1d5w", + "MYSsNybwrTzRIzQYUq_yFPc6ugcTrJdEZJDM4NswvUg", + "8sEAMQYnufo2UkKl80cUGQ", 4096, "Hello, world!" }, // Empty message. - { "g+ACk32a4gK2dS2xllKXn4c=", - "S3+Ki/+XtzR66gUp/zR75CC5JXO62pyr5fWfneTYwFE=", - "4RM6s19jJHdmqiVEJDp9jg==", + { "ebRdG2oMvjURqU4NXeY_XvM", + "S3-Ki_-XtzR66gUp_zR75CC5JXO62pyr5fWfneTYwFE", + "4RM6s19jJHdmqiVEJDp9jg", 4096, "" }, // Message with an invalid salt size. - { "rt4OiodS087DAQo6e24wA55k0hRPAHgz7OX7m+nj", - "wW3Iy5ma803lLd+ysPdHUe2NB3HqXbY0XhCCdG5Y1Gw=", - "N7oMH/xohAhMhOY=", + { "rt4OiodS087DAQo6e24wA55k0hRPAHgz7OX7m-nj", + "wW3Iy5ma803lLd-ysPdHUe2NB3HqXbY0XhCCdG5Y1Gw", + "N7oMH_xohAhMhOY", 4096, nullptr // expected to fail }, // Message with an invalid record size. - { "AsuoRkFtqLE1c0mGCae4OvgZSCSHWCoeRL9mXKjY", - "omxWz7tse3lgDpxUP+e7u14Dp1irvV3BdzXTcZOtsHs=", - "vKJD3bexto1hY64KVzS7ug==", - 3, + { "HNT3GJAt4XHmIagUhh-osH1", + "kR5BMfqMKOD1yrLKE2giObXHI7merrMtnoO2oqneqXA", + "SQeJSPrqHvTdSfAMF8bBzQ", + 8, nullptr // expected to fail }, - // Truncated message. - { "AGr4BfZSXW9txWkAG8pjg7IuRWWm1Mo8bDli/PSv", - "kR5BMfqMKOD1yrLKE2giObXHI7merrMtnoO2oqneqXA=", - "SQeJSPrqHvTdSfAMF8bBzQ==", - 13, - nullptr // expected to fail - }, // Message with multiple (2) records. - { "H2ujfPbpRbVSy+adIG2NRe4VxkX4V0r/zl6e9xnMSF6LSutblGdWLrwQc82Xh7DXAQlihW0q3" - "IQzHP+LIxuAiA==", - "W3W4gx7sqcfmBnvNNdO9d4MBCC1bvJkvsNjZOGD+CCg=", - "xG0TPGi9aIcxjpXKmaYBBQ==", + { "RqQVHRXlfYjzW9xhzh3V_KijLKjZiKzGXosqN_IaMzi0zI0tXXhC1urtrk3iWRoqttNXpkD2r" + "UCgLy8A1FnTjw", + "W3W4gx7sqcfmBnvNNdO9d4MBCC1bvJkvsNjZOGD-CCg", + "xG0TPGi9aIcxjpXKmaYBBQ", 7, nullptr // expected to fail } @@ -119,6 +122,18 @@ class GCMMessageCryptographerTest : public ::testing::Test { kKeySizeBits)); ASSERT_TRUE(random_key->GetRawKey(&key_)); + + std::string local_public_key, peer_public_key; + ASSERT_TRUE(base::Base64UrlDecode( + kLocalPublicKeyCommon, base::Base64UrlDecodePolicy::IGNORE_PADDING, + &local_public_key)); + ASSERT_TRUE(base::Base64UrlDecode( + kPeerPublicKeyCommon, base::Base64UrlDecodePolicy::IGNORE_PADDING, + &peer_public_key)); + + cryptographer_.reset( + new GCMMessageCryptographer(GCMMessageCryptographer::Label::P256, + local_public_key, peer_public_key)); } protected: @@ -133,12 +148,12 @@ class GCMMessageCryptographerTest : public ::testing::Test { return salt; } - GCMMessageCryptographer* cryptographer() { return &cryptographer_; } + GCMMessageCryptographer* cryptographer() { return cryptographer_.get(); } base::StringPiece key() const { return key_; } private: - GCMMessageCryptographer cryptographer_; + scoped_ptr<GCMMessageCryptographer> cryptographer_; std::string key_; }; @@ -254,8 +269,12 @@ TEST_F(GCMMessageCryptographerTest, EncryptionTestVectors) { for (size_t i = 0; i < arraysize(kEncryptionTestVectors); ++i) { SCOPED_TRACE(i); - ASSERT_TRUE(base::Base64Decode(kEncryptionTestVectors[i].key, &key)); - ASSERT_TRUE(base::Base64Decode(kEncryptionTestVectors[i].salt, &salt)); + ASSERT_TRUE(base::Base64UrlDecode( + kEncryptionTestVectors[i].key, + base::Base64UrlDecodePolicy::IGNORE_PADDING, &key)); + ASSERT_TRUE(base::Base64UrlDecode( + kEncryptionTestVectors[i].salt, + base::Base64UrlDecodePolicy::IGNORE_PADDING, &salt)); const bool has_output = kEncryptionTestVectors[i].output; const bool result = cryptographer()->Encrypt( @@ -267,8 +286,9 @@ TEST_F(GCMMessageCryptographerTest, EncryptionTestVectors) { } EXPECT_TRUE(result); - ASSERT_TRUE(base::Base64Decode(kEncryptionTestVectors[i].output, - &output)); + ASSERT_TRUE(base::Base64UrlDecode( + kEncryptionTestVectors[i].output, + base::Base64UrlDecodePolicy::IGNORE_PADDING, &output)); EXPECT_EQ(kEncryptionTestVectors[i].record_size, record_size); EXPECT_EQ(output, ciphertext); @@ -280,9 +300,15 @@ TEST_F(GCMMessageCryptographerTest, DecryptionTestVectors) { for (size_t i = 0; i < arraysize(kDecryptionTestVectors); ++i) { SCOPED_TRACE(i); - ASSERT_TRUE(base::Base64Decode(kDecryptionTestVectors[i].input, &input)); - ASSERT_TRUE(base::Base64Decode(kDecryptionTestVectors[i].key, &key)); - ASSERT_TRUE(base::Base64Decode(kDecryptionTestVectors[i].salt, &salt)); + ASSERT_TRUE(base::Base64UrlDecode( + kDecryptionTestVectors[i].input, + base::Base64UrlDecodePolicy::IGNORE_PADDING, &input)); + ASSERT_TRUE(base::Base64UrlDecode( + kDecryptionTestVectors[i].key, + base::Base64UrlDecodePolicy::IGNORE_PADDING, &key)); + ASSERT_TRUE(base::Base64UrlDecode( + kDecryptionTestVectors[i].salt, + base::Base64UrlDecodePolicy::IGNORE_PADDING, &salt)); const bool has_output = kDecryptionTestVectors[i].output; const bool result = cryptographer()->Decrypt( @@ -298,4 +324,119 @@ TEST_F(GCMMessageCryptographerTest, DecryptionTestVectors) { } } +// Reference test against the HTTP encryption coding IETF draft. Both the +// encrypting and decrypting routines of the GCMMessageCryptographer are +// covered by this test. +// +// https://tools.ietf.org/html/draft-thomson-http-encryption#section-5.5 +TEST_F(GCMMessageCryptographerTest, ReferenceTest) { + // base64url-encoded representation of the 16 octet salt. + const char kSalt[] = "Qg61ZJRva_XBE9IEUelU3A"; + + // base64url-encoded representation of the ciphertext, and the plaintext as + // a normal character array. + const char kCiphertext[] = "G6j_sfKg0qebO62yXpTCayN2KV24QitNiTvLgcFiEj0"; + const char kPlaintext[] = "I am the walrus"; + + // Private keys of the sender and receiver represented as ASN.1-encoded PKCS + // #8 EncryptedPrivateKeyInfo blocks, as required by the ECPrivateKey. + const char kReceiverPrivate[] = + "MIGxMBwGCiqGSIb3DQEMAQMwDgQIqMt4d7uJdt4CAggABIGQeikRHE3CqUeF-uUtJno9BL0g" + "mNRyDihZe8P3nF_g-NYVzvdQowsXfYeza6OQOdDuMXxnGgNToVy2jsiWVN6rxCaSMTY622y8" + "ajW5voSdqC2PakQ8ZNTPNHarLDMC9NpgGKrUh8hfRLhvb7vtbKIWmx-22rQB5yTYdqzN2m7A" + "GHMWRnVk0mMzMsMjZqYFaa2D"; + + const char kSenderPrivate[] = + "MIGxMBwGCiqGSIb3DQEMAQMwDgQIFfJ62c9VwXgCAggABIGQkRxDRPQjwuWp1C3-z1pYTDqF" + "_NZ1kbPsjmkC3JSv02oAYHtBAtKa2e3oAPqsPfCvoCJBJs6G4WY4EuEO1YFL6RKpNl3DpIUc" + "v9ShR27p_je_nyLpNBAxn2drnjlF_K6s4gcJmcvCxuNjAwOlLMPvQqGjOR2K_oMs1Hdq0EKJ" + "NwWt3WUVEpuQF_WhYjCVIeGO"; + + // Public keys of the sender and receiver represented as uncompressed points, + // and X.509 SubjectPublicKeyInfo blocks as required by NSS. + const char kReceiverPublicUncompressed[] = + "BCEkBjzL8Z3C-oi2Q7oE5t2Np-p7osjGLg93qUP0wvqRT21EEWyf0cQDQcakQMqz4hQKYOQ3" + "il2nNZct4HgAUQU"; + const char kReceiverPublicX509[] = + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEISQGPMvxncL6iLZDugTm3Y2n6nuiyMYuD3ep" + "Q_TC-pFPbUQRbJ_RxANBxqRAyrPiFApg5DeKXac1ly3geABRBQ"; + + const char kSenderPublicUncompressed[] = + "BDgpRKok2GZZDmS4r63vbJSUtcQx4Fq1V58-6-3NbZzSTlZsQiCEDTQy3CZ0ZMsqeqsEb7qW" + "2blQHA4S48fynTk"; + const char kSenderPublicX509[] = + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOClEqiTYZlkOZLivre9slJS1xDHgWrVXnz7r" + "7c1tnNJOVmxCIIQNNDLcJnRkyyp6qwRvupbZuVAcDhLjx_KdOQ"; + + // Convert the salt and the ciphertext to binary representations. + std::string salt, reference_ciphertext; + + ASSERT_TRUE(base::Base64UrlDecode( + kSalt, base::Base64UrlDecodePolicy::IGNORE_PADDING, &salt)); + ASSERT_TRUE(base::Base64UrlDecode( + kCiphertext, base::Base64UrlDecodePolicy::IGNORE_PADDING, + &reference_ciphertext)); + + // Convert the public and private keys to binary representations. + std::string receiver_private, receiver_public, receiver_public_x509; + std::string sender_private, sender_public, sender_public_x509; + + ASSERT_TRUE(base::Base64UrlDecode( + kReceiverPrivate, base::Base64UrlDecodePolicy::IGNORE_PADDING, + &receiver_private)); + ASSERT_TRUE(base::Base64UrlDecode( + kReceiverPublicUncompressed, base::Base64UrlDecodePolicy::IGNORE_PADDING, + &receiver_public)); + ASSERT_TRUE(base::Base64UrlDecode( + kReceiverPublicX509, base::Base64UrlDecodePolicy::IGNORE_PADDING, + &receiver_public_x509)); + + ASSERT_TRUE(base::Base64UrlDecode( + kSenderPrivate, base::Base64UrlDecodePolicy::IGNORE_PADDING, + &sender_private)); + ASSERT_TRUE(base::Base64UrlDecode( + kSenderPublicUncompressed, base::Base64UrlDecodePolicy::IGNORE_PADDING, + &sender_public)); + ASSERT_TRUE(base::Base64UrlDecode( + kSenderPublicX509, base::Base64UrlDecodePolicy::IGNORE_PADDING, + &sender_public_x509)); + + // Compute the shared secret between the sender and the receiver's keys. + std::string sender_shared_secret, receiver_shared_secret; + + ASSERT_TRUE(ComputeSharedP256Secret(sender_private, sender_public_x509, + receiver_public, &sender_shared_secret)); + ASSERT_TRUE(ComputeSharedP256Secret(receiver_private, receiver_public_x509, + sender_public, &receiver_shared_secret)); + + ASSERT_GT(sender_shared_secret.size(), 0u); + ASSERT_EQ(sender_shared_secret, receiver_shared_secret); + + GCMMessageCryptographer cryptographer( + GCMMessageCryptographer::Label::P256, receiver_public, sender_public); + + size_t record_size = 0; + std::string ciphertext; + + ASSERT_TRUE(cryptographer.Encrypt(kPlaintext, sender_shared_secret, salt, + &record_size, &ciphertext)); + + EXPECT_GT(record_size, 1u); + EXPECT_EQ(16u + 1u + strlen(kPlaintext), ciphertext.size()); + + // Verify that the created ciphertext matches the reference ciphertext. + EXPECT_EQ(reference_ciphertext, ciphertext); + + // Decrypt the ciphertext with the default record size to verify that the + // resulting plaintext matches the input text. + std::string plaintext; + + ASSERT_TRUE(cryptographer.Decrypt( + reference_ciphertext, receiver_shared_secret, salt, + 4096 /* record size */, &plaintext)); + + // Verify that the decrypted plaintext matches the reference plaintext. + EXPECT_EQ(kPlaintext, plaintext); +} + } // namespace gcm |