1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "base/bind.h" 6 #include "base/file_util.h" 7 #include "base/files/scoped_temp_dir.h" 8 #include "base/memory/ref_counted.h" 9 #include "base/memory/scoped_vector.h" 10 #include "base/message_loop/message_loop.h" 11 #include "base/run_loop.h" 12 #include "base/stl_util.h" 13 #include "chrome/browser/net/sqlite_server_bound_cert_store.h" 14 #include "chrome/common/chrome_constants.h" 15 #include "content/public/browser/browser_thread.h" 16 #include "content/public/test/test_browser_thread_bundle.h" 17 #include "net/base/test_data_directory.h" 18 #include "net/ssl/ssl_client_cert_type.h" 19 #include "net/test/cert_test_util.h" 20 #include "sql/statement.h" 21 #include "testing/gtest/include/gtest/gtest.h" 22 #include "webkit/browser/quota/mock_special_storage_policy.h" 23 24 using content::BrowserThread; 25 26 class SQLiteServerBoundCertStoreTest : public testing::Test { 27 public: 28 SQLiteServerBoundCertStoreTest() 29 : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {} 30 31 void Load( 32 ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert>* certs) { 33 base::RunLoop run_loop; 34 store_->Load(base::Bind(&SQLiteServerBoundCertStoreTest::OnLoaded, 35 base::Unretained(this), 36 &run_loop)); 37 run_loop.Run(); 38 certs->swap(certs_); 39 certs_.clear(); 40 } 41 42 void OnLoaded( 43 base::RunLoop* run_loop, 44 scoped_ptr<ScopedVector< 45 net::DefaultServerBoundCertStore::ServerBoundCert> > certs) { 46 certs_.swap(*certs); 47 run_loop->Quit(); 48 } 49 50 protected: 51 static void ReadTestKeyAndCert(std::string* key, std::string* cert) { 52 base::FilePath key_path = net::GetTestCertsDirectory().AppendASCII( 53 "unittest.originbound.key.der"); 54 base::FilePath cert_path = net::GetTestCertsDirectory().AppendASCII( 55 "unittest.originbound.der"); 56 ASSERT_TRUE(file_util::ReadFileToString(key_path, key)); 57 ASSERT_TRUE(file_util::ReadFileToString(cert_path, cert)); 58 } 59 60 static base::Time GetTestCertExpirationTime() { 61 // Cert expiration time from 'dumpasn1 unittest.originbound.der': 62 // GeneralizedTime 19/11/2111 02:23:45 GMT 63 // base::Time::FromUTCExploded can't generate values past 2038 on 32-bit 64 // linux, so we use the raw value here. 65 return base::Time::FromInternalValue(GG_INT64_C(16121816625000000)); 66 } 67 68 static base::Time GetTestCertCreationTime() { 69 // UTCTime 13/12/2011 02:23:45 GMT 70 base::Time::Exploded exploded_time; 71 exploded_time.year = 2011; 72 exploded_time.month = 12; 73 exploded_time.day_of_week = 0; // Unused. 74 exploded_time.day_of_month = 13; 75 exploded_time.hour = 2; 76 exploded_time.minute = 23; 77 exploded_time.second = 45; 78 exploded_time.millisecond = 0; 79 return base::Time::FromUTCExploded(exploded_time); 80 } 81 82 virtual void SetUp() { 83 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 84 store_ = new SQLiteServerBoundCertStore( 85 temp_dir_.path().Append(chrome::kOBCertFilename), NULL); 86 ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert> certs; 87 Load(&certs); 88 ASSERT_EQ(0u, certs.size()); 89 // Make sure the store gets written at least once. 90 store_->AddServerBoundCert( 91 net::DefaultServerBoundCertStore::ServerBoundCert( 92 "google.com", 93 base::Time::FromInternalValue(1), 94 base::Time::FromInternalValue(2), 95 "a", "b")); 96 } 97 98 content::TestBrowserThreadBundle thread_bundle_; 99 base::ScopedTempDir temp_dir_; 100 scoped_refptr<SQLiteServerBoundCertStore> store_; 101 ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert> certs_; 102 }; 103 104 // Test if data is stored as expected in the SQLite database. 105 TEST_F(SQLiteServerBoundCertStoreTest, TestPersistence) { 106 store_->AddServerBoundCert( 107 net::DefaultServerBoundCertStore::ServerBoundCert( 108 "foo.com", 109 base::Time::FromInternalValue(3), 110 base::Time::FromInternalValue(4), 111 "c", "d")); 112 113 ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert> certs; 114 // Replace the store effectively destroying the current one and forcing it 115 // to write its data to disk. Then we can see if after loading it again it 116 // is still there. 117 store_ = NULL; 118 // Make sure we wait until the destructor has run. 119 base::RunLoop().RunUntilIdle(); 120 store_ = new SQLiteServerBoundCertStore( 121 temp_dir_.path().Append(chrome::kOBCertFilename), NULL); 122 123 // Reload and test for persistence 124 Load(&certs); 125 ASSERT_EQ(2U, certs.size()); 126 net::DefaultServerBoundCertStore::ServerBoundCert* goog_cert; 127 net::DefaultServerBoundCertStore::ServerBoundCert* foo_cert; 128 if (certs[0]->server_identifier() == "google.com") { 129 goog_cert = certs[0]; 130 foo_cert = certs[1]; 131 } else { 132 goog_cert = certs[1]; 133 foo_cert = certs[0]; 134 } 135 ASSERT_EQ("google.com", goog_cert->server_identifier()); 136 ASSERT_STREQ("a", goog_cert->private_key().c_str()); 137 ASSERT_STREQ("b", goog_cert->cert().c_str()); 138 ASSERT_EQ(1, goog_cert->creation_time().ToInternalValue()); 139 ASSERT_EQ(2, goog_cert->expiration_time().ToInternalValue()); 140 ASSERT_EQ("foo.com", foo_cert->server_identifier()); 141 ASSERT_STREQ("c", foo_cert->private_key().c_str()); 142 ASSERT_STREQ("d", foo_cert->cert().c_str()); 143 ASSERT_EQ(3, foo_cert->creation_time().ToInternalValue()); 144 ASSERT_EQ(4, foo_cert->expiration_time().ToInternalValue()); 145 146 // Now delete the cert and check persistence again. 147 store_->DeleteServerBoundCert(*certs[0]); 148 store_->DeleteServerBoundCert(*certs[1]); 149 store_ = NULL; 150 // Make sure we wait until the destructor has run. 151 base::RunLoop().RunUntilIdle(); 152 certs.clear(); 153 store_ = new SQLiteServerBoundCertStore( 154 temp_dir_.path().Append(chrome::kOBCertFilename), NULL); 155 156 // Reload and check if the cert has been removed. 157 Load(&certs); 158 ASSERT_EQ(0U, certs.size()); 159 } 160 161 TEST_F(SQLiteServerBoundCertStoreTest, TestUpgradeV1) { 162 // Reset the store. We'll be using a different database for this test. 163 store_ = NULL; 164 165 base::FilePath v1_db_path(temp_dir_.path().AppendASCII("v1db")); 166 167 std::string key_data; 168 std::string cert_data; 169 ReadTestKeyAndCert(&key_data, &cert_data); 170 171 // Create a version 1 database. 172 { 173 sql::Connection db; 174 ASSERT_TRUE(db.Open(v1_db_path)); 175 ASSERT_TRUE(db.Execute( 176 "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY," 177 "value LONGVARCHAR);" 178 "INSERT INTO \"meta\" VALUES('version','1');" 179 "INSERT INTO \"meta\" VALUES('last_compatible_version','1');" 180 "CREATE TABLE origin_bound_certs (" 181 "origin TEXT NOT NULL UNIQUE PRIMARY KEY," 182 "private_key BLOB NOT NULL,cert BLOB NOT NULL);")); 183 184 sql::Statement add_smt(db.GetUniqueStatement( 185 "INSERT INTO origin_bound_certs (origin, private_key, cert) " 186 "VALUES (?,?,?)")); 187 add_smt.BindString(0, "google.com"); 188 add_smt.BindBlob(1, key_data.data(), key_data.size()); 189 add_smt.BindBlob(2, cert_data.data(), cert_data.size()); 190 ASSERT_TRUE(add_smt.Run()); 191 192 ASSERT_TRUE(db.Execute( 193 "INSERT INTO \"origin_bound_certs\" VALUES(" 194 "'foo.com',X'AA',X'BB');" 195 )); 196 } 197 198 // Load and test the DB contents twice. First time ensures that we can use 199 // the updated values immediately. Second time ensures that the updated 200 // values are stored and read correctly on next load. 201 for (int i = 0; i < 2; ++i) { 202 SCOPED_TRACE(i); 203 204 ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert> certs; 205 store_ = new SQLiteServerBoundCertStore(v1_db_path, NULL); 206 207 // Load the database. Because the existing v1 certs are implicitly of type 208 // RSA, which is unsupported, they're discarded. 209 Load(&certs); 210 ASSERT_EQ(0U, certs.size()); 211 212 store_ = NULL; 213 base::RunLoop().RunUntilIdle(); 214 215 // Verify the database version is updated. 216 { 217 sql::Connection db; 218 ASSERT_TRUE(db.Open(v1_db_path)); 219 sql::Statement smt(db.GetUniqueStatement( 220 "SELECT value FROM meta WHERE key = \"version\"")); 221 ASSERT_TRUE(smt.Step()); 222 EXPECT_EQ(4, smt.ColumnInt(0)); 223 EXPECT_FALSE(smt.Step()); 224 } 225 } 226 } 227 228 TEST_F(SQLiteServerBoundCertStoreTest, TestUpgradeV2) { 229 // Reset the store. We'll be using a different database for this test. 230 store_ = NULL; 231 232 base::FilePath v2_db_path(temp_dir_.path().AppendASCII("v2db")); 233 234 std::string key_data; 235 std::string cert_data; 236 ReadTestKeyAndCert(&key_data, &cert_data); 237 238 // Create a version 2 database. 239 { 240 sql::Connection db; 241 ASSERT_TRUE(db.Open(v2_db_path)); 242 ASSERT_TRUE(db.Execute( 243 "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY," 244 "value LONGVARCHAR);" 245 "INSERT INTO \"meta\" VALUES('version','2');" 246 "INSERT INTO \"meta\" VALUES('last_compatible_version','1');" 247 "CREATE TABLE origin_bound_certs (" 248 "origin TEXT NOT NULL UNIQUE PRIMARY KEY," 249 "private_key BLOB NOT NULL," 250 "cert BLOB NOT NULL," 251 "cert_type INTEGER);" 252 )); 253 254 sql::Statement add_smt(db.GetUniqueStatement( 255 "INSERT INTO origin_bound_certs (origin, private_key, cert, cert_type) " 256 "VALUES (?,?,?,?)")); 257 add_smt.BindString(0, "google.com"); 258 add_smt.BindBlob(1, key_data.data(), key_data.size()); 259 add_smt.BindBlob(2, cert_data.data(), cert_data.size()); 260 add_smt.BindInt64(3, 64); 261 ASSERT_TRUE(add_smt.Run()); 262 263 ASSERT_TRUE(db.Execute( 264 "INSERT INTO \"origin_bound_certs\" VALUES(" 265 "'foo.com',X'AA',X'BB',64);" 266 )); 267 } 268 269 // Load and test the DB contents twice. First time ensures that we can use 270 // the updated values immediately. Second time ensures that the updated 271 // values are saved and read correctly on next load. 272 for (int i = 0; i < 2; ++i) { 273 SCOPED_TRACE(i); 274 275 ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert> certs; 276 store_ = new SQLiteServerBoundCertStore(v2_db_path, NULL); 277 278 // Load the database and ensure the certs can be read. 279 Load(&certs); 280 ASSERT_EQ(2U, certs.size()); 281 282 ASSERT_EQ("google.com", certs[0]->server_identifier()); 283 ASSERT_EQ(GetTestCertExpirationTime(), 284 certs[0]->expiration_time()); 285 ASSERT_EQ(key_data, certs[0]->private_key()); 286 ASSERT_EQ(cert_data, certs[0]->cert()); 287 288 ASSERT_EQ("foo.com", certs[1]->server_identifier()); 289 // Undecodable cert, expiration time will be uninitialized. 290 ASSERT_EQ(base::Time(), certs[1]->expiration_time()); 291 ASSERT_STREQ("\xaa", certs[1]->private_key().c_str()); 292 ASSERT_STREQ("\xbb", certs[1]->cert().c_str()); 293 294 store_ = NULL; 295 // Make sure we wait until the destructor has run. 296 base::RunLoop().RunUntilIdle(); 297 298 // Verify the database version is updated. 299 { 300 sql::Connection db; 301 ASSERT_TRUE(db.Open(v2_db_path)); 302 sql::Statement smt(db.GetUniqueStatement( 303 "SELECT value FROM meta WHERE key = \"version\"")); 304 ASSERT_TRUE(smt.Step()); 305 EXPECT_EQ(4, smt.ColumnInt(0)); 306 EXPECT_FALSE(smt.Step()); 307 } 308 } 309 } 310 311 TEST_F(SQLiteServerBoundCertStoreTest, TestUpgradeV3) { 312 // Reset the store. We'll be using a different database for this test. 313 store_ = NULL; 314 315 base::FilePath v3_db_path(temp_dir_.path().AppendASCII("v3db")); 316 317 std::string key_data; 318 std::string cert_data; 319 ReadTestKeyAndCert(&key_data, &cert_data); 320 321 // Create a version 3 database. 322 { 323 sql::Connection db; 324 ASSERT_TRUE(db.Open(v3_db_path)); 325 ASSERT_TRUE(db.Execute( 326 "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY," 327 "value LONGVARCHAR);" 328 "INSERT INTO \"meta\" VALUES('version','3');" 329 "INSERT INTO \"meta\" VALUES('last_compatible_version','1');" 330 "CREATE TABLE origin_bound_certs (" 331 "origin TEXT NOT NULL UNIQUE PRIMARY KEY," 332 "private_key BLOB NOT NULL," 333 "cert BLOB NOT NULL," 334 "cert_type INTEGER," 335 "expiration_time INTEGER);" 336 )); 337 338 sql::Statement add_smt(db.GetUniqueStatement( 339 "INSERT INTO origin_bound_certs (origin, private_key, cert, cert_type, " 340 "expiration_time) VALUES (?,?,?,?,?)")); 341 add_smt.BindString(0, "google.com"); 342 add_smt.BindBlob(1, key_data.data(), key_data.size()); 343 add_smt.BindBlob(2, cert_data.data(), cert_data.size()); 344 add_smt.BindInt64(3, 64); 345 add_smt.BindInt64(4, 1000); 346 ASSERT_TRUE(add_smt.Run()); 347 348 ASSERT_TRUE(db.Execute( 349 "INSERT INTO \"origin_bound_certs\" VALUES(" 350 "'foo.com',X'AA',X'BB',64,2000);" 351 )); 352 } 353 354 // Load and test the DB contents twice. First time ensures that we can use 355 // the updated values immediately. Second time ensures that the updated 356 // values are saved and read correctly on next load. 357 for (int i = 0; i < 2; ++i) { 358 SCOPED_TRACE(i); 359 360 ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert> certs; 361 store_ = new SQLiteServerBoundCertStore(v3_db_path, NULL); 362 363 // Load the database and ensure the certs can be read. 364 Load(&certs); 365 ASSERT_EQ(2U, certs.size()); 366 367 ASSERT_EQ("google.com", certs[0]->server_identifier()); 368 ASSERT_EQ(1000, certs[0]->expiration_time().ToInternalValue()); 369 ASSERT_EQ(GetTestCertCreationTime(), 370 certs[0]->creation_time()); 371 ASSERT_EQ(key_data, certs[0]->private_key()); 372 ASSERT_EQ(cert_data, certs[0]->cert()); 373 374 ASSERT_EQ("foo.com", certs[1]->server_identifier()); 375 ASSERT_EQ(2000, certs[1]->expiration_time().ToInternalValue()); 376 // Undecodable cert, creation time will be uninitialized. 377 ASSERT_EQ(base::Time(), certs[1]->creation_time()); 378 ASSERT_STREQ("\xaa", certs[1]->private_key().c_str()); 379 ASSERT_STREQ("\xbb", certs[1]->cert().c_str()); 380 381 store_ = NULL; 382 // Make sure we wait until the destructor has run. 383 base::RunLoop().RunUntilIdle(); 384 385 // Verify the database version is updated. 386 { 387 sql::Connection db; 388 ASSERT_TRUE(db.Open(v3_db_path)); 389 sql::Statement smt(db.GetUniqueStatement( 390 "SELECT value FROM meta WHERE key = \"version\"")); 391 ASSERT_TRUE(smt.Step()); 392 EXPECT_EQ(4, smt.ColumnInt(0)); 393 EXPECT_FALSE(smt.Step()); 394 } 395 } 396 } 397 398 TEST_F(SQLiteServerBoundCertStoreTest, TestRSADiscarded) { 399 // Reset the store. We'll be using a different database for this test. 400 store_ = NULL; 401 402 base::FilePath v4_db_path(temp_dir_.path().AppendASCII("v4dbrsa")); 403 404 std::string key_data; 405 std::string cert_data; 406 ReadTestKeyAndCert(&key_data, &cert_data); 407 408 // Create a version 4 database with a mix of RSA and ECDSA certs. 409 { 410 sql::Connection db; 411 ASSERT_TRUE(db.Open(v4_db_path)); 412 ASSERT_TRUE(db.Execute( 413 "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY," 414 "value LONGVARCHAR);" 415 "INSERT INTO \"meta\" VALUES('version','4');" 416 "INSERT INTO \"meta\" VALUES('last_compatible_version','1');" 417 "CREATE TABLE origin_bound_certs (" 418 "origin TEXT NOT NULL UNIQUE PRIMARY KEY," 419 "private_key BLOB NOT NULL," 420 "cert BLOB NOT NULL," 421 "cert_type INTEGER," 422 "expiration_time INTEGER," 423 "creation_time INTEGER);" 424 )); 425 426 sql::Statement add_smt(db.GetUniqueStatement( 427 "INSERT INTO origin_bound_certs " 428 "(origin, private_key, cert, cert_type, expiration_time, creation_time)" 429 " VALUES (?,?,?,?,?,?)")); 430 add_smt.BindString(0, "google.com"); 431 add_smt.BindBlob(1, key_data.data(), key_data.size()); 432 add_smt.BindBlob(2, cert_data.data(), cert_data.size()); 433 add_smt.BindInt64(3, 64); 434 add_smt.BindInt64(4, GetTestCertExpirationTime().ToInternalValue()); 435 add_smt.BindInt64(5, base::Time::Now().ToInternalValue()); 436 ASSERT_TRUE(add_smt.Run()); 437 438 add_smt.Clear(); 439 add_smt.Assign(db.GetUniqueStatement( 440 "INSERT INTO origin_bound_certs " 441 "(origin, private_key, cert, cert_type, expiration_time, creation_time)" 442 " VALUES (?,?,?,?,?,?)")); 443 add_smt.BindString(0, "foo.com"); 444 add_smt.BindBlob(1, key_data.data(), key_data.size()); 445 add_smt.BindBlob(2, cert_data.data(), cert_data.size()); 446 add_smt.BindInt64(3, 1); 447 add_smt.BindInt64(4, GetTestCertExpirationTime().ToInternalValue()); 448 add_smt.BindInt64(5, base::Time::Now().ToInternalValue()); 449 ASSERT_TRUE(add_smt.Run()); 450 } 451 452 ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert> certs; 453 store_ = new SQLiteServerBoundCertStore(v4_db_path, NULL); 454 455 // Load the database and ensure the certs can be read. 456 Load(&certs); 457 // Only the ECDSA cert (for google.com) is read, the RSA one is discarded. 458 ASSERT_EQ(1U, certs.size()); 459 460 ASSERT_EQ("google.com", certs[0]->server_identifier()); 461 ASSERT_EQ(GetTestCertExpirationTime(), 462 certs[0]->expiration_time()); 463 ASSERT_EQ(key_data, certs[0]->private_key()); 464 ASSERT_EQ(cert_data, certs[0]->cert()); 465 466 store_ = NULL; 467 // Make sure we wait until the destructor has run. 468 base::RunLoop().RunUntilIdle(); 469 } 470