From 319b8a58241076ae6c0a0ccc3f9707f0167acb0a Mon Sep 17 00:00:00 2001 From: John Maguire Date: Tue, 28 Apr 2015 12:25:42 +0100 Subject: [PATCH] Use SHA512 signatures for the spotify blob. SHA512 is unsupported by reasonable QCA versions so we must use libcrypto++ instead. --- CMakeLists.txt | 9 +- debian/control | 6 +- dist/clementine.spec.in | 4 +- src/CMakeLists.txt | 6 +- src/config.h.in | 2 +- .../spotify/spotifyblobdownloader.cpp | 101 +++++++++++++----- src/internet/spotify/spotifyblobdownloader.h | 7 +- src/main.cpp | 8 -- 8 files changed, 92 insertions(+), 51 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94f2bf979..c8cc3e692 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ find_package(FFTW3) pkg_check_modules(CDIO libcdio) pkg_check_modules(CHROMAPRINT REQUIRED libchromaprint) +pkg_check_modules(CRYPTOPP libcrypto++) pkg_check_modules(GIO gio-2.0) pkg_check_modules(GLIB REQUIRED glib-2.0) pkg_check_modules(GOBJECT REQUIRED gobject-2.0) @@ -71,7 +72,6 @@ pkg_check_modules(LIBMTP libmtp>=1.0) pkg_check_modules(LIBMYGPO_QT libmygpo-qt>=1.0.7) pkg_check_modules(LIBPULSE libpulse) pkg_check_modules(LIBXML libxml-2.0) -pkg_check_modules(QCA qca2) pkg_check_modules(QJSON REQUIRED QJson) pkg_check_modules(SPOTIFY libspotify>=12.1.45) pkg_check_modules(TAGLIB REQUIRED taglib>=1.6) @@ -273,10 +273,11 @@ optional_component(LIBPULSE ON "Pulse audio integration" optional_component(VISUALISATIONS ON "Visualisations") -if(NOT HAVE_SPOTIFY_BLOB AND NOT QCA_FOUND) - message(FATAL_ERROR "Either QCA must be available or the non-GPL Spotify " +if(NOT HAVE_SPOTIFY_BLOB AND NOT CRYPTOPP_FOUND) + message(FATAL_ERROR "Either crypto++ must be available or the non-GPL Spotify " "code must be compiled in") -elseif(QCA_FOUND) +elseif(CRYPTOPP_FOUND) + set(HAVE_CRYPTOPP ON) set(HAVE_SPOTIFY_DOWNLOADER ON) endif() diff --git a/debian/control b/debian/control index b79595c33..a36bbb877 100644 --- a/debian/control +++ b/debian/control @@ -15,6 +15,7 @@ Build-Depends: debhelper (>= 7), libboost-serialization-dev, libcdio-cdda1, libchromaprint-dev, + libcrypto++-dev, libechonest-dev, libglew1.5-dev | libglew-dev, @@ -25,14 +26,12 @@ Build-Depends: debhelper (>= 7), libgstreamer1.0-dev, libgstreamer-plugins-base1.0-dev, libgpod-dev, - libimobiledevice-dev, libplist-dev, libusbmuxd-dev, libmtp-dev, libqjson-dev, protobuf-compiler, libprotobuf-dev, - libqca2-dev, libfftw3-dev, libsparsehash-dev, libsqlite3-dev, @@ -49,8 +48,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, gstreamer1.0-plugins-good, gstreamer1.0-plugins-ugly, gstreamer1.0-pulseaudio, - libprojectm-data | projectm-data, - libqca2-plugin-ossl + libprojectm-data | projectm-data Description: Modern music player and library organiser inspired by Amarok 1.4 Clementine focuses on a fast and easy-to-use interface for searching and playing your music. diff --git a/dist/clementine.spec.in b/dist/clementine.spec.in index 353dc181c..b9098049f 100644 --- a/dist/clementine.spec.in +++ b/dist/clementine.spec.in @@ -13,11 +13,11 @@ BuildRequires: desktop-file-utils liblastfm-devel taglib-devel gettext BuildRequires: qt4-devel boost-devel gcc-c++ glew-devel libgpod-devel BuildRequires: cmake gstreamer1-devel gstreamer1-plugins-base-devel BuildRequires: libmtp-devel protobuf-devel protobuf-compiler libcdio-devel -BuildRequires: qjson-devel qca2-devel fftw-devel sparsehash-devel +BuildRequires: qjson-devel cryptopp-devel fftw-devel sparsehash-devel BuildRequires: sqlite-devel pulseaudio-libs-devel libechonest-devel BuildRequires: libchromaprint-devel -Requires: libgpod protobuf-lite libcdio qjson qca-ossl sqlite +Requires: libgpod protobuf-lite libcdio qjson sqlite # GStreamer codec dependencies Requires: gstreamer1-plugins-ugly diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0f656b57c..fc87333f6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -855,7 +855,7 @@ optional_source(HAVE_SPOTIFY_DOWNLOADER HEADERS internet/spotify/spotifyblobdownloader.h INCLUDE_DIRECTORIES - ${QCA_INCLUDE_DIRS} + ${CRYPTOPP_INCLUDE_DIRS} ) # Platform specific - OS X @@ -1316,9 +1316,9 @@ endif(HAVE_BREAKPAD) if(HAVE_SPOTIFY_DOWNLOADER) target_link_libraries(clementine_lib - ${QCA_LIBRARIES} + ${CRYPTOPP_LIBRARIES} ) - link_directories(${QCA_LIBRARY_DIRS}) + link_directories(${CRYPTOPP_LIBRARY_DIRS}) endif(HAVE_SPOTIFY_DOWNLOADER) if(HAVE_LIBPULSE) diff --git a/src/config.h.in b/src/config.h.in index 342919fbc..f22fe9bc8 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -25,6 +25,7 @@ #cmakedefine HAVE_AUDIOCD #cmakedefine HAVE_BOX #cmakedefine HAVE_BREAKPAD +#cmakedefine HAVE_CRYPTOPP #cmakedefine HAVE_DBUS #cmakedefine HAVE_DEVICEKIT #cmakedefine HAVE_DROPBOX @@ -36,7 +37,6 @@ #cmakedefine HAVE_LIBMTP #cmakedefine HAVE_LIBPULSE #cmakedefine HAVE_MOODBAR -#cmakedefine HAVE_QCA #cmakedefine HAVE_SEAFILE #cmakedefine HAVE_SKYDRIVE #cmakedefine HAVE_SPARKLE diff --git a/src/internet/spotify/spotifyblobdownloader.cpp b/src/internet/spotify/spotifyblobdownloader.cpp index dd8604bcf..af7279bdb 100644 --- a/src/internet/spotify/spotifyblobdownloader.cpp +++ b/src/internet/spotify/spotifyblobdownloader.cpp @@ -21,25 +21,34 @@ #include "config.h" #include "spotifyservice.h" +#include "core/arraysize.h" #include "core/logging.h" #include "core/network.h" #include "core/utilities.h" #include #include +#include +#include #include #include #include -#ifdef HAVE_QCA -#include -#endif // HAVE_QCA - #ifdef Q_OS_UNIX #include #endif -const char* SpotifyBlobDownloader::kSignatureSuffix = ".sha1"; +#ifdef HAVE_CRYPTOPP +#include +#include +#endif // HAVE_CRYPTOPP + +namespace { +static const char PEM_HEADER[] = "-----BEGIN PUBLIC KEY-----\n"; +static const char PEM_FOOTER[] = "\n-----END PUBLIC KEY-----"; +} + +const char* SpotifyBlobDownloader::kSignatureSuffix = ".sha512"; SpotifyBlobDownloader::SpotifyBlobDownloader(const QString& version, const QString& path, @@ -125,32 +134,11 @@ void SpotifyBlobDownloader::ReplyFinished() { file_data[filename] = reply->readAll(); } -#ifdef HAVE_QCA - // 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"); + if (!CheckSignature(file_data, signature_filenames)) { + qLog(Warning) << "Signature checks failed"; return; } - // Verify signatures - for (const QString& signature_filename : signature_filenames) { - QString actual_filename = signature_filename; - actual_filename.remove(kSignatureSuffix); - - qLog(Debug) << "Verifying" << actual_filename << "against" - << signature_filename; - - if (!key.verifyMessage(file_data[actual_filename], - file_data[signature_filename], QCA::EMSA3_SHA1)) { - ShowError("Invalid signature: " + actual_filename); - return; - } - } -#endif // HAVE_QCA - // Make the destination directory and write the files into it QDir().mkpath(path_); @@ -195,6 +183,63 @@ void SpotifyBlobDownloader::ReplyFinished() { EmitFinished(); } +bool SpotifyBlobDownloader::CheckSignature( + const QMap& file_data, + const QStringList& signature_filenames) { +#ifdef HAVE_CRYPTOPP + QFile public_key_file(":/clementine-spotify-public.pem"); + public_key_file.open(QIODevice::ReadOnly); + QByteArray public_key_data = ConvertPEMToDER(public_key_file.readAll()); + + try { + CryptoPP::ByteQueue bytes; + bytes.Put(reinterpret_cast(public_key_data.constData()), + public_key_data.size()); + bytes.MessageEnd(); + + CryptoPP::RSA::PublicKey public_key; + public_key.Load(bytes); + + CryptoPP::RSASS::Verifier verifier( + public_key); + + for (const QString& signature_filename : signature_filenames) { + QString actual_filename = signature_filename; + actual_filename.remove(kSignatureSuffix); + + const bool result = verifier.VerifyMessage( + reinterpret_cast(file_data[actual_filename].constData()), + file_data[actual_filename].size(), + reinterpret_cast( + file_data[signature_filename].constData()), + file_data[signature_filename].size()); + qLog(Debug) << "Verifying" << actual_filename << "against" + << signature_filename << result; + if (!result) { + ShowError("Invalid signature: " + actual_filename); + return false; + } + } + } catch (std::exception e) { + // This should only happen if we fail to parse our own key. + qLog(Debug) << "Verifying spotify blob signature failed:" << e.what(); + return false; + } + return true; +#else + return false; +#endif // HAVE_CRYPTOPP +} + +QByteArray SpotifyBlobDownloader::ConvertPEMToDER(const QByteArray& pem) { + // Ensure we used char[] not char* + static_assert(sizeof(PEM_HEADER) > sizeof(char*), "PEM_HEADER mis-declared"); + static_assert(sizeof(PEM_FOOTER) > sizeof(char*), "PEM_FOOTER mis-declared"); + int start = pem.indexOf(PEM_HEADER) + arraysize(PEM_HEADER) - 1; + int length = pem.indexOf(PEM_FOOTER) - start; + return QByteArray::fromBase64(pem.mid(start, length)); +} + void SpotifyBlobDownloader::ReplyProgress() { int progress = 0; int total = 0; diff --git a/src/internet/spotify/spotifyblobdownloader.h b/src/internet/spotify/spotifyblobdownloader.h index d5879840b..c51ea4f13 100644 --- a/src/internet/spotify/spotifyblobdownloader.h +++ b/src/internet/spotify/spotifyblobdownloader.h @@ -20,6 +20,7 @@ #ifndef INTERNET_SPOTIFY_SPOTIFYBLOBDOWNLOADER_H_ #define INTERNET_SPOTIFY_SPOTIFYBLOBDOWNLOADER_H_ +#include #include class QNetworkAccessManager; @@ -40,7 +41,7 @@ class SpotifyBlobDownloader : public QObject { void Start(); - signals: +signals: void Finished(); private slots: @@ -52,6 +53,10 @@ class SpotifyBlobDownloader : public QObject { void ShowError(const QString& message); void EmitFinished(); + bool CheckSignature(const QMap& file_data, + const QStringList& signature_filenames); + static QByteArray ConvertPEMToDER(const QByteArray& pem); + private: QString version_; QString path_; diff --git a/src/main.cpp b/src/main.cpp index 8e9ec381f..24ad9dddd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -78,10 +78,6 @@ #include -#ifdef HAVE_SPOTIFY_DOWNLOADER -#include -#endif - #ifdef Q_OS_DARWIN #include #include @@ -398,10 +394,6 @@ int main(int argc, char* argv[]) { } #endif -#ifdef HAVE_SPOTIFY_DOWNLOADER - QCA::Initializer qca_initializer; -#endif - // Resources Q_INIT_RESOURCE(data); Q_INIT_RESOURCE(translations);