Verify the signatures of spotify blob files downloaded at runtime. Should protect against MITM attacks, and compromises of our server.

This commit is contained in:
David Sansome 2011-11-05 01:12:28 +00:00
parent a2327c4eb7
commit c12b3ab399
8 changed files with 104 additions and 11 deletions

View File

@ -68,6 +68,7 @@ pkg_check_modules(LIBMTP libmtp>=1.0)
pkg_check_modules(INDICATEQT indicate-qt) pkg_check_modules(INDICATEQT indicate-qt)
pkg_check_modules(SPOTIFY libspotify>=0.0.8) pkg_check_modules(SPOTIFY libspotify>=0.0.8)
pkg_check_modules(CDIO libcdio) pkg_check_modules(CDIO libcdio)
pkg_check_modules(QCA qca2)
if (WIN32) if (WIN32)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
@ -233,9 +234,9 @@ if(ENABLE_BREAKPAD)
set(HAVE_BREAKPAD ON) set(HAVE_BREAKPAD ON)
endif(ENABLE_BREAKPAD) endif(ENABLE_BREAKPAD)
if(ENABLE_SPOTIFY AND PROTOBUF_FOUND AND PROTOBUF_PROTOC_EXECUTABLE) if(ENABLE_SPOTIFY AND PROTOBUF_FOUND AND PROTOBUF_PROTOC_EXECUTABLE AND QCA_FOUND)
set(HAVE_SPOTIFY ON) set(HAVE_SPOTIFY ON)
endif(ENABLE_SPOTIFY AND PROTOBUF_FOUND AND PROTOBUF_PROTOC_EXECUTABLE) endif(ENABLE_SPOTIFY AND PROTOBUF_FOUND AND PROTOBUF_PROTOC_EXECUTABLE AND QCA_FOUND)
if(ENABLE_SPOTIFY_BLOB AND PROTOBUF_FOUND AND PROTOBUF_PROTOC_EXECUTABLE AND SPOTIFY_FOUND) if(ENABLE_SPOTIFY_BLOB AND PROTOBUF_FOUND AND PROTOBUF_PROTOC_EXECUTABLE AND SPOTIFY_FOUND)
set(HAVE_SPOTIFY_BLOB ON) set(HAVE_SPOTIFY_BLOB ON)

View File

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqAvoUlyct8cM/dM5OBwP
sqXNJEbxdMsbtX8fXErcj+r1qSlOG1KtFZmVXw8FZtq3WQddgb95w/T8zvP1v0dt
6ZbsYlsE+TqTiTcN1rpbVi33SpcWGZ7d2o+bbTEaVagVuwccPDvfMu005/zzFl0f
iRVg2zLkFYuLRDZpOwbgw7KBKTVqaB/aeRK/sjXSjaZdizuxW4WnmKYWML3h4RpZ
kg5+faMQiG9sA0iH27PzXPZWVG9Py2ypyVA4pSQIZFveYbr9XpcxPhcEalXbxNU8
S7OzDbWmjNIh7KCMy2F7NxnW92b+7WRUcjp953U2LKtcppZK3AOlnETzkHcmB7cD
sQIDAQAB
-----END PUBLIC KEY-----

View File

@ -329,5 +329,6 @@
<file>providers/grooveshark.png</file> <file>providers/grooveshark.png</file>
<file>allthethings.png</file> <file>allthethings.png</file>
<file>globalsearch.css</file> <file>globalsearch.css</file>
<file>clementine-spotify-public.pem</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -709,6 +709,8 @@ optional_source(HAVE_SPOTIFY
internet/spotifyservice.h internet/spotifyservice.h
internet/spotifysettingspage.h internet/spotifysettingspage.h
resolvers/spotifyresolver.h resolvers/spotifyresolver.h
INCLUDE_DIRECTORIES
${QCA_INCLUDE_DIRS}
) )
# Platform specific - OS X # Platform specific - OS X
@ -1047,6 +1049,7 @@ if(HAVE_SPOTIFY)
target_link_libraries(clementine_lib target_link_libraries(clementine_lib
clementine-spotifyblob-messages clementine-spotifyblob-messages
gstspotifytcpsrc gstspotifytcpsrc
${QCA_LIBRARIES}
) )
endif(HAVE_SPOTIFY) endif(HAVE_SPOTIFY)

View File

@ -19,11 +19,16 @@
#include "spotifyservice.h" #include "spotifyservice.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/network.h" #include "core/network.h"
#include "core/utilities.h"
#include <QDir> #include <QDir>
#include <QMessageBox> #include <QMessageBox>
#include <QNetworkReply> #include <QNetworkReply>
#include <QProgressDialog> #include <QProgressDialog>
#include <QtCrypto>
const char* SpotifyBlobDownloader::kSignatureSuffix = ".sha1";
SpotifyBlobDownloader::SpotifyBlobDownloader( SpotifyBlobDownloader::SpotifyBlobDownloader(
const QString& version, const QString& path, QObject* parent) const QString& version, const QString& path, QObject* parent)
@ -56,7 +61,10 @@ void SpotifyBlobDownloader::Start() {
qDeleteAll(replies_); qDeleteAll(replies_);
replies_.clear(); replies_.clear();
const QStringList filenames = QStringList() << "blob" << "libspotify.so.8"; const QStringList filenames = QStringList()
<< "blob"
<< "blob" + QString(kSignatureSuffix)
<< "libspotify.so.8";
foreach (const QString& filename, filenames) { foreach (const QString& filename, filenames) {
const QUrl url(SpotifyService::kBlobDownloadUrl + version_ + "/" + filename); const QUrl url(SpotifyService::kBlobDownloadUrl + version_ + "/" + filename);
@ -87,12 +95,14 @@ void SpotifyBlobDownloader::ReplyFinished() {
} }
} }
// Make the destination directory and write the files into it // Let's verify signatures in a temporary directory first, before we write
QDir().mkpath(path_); // anything into its final position.
QString temp_directory = Utilities::MakeTempDir();
QStringList signatures;
foreach (QNetworkReply* reply, replies_) { foreach (QNetworkReply* reply, replies_) {
const QString filename = reply->url().path().section('/', -1, -1); const QString filename = reply->url().path().section('/', -1, -1);
const QString path = path_ + "/" + filename; const QString path = temp_directory + "/" + filename;
qLog(Info) << "Saving file" << path; qLog(Info) << "Saving file" << path;
QFile file(path); QFile file(path);
@ -101,10 +111,69 @@ void SpotifyBlobDownloader::ReplyFinished() {
return; return;
} }
if (path.endsWith(kSignatureSuffix)) {
signatures << path;
}
file.setPermissions(QFile::Permissions(0x7755)); file.setPermissions(QFile::Permissions(0x7755));
file.write(reply->readAll()); file.write(reply->readAll());
} }
// Load the public key
QCA::ConvertResult conversion_result;
QCA::PublicKey key = QCA::PublicKey::fromPEMFile(":/clementine-spotify-public.pem",
&conversion_result);
if (QCA::ConvertGood != conversion_result) {
ShowError("Failed to load Spotify public key");
return;
}
// Verify signatures
foreach (const QString& signature_filename, signatures) {
QString filename = signature_filename;
filename.remove(kSignatureSuffix);
qLog(Debug) << "Verifying" << filename << "against" << signature_filename;
QFile actual_file(filename);
if (!actual_file.open(QIODevice::ReadOnly))
return;
QFile signature_file(signature_filename);
if (!signature_file.open(QIODevice::ReadOnly))
return;
if (!key.verifyMessage(actual_file.readAll(), signature_file.readAll(),
QCA::EMSA3_SHA1)) {
ShowError("Invalid signature: " + filename);
return;
}
qLog(Debug) << "Verification OK";
}
// Make the destination directory and write the files into it
QDir().mkpath(path_);
foreach (QNetworkReply* reply, replies_) {
const QString filename = reply->url().path().section('/', -1, -1);
const QString source_path = temp_directory + "/" + filename;
const QString dest_path = path_ + "/" + filename;
if (filename.endsWith(kSignatureSuffix))
continue;
qLog(Info) << "Moving" << source_path << "to" << dest_path;
if (!QFile::rename(source_path, dest_path)) {
ShowError("Writing file failed: " + dest_path);
return;
}
}
// Remove the temporary directory
Utilities::RemoveRecursive(temp_directory);
EmitFinished(); EmitFinished();
} }

View File

@ -32,6 +32,8 @@ public:
QObject* parent = 0); QObject* parent = 0);
~SpotifyBlobDownloader(); ~SpotifyBlobDownloader();
static const char* kSignatureSuffix;
static bool Prompt(); static bool Prompt();
void Start(); void Start();

View File

@ -78,6 +78,10 @@ using boost::scoped_ptr;
#include <echonest/Config.h> #include <echonest/Config.h>
#ifdef HAVE_SPOTIFY
#include <QtCrypto>
#endif
#ifdef Q_OS_DARWIN #ifdef Q_OS_DARWIN
#include <sys/resource.h> #include <sys/resource.h>
#include <sys/sysctl.h> #include <sys/sysctl.h>
@ -369,6 +373,10 @@ int main(int argc, char *argv[]) {
} }
#endif #endif
#ifdef HAVE_SPOTIFY
QCA::Initializer qca_initializer;
#endif
// Resources // Resources
Q_INIT_RESOURCE(data); Q_INIT_RESOURCE(data);
Q_INIT_RESOURCE(translations); Q_INIT_RESOURCE(translations);

View File

@ -566,7 +566,7 @@ msgstr ""
msgid "Always start playing" msgid "Always start playing"
msgstr "" msgstr ""
#: internet/spotifyblobdownloader.cpp:50 #: internet/spotifyblobdownloader.cpp:55
msgid "" msgid ""
"An additional plugin is required to use Spotify in Clementine. Would you " "An additional plugin is required to use Spotify in Clementine. Would you "
"like to download and install it now?" "like to download and install it now?"
@ -793,7 +793,7 @@ msgstr ""
msgid "CUE sheet support" msgid "CUE sheet support"
msgstr "" msgstr ""
#: internet/spotifyblobdownloader.cpp:34 #: internet/spotifyblobdownloader.cpp:39
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
@ -1432,7 +1432,7 @@ msgstr ""
msgid "Downloading Magnatune catalogue" msgid "Downloading Magnatune catalogue"
msgstr "" msgstr ""
#: internet/spotifyblobdownloader.cpp:34 #: internet/spotifyblobdownloader.cpp:39
msgid "Downloading Spotify plugin" msgid "Downloading Spotify plugin"
msgstr "" msgstr ""
@ -1584,7 +1584,7 @@ msgstr ""
msgid "Error deleting songs" msgid "Error deleting songs"
msgstr "" msgstr ""
#: internet/spotifyblobdownloader.cpp:137 #: internet/spotifyblobdownloader.cpp:205
msgid "Error downloading Spotify plugin" msgid "Error downloading Spotify plugin"
msgstr "" msgstr ""
@ -3696,7 +3696,7 @@ msgstr ""
msgid "Spotify plugin" msgid "Spotify plugin"
msgstr "" msgstr ""
#: internet/spotifyblobdownloader.cpp:49 #: internet/spotifyblobdownloader.cpp:54
msgid "Spotify plugin not installed" msgid "Spotify plugin not installed"
msgstr "" msgstr ""