Commit 006f9034 authored by peter's avatar peter Committed by Commit bot

Add a parser for messages with a Web Push Protocol-based payload

This will help to avoid doing manual parsing and verification of the
inbound messages in multiple places.

BUG=679789

Review-Url: https://codereview.chromium.org/2888763006
Cr-Commit-Position: refs/heads/master@{#473889}
parent 795c4f10
...@@ -14,6 +14,8 @@ static_library("crypto") { ...@@ -14,6 +14,8 @@ static_library("crypto") {
"gcm_key_store.h", "gcm_key_store.h",
"gcm_message_cryptographer.cc", "gcm_message_cryptographer.cc",
"gcm_message_cryptographer.h", "gcm_message_cryptographer.h",
"message_payload_parser.cc",
"message_payload_parser.h",
"p256_key_util.cc", "p256_key_util.cc",
"p256_key_util.h", "p256_key_util.h",
] ]
...@@ -52,6 +54,7 @@ source_set("unit_tests") { ...@@ -52,6 +54,7 @@ source_set("unit_tests") {
"gcm_encryption_provider_unittest.cc", "gcm_encryption_provider_unittest.cc",
"gcm_key_store_unittest.cc", "gcm_key_store_unittest.cc",
"gcm_message_cryptographer_unittest.cc", "gcm_message_cryptographer_unittest.cc",
"message_payload_parser_unittest.cc",
"p256_key_util_unittest.cc", "p256_key_util_unittest.cc",
] ]
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "components/gcm_driver/crypto/message_payload_parser.h"
#include "components/gcm_driver/crypto/p256_key_util.h" #include "components/gcm_driver/crypto/p256_key_util.h"
#include "crypto/random.h" #include "crypto/random.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -23,7 +24,6 @@ namespace { ...@@ -23,7 +24,6 @@ namespace {
const char kExamplePlaintext[] = "Example plaintext"; const char kExamplePlaintext[] = "Example plaintext";
// Expected sizes of the different input given to the cryptographer. // Expected sizes of the different input given to the cryptographer.
constexpr size_t kUncompressedPointSize = 65;
constexpr size_t kEcdhSharedSecretSize = 32; constexpr size_t kEcdhSharedSecretSize = 32;
constexpr size_t kAuthSecretSize = 16; constexpr size_t kAuthSecretSize = 16;
constexpr size_t kSaltSize = 16; constexpr size_t kSaltSize = 16;
...@@ -866,45 +866,13 @@ TEST_F(GCMMessageCryptographerReferenceTest, ReferenceDraft08) { ...@@ -866,45 +866,13 @@ TEST_F(GCMMessageCryptographerReferenceTest, ReferenceDraft08) {
base::Base64UrlDecodePolicy::IGNORE_PADDING, base::Base64UrlDecodePolicy::IGNORE_PADDING,
&message)); &message));
// TODO(peter): Break out the following in a separate message parser class so MessagePayloadParser message_parser(message);
// that it can be reused by the GCMEncryptionProvider (on the receiving path) ASSERT_TRUE(message_parser.IsValid());
// and the gcm_crypto_test_helpers.cc file (on the sending path) too.
// base::StringPiece salt = message_parser.salt();
// The message contains a binary header in the following format: uint32_t record_size = message_parser.record_size();
// [ salt(16) | record_size(4) | sender_public_key_len(1) | base::StringPiece sender_public_key = message_parser.public_key();
// sender_public_key(sender_public_key_len) ] base::StringPiece ciphertext = message_parser.ciphertext();
//
// For Web Push Encryption, which uses a P-256 sender key as uncompressed
// P-256 EC points, the length of the sender key is 65 bytes, making the
// total, fixed length of the header 86 bytes.
//
// The regular AEAD_AES_128_GCM ciphertext follows immediately after this. The
// minimum overhead for a single record is 18 bytes. This means that an
// incoming message must be at least 104 bytes in size.
ASSERT_GE(message.size(), 104u);
const char* current = &message.front();
uint32_t record_size;
uint8_t sender_public_key_length;
base::StringPiece salt(current, kSaltSize);
current += kSaltSize;
base::ReadBigEndian(current, &record_size);
current += sizeof(record_size);
base::ReadBigEndian(current, &sender_public_key_length);
current += sizeof(sender_public_key_length);
ASSERT_EQ(sender_public_key_length, kUncompressedPointSize);
base::StringPiece sender_public_key(current, sender_public_key_length);
current += sender_public_key_length;
base::StringPiece ciphertext(
current, message.size() - kSaltSize - sizeof(record_size) -
sizeof(sender_public_key_length) - sender_public_key_length);
std::string sender_shared_secret, receiver_shared_secret; std::string sender_shared_secret, receiver_shared_secret;
......
// Copyright 2017 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/message_payload_parser.h"
#include "base/big_endian.h"
namespace gcm {
namespace {
// Size, in bytes, of the salt included in the message header.
constexpr size_t kSaltSize = 16;
// Size, in bytes, of the uncompressed point included in the message header.
constexpr size_t kUncompressedPointSize = 65;
// Size, in bytes, of the smallest allowable record_size value.
constexpr size_t kMinimumRecordSize = 18;
// Size, in bytes, of an empty message with the minimum amount of padding.
constexpr size_t kMinimumMessageSize =
kSaltSize + sizeof(uint32_t) + sizeof(uint8_t) + kUncompressedPointSize +
kMinimumRecordSize;
} // namespace
MessagePayloadParser::MessagePayloadParser(base::StringPiece message) {
if (message.size() < kMinimumMessageSize)
return;
salt_ = message.substr(0, kSaltSize).as_string();
message.remove_prefix(kSaltSize);
base::ReadBigEndian(message.data(), &record_size_);
message.remove_prefix(sizeof(record_size_));
if (record_size_ < kMinimumRecordSize)
return;
uint8_t public_key_length;
base::ReadBigEndian(message.data(), &public_key_length);
message.remove_prefix(sizeof(public_key_length));
if (public_key_length != kUncompressedPointSize)
return;
if (message[0] != 0x04)
return;
public_key_ = message.substr(0, kUncompressedPointSize).as_string();
message.remove_prefix(kUncompressedPointSize);
ciphertext_ = message.as_string();
if (ciphertext_.size() < kMinimumRecordSize)
return;
is_valid_ = true;
}
MessagePayloadParser::~MessagePayloadParser() = default;
} // namespace gcm
// Copyright 2017 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 COMPONENTS_GCM_DRIVER_CRYPTO_MESSAGE_PAYLOAD_PARSER_H_
#define COMPONENTS_GCM_DRIVER_CRYPTO_MESSAGE_PAYLOAD_PARSER_H_
#include <stdint.h>
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string_piece.h"
namespace gcm {
// Parses and validates the binary message payload included in messages that
// are encrypted per draft-ietf-webpush-encryption-08:
//
// https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-08#section-2.1
//
// In summary, such messages start with a binary header block that includes the
// parameters needed to decrypt the content, other than the key. All content
// following this binary header is considered the ciphertext.
//
// +-----------+--------+-----------+-----------------+
// | salt (16) | rs (4) | idlen (1) | public_key (65) |
// +-----------+--------+-----------+-----------------+
//
// Specific to Web Push encryption, the `public_key` parameter of this header
// must be set to the ECDH public key of the sender. This is a point on the
// P-256 elliptic curve in uncompressed form, 65 bytes long starting with 0x04.
//
// https://tools.ietf.org/html/draft-ietf-webpush-encryption-08#section-3.1
class MessagePayloadParser {
public:
explicit MessagePayloadParser(base::StringPiece message);
~MessagePayloadParser();
// Returns whether the parser represents a valid message.
bool IsValid() const { return is_valid_; }
// Returns the 16-byte long salt for the message. Must only be called after
// validity of the message has been verified.
const std::string& salt() const {
CHECK(is_valid_);
return salt_;
}
// Returns the record size for the message. Must only be called after validity
// of the message has been verified.
uint32_t record_size() const {
CHECK(is_valid_);
return record_size_;
}
// Returns the sender's ECDH public key for the message. This will be a point
// on the P-256 elliptic curve in uncompressed form. Must only be called after
// validity of the message has been verified.
const std::string& public_key() const {
CHECK(is_valid_);
return public_key_;
}
// Returns the ciphertext for the message. This will be at least the size of
// a single record, which is 18 octets. Must only be called after validity of
// the message has been verified.
const std::string& ciphertext() const {
CHECK(is_valid_);
return ciphertext_;
}
private:
bool is_valid_ = false;
std::string salt_;
uint32_t record_size_ = 0;
std::string public_key_;
std::string ciphertext_;
DISALLOW_COPY_AND_ASSIGN(MessagePayloadParser);
};
} // namespace gcm
#endif // COMPONENTS_GCM_DRIVER_CRYPTO_MESSAGE_PAYLOAD_PARSER_H_
// Copyright 2017 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/message_payload_parser.h"
#include "base/big_endian.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gcm {
namespace {
constexpr size_t kSaltSize = 16;
constexpr size_t kPublicKeySize = 65;
constexpr size_t kCiphertextSize = 18;
const uint8_t kValidMessage[] = {
// salt (16 bytes, kSaltSize)
0x59, 0xFD, 0x35, 0x97, 0x3B, 0xF3, 0x66, 0xA7, 0xEB, 0x8D, 0x44, 0x1E,
0xCB, 0x4D, 0xFC, 0xD8,
// rs (4 bytes, in network byte order)
0x00, 0x00, 0x00, 0x12,
// idlen (1 byte)
0x41,
// public key (65 bytes, kPublicKeySize)
0x04, 0x35, 0x02, 0x67, 0xB9, 0x10, 0x8F, 0x9B, 0xF1, 0x85, 0xF5, 0x1B,
0xD7, 0xA4, 0xEF, 0xBD, 0x28, 0xB3, 0x11, 0x40, 0xBA, 0xD0, 0xEE, 0xB2,
0x97, 0xDA, 0x6A, 0x93, 0x2D, 0x26, 0x45, 0xBD, 0xB2, 0x9A, 0x9F, 0xB8,
0x19, 0xD8, 0x21, 0x6F, 0x66, 0xE3, 0xF6, 0x0B, 0x74, 0xB2, 0x28, 0x38,
0xDC, 0xA7, 0x8A, 0x58, 0x0D, 0x56, 0x47, 0x3E, 0xD0, 0x5B, 0x5C, 0x93,
0x4E, 0xB3, 0x89, 0x87, 0x64,
// payload (18 bytes, kCiphertextSize)
0x3F, 0xD8, 0x95, 0x2C, 0xA2, 0x11, 0xBD, 0x7B, 0x57, 0xB2, 0x00, 0xBD,
0x57, 0x68, 0x3F, 0xF0, 0x14, 0x57};
static_assert(arraysize(kValidMessage) == 104,
"The smallest valid message is 104 bytes in size.");
// Creates an std::string for the |kValidMessage| constant.
std::string CreateMessageString() {
return std::string(reinterpret_cast<const char*>(kValidMessage),
arraysize(kValidMessage));
}
TEST(MessagePayloadParserTest, ValidMessage) {
MessagePayloadParser parser(CreateMessageString());
ASSERT_TRUE(parser.IsValid());
const uint8_t* salt = kValidMessage;
ASSERT_EQ(parser.salt().size(), kSaltSize);
EXPECT_EQ(parser.salt(), std::string(salt, salt + kSaltSize));
ASSERT_EQ(parser.record_size(), 18u);
const uint8_t* public_key =
kValidMessage + kSaltSize + sizeof(uint32_t) + sizeof(uint8_t);
ASSERT_EQ(parser.public_key().size(), kPublicKeySize);
EXPECT_EQ(parser.public_key(),
std::string(public_key, public_key + kPublicKeySize));
const uint8_t* ciphertext = kValidMessage + kSaltSize + sizeof(uint32_t) +
sizeof(uint8_t) + kPublicKeySize;
ASSERT_EQ(parser.ciphertext().size(), kCiphertextSize);
EXPECT_EQ(parser.ciphertext(),
std::string(ciphertext, ciphertext + kCiphertextSize));
}
TEST(MessagePayloadParserTest, MinimumMessageSize) {
std::string message = CreateMessageString();
message.resize(arraysize(kValidMessage) / 2);
MessagePayloadParser parser(message);
EXPECT_FALSE(parser.IsValid());
}
TEST(MessagePayloadParserTest, MinimumRecordSize) {
std::string message = CreateMessageString();
uint32_t invalid_record_size = 11;
base::WriteBigEndian(&message[0] + 16 /* salt */, invalid_record_size);
MessagePayloadParser parser(message);
EXPECT_FALSE(parser.IsValid());
}
TEST(MessagePayloadParserTest, InvalidPublicKey) {
std::string message = CreateMessageString();
uint8_t invalid_public_key_size = 42;
base::WriteBigEndian(&message[0] + 16 /* salt */ + 4 /* rs */,
invalid_public_key_size);
MessagePayloadParser parser(message);
EXPECT_FALSE(parser.IsValid());
}
} // namespace
} // namespace gcm
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