F:/KPlato/koffice/libs/store/KoEncryptedStore.cpp

Aller à la documentation de ce fichier.
00001 /* This file is part of the KDE project
00002    Copyright (C) 2006 Thomas Schaap <thomas.schaap@kdemail.net>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017    Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "KoXmlWriter.h"
00021 #include <KoXmlReader.h>
00022 #include "KoZipStore.h"
00023 #include <QString>
00024 #include <QByteArray>
00025 #include <QIODevice>
00026 #include <QWidget>
00027 #include <QtCrypto>
00028 #include <kpassworddialog.h>
00029 #include "KoEncryptedStore.h"
00030 #include <kwallet.h>
00031 #include <klocale.h>
00032 #include <kfilterdev.h>
00033 #include <kmessage.h>
00034 #include <kmessagebox.h>
00035 
00036 struct KoEncryptedStore_EncryptionData {
00037     // Needed for Key Derivation
00038     QSecureArray salt;
00039     unsigned int iterationCount;
00040 
00041     // Needed for enc/decryption
00042     QSecureArray initVector;
00043 
00044     // Needed for (optional) password-checking
00045     QSecureArray checksum;
00046     // checksumShort is set to true if the checksum-algorithm is SHA1/1K, which basically means we only use the first 1024 bytes of the unencrypted file to check against (see also http://www.openoffice.org/servlets/ReadMsg?list=dev&msgNo=17498)
00047     bool checksumShort;
00048 
00049     // The size of the uncompressed file
00050     qint64 filesize;
00051 };
00052 
00053 namespace {
00054     const char* MANIFEST_FILE = "META-INF/manifest.xml";
00055     const char* META_FILE = "meta.xml";
00056     const char* THUMBNAIL_FILE = "Thumbnails/thumbnail.png";
00057 }
00058 
00059 KoEncryptedStore::KoEncryptedStore( const QString & filename, Mode mode, const QByteArray & appIdentification )
00060     : m_init_url( NULL ), m_init_dev( NULL ), m_init_deferred( false ), m_init_appIdentification( appIdentification ), m_qcaInit( QCA::Initializer() ), m_password( QSecureArray() ), m_window( NULL ), m_filename( QString( filename ) ), m_manifestBuffer( QByteArray() ) {
00061     if( mode == Write ) {
00062         // Prevent the underlying store from being opened if encryption is not supported
00063         m_bGood = QCA::isSupported( "sha1" ) && QCA::isSupported( "pbkdf2(sha1)" ) && init( mode, appIdentification );
00064         m_store = NULL;
00065         m_init_deferred = true;
00066         return;
00067     }
00068     m_store = new KoZipStore( filename, mode, appIdentification );
00069     m_bGood = m_store && !m_store->bad( );
00070 
00071     if( m_bGood ) {
00072         m_bGood = init( mode, appIdentification );
00073     }
00074 }
00075 
00076 KoEncryptedStore::KoEncryptedStore( QIODevice *dev, Mode mode, const QByteArray & appIdentification )
00077     : m_init_url( NULL ), m_init_dev( dev ), m_init_deferred( false ), m_init_appIdentification( appIdentification ), m_qcaInit( QCA::Initializer() ), m_password( QSecureArray() ), m_window( NULL ), m_filename( QString( ) ), m_manifestBuffer( QByteArray() ) {
00078     if( mode == Write ) {
00079         // Prevent the underlying store from being opened if encryption is not supported
00080         m_bGood = QCA::isSupported( "sha1" ) && QCA::isSupported( "pbkdf2(sha1)" ) && init( mode, appIdentification );
00081         m_store = NULL;
00082         m_init_deferred = true;
00083         return;
00084     }
00085     m_store = new KoZipStore( dev, mode, appIdentification );
00086     m_bGood = m_store && !m_store->bad( );
00087 
00088     if( m_bGood ) {
00089         m_bGood = init( mode, appIdentification );
00090     }
00091 }
00092 
00093 KoEncryptedStore::KoEncryptedStore( QWidget* window, const KUrl& url, const QString & filename, Mode mode, const QByteArray & appIdentification )
00094     : m_init_url( url ), m_init_dev( NULL ), m_init_deferred( false ), m_init_appIdentification( appIdentification ), m_qcaInit( QCA::Initializer() ), m_password( QSecureArray() ), m_window( window ), m_filename( QString( url.url( ) ) ), m_manifestBuffer( QByteArray() ) {
00095     if( mode == Write ) {
00096         // Prevent the underlying store from being opened if encryption is not supported
00097         m_bGood = QCA::isSupported( "sha1" ) && QCA::isSupported( "pbkdf2(sha1)" ) && init( mode, appIdentification );
00098         m_store = NULL;
00099         m_init_deferred = true;
00100         return;
00101     }
00102     m_store = new KoZipStore( window, url, filename, mode, appIdentification );
00103     m_bGood = m_store && !m_store->bad( );
00104 
00105     if( m_bGood ) {
00106         m_bGood = init( mode, appIdentification );
00107     }
00108 }
00109 
00110 KoEncryptedStore::~KoEncryptedStore() {
00111     if( isOpen( ) ) {
00112         close( );
00113     }
00114     if( m_store ) {
00115         if( m_store->isOpen( ) ) {
00116             m_store->close( );
00117         }
00118         if( m_mode == Write ) {
00119             // First change the manifest file and write it
00120             // We'll use the QDom classes here, since KoXmlReader and KoXmlWriter have no way of copying a complete xml-file
00121             // other than parsing it completely and rebuilding it.
00122             // Errorhandling here is done to prevent data from being lost whatever happens
00123             QDomDocument document;
00124             if( m_manifestBuffer.isEmpty( ) ) {
00125                 // No manifest? Better create one
00126                 document = QDomDocument::QDomDocument( );
00127                 QDomElement rootElement = document.createElement( "manifest:manifest" );
00128                 rootElement.setAttribute( "xmlns:manifest", "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" );
00129                 document.appendChild( rootElement );
00130             }
00131             if( !m_manifestBuffer.isEmpty( ) && !document.setContent( m_manifestBuffer ) ) {
00132                 // Oi! That's fresh XML we should have here!
00133                 // This is the only case we can't fix
00134                 KMessage::message( KMessage::Error, i18n( "The manifest file seems to be corrupted. It cannot be modified and the document will remain unreadable. Please try and save the document again to prevent losing your work." ) );
00135             }
00136             else {
00137                 QDomElement documentElement = document.documentElement( );
00138                 QDomNodeList fileElements = documentElement.elementsByTagName( "manifest:file-entry" );
00139                 // Search all files in the manifest
00140                 QStringList foundFiles;
00141                 for( int i = 0; i < fileElements.size( ); i++ ) {
00142                     printf( "Tussen, nummertje %d:\n\n%s\n\n", i, document.toByteArray( ).data( ) );
00143                     QDomElement fileElement = fileElements.item( i ).toElement( );
00144                     QString fullpath = fileElement.toElement( ).attribute( "manifest:full-path" );
00145                     // See if it's encrypted
00146                     if( fullpath.isEmpty( ) || !m_encryptionData.contains( normalizedFullPath( fullpath ) ) ) {
00147                         continue;
00148                     }
00149                     foundFiles += normalizedFullPath( fullpath );
00150                     KoEncryptedStore_EncryptionData encData = m_encryptionData.value( normalizedFullPath( fullpath ) );
00151                     // Set the unencrypted size of the file
00152                     fileElement.setAttribute( "manifest:size", encData.filesize );
00153                     // See if the user of this store has already provided (old) encryption data
00154                     QDomNodeList childElements = fileElement.elementsByTagName( "manifest:encryption-data" );
00155                     QDomElement encryptionElement;
00156                     QDomElement algorithmElement;
00157                     QDomElement keyDerivationElement;
00158                     if( childElements.isEmpty( ) ) { 
00159                         encryptionElement = document.createElement( "manifest:encryption-data" );
00160                         fileElement.appendChild( encryptionElement );
00161                     }
00162                     else {
00163                         encryptionElement = childElements.item( 0 ).toElement( );
00164                     }
00165                     childElements = encryptionElement.elementsByTagName( "manifest:algorithm" );
00166                     if( childElements.isEmpty( ) ) {
00167                         algorithmElement = document.createElement( "manifest:algorithm" );
00168                         encryptionElement.appendChild( algorithmElement );
00169                     }
00170                     else {
00171                         algorithmElement = childElements.item( 0 ).toElement( );
00172                     }
00173                     childElements = encryptionElement.elementsByTagName( "manifest:key-derivation" );
00174                     if( childElements.isEmpty( ) ) {
00175                         keyDerivationElement = document.createElement( "manifest:key-derivation" );
00176                         encryptionElement.appendChild( keyDerivationElement );
00177                     }
00178                     else {
00179                         keyDerivationElement = childElements.item( 0 ).toElement( );
00180                     }
00181                     // Set the right encryption data
00182                     QCA::Base64 encoder;
00183                     QSecureArray checksum = encoder.update( encData.checksum );
00184                     checksum += encoder.final( );
00185                 //    encryptionElement.setAttribute( "manifest:checksum", QString( checksum.toByteArray( ) ) );
00186                     if( encData.checksumShort ) {
00187                   //      encryptionElement.setAttribute( "manifest:checksum-type", "SHA1/1K" );
00188                     }
00189                     else {
00190                     //    encryptionElement.setAttribute( "manifest:checksum-type", "SHA1" );
00191                     }
00192                     encoder.clear( );
00193                     QSecureArray initVector = encoder.update( encData.initVector );
00194                     initVector += encoder.final( );
00195                     algorithmElement.setAttribute( "manifest:initialisation-vector", QString( initVector.toByteArray( ) ) );
00196                     algorithmElement.setAttribute( "manifest:algorithm-name", "Blowfish CFB" );
00197                     encoder.clear( );
00198                     QSecureArray salt = encoder.update( encData.salt );
00199                     salt += encoder.final( );
00200                     keyDerivationElement.setAttribute( "manifest:salt", QString( salt.toByteArray( ) ) );
00201                     keyDerivationElement.setAttribute( "manifest:key-derivation-name", "PBKDF2" );
00202                 }
00203                 if( foundFiles.size( ) < m_encryptionData.size( ) ) {
00204                     QList<QString> keys = m_encryptionData.keys( );
00205                     for( int i = 0; i < keys.size( ); i++ ) {
00206                         if( !foundFiles.contains( normalizedFullPath( keys.value( i ) ) ) ) {
00207                             KoEncryptedStore_EncryptionData encData = m_encryptionData.value( normalizedFullPath( keys.value( i ) ) );
00208                             QDomElement fileElement = document.createElement( "manifest:file-entry" );
00209                             fileElement.setAttribute( "manifest:full-path", normalizedFullPath( keys.value( i ) ).mid( 1 ) );
00210                             fileElement.setAttribute( "manifest:size", encData.filesize );
00211                             fileElement.setAttribute( "manifest:media-type", "" );
00212                             documentElement.appendChild( fileElement );
00213                             QDomElement encryptionElement = document.createElement( "manifest:encryption-data" );
00214                             QCA::Base64 encoder;
00215                             QSecureArray checksum = encoder.update( encData.checksum );
00216                             checksum += encoder.final( );
00217                             encoder.clear( );
00218                             QSecureArray initVector = encoder.update( encData.initVector );
00219                             initVector += encoder.final( );
00220                             encoder.clear( );
00221                             QSecureArray salt = encoder.update( encData.salt );
00222                             salt += encoder.final( );
00223                             encryptionElement.setAttribute( "manifest:checksum", QString( checksum.toByteArray( ) ) );
00224                             if( encData.checksumShort ) {
00225                                 encryptionElement.setAttribute( "manifest:checksum-type", "SHA1/1K" );
00226                             }
00227                             else {
00228                                 encryptionElement.setAttribute( "manifest:checksum-type", "SHA1" );
00229                             }
00230                             fileElement.appendChild( encryptionElement );
00231                             QDomElement algorithmElement = document.createElement( "manifest:algorithm" );
00232                             algorithmElement.setAttribute( "manifest:algorithm-name", "Blowfish CFB" );
00233                             algorithmElement.setAttribute( "manifest:initialisation-vector", QString( initVector.toByteArray( ) ) );
00234                             encryptionElement.appendChild( algorithmElement );
00235                             QDomElement keyDerivationElement = document.createElement( "manifest:key-derivation" );
00236                             keyDerivationElement.setAttribute( "manifest:key-derivation-name", "PBKDF2" );
00237                             keyDerivationElement.setAttribute( "manifest:salt", QString( salt.toByteArray( ) ) );
00238                             encryptionElement.appendChild( keyDerivationElement );
00239                         }
00240                     }
00241                 }
00242                 printf( "Voor:\n\n%s\n", m_manifestBuffer.data( ) );
00243                 m_manifestBuffer = document.toByteArray( );
00244                 printf( "\nNa:\n\n%s\n\n", m_manifestBuffer.data( ) );
00245                 if( !m_store->open( MANIFEST_FILE ) ) {
00246                     KMessage::message( KMessage::Error, i18n( "The manifest file cannot be opened. The document will remain unreadable. Please try and save the document again to prevent losing your work." ) );
00247                 }
00248                 else {
00249                     if( static_cast<KoStore *>( m_store )->write( m_manifestBuffer ) != m_manifestBuffer.size( ) ) {
00250                         KMessage::message( KMessage::Error, i18n( "The manifest file cannot be opened. The document will remain unreadable. Please try and save the document again to prevent losing your work." ) );
00251                     }
00252                     m_store->close( );
00253                 }
00254             }
00255         }
00256 
00257         delete m_store;
00258     }
00259 }
00260 
00261 KoZipStore* KoEncryptedStore::ripZipStore( ) {
00262     if( !initBackend( ) ) {
00263         return NULL;
00264     }
00265     if( m_store ) {
00266         KoZipStore *store = m_store;
00267         m_store = NULL;
00268         return store;
00269     }
00270     return NULL;
00271 }
00272 
00273 bool KoEncryptedStore::initBackend( ) {
00274     if( m_init_deferred ) {
00275         if( m_init_dev ) {
00276             m_store = new KoZipStore( m_init_dev, m_mode, m_init_appIdentification );
00277             m_init_dev = NULL;
00278         }
00279         else if( !m_init_url.isEmpty( ) ) {
00280             m_store = new KoZipStore( m_window, m_init_url, m_filename, m_mode, m_init_appIdentification );
00281             m_init_url = KUrl( );
00282         }
00283         else {
00284             m_store = new KoZipStore( m_filename, m_mode, m_init_appIdentification );
00285         }
00286         m_init_deferred = false;
00287         if( !m_store || m_store->bad( ) ) {
00288             m_bGood = false;
00289             if( isOpen( ) ) {
00290                 close( );
00291             }
00292             return false;
00293         }
00294         if( isOpen( ) ) {
00295             if( !m_store->open( normalizedFullPath( m_sName ) ) ) {
00296                 m_bGood = false;
00297                 close( );
00298                 return false;
00299             }
00300         }
00301         m_init_deferred = false;
00302     }
00303     return true;
00304 }
00305 
00306 KoStore* KoEncryptedStore::createEncryptedStoreReader( const QString & filename, const QByteArray & appIdentification ) {
00307     KoEncryptedStore *encStore = new KoEncryptedStore( filename, Read, appIdentification );
00308     if( encStore->isEncrypted( ) ) {
00309         return encStore;
00310     }
00311     KoZipStore *zipStore = encStore->ripZipStore( );
00312     delete encStore;
00313     return zipStore;
00314 }
00315 
00316 KoStore* KoEncryptedStore::createEncryptedStoreReader( QIODevice *dev, const QByteArray & appIdentification ) {
00317     KoEncryptedStore *encStore = new KoEncryptedStore( dev, Read, appIdentification );
00318     if( encStore->isEncrypted( ) ) {
00319         return encStore;
00320     }
00321     KoZipStore *zipStore = encStore->ripZipStore( );
00322     delete encStore;
00323     return zipStore;
00324 }
00325 
00326 KoStore* KoEncryptedStore::createEncryptedStoreReader( QWidget* window, const KUrl& url, const QString & filename, const QByteArray & appIdentification ) {
00327     KoEncryptedStore *encStore = new KoEncryptedStore( window, url, filename, Read, appIdentification );
00328     if( encStore->isEncrypted( ) ) {
00329         return encStore;
00330     }
00331     KoZipStore *zipStore = encStore->ripZipStore( );
00332     delete encStore;
00333     return zipStore;
00334 }
00335 
00336 bool KoEncryptedStore::init( Mode mode, const QByteArray& /*appIdentification*/ ) {
00337     KoStore::init( mode );
00338     m_mode = mode;
00339     if( mode == Read ) {
00340         // Read the manifest-file, so we can get the data out we'll need to decrypt the other files in the store
00341         m_store->pushDirectory( );
00342         bool ok = m_store->open( "tar:/META-INF/manifest.xml" );
00343         if( !ok ) {
00344             if( !m_store->bad( ) && !m_store->hasFile( "tar:/META-INF/manifest.xml" ) ) {
00345                 return true; // No manifest file? OK, *I* won't complain.
00346             }
00347             m_bGood = false;
00348             return false;
00349         }
00350         QIODevice *dev = m_store->device( );
00351 
00352         KoXmlDocument xmldoc;
00353         if( !xmldoc.setContent( dev ) ) {
00354             KMessage::message( KMessage::Warning, i18n( "The manifest file seems to be corrupted. The document could not be opened." ) );
00355             m_store->close( );
00356             m_bGood = false;
00357             return false;
00358         }
00359         KoXmlElement xmlroot = xmldoc.documentElement( );
00360         if( xmlroot.tagName( ) != "manifest:manifest" ) {
00361             KMessage::message( KMessage::Warning, i18n( "The manifest file seems to be corrupted. The document could not be opened." ) );
00362             m_store->close( );
00363             m_bGood = false;
00364             return false;
00365         }
00366 
00367         if( xmlroot.hasChildNodes( ) ) {
00368             QCA::Base64 base64decoder( QCA::Decode );
00369             KoXmlNode xmlnode = xmlroot.firstChild( );
00370             while( !xmlnode.isNull( ) ) {
00371                 // Search for files
00372                 if( !xmlnode.isElement( ) || xmlnode.toElement( ).tagName( ) != "manifest:file-entry" || !xmlnode.hasChildNodes( ) || !xmlnode.toElement( ).hasAttribute( "manifest:full-path" ) ) {
00373                     xmlnode = xmlnode.nextSibling( );
00374                     continue;
00375                 }
00376 
00377                 // Build a structure to hold the data and fill it with defaults
00378                 KoEncryptedStore_EncryptionData encData;
00379                 encData.filesize = 0;
00380                 encData.checksum = QSecureArray();
00381                 encData.checksumShort = false;
00382                 encData.salt = QSecureArray();
00383                 encData.iterationCount = 0;
00384                 encData.initVector = QSecureArray();
00385 
00386                 // Get some info about the file
00387                 QString fullpath = xmlnode.toElement( ).attribute( "manifest:full-path" );
00388                 if( xmlnode.toElement( ).hasAttribute( "manifest:size" ) ) {
00389                     encData.filesize = xmlnode.toElement( ).attribute( "manifest:size" ).toUInt( );
00390                 }
00391 
00392                 // Find the embedded encryption-data block
00393                 KoXmlNode xmlencnode = xmlnode.firstChild( );
00394                 while( !xmlencnode.isNull( ) && ( !xmlencnode.isElement( ) || xmlencnode.toElement( ).tagName( ) != "manifest:encryption-data" || !xmlencnode.hasChildNodes( ) ) ) {
00395                     xmlencnode = xmlencnode.nextSibling( );
00396                 }
00397                 if( xmlencnode.isNull( ) ) {
00398                     xmlnode = xmlnode.nextSibling( );
00399                     continue;
00400                 }
00401 
00402                 // Find some things about the checksum
00403                 if( xmlencnode.toElement( ).hasAttribute( "manifest:checksum" ) ) {
00404                     base64decoder.clear( );
00405                     encData.checksum = base64decoder.update( QSecureArray( xmlencnode.toElement( ).attribute( "manifest:checksum" ).toAscii( ) ) );
00406                     encData.checksum += base64decoder.final( );
00407                     if( xmlencnode.toElement( ).hasAttribute( "manifest:checksum-type" ) ) {
00408                         QString checksumType = xmlencnode.toElement( ).attribute( "manifest:checksum-type" );
00409                         if( checksumType == "SHA1" ) {
00410                             encData.checksumShort = false;
00411                         }
00412                         // For this particual hash-type: check KoEncryptedStore_encryptionData.checksumShort
00413                         else if( checksumType == "SHA1/1K" ) {
00414                             encData.checksumShort = true;
00415                         }
00416                         else {
00417                             // Checksum type unknown
00418                             KMessage::message( KMessage::Warning, i18n( "This document contains an unknown checksum. When you give a password it might not be verified." ) );
00419                             encData.checksum = QSecureArray();
00420                         }
00421                     }
00422                     else {
00423                         encData.checksumShort = false;
00424                     }
00425                 }
00426 
00427                 KoXmlNode xmlencattr = xmlencnode.firstChild( );
00428                 bool algorithmFound = false;
00429                 bool keyDerivationFound = false;
00430                 // Search all data about encrption
00431                 while( !xmlencattr.isNull( ) ) {
00432                     if( !xmlencattr.isElement( ) ) {
00433                         xmlencattr = xmlencattr.nextSibling( );
00434                         continue;
00435                     }
00436                     
00437                     // Find some things about the encryption algorithm
00438                     if( xmlencattr.toElement( ).tagName( ) == "manifest:algorithm" && xmlencattr.toElement( ).hasAttribute( "manifest:initialisation-vector" ) ) {
00439                         algorithmFound = true;
00440                         base64decoder.clear( );
00441                         encData.initVector = base64decoder.update( QSecureArray( xmlencattr.toElement( ).attribute( "manifest:initialisation-vector" ).toAscii( ) ) );
00442                         encData.initVector += base64decoder.final( );
00443                         if( xmlencattr.toElement( ).hasAttribute( "manifest:algorithm-name" ) && xmlencattr.toElement( ).attribute( "manifest:algorithm-name" ) != "Blowfish CFB" ) {
00444                             KMessage::message( KMessage::Warning, i18n( "This document contains an unknown encryption method. Some parts may be unreadable." ) );
00445                             encData.initVector = QSecureArray();
00446                         }
00447                     }
00448 
00449                     // Find some things about the key derivation
00450                     if( xmlencattr.toElement( ).tagName( ) == "manifest:key-derivation" && xmlencattr.toElement( ).hasAttribute( "manifest:salt" ) ) {
00451                         keyDerivationFound = true;
00452                         base64decoder.clear( );
00453                         encData.salt = base64decoder.update( QSecureArray( xmlencattr.toElement( ).attribute( "manifest:salt" ).toAscii( ) ) );
00454                         encData.salt += base64decoder.final( );
00455                         encData.iterationCount = 1024;
00456                         if( xmlencattr.toElement( ).hasAttribute( "manifest:iteration-count" ) ) {
00457                             encData.iterationCount = xmlencattr.toElement( ).attribute( "manifest:iteration-count" ).toUInt( );
00458                         }
00459                         if( xmlencattr.toElement( ).hasAttribute( "manifest:key-derivation-name" ) && xmlencattr.toElement( ).attribute( "manifest:key-derivation-name" ) != "PBKDF2" ) {
00460                             KMessage::message( KMessage::Warning, i18n( "This document contains an unknown encryption method. Some parts may be unreadable." ) );
00461                             encData.salt = QSecureArray();
00462                         }
00463                     }
00464                     
00465                     xmlencattr = xmlencattr.nextSibling( );
00466                 }
00467 
00468                 // Only use this encryption data if it makes sense to use it
00469                 if( !( encData.salt.isEmpty() || encData.initVector.isEmpty() ) ) {
00470                     m_encryptionData.insert( normalizedFullPath( fullpath ), encData );
00471                     if( !( algorithmFound && keyDerivationFound ) ) {
00472                         KMessage::message( KMessage::Warning, i18n( "This document contains incomplete encryption data. Some parts may be unreadable." ) );
00473                     }
00474                 }
00475 
00476                 xmlnode = xmlnode.nextSibling( );
00477             }
00478         }
00479 
00480         // Let's make sure we're clean at the beginning again: no one needs to know we've been nosing around
00481         m_store->close( );
00482         m_store->popDirectory( );
00483     }
00484     
00485     return true;
00486 }
00487 
00488 bool KoEncryptedStore::isEncrypted( ) {
00489     if( m_mode == Read ) {
00490         return !m_encryptionData.isEmpty( );
00491     }
00492     return true;
00493 }
00494 
00495 bool KoEncryptedStore::openWrite( const QString& name ) {
00496     if( normalizedFullPath( name ) != MANIFEST_FILE ) {
00497         if( !m_store && !m_init_deferred ) {
00498             return false;
00499         }
00500         if( m_store && !m_store->open( normalizedFullPath( name ) ) ) {
00501             return false;
00502         }
00503     }
00504     m_stream = new QBuffer( );
00505     if( !m_stream->open( QIODevice::WriteOnly ) ) {
00506         return false;
00507     }
00508     return true;
00509 }
00510 
00511 bool KoEncryptedStore::openRead( const QString& name ) {
00512     if( !m_store || !m_store->open( name ) ) {
00513         return false;
00514     }
00515     if( !m_encryptionData.contains( normalizedFullPath( name ) ) ) {
00516         // The file is not encrypted, or at least we don't know about it, simply pass on
00517         if( m_stream ) {
00518             delete m_stream;
00519         }
00520         m_stream = m_store->device( );
00521         m_iSize = m_store->size( );
00522     }
00523     else {
00524         QSecureArray encryptedFile( m_store->device( )->readAll( ) );
00525         if( encryptedFile.size( ) != m_store->size( ) ) {
00526             // Read error detected
00527             m_store->close( );
00528             return false;
00529         }
00530         m_store->close( );
00531         KoEncryptedStore_EncryptionData encData = m_encryptionData.value( normalizedFullPath( name ) );
00532         QSecureArray decrypted;
00533 
00534         // If we don't have a password yet, try and find one
00535         if( m_password.isEmpty( ) ) {
00536             findPasswordInKWallet( );
00537         }
00538 
00539         // Used to be "while( passwordOK )", but why use a flag if I can just say "break" on that one place I set that flag?
00540         while( true ) {
00541             QByteArray pass;
00542             QSecureArray password;
00543             int keepPass = 0;
00544             // I already have a password! Let's try it. If it's not good, we can dump it, anyway.
00545             if( !m_password.isEmpty( ) ) {
00546                 password = m_password;
00547                 m_password = QSecureArray();
00548             }
00549             else {
00550                 QByteArray pass;
00551                 if( !m_filename.isNull( ) )
00552                     keepPass = 1;
00553                 if( KPasswordDialog::getPassword( m_window, pass, i18n( "Please enter the password to open this file." ), &keepPass ) == KPasswordDialog::Rejected ) {
00554                     return false;
00555                 }
00556                 password = QSecureArray( pass );
00557                 if( password.isEmpty( ) ) {
00558                     continue;
00559                 }
00560             }
00561 
00562             decrypted = decryptFile( encryptedFile, encData, password );
00563             if( decrypted.isEmpty() ) {
00564                 return false;
00565             }
00566 
00567             if( !encData.checksum.isEmpty() ) {
00568                 QSecureArray checksum;
00569                 if( encData.checksumShort && decrypted.size( ) > 1024 ) {
00570                     // TODO: Eww!!!! I don't want to convert via insecure arrays to get the first 1K characters of a secure array <- fix QCA?
00571                     checksum = QCA::Hash( "sha1" ).hash( QSecureArray( decrypted.toByteArray( ).left( 1024 ) ) );
00572                 }
00573                 else {
00574                     checksum = QCA::Hash( "sha1" ).hash( decrypted );
00575                 }
00576                 if( checksum != encData.checksum ) {
00577                     continue;
00578                 }
00579             }
00580 
00581             // The password passed all possible tests, so let's accept it
00582             m_password = password;
00583 
00584             if( keepPass ) {
00585                 savePasswordInKWallet( );
00586             }
00587 
00588             break;
00589         }
00590 
00591         QByteArray *resultArray = new QByteArray( decrypted.toByteArray( ) );
00592         QIODevice *resultDevice = KFilterDev::device( new QBuffer( resultArray, NULL ), "application/x-gzip" );
00593         if( !resultDevice ) {
00594             delete resultArray;
00595             return false;
00596         }
00597         static_cast<KFilterDev*>( resultDevice )->setSkipHeaders( );
00598         m_stream = resultDevice;
00599         m_iSize = encData.filesize;
00600     }
00601 
00602     return true;
00603 }
00604 
00605 void KoEncryptedStore::findPasswordInKWallet( ) {
00606     /* About KWallet access
00607      *
00608      * The choice has been made to postfix every entry in a kwallet concerning passwords for opendocument files with /opendocument
00609      * This choice has been made since, at the time of this writing, the author could not find any reference as to standardized
00610      * naming schemes for entries in the wallet. Since collision of passwords in entries should be avoided and is at least possible,
00611      * considering remote files might be both protected by a secured web-area (konqueror makes an entry) and a password (we make an
00612      * entry), it seems a good thing to make sure it won't happen.
00613      */
00614     if( !m_filename.isNull( ) && !KWallet::Wallet::folderDoesNotExist( KWallet::Wallet::LocalWallet( ), KWallet::Wallet::PasswordFolder( ) ) && !KWallet::Wallet::keyDoesNotExist( KWallet::Wallet::LocalWallet( ), KWallet::Wallet::PasswordFolder( ), m_filename + "/opendocument" ) ) {
00615         KWallet::Wallet *wallet = KWallet::Wallet::openWallet( KWallet::Wallet::LocalWallet( ), m_window ? m_window->winId( ) : 0 );
00616         if( wallet ) {
00617             if( wallet->setFolder( KWallet::Wallet::PasswordFolder( ) ) ) {
00618                 QString pass;
00619                 wallet->readPassword( m_filename + "/opendocument", pass );
00620                 m_password = QSecureArray( pass.toUtf8( ) );
00621             }
00622             delete wallet;
00623         }
00624     }
00625 }
00626 
00627 void KoEncryptedStore::savePasswordInKWallet( ) {
00628     KWallet::Wallet *wallet = KWallet::Wallet::openWallet( KWallet::Wallet::LocalWallet( ), m_window ? m_window->winId( ) : 0 );
00629     if( wallet ) {
00630         if( !wallet->hasFolder( KWallet::Wallet::PasswordFolder( ) ) ) {
00631             wallet->createFolder( KWallet::Wallet::PasswordFolder( ) );
00632         }
00633         if( wallet->setFolder( KWallet::Wallet::PasswordFolder( ) ) ) {
00634             if( wallet->hasEntry( m_filename + "/opendocument" ) ) {
00635                 wallet->removeEntry( m_filename + "/opendocument" );
00636             }
00637             wallet->writePassword( m_filename + "/opendocument", m_password.toByteArray( ).data( ) );
00638         }
00639         delete wallet;
00640     }
00641 }
00642 
00643 QSecureArray KoEncryptedStore::decryptFile( QSecureArray & encryptedFile, KoEncryptedStore_EncryptionData & encData, QSecureArray & password ) {
00644     if( !QCA::isSupported( "sha1" ) || !QCA::isSupported( "pbkdf2(sha1)" ) ) {
00645         return QSecureArray( );
00646     }
00647     QSecureArray keyhash = QCA::Hash( "sha1" ).hash( password );
00648     QCA::SymmetricKey key = QCA::PBKDF2( "sha1" ).makeKey( keyhash, QCA::InitializationVector( encData.salt ), 16, encData.iterationCount );
00649     QCA::Cipher decrypter( "blowfish", QCA::Cipher::CFB, QCA::Cipher::DefaultPadding, QCA::Decode, key, QCA::InitializationVector( encData.initVector ) );
00650     QSecureArray result = decrypter.update( encryptedFile );
00651     result += decrypter.final( );
00652     return result;
00653 }
00654 
00655 bool KoEncryptedStore::setPassword( const QString& password ) {
00656     if( !m_password.isEmpty() || password.isEmpty() ) {
00657         return false;
00658     }
00659     m_password = QSecureArray( password.toUtf8() );
00660     initBackend( );
00661     return true;
00662 }
00663 
00664 bool KoEncryptedStore::closeWrite() {
00665     bool passWasAsked = false;
00666     if( normalizedFullPath( m_sName ) == MANIFEST_FILE ) {
00667         m_manifestBuffer = static_cast<QBuffer*>( m_stream )->buffer( );
00668         return true;
00669     }
00670     // Allow close, but don't process it when nothing can be done in the backend
00671     if( !m_store || !m_store->isOpen( ) ) {
00672         return true;
00673     }
00674 
00675     // Find a password
00676     if( m_password.isEmpty() ) {
00677         findPasswordInKWallet( );
00678     }
00679     while( m_password.isEmpty( ) ) {
00680         QByteArray pass;
00681         if( KPasswordDialog::getNewPassword( m_window ? m_window : NULL, pass, i18n( "Please enter the password to encrypt the document with." ) ) == KPasswordDialog::Rejected ) {
00682             return false;
00683         }
00684         m_password = QSecureArray( pass );
00685         passWasAsked = true;
00686     }
00687     // So, we have a password, then we can initialize the backend (if necessary)
00688     if( !initBackend( ) ) {
00689         return false;
00690     }
00691 
00692     // Ask the user to save the password
00693     if( passWasAsked && KMessageBox::questionYesNo( m_window ? m_window : NULL, i18n( "Do you want to save the password?" ) ) == KMessageBox::Yes ) {
00694         savePasswordInKWallet( );
00695     }
00696 
00697     QByteArray resultData;
00698     if( normalizedFullPath( m_sName ) == META_FILE ) {
00699         // Save as-is
00700         resultData = static_cast<QBuffer*>( m_stream )->buffer( );
00701     }
00702     else if( normalizedFullPath( m_sName ) == THUMBNAIL_FILE ) {
00703         // TODO: Replace with a generic 'encrypted'-thumbnail
00704         resultData = static_cast<QBuffer*>( m_stream )->buffer( );
00705     }
00706     else {
00707         // Build all cryptographic data
00708         QSecureArray passwordHash = QCA::Hash( "sha1" ).hash( m_password );
00709         QCA::Random random;
00710         KoEncryptedStore_EncryptionData encData;
00711         encData.initVector = random.randomArray( 8, QCA::Random::LongTermKey );
00712         encData.salt = random.randomArray( 16, QCA::Random::LongTermKey );
00713         encData.iterationCount = 1024;
00714         QCA::SymmetricKey key = QCA::PBKDF2( "sha1" ).makeKey( passwordHash, QCA::InitializationVector( encData.salt ), 16, encData.iterationCount );
00715         QCA::Cipher encrypter( "blowfish", QCA::Cipher::CFB, QCA::Cipher::DefaultPadding, QCA::Encode, key, QCA::InitializationVector( encData.initVector ) );
00716 
00717         // Get the written data
00718         QByteArray data = static_cast<QBuffer*>( m_stream )->buffer( );
00719         encData.filesize = data.size( );
00720 
00721         // Compress the data
00722         QBuffer compressedData;
00723         QIODevice *compressDevice = KFilterDev::device( &compressedData, "application/x-gzip", false );
00724         if( !compressDevice) {
00725             return false;
00726         }
00727         static_cast<KFilterDev*>( compressDevice )->setSkipHeaders( );
00728         if( !compressDevice->open( QIODevice::WriteOnly ) ) {
00729             delete compressDevice;
00730             return false;
00731         }
00732         if( compressDevice->write( data ) != data.size( ) ) {
00733             delete compressDevice;
00734             return false;
00735         }
00736         compressDevice->close( );
00737         delete compressDevice;
00738 
00739         // Take a checksum of the data
00740         // Use the SHA1/1K method until it's clear if OOo supports plain SHA1
00741         // TODO: Find that out!
00742         if( data.size( ) > 1024 ) {
00743             QByteArray datatmp = compressedData.buffer( ).left( 1024 );
00744             encData.checksum = QCA::Hash( "sha1" ).hash( QSecureArray( datatmp ) );
00745         }
00746         else {
00747             encData.checksum = QCA::Hash( "sha1" ).hash( QSecureArray( compressedData.buffer( ) ) );
00748         }
00749         encData.checksumShort = true;
00750 
00751         // Encrypt the data
00752         QSecureArray result = encrypter.update( QSecureArray( compressedData.buffer( ) ) );
00753         result += encrypter.final( );
00754         resultData = result.toByteArray( );
00755 
00756         m_encryptionData.insert( normalizedFullPath( m_sName ), encData );
00757     }
00758 
00759     // Write it
00760     // TODO: Make sure it isn't compressed
00761     static_cast<KoStore *>( m_store )->write( resultData );
00762     
00763     return m_store->close( );
00764 }
00765 
00766 bool KoEncryptedStore::closeRead() {
00767     if( m_store && m_store->isOpen( ) ) {
00768         if( !m_store->close( ) ) {
00769             return false;
00770         }
00771         // m_stream is closed by m_store
00772         m_stream = NULL;
00773     }
00774     else if( m_stream ) {
00775         delete m_stream;
00776         m_stream = NULL;
00777     }
00778     return true;
00779 }
00780 
00781 // TODO: test these constructions
00782 bool KoEncryptedStore::enterRelativeDirectory( const QString& dirName ) {
00783     if( m_store ) {
00784         m_store->pushDirectory( );
00785         bool res = m_store->enterDirectory( currentDirectory( ) ) && m_store->enterDirectory( dirName );
00786         m_store->popDirectory( );
00787         return res;
00788     }
00789     return m_init_deferred;
00790 }
00791 
00792 bool KoEncryptedStore::enterAbsoluteDirectory( const QString& path ) {
00793     if( m_store ) {
00794         m_store->pushDirectory( );
00795         bool res = m_store->enterDirectory( path );
00796         m_store->popDirectory( );
00797         return res;
00798     }
00799     return m_init_deferred;
00800 }
00801 
00802 bool KoEncryptedStore::fileExists( const QString& absPath ) const {
00803     if( m_mode == Write ) {
00804         return m_strFiles.contains( absPath );
00805     }
00806     if( m_store ) {
00807         int pos;
00808         QString tmp( absPath );
00809 
00810         // Clean path first
00811         if( tmp.left( 5 ) == "tar:/" )
00812             tmp = tmp.mid( 5 );
00813         if( tmp[0] == '/' )
00814             tmp = tmp.mid( 1 );
00815 
00816         m_store->pushDirectory( );
00817         if( !m_store->enterDirectory( "tar:/" ) ) {
00818             m_store->popDirectory( );
00819             return false;
00820         }
00821 
00822         while( ( pos = tmp.indexOf( '/' ) ) != -1 ) {
00823             if( !m_store->enterDirectory( tmp.left( pos ) ) ) {
00824                 m_store->popDirectory( );
00825                 return false;
00826             }
00827             tmp = tmp.mid( pos + 1 );
00828         }
00829 
00830         bool result = m_store->hasFile( absPath );
00831         m_store->popDirectory( );
00832         return result;
00833     }
00834     return false;
00835 }
00836 
00837 // Stupid little method to fix differences between internal paths and manifest:full-path paths
00838 QString KoEncryptedStore::normalizedFullPath( const QString& fullpath ) {
00839     if( fullpath.startsWith( "/" ) ) {
00840         return fullpath.mid( 1 );
00841     }
00842     return fullpath;
00843 }

Généré le Wed Nov 22 23:41:14 2006 pour KPlato par  doxygen 1.5.1-p1