1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-07-31 03:10:12 +02:00

Beginning of tomahawk-side osx handling of binary resolvers

This commit is contained in:
Leo Franchi
2012-05-10 23:01:55 -04:00
parent 7adb3fc737
commit 42a1119cba
10 changed files with 335 additions and 86 deletions

View File

@@ -143,5 +143,6 @@
<file>data/images/process-stop.png</file>
<file>data/icons/tomahawk-icon-128x128-grayscale.png</file>
<file>data/images/collection.png</file>
<file>data/misc/tomahawk_pubkey.pem</file>
</qresource>
</RCC>

View File

@@ -23,8 +23,6 @@
#include "Pipeline.h"
#include <attica/downloaditem.h>
#include <quazip.h>
#include <quazipfile.h>
#include <QNetworkReply>
#include <QTemporaryFile>
@@ -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 )
{

View File

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

View File

@@ -1,6 +1,7 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -1,6 +1,7 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -39,6 +39,9 @@
#include <QMutex>
#include <QCryptographicHash>
#include <quazip.h>
#include <quazipfile.h>
#ifdef Q_WS_WIN
#include <windows.h>
#include <shlobj.h>
@@ -49,6 +52,10 @@
#include <sys/sysctl.h>
#endif
#ifdef QCA2_FOUND
#include <QtCrypto>
#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

View File

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

View File

@@ -0,0 +1,37 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012, Leo Franchi <lfranchi@kde.org
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TOMAHAWKUTILS_MAC_H
#define TOMAHAWKUTILS_MAC_H
#include <QObject>
#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

View File

@@ -1,6 +1,54 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2012, Leo Franchi <lfranchi@kde.org
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TomahawkUtils.h"
#include "TomahawkUtils_Mac.h"
#include "mac/FileHelpers.h"
#include <QTemporaryFile>
#import <AppKit/NSApplication.h>
#import <Foundation/Foundation.h>
@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];
}
}

View File

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