diff options
author | ryanmyers@chromium.org <ryanmyers@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-07 08:32:30 +0000 |
---|---|---|
committer | ryanmyers@chromium.org <ryanmyers@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-07 08:32:30 +0000 |
commit | e9c3662f70af48bf571bc515b768ddb31dfe539a (patch) | |
tree | 66f417933255fc19d9bbd3c6995c62585f3815eb /google_apis | |
parent | fda3b119daf1a351d53a2e5eb01ff3cb78d00cef (diff) | |
download | chromium_src-e9c3662f70af48bf571bc515b768ddb31dfe539a.zip chromium_src-e9c3662f70af48bf571bc515b768ddb31dfe539a.tar.gz chromium_src-e9c3662f70af48bf571bc515b768ddb31dfe539a.tar.bz2 |
Per discussion, implement the Omaha Client Update Protocol (CUP) in src/crypto.
Since this will not be used on Android or iOS, only the NSS implementation is
complete; OpenSSL is stubbed out.
Review URL: https://chromiumcodereview.appspot.com/15793005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@204755 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'google_apis')
-rw-r--r-- | google_apis/cup/OWNERS | 1 | ||||
-rw-r--r-- | google_apis/cup/client_update_protocol.cc | 305 | ||||
-rw-r--r-- | google_apis/cup/client_update_protocol.h | 138 | ||||
-rw-r--r-- | google_apis/cup/client_update_protocol_nss.cc | 81 | ||||
-rw-r--r-- | google_apis/cup/client_update_protocol_openssl.cc | 27 | ||||
-rw-r--r-- | google_apis/cup/client_update_protocol_unittest.cc | 165 | ||||
-rw-r--r-- | google_apis/google_apis.gyp | 21 |
7 files changed, 738 insertions, 0 deletions
diff --git a/google_apis/cup/OWNERS b/google_apis/cup/OWNERS new file mode 100644 index 0000000..00f9726 --- /dev/null +++ b/google_apis/cup/OWNERS @@ -0,0 +1 @@ +ryanmyers@chromium.org diff --git a/google_apis/cup/client_update_protocol.cc b/google_apis/cup/client_update_protocol.cc new file mode 100644 index 0000000..23323af --- /dev/null +++ b/google_apis/cup/client_update_protocol.cc @@ -0,0 +1,305 @@ +// 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 "google_apis/cup/client_update_protocol.h" + +#include "base/base64.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/sha1.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "crypto/hmac.h" +#include "crypto/random.h" + +namespace { + +base::StringPiece ByteVectorToSP(const std::vector<uint8>& vec) { + if (vec.empty()) + return base::StringPiece(); + + return base::StringPiece(reinterpret_cast<const char*>(&vec[0]), vec.size()); +} + +// This class needs to implement the same hashing and signing functions as the +// Google Update server; for now, this is SHA-1 and HMAC-SHA1, but this may +// change to SHA-256 in the near future. For this reason, all primitives are +// wrapped. The name "SymSign" is used to mirror the CUP specification. +size_t HashDigestSize() { + return base::kSHA1Length; +} + +std::vector<uint8> Hash(const std::vector<uint8>& data) { + std::vector<uint8> result(HashDigestSize()); + base::SHA1HashBytes(data.empty() ? NULL : &data[0], + data.size(), + &result[0]); + return result; +} + +std::vector<uint8> Hash(const base::StringPiece& sdata) { + std::vector<uint8> result(HashDigestSize()); + base::SHA1HashBytes(sdata.empty() ? + NULL : + reinterpret_cast<const unsigned char*>(sdata.data()), + sdata.length(), + &result[0]); + return result; +} + +std::vector<uint8> SymConcat(uint8 id, + const std::vector<uint8>* h1, + const std::vector<uint8>* h2, + const std::vector<uint8>* h3) { + std::vector<uint8> result; + result.push_back(id); + const std::vector<uint8>* args[] = { h1, h2, h3 }; + for (size_t i = 0; i != arraysize(args); ++i) { + if (args[i]) { + DCHECK_EQ(args[i]->size(), HashDigestSize()); + result.insert(result.end(), args[i]->begin(), args[i]->end()); + } + } + + return result; +} + +std::vector<uint8> SymSign(const std::vector<uint8>& key, + const std::vector<uint8>& hashes) { + DCHECK(!key.empty()); + DCHECK(!hashes.empty()); + + crypto::HMAC hmac(crypto::HMAC::SHA1); + if (!hmac.Init(&key[0], key.size())) + return std::vector<uint8>(); + + std::vector<uint8> result(hmac.DigestLength()); + if (!hmac.Sign(ByteVectorToSP(hashes), &result[0], result.size())) + return std::vector<uint8>(); + + return result; +} + +bool SymSignVerify(const std::vector<uint8>& key, + const std::vector<uint8>& hashes, + const std::vector<uint8>& server_proof) { + DCHECK(!key.empty()); + DCHECK(!hashes.empty()); + DCHECK(!server_proof.empty()); + + crypto::HMAC hmac(crypto::HMAC::SHA1); + if (!hmac.Init(&key[0], key.size())) + return false; + + return hmac.Verify(ByteVectorToSP(hashes), ByteVectorToSP(server_proof)); +} + +// RsaPad() is implemented as described in the CUP spec. It is NOT a general +// purpose padding algorithm. +std::vector<uint8> RsaPad(size_t rsa_key_size, + const std::vector<uint8>& entropy) { + DCHECK_GE(rsa_key_size, HashDigestSize()); + + // The result gets padded with zeros if the result size is greater than + // the size of the buffer provided by the caller. + std::vector<uint8> result(entropy); + result.resize(rsa_key_size - HashDigestSize()); + + // For use with RSA, the input needs to be smaller than the RSA modulus, + // which has always the msb set. + result[0] &= 127; // Reset msb + result[0] |= 64; // Set second highest bit. + + std::vector<uint8> digest = Hash(result); + result.insert(result.end(), digest.begin(), digest.end()); + DCHECK_EQ(result.size(), rsa_key_size); + return result; +} + +// CUP passes the versioned secret in the query portion of the URL for the +// update check service -- and that means that a URL-safe variant of Base64 is +// needed. Call the standard Base64 encoder/decoder and then apply fixups. +std::string UrlSafeB64Encode(const std::vector<uint8>& data) { + std::string result; + if (!base::Base64Encode(ByteVectorToSP(data), &result)) + return std::string(); + + // Do an tr|+/|-_| on the output, and strip any '=' padding. + for (std::string::iterator it = result.begin(); it != result.end(); ++it) { + switch (*it) { + case '+': + *it = '-'; + break; + case '/': + *it = '_'; + break; + default: + break; + } + } + TrimString(result, "=", &result); + + return result; +} + +std::vector<uint8> UrlSafeB64Decode(const base::StringPiece& input) { + std::string unsafe(input.begin(), input.end()); + for (std::string::iterator it = unsafe.begin(); it != unsafe.end(); ++it) { + switch (*it) { + case '-': + *it = '+'; + break; + case '_': + *it = '/'; + break; + default: + break; + } + } + if (unsafe.length() % 4) + unsafe.append(4 - (unsafe.length() % 4), '='); + + std::string decoded; + if (!base::Base64Decode(unsafe, &decoded)) + return std::vector<uint8>(); + + return std::vector<uint8>(decoded.begin(), decoded.end()); +} + +} // end namespace + +ClientUpdateProtocol::ClientUpdateProtocol(int key_version) + : pub_key_version_(key_version) { +} + +scoped_ptr<ClientUpdateProtocol> ClientUpdateProtocol::Create( + int key_version, + const base::StringPiece& public_key) { + DCHECK_GT(key_version, 0); + DCHECK(!public_key.empty()); + + scoped_ptr<ClientUpdateProtocol> result( + new ClientUpdateProtocol(key_version)); + if (!result) + return scoped_ptr<ClientUpdateProtocol>(); + + if (!result->LoadPublicKey(public_key)) + return scoped_ptr<ClientUpdateProtocol>(); + + if (!result->BuildRandomSharedKey()) + return scoped_ptr<ClientUpdateProtocol>(); + + return result.Pass(); +} + +std::string ClientUpdateProtocol::GetVersionedSecret() const { + return base::StringPrintf("%d:%s", + pub_key_version_, + UrlSafeB64Encode(encrypted_key_source_).c_str()); +} + +bool ClientUpdateProtocol::SignRequest(const base::StringPiece& url, + const base::StringPiece& request_body, + std::string* client_proof) { + DCHECK(!encrypted_key_source_.empty()); + DCHECK(!url.empty()); + DCHECK(!request_body.empty()); + DCHECK(client_proof); + + // Compute the challenge hash: + // hw = HASH(HASH(v|w)|HASH(request_url)|HASH(body)). + // Keep the challenge hash for later to validate the server's response. + std::vector<uint8> internal_hashes; + + std::vector<uint8> h; + h = Hash(GetVersionedSecret()); + internal_hashes.insert(internal_hashes.end(), h.begin(), h.end()); + h = Hash(url); + internal_hashes.insert(internal_hashes.end(), h.begin(), h.end()); + h = Hash(request_body); + internal_hashes.insert(internal_hashes.end(), h.begin(), h.end()); + DCHECK_EQ(internal_hashes.size(), 3 * HashDigestSize()); + + client_challenge_hash_ = Hash(internal_hashes); + + // Sign the challenge hash (hw) using the shared key (sk) to produce the + // client proof (cp). + std::vector<uint8> raw_client_proof = + SymSign(shared_key_, SymConcat(3, &client_challenge_hash_, NULL, NULL)); + if (raw_client_proof.empty()) { + client_challenge_hash_.clear(); + return false; + } + + *client_proof = UrlSafeB64Encode(raw_client_proof); + return true; +} + +bool ClientUpdateProtocol::ValidateResponse( + const base::StringPiece& response_body, + const base::StringPiece& server_cookie, + const base::StringPiece& server_proof) { + DCHECK(!client_challenge_hash_.empty()); + + if (response_body.empty() || server_cookie.empty() || server_proof.empty()) + return false; + + // Decode the server proof from URL-safe Base64 to a binary HMAC for the + // response. + std::vector<uint8> sp_decoded = UrlSafeB64Decode(server_proof); + if (sp_decoded.empty()) + return false; + + // If the request was received by the server, the server will use its + // private key to decrypt |w_|, yielding the original contents of |r_|. + // The server can then recreate |sk_|, compute |hw_|, and SymSign(3|hw) + // to ensure that the cp matches the contents. It will then use |sk_| + // to sign its response, producing the server proof |sp|. + std::vector<uint8> hm = Hash(response_body); + std::vector<uint8> hc = Hash(server_cookie); + return SymSignVerify(shared_key_, + SymConcat(1, &client_challenge_hash_, &hm, &hc), + sp_decoded); +} + +bool ClientUpdateProtocol::BuildRandomSharedKey() { + DCHECK_GE(PublicKeyLength(), HashDigestSize()); + + // Start by generating some random bytes that are suitable to be encrypted; + // this will be the source of the shared HMAC key that client and server use. + // (CUP specification calls this "r".) + std::vector<uint8> key_source; + std::vector<uint8> entropy(PublicKeyLength() - HashDigestSize()); + crypto::RandBytes(&entropy[0], entropy.size()); + key_source = RsaPad(PublicKeyLength(), entropy); + + return DeriveSharedKey(key_source); +} + +bool ClientUpdateProtocol::SetSharedKeyForTesting( + const base::StringPiece& key_source) { + DCHECK_EQ(key_source.length(), PublicKeyLength()); + + return DeriveSharedKey(std::vector<uint8>(key_source.begin(), + key_source.end())); +} + +bool ClientUpdateProtocol::DeriveSharedKey(const std::vector<uint8>& source) { + DCHECK(!source.empty()); + DCHECK_GE(source.size(), HashDigestSize()); + DCHECK_EQ(source.size(), PublicKeyLength()); + + // Hash the key source (r) to generate a new shared HMAC key (sk'). + shared_key_ = Hash(source); + + // Encrypt the key source (r) using the public key (pk[v]) to generate the + // encrypted key source (w). + if (!EncryptKeySource(source)) + return false; + if (encrypted_key_source_.size() != PublicKeyLength()) + return false; + + return true; +} + diff --git a/google_apis/cup/client_update_protocol.h b/google_apis/cup/client_update_protocol.h new file mode 100644 index 0000000..786c01f --- /dev/null +++ b/google_apis/cup/client_update_protocol.h @@ -0,0 +1,138 @@ +// 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 GOOGLE_APIS_CUP_CLIENT_UPDATE_PROTOCOL_H_ +#define GOOGLE_APIS_CUP_CLIENT_UPDATE_PROTOCOL_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" + +// Forward declare types for NSS. +#if defined(USE_NSS) || defined(OS_WIN) || defined(OS_MACOSX) +typedef struct SECKEYPublicKeyStr SECKEYPublicKey; +#endif + +// Client Update Protocol, or CUP, is used by Google Update (Omaha) servers to +// ensure freshness and authenticity of update checks over HTTP, without the +// overhead of HTTPS -- namely, no PKI, no guarantee of privacy, and no request +// replay protection (since update checks are idempotent). +// +// http://omaha.googlecode.com/svn/wiki/cup.html +// +// Each ClientUpdateProtocol represents a single update check in flight -- a +// call to SignRequest() generates internal state used by ValidateResponse(). +// +// This implementation does not persist client proofs by design. +class ClientUpdateProtocol { + public: + ~ClientUpdateProtocol(); + + // Initializes this instance of CUP with a versioned public key. |key_version| + // must be non-negative. |public_key| is expected to be a DER-encoded ASN.1 + // Subject Public Key Info. Returns a NULL pointer on failure. + static scoped_ptr<ClientUpdateProtocol> Create( + int key_version, const base::StringPiece& public_key); + + // Returns a versioned encrypted secret (v|w) in a URL-safe Base64 encoding. + // Add to your URL before calling SignRequest(). + std::string GetVersionedSecret() const; + + // Generates freshness/authentication data for an outgoing update check. + // |url| contains the the URL that the request will be sent to; it should + // include GetVersionedSecret() in its query string. This needs to be + // formatted in the way that the Omaha server expects: omit the scheme and + // any port number. (e.g. "//tools.google.com/service/update2?w=1:abcdef") + // |request_body| contains the body of the update check request in UTF-8. + // + // On success, returns true, and |client_proof| receives a Base64-encoded + // client proof, which should be sent in the If-Match HTTP header. On + // failure, returns false, and |client_proof| is not modified. + // + // This method will store internal state in this instance used by calls to + // ValidateResponse(); if you need to have multiple update checks in flight, + // initialize a separate CUP instance for each one. + bool SignRequest(const base::StringPiece& url, + const base::StringPiece& request_body, + std::string* client_proof); + + // Validates a response given to a update check request previously signed + // with SignRequest(). |request_body| contains the body of the response in + // UTF-8. |server_cookie| contains the persisted credential cookie provided + // by the server. |server_proof| contains the Base64-encoded server proof, + // which is passed in the ETag HTTP header. Returns true if the response is + // valid. + // This method uses internal state that is set by a prior SignRequest() call. + bool ValidateResponse(const base::StringPiece& response_body, + const base::StringPiece& server_cookie, + const base::StringPiece& server_proof); + + private: + friend class CupTest; + + explicit ClientUpdateProtocol(int key_version); + + // Decodes |public_key| into the appropriate internal structures. Returns + // the length of the public key (modulus) in bytes, or 0 on failure. + bool LoadPublicKey(const base::StringPiece& public_key); + + // Returns the size of the public key in bytes, or 0 on failure. + size_t PublicKeyLength(); + + // Helper function for BuildSharedKey() -- encrypts |key_source| (r) using + // the loaded public key, filling out |encrypted_key_source_| (w). + // Returns true on success. + bool EncryptKeySource(const std::vector<uint8>& key_source); + + // Generates a random key source and passes it to DeriveSharedKey(). + // Returns true on success. + bool BuildRandomSharedKey(); + + // Sets a fixed key source from a character string and passes it to + // DeriveSharedKey(). Used for unit testing only. Returns true on success. + bool SetSharedKeyForTesting(const base::StringPiece& fixed_key_source); + + // Given a key source (r), derives the values of |shared_key_| (sk') and + // encrypted_key_source_ (w). Returns true on success. + bool DeriveSharedKey(const std::vector<uint8>& source); + + // The server keeps multiple private keys; a version must be sent so that + // the right private key is used to decode the versioned secret. (The CUP + // specification calls this "v".) + int pub_key_version_; + + // Holds the shared key, which is used to generate an HMAC signature for both + // the update check request and the update response. The client builds it + // locally, but sends the server an encrypted copy of the key source to + // synthesize it on its own. (The CUP specification calls this "sk'".) + std::vector<uint8> shared_key_; + + // Holds the original contents of key_source_ that have been encrypted with + // the server's public key. The client sends this, along with the version of + // the keypair that was used, to the server. The server decrypts it using its + // private key to get the contents of key_source_, from which it recreates the + // shared key. (The CUP specification calls this "w".) + std::vector<uint8> encrypted_key_source_; + + // Holds the hash of the update check request, the URL that it was sent to, + // and the versioned secret. This is filled out by a successful call to + // SignRequest(), and used by ValidateResponse() to confirm that the server + // has successfully decoded the versioned secret and signed the response using + // the same shared key as our own. (The CUP specification calls this "hw".) + std::vector<uint8> client_challenge_hash_; + + // The public key used to encrypt the key source. (The CUP specification + // calls this "pk[v]".) +#if defined(USE_NSS) || defined(OS_WIN) || defined(OS_MACOSX) + SECKEYPublicKey* public_key_; +#endif + + DISALLOW_IMPLICIT_CONSTRUCTORS(ClientUpdateProtocol); +}; + +#endif // GOOGLE_APIS_CUP_CLIENT_UPDATE_PROTOCOL_H_ + diff --git a/google_apis/cup/client_update_protocol_nss.cc b/google_apis/cup/client_update_protocol_nss.cc new file mode 100644 index 0000000..b369c13 --- /dev/null +++ b/google_apis/cup/client_update_protocol_nss.cc @@ -0,0 +1,81 @@ +// 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 "google_apis/cup/client_update_protocol.h" + +#include <keyhi.h> +#include <pk11pub.h> +#include <seccomon.h> + +#include "base/logging.h" +#include "crypto/nss_util.h" +#include "crypto/scoped_nss_types.h" + +typedef scoped_ptr_malloc< + CERTSubjectPublicKeyInfo, + crypto::NSSDestroyer<CERTSubjectPublicKeyInfo, + SECKEY_DestroySubjectPublicKeyInfo> > + ScopedCERTSubjectPublicKeyInfo; + +ClientUpdateProtocol::~ClientUpdateProtocol() { + if (public_key_) + SECKEY_DestroyPublicKey(public_key_); +} + +bool ClientUpdateProtocol::LoadPublicKey(const base::StringPiece& public_key) { + crypto::EnsureNSSInit(); + + // The binary blob |public_key| is expected to be a DER-encoded ASN.1 + // Subject Public Key Info. + SECItem spki_item; + spki_item.type = siBuffer; + spki_item.data = + reinterpret_cast<unsigned char*>(const_cast<char*>(public_key.data())); + spki_item.len = static_cast<unsigned int>(public_key.size()); + + ScopedCERTSubjectPublicKeyInfo spki( + SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_item)); + if (!spki.get()) + return false; + + public_key_ = SECKEY_ExtractPublicKey(spki.get()); + if (!public_key_) + return false; + + if (!PublicKeyLength()) + return false; + + return true; +} + +size_t ClientUpdateProtocol::PublicKeyLength() { + if (!public_key_) + return 0; + + return SECKEY_PublicKeyStrength(public_key_); +} + +bool ClientUpdateProtocol::EncryptKeySource( + const std::vector<uint8>& key_source) { + // WARNING: This call bypasses the usual PKCS #1 padding and does direct RSA + // exponentiation. This is not secure without taking measures to ensure that + // the contents of r are suitable. This is done to remain compatible with + // the implementation on the Google Update servers; don't copy-paste this + // code arbitrarily and expect it to work and/or remain secure! + if (!public_key_) + return false; + + size_t keysize = SECKEY_PublicKeyStrength(public_key_); + if (key_source.size() != keysize) + return false; + + encrypted_key_source_.resize(keysize); + return SECSuccess == PK11_PubEncryptRaw( + public_key_, + &encrypted_key_source_[0], + const_cast<unsigned char*>(&key_source[0]), + key_source.size(), + NULL); +} + diff --git a/google_apis/cup/client_update_protocol_openssl.cc b/google_apis/cup/client_update_protocol_openssl.cc new file mode 100644 index 0000000..74f99ec --- /dev/null +++ b/google_apis/cup/client_update_protocol_openssl.cc @@ -0,0 +1,27 @@ +// 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 "google_apis/cup/client_update_protocol.h" + +#include "base/logging.h" + +ClientUpdateProtocol::~ClientUpdateProtocol() {} + +bool ClientUpdateProtocol::LoadPublicKey(const base::StringPiece& public_key) { + NOTIMPLEMENTED(); + return false; +} + +size_t ClientUpdateProtocol::PublicKeyLength() { + NOTIMPLEMENTED(); + return 0; +} + +bool ClientUpdateProtocol::EncryptKeySource( + const std::vector<uint8>& key_source) { + NOTIMPLEMENTED(); + return false; +} + + diff --git a/google_apis/cup/client_update_protocol_unittest.cc b/google_apis/cup/client_update_protocol_unittest.cc new file mode 100644 index 0000000..6a2abed --- /dev/null +++ b/google_apis/cup/client_update_protocol_unittest.cc @@ -0,0 +1,165 @@ +// 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 <limits> +#include <vector> + +#include "base/base64.h" +#include "base/memory/scoped_ptr.h" +#include "crypto/random.h" +#include "crypto/secure_util.h" +#include "google_apis/cup/client_update_protocol.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +std::string GetPublicKeyForTesting() { + // How to generate this key: + // openssl genpkey -out cr.pem -outform PEM -algorithm RSA + // -pkeyopt rsa_keygen_pubexp:3 + // openssl rsa -in cr.pem -pubout -out cr_pub.pem + + static const char kCupTestKey1024_Base64[] = + "MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQC7ct1JhLSol2DkBcJdNjR3KkEA" + "ZfXpF22lDD2WZu5JAZ4NiZqnHsKGJNPUbCH4AhFsXmuW5wEHhUVNhsMP6F9mQ06D" + "i+ygwZ8aXlklmW4S0Et+SNg3i73fnYn0KDQzrzJnMu46s/CFPhjr4f0TH9b7oHkU" + "XbqNZtG6gwaN1bmzFwIBAw=="; + + std::string result; + if (!base::Base64Decode(std::string(kCupTestKey1024_Base64), &result)) + return ""; + + return result; +} + +} // end namespace + +#if defined(USE_OPENSSL) + +// Once CUP is implemented for OpenSSL, remove this #if block. +TEST(CupTest, OpenSSLStub) { + scoped_ptr<ClientUpdateProtocol> cup = + ClientUpdateProtocol::Create(8, GetPublicKeyForTesting()); + ASSERT_FALSE(cup.get()); +} + +#else + +class CupTest : public testing::Test { + protected: + virtual void SetUp() { + cup_ = ClientUpdateProtocol::Create(8, GetPublicKeyForTesting()); + ASSERT_TRUE(cup_.get()); + } + + void OverrideRAndRebuildKeys() { + // This must be the same length as the modulus of the public key being + // used in the unit test. + static const size_t kPublicKeyLength = 1024 / 8; + static const char kFixedR[] = + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. "; + + ASSERT_EQ(kPublicKeyLength, strlen(kFixedR)); + ASSERT_TRUE(cup_->SetSharedKeyForTesting(kFixedR)); + } + + ClientUpdateProtocol& CUP() { + return *cup_.get(); + } + + private: + scoped_ptr<ClientUpdateProtocol> cup_; +}; + +TEST_F(CupTest, GetVersionedSecret) { + // Given a fixed public key set in the test fixture, if the key source |r| + // is filled with known data, |w| can be tested against an expected output, + // and the signing/verification for an given request becomes fixed. + // + // The expected output can be generated using this command line, where + // plaintext.bin is the contents of kFixedR[] in OverrideRAndRebuildKeys(): + // + // openssl rsautl -inkey cr2_pub.pem -pubin -encrypt -raw + // -in plaintext.bin | base64 + // + // Remember to prepend the key version number, and fix up the Base64 + // afterwards to be URL-safe. + + static const char kExpectedVW[] = + "8:lMmNR3mVbOitbq8ceYGStFBwrJcpvY-sauFSbMVe6VONS9x42xTOLY_KdqsWCy" + "KuiJBiQziQLOybPUyA9vk0N5kMnC90LIh2nP2FgFG0M0Z22qjB3drsdJPi7TQZbb" + "Xhqm587M8vjc6VlM_eoC0qYwCPaXBqXjsyiHnXetcn5X0"; + + EXPECT_NE(kExpectedVW, CUP().GetVersionedSecret()); + OverrideRAndRebuildKeys(); + EXPECT_EQ(kExpectedVW, CUP().GetVersionedSecret()); +} + +TEST_F(CupTest, SignRequest) { + static const char kUrl[] = "//testserver.chromium.org/update"; + static const char kUrlQuery[] = "?junk=present"; + static const char kRequest[] = "testbody"; + + static const char kExpectedCP[] = "tfjmVMDAbU0-Kye4PjrCuyQIDCU"; + + OverrideRAndRebuildKeys(); + + // Check the case with no query string other than v|w. + std::string url(kUrl); + url.append("?w="); + url.append(CUP().GetVersionedSecret()); + + std::string cp; + ASSERT_TRUE(CUP().SignRequest(url, kRequest, &cp)); + + // Check the case with a pre-existing query string. + std::string url2(kUrl); + url2.append(kUrlQuery); + url2.append("&w="); + url2.append(CUP().GetVersionedSecret()); + + std::string cp2; + ASSERT_TRUE(CUP().SignRequest(url2, kRequest, &cp2)); + + // Changes in the URL should result in changes in the client proof. + EXPECT_EQ(kExpectedCP, cp2); + EXPECT_NE(cp, cp2); +} + +TEST_F(CupTest, ValidateResponse) { + static const char kUrl[] = "//testserver.chromium.orgupdate?junk=present&w="; + static const char kRequest[] = "testbody"; + + static const char kGoodResponse[] = "intact_response"; + static const char kGoodC[] = "c=EncryptedDataFromTheUpdateServer"; + static const char kGoodSP[] = "5rMFMPL9Hgqb-2J8kL3scsHeNgg"; + + static const char kBadResponse[] = "tampered_response"; + static const char kBadC[] = "c=TotalJunkThatAnAttackerCouldSend"; + static const char kBadSP[] = "Base64TamperedShaOneHash"; + + OverrideRAndRebuildKeys(); + + std::string url(kUrl); + url.append(CUP().GetVersionedSecret()); + + std::string client_proof; + ASSERT_TRUE(CUP().SignRequest(url, kRequest, &client_proof)); + + // Return true on a valid response and server proof. + EXPECT_TRUE(CUP().ValidateResponse(kGoodResponse, kGoodC, kGoodSP)); + + // Return false on anything invalid. + EXPECT_FALSE(CUP().ValidateResponse(kBadResponse, kGoodC, kGoodSP)); + EXPECT_FALSE(CUP().ValidateResponse(kGoodResponse, kBadC, kGoodSP)); + EXPECT_FALSE(CUP().ValidateResponse(kGoodResponse, kGoodC, kBadSP)); + EXPECT_FALSE(CUP().ValidateResponse(kGoodResponse, kBadC, kBadSP)); + EXPECT_FALSE(CUP().ValidateResponse(kBadResponse, kGoodC, kBadSP)); + EXPECT_FALSE(CUP().ValidateResponse(kBadResponse, kBadC, kGoodSP)); + EXPECT_FALSE(CUP().ValidateResponse(kBadResponse, kBadC, kBadSP)); +} + +#endif // !defined(USE_OPENSSL) + diff --git a/google_apis/google_apis.gyp b/google_apis/google_apis.gyp index 9a6ec47..9ae23dd 100644 --- a/google_apis/google_apis.gyp +++ b/google_apis/google_apis.gyp @@ -48,8 +48,29 @@ 'GOOGLE_CLIENT_SECRET_REMOTING="<(google_default_client_secret)"', ] }], + [ 'OS == "android"', { + 'dependencies': [ + '../third_party/openssl/openssl.gyp:openssl', + ], + 'sources/': [ + ['exclude', 'cup/client_update_protocol_nss\.cc$'], + ], + }], + [ 'use_openssl==1', { + 'sources!': [ + 'cup/client_update_protocol_nss.cc', + ], + }, { + 'sources!': [ + 'cup/client_update_protocol_openssl.cc', + ], + },], ], 'sources': [ + 'cup/client_update_protocol.cc', + 'cup/client_update_protocol.h', + 'cup/client_update_protocol_nss.cc', + 'cup/client_update_protocol_openssl.cc', 'gaia/gaia_auth_consumer.cc', 'gaia/gaia_auth_consumer.h', 'gaia/gaia_auth_fetcher.cc', |