Use SHA512 signatures for the spotify blob.
SHA512 is unsupported by reasonable QCA versions so we must use libcrypto++ instead.
This commit is contained in:
parent
4cbe098b83
commit
319b8a5824
|
@ -58,6 +58,7 @@ find_package(FFTW3)
|
||||||
|
|
||||||
pkg_check_modules(CDIO libcdio)
|
pkg_check_modules(CDIO libcdio)
|
||||||
pkg_check_modules(CHROMAPRINT REQUIRED libchromaprint)
|
pkg_check_modules(CHROMAPRINT REQUIRED libchromaprint)
|
||||||
|
pkg_check_modules(CRYPTOPP libcrypto++)
|
||||||
pkg_check_modules(GIO gio-2.0)
|
pkg_check_modules(GIO gio-2.0)
|
||||||
pkg_check_modules(GLIB REQUIRED glib-2.0)
|
pkg_check_modules(GLIB REQUIRED glib-2.0)
|
||||||
pkg_check_modules(GOBJECT REQUIRED gobject-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(LIBMYGPO_QT libmygpo-qt>=1.0.7)
|
||||||
pkg_check_modules(LIBPULSE libpulse)
|
pkg_check_modules(LIBPULSE libpulse)
|
||||||
pkg_check_modules(LIBXML libxml-2.0)
|
pkg_check_modules(LIBXML libxml-2.0)
|
||||||
pkg_check_modules(QCA qca2)
|
|
||||||
pkg_check_modules(QJSON REQUIRED QJson)
|
pkg_check_modules(QJSON REQUIRED QJson)
|
||||||
pkg_check_modules(SPOTIFY libspotify>=12.1.45)
|
pkg_check_modules(SPOTIFY libspotify>=12.1.45)
|
||||||
pkg_check_modules(TAGLIB REQUIRED taglib>=1.6)
|
pkg_check_modules(TAGLIB REQUIRED taglib>=1.6)
|
||||||
|
@ -273,10 +273,11 @@ optional_component(LIBPULSE ON "Pulse audio integration"
|
||||||
|
|
||||||
optional_component(VISUALISATIONS ON "Visualisations")
|
optional_component(VISUALISATIONS ON "Visualisations")
|
||||||
|
|
||||||
if(NOT HAVE_SPOTIFY_BLOB AND NOT QCA_FOUND)
|
if(NOT HAVE_SPOTIFY_BLOB AND NOT CRYPTOPP_FOUND)
|
||||||
message(FATAL_ERROR "Either QCA must be available or the non-GPL Spotify "
|
message(FATAL_ERROR "Either crypto++ must be available or the non-GPL Spotify "
|
||||||
"code must be compiled in")
|
"code must be compiled in")
|
||||||
elseif(QCA_FOUND)
|
elseif(CRYPTOPP_FOUND)
|
||||||
|
set(HAVE_CRYPTOPP ON)
|
||||||
set(HAVE_SPOTIFY_DOWNLOADER ON)
|
set(HAVE_SPOTIFY_DOWNLOADER ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ Build-Depends: debhelper (>= 7),
|
||||||
libboost-serialization-dev,
|
libboost-serialization-dev,
|
||||||
libcdio-cdda1,
|
libcdio-cdda1,
|
||||||
libchromaprint-dev,
|
libchromaprint-dev,
|
||||||
|
libcrypto++-dev,
|
||||||
libechonest-dev,
|
libechonest-dev,
|
||||||
libglew1.5-dev |
|
libglew1.5-dev |
|
||||||
libglew-dev,
|
libglew-dev,
|
||||||
|
@ -25,14 +26,12 @@ Build-Depends: debhelper (>= 7),
|
||||||
libgstreamer1.0-dev,
|
libgstreamer1.0-dev,
|
||||||
libgstreamer-plugins-base1.0-dev,
|
libgstreamer-plugins-base1.0-dev,
|
||||||
libgpod-dev,
|
libgpod-dev,
|
||||||
libimobiledevice-dev,
|
|
||||||
libplist-dev,
|
libplist-dev,
|
||||||
libusbmuxd-dev,
|
libusbmuxd-dev,
|
||||||
libmtp-dev,
|
libmtp-dev,
|
||||||
libqjson-dev,
|
libqjson-dev,
|
||||||
protobuf-compiler,
|
protobuf-compiler,
|
||||||
libprotobuf-dev,
|
libprotobuf-dev,
|
||||||
libqca2-dev,
|
|
||||||
libfftw3-dev,
|
libfftw3-dev,
|
||||||
libsparsehash-dev,
|
libsparsehash-dev,
|
||||||
libsqlite3-dev,
|
libsqlite3-dev,
|
||||||
|
@ -49,8 +48,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends},
|
||||||
gstreamer1.0-plugins-good,
|
gstreamer1.0-plugins-good,
|
||||||
gstreamer1.0-plugins-ugly,
|
gstreamer1.0-plugins-ugly,
|
||||||
gstreamer1.0-pulseaudio,
|
gstreamer1.0-pulseaudio,
|
||||||
libprojectm-data | projectm-data,
|
libprojectm-data | projectm-data
|
||||||
libqca2-plugin-ossl
|
|
||||||
Description: Modern music player and library organiser inspired by Amarok 1.4
|
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
|
Clementine focuses on a fast and easy-to-use interface for searching and
|
||||||
playing your music.
|
playing your music.
|
||||||
|
|
|
@ -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: qt4-devel boost-devel gcc-c++ glew-devel libgpod-devel
|
||||||
BuildRequires: cmake gstreamer1-devel gstreamer1-plugins-base-devel
|
BuildRequires: cmake gstreamer1-devel gstreamer1-plugins-base-devel
|
||||||
BuildRequires: libmtp-devel protobuf-devel protobuf-compiler libcdio-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: sqlite-devel pulseaudio-libs-devel libechonest-devel
|
||||||
BuildRequires: libchromaprint-devel
|
BuildRequires: libchromaprint-devel
|
||||||
|
|
||||||
Requires: libgpod protobuf-lite libcdio qjson qca-ossl sqlite
|
Requires: libgpod protobuf-lite libcdio qjson sqlite
|
||||||
|
|
||||||
# GStreamer codec dependencies
|
# GStreamer codec dependencies
|
||||||
Requires: gstreamer1-plugins-ugly
|
Requires: gstreamer1-plugins-ugly
|
||||||
|
|
|
@ -855,7 +855,7 @@ optional_source(HAVE_SPOTIFY_DOWNLOADER
|
||||||
HEADERS
|
HEADERS
|
||||||
internet/spotify/spotifyblobdownloader.h
|
internet/spotify/spotifyblobdownloader.h
|
||||||
INCLUDE_DIRECTORIES
|
INCLUDE_DIRECTORIES
|
||||||
${QCA_INCLUDE_DIRS}
|
${CRYPTOPP_INCLUDE_DIRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Platform specific - OS X
|
# Platform specific - OS X
|
||||||
|
@ -1316,9 +1316,9 @@ endif(HAVE_BREAKPAD)
|
||||||
|
|
||||||
if(HAVE_SPOTIFY_DOWNLOADER)
|
if(HAVE_SPOTIFY_DOWNLOADER)
|
||||||
target_link_libraries(clementine_lib
|
target_link_libraries(clementine_lib
|
||||||
${QCA_LIBRARIES}
|
${CRYPTOPP_LIBRARIES}
|
||||||
)
|
)
|
||||||
link_directories(${QCA_LIBRARY_DIRS})
|
link_directories(${CRYPTOPP_LIBRARY_DIRS})
|
||||||
endif(HAVE_SPOTIFY_DOWNLOADER)
|
endif(HAVE_SPOTIFY_DOWNLOADER)
|
||||||
|
|
||||||
if(HAVE_LIBPULSE)
|
if(HAVE_LIBPULSE)
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#cmakedefine HAVE_AUDIOCD
|
#cmakedefine HAVE_AUDIOCD
|
||||||
#cmakedefine HAVE_BOX
|
#cmakedefine HAVE_BOX
|
||||||
#cmakedefine HAVE_BREAKPAD
|
#cmakedefine HAVE_BREAKPAD
|
||||||
|
#cmakedefine HAVE_CRYPTOPP
|
||||||
#cmakedefine HAVE_DBUS
|
#cmakedefine HAVE_DBUS
|
||||||
#cmakedefine HAVE_DEVICEKIT
|
#cmakedefine HAVE_DEVICEKIT
|
||||||
#cmakedefine HAVE_DROPBOX
|
#cmakedefine HAVE_DROPBOX
|
||||||
|
@ -36,7 +37,6 @@
|
||||||
#cmakedefine HAVE_LIBMTP
|
#cmakedefine HAVE_LIBMTP
|
||||||
#cmakedefine HAVE_LIBPULSE
|
#cmakedefine HAVE_LIBPULSE
|
||||||
#cmakedefine HAVE_MOODBAR
|
#cmakedefine HAVE_MOODBAR
|
||||||
#cmakedefine HAVE_QCA
|
|
||||||
#cmakedefine HAVE_SEAFILE
|
#cmakedefine HAVE_SEAFILE
|
||||||
#cmakedefine HAVE_SKYDRIVE
|
#cmakedefine HAVE_SKYDRIVE
|
||||||
#cmakedefine HAVE_SPARKLE
|
#cmakedefine HAVE_SPARKLE
|
||||||
|
|
|
@ -21,25 +21,34 @@
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "spotifyservice.h"
|
#include "spotifyservice.h"
|
||||||
|
#include "core/arraysize.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/network.h"
|
#include "core/network.h"
|
||||||
#include "core/utilities.h"
|
#include "core/utilities.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QIODevice>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QProgressDialog>
|
#include <QProgressDialog>
|
||||||
|
|
||||||
#ifdef HAVE_QCA
|
|
||||||
#include <QtCrypto>
|
|
||||||
#endif // HAVE_QCA
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char* SpotifyBlobDownloader::kSignatureSuffix = ".sha1";
|
#ifdef HAVE_CRYPTOPP
|
||||||
|
#include <crypto++/pkcspad.h>
|
||||||
|
#include <crypto++/rsa.h>
|
||||||
|
#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,
|
SpotifyBlobDownloader::SpotifyBlobDownloader(const QString& version,
|
||||||
const QString& path,
|
const QString& path,
|
||||||
|
@ -125,32 +134,11 @@ void SpotifyBlobDownloader::ReplyFinished() {
|
||||||
file_data[filename] = reply->readAll();
|
file_data[filename] = reply->readAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_QCA
|
if (!CheckSignature(file_data, signature_filenames)) {
|
||||||
// Load the public key
|
qLog(Warning) << "Signature checks failed";
|
||||||
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;
|
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
|
// Make the destination directory and write the files into it
|
||||||
QDir().mkpath(path_);
|
QDir().mkpath(path_);
|
||||||
|
|
||||||
|
@ -195,6 +183,63 @@ void SpotifyBlobDownloader::ReplyFinished() {
|
||||||
EmitFinished();
|
EmitFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SpotifyBlobDownloader::CheckSignature(
|
||||||
|
const QMap<QString, QByteArray>& 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<const byte*>(public_key_data.constData()),
|
||||||
|
public_key_data.size());
|
||||||
|
bytes.MessageEnd();
|
||||||
|
|
||||||
|
CryptoPP::RSA::PublicKey public_key;
|
||||||
|
public_key.Load(bytes);
|
||||||
|
|
||||||
|
CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA512>::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<const byte*>(file_data[actual_filename].constData()),
|
||||||
|
file_data[actual_filename].size(),
|
||||||
|
reinterpret_cast<const byte*>(
|
||||||
|
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() {
|
void SpotifyBlobDownloader::ReplyProgress() {
|
||||||
int progress = 0;
|
int progress = 0;
|
||||||
int total = 0;
|
int total = 0;
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#ifndef INTERNET_SPOTIFY_SPOTIFYBLOBDOWNLOADER_H_
|
#ifndef INTERNET_SPOTIFY_SPOTIFYBLOBDOWNLOADER_H_
|
||||||
#define INTERNET_SPOTIFY_SPOTIFYBLOBDOWNLOADER_H_
|
#define INTERNET_SPOTIFY_SPOTIFYBLOBDOWNLOADER_H_
|
||||||
|
|
||||||
|
#include <QMap>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
|
@ -40,7 +41,7 @@ class SpotifyBlobDownloader : public QObject {
|
||||||
|
|
||||||
void Start();
|
void Start();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void Finished();
|
void Finished();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
@ -52,6 +53,10 @@ class SpotifyBlobDownloader : public QObject {
|
||||||
void ShowError(const QString& message);
|
void ShowError(const QString& message);
|
||||||
void EmitFinished();
|
void EmitFinished();
|
||||||
|
|
||||||
|
bool CheckSignature(const QMap<QString, QByteArray>& file_data,
|
||||||
|
const QStringList& signature_filenames);
|
||||||
|
static QByteArray ConvertPEMToDER(const QByteArray& pem);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString version_;
|
QString version_;
|
||||||
QString path_;
|
QString path_;
|
||||||
|
|
|
@ -78,10 +78,6 @@
|
||||||
|
|
||||||
#include <echonest/Config.h>
|
#include <echonest/Config.h>
|
||||||
|
|
||||||
#ifdef HAVE_SPOTIFY_DOWNLOADER
|
|
||||||
#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>
|
||||||
|
@ -398,10 +394,6 @@ int main(int argc, char* argv[]) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_SPOTIFY_DOWNLOADER
|
|
||||||
QCA::Initializer qca_initializer;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Resources
|
// Resources
|
||||||
Q_INIT_RESOURCE(data);
|
Q_INIT_RESOURCE(data);
|
||||||
Q_INIT_RESOURCE(translations);
|
Q_INIT_RESOURCE(translations);
|
||||||
|
|
Loading…
Reference in New Issue