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