| Index: net/base/transport_security_state_unittest.cc
|
| ===================================================================
|
| --- net/base/transport_security_state_unittest.cc (revision 165283)
|
| +++ net/base/transport_security_state_unittest.cc (working copy)
|
| @@ -45,405 +45,6 @@
|
| }
|
| };
|
|
|
| -TEST_F(TransportSecurityStateTest, BogusHeaders) {
|
| - TransportSecurityState::DomainState state;
|
| - base::Time now = base::Time::Now();
|
| -
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, ""));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, " "));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "abc"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, " abc"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, " abc "));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, " max-age"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, " max-age "));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age="));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, " max-age="));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, " max-age ="));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, " max-age= "));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, " max-age = "));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, " max-age = xy"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, " max-age = 3488a923"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488a923 "));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-ag=3488923"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-aged=3488923"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age==3488923"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "amax-age=3488923"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=-3488923"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923;"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923 e"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(
|
| - now, "max-age=3488923 includesubdomain"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923includesubdomains"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923=includesubdomains"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923 includesubdomainx"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923 includesubdomain="));
|
| - EXPECT_FALSE(state.ParseSTSHeader(
|
| - now, "max-age=3488923 includesubdomain=true"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=3488923 includesubdomainsx"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(
|
| - now, "max-age=3488923 includesubdomains x"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=34889.23 includesubdomains"));
|
| - EXPECT_FALSE(state.ParseSTSHeader(now, "max-age=34889 includesubdomains"));
|
| -
|
| - // Check that |state| was not updated by expecting the default
|
| - // values for its predictable fields.
|
| - EXPECT_EQ(state.upgrade_mode,
|
| - TransportSecurityState::DomainState::MODE_FORCE_HTTPS);
|
| - EXPECT_FALSE(state.include_subdomains);
|
| -}
|
| -
|
| -static bool GetPublicKeyHash(const net::X509Certificate::OSCertHandle& cert,
|
| - HashValue* hash) {
|
| - std::string der_bytes;
|
| - if (!net::X509Certificate::GetDEREncoded(cert, &der_bytes))
|
| - return false;
|
| -
|
| - base::StringPiece spki;
|
| - if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki))
|
| - return false;
|
| -
|
| - switch (hash->tag) {
|
| - case HASH_VALUE_SHA1:
|
| - base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(spki.data()),
|
| - spki.size(), hash->data());
|
| - break;
|
| - case HASH_VALUE_SHA256:
|
| - crypto::SHA256HashString(spki, hash->data(), crypto::kSHA256Length);
|
| - break;
|
| - default:
|
| - NOTREACHED() << "Unknown HashValueTag " << hash->tag;
|
| - }
|
| -
|
| - return true;
|
| -}
|
| -
|
| -static std::string GetPinFromCert(X509Certificate* cert, HashValueTag tag) {
|
| - HashValue spki_hash(tag);
|
| - EXPECT_TRUE(GetPublicKeyHash(cert->os_cert_handle(), &spki_hash));
|
| -
|
| - std::string base64;
|
| - base::Base64Encode(base::StringPiece(
|
| - reinterpret_cast<char*>(spki_hash.data()), spki_hash.size()), &base64);
|
| -
|
| - std::string label;
|
| - switch (tag) {
|
| - case HASH_VALUE_SHA1:
|
| - label = "pin-sha1=";
|
| - break;
|
| - case HASH_VALUE_SHA256:
|
| - label = "pin-sha256=";
|
| - break;
|
| - default:
|
| - NOTREACHED() << "Unknown HashValueTag " << tag;
|
| - }
|
| -
|
| - return label + HttpUtil::Quote(base64);
|
| -}
|
| -
|
| -static void TestBogusPinsHeaders(HashValueTag tag) {
|
| - TransportSecurityState::DomainState state;
|
| - SSLInfo ssl_info;
|
| - ssl_info.cert =
|
| - ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem");
|
| - std::string good_pin = GetPinFromCert(ssl_info.cert, tag);
|
| - base::Time now = base::Time::Now();
|
| -
|
| - // The backup pin is fake --- it just has to not be in the chain.
|
| - std::string backup_pin = "pin-sha1=" +
|
| - HttpUtil::Quote("6dcfXufJLW3J6S/9rRe4vUlBj5g=");
|
| -
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, " ", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "abc", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, " abc", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, " abc ", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "max-age", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, " max-age", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, " max-age ", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, " max-age=", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, " max-age =", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, " max-age= ", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, " max-age = ", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, " max-age = xy", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(
|
| - now,
|
| - " max-age = 3488a923",
|
| - ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=3488a923 ", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now,
|
| - "max-ag=3488923pins=" + good_pin + "," + backup_pin,
|
| - ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "max-aged=3488923" + backup_pin,
|
| - ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "max-aged=3488923; " + backup_pin,
|
| - ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now,
|
| - "max-aged=3488923; " + backup_pin + ";" + backup_pin,
|
| - ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now,
|
| - "max-aged=3488923; " + good_pin + ";" + good_pin,
|
| - ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "max-aged=3488923; " + good_pin,
|
| - ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "max-age==3488923", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "amax-age=3488923", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=-3488923", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=3488923;", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=3488923 e", ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(
|
| - now,
|
| - "max-age=3488923 includesubdomain",
|
| - ssl_info));
|
| - EXPECT_FALSE(state.ParsePinsHeader(now, "max-age=34889.23", ssl_info));
|
| -
|
| - // Check that |state| was not updated by expecting the default
|
| - // values for its predictable fields.
|
| - EXPECT_EQ(state.upgrade_mode,
|
| - TransportSecurityState::DomainState::MODE_FORCE_HTTPS);
|
| - EXPECT_FALSE(state.include_subdomains);
|
| -}
|
| -
|
| -TEST_F(TransportSecurityStateTest, BogusPinsHeadersSHA1) {
|
| - TestBogusPinsHeaders(HASH_VALUE_SHA1);
|
| -}
|
| -
|
| -TEST_F(TransportSecurityStateTest, BogusPinsHeadersSHA256) {
|
| - TestBogusPinsHeaders(HASH_VALUE_SHA256);
|
| -}
|
| -
|
| -TEST_F(TransportSecurityStateTest, ValidSTSHeaders) {
|
| - TransportSecurityState::DomainState state;
|
| - base::Time expiry;
|
| - base::Time now = base::Time::Now();
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(now, "max-age=243"));
|
| - expiry = now + base::TimeDelta::FromSeconds(243);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_FALSE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(now, " Max-agE = 567"));
|
| - expiry = now + base::TimeDelta::FromSeconds(567);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_FALSE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(now, " mAx-aGe = 890 "));
|
| - expiry = now + base::TimeDelta::FromSeconds(890);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_FALSE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(now, "max-age=123;incLudesUbdOmains"));
|
| - expiry = now + base::TimeDelta::FromSeconds(123);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_TRUE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(now, "incLudesUbdOmains; max-age=123"));
|
| - expiry = now + base::TimeDelta::FromSeconds(123);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_TRUE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(now, " incLudesUbdOmains; max-age=123"));
|
| - expiry = now + base::TimeDelta::FromSeconds(123);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_TRUE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(now,
|
| - " incLudesUbdOmains; max-age=123; pumpkin=kitten"));
|
| - expiry = now + base::TimeDelta::FromSeconds(123);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_TRUE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(now,
|
| - " pumpkin=894; incLudesUbdOmains; max-age=123 "));
|
| - expiry = now + base::TimeDelta::FromSeconds(123);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_TRUE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(now,
|
| - " pumpkin; incLudesUbdOmains; max-age=123 "));
|
| - expiry = now + base::TimeDelta::FromSeconds(123);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_TRUE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(now,
|
| - " pumpkin; incLudesUbdOmains; max-age=\"123\" "));
|
| - expiry = now + base::TimeDelta::FromSeconds(123);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_TRUE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(now,
|
| - "animal=\"squirrel; distinguished\"; incLudesUbdOmains; max-age=123"));
|
| - expiry = now + base::TimeDelta::FromSeconds(123);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_TRUE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(now, "max-age=394082; incLudesUbdOmains"));
|
| - expiry = now + base::TimeDelta::FromSeconds(394082);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_TRUE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(
|
| - now, "max-age=39408299 ;incLudesUbdOmains"));
|
| - expiry = now + base::TimeDelta::FromSeconds(
|
| - std::min(TransportSecurityState::kMaxHSTSAgeSecs, 39408299l));
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_TRUE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(
|
| - now, "max-age=394082038 ; incLudesUbdOmains"));
|
| - expiry = now + base::TimeDelta::FromSeconds(
|
| - std::min(TransportSecurityState::kMaxHSTSAgeSecs, 394082038l));
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_TRUE(state.include_subdomains);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(
|
| - now, " max-age=0 ; incLudesUbdOmains "));
|
| - expiry = now + base::TimeDelta::FromSeconds(0);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_TRUE(state.include_subdomains);
|
| - // When max-age == 0, we downgrade to MODE_DEFAULT rather than deleting
|
| - // the entire DomainState. (That is because we currently overload
|
| - // DomainState to also include pins, and we don't want to invalidate any
|
| - // opportunistic pins that may be in place.)
|
| - EXPECT_EQ(TransportSecurityState::DomainState::MODE_DEFAULT,
|
| - state.upgrade_mode);
|
| -
|
| - EXPECT_TRUE(state.ParseSTSHeader(
|
| - now,
|
| - " max-age=999999999999999999999999999999999999999999999 ;"
|
| - " incLudesUbdOmains "));
|
| - expiry = now + base::TimeDelta::FromSeconds(
|
| - TransportSecurityState::kMaxHSTSAgeSecs);
|
| - EXPECT_EQ(expiry, state.upgrade_expiry);
|
| - EXPECT_TRUE(state.include_subdomains);
|
| -}
|
| -
|
| -static void TestValidPinsHeaders(HashValueTag tag) {
|
| - TransportSecurityState::DomainState state;
|
| - base::Time expiry;
|
| - base::Time now = base::Time::Now();
|
| -
|
| - // Set up a realistic SSLInfo with a realistic cert chain.
|
| - FilePath certs_dir = GetTestCertsDirectory();
|
| - scoped_refptr<X509Certificate> ee_cert =
|
| - ImportCertFromFile(certs_dir, "2048-rsa-ee-by-2048-rsa-intermediate.pem");
|
| - ASSERT_NE(static_cast<X509Certificate*>(NULL), ee_cert);
|
| - scoped_refptr<X509Certificate> intermediate =
|
| - ImportCertFromFile(certs_dir, "2048-rsa-intermediate.pem");
|
| - ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate);
|
| - X509Certificate::OSCertHandles intermediates;
|
| - intermediates.push_back(intermediate->os_cert_handle());
|
| - SSLInfo ssl_info;
|
| - ssl_info.cert = X509Certificate::CreateFromHandle(ee_cert->os_cert_handle(),
|
| - intermediates);
|
| -
|
| - // Add the root that signed the intermediate for this test.
|
| - scoped_refptr<X509Certificate> root_cert =
|
| - ImportCertFromFile(certs_dir, "2048-rsa-root.pem");
|
| - ASSERT_NE(static_cast<X509Certificate*>(NULL), root_cert);
|
| - ScopedTestRoot scoped_root(root_cert);
|
| -
|
| - // Verify has the side-effect of populating public_key_hashes, which
|
| - // ParsePinsHeader needs. (It wants to check pins against the validated
|
| - // chain, not just the presented chain.)
|
| - int rv = ERR_FAILED;
|
| - CertVerifyResult result;
|
| - scoped_ptr<CertVerifier> verifier(CertVerifier::CreateDefault());
|
| - TestCompletionCallback callback;
|
| - CertVerifier::RequestHandle handle = NULL;
|
| - rv = verifier->Verify(ssl_info.cert, "127.0.0.1", 0, NULL, &result,
|
| - callback.callback(), &handle, BoundNetLog());
|
| - rv = callback.GetResult(rv);
|
| - ASSERT_EQ(OK, rv);
|
| - // Normally, ssl_client_socket_nss would do this, but for a unit test we
|
| - // fake it.
|
| - ssl_info.public_key_hashes = result.public_key_hashes;
|
| - std::string good_pin = GetPinFromCert(ssl_info.cert, /*tag*/HASH_VALUE_SHA1);
|
| - DLOG(WARNING) << "good pin: " << good_pin;
|
| -
|
| - // The backup pin is fake --- we just need an SPKI hash that does not match
|
| - // the hash of any SPKI in the certificate chain.
|
| - std::string backup_pin = "pin-sha1=" +
|
| - HttpUtil::Quote("6dcfXufJLW3J6S/9rRe4vUlBj5g=");
|
| -
|
| - EXPECT_TRUE(state.ParsePinsHeader(
|
| - now,
|
| - "max-age=243; " + good_pin + ";" + backup_pin,
|
| - ssl_info));
|
| - expiry = now + base::TimeDelta::FromSeconds(243);
|
| - EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
|
| -
|
| - EXPECT_TRUE(state.ParsePinsHeader(
|
| - now,
|
| - " " + good_pin + "; " + backup_pin + " ; Max-agE = 567",
|
| - ssl_info));
|
| - expiry = now + base::TimeDelta::FromSeconds(567);
|
| - EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
|
| -
|
| - EXPECT_TRUE(state.ParsePinsHeader(
|
| - now,
|
| - good_pin + ";" + backup_pin + " ; mAx-aGe = 890 ",
|
| - ssl_info));
|
| - expiry = now + base::TimeDelta::FromSeconds(890);
|
| - EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
|
| -
|
| - EXPECT_TRUE(state.ParsePinsHeader(
|
| - now,
|
| - good_pin + ";" + backup_pin + "; max-age=123;IGNORED;",
|
| - ssl_info));
|
| - expiry = now + base::TimeDelta::FromSeconds(123);
|
| - EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
|
| -
|
| - EXPECT_TRUE(state.ParsePinsHeader(
|
| - now,
|
| - "max-age=394082;" + backup_pin + ";" + good_pin + "; ",
|
| - ssl_info));
|
| - expiry = now + base::TimeDelta::FromSeconds(394082);
|
| - EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
|
| -
|
| - EXPECT_TRUE(state.ParsePinsHeader(
|
| - now,
|
| - "max-age=39408299 ;" + backup_pin + ";" + good_pin + "; ",
|
| - ssl_info));
|
| - expiry = now + base::TimeDelta::FromSeconds(
|
| - std::min(TransportSecurityState::kMaxHSTSAgeSecs, 39408299l));
|
| - EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
|
| -
|
| - EXPECT_TRUE(state.ParsePinsHeader(
|
| - now,
|
| - "max-age=39408038 ; cybers=39408038 ; " +
|
| - good_pin + ";" + backup_pin + "; ",
|
| - ssl_info));
|
| - expiry = now + base::TimeDelta::FromSeconds(
|
| - std::min(TransportSecurityState::kMaxHSTSAgeSecs, 394082038l));
|
| - EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
|
| -
|
| - EXPECT_TRUE(state.ParsePinsHeader(
|
| - now,
|
| - " max-age=0 ; " + good_pin + ";" + backup_pin,
|
| - ssl_info));
|
| - expiry = now + base::TimeDelta::FromSeconds(0);
|
| - EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
|
| -
|
| - EXPECT_TRUE(state.ParsePinsHeader(
|
| - now,
|
| - " max-age=999999999999999999999999999999999999999999999 ; " +
|
| - backup_pin + ";" + good_pin + "; ",
|
| - ssl_info));
|
| - expiry = now +
|
| - base::TimeDelta::FromSeconds(TransportSecurityState::kMaxHSTSAgeSecs);
|
| - EXPECT_EQ(expiry, state.dynamic_spki_hashes_expiry);
|
| -}
|
| -
|
| -TEST_F(TransportSecurityStateTest, ValidPinsHeadersSHA1) {
|
| - TestValidPinsHeaders(HASH_VALUE_SHA1);
|
| -}
|
| -
|
| -TEST_F(TransportSecurityStateTest, ValidPinsHeadersSHA256) {
|
| - TestValidPinsHeaders(HASH_VALUE_SHA256);
|
| -}
|
| -
|
| TEST_F(TransportSecurityStateTest, SimpleMatches) {
|
| TransportSecurityState state;
|
| TransportSecurityState::DomainState domain_state;
|
| @@ -919,8 +520,7 @@
|
| static bool AddHash(const std::string& type_and_base64,
|
| HashValueVector* out) {
|
| HashValue hash;
|
| -
|
| - if (!TransportSecurityState::ParsePin(type_and_base64, &hash))
|
| + if (!hash.FromString(type_and_base64))
|
| return false;
|
|
|
| out->push_back(hash);
|
|
|