Skip to content

Commit

Permalink
Implement QUIC key extraction.
Browse files Browse the repository at this point in the history
Added a new subkey_secret output to crypto::HKDF which is
saved by the forward-secure key derivation and used for a new
ExportKeyingMaterial method on QuicCryptoStream. This will be used
in Chromium for WebRTC on QUIC.

Generated some tests by making a straightforward alternative
implementation in Python.

Written by Daniel Ziegler.

Merge internal CL: 72073257

R=agl@chromium.org,dmziegler@chromium.org
BUG=

Review URL: https://codereview.chromium.org/423333002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@286738 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
wtc@chromium.org committed Jul 31, 2014
1 parent cc405e4 commit 2fe8b63
Show file tree
Hide file tree
Showing 12 changed files with 254 additions and 38 deletions.
14 changes: 11 additions & 3 deletions crypto/hkdf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "crypto/hkdf.h"

#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "crypto/hmac.h"

namespace crypto {
Expand All @@ -15,7 +16,8 @@ HKDF::HKDF(const base::StringPiece& secret,
const base::StringPiece& salt,
const base::StringPiece& info,
size_t key_bytes_to_generate,
size_t iv_bytes_to_generate) {
size_t iv_bytes_to_generate,
size_t subkey_secret_bytes_to_generate) {
// https://tools.ietf.org/html/rfc5869#section-2.2
base::StringPiece actual_salt = salt;
char zeros[kSHA256HashLength];
Expand All @@ -40,8 +42,9 @@ HKDF::HKDF(const base::StringPiece& secret,
// https://tools.ietf.org/html/rfc5869#section-2.3
// Perform the Expand phase to turn the pseudorandom key
// and info into the output keying material.
const size_t material_length =
2*key_bytes_to_generate + 2*iv_bytes_to_generate;
const size_t material_length = 2 * key_bytes_to_generate +
2 * iv_bytes_to_generate +
subkey_secret_bytes_to_generate;
const size_t n = (material_length + kSHA256HashLength-1) /
kSHA256HashLength;
DCHECK_LT(n, 256u);
Expand Down Expand Up @@ -90,6 +93,11 @@ HKDF::HKDF(const base::StringPiece& secret,
j += iv_bytes_to_generate;
server_write_iv_ = base::StringPiece(reinterpret_cast<char*>(&output_[j]),
iv_bytes_to_generate);
j += iv_bytes_to_generate;
}
if (subkey_secret_bytes_to_generate) {
subkey_secret_ = base::StringPiece(reinterpret_cast<char*>(&output_[j]),
subkey_secret_bytes_to_generate);
}
}

Expand Down
19 changes: 13 additions & 6 deletions crypto/hkdf.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
#include <vector>

#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_piece.h"
#include "build/build_config.h"
#include "crypto/crypto_export.h"

namespace crypto {
Expand All @@ -20,21 +18,26 @@ namespace crypto {
// See https://tools.ietf.org/html/rfc5869 for details.
class CRYPTO_EXPORT HKDF {
public:
// |secret|: The input shared secret (or, from RFC 5869, the IKM).
// |secret|: the input shared secret (or, from RFC 5869, the IKM).
// |salt|: an (optional) public salt / non-secret random value. While
// optional, callers are strongly recommended to provide a salt. There is no
// added security value in making this larger than the SHA-256 block size of
// 64 bytes.
// |info|: an (optional) label to distinguish different uses of HKDF. It is
// optional context and application specific information (can be a zero-length
// string).
// |key_bytes_to_generate|: the number of bytes of key material to generate.
// |iv_bytes_to_generate|: the number of bytes of IV to generate.
// |key_bytes_to_generate|: the number of bytes of key material to generate
// for both client and server.
// |iv_bytes_to_generate|: the number of bytes of IV to generate for both
// client and server.
// |subkey_secret_bytes_to_generate|: the number of bytes of subkey secret to
// generate, shared between client and server.
HKDF(const base::StringPiece& secret,
const base::StringPiece& salt,
const base::StringPiece& info,
size_t key_bytes_to_generate,
size_t iv_bytes_to_generate);
size_t iv_bytes_to_generate,
size_t subkey_secret_bytes_to_generate);
~HKDF();

base::StringPiece client_write_key() const {
Expand All @@ -49,6 +52,9 @@ class CRYPTO_EXPORT HKDF {
base::StringPiece server_write_iv() const {
return server_write_iv_;
}
base::StringPiece subkey_secret() const {
return subkey_secret_;
}

private:
std::vector<uint8> output_;
Expand All @@ -57,6 +63,7 @@ class CRYPTO_EXPORT HKDF {
base::StringPiece server_write_key_;
base::StringPiece client_write_iv_;
base::StringPiece server_write_iv_;
base::StringPiece subkey_secret_;
};

} // namespace crypto
Expand Down
2 changes: 1 addition & 1 deletion crypto/hkdf_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ TEST(HKDFTest, HKDF) {

// We set the key_length to the length of the expected output and then take
// the result from the first key, which is the client write key.
HKDF hkdf(key, salt, info, expected.size(), 0);
HKDF hkdf(key, salt, info, expected.size(), 0, 0);

ASSERT_EQ(expected.size(), hkdf.client_write_key().size());
EXPECT_EQ(0, memcmp(expected.data(), hkdf.client_write_key().data(),
Expand Down
2 changes: 2 additions & 0 deletions net/quic/crypto/crypto_handshake.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ struct NET_EXPORT_PRIVATE QuicCryptoNegotiatedParameters {
QuicTag aead;
std::string initial_premaster_secret;
std::string forward_secure_premaster_secret;
// subkey_secret is used as the PRK input to the HKDF used for key extraction.
std::string subkey_secret;
CrypterPair initial_crypters;
CrypterPair forward_secure_crypters;
// Normalized SNI: converted to lower case and trailing '.' removed.
Expand Down
68 changes: 54 additions & 14 deletions net/quic/crypto/crypto_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "url/url_canon.h"

using base::StringPiece;
using std::numeric_limits;
using std::string;

namespace net {
Expand Down Expand Up @@ -83,11 +84,14 @@ bool CryptoUtils::DeriveKeys(StringPiece premaster_secret,
StringPiece server_nonce,
const string& hkdf_input,
Perspective perspective,
CrypterPair* out) {
out->encrypter.reset(QuicEncrypter::Create(aead));
out->decrypter.reset(QuicDecrypter::Create(aead));
size_t key_bytes = out->encrypter->GetKeySize();
size_t nonce_prefix_bytes = out->encrypter->GetNoncePrefixSize();
CrypterPair* crypters,
string* subkey_secret) {
crypters->encrypter.reset(QuicEncrypter::Create(aead));
crypters->decrypter.reset(QuicDecrypter::Create(aead));
size_t key_bytes = crypters->encrypter->GetKeySize();
size_t nonce_prefix_bytes = crypters->encrypter->GetNoncePrefixSize();
size_t subkey_secret_bytes =
subkey_secret == NULL ? 0 : premaster_secret.length();

StringPiece nonce = client_nonce;
string nonce_storage;
Expand All @@ -97,23 +101,59 @@ bool CryptoUtils::DeriveKeys(StringPiece premaster_secret,
}

crypto::HKDF hkdf(premaster_secret, nonce, hkdf_input, key_bytes,
nonce_prefix_bytes);
nonce_prefix_bytes, subkey_secret_bytes);
if (perspective == SERVER) {
if (!out->encrypter->SetKey(hkdf.server_write_key()) ||
!out->encrypter->SetNoncePrefix(hkdf.server_write_iv()) ||
!out->decrypter->SetKey(hkdf.client_write_key()) ||
!out->decrypter->SetNoncePrefix(hkdf.client_write_iv())) {
if (!crypters->encrypter->SetKey(hkdf.server_write_key()) ||
!crypters->encrypter->SetNoncePrefix(hkdf.server_write_iv()) ||
!crypters->decrypter->SetKey(hkdf.client_write_key()) ||
!crypters->decrypter->SetNoncePrefix(hkdf.client_write_iv())) {
return false;
}
} else {
if (!out->encrypter->SetKey(hkdf.client_write_key()) ||
!out->encrypter->SetNoncePrefix(hkdf.client_write_iv()) ||
!out->decrypter->SetKey(hkdf.server_write_key()) ||
!out->decrypter->SetNoncePrefix(hkdf.server_write_iv())) {
if (!crypters->encrypter->SetKey(hkdf.client_write_key()) ||
!crypters->encrypter->SetNoncePrefix(hkdf.client_write_iv()) ||
!crypters->decrypter->SetKey(hkdf.server_write_key()) ||
!crypters->decrypter->SetNoncePrefix(hkdf.server_write_iv())) {
return false;
}
}
if (subkey_secret != NULL) {
hkdf.subkey_secret().CopyToString(subkey_secret);
}

return true;
}

// static
bool CryptoUtils::ExportKeyingMaterial(StringPiece subkey_secret,
StringPiece label,
StringPiece context,
size_t result_len,
string* result) {
for (size_t i = 0; i < label.length(); i++) {
if (label[i] == '\0') {
LOG(ERROR) << "ExportKeyingMaterial label may not contain NULs";
return false;
}
}
// Create HKDF info input: null-terminated label + length-prefixed context
if (context.length() >= numeric_limits<uint32>::max()) {
LOG(ERROR) << "Context value longer than 2^32";
return false;
}
uint32 context_length = static_cast<uint32>(context.length());
string info = label.as_string();
info.push_back('\0');
info.append(reinterpret_cast<char*>(&context_length), sizeof(context_length));
info.append(context.data(), context.length());

crypto::HKDF hkdf(subkey_secret,
StringPiece() /* no salt */,
info,
result_len,
0 /* no fixed IV */,
0 /* no subkey secret */);
hkdf.client_write_key().CopyToString(result);
return true;
}

Expand Down
26 changes: 19 additions & 7 deletions net/quic/crypto/crypto_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,31 @@ class NET_EXPORT_PRIVATE CryptoUtils {
// literals. IsValidSNI() should be called before calling NormalizeHostname().
static std::string NormalizeHostname(const char* hostname);

// DeriveKeys populates |out->encrypter| and |out->decrypter| given the
// contents of |premaster_secret|, |client_nonce|, |server_nonce| and
// |hkdf_input|. |aead| determines which cipher will be used. |perspective|
// controls whether the server's keys are assigned to |encrypter| or
// |decrypter|. |server_nonce| is optional and, if non-empty, is mixed into
// the key derivation.
// DeriveKeys populates |crypters->encrypter|, |crypters->decrypter|, and
// |subkey_secret| (optional -- may be null) given the contents of
// |premaster_secret|, |client_nonce|, |server_nonce| and |hkdf_input|. |aead|
// determines which cipher will be used. |perspective| controls whether the
// server's keys are assigned to |encrypter| or |decrypter|. |server_nonce| is
// optional and, if non-empty, is mixed into the key derivation.
// |subkey_secret| will have the same length as |premaster_secret|.
static bool DeriveKeys(base::StringPiece premaster_secret,
QuicTag aead,
base::StringPiece client_nonce,
base::StringPiece server_nonce,
const std::string& hkdf_input,
Perspective perspective,
CrypterPair* out);
CrypterPair* crypters,
std::string* subkey_secret);

// Performs key extraction to derive a new secret of |result_len| bytes
// dependent on |subkey_secret|, |label|, and |context|. Returns false if the
// parameters are invalid (e.g. |label| contains null bytes); returns true on
// success.
static bool ExportKeyingMaterial(base::StringPiece subkey_secret,
base::StringPiece label,
base::StringPiece context,
size_t result_len,
std::string* result);

private:
DISALLOW_COPY_AND_ASSIGN(CryptoUtils);
Expand Down
82 changes: 82 additions & 0 deletions net/quic/crypto/crypto_utils_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "net/quic/crypto/crypto_utils.h"

#include "net/quic/test_tools/quic_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {
Expand Down Expand Up @@ -44,6 +45,87 @@ TEST(CryptoUtilsTest, NormalizeHostname) {
}
}

TEST(CryptoUtilsTest, TestExportKeyingMaterial) {
const struct TestVector {
// Input (strings of hexadecimal digits):
const char* subkey_secret;
const char* label;
const char* context;
size_t result_len;

// Expected output (string of hexadecimal digits):
const char* expected; // Null if it should fail.
} test_vector[] = {
// Try a typical input
{ "4823c1189ecc40fce888fbb4cf9ae6254f19ba12e6d9af54788f195a6f509ca3",
"e934f78d7a71dd85420fceeb8cea0317",
"b8d766b5d3c8aba0009c7ed3de553eba53b4de1030ea91383dcdf724cd8b7217",
32,
"a9979da0d5f1c1387d7cbe68f5c4163ddb445a03c4ad6ee72cb49d56726d679e"
},
// Don't let the label contain nulls
{ "14fe51e082ffee7d1b4d8d4ab41f8c55",
"3132333435363700",
"58585858585858585858585858585858",
16,
NULL
},
// Make sure nulls in the context are fine
{ "d862c2e36b0a42f7827c67ebc8d44df7",
"7a5b95e4e8378123",
"4142434445464700",
16,
"12d418c6d0738a2e4d85b2d0170f76e1"
},
// ... and give a different result than without
{ "d862c2e36b0a42f7827c67ebc8d44df7",
"7a5b95e4e8378123",
"41424344454647",
16,
"abfa1c479a6e3ffb98a11dee7d196408"
},
// Try weird lengths
{ "d0ec8a34f6cc9a8c96",
"49711798cc6251",
"933d4a2f30d22f089cfba842791116adc121e0",
23,
"c9a46ed0757bd1812f1f21b4d41e62125fec8364a21db7"
},
};

for (size_t i = 0; i < arraysize(test_vector); i++) {
// Decode the test vector.
string subkey_secret;
string label;
string context;
ASSERT_TRUE(DecodeHexString(test_vector[i].subkey_secret, &subkey_secret));
ASSERT_TRUE(DecodeHexString(test_vector[i].label, &label));
ASSERT_TRUE(DecodeHexString(test_vector[i].context, &context));
size_t result_len = test_vector[i].result_len;
bool expect_ok = test_vector[i].expected != NULL;
string expected;
if (expect_ok) {
ASSERT_TRUE(DecodeHexString(test_vector[i].expected, &expected));
}

string result;
bool ok = CryptoUtils::ExportKeyingMaterial(subkey_secret,
label,
context,
result_len,
&result);
EXPECT_EQ(expect_ok, ok);
if (expect_ok) {
EXPECT_EQ(result_len, result.length());
test::CompareCharArraysWithHexError("HKDF output",
result.data(),
result.length(),
expected.data(),
expected.length());
}
}
}

} // namespace
} // namespace test
} // namespace net
9 changes: 6 additions & 3 deletions net/quic/crypto/quic_crypto_client_config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,8 @@ QuicErrorCode QuicCryptoClientConfig::FillClientHello(
if (!CryptoUtils::DeriveKeys(out_params->initial_premaster_secret,
out_params->aead, out_params->client_nonce,
out_params->server_nonce, hkdf_input,
CryptoUtils::CLIENT, &crypters)) {
CryptoUtils::CLIENT, &crypters,
NULL /* subkey secret */)) {
*error_details = "Symmetric key setup failed";
return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED;
}
Expand Down Expand Up @@ -557,7 +558,8 @@ QuicErrorCode QuicCryptoClientConfig::FillClientHello(
if (!CryptoUtils::DeriveKeys(
out_params->initial_premaster_secret, out_params->aead,
out_params->client_nonce, out_params->server_nonce, hkdf_input,
CryptoUtils::CLIENT, &out_params->initial_crypters)) {
CryptoUtils::CLIENT, &out_params->initial_crypters,
NULL /* subkey secret */)) {
*error_details = "Symmetric key setup failed";
return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED;
}
Expand Down Expand Up @@ -737,7 +739,8 @@ QuicErrorCode QuicCryptoClientConfig::ProcessServerHello(
if (!CryptoUtils::DeriveKeys(
out_params->forward_secure_premaster_secret, out_params->aead,
out_params->client_nonce, out_params->server_nonce, hkdf_input,
CryptoUtils::CLIENT, &out_params->forward_secure_crypters)) {
CryptoUtils::CLIENT, &out_params->forward_secure_crypters,
&out_params->subkey_secret)) {
*error_details = "Symmetric key setup failed";
return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED;
}
Expand Down
Loading

0 comments on commit 2fe8b63

Please sign in to comment.