Index: components/gcm_driver/crypto/gcm_message_cryptographer_unittest.cc |
diff --git a/components/gcm_driver/crypto/gcm_message_cryptographer_unittest.cc b/components/gcm_driver/crypto/gcm_message_cryptographer_unittest.cc |
index a1a0280e013012f42ced3b5b2bc2df00e1d6d366..aadd2b3b185d812a102275a72c68c0b95d3ed546 100644 |
--- a/components/gcm_driver/crypto/gcm_message_cryptographer_unittest.cc |
+++ b/components/gcm_driver/crypto/gcm_message_cryptographer_unittest.cc |
@@ -5,12 +5,20 @@ |
#include "components/gcm_driver/crypto/gcm_message_cryptographer.h" |
#include "base/base64.h" |
+#include "base/base64url.h" |
#include "base/memory/scoped_ptr.h" |
#include "base/strings/string_util.h" |
+#include "components/gcm_driver/crypto/p256_key_util.h" |
#include "crypto/random.h" |
#include "crypto/symmetric_key.h" |
#include "testing/gtest/include/gtest/gtest.h" |
+#include "crypto/ec_private_key.h" |
+#include "crypto/scoped_openssl_types.h" |
+#include <openssl/ec.h> |
+#include <openssl/pkcs12.h> |
+#include <openssl/x509.h> |
+ |
namespace gcm { |
namespace { |
@@ -109,6 +117,169 @@ const TestVector kDecryptionTestVectors[] = { |
} |
}; |
+// ----------------------------------------------------------------------------- |
+ |
+using ScopedPKCS8_PRIV_KEY_INFO = |
+ crypto::ScopedOpenSSL<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>; |
+using ScopedX509_SIG = crypto::ScopedOpenSSL<X509_SIG, X509_SIG_free>; |
+ |
+// Takes a private key in X.509 SubjectPublicKeyInfo block format and converts |
+// it to an ASN.1-encoded PKCS #8 EncryptedPrivateKeyInfo, as is accepted by |
+// the ECPrivateKey infrastructure. |
+std::string ConvertRawPrivateKeyToPKCS8EncryptedPrivateKeyInfo( |
+ const base::StringPiece& raw_private_key, |
+ const base::StringPiece& public_key) { |
+ const uint8_t* raw_private_key_ptr = |
+ reinterpret_cast<const uint8_t*>(raw_private_key.data()); |
+ const uint8_t* public_key_ptr = |
+ reinterpret_cast<const uint8_t*>(public_key.data()); |
+ |
+ crypto::ScopedEC_GROUP p256(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); |
+ crypto::ScopedEC_KEY key(EC_KEY_new()); |
+ DCHECK(p256 && key); |
+ |
+ // Import the |raw_private_key| to the |key| through a BIGNUM. |
+ crypto::ScopedBIGNUM bignum( |
+ BN_bin2bn(raw_private_key_ptr, raw_private_key.size(), nullptr)); |
+ if (!bignum) { |
+ LOG(ERROR) << "Unable to initialize the ScopedBIGNUM"; |
+ return std::string(); |
+ } |
+ |
+ if (!EC_KEY_set_private_key(key.get(), bignum.get())) { |
+ LOG(ERROR) << "Unable to set the private key"; |
+ return std::string(); |
+ } |
+ |
+ // Import the |public_key| to the |key| by assembling an EC point. Because the |
+ // |public_key| is an uncompressed EC point, we can access the data directly. |
+ crypto::ScopedEC_POINT point(EC_POINT_new(p256.get())); |
+ crypto::ScopedBIGNUM x(BN_new()), y(BN_new()); |
+ DCHECK(point && x && y); |
+ |
+ if (BN_bin2bn(public_key_ptr + 1 + 0, 32, x.get()) == nullptr || |
+ BN_bin2bn(public_key_ptr + 1 + 32, 32, y.get()) == nullptr) { |
+ LOG(ERROR) << "Unable to create BIGNUMs for the public point's x/y"; |
+ return std::string(); |
+ } |
+ |
+ if (!EC_POINT_set_affine_coordinates_GFp(p256.get(), point.get(), x.get(), |
+ y.get(), nullptr)) { |
+ LOG(ERROR) << "Unable to set the coordinates of the public point"; |
+ return std::string(); |
+ } |
+ |
+ if (!EC_KEY_set_group(key.get(), p256.get()) || |
+ !EC_KEY_set_public_key(key.get(), point.get())) { |
+ LOG(ERROR) << "Unable to set the public key"; |
+ return std::string(); |
+ } |
+ |
+ // Verify that the created EC_KEY is valid. Crashes might occur if it's not. |
+ if (!EC_KEY_check_key(key.get())) { |
+ LOG(ERROR) << "Unable to verify validity of the key"; |
+ return std::string(); |
+ } |
+ |
+ // Create a EVP_PKEY from the EC_KEY pair that was created. |
+ crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new()); |
+ if (!pkey || !EVP_PKEY_set1_EC_KEY(pkey.get(), key.get())) { |
+ LOG(ERROR) << "Unable to create the private key"; |
+ return std::string(); |
+ } |
+ |
+ std::vector<uint8_t> encrypted_private_key_buf; |
+ scoped_ptr<crypto::ECPrivateKey> ec_private_key( |
+ crypto::ECPrivateKey::Create()); |
+ ec_private_key->key_ = pkey.release(); |
+ |
+ // Export the EVP_PKEY as an ASN.1-encoded PKCS #8 EncryptedPrivateKeyInfo |
+ // block, as expected by the ECPrivateKey import routines. |
+ if (!ec_private_key->ExportEncryptedPrivateKey("" /* password */, |
+ 1 /* iterations */, |
+ &encrypted_private_key_buf)) { |
+ LOG(ERROR) << "Unable to export the encrypted private key"; |
+ return std::string(); |
+ } |
+ |
+ return std::string(reinterpret_cast<char*>(encrypted_private_key_buf.data()), |
+ encrypted_private_key_buf.size()); |
+ |
+/** |
+ std::string raw_public_key, raw_public_key_rep; |
+ DCHECK(ec_private_key->ExportRawPublicKey(&raw_public_key)); |
+ |
+ raw_public_key_rep = "\04" + raw_public_key; |
+ |
+ LOG(INFO) << "r_p_k: (" << raw_public_key_rep.size() << ")"; |
+ LOG(INFO) << "p_k: (" << raw_public_key_rep.size() << ")"; |
+ |
+ LOG(INFO) << (raw_public_key_rep == public_key); |
+ |
+ return std::string(); |
+ |
+ |
+ int public_key_len = i2d_PublicKey(pkey.get(), nullptr); |
+ if (public_key_len != 65) |
+ return std::string(); |
+ |
+ uint8_t public_key_buffer[public_key_len]; |
+ |
+ uint8_t* public_key_buffer_ptr = public_key_buffer; |
+ if (i2d_PublicKey(pkey.get(), &public_key_buffer_ptr) != public_key_len) |
+ return std::string(); |
+ |
+ std::string regenerated_public_key( |
+ reinterpret_cast<char*>(public_key_buffer), public_key_len); |
+ |
+ LOG(INFO) << (regenerated_public_key == public_key); |
+ |
+ ScopedPKCS8_PRIV_KEY_INFO pkcs8(EVP_PKEY2PKCS8(pkey.get())); |
+ if (!pkcs8) { |
+ LOG(ERROR) << "Unable to initialize the PKCS8_PRIV_KEY_INFO"; |
+ return std::string(); |
+ } |
+ |
+ ScopedX509_SIG encrypted(PKCS8_encrypt_pbe( |
+ NID_pbe_WithSHA1And3_Key_TripleDES_CBC, |
+ nullptr, |
+ nullptr, |
+ 0, |
+ nullptr, |
+ 0, |
+ 1, |
+ pkcs8.get())); |
+ if (!encrypted) { |
+ LOG(ERROR) << "Unable to encrypt the PKCS8_PRIV_KEY_INFO"; |
+ return std::string(); |
+ } |
+ |
+ crypto::ScopedBIO bio(BIO_new(BIO_s_mem())); |
+ if (!i2d_PKCS8_bio(bio.get(), encrypted.get())) { |
+ LOG(ERROR) << "Unable to convert PKCS8 to a PKI ScopedBIO (#1)"; |
+ return std::string(); |
+ } |
+ |
+ char* bio_data = nullptr; |
+ long bio_length = BIO_get_mem_data(bio.get(), &bio_data); |
+ if (!bio_data || bio_length < 0) { |
+ LOG(ERROR) << "Unable to convert PKCS8 to a PKI ScopedBIO (#2)"; |
+ return std::string(); |
+ } |
+ |
+ std::string key_string = std::string(bio_data, bio_data + bio_length); |
+ |
+ std::string encoded_string; |
+ base::Base64UrlEncode(key_string, base::Base64UrlEncodePolicy::OMIT_PADDING, |
+ &encoded_string); |
+ LOG(INFO) << "Encrypted key: [" << encoded_string << "]"; |
+ |
+ // TODO: Get a X.509 SubjectPublicKeyInfo |
+ |
+ return key_string; |
+**/ |
+} |
+ |
} // namespace |
class GCMMessageCryptographerTest : public ::testing::Test { |
@@ -119,6 +290,17 @@ class GCMMessageCryptographerTest : public ::testing::Test { |
kKeySizeBits)); |
ASSERT_TRUE(random_key->GetRawKey(&key_)); |
+ |
+ std::string local_public_key, local_public_key_x509, local_private_key; |
+ std::string peer_public_key, peer_public_key_x509, peer_private_key; |
+ |
+ ASSERT_TRUE(CreateP256KeyPair(&local_private_key, &local_public_key_x509, |
+ &local_public_key)); |
+ ASSERT_TRUE(CreateP256KeyPair(&peer_private_key, &peer_public_key_x509, |
+ &peer_public_key)); |
+ |
+ cryptographer_.reset( |
+ new GCMMessageCryptographer(local_public_key, peer_public_key)); |
} |
protected: |
@@ -133,12 +315,12 @@ class GCMMessageCryptographerTest : public ::testing::Test { |
return salt; |
} |
- GCMMessageCryptographer* cryptographer() { return &cryptographer_; } |
+ GCMMessageCryptographer* cryptographer() { return cryptographer_.get(); } |
base::StringPiece key() const { return key_; } |
private: |
- GCMMessageCryptographer cryptographer_; |
+ scoped_ptr<GCMMessageCryptographer> cryptographer_; |
std::string key_; |
}; |
@@ -298,4 +480,87 @@ TEST_F(GCMMessageCryptographerTest, DecryptionTestVectors) { |
} |
} |
+template <typename I> std::string n2hexstr(I w, size_t hex_len = sizeof(I)<<1) { |
+ static const char* digits = "0123456789ABCDEF"; |
+ std::string rc(hex_len,'0'); |
+ for (size_t i=0, j=(hex_len-1)*4 ; i<hex_len; ++i,j-=4) |
+ rc[i] = digits[(w>>j) & 0x0f]; |
+ return rc; |
+} |
+ |
+TEST_F(GCMMessageCryptographerTest, ReferenceTest) { |
+ // This test verifies Chrome's implementation against the reference vector |
+ // given in the draft-thomson-http-encryption examples. |
+ const char kSalt[] = "Qg61ZJRva_XBE9IEUelU3A"; |
+ const char kPayload[] = "G6j_sfKg0qebO62yXpTCayN2KV24QitNiTvLgcFiEj0"; |
+ |
+ const char kLocalPrivateKey[] = "9FWl15_QUQAWDaD3k3l50ZBZQJ4au27F1V4F0uLSD_M"; |
+ const char kLocalPublicKey[] = |
+ "BCEkBjzL8Z3C-oi2Q7oE5t2Np-p7osjGLg93qUP0wvqRT21EEWyf0cQDQcakQMqz4hQKYOQ3" |
+ "il2nNZct4HgAUQU"; |
+ |
+ // Note: X.509 SubjectPublicKeyInfo representation of |kLocalPublicKey|. |
+ const char kLocalPublicKeyX509[] = ""; // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
+ |
+ const char kPeerPrivateKey[] = "vG7TmzUX9NfVR4XUGBkLAFu8iDyQe-q_165JkkN0Vlw"; |
+ const char kPeerPublicKey[] = |
+ "BDgpRKok2GZZDmS4r63vbJSUtcQx4Fq1V58-6-3NbZzSTlZsQiCEDTQy3CZ0ZMsqeqsEb7qW" |
+ "2blQHA4S48fynTk"; |
+ |
+ const char kExpectedOutput[] = "I am the walrus"; |
+ |
+ std::string salt; |
+ ASSERT_TRUE(base::Base64UrlDecode( |
+ kSalt, base::Base64UrlDecodePolicy::IGNORE_PADDING, &salt)); |
+ |
+ std::string payload; |
+ ASSERT_TRUE(base::Base64UrlDecode( |
+ kPayload, base::Base64UrlDecodePolicy::IGNORE_PADDING, &payload)); |
+ |
+ std::string local_private_key, local_public_key, local_public_key_x509; |
+ ASSERT_TRUE(base::Base64UrlDecode(kLocalPrivateKey, |
+ base::Base64UrlDecodePolicy::IGNORE_PADDING, |
+ &local_private_key)); |
+ ASSERT_TRUE(base::Base64UrlDecode(kLocalPublicKey, |
+ base::Base64UrlDecodePolicy::IGNORE_PADDING, |
+ &local_public_key)); |
+ ASSERT_TRUE(base::Base64UrlDecode(kLocalPublicKeyX509, |
+ base::Base64UrlDecodePolicy::IGNORE_PADDING, |
+ &local_public_key_x509)); |
+ |
+ std::string peer_private_key, peer_public_key; |
+ ASSERT_TRUE(base::Base64UrlDecode(kPeerPrivateKey, |
+ base::Base64UrlDecodePolicy::IGNORE_PADDING, |
+ &peer_private_key)); |
+ ASSERT_TRUE(base::Base64UrlDecode(kPeerPublicKey, |
+ base::Base64UrlDecodePolicy::IGNORE_PADDING, |
+ &peer_public_key)); |
+ |
+ std::string local_output, peer_output; |
+ for (size_t i = 0; i < local_public_key.size(); ++i) |
+ local_output += "0x" + n2hexstr(local_public_key[i]) + " "; |
+ |
+ for (size_t i = 0; i < peer_public_key.size(); ++i) |
+ peer_output += " " + std::to_string(static_cast<int>(peer_public_key[i])); |
+ |
+ LOG(INFO) << "Local private: (" << local_public_key.size() << ") [" << local_output << "]"; |
+ //LOG(INFO) << "Peer public: (" << peer_public_key.size() << ") [" << peer_output << "]"; |
+ |
+ std::string encrypted_private_key = |
+ ConvertRawPrivateKeyToPKCS8EncryptedPrivateKeyInfo(local_private_key, |
+ local_public_key); |
+ |
+ std::string shared_secret; |
+ ASSERT_TRUE(ComputeSharedP256Secret(encrypted_private_key, local_public_key_x509, |
+ peer_public_key, &shared_secret)); |
+ |
+ std::string plaintext; |
+ |
+ GCMMessageCryptographer cryptographer(local_public_key, peer_public_key); |
+ ASSERT_TRUE(cryptographer.Decrypt(payload, shared_secret, salt, 4096, |
+ &plaintext)); |
+ |
+ EXPECT_EQ(kExpectedOutput, plaintext); |
+} |
+ |
} // namespace gcm |