diff --git a/resources.qrc b/resources.qrc
index 602774165..ee77f0f51 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -143,5 +143,6 @@
data/images/process-stop.png
data/icons/tomahawk-icon-128x128-grayscale.png
data/images/collection.png
+ data/misc/tomahawk_pubkey.pem
diff --git a/src/libtomahawk/AtticaManager.cpp b/src/libtomahawk/AtticaManager.cpp
index deeabeff6..b8636f4b6 100644
--- a/src/libtomahawk/AtticaManager.cpp
+++ b/src/libtomahawk/AtticaManager.cpp
@@ -23,8 +23,6 @@
#include "Pipeline.h"
#include
-#include
-#include
#include
#include
@@ -451,6 +449,7 @@ AtticaManager::installResolver( const Content& resolver, bool autoCreateAccount
connect( job, SIGNAL( finished( Attica::BaseJob* ) ), this, SLOT( resolverDownloadFinished( Attica::BaseJob* ) ) );
job->setProperty( "resolverId", resolver.id() );
job->setProperty( "createAccount", autoCreateAccount );
+ job->setProperty( "binarySignature", resolver.attribute("signature"));
job->start();
}
@@ -487,6 +486,7 @@ AtticaManager::resolverDownloadFinished ( BaseJob* j )
connect( reply, SIGNAL( finished() ), this, SLOT( payloadFetched() ) );
reply->setProperty( "resolverId", job->property( "resolverId" ) );
reply->setProperty( "createAccount", job->property( "createAccount" ) );
+ reply->setProperty( "binarySignature", job->property( "binarySignature" ) );
}
else
{
@@ -513,23 +513,50 @@ AtticaManager::payloadFetched()
f.write( reply->readAll() );
f.close();
- QString resolverId = reply->property( "resolverId" ).toString();
- QDir dir( extractPayload( f.fileName(), resolverId ) );
- QString resolverPath = dir.absoluteFilePath( m_resolverStates[ resolverId ].scriptPath );
-
- if ( !resolverPath.isEmpty() )
+ bool installedSuccessfully = false;
+ const QString resolverId = reply->property( "resolverId" ).toString();
+ if ( m_resolverStates[ resolverId ].binary )
{
- // update with absolute, not relative, path
- m_resolverStates[ resolverId ].scriptPath = resolverPath;
-
- if ( reply->property( "createAccount" ).toBool() )
+ // First ensure the signature matches. If we can't verify it, abort!
+ const QString signature = reply->property( "binarySignature" ).toString();
+ // Must have a signature for binary resolvers...
+ Q_ASSERT( !signature.isEmpty() );
+ if ( signature.isEmpty() )
+ return;
+ if ( !TomahawkUtils::verifyFile( f.fileName(), signature ) )
{
- // Do the install / add to tomahawk
- Tomahawk::Accounts::Account* resolver = Tomahawk::Accounts::ResolverAccountFactory::createFromPath( resolverPath, "resolveraccount", true );
- Tomahawk::Accounts::AccountManager::instance()->addAccount( resolver );
- TomahawkSettings::instance()->addAccount( resolver->accountId() );
+ qWarning() << "FILE SIGNATURE FAILED FOR BINARY RESOLVER! WARNING! :" << f.fileName() << signature;
+ return;
}
+#ifdef Q_OS_MAC
+#elif Q_OS_WIN
+#endif
+ }
+ else
+ {
+ QDir dir( TomahawkUtils::extractScriptPayload( f.fileName(), resolverId ) );
+ QString resolverPath = dir.absoluteFilePath( m_resolverStates[ resolverId ].scriptPath );
+
+ if ( !resolverPath.isEmpty() )
+ {
+ // update with absolute, not relative, path
+ m_resolverStates[ resolverId ].scriptPath = resolverPath;
+
+ if ( reply->property( "createAccount" ).toBool() )
+ {
+ // Do the install / add to tomahawk
+ Tomahawk::Accounts::Account* resolver = Tomahawk::Accounts::ResolverAccountFactory::createFromPath( resolverPath, "resolveraccount", true );
+ Tomahawk::Accounts::AccountManager::instance()->addAccount( resolver );
+ TomahawkSettings::instance()->addAccount( resolver->accountId() );
+ }
+
+ installedSuccessfully = true;
+ }
+ }
+
+ if ( installedSuccessfully )
+ {
m_resolverStates[ resolverId ].state = Installed;
TomahawkSettingsGui::instanceGui()->setAtticaResolverStates( m_resolverStates );
emit resolverInstalled( resolverId );
@@ -543,75 +570,6 @@ AtticaManager::payloadFetched()
}
-QString
-AtticaManager::extractPayload( const QString& filename, const QString& resolverId ) const
-{
- // uses QuaZip to extract the temporary zip file to the user's tomahawk data/resolvers directory
- QuaZip zipFile( filename );
- if ( !zipFile.open( QuaZip::mdUnzip ) )
- {
- tLog() << "Failed to QuaZip open:" << zipFile.getZipError();
- return QString();
- }
-
- if ( !zipFile.goToFirstFile() )
- {
- tLog() << "Failed to go to first file in zip archive: " << zipFile.getZipError();
- return QString();
- }
-
- QDir resolverDir = TomahawkUtils::appDataDir();
- if ( !resolverDir.mkpath( QString( "atticaresolvers/%1" ).arg( resolverId ) ) )
- {
- tLog() << "Failed to mkdir resolver save dir: " << TomahawkUtils::appDataDir().absoluteFilePath( QString( "atticaresolvers/%1" ).arg( resolverId ) );
- return QString();
- }
- resolverDir.cd( QString( "atticaresolvers/%1" ).arg( resolverId ) );
- tDebug() << "Installing resolver to:" << resolverDir.absolutePath();
-
- QuaZipFile fileInZip( &zipFile );
- do
- {
- QuaZipFileInfo info;
- zipFile.getCurrentFileInfo( &info );
-
- if ( !fileInZip.open( QIODevice::ReadOnly ) )
- {
- tLog() << "Failed to open file inside zip archive:" << info.name << zipFile.getZipName() << "with error:" << zipFile.getZipError();
- continue;
- }
-
- QFile out( resolverDir.absoluteFilePath( fileInZip.getActualFileName() ) );
-
- QStringList parts = fileInZip.getActualFileName().split( "/" );
- if ( parts.size() > 1 )
- {
- QStringList dirs = parts.mid( 0, parts.size() - 1 );
- QString dirPath = dirs.join( "/" ); // QDir translates / to \ internally if necessary
- resolverDir.mkpath( dirPath );
- }
-
- // make dir if there is one needed
- QDir d( fileInZip.getActualFileName() );
-
- tDebug() << "Writing to output file..." << out.fileName();
- if ( !out.open( QIODevice::WriteOnly ) )
- {
- tLog() << "Failed to open resolver extract file:" << out.errorString() << info.name;
- continue;
- }
-
-
- out.write( fileInZip.readAll() );
- out.close();
- fileInZip.close();
-
- } while ( zipFile.goToNextFile() );
-
- return resolverDir.absolutePath();
-}
-
-
void
AtticaManager::uninstallResolver( const QString& pathToResolver )
{
diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt
index c77b302e5..af98f0fdc 100644
--- a/src/libtomahawk/CMakeLists.txt
+++ b/src/libtomahawk/CMakeLists.txt
@@ -391,6 +391,7 @@ IF( APPLE )
infosystem/infoplugins/mac/Adium.mm
infosystem/infoplugins/mac/AdiumPlugin.cpp
utils/TomahawkUtils_Mac.mm
+ mac/FileHelpers.mm
thirdparty/Qocoa/qsearchfield_mac.mm )
SET_SOURCE_FILES_PROPERTIES(utils/TomahawkUtils_Mac.mm PROPERTIES COMPILE_FLAGS "-fvisibility=default")
@@ -400,10 +401,11 @@ IF( APPLE )
# System
${COREAUDIO_LIBRARY}
${COREFOUNDATION_LIBRARY}
- ${FOUNDATION_LIBRARY}
+ ${FOUNDATION_LIBRARY}
${SCRIPTINGBRIDGE_LIBRARY}
/System/Library/Frameworks/AppKit.framework
+ /System/Library/Frameworks/Security.framework
)
ELSE( APPLE )
SET( libGuiSources ${libGuiSources} thirdparty/Qocoa/qsearchfield.cpp )
diff --git a/src/libtomahawk/database/DatabaseCollection.cpp b/src/libtomahawk/database/DatabaseCollection.cpp
index cb864cc66..2dca1980d 100644
--- a/src/libtomahawk/database/DatabaseCollection.cpp
+++ b/src/libtomahawk/database/DatabaseCollection.cpp
@@ -1,6 +1,7 @@
/* === This file is part of Tomahawk Player - ===
*
* Copyright 2010-2011, Christian Muehlhaeuser
+ * Copyright 2010-2011, Leo Franchi
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/libtomahawk/database/DatabaseCollection.h b/src/libtomahawk/database/DatabaseCollection.h
index a9e9fae65..de94bbd6c 100644
--- a/src/libtomahawk/database/DatabaseCollection.h
+++ b/src/libtomahawk/database/DatabaseCollection.h
@@ -1,6 +1,7 @@
/* === This file is part of Tomahawk Player - ===
*
* Copyright 2010-2011, Christian Muehlhaeuser
+ * Copyright 2010-2011, Leo Franchi
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/libtomahawk/utils/TomahawkUtils.cpp b/src/libtomahawk/utils/TomahawkUtils.cpp
index 8fcf170af..c2cc93b18 100644
--- a/src/libtomahawk/utils/TomahawkUtils.cpp
+++ b/src/libtomahawk/utils/TomahawkUtils.cpp
@@ -39,6 +39,9 @@
#include
#include
+#include
+#include
+
#ifdef Q_WS_WIN
#include
#include
@@ -49,6 +52,10 @@
#include
#endif
+#ifdef QCA2_FOUND
+#include
+#endif
+
namespace TomahawkUtils
{
static quint64 s_infosystemRequestId = 0;
@@ -676,4 +683,160 @@ SharedTimeLine::disconnectNotify( const char* signal )
}
+bool
+verifyFile( const QString &filePath, const QString &signature )
+{
+ QCA::Initializer init;
+
+ if( !QCA::isSupported( "sha1" ) )
+ {
+ qWarning() << "SHA1 not supported by QCA, aborting.";
+ return false;
+ }
+
+ // The signature for the resolver.zip was created like so:
+ // openssl dgst -sha1 -binary < "#{tarball}" | openssl dgst -dss1 -sign "#{ARGV[2]}" | openssl enc -base64
+ // which means we need to decode it with QCA's DSA public key signature verification tools
+ // The input data is:
+ // file -> SHA1 binary format -> DSS1/DSA signed -> base64 encoded.
+
+ // Step 1: Load the public key
+ // Public key is in :/data/misc/tomahawk_pubkey.pem
+ QFile f( ":/data/misc/tomahawk_pubkey.pem" );
+ if ( !f.open( QIODevice::ReadOnly ) )
+ {
+ qWarning() << "Unable to read public key from resources!";
+ return false;
+ }
+
+ const QString pubkeyData = QString::fromUtf8( f.readAll() );
+ QCA::ConvertResult conversionResult;
+ QCA::PublicKey publicKey = QCA::PublicKey::fromPEM( pubkeyData, &conversionResult );
+ if ( QCA::ConvertGood != conversionResult)
+ {
+ qWarning() << "Public key reading/loading failed! Tried to load public key:" << pubkeyData;
+ return false;
+ }
+
+ if ( !publicKey.canVerify() )
+ {
+ qWarning() << "Loaded Tomahawk public key but cannot use it to verify! What is up....";
+ return false;
+ }
+
+ // Step 2: Get the SHA1 of the file contents
+ QFile toVerify( filePath );
+ if ( !toVerify.exists() || !toVerify.open( QIODevice::ReadOnly ) )
+ {
+ qWarning() << "Failed to open file we are trying to verify!" << filePath;
+ return false;
+ }
+
+ QCA::Hash fileHash = QCA::Hash( "sha1 ");
+ //QCA::SecureArray fileData( toVerify.readAll() );
+ //fileHash.update( fileData );
+ const QByteArray fileHashData = QCA::Hash( "sha1" ).hash( toVerify.readAll() ).toByteArray();
+ toVerify.close();
+
+ // Step 3: Base64 decode the signature
+ QCA::Base64 decoder( QCA::Decode );
+ const QByteArray decodedSignature = decoder.decode( QCA::SecureArray( signature.trimmed().toUtf8() ) ).toByteArray();
+ if ( decodedSignature.isEmpty() )
+ {
+ qWarning() << "Got empty signature after we tried to decode it from Base64:" << signature.trimmed().toUtf8() << decodedSignature.toBase64();
+ return false;
+ }
+
+ // Step 4: Do the actual verifying!
+ const bool result = publicKey.verifyMessage( fileHashData, decodedSignature, QCA::EMSA1_SHA1, QCA::DERSequence );
+ if ( !result )
+ {
+ qWarning() << "File" << filePath << "FAILED VERIFICATION against our input signature!";
+ return false;
+ }
+
+ qDebug() << "Successfully verified signature of downloaded file:" << filePath;
+
+ return true;
+}
+
+
+QString
+extractScriptPayload( const QString& filename, const QString& resolverId )
+{
+ // uses QuaZip to extract the temporary zip file to the user's tomahawk data/resolvers directory
+ QuaZip zipFile( filename );
+ if ( !zipFile.open( QuaZip::mdUnzip ) )
+ {
+ tLog() << "Failed to QuaZip open:" << zipFile.getZipError();
+ return QString();
+ }
+
+ if ( !zipFile.goToFirstFile() )
+ {
+ tLog() << "Failed to go to first file in zip archive: " << zipFile.getZipError();
+ return QString();
+ }
+
+ QDir resolverDir = appDataDir();
+ if ( !resolverDir.mkpath( QString( "atticaresolvers/%1" ).arg( resolverId ) ) )
+ {
+ tLog() << "Failed to mkdir resolver save dir: " << TomahawkUtils::appDataDir().absoluteFilePath( QString( "atticaresolvers/%1" ).arg( resolverId ) );
+ return QString();
+ }
+ resolverDir.cd( QString( "atticaresolvers/%1" ).arg( resolverId ) );
+ tDebug() << "Installing resolver to:" << resolverDir.absolutePath();
+
+ QuaZipFile fileInZip( &zipFile );
+ do
+ {
+ QuaZipFileInfo info;
+ zipFile.getCurrentFileInfo( &info );
+
+ if ( !fileInZip.open( QIODevice::ReadOnly ) )
+ {
+ tLog() << "Failed to open file inside zip archive:" << info.name << zipFile.getZipName() << "with error:" << zipFile.getZipError();
+ continue;
+ }
+
+ QFile out( resolverDir.absoluteFilePath( fileInZip.getActualFileName() ) );
+
+ QStringList parts = fileInZip.getActualFileName().split( "/" );
+ if ( parts.size() > 1 )
+ {
+ QStringList dirs = parts.mid( 0, parts.size() - 1 );
+ QString dirPath = dirs.join( "/" ); // QDir translates / to \ internally if necessary
+ resolverDir.mkpath( dirPath );
+ }
+
+ // make dir if there is one needed
+ QDir d( fileInZip.getActualFileName() );
+
+ tDebug() << "Writing to output file..." << out.fileName();
+ if ( !out.open( QIODevice::WriteOnly ) )
+ {
+ tLog() << "Failed to open resolver extract file:" << out.errorString() << info.name;
+ continue;
+ }
+
+
+ out.write( fileInZip.readAll() );
+ out.close();
+ fileInZip.close();
+
+ } while ( zipFile.goToNextFile() );
+
+ return resolverDir.absolutePath();
+}
+
+
+#if !defined(Q_OS_MAC) // && !defined(Q_OS_WIN)
+void
+extractBinaryResolver( const QString& zipFilename, const QString& resolverId, QObject* )
+{
+ // No support for binary resolvers on linux! Shouldn't even have been allowed to see/install..
+ Q_ASSERT( false );
+}
+#endif
+
} // ns
diff --git a/src/libtomahawk/utils/TomahawkUtils.h b/src/libtomahawk/utils/TomahawkUtils.h
index 8269d3134..93f73cc5d 100644
--- a/src/libtomahawk/utils/TomahawkUtils.h
+++ b/src/libtomahawk/utils/TomahawkUtils.h
@@ -137,6 +137,13 @@ namespace TomahawkUtils
DLLEXPORT QString md5( const QByteArray& data );
DLLEXPORT bool removeDirectory( const QString& dir );
+ DLLEXPORT bool verifyFile( const QString& filePath, const QString& signature );
+ DLLEXPORT QString extractScriptPayload( const QString& filename, const QString& resolverId );
+
+ // Extracting may be asynchronous, pass in a receiver object with the following slots:
+ // extractSucceeded( const QString& path ) and extractFailed() to be notified/
+ DLLEXPORT void extractBinaryResolver( const QString& zipFilename, const QString& resolverId, QObject* receiver );
+
/**
* This helper is designed to help "update" an existing playlist with a newer revision of itself.
* To avoid re-loading the whole playlist and re-resolving tracks that are the same in the old playlist,
diff --git a/src/libtomahawk/utils/TomahawkUtils_Mac.h b/src/libtomahawk/utils/TomahawkUtils_Mac.h
new file mode 100644
index 000000000..635db4117
--- /dev/null
+++ b/src/libtomahawk/utils/TomahawkUtils_Mac.h
@@ -0,0 +1,37 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2012, Leo Franchi .
+ */
+
+#ifndef TOMAHAWKUTILS_MAC_H
+#define TOMAHAWKUTILS_MAC_H
+
+#include
+
+#import "mac/FileHelpers.h"
+
+@interface MoveDelegate : NSObject
+{
+ QObject* receiver;
+ QString path;
+}
+- (void)setReceiver:(QObject*)receiver;
+- (void)setMoveTo:(QString)path;
+- (void)moveFinished;
+- (void)moveFailedWithError:(NSError *)error;
+@end
+
+#endif // TOMAHAWKUTILS_MAC_H
diff --git a/src/libtomahawk/utils/TomahawkUtils_Mac.mm b/src/libtomahawk/utils/TomahawkUtils_Mac.mm
index aeac94866..5bb6558e5 100644
--- a/src/libtomahawk/utils/TomahawkUtils_Mac.mm
+++ b/src/libtomahawk/utils/TomahawkUtils_Mac.mm
@@ -1,6 +1,54 @@
+/* === This file is part of Tomahawk Player - ===
+ *
+ * Copyright 2012, Leo Franchi .
+ */
+
#include "TomahawkUtils.h"
+#include "TomahawkUtils_Mac.h"
+#include "mac/FileHelpers.h"
+
+#include
+
#import
+#import
+
+@implementation MoveDelegate
+
+
+-(void) setReceiver:(QObject*) object
+{
+ receiver = object;
+}
+
+-(void) setMoveTo:(QString) p
+{
+ path = p;
+}
+
+- (void)moveFinished
+{
+ QMetaObject::invokeMethod(receiver, "installSucceeded", Qt::DirectConnection, Q_ARG(QString, path));
+}
+
+- (void)moveFailedWithError:(NSError *)error
+{
+ QMetaObject::invokeMethod(receiver, "installFailed", Qt::DirectConnection);
+}
+@end
namespace TomahawkUtils
{
@@ -10,4 +58,35 @@ bringToFront() {
[NSApp activateIgnoringOtherApps:YES];
}
+
+void
+extractBinaryResolver( const QString& zipFilename, const QString& resolverId, QObject* receiver )
+{
+ /**
+ On OS X, we have to do the following:
+ 2) Extract file in temporary location
+ 3) Authenticate to be able to have write access to the /Applications folder
+ 4) Copy the contents of the zipfile to the Tomahawk.app/Contents/MacOS/ folder
+ 5) Call result slots on receiver object
+ */
+
+ MoveDelegate* del = [[MoveDelegate alloc] init];
+ [del setReceiver: receiver];
+
+ // Unzip in temporary folder and copy the contents to MacOS/
+ NSError* err = NULL;
+ NSFileManager *manager = [[[NSFileManager alloc] init] autorelease];
+ NSURL* tempDir = [manager URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:NULL create:YES error:&err];
+ if ( err )
+ {
+ qDebug() << "GOT ERROR trying to create temp dir to unzip in...:" << err;
+ return;
+ }
+
+ qDebug() << "Using temporary directory:" << [tempDir absoluteString];
+
+
+// [del setMoveTo: to];
+}
+
}
diff --git a/src/main.cpp b/src/main.cpp
index fe0332d1f..2c786bf55 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -121,7 +121,7 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
int
main( int argc, char *argv[] )
{
- #ifdef Q_WS_MAC
+#ifdef Q_WS_MAC
// Do Mac specific startup to get media keys working.
// This must go before QApplication initialisation.
Tomahawk::macMain();