Skip to content

Commit

Permalink
Implement ECDH for WebCrypto using BoringSSL (chromium-side).
Browse files Browse the repository at this point in the history
BUG=399093

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

Cr-Commit-Position: refs/heads/master@{#306112}
  • Loading branch information
eroman authored and Commit bot committed Nov 28, 2014
1 parent 271679c commit ed48e81
Show file tree
Hide file tree
Showing 10 changed files with 661 additions and 1 deletion.
6 changes: 5 additions & 1 deletion content/child/webcrypto/algorithm_registry.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class AlgorithmRegistry {
rsa_ssa_(CreatePlatformRsaSsaImplementation()),
rsa_oaep_(CreatePlatformRsaOaepImplementation()),
rsa_pss_(CreatePlatformRsaPssImplementation()),
ecdsa_(CreatePlatformEcdsaImplementation()) {
ecdsa_(CreatePlatformEcdsaImplementation()),
ecdh_(CreatePlatformEcdhImplementation()) {
PlatformInit();
}

Expand Down Expand Up @@ -58,6 +59,8 @@ class AlgorithmRegistry {
return rsa_pss_.get();
case blink::WebCryptoAlgorithmIdEcdsa:
return ecdsa_.get();
case blink::WebCryptoAlgorithmIdEcdh:
return ecdh_.get();
default:
return NULL;
}
Expand All @@ -74,6 +77,7 @@ class AlgorithmRegistry {
const scoped_ptr<AlgorithmImplementation> rsa_oaep_;
const scoped_ptr<AlgorithmImplementation> rsa_pss_;
const scoped_ptr<AlgorithmImplementation> ecdsa_;
const scoped_ptr<AlgorithmImplementation> ecdh_;
};

} // namespace
Expand Down
5 changes: 5 additions & 0 deletions content/child/webcrypto/nss/util_nss.cc
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ AlgorithmImplementation* CreatePlatformEcdsaImplementation() {
return NULL;
}

AlgorithmImplementation* CreatePlatformEcdhImplementation() {
// TODO(eroman): http://crbug.com/399093
return NULL;
}

} // namespace webcrypto

} // namespace content
137 changes: 137 additions & 0 deletions content/child/webcrypto/openssl/ecdh_openssl.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright 2014 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 <openssl/ec.h>
#include <openssl/ecdh.h>
#include <openssl/evp.h>

#include "base/logging.h"
#include "content/child/webcrypto/algorithm_implementation.h"
#include "content/child/webcrypto/crypto_data.h"
#include "content/child/webcrypto/generate_key_result.h"
#include "content/child/webcrypto/openssl/ec_key_openssl.h"
#include "content/child/webcrypto/openssl/key_openssl.h"
#include "content/child/webcrypto/openssl/util_openssl.h"
#include "content/child/webcrypto/status.h"
#include "content/child/webcrypto/webcrypto_util.h"
#include "crypto/openssl_util.h"
#include "crypto/scoped_openssl_types.h"
#include "crypto/secure_util.h"
#include "third_party/WebKit/public/platform/WebCryptoAlgorithmParams.h"
#include "third_party/WebKit/public/platform/WebCryptoKey.h"
#include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h"

namespace content {

namespace webcrypto {

namespace {

// TODO(eroman): Support the "raw" format for ECDH key import + export, as
// specified by WebCrypto spec.

// TODO(eroman): Allow id-ecDH in SPKI and PKCS#8 import
// (http://crbug.com/389400)

class EcdhImplementation : public EcAlgorithm {
public:
EcdhImplementation()
: EcAlgorithm(0,
blink::WebCryptoKeyUsageDeriveKey |
blink::WebCryptoKeyUsageDeriveBits) {}

const char* GetJwkAlgorithm(
const blink::WebCryptoNamedCurve curve) const override {
// JWK import for ECDH does not enforce any required value for "alg".
return "";
}

Status DeriveBits(const blink::WebCryptoAlgorithm& algorithm,
const blink::WebCryptoKey& base_key,
unsigned int length_bits,
std::vector<uint8_t>* derived_bytes) const override {
if (base_key.type() != blink::WebCryptoKeyTypePrivate)
return Status::ErrorUnexpectedKeyType();

// Verify the "publicKey" parameter. The only guarantee from Blink is that
// it is a valid WebCryptoKey, but it could be any type.
const blink::WebCryptoKey& public_key =
algorithm.ecdhKeyDeriveParams()->publicKey();

if (public_key.type() != blink::WebCryptoKeyTypePublic)
return Status::ErrorEcdhPublicKeyWrongType();

// Make sure it is an EC key.
if (!public_key.algorithm().ecParams())
return Status::ErrorEcdhPublicKeyWrongType();

// TODO(eroman): This is not described by the spec:
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=27404
if (public_key.algorithm().id() != blink::WebCryptoAlgorithmIdEcdh)
return Status::ErrorEcdhPublicKeyWrongAlgorithm();

// The public and private keys come from different key pairs, however their
// curves must match.
if (public_key.algorithm().ecParams()->namedCurve() !=
base_key.algorithm().ecParams()->namedCurve()) {
return Status::ErrorEcdhCurveMismatch();
}

// Handle the empty length case now to avoid calling an undefined
// |&derived_bytes->front()| later.
if (length_bits == 0) {
derived_bytes->clear();
return Status::Success();
}

crypto::ScopedEC_KEY public_key_ec(
EVP_PKEY_get1_EC_KEY(AsymKeyOpenSsl::Cast(public_key)->key()));

const EC_POINT* public_key_point =
EC_KEY_get0_public_key(public_key_ec.get());

crypto::ScopedEC_KEY private_key_ec(
EVP_PKEY_get1_EC_KEY(AsymKeyOpenSsl::Cast(base_key)->key()));

// The size of the shared secret is the field size in bytes (rounded up).
// Note that, if rounding was required, the most significant bits of the
// secret are zero. So for P-521, the maximum length is 528 bits, not 521.
int field_size_bytes =
(EC_GROUP_get_degree(EC_KEY_get0_group(private_key_ec.get())) + 7) / 8;

if (length_bits > static_cast<unsigned int>(field_size_bytes * 8))
return Status::ErrorEcdhLengthTooBig(field_size_bytes * 8);

// Resize to target length in bytes (BoringSSL can operate on a shorter
// buffer than field_size_bytes).
derived_bytes->resize((length_bits + 7) / 8);

int result =
ECDH_compute_key(&derived_bytes->front(), derived_bytes->size(),
public_key_point, private_key_ec.get(), 0);
if (result < 0 || static_cast<size_t>(result) != derived_bytes->size())
return Status::OperationError();

// Zero any "unused bits" in the resulting byte array.
// TODO(eroman): This is not described by the spec:
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=27402
unsigned int remainder_bits = length_bits % 8;
if (remainder_bits) {
(*derived_bytes)[derived_bytes->size() - 1] &=
~((0xFF) >> remainder_bits);
}

return Status::Success();
}
};

} // namespace

AlgorithmImplementation* CreatePlatformEcdhImplementation() {
return new EcdhImplementation;
}

} // namespace webcrypto

} // namespace content
1 change: 1 addition & 0 deletions content/child/webcrypto/platform_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ AlgorithmImplementation* CreatePlatformRsaOaepImplementation();
AlgorithmImplementation* CreatePlatformRsaSsaImplementation();
AlgorithmImplementation* CreatePlatformRsaPssImplementation();
AlgorithmImplementation* CreatePlatformEcdsaImplementation();
AlgorithmImplementation* CreatePlatformEcdhImplementation();

} // namespace webcrypto

Expand Down
26 changes: 26 additions & 0 deletions content/child/webcrypto/status.cc
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,32 @@ Status Status::JwkOctetStringWrongLength(const std::string& member_name,
member_name.c_str(), actual_length, expected_length));
}

Status Status::ErrorEcdhPublicKeyWrongType() {
return Status(
blink::WebCryptoErrorTypeInvalidAccess,
"The public parameter for ECDH key derivation is not a public EC key");
}

Status Status::ErrorEcdhPublicKeyWrongAlgorithm() {
return Status(
blink::WebCryptoErrorTypeInvalidAccess,
"The public parameter for ECDH key derivation must be for ECDH");
}

Status Status::ErrorEcdhCurveMismatch() {
return Status(blink::WebCryptoErrorTypeInvalidAccess,
"The public parameter for ECDH key derivation is for a "
"different named curve");
}

Status Status::ErrorEcdhLengthTooBig(unsigned int max_length_bits) {
return Status(blink::WebCryptoErrorTypeInvalidAccess,
base::StringPrintf(
"Length specified for ECDH key derivation is too large. "
"Maximum allowed is %u bits",
max_length_bits));
}

Status::Status(blink::WebCryptoErrorType error_type,
const std::string& error_details_utf8)
: type_(TYPE_ERROR),
Expand Down
13 changes: 13 additions & 0 deletions content/child/webcrypto/status.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,19 @@ class CONTENT_EXPORT Status {
size_t expected_length,
size_t actual_length);

// The public key given for ECDH key derivation was not an EC public key.
static Status ErrorEcdhPublicKeyWrongType();

// The public key's algorithm was not ECDH.
static Status ErrorEcdhPublicKeyWrongAlgorithm();

// The public and private keys given to ECDH key derivation were not for the
// same named curve.
static Status ErrorEcdhCurveMismatch();

// The requested bit length for ECDH key derivation was too large.
static Status ErrorEcdhLengthTooBig(unsigned int max_length_bits);

private:
enum Type { TYPE_ERROR, TYPE_SUCCESS };

Expand Down
107 changes: 107 additions & 0 deletions content/child/webcrypto/test/ecdh_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2014 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 "base/stl_util.h"
#include "content/child/webcrypto/algorithm_dispatch.h"
#include "content/child/webcrypto/crypto_data.h"
#include "content/child/webcrypto/jwk.h"
#include "content/child/webcrypto/status.h"
#include "content/child/webcrypto/test/test_helpers.h"
#include "content/child/webcrypto/webcrypto_util.h"
#include "third_party/WebKit/public/platform/WebCryptoAlgorithmParams.h"
#include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h"

namespace content {

namespace webcrypto {

namespace {

bool SupportsEcdh() {
#if defined(USE_OPENSSL)
return true;
#else
LOG(ERROR) << "Skipping ECDH test because unsupported";
return false;
#endif
}

// TODO(eroman): Test passing an RSA public key instead of ECDH key.
// TODO(eroman): Test passing an ECDSA public key

blink::WebCryptoAlgorithm CreateEcdhImportAlgorithm(
blink::WebCryptoNamedCurve named_curve) {
return CreateEcImportAlgorithm(blink::WebCryptoAlgorithmIdEcdh, named_curve);
}

blink::WebCryptoAlgorithm CreateEcdhDeriveParams(
const blink::WebCryptoKey& public_key) {
return blink::WebCryptoAlgorithm::adoptParamsAndCreate(
blink::WebCryptoAlgorithmIdEcdh,
new blink::WebCryptoEcdhKeyDeriveParams(public_key));
}

TEST(WebCryptoEcdhTest, VerifyKnownAnswer) {
if (!SupportsEcdh())
return;

scoped_ptr<base::ListValue> tests;
ASSERT_TRUE(ReadJsonTestFileToList("ecdh.json", &tests));

for (size_t test_index = 0; test_index < tests->GetSize(); ++test_index) {
SCOPED_TRACE(test_index);

const base::DictionaryValue* test;
ASSERT_TRUE(tests->GetDictionary(test_index, &test));

// Import the public key.
const base::DictionaryValue* public_key_json = NULL;
EXPECT_TRUE(test->GetDictionary("public_key", &public_key_json));
blink::WebCryptoNamedCurve curve =
GetCurveNameFromDictionary(public_key_json, "crv");
blink::WebCryptoKey public_key;
ASSERT_EQ(
Status::Success(),
ImportKey(blink::WebCryptoKeyFormatJwk,
CryptoData(MakeJsonVector(*public_key_json)),
CreateEcdhImportAlgorithm(curve), true, 0, &public_key));

// Import the private key.
const base::DictionaryValue* private_key_json = NULL;
EXPECT_TRUE(test->GetDictionary("private_key", &private_key_json));
curve = GetCurveNameFromDictionary(private_key_json, "crv");
blink::WebCryptoKey private_key;
ASSERT_EQ(Status::Success(),
ImportKey(blink::WebCryptoKeyFormatJwk,
CryptoData(MakeJsonVector(*private_key_json)),
CreateEcdhImportAlgorithm(curve), true,
blink::WebCryptoKeyUsageDeriveBits, &private_key));

// Now try to derive bytes.
std::vector<uint8_t> derived_bytes;
int length_bits = 0;
ASSERT_TRUE(test->GetInteger("length_bits", &length_bits));

// If the test didn't specify an error, that implies it expects success.
std::string expected_error = "Success";
test->GetString("error", &expected_error);

Status status = DeriveBits(CreateEcdhDeriveParams(public_key), private_key,
length_bits, &derived_bytes);
ASSERT_EQ(expected_error, StatusToString(status));
if (status.IsError())
continue;

std::vector<uint8_t> expected_bytes =
GetBytesFromHexString(test, "derived_bytes");

EXPECT_EQ(CryptoData(expected_bytes), CryptoData(derived_bytes));
}
}

} // namespace

} // namespace webcrypto

} // namespace content
1 change: 1 addition & 0 deletions content/content_child.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@
'child/webcrypto/openssl/aes_key_openssl.h',
'child/webcrypto/openssl/aes_kw_openssl.cc',
'child/webcrypto/openssl/ec_key_openssl.cc',
'child/webcrypto/openssl/ecdh_openssl.cc',
'child/webcrypto/openssl/ecdsa_openssl.cc',
'child/webcrypto/openssl/hmac_openssl.cc',
'child/webcrypto/openssl/key_openssl.cc',
Expand Down
1 change: 1 addition & 0 deletions content/content_tests.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@
'child/webcrypto/test/aes_ctr_unittest.cc',
'child/webcrypto/test/aes_gcm_unittest.cc',
'child/webcrypto/test/aes_kw_unittest.cc',
'child/webcrypto/test/ecdh_unittest.cc',
'child/webcrypto/test/ecdsa_unittest.cc',
'child/webcrypto/test/hmac_unittest.cc',
'child/webcrypto/test/rsa_oaep_unittest.cc',
Expand Down
Loading

0 comments on commit ed48e81

Please sign in to comment.