diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2a82d0231..b01f9c120 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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)
diff --git a/data/clementine-spotify-public.pem b/data/clementine-spotify-public.pem
new file mode 100644
index 000000000..8b9dc411a
--- /dev/null
+++ b/data/clementine-spotify-public.pem
@@ -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-----
diff --git a/data/data.qrc b/data/data.qrc
index f4a54f2e7..62407e433 100644
--- a/data/data.qrc
+++ b/data/data.qrc
@@ -329,5 +329,6 @@
providers/grooveshark.png
allthethings.png
globalsearch.css
+ clementine-spotify-public.pem
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e8b8f6e24..bca783b2a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -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)
diff --git a/src/internet/spotifyblobdownloader.cpp b/src/internet/spotifyblobdownloader.cpp
index a1eb07a87..e72911242 100644
--- a/src/internet/spotifyblobdownloader.cpp
+++ b/src/internet/spotifyblobdownloader.cpp
@@ -19,11 +19,16 @@
#include "spotifyservice.h"
#include "core/logging.h"
#include "core/network.h"
+#include "core/utilities.h"
#include
#include
#include
#include
+#include
+
+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();
}
diff --git a/src/internet/spotifyblobdownloader.h b/src/internet/spotifyblobdownloader.h
index f0300b3ff..7304aed4c 100644
--- a/src/internet/spotifyblobdownloader.h
+++ b/src/internet/spotifyblobdownloader.h
@@ -32,6 +32,8 @@ public:
QObject* parent = 0);
~SpotifyBlobDownloader();
+ static const char* kSignatureSuffix;
+
static bool Prompt();
void Start();
diff --git a/src/main.cpp b/src/main.cpp
index fdf7f402d..9ab034a9e 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -78,6 +78,10 @@ using boost::scoped_ptr;
#include
+#ifdef HAVE_SPOTIFY
+ #include
+#endif
+
#ifdef Q_OS_DARWIN
#include
#include
@@ -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);
diff --git a/src/translations/translations.pot b/src/translations/translations.pot
index 1009594ee..468165a44 100644
--- a/src/translations/translations.pot
+++ b/src/translations/translations.pot
@@ -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 ""