diff options
author | rafaelw@chromium.org <rafaelw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-12 20:45:45 +0000 |
---|---|---|
committer | rafaelw@chromium.org <rafaelw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-12 20:45:45 +0000 |
commit | fbcc4030655802213aab5b731f1180062d232f2d (patch) | |
tree | 41da9d881467d61d9ecde52306f5aacebdcf9d33 /chrome/browser/extensions/extensions_service.cc | |
parent | cdb4266b62202b44eb1fb36877398c2cb5504917 (diff) | |
download | chromium_src-fbcc4030655802213aab5b731f1180062d232f2d.zip chromium_src-fbcc4030655802213aab5b731f1180062d232f2d.tar.gz chromium_src-fbcc4030655802213aab5b731f1180062d232f2d.tar.bz2 |
Verify signed .crx extension installations
This is second try of:
http://codereview.chromium.org/115682
that was comitted in in 18189 and reverted.
BUG=12114
R=erikkay,wtc,aa
Review URL: http://codereview.chromium.org/126014
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@18316 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/extensions/extensions_service.cc')
-rw-r--r-- | chrome/browser/extensions/extensions_service.cc | 251 |
1 files changed, 203 insertions, 48 deletions
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index bf79e78..6d99e84 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -6,6 +6,7 @@ #include "app/l10n_util.h" #include "base/command_line.h" +#include "base/crypto/signature_verifier.h" #include "base/file_util.h" #include "base/gfx/png_encoder.h" #include "base/scoped_handle.h" @@ -20,6 +21,7 @@ #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_thread.h" +#include "chrome/browser/extensions/extension_creator.h" #include "chrome/browser/extensions/extension_browser_event_router.h" #include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/external_extension_provider.h" @@ -38,6 +40,7 @@ #include "chrome/common/url_constants.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" +#include "net/base/base64.h" #include "third_party/skia/include/core/SkBitmap.h" #if defined(OS_WIN) @@ -48,6 +51,8 @@ // ExtensionsService. +const char ExtensionsService::kExtensionHeaderMagic[] = "Cr24"; + const char* ExtensionsService::kInstallDirectoryName = "Extensions"; const char* ExtensionsService::kCurrentVersionFileName = "Current Version"; const char* ExtensionsServiceBackend::kTempExtensionName = "TEMP_INSTALL"; @@ -65,8 +70,17 @@ const wchar_t kState[] = L"state"; // A temporary subdirectory where we unpack extensions. const char* kUnpackExtensionDir = "TEMP_UNPACK"; -// The version of the extension package that this code understands. -const uint32 kExpectedVersion = 1; +// Unpacking errors +const char* kBadMagicNumberError = "Bad magic number"; +const char* kBadHeaderSizeError = "Excessively large key or signature"; +const char* kBadVersionNumberError = "Bad version number"; +const char* kInvalidExtensionHeaderError = "Invalid extension header"; +const char* kInvalidPublicKeyError = "Invalid public key"; +const char* kInvalidSignatureError = "Invalid signature"; +const char* kSignatureVerificationFailed = "Signature verification failed"; +const char* kSignatureVerificationInitFailed = + "Signature verification initialization failed. This is most likely " + "caused by a public key in the wrong format (should encode algorithm)."; } // This class coordinates an extension unpack task which is run in a separate @@ -77,11 +91,12 @@ class ExtensionsServiceBackend::UnpackerClient public: UnpackerClient(ExtensionsServiceBackend* backend, const FilePath& extension_path, + const std::string& public_key, const std::string& expected_id, bool from_external) : backend_(backend), extension_path_(extension_path), - expected_id_(expected_id), from_external_(from_external), - got_response_(false) { + public_key_(public_key), expected_id_(expected_id), + from_external_(from_external), got_response_(false) { } // Starts the unpack task. We call back to the backend when the task is done, @@ -146,6 +161,14 @@ class ExtensionsServiceBackend::UnpackerClient void OnUnpackExtensionSucceededImpl( const DictionaryValue& manifest, const ExtensionUnpacker::DecodedImages& images) { + // Add our public key into the parsed manifest. We want it to be saved so + // that we can later refer to it (eg for generating ids, validating + // signatures, etc). + // The const_cast is hacky, but seems like the right thing here, rather than + // making a full copy just to make this change. + const_cast<DictionaryValue*>(&manifest)->SetString( + Extension::kPublicKeyKey, public_key_); + // The extension was unpacked to the temp dir inside our unpacking dir. FilePath extension_dir = temp_extension_path_.DirName().AppendASCII( ExtensionsServiceBackend::kTempExtensionName); @@ -182,6 +205,9 @@ class ExtensionsServiceBackend::UnpackerClient // The path to the crx file that we're installing. FilePath extension_path_; + // The public key of the extension we're installing. + std::string public_key_; + // The path to the copy of the crx file in the temporary directory where we're // unpacking it. FilePath temp_extension_path_; @@ -355,7 +381,7 @@ void ExtensionsService::OnExtensionsLoaded(ExtensionList* new_extensions) { } void ExtensionsService::OnExtensionInstalled(Extension* extension, - bool update) { + Extension::InstallType install_type) { UpdateExtensionPref(ASCIIToWide(extension->id()), kState, Value::CreateIntegerValue(Extension::ENABLED), false); UpdateExtensionPref(ASCIIToWide(extension->id()), kLocation, @@ -385,7 +411,7 @@ void ExtensionsService::OnExternalExtensionInstalled( Value::CreateIntegerValue(location), true); } -void ExtensionsService::OnExtensionVersionReinstalled(const std::string& id) { +void ExtensionsService::OnExtensionOverinstallAttempted(const std::string& id) { Extension* extension = GetExtensionByID(id); if (extension && extension->IsTheme()) { NotificationService::current()->Notify( @@ -630,6 +656,21 @@ void ExtensionsServiceBackend::LoadSingleExtension( } } +DictionaryValue* ExtensionsServiceBackend::ReadManifest(FilePath manifest_path, + std::string* error) { + JSONFileValueSerializer serializer(manifest_path); + scoped_ptr<Value> root(serializer.Deserialize(error)); + if (!root.get()) + return NULL; + + if (!root->IsType(Value::TYPE_DICTIONARY)) { + *error = Extension::kInvalidManifestError; + return NULL; + } + + return static_cast<DictionaryValue*>(root.release()); +} + Extension* ExtensionsServiceBackend::LoadExtension( const FilePath& extension_path, Extension::Location location, @@ -641,22 +682,15 @@ Extension* ExtensionsServiceBackend::LoadExtension( return NULL; } - JSONFileValueSerializer serializer(manifest_path); std::string error; - scoped_ptr<Value> root(serializer.Deserialize(&error)); + scoped_ptr<DictionaryValue> root(ReadManifest(manifest_path, &error)); if (!root.get()) { ReportExtensionLoadError(extension_path, error); return NULL; } - if (!root->IsType(Value::TYPE_DICTIONARY)) { - ReportExtensionLoadError(extension_path, Extension::kInvalidManifestError); - return NULL; - } - scoped_ptr<Extension> extension(new Extension(extension_path)); - if (!extension->InitFromValue(*static_cast<DictionaryValue*>(root.get()), - require_id, &error)) { + if (!extension->InitFromValue(*root.get(), require_id, &error)) { ReportExtensionLoadError(extension_path, error); return NULL; } @@ -765,28 +799,36 @@ bool ExtensionsServiceBackend::ReadCurrentVersion(const FilePath& dir, return false; } -bool ExtensionsServiceBackend::CheckCurrentVersion( +Extension::InstallType ExtensionsServiceBackend::CompareToInstalledVersion( + const std::string& id, const std::string& new_version_str, - const std::string& current_version_str, - const FilePath& dest_dir) { + std::string *current_version_str) { + CHECK(current_version_str); + FilePath dir(install_directory_.AppendASCII(id.c_str())); + if (!ReadCurrentVersion(dir, current_version_str)) + return Extension::NEW_INSTALL; + scoped_ptr<Version> current_version( - Version::GetVersionFromString(current_version_str)); + Version::GetVersionFromString(*current_version_str)); scoped_ptr<Version> new_version( - Version::GetVersionFromString(new_version_str)); - if (current_version->CompareTo(*new_version) >= 0) { - // Verify that the directory actually exists. If it doesn't we'll return - // true so that the install code will repair the broken installation. - // TODO(erikkay): A further step would be to verify that the extension - // has actually loaded successfully. - FilePath version_dir = dest_dir.AppendASCII(current_version_str); - if (file_util::PathExists(version_dir)) { - std::string id = WideToASCII(dest_dir.BaseName().ToWStringHack()); - StringToLowerASCII(&id); - ReportExtensionVersionReinstalled(id); - return false; - } - } - return true; + Version::GetVersionFromString(new_version_str)); + int comp = new_version->CompareTo(*current_version); + if (comp > 0) + return Extension::UPGRADE; + else if (comp == 0) + return Extension::REINSTALL; + else + return Extension::DOWNGRADE; +} + +bool ExtensionsServiceBackend::NeedsReinstall(const std::string& id, + const std::string& current_version) { + // Verify that the directory actually exists. + // TODO(erikkay): A further step would be to verify that the extension + // has actually loaded successfully. + FilePath dir(install_directory_.AppendASCII(id.c_str())); + FilePath version_dir(dir.AppendASCII(current_version)); + return !file_util::PathExists(version_dir); } bool ExtensionsServiceBackend::InstallDirSafely(const FilePath& source_dir, @@ -873,13 +915,107 @@ void ExtensionsServiceBackend::InstallExtension( } void ExtensionsServiceBackend::InstallOrUpdateExtension( - const FilePath& extension_path, const std::string& expected_id, + const FilePath& extension_path, + const std::string& expected_id, bool from_external) { - UnpackerClient* client = - new UnpackerClient(this, extension_path, expected_id, from_external); + std::string actual_public_key; + if (!ValidateSignature(extension_path, &actual_public_key)) + return; // Failures reported within ValidateSignature(). + + UnpackerClient* client = new UnpackerClient( + this, extension_path, actual_public_key, expected_id, from_external); client->Start(); } +bool ExtensionsServiceBackend::ValidateSignature(const FilePath& extension_path, + std::string* key_out) { + ScopedStdioHandle file(file_util::OpenFile(extension_path, "rb")); + if (!file.get()) { + ReportExtensionInstallError(extension_path, "Could not open file."); + return NULL; + } + + // Read and verify the header. + ExtensionsService::ExtensionHeader header; + size_t len; + + // TODO(erikkay): Yuck. I'm not a big fan of this kind of code, but it + // appears that we don't have any endian/alignment aware serialization + // code in the code base. So for now, this assumes that we're running + // on a little endian machine with 4 byte alignment. + len = fread(&header, 1, sizeof(ExtensionsService::ExtensionHeader), + file.get()); + if (len < sizeof(ExtensionsService::ExtensionHeader)) { + ReportExtensionInstallError(extension_path, kInvalidExtensionHeaderError); + return false; + } + if (strncmp(ExtensionsService::kExtensionHeaderMagic, header.magic, + sizeof(header.magic))) { + ReportExtensionInstallError(extension_path, kBadMagicNumberError); + return false; + } + if (header.version != ExtensionsService::kCurrentVersion) { + ReportExtensionInstallError(extension_path, kBadVersionNumberError); + return false; + } + if (header.key_size > ExtensionsService::kMaxPublicKeySize || + header.signature_size > ExtensionsService::kMaxSignatureSize) { + ReportExtensionInstallError(extension_path, kBadHeaderSizeError); + return false; + } + + std::vector<uint8> key; + key.resize(header.key_size); + len = fread(&key.front(), sizeof(uint8), header.key_size, file.get()); + if (len < header.key_size) { + ReportExtensionInstallError(extension_path, kInvalidPublicKeyError); + return false; + } + + std::vector<uint8> signature; + signature.resize(header.signature_size); + len = fread(&signature.front(), sizeof(uint8), header.signature_size, + file.get()); + if (len < header.signature_size) { + ReportExtensionInstallError(extension_path, kInvalidSignatureError); + return false; + } + + // Note: this structure is an ASN.1 which encodes the algorithm used + // with its parameters. This is defined in PKCS #1 v2.1 (RFC 3447). + // It is encoding: { OID sha1WithRSAEncryption PARAMETERS NULL } + // TODO(aa): This needs to be factored away someplace common. + const uint8 signature_algorithm[15] = { + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00 + }; + + base::SignatureVerifier verifier; + if (!verifier.VerifyInit(signature_algorithm, + sizeof(signature_algorithm), + &signature.front(), + signature.size(), + &key.front(), + key.size())) { + ReportExtensionInstallError(extension_path, + kSignatureVerificationInitFailed); + return false; + } + + unsigned char buf[1 << 12]; + while ((len = fread(buf, 1, sizeof(buf), file.get())) > 0) + verifier.VerifyUpdate(buf, len); + + if (!verifier.VerifyFinal()) { + ReportExtensionInstallError(extension_path, kSignatureVerificationFailed); + return false; + } + + net::Base64Encode(std::string(reinterpret_cast<char*>(&key.front()), + key.size()), key_out); + return true; +} + void ExtensionsServiceBackend::OnExtensionUnpacked( const FilePath& extension_path, const FilePath& temp_extension_dir, @@ -942,11 +1078,25 @@ void ExtensionsServiceBackend::OnExtensionUnpacked( FilePath dest_dir = install_directory_.AppendASCII(extension.id()); std::string version = extension.VersionString(); std::string current_version; - bool was_update = false; - if (ReadCurrentVersion(dest_dir, ¤t_version)) { - if (!CheckCurrentVersion(version, current_version, dest_dir)) + Extension::InstallType install_type = + CompareToInstalledVersion(extension.id(), version, ¤t_version); + + // Do not allow downgrade. + if (install_type == Extension::DOWNGRADE) { + ReportExtensionInstallError(extension_path, + "Error: Attempt to downgrade extension from more recent version."); + return; + } + + if (install_type == Extension::REINSTALL) { + if (NeedsReinstall(extension.id(), current_version)) { + // Treat corrupted existing installation as new install case. + install_type = Extension::NEW_INSTALL; + } else { + // The client may use this as a signal (to switch themes, for instance). + ReportExtensionOverinstallAttempted(extension.id()); return; - was_update = true; + } } // Write our parsed manifest back to disk, to ensure it doesn't contain an @@ -1044,7 +1194,7 @@ void ExtensionsServiceBackend::OnExtensionUnpacked( frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod( frontend_, &ExtensionsService::OnExtensionInstalled, extension, - was_update)); + install_type)); // Only one extension, but ReportExtensionsLoaded can handle multiple, // so we need to construct a list. @@ -1073,10 +1223,10 @@ void ExtensionsServiceBackend::ReportExtensionInstallError( ExtensionErrorReporter::GetInstance()->ReportError(message, alert_on_error_); } -void ExtensionsServiceBackend::ReportExtensionVersionReinstalled( +void ExtensionsServiceBackend::ReportExtensionOverinstallAttempted( const std::string& id) { frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod( - frontend_, &ExtensionsService::OnExtensionVersionReinstalled, id)); + frontend_, &ExtensionsService::OnExtensionOverinstallAttempted, id)); } bool ExtensionsServiceBackend::ShouldSkipInstallingExtension( @@ -1224,9 +1374,14 @@ void ExtensionsServiceBackend::OnExternalExtensionFound( bool ExtensionsServiceBackend::ShouldInstall(const std::string& id, const Version* version) { - FilePath dir(install_directory_.AppendASCII(id.c_str())); std::string current_version; - if (ReadCurrentVersion(dir, ¤t_version)) - return CheckCurrentVersion(version->GetString(), current_version, dir); - return true; + Extension::InstallType install_type = + CompareToInstalledVersion(id, version->GetString(), ¤t_version); + + if (install_type == Extension::DOWNGRADE) + return false; + + return (install_type == Extension::UPGRADE || + install_type == Extension::NEW_INSTALL || + NeedsReinstall(id, current_version)); } |