summaryrefslogtreecommitdiffstats
path: root/google_apis
diff options
context:
space:
mode:
authorryanmyers@chromium.org <ryanmyers@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-07 08:32:30 +0000
committerryanmyers@chromium.org <ryanmyers@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-07 08:32:30 +0000
commite9c3662f70af48bf571bc515b768ddb31dfe539a (patch)
tree66f417933255fc19d9bbd3c6995c62585f3815eb /google_apis
parentfda3b119daf1a351d53a2e5eb01ff3cb78d00cef (diff)
downloadchromium_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/OWNERS1
-rw-r--r--google_apis/cup/client_update_protocol.cc305
-rw-r--r--google_apis/cup/client_update_protocol.h138
-rw-r--r--google_apis/cup/client_update_protocol_nss.cc81
-rw-r--r--google_apis/cup/client_update_protocol_openssl.cc27
-rw-r--r--google_apis/cup/client_update_protocol_unittest.cc165
-rw-r--r--google_apis/google_apis.gyp21
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',