Expose key derivation through a simple interface for use in WebRTC.

This change just wraps the openssl key derivation functions in a simple
interface in a similar way to how we do it for messagedigest.h so we aren't
coupled to openssl in the core implementation.

Bug: webrtc:9917
Change-Id: I8556bd6e38b7da34d93abbe29415c3366f6532ba
Reviewed-on: https://webrtc-review.googlesource.com/c/107981
Reviewed-by: Qingsi Wang <qingsi@webrtc.org>
Commit-Queue: Benjamin Wright <benwright@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25440}
diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn
index 8da6451..e16648a 100644
--- a/rtc_base/BUILD.gn
+++ b/rtc_base/BUILD.gn
@@ -749,6 +749,8 @@
     "ipaddress.cc",
     "ipaddress.h",
     "keep_ref_until_done.h",
+    "key_derivation.cc",
+    "key_derivation.h",
     "mdns_responder_interface.h",
     "messagedigest.cc",
     "messagedigest.h",
@@ -769,6 +771,8 @@
     "nullsocketserver.cc",
     "nullsocketserver.h",
     "openssl.h",
+    "openssl_key_derivation_hkdf.cc",
+    "openssl_key_derivation_hkdf.h",
     "openssladapter.cc",
     "openssladapter.h",
     "opensslcertificate.cc",
@@ -1261,6 +1265,7 @@
     }
     if (is_posix || is_fuchsia) {
       sources += [
+        "openssl_key_derivation_hkdf_unittest.cc",
         "openssladapter_unittest.cc",
         "opensslsessioncache_unittest.cc",
         "opensslutility_unittest.cc",
diff --git a/rtc_base/key_derivation.cc b/rtc_base/key_derivation.cc
new file mode 100644
index 0000000..288e407
--- /dev/null
+++ b/rtc_base/key_derivation.cc
@@ -0,0 +1,31 @@
+/*
+ *  Copyright 2018 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "rtc_base/key_derivation.h"
+
+#include "absl/memory/memory.h"
+#include "rtc_base/openssl_key_derivation_hkdf.h"
+
+namespace rtc {
+
+KeyDerivation::KeyDerivation() = default;
+KeyDerivation::~KeyDerivation() = default;
+
+// static
+std::unique_ptr<KeyDerivation> KeyDerivation::Create(
+    KeyDerivationAlgorithm key_derivation_algorithm) {
+  switch (key_derivation_algorithm) {
+    case KeyDerivationAlgorithm::HKDF_SHA256:
+      return absl::make_unique<OpenSSLKeyDerivationHKDF>();
+  }
+  RTC_NOTREACHED();
+}
+
+}  // namespace rtc
diff --git a/rtc_base/key_derivation.h b/rtc_base/key_derivation.h
new file mode 100644
index 0000000..fa329ae
--- /dev/null
+++ b/rtc_base/key_derivation.h
@@ -0,0 +1,70 @@
+/*
+ *  Copyright 2018 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef RTC_BASE_KEY_DERIVATION_H_
+#define RTC_BASE_KEY_DERIVATION_H_
+
+#include <memory>
+
+#include "absl/types/optional.h"
+#include "api/array_view.h"
+#include "rtc_base/buffer.h"
+#include "rtc_base/constructormagic.h"
+
+namespace rtc {
+
+// Defines the set of key derivation algorithms that are supported. It is ideal
+// to keep this list as small as possible.
+enum class KeyDerivationAlgorithm {
+  // This algorithm is not suitable to generate a key from a password. Please
+  // only use with a cryptographically random master secret.
+  HKDF_SHA256
+};
+
+// KeyDerivation provides a generic interface for deriving keys in WebRTC. This
+// class should be used over directly accessing openssl or boringssl primitives
+// so that we can maintain seperate implementations.
+// Example:
+//   auto kd = KeyDerivation::Create(KeyDerivationAlgorithm::HDKF_SHA526);
+//   if (kd == nullptr) return;
+//   auto derived_key_or = kd->DeriveKey(secret, salt, label);
+//   if (!derived_key_or.ok()) return;
+//   DoSomethingWithKey(derived_key_or.value());
+class KeyDerivation {
+ public:
+  KeyDerivation();
+  virtual ~KeyDerivation();
+
+  // Derives a new key from existing key material.
+  // secret - The random secret value you wish to derive a key from.
+  // salt - Optional but recommended (non secret) cryptographically random.
+  // label - A non secret but unique label value to determine the derivation.
+  // derived_key_byte_size - This must be at least 128 bits.
+  // return - An optional ZeroOnFreeBuffer containing the derived key or
+  // absl::nullopt. Nullopt indicates a failure in derivation.
+  virtual absl::optional<ZeroOnFreeBuffer<uint8_t>> DeriveKey(
+      rtc::ArrayView<const uint8_t> secret,
+      rtc::ArrayView<const uint8_t> salt,
+      rtc::ArrayView<const uint8_t> label,
+      size_t derived_key_byte_size) = 0;
+
+  // Static factory that will return an implementation that is capable of
+  // handling the key derivation with the requested algorithm. If no
+  // implementation is available nullptr will be returned.
+  static std::unique_ptr<KeyDerivation> Create(
+      KeyDerivationAlgorithm key_derivation_algorithm);
+
+ private:
+  RTC_DISALLOW_COPY_AND_ASSIGN(KeyDerivation);
+};
+
+}  // namespace rtc
+
+#endif  // RTC_BASE_KEY_DERIVATION_H_
diff --git a/rtc_base/openssl_key_derivation_hkdf.cc b/rtc_base/openssl_key_derivation_hkdf.cc
new file mode 100644
index 0000000..52af667
--- /dev/null
+++ b/rtc_base/openssl_key_derivation_hkdf.cc
@@ -0,0 +1,70 @@
+/*
+ *  Copyright 2018 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "rtc_base/openssl_key_derivation_hkdf.h"
+
+#include <openssl/digest.h>
+#include <openssl/err.h>
+#include <openssl/hkdf.h>
+#include <openssl/sha.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "rtc_base/buffer.h"
+#include "rtc_base/openssl.h"
+
+namespace rtc {
+
+OpenSSLKeyDerivationHKDF::OpenSSLKeyDerivationHKDF() = default;
+OpenSSLKeyDerivationHKDF::~OpenSSLKeyDerivationHKDF() = default;
+
+const size_t OpenSSLKeyDerivationHKDF::kMinKeyByteSize = 16;
+const size_t OpenSSLKeyDerivationHKDF::kMaxKeyByteSize =
+    255 * SHA256_DIGEST_LENGTH;
+const size_t OpenSSLKeyDerivationHKDF::kMinSecretByteSize = 16;
+
+absl::optional<ZeroOnFreeBuffer<uint8_t>> OpenSSLKeyDerivationHKDF::DeriveKey(
+    rtc::ArrayView<const uint8_t> secret,
+    rtc::ArrayView<const uint8_t> salt,
+    rtc::ArrayView<const uint8_t> label,
+    size_t derived_key_byte_size) {
+  // Prevent deriving less than 128 bits of key material or more than the max.
+  if (derived_key_byte_size < kMinKeyByteSize ||
+      derived_key_byte_size > kMaxKeyByteSize) {
+    return absl::nullopt;
+  }
+  // The secret must reach the minimum number of bits to be secure.
+  if (secret.data() == nullptr || secret.size() < kMinSecretByteSize) {
+    return absl::nullopt;
+  }
+  // Empty labels are always invalid in derivation.
+  if (label.data() == nullptr || label.size() == 0) {
+    return absl::nullopt;
+  }
+  // If a random salt is not provided use all zeros.
+  rtc::Buffer salt_buffer;
+  if (salt.data() == nullptr || salt.size() == 0) {
+    salt_buffer.SetSize(SHA256_DIGEST_LENGTH);
+    std::fill(salt_buffer.begin(), salt_buffer.end(), 0);
+    salt = salt_buffer;
+  }
+  // This buffer will erase itself on release.
+  ZeroOnFreeBuffer<uint8_t> derived_key_buffer(derived_key_byte_size, 0);
+  if (!HKDF(derived_key_buffer.data(), derived_key_buffer.size(), EVP_sha256(),
+            secret.data(), secret.size(), salt.data(), salt.size(),
+            label.data(), label.size())) {
+    return absl::nullopt;
+  }
+  return absl::optional<ZeroOnFreeBuffer<uint8_t>>(
+      std::move(derived_key_buffer));
+}
+
+}  // namespace rtc
diff --git a/rtc_base/openssl_key_derivation_hkdf.h b/rtc_base/openssl_key_derivation_hkdf.h
new file mode 100644
index 0000000..ebf43cf
--- /dev/null
+++ b/rtc_base/openssl_key_derivation_hkdf.h
@@ -0,0 +1,54 @@
+/*
+ *  Copyright 2018 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef RTC_BASE_OPENSSL_KEY_DERIVATION_HKDF_H_
+#define RTC_BASE_OPENSSL_KEY_DERIVATION_HKDF_H_
+
+#include "rtc_base/constructormagic.h"
+#include "rtc_base/key_derivation.h"
+
+namespace rtc {
+
+// OpenSSLKeyDerivationHKDF provides a concrete implementation of the
+// KeyDerivation interface to support the HKDF algorithm using the
+// OpenSSL/BoringSSL internal implementation.
+class OpenSSLKeyDerivationHKDF final : public KeyDerivation {
+ public:
+  OpenSSLKeyDerivationHKDF();
+  ~OpenSSLKeyDerivationHKDF() override;
+
+  // General users shouldn't be generating keys smaller than 128 bits.
+  static const size_t kMinKeyByteSize;
+  // The maximum available derivation size 255*DIGEST_LENGTH
+  static const size_t kMaxKeyByteSize;
+  // The minimum acceptable secret size.
+  static const size_t kMinSecretByteSize;
+
+  // Derives a new key from existing key material using HKDF.
+  // secret - The random secret value you wish to derive a key from.
+  // salt - Optional (non secret) cryptographically random value.
+  // label - A non secret but unique label value to determine the derivation.
+  // derived_key_byte_size - The size of the derived key.
+  // return - A ZeroOnFreeBuffer containing the derived key or an error
+  // condition. Checking error codes is explicit in the API and error should
+  // never be ignored.
+  absl::optional<ZeroOnFreeBuffer<uint8_t>> DeriveKey(
+      rtc::ArrayView<const uint8_t> secret,
+      rtc::ArrayView<const uint8_t> salt,
+      rtc::ArrayView<const uint8_t> label,
+      size_t derived_key_byte_size) override;
+
+ private:
+  RTC_DISALLOW_COPY_AND_ASSIGN(OpenSSLKeyDerivationHKDF);
+};
+
+}  // namespace rtc
+
+#endif  // RTC_BASE_OPENSSL_KEY_DERIVATION_HKDF_H_
diff --git a/rtc_base/openssl_key_derivation_hkdf_unittest.cc b/rtc_base/openssl_key_derivation_hkdf_unittest.cc
new file mode 100644
index 0000000..92df42f
--- /dev/null
+++ b/rtc_base/openssl_key_derivation_hkdf_unittest.cc
@@ -0,0 +1,107 @@
+/*
+ *  Copyright 2018 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "rtc_base/openssl_key_derivation_hkdf.h"
+
+#include <utility>
+
+#include "test/gmock.h"
+
+namespace rtc {
+namespace {
+
+// Validates that a basic valid call works correctly.
+TEST(OpenSSLKeyDerivationHKDF, DerivationBasicTest) {
+  rtc::Buffer secret(32);
+  rtc::Buffer salt(32);
+  rtc::Buffer label(32);
+  const size_t derived_key_byte_size = 16;
+
+  OpenSSLKeyDerivationHKDF hkdf;
+  auto key_or = hkdf.DeriveKey(secret, salt, label, derived_key_byte_size);
+  EXPECT_TRUE(key_or.has_value());
+  ZeroOnFreeBuffer<uint8_t> key = std::move(key_or.value());
+  EXPECT_EQ(derived_key_byte_size, key.size());
+}
+
+// Derivation fails if output is too small.
+TEST(OpenSSLKeyDerivationHKDF, DerivationFailsIfOutputIsTooSmall) {
+  rtc::Buffer secret(32);
+  rtc::Buffer salt(32);
+  rtc::Buffer label(32);
+  const size_t derived_key_byte_size = 15;
+
+  OpenSSLKeyDerivationHKDF hkdf;
+  auto key_or = hkdf.DeriveKey(secret, salt, label, derived_key_byte_size);
+  EXPECT_FALSE(key_or.has_value());
+}
+
+// Derivation fails if output is too large.
+TEST(OpenSSLKeyDerivationHKDF, DerivationFailsIfOutputIsTooLarge) {
+  rtc::Buffer secret(32);
+  rtc::Buffer salt(32);
+  rtc::Buffer label(32);
+  const size_t derived_key_byte_size = 256 * 32;
+
+  OpenSSLKeyDerivationHKDF hkdf;
+  auto key_or = hkdf.DeriveKey(secret, salt, label, derived_key_byte_size);
+  EXPECT_FALSE(key_or.has_value());
+}
+
+// Validates that too little key material causes a failure.
+TEST(OpenSSLKeyDerivationHKDF, DerivationFailsWithInvalidSecret) {
+  rtc::Buffer secret(15);
+  rtc::Buffer salt(32);
+  rtc::Buffer label(32);
+  const size_t derived_key_byte_size = 16;
+
+  OpenSSLKeyDerivationHKDF hkdf;
+  auto key_or_0 = hkdf.DeriveKey(secret, salt, label, derived_key_byte_size);
+  EXPECT_FALSE(key_or_0.has_value());
+
+  auto key_or_1 = hkdf.DeriveKey(nullptr, salt, label, derived_key_byte_size);
+  EXPECT_FALSE(key_or_1.has_value());
+
+  rtc::Buffer secret_empty;
+  auto key_or_2 =
+      hkdf.DeriveKey(secret_empty, salt, label, derived_key_byte_size);
+  EXPECT_FALSE(key_or_2.has_value());
+}
+
+// Validates that HKDF works without a salt being set.
+TEST(OpenSSLKeyDerivationHKDF, DerivationWorksWithNoSalt) {
+  rtc::Buffer secret(32);
+  rtc::Buffer label(32);
+  const size_t derived_key_byte_size = 16;
+
+  OpenSSLKeyDerivationHKDF hkdf;
+  auto key_or = hkdf.DeriveKey(secret, nullptr, label, derived_key_byte_size);
+  EXPECT_TRUE(key_or.has_value());
+}
+
+// Validates that a label is required to work correctly.
+TEST(OpenSSLKeyDerivationHKDF, DerivationRequiresLabel) {
+  rtc::Buffer secret(32);
+  rtc::Buffer salt(32);
+  rtc::Buffer label(1);
+  const size_t derived_key_byte_size = 16;
+
+  OpenSSLKeyDerivationHKDF hkdf;
+  auto key_or_0 = hkdf.DeriveKey(secret, salt, label, derived_key_byte_size);
+  EXPECT_TRUE(key_or_0.has_value());
+  ZeroOnFreeBuffer<uint8_t> key = std::move(key_or_0.value());
+  EXPECT_EQ(key.size(), derived_key_byte_size);
+
+  auto key_or_1 = hkdf.DeriveKey(secret, salt, nullptr, derived_key_byte_size);
+  EXPECT_FALSE(key_or_1.has_value());
+}
+
+}  // namespace
+}  // namespace rtc