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(SPOTIFY libspotify>=0.0.8)
pkg_check_modules(CDIO libcdio)
pkg_check_modules(QCA qca2)
if (WIN32)
find_package(ZLIB REQUIRED)
@ -233,9 +234,9 @@ if(ENABLE_BREAKPAD)
set(HAVE_BREAKPAD ON)
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)
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)
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>allthethings.png</file>
<file>globalsearch.css</file>
<file>clementine-spotify-public.pem</file>
</qresource>
</RCC>

View File

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

View File

@ -19,11 +19,16 @@
#include "spotifyservice.h"
#include "core/logging.h"
#include "core/network.h"
#include "core/utilities.h"
#include <QDir>
#include <QMessageBox>
#include <QNetworkReply>
#include <QProgressDialog>
#include <QtCrypto>
const char* SpotifyBlobDownloader::kSignatureSuffix = ".sha1";
SpotifyBlobDownloader::SpotifyBlobDownloader(
const QString& version, const QString& path, QObject* parent)
@ -56,7 +61,10 @@ void SpotifyBlobDownloader::Start() {
qDeleteAll(replies_);
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) {
const QUrl url(SpotifyService::kBlobDownloadUrl + version_ + "/" + filename);
@ -87,12 +95,14 @@ void SpotifyBlobDownloader::ReplyFinished() {
}
}
// Make the destination directory and write the files into it
QDir().mkpath(path_);
// Let's verify signatures in a temporary directory first, before we write
// anything into its final position.
QString temp_directory = Utilities::MakeTempDir();
QStringList signatures;
foreach (QNetworkReply* reply, replies_) {
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;
QFile file(path);
@ -101,10 +111,69 @@ void SpotifyBlobDownloader::ReplyFinished() {
return;
}
if (path.endsWith(kSignatureSuffix)) {
signatures << path;
}
file.setPermissions(QFile::Permissions(0x7755));
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();
}

View File

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

View File

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

View File

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