1 /***************************************************************************
2 * Copyright (C) 2007, 2009 by Brad Hards <bradh@frogmouth.net> *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 ***************************************************************************/
14 #include <QXmlStreamReader>
18 #include <KMessageBox>
19 #include <KPasswordDialog>
20 #include <KWallet/Wallet>
24 //---------------------------------------------------------------------
26 ManifestEntry::ManifestEntry( const QString
&fileName
) :
27 m_fileName( fileName
)
31 void ManifestEntry::setMimeType( const QString
&mimeType
)
33 m_mimeType
= mimeType
;
36 void ManifestEntry::setSize( const QString
&size
)
41 QString
ManifestEntry::fileName() const
46 QString
ManifestEntry::mimeType() const
51 QString
ManifestEntry::size() const
56 void ManifestEntry::setChecksumType( const QString
&checksumType
)
58 m_checksumType
= checksumType
;
61 QString
ManifestEntry::checksumType() const
63 return m_checksumType
;
66 void ManifestEntry::setChecksum( const QString
&checksum
)
68 m_checksum
= QByteArray::fromBase64( checksum
.toAscii() );
71 QByteArray
ManifestEntry::checksum() const
76 void ManifestEntry::setAlgorithm( const QString
&algorithm
)
78 m_algorithm
= algorithm
;
81 QString
ManifestEntry::algorithm() const
86 void ManifestEntry::setInitialisationVector( const QString
&initialisationVector
)
88 m_initialisationVector
= QByteArray::fromBase64( initialisationVector
.toAscii() );
91 QByteArray
ManifestEntry::initialisationVector() const
93 return m_initialisationVector
;
96 void ManifestEntry::setKeyDerivationName( const QString
&keyDerivationName
)
98 m_keyDerivationName
= keyDerivationName
;
101 QString
ManifestEntry::keyDerivationName() const
103 return m_keyDerivationName
;
106 void ManifestEntry::setIterationCount( const QString
&iterationCount
)
108 m_iterationCount
= iterationCount
.toInt();
111 int ManifestEntry::iterationCount() const
113 return m_iterationCount
;
116 void ManifestEntry::setSalt( const QString
&salt
)
118 m_salt
= QByteArray::fromBase64( salt
.toAscii() );
121 QByteArray
ManifestEntry::salt() const
126 //---------------------------------------------------------------------
128 Manifest::Manifest( const QString
&odfFileName
, const QByteArray
&manifestData
)
129 : m_odfFileName( odfFileName
), m_haveGoodPassword( false ), m_userCancelled( false )
131 // I don't know why the parser barfs on this.
132 QByteArray manifestCopy
= manifestData
;
133 manifestCopy
.replace(QByteArray("DOCTYPE manifest:manifest"), QByteArray("DOCTYPE manifest"));
135 QXmlStreamReader
xml( manifestCopy
);
137 ManifestEntry
*currentEntry
= 0;
138 while ( ! xml
.atEnd() ) {
140 if ( (xml
.tokenType() == QXmlStreamReader::NoToken
) ||
141 (xml
.tokenType() == QXmlStreamReader::Invalid
) ||
142 (xml
.tokenType() == QXmlStreamReader::StartDocument
) ||
143 (xml
.tokenType() == QXmlStreamReader::EndDocument
) ||
144 (xml
.tokenType() == QXmlStreamReader::DTD
) ||
145 (xml
.tokenType() == QXmlStreamReader::Characters
) ) {
148 if (xml
.tokenType() == QXmlStreamReader::StartElement
) {
149 if ( xml
.name().toString() == "manifest" ) {
151 } else if ( xml
.name().toString() == "file-entry" ) {
152 QXmlStreamAttributes attributes
= xml
.attributes();
153 if (currentEntry
!= 0) {
154 kWarning(OooDebug
) << "Got new StartElement for new file-entry, but haven't finished the last one yet!";
155 kWarning(OooDebug
) << "processing" << currentEntry
->fileName() << ", got" << attributes
.value("manifest:full-path").toString();
157 currentEntry
= new ManifestEntry( attributes
.value("manifest:full-path").toString() );
158 currentEntry
->setMimeType( attributes
.value("manifest:media-type").toString() );
159 currentEntry
->setSize( attributes
.value("manifest:size").toString() );
160 } else if ( xml
.name().toString() == "encryption-data" ) {
161 if (currentEntry
== 0) {
162 kWarning(OooDebug
) << "Got encryption-data without valid file-entry at line" << xml
.lineNumber();
165 QXmlStreamAttributes encryptionAttributes
= xml
.attributes();
166 currentEntry
->setChecksumType( encryptionAttributes
.value("manifest:checksum-type").toString() );
167 currentEntry
->setChecksum( encryptionAttributes
.value("manifest:checksum").toString() );
168 } else if ( xml
.name().toString() == "algorithm" ) {
169 if (currentEntry
== 0) {
170 kWarning(OooDebug
) << "Got algorithm without valid file-entry at line" << xml
.lineNumber();
173 QXmlStreamAttributes algorithmAttributes
= xml
.attributes();
174 currentEntry
->setAlgorithm( algorithmAttributes
.value("manifest:algorithm-name").toString() );
175 currentEntry
->setInitialisationVector( algorithmAttributes
.value("manifest:initialisation-vector").toString() );
176 } else if ( xml
.name().toString() == "key-derivation" ) {
177 if (currentEntry
== 0) {
178 kWarning(OooDebug
) << "Got key-derivation without valid file-entry at line" << xml
.lineNumber();
181 QXmlStreamAttributes kdfAttributes
= xml
.attributes();
182 currentEntry
->setKeyDerivationName( kdfAttributes
.value("manifest:key-derivation-name").toString() );
183 currentEntry
->setIterationCount( kdfAttributes
.value("manifest:iteration-count").toString() );
184 currentEntry
->setSalt( kdfAttributes
.value("manifest:salt").toString() );
186 // handle other StartDocument types here
187 kWarning(OooDebug
) << "Unexpected start document type: " << xml
.name().toString();
189 } else if ( xml
.tokenType() == QXmlStreamReader::EndElement
) {
190 if ( xml
.name().toString() == "manifest" ) {
192 } else if ( xml
.name().toString() == "file-entry") {
193 if (currentEntry
== 0) {
194 kWarning(OooDebug
) << "Got EndElement for file-entry without valid StartElement at line" << xml
.lineNumber();
197 // we're finished processing that file entry
198 if ( mEntries
.contains( currentEntry
->fileName() ) ) {
199 kWarning(OooDebug
) << "Can't insert entry because of duplicate name:" << currentEntry
->fileName();
202 mEntries
.insert( currentEntry
->fileName(), currentEntry
);
208 if (xml
.hasError()) {
209 kWarning(OooDebug
) << "error: " << xml
.errorString() << xml
.lineNumber() << xml
.columnNumber();
213 Manifest::~Manifest()
215 savePasswordToWallet();
217 qDeleteAll( mEntries
);
220 ManifestEntry
* Manifest::entryByName( const QString
&filename
)
222 return mEntries
.value( filename
, 0 );
225 bool Manifest::testIfEncrypted( const QString
&filename
)
227 ManifestEntry
*entry
= entryByName( filename
);
230 return ( entry
->salt().length() > 0 );
236 void Manifest::getPasswordFromUser()
238 // TODO: This should have a proper parent
239 KPasswordDialog
dlg( 0, KPasswordDialog::KPasswordDialogFlags() );
240 dlg
.setCaption( i18n( "Document Password" ) );
241 dlg
.setPrompt( i18n( "Please insert the password to read the document:" ) );
244 m_userCancelled
= true;
246 m_password
= dlg
.password();
250 void Manifest::getPasswordFromWallet()
252 if ( KWallet::Wallet::folderDoesNotExist( KWallet::Wallet::LocalWallet(), KWallet::Wallet::PasswordFolder() ) ) {
256 if ( m_odfFileName
.isEmpty() ) {
259 // This naming is consistent with how KOffice does it.
260 QString entryKey
= m_odfFileName
+ "/opendocument";
262 if ( KWallet::Wallet::keyDoesNotExist( KWallet::Wallet::LocalWallet(), KWallet::Wallet::PasswordFolder(), entryKey
) ) {
266 // TODO: this should have a proper parent. I can't see a way to get one though...
267 KWallet::Wallet
*wallet
= KWallet::Wallet::openWallet( KWallet::Wallet::LocalWallet(), 0 );
272 if ( ! wallet
->setFolder( KWallet::Wallet::PasswordFolder() ) ) {
277 wallet
->readPassword( entryKey
, m_password
);
281 void Manifest::savePasswordToWallet()
283 if ( ! m_haveGoodPassword
) {
287 if ( m_odfFileName
.isEmpty() ) {
291 // TODO: this should have a proper parent. I can't see a way to get one though...
292 KWallet::Wallet
*wallet
= KWallet::Wallet::openWallet( KWallet::Wallet::LocalWallet(), 0 );
297 if ( ! wallet
->hasFolder( KWallet::Wallet::PasswordFolder() ) ) {
298 wallet
->createFolder( KWallet::Wallet::PasswordFolder() );
301 if ( ! wallet
->setFolder( KWallet::Wallet::PasswordFolder() ) ) {
306 // This naming is consistent with how KOffice does it.
307 QString entryKey
= m_odfFileName
+ "/opendocument";
309 if ( wallet
->hasEntry( entryKey
) ) {
310 wallet
->removeEntry( entryKey
);
313 wallet
->writePassword( entryKey
, m_password
);
318 void Manifest::checkPassword( ManifestEntry
*entry
, const QByteArray
&fileData
, QByteArray
*decryptedData
)
321 QCA::SymmetricKey key
= QCA::PBKDF2( "sha1" ).makeKey( QCA::Hash( "sha1" ).hash( m_password
.toLocal8Bit() ),
322 QCA::InitializationVector( entry
->salt() ),
324 entry
->iterationCount() );
326 QCA::Cipher
decoder( "blowfish", QCA::Cipher::CFB
, QCA::Cipher::DefaultPadding
,
327 QCA::Decode
, key
, QCA::InitializationVector( entry
->initialisationVector() ) );
328 *decryptedData
= decoder
.update( QCA::MemoryRegion(fileData
) ).toByteArray();
329 *decryptedData
+= decoder
.final().toByteArray();
332 if ( entry
->checksumType() == "SHA1/1K" ) {
333 csum
= QCA::Hash( "sha1").hash( decryptedData
->left(1024) ).toByteArray();
334 } else if ( entry
->checksumType() == "SHA1" ) {
335 csum
= QCA::Hash( "sha1").hash( *decryptedData
).toByteArray();
337 kDebug(OooDebug
) << "unknown checksum type: " << entry
->checksumType();
338 // we can only assume it will be OK.
339 m_haveGoodPassword
= true;
343 if ( entry
->checksum() == csum
) {
344 m_haveGoodPassword
= true;
346 m_haveGoodPassword
= false;
349 m_haveGoodPassword
= false;
353 QByteArray
Manifest::decryptFile( const QString
&filename
, const QByteArray
&fileData
)
356 ManifestEntry
*entry
= entryByName( filename
);
358 if ( ! QCA::isSupported( "sha1" ) ) {
359 KMessageBox::error( 0, i18n("This document is encrypted, and crypto support is compiled in, but a hashing plugin could not be located") );
360 // in the hope that it wasn't really encrypted...
361 return QByteArray( fileData
);
364 if ( ! QCA::isSupported( "pbkdf2(sha1)") ) {
365 KMessageBox::error( 0, i18n("This document is encrypted, and crypto support is compiled in, but a key derivation plugin could not be located") );
366 // in the hope that it wasn't really encrypted...
367 return QByteArray( fileData
);
370 if ( ! QCA::isSupported( "blowfish-cfb") ) {
371 KMessageBox::error( 0, i18n("This document is encrypted, and crypto support is compiled in, but a cipher plugin could not be located") );
372 // in the hope that it wasn't really encrypted...
373 return QByteArray( fileData
);
376 if (m_userCancelled
) {
381 QByteArray decryptedData
;
382 if (! m_haveGoodPassword
) {
383 getPasswordFromWallet();
384 checkPassword( entry
, fileData
, &decryptedData
);
388 if (! m_haveGoodPassword
) {
389 getPasswordFromUser();
392 if (m_userCancelled
) {
396 checkPassword( entry
, fileData
, &decryptedData
);
397 if ( !m_haveGoodPassword
) {
398 KMessageBox::information( 0, i18n("The password is not correct."), i18n("Incorrect password") );
400 // kDebug(OooDebug) << "Have good password";
403 } while ( ( ! m_haveGoodPassword
) && ( ! m_userCancelled
) );
405 if ( m_haveGoodPassword
) {
406 QIODevice
*decompresserDevice
= KFilterDev::device( new QBuffer( &decryptedData
, 0 ), "application/x-gzip", true );
407 if( !decompresserDevice
) {
408 kDebug(OooDebug
) << "Couldn't create decompressor";
409 // hopefully it isn't compressed then!
410 return QByteArray( fileData
);
413 static_cast<KFilterDev
*>( decompresserDevice
)->setSkipHeaders( );
415 decompresserDevice
->open( QIODevice::ReadOnly
);
417 return decompresserDevice
->readAll();
420 return QByteArray( fileData
);
423 // TODO: This should have a proper parent
424 KMessageBox::error( 0, i18n("This document is encrypted, but Okular was compiled without crypto support. This document will probably not open.") );
425 // this is equivalent to what happened before all this Manifest stuff :-)
426 return QByteArray( fileData
);