1c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// Copyright (c) 2013 The Chromium Authors. All rights reserved. 2c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 3c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// found in the LICENSE file. 4c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 5558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch#include "chromeos/cert_loader.h" 6c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 7c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include <algorithm> 8c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "base/bind.h" 105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "base/location.h" 11558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch#include "base/message_loop/message_loop_proxy.h" 12a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)#include "base/strings/string_number_conversions.h" 13c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "base/task_runner_util.h" 14c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "base/threading/worker_pool.h" 15c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "crypto/nss_util.h" 16a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "crypto/scoped_nss_types.h" 17c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "net/cert/nss_cert_database.h" 185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "net/cert/nss_cert_database_chromeos.h" 195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "net/cert/x509_certificate.h" 20c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 21c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)namespace chromeos { 22c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 23558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdochstatic CertLoader* g_cert_loader = NULL; 243240926e260ce088908e02ac07a6cf7b0c0cbf44Ben Murdoch 25558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch// static 26558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdochvoid CertLoader::Initialize() { 27558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch CHECK(!g_cert_loader); 28558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch g_cert_loader = new CertLoader(); 29558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch} 30558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch 31558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch// static 32558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdochvoid CertLoader::Shutdown() { 33558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch CHECK(g_cert_loader); 34558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch delete g_cert_loader; 35558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch g_cert_loader = NULL; 36558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch} 37558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch 38558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch// static 39558790d6acca3451cf3a6b497803a5f07d0bec58Ben MurdochCertLoader* CertLoader::Get() { 403240926e260ce088908e02ac07a6cf7b0c0cbf44Ben Murdoch CHECK(g_cert_loader) << "CertLoader::Get() called before Initialize()"; 41558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch return g_cert_loader; 42558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch} 43558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch 44558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch// static 45558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdochbool CertLoader::IsInitialized() { 46558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch return g_cert_loader; 47558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch} 48558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch 49c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)CertLoader::CertLoader() 505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) : certificates_loaded_(false), 5190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) certificates_update_required_(false), 5290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) certificates_update_running_(false), 535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) database_(NULL), 545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) force_hardware_backed_for_test_(false), 555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) cert_list_(new net::CertificateList), 565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) weak_factory_(this) { 57558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch} 58558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch 595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)CertLoader::~CertLoader() { 605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) net::CertDatabase::GetInstance()->RemoveObserver(this); 613551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)} 623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void CertLoader::StartWithNSSDB(net::NSSCertDatabase* database) { 645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) CHECK(!database_); 655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) database_ = database; 66c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Start observing cert database for changes. 685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Observing net::CertDatabase is preferred over observing |database_| 695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // directly, as |database_| observers receive only events generated directly 705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // by |database_|, so they may miss a few relevant ones. 715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // TODO(tbarzic): Once singleton NSSCertDatabase is removed, investigate if 725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // it would be OK to observe |database_| directly; or change NSSCertDatabase 735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // to send notification on all relevant changes. 745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) net::CertDatabase::GetInstance()->AddObserver(this); 753240926e260ce088908e02ac07a6cf7b0c0cbf44Ben Murdoch 765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) LoadCertificates(); 77c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 78c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 79c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void CertLoader::AddObserver(CertLoader::Observer* observer) { 80c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) observers_.AddObserver(observer); 81c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 82c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 83c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void CertLoader::RemoveObserver(CertLoader::Observer* observer) { 84c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) observers_.RemoveObserver(observer); 85c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 86c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 87c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool CertLoader::IsHardwareBacked() const { 88a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (force_hardware_backed_for_test_) 89a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return true; 90a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (!database_) 91a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return false; 92a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) crypto::ScopedPK11Slot slot(database_->GetPrivateSlot()); 931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (!slot) 941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return false; 95a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return PK11_IsHW(slot.get()); 96c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 97c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)bool CertLoader::IsCertificateHardwareBacked( 995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) const net::X509Certificate* cert) const { 1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (!database_) 1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return false; 1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return database_->IsHardwareBacked(cert); 10390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)} 104c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)bool CertLoader::CertificatesLoading() const { 1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return database_ && !certificates_loaded_; 107558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch} 108558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch 109c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch// static 110c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch// 111a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)// For background see this discussion on dev-tech-crypto.lists.mozilla.org: 112a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)// http://web.archiveorange.com/archive/v/6JJW7E40sypfZGtbkzxX 113a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)// 114a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)// NOTE: This function relies on the convention that the same PKCS#11 ID 115a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)// is shared between a certificate and its associated private and public 116a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)// keys. I tried to implement this with PK11_GetLowLevelKeyIDForCert(), 117a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)// but that always returns NULL on Chrome OS for me. 1185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)std::string CertLoader::GetPkcs11IdAndSlotForCert( 1195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) const net::X509Certificate& cert, 1205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) int* slot_id) { 1215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) DCHECK(slot_id); 1225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 123a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) CERTCertificateStr* cert_handle = cert.os_cert_handle(); 124a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) SECKEYPrivateKey *priv_key = 125a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) PK11_FindKeyByAnyCert(cert_handle, NULL /* wincx */); 126a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) if (!priv_key) 127a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) return std::string(); 128a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) 1295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *slot_id = static_cast<int>(PK11_GetSlotID(priv_key->pkcs11Slot)); 1305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 131a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) // Get the CKA_ID attribute for a key. 132a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) SECItem* sec_item = PK11_GetLowLevelKeyIDForPrivateKey(priv_key); 133a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) std::string pkcs11_id; 134a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) if (sec_item) { 135a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) pkcs11_id = base::HexEncode(sec_item->data, sec_item->len); 136a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) SECITEM_FreeItem(sec_item, PR_TRUE); 137a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) } 138a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) SECKEY_DestroyPrivateKey(priv_key); 139a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) 140a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) return pkcs11_id; 141a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)} 142a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) 1433551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)void CertLoader::LoadCertificates() { 144558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch CHECK(thread_checker_.CalledOnValidThread()); 1453551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) VLOG(1) << "LoadCertificates: " << certificates_update_running_; 14690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 14790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) if (certificates_update_running_) { 14890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) certificates_update_required_ = true; 14990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return; 15090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 15190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 15290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) certificates_update_running_ = true; 15390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) certificates_update_required_ = false; 1543240926e260ce088908e02ac07a6cf7b0c0cbf44Ben Murdoch 1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) database_->ListCerts( 1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::Bind(&CertLoader::UpdateCertificates, weak_factory_.GetWeakPtr())); 157c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 158c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void CertLoader::UpdateCertificates( 1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) scoped_ptr<net::CertificateList> cert_list) { 161c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) CHECK(thread_checker_.CalledOnValidThread()); 16290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) DCHECK(certificates_update_running_); 16390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) VLOG(1) << "UpdateCertificates: " << cert_list->size(); 164c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 16590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) // Ignore any existing certificates. 1665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) cert_list_ = cert_list.Pass(); 167c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 1683240926e260ce088908e02ac07a6cf7b0c0cbf44Ben Murdoch bool initial_load = !certificates_loaded_; 16990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) certificates_loaded_ = true; 1703240926e260ce088908e02ac07a6cf7b0c0cbf44Ben Murdoch NotifyCertificatesLoaded(initial_load); 171a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles) 17290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) certificates_update_running_ = false; 17390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) if (certificates_update_required_) 1743551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) LoadCertificates(); 175c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 176c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 177c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void CertLoader::NotifyCertificatesLoaded(bool initial_load) { 178c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) FOR_EACH_OBSERVER(Observer, observers_, 1795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) OnCertificatesLoaded(*cert_list_, initial_load)); 180c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 181c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 1821e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)void CertLoader::OnCACertChanged(const net::X509Certificate* cert) { 1831e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // This is triggered when a CA certificate is modified. 1841e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) VLOG(1) << "OnCACertChanged"; 1851e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) LoadCertificates(); 186c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 187c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 188c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void CertLoader::OnCertAdded(const net::X509Certificate* cert) { 1891e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // This is triggered when a client certificate is added. 19090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) VLOG(1) << "OnCertAdded"; 1913551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) LoadCertificates(); 192c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 193c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 194c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void CertLoader::OnCertRemoved(const net::X509Certificate* cert) { 19590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) VLOG(1) << "OnCertRemoved"; 1963551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) LoadCertificates(); 197c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 198c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 199c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} // namespace chromeos 200