diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn index 8da64511a3..e16648a661 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn @@ -749,6 +749,8 @@ rtc_static_library("rtc_base") { "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 @@ rtc_static_library("rtc_base") { "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 (rtc_include_tests) { } 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 0000000000..288e407ebc --- /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::Create( + KeyDerivationAlgorithm key_derivation_algorithm) { + switch (key_derivation_algorithm) { + case KeyDerivationAlgorithm::HKDF_SHA256: + return absl::make_unique(); + } + RTC_NOTREACHED(); +} + +} // namespace rtc diff --git a/rtc_base/key_derivation.h b/rtc_base/key_derivation.h new file mode 100644 index 0000000000..fa329aeb4c --- /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 + +#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> DeriveKey( + rtc::ArrayView secret, + rtc::ArrayView salt, + rtc::ArrayView 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 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 0000000000..52af667645 --- /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 +#include +#include +#include + +#include +#include + +#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> OpenSSLKeyDerivationHKDF::DeriveKey( + rtc::ArrayView secret, + rtc::ArrayView salt, + rtc::ArrayView 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 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>( + 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 0000000000..ebf43cfaa7 --- /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> DeriveKey( + rtc::ArrayView secret, + rtc::ArrayView salt, + rtc::ArrayView 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 0000000000..92df42ffd3 --- /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 + +#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 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 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