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:
John Maguire 2015-04-28 12:25:42 +01:00
parent 4cbe098b83
commit 319b8a5824
8 changed files with 92 additions and 51 deletions

View File

@ -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()

6
debian/control vendored
View File

@ -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.

View File

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

View File

@ -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)

View File

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

View File

@ -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 <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QIODevice>
#include <QMessageBox>
#include <QNetworkReply>
#include <QProgressDialog>
#ifdef HAVE_QCA
#include <QtCrypto>
#endif // HAVE_QCA
#ifdef Q_OS_UNIX
#include <unistd.h>
#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,
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<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() {
int progress = 0;
int total = 0;

View File

@ -20,6 +20,7 @@
#ifndef INTERNET_SPOTIFY_SPOTIFYBLOBDOWNLOADER_H_
#define INTERNET_SPOTIFY_SPOTIFYBLOBDOWNLOADER_H_
#include <QMap>
#include <QObject>
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<QString, QByteArray>& file_data,
const QStringList& signature_filenames);
static QByteArray ConvertPEMToDER(const QByteArray& pem);
private:
QString version_;
QString path_;

View File

@ -78,10 +78,6 @@
#include <echonest/Config.h>
#ifdef HAVE_SPOTIFY_DOWNLOADER
#include <QtCrypto>
#endif
#ifdef Q_OS_DARWIN
#include <sys/resource.h>
#include <sys/sysctl.h>
@ -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);