blob: dd2e06b6dc224e54fc29623d2a3d035c14e83535 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/client_update_protocol/cup.h"
#include <array>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include "base/base64url.h"
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "components/client_update_protocol/features.h"
#include "crypto/hash.h"
#include "crypto/keypair.h"
#include "crypto/sign.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/fuzztest/src/fuzztest/fuzztest.h"
namespace client_update_protocol {
namespace {
// How to generate this key:
// openssl ecparam -genkey -name prime256v1 -out ecpriv.pem
// openssl ec -in ecpriv.pem -pubout -out ecpub.pem
// and use xxd -i to convert it to comma-separated hex.
//
// If you change this key, you will also need to change all the test data.
constexpr auto kCupEcdsaTestKey = std::to_array<uint8_t>({
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, 0x24, 0xd3, 0xa3, 0x2b, 0x23, 0x7a, 0x50, 0x7c, 0x94,
0x1a, 0x41, 0xa8, 0xc3, 0xec, 0x42, 0x99, 0x0b, 0x61, 0x41, 0x75, 0x28,
0xf6, 0xc7, 0x7b, 0x44, 0x85, 0xd2, 0xa5, 0x52, 0x0c, 0xef, 0xaf, 0x14,
0x95, 0xb1, 0x9b, 0xff, 0x92, 0x6b, 0x9c, 0x84, 0xa3, 0x49, 0x87, 0xa3,
0x09, 0xcf, 0xe9, 0xc5, 0x0a, 0x28, 0x23, 0xa9, 0x89, 0x4f, 0x35, 0x8b,
0xde, 0x96, 0x5b, 0xe5, 0x30, 0x35, 0x0c,
});
// Post-Quantum Crypto (ML-DSA-44) PKCS#8 PrivateKeyInfo block (FIPS 204).
//
// How to generate this key:
// openssl genpkey -algorithm ML-DSA-44 -outform DER -out mldsa44_priv.der
// and use xxd -i to convert it to comma-separated hex.
constexpr auto kCupMldsa44TestPrivateKeyInfo = std::to_array<uint8_t>({
0x30, 0x34, 0x02, 0x01, 0x00, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11, 0x04, 0x22, 0x80, 0x20,
0x32, 0x0b, 0x8f, 0x1f, 0xd0, 0x18, 0x68, 0xf4, 0xeb, 0x50, 0xad,
0x05, 0xd8, 0x5e, 0x28, 0x24, 0x22, 0x0e, 0xcb, 0x2e, 0xa9, 0x2e,
0x3f, 0x76, 0x93, 0x29, 0x43, 0x1b, 0xf5, 0xdc, 0xe6, 0xb2,
});
} // end namespace
class CupEcdsaTest : public testing::Test {
protected:
Cup& cup() { return cup_; }
private:
Cup cup_{8, kCupEcdsaTestKey};
};
void EcdsaCupTestOneInputDoesNotCrash(std::string params,
std::string response_body,
std::string signature) {
client_update_protocol::Cup cup(8, kCupEcdsaTestKey);
cup.PrepareRequestParameters(params);
cup.ValidateResponse(response_body, signature);
}
FUZZ_TEST(CupEcdsaFuzzTest, EcdsaCupTestOneInputDoesNotCrash);
TEST_F(CupEcdsaTest, PrepareRequestParameters) {
static constexpr std::string_view kRequest =
"TestSequenceForCupEcdsaUnitTest";
static constexpr std::string_view kRequestHashWithName =
"&cup2hreq="
"cde1f7dc1311ed96813057ca321c2f5a17ea2c9c776ee0eb31965f7985a3074a";
static constexpr std::string_view kKeyIdWithName = "cup2key=8:";
base::flat_set<std::string> queries;
for (int i = 0; i < 3; ++i) {
const std::string query = cup().PrepareRequestParameters(kRequest);
// With a 256-bit nonce, the probability of collision is negligible.
EXPECT_FALSE(queries.contains(query));
queries.insert(query);
EXPECT_TRUE(query.starts_with(kKeyIdWithName));
EXPECT_TRUE(query.ends_with(kRequestHashWithName));
// The nonce is a base64url-encoded, 32-byte (256-bit) string.
std::string_view nonce_b64 = query;
nonce_b64.remove_prefix(kKeyIdWithName.size());
nonce_b64.remove_suffix(kRequestHashWithName.size());
std::string nonce;
EXPECT_TRUE(base::Base64UrlDecode(
nonce_b64, base::Base64UrlDecodePolicy::DISALLOW_PADDING, &nonce));
EXPECT_EQ(nonce.size(), 32u);
}
}
TEST_F(CupEcdsaTest, ValidateResponse_TestETagParsing) {
// Invalid ETags are gracefully rejected without a crash.
cup().PrepareRequestParameters("Request_A");
cup().OverrideNonceForTesting(8, 12345);
// Expect a pass for a well-formed etag.
EXPECT_TRUE(cup().ValidateResponse(
"Response_A",
"3044"
"02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
"02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
// Reject empty etags.
EXPECT_FALSE(cup().ValidateResponse("Response_A", ""));
// Reject etags with zero-length hashes or signatures, even if the other
// component is wellformed.
EXPECT_FALSE(cup().ValidateResponse("Response_A", ":"));
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3044"
"02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
"02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
":"));
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
// Reject etags with non-hex content in either component.
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3044"
"02207fb15d24e66c168ac150458__ae51f843c4858e27d41be3f9396d4919bbd5656"
"02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3044"
"02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
"02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
":2727bc2b3c33feb6800a830f4055901d__7d65a84184c5fbeb3f816db0a243f5"));
// Reject etags where either/both component has a length that's not a
// multiple of 2 (i.e. not a valid hex encoding).
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3044"
"02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
"02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3044"
"02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
"02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f"));
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3044"
"02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
"02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f"));
// Reject etags where the hash is even, but not 256 bits.
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3044"
"02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
"02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3044"
"02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
"02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5ff"));
// Reject etags where the signature field is too small to be valid. (Note that
// the case isn't even a signature -- it's a validly encoded ASN.1 NULL.)
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"0500"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
// Reject etags where the signature field is too big to be a valid signature.
// (This is a validly formed structure, but both ints are over 256 bits.)
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3048"
"202207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
"202207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5ff"));
// Reject etags where the signature is valid DER-encoded ASN.1, but is not
// an ECDSA signature. (This is actually stressing crypto's SignatureValidator
// library, and not CUP's use of it, but it's worth testing here.) Cases:
// * Something that's not a sequence
// * Sequences that contain things other than ints (i.e. octet strings)
// * Sequences that contain a negative int.
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"0406020100020100"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3044"
"06200123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
"06200123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3046"
"02047fffffff"
"0220ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
// Reject etags where the signature is not a valid DER encoding. (Again, this
// is stressing SignatureValidator.) Test cases are:
// * No length field
// * Zero length field
// * One of the ints has truncated content
// * One of the ints has content longer than its length field
// * A positive int is improperly zero-padded
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"30"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3000"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3044"
"02207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
"02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3044"
"02207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00"
"02207fb15d24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243"));
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3044"
"022000007f24e66c168ac150458c7ae51f843c4858e27d41be3f9396d4919bbd5656"
"02202291bae598e4a41118ea1df24ce8494d4055b2842dc046e0223f5e17e86bd10e"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
}
TEST_F(CupEcdsaTest, ValidateResponse_TestSigning) {
cup().PrepareRequestParameters("Request_A");
cup().OverrideNonceForTesting(8, 12345);
// How to generate an ECDSA signature:
// echo -n Request_A | sha256sum | cut -d " " -f 1 > h
// echo -n Response_A | sha256sum | cut -d " " -f 1 >> h
// cat h | xxd -r -p > hbin
// echo -n 8:12345 >> hbin
// sha256sum hbin | cut -d " " -f 1 | xxd -r -p > hbin2
// openssl dgst -hex -sha256 -sign ecpriv.pem hbin2 | cut -d " " -f 2 > sig
// echo -n :Request_A | sha256sum | cut -d " " -f 1 >> sig
// cat sig
// It's useful to throw this in a bash script and parameterize it if you're
// updating this unit test.
// Valid case:
// * Send "Request_A" with key 8 / nonce 12345 to server.
// * Receive "Response_A", signature, and observed request hash from server.
// * Signature signs HASH(Request_A) | HASH(Response_A) | 8:12345.
// * Observed hash matches HASH(Request_A).
EXPECT_TRUE(cup().ValidateResponse(
"Response_A",
"3045022077a2d004f1643a92af5d356877c3434c46519ce32882d6e30ef6d154ee9775e3"
"022100aca63c77d34152bdc0918ae0629e82b59314e5459f607cdc5ac95f1a4b7c31a2"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
// Failure case: "Request_A" made it to the server intact, but the response
// body is modified to "Response_B" on return. The signature is now invalid.
EXPECT_FALSE(cup().ValidateResponse(
"Response_B",
"3045022077a2d004f1643a92af5d356877c3434c46519ce32882d6e30ef6d154ee9775e3"
"022100aca63c77d34152bdc0918ae0629e82b59314e5459f607cdc5ac95f1a4b7c31a2"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
// Failure case: Request body was modified to "Request_B" before it reached
// the server. Test a fast reject based on the observed_hash parameter.
EXPECT_FALSE(cup().ValidateResponse(
"Response_B",
"304402206289a7765f0371c7c48796779747f1166707d5937a99af518845f44af95876"
"8c0220139fe935fde3e6b416ee742f91c6a480113762d78d889a2661de37576866d21c"
":80e3ef1b373efe5f2a8383a0cf9c89fb2e0cbb8e85db4813655ff5dc05009e7e"));
// Failure case: Request body was modified to "Request_B" before it reached
// the server. Test a slow reject based on a signature mismatch.
EXPECT_FALSE(cup().ValidateResponse(
"Response_B",
"304402206289a7765f0371c7c48796779747f1166707d5937a99af518845f44af95876"
"8c0220139fe935fde3e6b416ee742f91c6a480113762d78d889a2661de37576866d21c"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
// Failure case: Request/response are intact, but the signature is invalid
// because it was signed against a different nonce (67890).
EXPECT_FALSE(cup().ValidateResponse(
"Response_A",
"3046022100d3bbb1fb4451c8e04a07fe95404cc39121ed0e0bc084f87de19d52eee50a97"
"bf022100dd7d41d467be2af98d9116b0c7ba09740d54578c02a02f74da5f089834be3403"
":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5"));
}
class CupMldsa44Test : public testing::Test {
protected:
Cup& cup() { return cup_; }
std::string SignResponse(std::string_view request_body,
std::string_view response_body,
std::string_view cup2key_params) {
auto req_hash = crypto::hash::Sha256(base::as_byte_span(request_body));
auto resp_hash = crypto::hash::Sha256(base::as_byte_span(response_body));
crypto::hash::Hasher hasher(crypto::hash::HashKind::kSha256);
hasher.Update(req_hash);
hasher.Update(resp_hash);
hasher.Update(base::as_byte_span(cup2key_params));
std::array<uint8_t, crypto::hash::kSha256Size> inner_hash = {};
hasher.Finish(inner_hash);
auto signature_bytes = crypto::sign::Sign(
crypto::sign::SignatureKind::MLDSA_44, priv_key_, inner_hash);
return base::StrCat({base::HexEncodeLower(signature_bytes), ":",
base::HexEncodeLower(req_hash)});
}
std::string GetCup2KeyParams(std::string_view req_params) {
size_t start = req_params.find("cup2key=") + 8;
size_t end = req_params.find("&", start);
return std::string(req_params.substr(start, end - start));
}
CupMldsa44Test() {
scoped_feature_list_.InitAndEnableFeature(features::kPqcCupSigning);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
crypto::keypair::PrivateKey priv_key_ =
*crypto::keypair::PrivateKey::FromPrivateKeyInfo(
kCupMldsa44TestPrivateKeyInfo);
Cup cup_{16, crypto::keypair::PublicKey::FromPrivateKey(priv_key_)
.ToSubjectPublicKeyInfo()};
};
void Mldsa44CupTestOneInputDoesNotCrash(std::string params,
std::string response_body,
std::string signature) {
auto priv = crypto::keypair::PrivateKey::FromPrivateKeyInfo(
kCupMldsa44TestPrivateKeyInfo);
const auto kMldsaKeyPubBytes =
crypto::keypair::PublicKey::FromPrivateKey(*priv)
.ToSubjectPublicKeyInfo();
client_update_protocol::Cup cup(16, kMldsaKeyPubBytes);
cup.PrepareRequestParameters(params);
cup.ValidateResponse(response_body, signature);
}
FUZZ_TEST(CupMldsa44FuzzTest, Mldsa44CupTestOneInputDoesNotCrash);
TEST_F(CupMldsa44Test, PrepareRequestParameters) {
static constexpr std::string_view kRequest =
"TestSequenceForCupMldsa44UnitTest";
static constexpr std::string_view kRequestHashWithName =
"&cup2hreq="
"6cc674cc7d21d2eb7aac815fcf2814a793bac092f326f0a65cff0b539269099e";
static constexpr std::string_view kKeyIdWithName = "cup2key=ML-DSA-44-16:";
base::flat_set<std::string> queries;
for (int i = 0; i < 3; ++i) {
const std::string query = cup().PrepareRequestParameters(kRequest);
// With a 256-bit nonce, the probability of collision is negligible.
EXPECT_FALSE(queries.contains(query));
queries.insert(query);
EXPECT_TRUE(query.starts_with(kKeyIdWithName));
EXPECT_TRUE(query.ends_with(kRequestHashWithName));
// The nonce is a base64url-encoded, 32-byte (256-bit) string.
std::string_view nonce_b64 = query;
nonce_b64.remove_prefix(kKeyIdWithName.size());
nonce_b64.remove_suffix(kRequestHashWithName.size());
std::string nonce;
EXPECT_TRUE(base::Base64UrlDecode(
nonce_b64, base::Base64UrlDecodePolicy::DISALLOW_PADDING, &nonce));
EXPECT_EQ(nonce.size(), 32u);
}
}
TEST_F(CupMldsa44Test, ValidateResponse_TestETagParsing) {
// Invalid ETags are gracefully rejected without a crash.
std::string valid_etag = SignResponse(
"Request_A", "Response_A",
GetCup2KeyParams(cup().PrepareRequestParameters("Request_A")));
// Expect a pass for a well-formed etag.
EXPECT_TRUE(cup().ValidateResponse("Response_A", valid_etag));
// Reject empty etags.
EXPECT_FALSE(cup().ValidateResponse("Response_A", ""));
// Reject etags with zero-length hashes or signatures.
EXPECT_FALSE(cup().ValidateResponse("Response_A", ":"));
EXPECT_FALSE(cup().ValidateResponse(
"Response_A", valid_etag.substr(0, valid_etag.find(':') + 1)));
EXPECT_FALSE(cup().ValidateResponse("Response_A",
valid_etag.substr(valid_etag.find(':'))));
// Reject etags with non-hex content.
std::string bad_hex_etag = valid_etag;
bad_hex_etag[0] = '_';
bad_hex_etag[1] = '_';
EXPECT_FALSE(cup().ValidateResponse("Response_A", bad_hex_etag));
std::string bad_hex_hash = valid_etag;
bad_hex_hash[bad_hex_hash.size() - 1] = '_';
bad_hex_hash[bad_hex_hash.size() - 2] = '_';
EXPECT_FALSE(cup().ValidateResponse("Response_A", bad_hex_hash));
// Reject etags where either component has an odd length.
EXPECT_FALSE(cup().ValidateResponse("Response_A",
valid_etag.substr(1))); // odd signature
EXPECT_FALSE(cup().ValidateResponse(
"Response_A", valid_etag.substr(0, valid_etag.size() - 1)));
// Reject etags where the hash is even, but not 256 bits (32 bytes = 64 hex
// chars).
std::string short_hash =
valid_etag.substr(0, valid_etag.find(':') + 1) +
"2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243";
EXPECT_FALSE(cup().ValidateResponse("Response_A", short_hash));
std::string long_hash = valid_etag + "ff";
EXPECT_FALSE(cup().ValidateResponse("Response_A", long_hash));
// Reject etags where the signature size is not 2420 bytes (4840 hex chars).
std::string short_sig = valid_etag.substr(2); // 2419 bytes
EXPECT_FALSE(cup().ValidateResponse("Response_A", short_sig));
std::string long_sig = "ff" + valid_etag; // 2421 bytes
EXPECT_FALSE(cup().ValidateResponse("Response_A", long_sig));
}
TEST_F(CupMldsa44Test, ValidateResponse_TestSigning) {
std::string query = cup().PrepareRequestParameters("Request_A");
std::string cup2key = GetCup2KeyParams(query);
// Valid case:
std::string valid_etag = SignResponse("Request_A", "Response_A", cup2key);
EXPECT_TRUE(cup().ValidateResponse("Response_A", valid_etag));
// Failure case: Response modified.
EXPECT_FALSE(cup().ValidateResponse("Response_B", valid_etag));
// Failure case: Request modified (fast reject via hash mismatch in ETag).
auto req_b_hash = crypto::hash::Sha256(base::as_byte_span("Request_B"));
std::string bad_hash_etag = valid_etag.substr(0, valid_etag.find(':') + 1) +
base::HexEncodeLower(req_b_hash);
EXPECT_FALSE(cup().ValidateResponse("Response_A", bad_hash_etag));
// Failure case: Request modified (slow reject via signature mismatch).
std::string sig_for_b = SignResponse("Request_B", "Response_A", cup2key);
EXPECT_FALSE(cup().ValidateResponse("Response_A", sig_for_b));
// Failure case: Wrong nonce (signed against a different nonce).
std::string wrong_cup2key =
base::StrCat({cup2key.substr(0, cup2key.find(':') + 1),
"0000000000000000000000000000000000000000000000000000000000"
"000000"});
std::string wrong_nonce_etag =
SignResponse("Request_A", "Response_A", wrong_cup2key);
EXPECT_FALSE(cup().ValidateResponse("Response_A", wrong_nonce_etag));
}
} // namespace client_update_protocol