diff options
author | juanlang@google.com <juanlang@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-07 12:34:54 +0000 |
---|---|---|
committer | juanlang@google.com <juanlang@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-07 12:34:54 +0000 |
commit | 41dd185b0585cae11ad4cbf19d54db69be7c93a2 (patch) | |
tree | 57198dfbea496db17581aa85a56da5eba33a50fe /net | |
parent | 649cb389289c40dd7a24f8964066d2256e3c8576 (diff) | |
download | chromium_src-41dd185b0585cae11ad4cbf19d54db69be7c93a2.zip chromium_src-41dd185b0585cae11ad4cbf19d54db69be7c93a2.tar.gz chromium_src-41dd185b0585cae11ad4cbf19d54db69be7c93a2.tar.bz2 |
Add a utility method to convert SPKI from DER to JWK, so far implemented only for EC P256v1 (which is used for TLS channel IDs.)
BUG=259097
Review URL: https://chromiumcodereview.appspot.com/21561003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@216163 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/cert/jwk_serializer.h | 30 | ||||
-rw-r--r-- | net/cert/jwk_serializer_nss.cc | 118 | ||||
-rw-r--r-- | net/cert/jwk_serializer_openssl.cc | 22 | ||||
-rw-r--r-- | net/cert/jwk_serializer_unittest.cc | 148 | ||||
-rw-r--r-- | net/net.gyp | 6 |
5 files changed, 324 insertions, 0 deletions
diff --git a/net/cert/jwk_serializer.h b/net/cert/jwk_serializer.h new file mode 100644 index 0000000..7a12a36 --- /dev/null +++ b/net/cert/jwk_serializer.h @@ -0,0 +1,30 @@ +// Copyright 2013 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. + +#ifndef NET_CERT_JWK_SERIALIZER_H_ +#define NET_CERT_JWK_SERIALIZER_H_ + +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" + +namespace base { +class DictionaryValue; +} + +namespace net { + +namespace JwkSerializer { + +// Converts a subject public key info from DER to JWK. +// See http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-13 for +// the output format. +NET_EXPORT_PRIVATE bool ConvertSpkiFromDerToJwk( + const base::StringPiece& spki_der, + base::DictionaryValue* public_key_jwk); + +} // namespace JwkSerializer + +} // namespace net + +#endif // NET_CERT_JWK_SERIALIZER_H_ diff --git a/net/cert/jwk_serializer_nss.cc b/net/cert/jwk_serializer_nss.cc new file mode 100644 index 0000000..0259c5c --- /dev/null +++ b/net/cert/jwk_serializer_nss.cc @@ -0,0 +1,118 @@ +// Copyright 2013 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 "net/cert/jwk_serializer.h" + +#include <cert.h> +#include <keyhi.h> +#include <nss.h> + +#include "base/base64.h" +#include "base/values.h" +#include "crypto/nss_util.h" +#include "crypto/scoped_nss_types.h" + +namespace net { + +namespace JwkSerializer { + +namespace { + +bool ConvertEcPrime256v1PublicKeyInfoToJwk( + CERTSubjectPublicKeyInfo* spki, + base::DictionaryValue* public_key_jwk) { + static const int kPrime256v1EncodingType = 4; + static const int kPrime256v1PublicKeyLength = 64; + // The public key value is encoded as 0x04 + 64 bytes of public key. + // NSS gives the length as the bit length. + if (spki->subjectPublicKey.len != (kPrime256v1PublicKeyLength + 1) * 8 || + spki->subjectPublicKey.data[0] != kPrime256v1EncodingType) + return false; + + public_key_jwk->SetString("alg", "EC"); + public_key_jwk->SetString("crv", "P-256"); + + base::StringPiece x( + reinterpret_cast<char*>(spki->subjectPublicKey.data + 1), + kPrime256v1PublicKeyLength / 2); + std::string x_b64; + base::Base64Encode(x, &x_b64); + public_key_jwk->SetString("x", x_b64); + + base::StringPiece y( + reinterpret_cast<char*>(spki->subjectPublicKey.data + 1 + + kPrime256v1PublicKeyLength / 2), + kPrime256v1PublicKeyLength / 2); + std::string y_b64; + base::Base64Encode(y, &y_b64); + public_key_jwk->SetString("y", y_b64); + return true; +} + +bool ConvertEcPublicKeyInfoToJwk( + CERTSubjectPublicKeyInfo* spki, + base::DictionaryValue* public_key_jwk) { + // 1.2.840.10045.3.1.7 + // (iso.member-body.us.ansi-x9-62.ellipticCurve.primeCurve.prime256v1) + // (This includes the DER-encoded type (OID) and length: parameters can be + // anything, so the DER type isn't implied, and NSS includes it.) + static const unsigned char kPrime256v1[] = { + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 + }; + if (spki->algorithm.parameters.len == sizeof(kPrime256v1) && + !memcmp(spki->algorithm.parameters.data, kPrime256v1, + sizeof(kPrime256v1))) { + return ConvertEcPrime256v1PublicKeyInfoToJwk(spki, public_key_jwk); + } + // TODO(juanlang): other curves + return false; +} + +typedef scoped_ptr_malloc< + CERTSubjectPublicKeyInfo, + crypto::NSSDestroyer<CERTSubjectPublicKeyInfo, + SECKEY_DestroySubjectPublicKeyInfo> > + ScopedCERTSubjectPublicKeyInfo; + +} // namespace + +bool ConvertSpkiFromDerToJwk( + const base::StringPiece& spki_der, + base::DictionaryValue* public_key_jwk) { + public_key_jwk->Clear(); + + crypto::EnsureNSSInit(); + + if (!NSS_IsInitialized()) + return false; + + SECItem sec_item; + sec_item.data = const_cast<unsigned char*>( + reinterpret_cast<const unsigned char*>(spki_der.data())); + sec_item.len = spki_der.size(); + ScopedCERTSubjectPublicKeyInfo spki( + SECKEY_DecodeDERSubjectPublicKeyInfo(&sec_item)); + if (!spki) + return false; + + // 1.2.840.10045.2 + // (iso.member-body.us.ansi-x9-62.id-ecPublicKey) + // (This omits the ASN.1 encoding of the type (OID) and length: the fact that + // this is an OID is already clear, and NSS omits it here.) + static const unsigned char kIdEcPublicKey[] = { + 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 + }; + bool rv = false; + if (spki->algorithm.algorithm.len == sizeof(kIdEcPublicKey) && + !memcmp(spki->algorithm.algorithm.data, kIdEcPublicKey, + sizeof(kIdEcPublicKey))) { + rv = ConvertEcPublicKeyInfoToJwk(spki.get(), public_key_jwk); + } + // TODO(juanlang): other algorithms + return rv; +} + +} // namespace JwkSerializer + +} // namespace net diff --git a/net/cert/jwk_serializer_openssl.cc b/net/cert/jwk_serializer_openssl.cc new file mode 100644 index 0000000..ef15b4b --- /dev/null +++ b/net/cert/jwk_serializer_openssl.cc @@ -0,0 +1,22 @@ +// Copyright 2013 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/logging.h" +#include "net/cert/jwk_serializer.h" + +namespace net { + +namespace JwkSerializer { + +bool ConvertSpkiFromDerToJwk( + const base::StringPiece& spki_der, + base::DictionaryValue* public_key_jwk) { + // TODO(juanlang): implement + NOTIMPLEMENTED(); + return false; +} + +} // namespace JwkSerializer + +} // namespace net diff --git a/net/cert/jwk_serializer_unittest.cc b/net/cert/jwk_serializer_unittest.cc new file mode 100644 index 0000000..37b8002 --- /dev/null +++ b/net/cert/jwk_serializer_unittest.cc @@ -0,0 +1,148 @@ +// Copyright 2013 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 "net/cert/jwk_serializer.h" + +#include "base/base64.h" +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +// This is the ASN.1 prefix for a P-256 public key. Specifically it's: +// SEQUENCE +// SEQUENCE +// OID id-ecPublicKey +// OID prime256v1 +// BIT STRING, length 66, 0 trailing bits: 0x04 +// +// The 0x04 in the BIT STRING is the prefix for an uncompressed, X9.62 +// public key. Following that are the two field elements as 32-byte, +// big-endian numbers, as required by the Channel ID. +static const unsigned char kP256SpkiPrefix[] = { + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, + 0x42, 0x00, 0x04 +}; +static const unsigned int kEcPointSize = 32U; + +// This is a valid P-256 public key. +static const unsigned char kSpkiEc[] = { + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, + 0x42, 0x00, 0x04, + 0x29, 0x5d, 0x6e, 0xfe, 0x33, 0x77, 0x26, 0xea, + 0x5b, 0xa4, 0xe6, 0x1b, 0x34, 0x6e, 0x7b, 0xa0, + 0xa3, 0x8f, 0x33, 0x49, 0xa0, 0x9c, 0xae, 0x98, + 0xbd, 0x46, 0x0d, 0xf6, 0xd4, 0x5a, 0xdc, 0x8a, + 0x1f, 0x8a, 0xb2, 0x20, 0x51, 0xb7, 0xd2, 0x87, + 0x0d, 0x53, 0x7e, 0x5d, 0x94, 0xa3, 0xe0, 0x34, + 0x16, 0xa1, 0xcc, 0x10, 0x48, 0xcd, 0x70, 0x9c, + 0x05, 0xd3, 0xd2, 0xca, 0xdf, 0x44, 0x2f, 0xf4 +}; + +// This is a P-256 public key with 0 X and Y values. +static const unsigned char kSpkiEcWithZeroXY[] = { + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, + 0x42, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +#if !defined(USE_OPENSSL) + +TEST(JwkSerializerNSSTest, ConvertSpkiFromDerToJwkEc) { + base::StringPiece spki; + base::DictionaryValue public_key_jwk; + + EXPECT_FALSE(JwkSerializer::ConvertSpkiFromDerToJwk(spki, &public_key_jwk)); + EXPECT_TRUE(public_key_jwk.empty()); + + // Test the result of a "normal" point on this curve. + spki.set(reinterpret_cast<const char*>(kSpkiEc), sizeof(kSpkiEc)); + EXPECT_TRUE(JwkSerializer::ConvertSpkiFromDerToJwk(spki, &public_key_jwk)); + + std::string string_value; + EXPECT_TRUE(public_key_jwk.GetString("alg", &string_value)); + EXPECT_STREQ("EC", string_value.c_str()); + EXPECT_TRUE(public_key_jwk.GetString("crv", &string_value)); + EXPECT_STREQ("P-256", string_value.c_str()); + + EXPECT_TRUE(public_key_jwk.GetString("x", &string_value)); + std::string decoded_coordinate; + EXPECT_TRUE(base::Base64Decode(string_value, &decoded_coordinate)); + EXPECT_EQ(kEcPointSize, decoded_coordinate.size()); + EXPECT_EQ(0, + memcmp(decoded_coordinate.data(), + kSpkiEc + sizeof(kP256SpkiPrefix), + kEcPointSize)); + + EXPECT_TRUE(public_key_jwk.GetString("y", &string_value)); + EXPECT_TRUE(base::Base64Decode(string_value, &decoded_coordinate)); + EXPECT_EQ(kEcPointSize, decoded_coordinate.size()); + EXPECT_EQ(0, + memcmp(decoded_coordinate.data(), + kSpkiEc + sizeof(kP256SpkiPrefix) + kEcPointSize, + kEcPointSize)); + + // Test the result of a corner case: leading 0s in the x, y coordinates are + // not trimmed, but the point is fixed-length encoded. + spki.set(reinterpret_cast<const char*>(kSpkiEcWithZeroXY), + sizeof(kSpkiEcWithZeroXY)); + EXPECT_TRUE(JwkSerializer::ConvertSpkiFromDerToJwk(spki, &public_key_jwk)); + + EXPECT_TRUE(public_key_jwk.GetString("alg", &string_value)); + EXPECT_STREQ("EC", string_value.c_str()); + EXPECT_TRUE(public_key_jwk.GetString("crv", &string_value)); + EXPECT_STREQ("P-256", string_value.c_str()); + + EXPECT_TRUE(public_key_jwk.GetString("x", &string_value)); + EXPECT_TRUE(base::Base64Decode(string_value, &decoded_coordinate)); + EXPECT_EQ(kEcPointSize, decoded_coordinate.size()); + EXPECT_EQ(0, + memcmp(decoded_coordinate.data(), + kSpkiEcWithZeroXY + sizeof(kP256SpkiPrefix), + kEcPointSize)); + + EXPECT_TRUE(public_key_jwk.GetString("y", &string_value)); + EXPECT_TRUE(base::Base64Decode(string_value, &decoded_coordinate)); + EXPECT_EQ(kEcPointSize, decoded_coordinate.size()); + EXPECT_EQ(0, + memcmp(decoded_coordinate.data(), + kSpkiEcWithZeroXY + sizeof(kP256SpkiPrefix) + kEcPointSize, + kEcPointSize)); +} + +#else + +// For OpenSSL, JwkSerializer::ConvertSpkiFromDerToJwk() is not yet implemented +// and should return false. This unit test ensures that a stub implementation +// is present. +TEST(JwkSerializerOpenSSLTest, ConvertSpkiFromDerToJwkNotImplemented) { + base::StringPiece spki; + base::DictionaryValue public_key_jwk; + + // The empty SPKI is trivially non-convertible... + EXPECT_FALSE(JwkSerializer::ConvertSpkiFromDerToJwk(spki, &public_key_jwk)); + EXPECT_TRUE(public_key_jwk.empty()); + // but even a valid SPKI is non-convertible via the stub OpenSSL + // implementation. + spki.set(reinterpret_cast<const char*>(kSpkiEc), sizeof(kSpkiEc)); + EXPECT_FALSE(JwkSerializer::ConvertSpkiFromDerToJwk(spki, &public_key_jwk)); + EXPECT_TRUE(public_key_jwk.empty()); +} + +#endif // !defined(USE_OPENSSL) + +} // namespace net diff --git a/net/net.gyp b/net/net.gyp index a19cdac..e01f41b 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -271,6 +271,9 @@ 'cert/crl_set.h', 'cert/ev_root_ca_metadata.cc', 'cert/ev_root_ca_metadata.h', + 'cert/jwk_serializer_nss.cc', + 'cert/jwk_serializer_openssl.cc', + 'cert/jwk_serializer.h', 'cert/multi_threaded_cert_verifier.cc', 'cert/multi_threaded_cert_verifier.h', 'cert/nss_cert_database.cc', @@ -1199,6 +1202,7 @@ 'cert/cert_database_nss.cc', 'cert/cert_verify_proc_nss.cc', 'cert/cert_verify_proc_nss.h', + 'cert/jwk_serializer_nss.cc', 'cert/nss_cert_database.cc', 'cert/nss_cert_database.h', 'cert/test_root_certs_nss.cc', @@ -1236,6 +1240,7 @@ 'cert/cert_database_openssl.cc', 'cert/cert_verify_proc_openssl.cc', 'cert/cert_verify_proc_openssl.h', + 'cert/jwk_serializer_openssl.cc', 'cert/test_root_certs_openssl.cc', 'cert/x509_certificate_openssl.cc', 'cert/x509_util_openssl.cc', @@ -1524,6 +1529,7 @@ 'cert/cert_verify_proc_unittest.cc', 'cert/crl_set_unittest.cc', 'cert/ev_root_ca_metadata_unittest.cc', + 'cert/jwk_serializer_unittest.cc', 'cert/multi_threaded_cert_verifier_unittest.cc', 'cert/nss_cert_database_unittest.cc', 'cert/pem_tokenizer_unittest.cc', |