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:
parent
a2327c4eb7
commit
c12b3ab399
|
@ -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)
|
||||||
|
|
|
@ -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-----
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 ""
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue