Add SimpleTicketCrypter implementation

gfe-relnote: n/a (test tools only change)
PiperOrigin-RevId: 310001350
Change-Id: I280cce083e50c4f4d17418a3a7cbd4329a91decb
diff --git a/quic/tools/simple_ticket_crypter.cc b/quic/tools/simple_ticket_crypter.cc
new file mode 100644
index 0000000..3da114e
--- /dev/null
+++ b/quic/tools/simple_ticket_crypter.cc
@@ -0,0 +1,109 @@
+// 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 "net/third_party/quiche/src/quic/tools/simple_ticket_crypter.h"
+
+#include "third_party/boringssl/src/include/openssl/aead.h"
+#include "third_party/boringssl/src/include/openssl/rand.h"
+
+namespace quic {
+
+namespace {
+
+constexpr QuicTime::Delta kTicketKeyLifetime =
+    QuicTime::Delta::FromSeconds(60 * 60 * 24 * 7);
+
+// The format of an encrypted ticket is 1 byte for the key epoch, followed by
+// 16 bytes of IV, followed by the output from the AES-GCM Seal operation. The
+// seal operation has an overhead of 16 bytes for its auth tag.
+constexpr size_t kEpochSize = 1;
+constexpr size_t kIVSize = 16;
+constexpr size_t kAuthTagSize = 16;
+
+// Offsets into the ciphertext to make message parsing easier.
+constexpr size_t kIVOffset = kEpochSize;
+constexpr size_t kMessageOffset = kIVOffset + kIVSize;
+
+}  // namespace
+
+SimpleTicketCrypter::SimpleTicketCrypter(QuicClock* clock) : clock_(clock) {
+  RAND_bytes(&key_epoch_, 1);
+  current_key_ = NewKey();
+}
+
+SimpleTicketCrypter::~SimpleTicketCrypter() = default;
+
+size_t SimpleTicketCrypter::MaxOverhead() {
+  return kEpochSize + kIVSize + kAuthTagSize;
+}
+
+std::vector<uint8_t> SimpleTicketCrypter::Encrypt(
+    quiche::QuicheStringPiece in) {
+  MaybeRotateKeys();
+  std::vector<uint8_t> out(in.size() + MaxOverhead());
+  out[0] = key_epoch_;
+  RAND_bytes(out.data() + kIVOffset, kIVSize);
+  size_t out_len;
+  const EVP_AEAD_CTX* ctx = current_key_->aead_ctx.get();
+  if (!EVP_AEAD_CTX_seal(ctx, out.data() + kMessageOffset, &out_len,
+                         out.size() - kMessageOffset, out.data() + kIVOffset,
+                         kIVSize, reinterpret_cast<const uint8_t*>(in.data()),
+                         in.size(), nullptr, 0)) {
+    return std::vector<uint8_t>();
+  }
+  out.resize(out_len + kMessageOffset);
+  return out;
+}
+
+std::vector<uint8_t> SimpleTicketCrypter::Decrypt(
+    quiche::QuicheStringPiece in) {
+  MaybeRotateKeys();
+  if (in.size() < kMessageOffset) {
+    return std::vector<uint8_t>();
+  }
+  const uint8_t* input = reinterpret_cast<const uint8_t*>(in.data());
+  std::vector<uint8_t> out(in.size() - kMessageOffset);
+  size_t out_len;
+  const EVP_AEAD_CTX* ctx = current_key_->aead_ctx.get();
+  if (input[0] != key_epoch_) {
+    if (input[0] == static_cast<uint8_t>(key_epoch_ - 1) && previous_key_) {
+      ctx = previous_key_->aead_ctx.get();
+    } else {
+      return std::vector<uint8_t>();
+    }
+  }
+  if (!EVP_AEAD_CTX_open(ctx, out.data(), &out_len, out.size(),
+                         input + kIVOffset, kIVSize, input + kMessageOffset,
+                         in.size() - kMessageOffset, nullptr, 0)) {
+    return std::vector<uint8_t>();
+  }
+  out.resize(out_len);
+  return out;
+}
+
+void SimpleTicketCrypter::Decrypt(
+    quiche::QuicheStringPiece in,
+    std::unique_ptr<quic::ProofSource::DecryptCallback> callback) {
+  callback->Run(Decrypt(in));
+}
+
+void SimpleTicketCrypter::MaybeRotateKeys() {
+  QuicTime now = clock_->ApproximateNow();
+  if (current_key_->expiration < now) {
+    previous_key_ = std::move(current_key_);
+    current_key_ = NewKey();
+    key_epoch_++;
+  }
+}
+
+std::unique_ptr<SimpleTicketCrypter::Key> SimpleTicketCrypter::NewKey() {
+  auto key = std::make_unique<SimpleTicketCrypter::Key>();
+  RAND_bytes(key->key, kKeySize);
+  EVP_AEAD_CTX_init(key->aead_ctx.get(), EVP_aead_aes_128_gcm(), key->key,
+                    kKeySize, EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr);
+  key->expiration = clock_->ApproximateNow() + kTicketKeyLifetime;
+  return key;
+}
+
+}  // namespace quic
diff --git a/quic/tools/simple_ticket_crypter.h b/quic/tools/simple_ticket_crypter.h
new file mode 100644
index 0000000..4c6b9ce
--- /dev/null
+++ b/quic/tools/simple_ticket_crypter.h
@@ -0,0 +1,55 @@
+// 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 QUICHE_QUIC_TOOLS_SIMPLE_TICKET_CRYPTER_H_
+#define QUICHE_QUIC_TOOLS_SIMPLE_TICKET_CRYPTER_H_
+
+#include "third_party/boringssl/src/include/openssl/aead.h"
+#include "net/third_party/quiche/src/quic/core/crypto/proof_source.h"
+#include "net/third_party/quiche/src/quic/core/quic_clock.h"
+#include "net/third_party/quiche/src/quic/core/quic_time.h"
+
+namespace quic {
+
+// SimpleTicketCrypter implements the QUIC ProofSource::TicketCrypter interface.
+// It generates a random key at startup and every 7 days it rotates the key,
+// keeping track of the previous key used to facilitate decrypting older
+// tickets. This implementation is not suitable for server setups where multiple
+// servers need to share keys.
+class QUIC_EXPORT SimpleTicketCrypter
+    : public quic::ProofSource::TicketCrypter {
+ public:
+  explicit SimpleTicketCrypter(QuicClock* clock);
+  ~SimpleTicketCrypter() override;
+
+  size_t MaxOverhead() override;
+  std::vector<uint8_t> Encrypt(quiche::QuicheStringPiece in) override;
+  void Decrypt(
+      quiche::QuicheStringPiece in,
+      std::unique_ptr<quic::ProofSource::DecryptCallback> callback) override;
+
+ private:
+  std::vector<uint8_t> Decrypt(quiche::QuicheStringPiece in);
+
+  void MaybeRotateKeys();
+
+  static constexpr size_t kKeySize = 16;
+
+  struct Key {
+    uint8_t key[kKeySize];
+    bssl::ScopedEVP_AEAD_CTX aead_ctx;
+    QuicTime expiration = QuicTime::Zero();
+  };
+
+  std::unique_ptr<Key> NewKey();
+
+  std::unique_ptr<Key> current_key_;
+  std::unique_ptr<Key> previous_key_;
+  uint8_t key_epoch_ = 0;
+  QuicClock* clock_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_SIMPLE_TICKET_CRYPTER_H_
diff --git a/quic/tools/simple_ticket_crypter_test.cc b/quic/tools/simple_ticket_crypter_test.cc
new file mode 100644
index 0000000..e609dc0
--- /dev/null
+++ b/quic/tools/simple_ticket_crypter_test.cc
@@ -0,0 +1,112 @@
+// 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 "net/third_party/quiche/src/quic/tools/simple_ticket_crypter.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_clock.h"
+
+namespace quic {
+namespace test {
+
+namespace {
+
+constexpr QuicTime::Delta kOneDay = QuicTime::Delta::FromSeconds(60 * 60 * 24);
+
+}  // namespace
+
+class DecryptCallback : public quic::ProofSource::DecryptCallback {
+ public:
+  explicit DecryptCallback(std::vector<uint8_t>* out) : out_(out) {}
+
+  void Run(std::vector<uint8_t> plaintext) override { *out_ = plaintext; }
+
+ private:
+  std::vector<uint8_t>* out_;
+};
+
+quiche::QuicheStringPiece StringPiece(const std::vector<uint8_t>& in) {
+  return quiche::QuicheStringPiece(reinterpret_cast<const char*>(in.data()),
+                                   in.size());
+}
+
+class SimpleTicketCrypterTest : public QuicTest {
+ public:
+  SimpleTicketCrypterTest() : ticket_crypter_(&mock_clock_) {}
+
+ protected:
+  MockClock mock_clock_;
+  SimpleTicketCrypter ticket_crypter_;
+};
+
+TEST_F(SimpleTicketCrypterTest, EncryptDecrypt) {
+  std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
+  std::vector<uint8_t> ciphertext =
+      ticket_crypter_.Encrypt(StringPiece(plaintext));
+  EXPECT_NE(plaintext, ciphertext);
+
+  std::vector<uint8_t> out_plaintext;
+  ticket_crypter_.Decrypt(StringPiece(ciphertext),
+                          std::make_unique<DecryptCallback>(&out_plaintext));
+  EXPECT_EQ(out_plaintext, plaintext);
+}
+
+TEST_F(SimpleTicketCrypterTest, CiphertextsDiffer) {
+  std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
+  std::vector<uint8_t> ciphertext1 =
+      ticket_crypter_.Encrypt(StringPiece(plaintext));
+  std::vector<uint8_t> ciphertext2 =
+      ticket_crypter_.Encrypt(StringPiece(plaintext));
+  EXPECT_NE(ciphertext1, ciphertext2);
+}
+
+TEST_F(SimpleTicketCrypterTest, DecryptionFailureWithModifiedCiphertext) {
+  std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
+  std::vector<uint8_t> ciphertext =
+      ticket_crypter_.Encrypt(StringPiece(plaintext));
+  EXPECT_NE(plaintext, ciphertext);
+
+  // Check that a bit flip in any byte will cause a decryption failure.
+  for (size_t i = 0; i < ciphertext.size(); i++) {
+    SCOPED_TRACE(i);
+    std::vector<uint8_t> munged_ciphertext = ciphertext;
+    munged_ciphertext[i] ^= 1;
+    std::vector<uint8_t> out_plaintext;
+    ticket_crypter_.Decrypt(StringPiece(munged_ciphertext),
+                            std::make_unique<DecryptCallback>(&out_plaintext));
+    EXPECT_TRUE(out_plaintext.empty());
+  }
+}
+
+TEST_F(SimpleTicketCrypterTest, DecryptionFailureWithEmptyCiphertext) {
+  std::vector<uint8_t> out_plaintext;
+  ticket_crypter_.Decrypt(quiche::QuicheStringPiece(),
+                          std::make_unique<DecryptCallback>(&out_plaintext));
+  EXPECT_TRUE(out_plaintext.empty());
+}
+
+TEST_F(SimpleTicketCrypterTest, KeyRotation) {
+  std::vector<uint8_t> plaintext = {1, 2, 3};
+  std::vector<uint8_t> ciphertext =
+      ticket_crypter_.Encrypt(StringPiece(plaintext));
+  EXPECT_FALSE(ciphertext.empty());
+
+  // Advance the clock 8 days, so the key used for |ciphertext| is now the
+  // previous key. Check that decryption still works.
+  mock_clock_.AdvanceTime(kOneDay * 8);
+  std::vector<uint8_t> out_plaintext;
+  ticket_crypter_.Decrypt(StringPiece(ciphertext),
+                          std::make_unique<DecryptCallback>(&out_plaintext));
+  EXPECT_EQ(out_plaintext, plaintext);
+
+  // Advance the clock 8 more days. Now the original key should be expired and
+  // decryption should fail.
+  mock_clock_.AdvanceTime(kOneDay * 8);
+  ticket_crypter_.Decrypt(StringPiece(ciphertext),
+                          std::make_unique<DecryptCallback>(&out_plaintext));
+  EXPECT_TRUE(out_plaintext.empty());
+}
+
+}  // namespace test
+}  // namespace quic