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 ""