2021-09-27 11:14:20 +02:00
// Copyright (C) 2019-2021 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// with the written consent of the copyright owner, any later version.
//
// PDF4QT 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.
# include "pdfsecurityhandler.h"
# include "pdfexception.h"
# include "pdfencoding.h"
# include "pdfvisitor.h"
# include "pdfutils.h"
# include "pdfdocumentbuilder.h"
# include <QRandomGenerator>
# include <openssl/rc4.h>
# include <openssl/md5.h>
# include <openssl/aes.h>
# include <openssl/sha.h>
# include <array>
namespace pdf
{
// Padding password
static constexpr std : : array < uint8_t , 32 > PDFPasswordPadding = {
0x28 , 0xBF , 0x4E , 0x5E , 0x4E , 0x75 , 0x8A , 0x41 ,
0x64 , 0x00 , 0x4E , 0x56 , 0xFF , 0xFA , 0x01 , 0x08 ,
0x2E , 0x2E , 0x00 , 0xB6 , 0xD0 , 0x68 , 0x3E , 0x80 ,
0x2F , 0x0C , 0xA9 , 0xFE , 0x64 , 0x53 , 0x69 , 0x7A
} ;
class PDFDecryptOrEncryptObjectVisitor : public PDFAbstractVisitor
{
public :
enum class Mode
{
Decrypt ,
Encrypt
} ;
explicit PDFDecryptOrEncryptObjectVisitor ( const PDFSecurityHandler * securityHandler , PDFObjectReference reference , Mode mode ) :
m_securityHandler ( securityHandler ) ,
m_reference ( reference ) ,
m_mode ( mode )
{
m_objectStack . reserve ( 32 ) ;
}
virtual void visitNull ( ) override ;
virtual void visitBool ( bool value ) override ;
virtual void visitInt ( PDFInteger value ) override ;
virtual void visitReal ( PDFReal value ) override ;
virtual void visitString ( PDFStringRef string ) override ;
virtual void visitName ( PDFStringRef name ) override ;
virtual void visitArray ( const PDFArray * array ) override ;
virtual void visitDictionary ( const PDFDictionary * dictionary ) override ;
virtual void visitStream ( const PDFStream * stream ) override ;
virtual void visitReference ( const PDFObjectReference reference ) override ;
PDFObject getProcessedObject ( ) ;
private :
const PDFSecurityHandler * m_securityHandler = nullptr ;
std : : vector < PDFObject > m_objectStack ;
PDFObjectReference m_reference ;
Mode m_mode = Mode : : Decrypt ;
} ;
void PDFDecryptOrEncryptObjectVisitor : : visitNull ( )
{
m_objectStack . push_back ( PDFObject : : createNull ( ) ) ;
}
void PDFDecryptOrEncryptObjectVisitor : : visitBool ( bool value )
{
m_objectStack . push_back ( PDFObject : : createBool ( value ) ) ;
}
void PDFDecryptOrEncryptObjectVisitor : : visitInt ( PDFInteger value )
{
m_objectStack . push_back ( PDFObject : : createInteger ( value ) ) ;
}
void PDFDecryptOrEncryptObjectVisitor : : visitReal ( PDFReal value )
{
m_objectStack . push_back ( PDFObject : : createReal ( value ) ) ;
}
void PDFDecryptOrEncryptObjectVisitor : : visitString ( PDFStringRef string )
{
switch ( m_mode )
{
case pdf : : PDFDecryptOrEncryptObjectVisitor : : Mode : : Decrypt :
m_objectStack . push_back ( PDFObject : : createString ( m_securityHandler - > decrypt ( string . getString ( ) , m_reference , PDFSecurityHandler : : EncryptionScope : : String ) ) ) ;
break ;
case pdf : : PDFDecryptOrEncryptObjectVisitor : : Mode : : Encrypt :
m_objectStack . push_back ( PDFObject : : createString ( m_securityHandler - > encrypt ( string . getString ( ) , m_reference , PDFSecurityHandler : : EncryptionScope : : String ) ) ) ;
break ;
default :
Q_ASSERT ( false ) ;
break ;
}
}
void PDFDecryptOrEncryptObjectVisitor : : visitName ( PDFStringRef name )
{
m_objectStack . push_back ( PDFObject : : createName ( name ) ) ;
}
void PDFDecryptOrEncryptObjectVisitor : : visitArray ( const PDFArray * array )
{
acceptArray ( array ) ;
// We have all objects on the stack
Q_ASSERT ( array - > getCount ( ) < = m_objectStack . size ( ) ) ;
auto it = std : : next ( m_objectStack . cbegin ( ) , m_objectStack . size ( ) - array - > getCount ( ) ) ;
std : : vector < PDFObject > objects ( it , m_objectStack . cend ( ) ) ;
PDFObject object = PDFObject : : createArray ( std : : make_shared < PDFArray > ( qMove ( objects ) ) ) ;
m_objectStack . erase ( it , m_objectStack . cend ( ) ) ;
m_objectStack . push_back ( object ) ;
}
void PDFDecryptOrEncryptObjectVisitor : : visitDictionary ( const PDFDictionary * dictionary )
{
Q_ASSERT ( dictionary ) ;
// We must check, if it is or isn't a signature dictionary. If it is,
// then don't decrypt/encrypt the Content value. We also don't check, if signature
// isn't indirectly referenced by reference. Hope it isn't...
const PDFObject & typeObject = dictionary - > get ( " Type " ) ;
bool isSignatureObject = ( typeObject . isName ( ) & & typeObject . getString ( ) = = " Sig " ) ;
std : : vector < PDFDictionary : : DictionaryEntry > entries ;
entries . reserve ( dictionary - > getCount ( ) ) ;
for ( size_t i = 0 , count = dictionary - > getCount ( ) ; i < count ; + + i )
{
if ( isSignatureObject & & dictionary - > getKey ( i ) = = " Contents " )
{
entries . emplace_back ( dictionary - > getKey ( i ) , dictionary - > getValue ( i ) ) ;
}
else
{
dictionary - > getValue ( i ) . accept ( this ) ;
entries . emplace_back ( dictionary - > getKey ( i ) , m_objectStack . back ( ) ) ;
m_objectStack . pop_back ( ) ;
}
}
m_objectStack . push_back ( PDFObject : : createDictionary ( std : : make_shared < PDFDictionary > ( qMove ( entries ) ) ) ) ;
}
void PDFDecryptOrEncryptObjectVisitor : : visitStream ( const PDFStream * stream )
{
// Don't decrypt/encrypt, if it is a Metadata stream and Metadata encryption is turned off
const PDFDictionary * dictionary = stream - > getDictionary ( ) ;
const PDFObject & typeObject = dictionary - > get ( " Type " ) ;
bool isMetadata = ( typeObject . isName ( ) & & typeObject . getString ( ) = = " Metadata " ) ;
if ( isMetadata & & ! m_securityHandler - > isMetadataEncrypted ( ) )
{
m_objectStack . push_back ( PDFObject : : createStream ( std : : make_shared < PDFStream > ( PDFDictionary ( * dictionary ) , QByteArray ( * stream - > getContent ( ) ) ) ) ) ;
return ;
}
// Decrypt/encrypt the dictionary
visitDictionary ( dictionary ) ;
PDFObject dictionaryObject = m_objectStack . back ( ) ;
m_objectStack . pop_back ( ) ;
// We must also handle situation, that stream has specified Crypt filter.
// In this case, we must delegate decryption/encryption to the stream filters.
PDFDictionary processedDictionary ( * dictionaryObject . getDictionary ( ) ) ;
QByteArray processedData ;
if ( ! processedDictionary . hasKey ( " Crypt " ) )
{
// Is it an embedded file?
const PDFObject & object = processedDictionary . get ( " Type " ) ;
const bool isEmbeddedFile = object . isName ( ) & & object . getString ( ) = = " EmbeddedFile " ;
const PDFSecurityHandler : : EncryptionScope scope = ! isEmbeddedFile ? PDFSecurityHandler : : EncryptionScope : : Stream : PDFSecurityHandler : : EncryptionScope : : EmbeddedFile ;
switch ( m_mode )
{
case pdf : : PDFDecryptOrEncryptObjectVisitor : : Mode : : Decrypt :
processedData = m_securityHandler - > decrypt ( * stream - > getContent ( ) , m_reference , scope ) ;
break ;
case pdf : : PDFDecryptOrEncryptObjectVisitor : : Mode : : Encrypt :
processedData = m_securityHandler - > encrypt ( * stream - > getContent ( ) , m_reference , scope ) ;
break ;
default :
Q_ASSERT ( false ) ;
break ;
}
processedDictionary . setEntry ( PDFInplaceOrMemoryString ( " Length " ) , PDFObject : : createInteger ( processedData . size ( ) ) ) ;
}
else
{
switch ( m_mode )
{
case pdf : : PDFDecryptOrEncryptObjectVisitor : : Mode : : Decrypt :
{
processedData = * stream - > getContent ( ) ;
processedDictionary . setEntry ( PDFInplaceOrMemoryString ( PDFSecurityHandler : : OBJECT_REFERENCE_DICTIONARY_NAME ) , PDFObject : : createReference ( m_reference ) ) ;
break ;
}
case pdf : : PDFDecryptOrEncryptObjectVisitor : : Mode : : Encrypt :
{
processedData = * stream - > getContent ( ) ;
processedDictionary . removeEntry ( PDFSecurityHandler : : OBJECT_REFERENCE_DICTIONARY_NAME ) ;
break ;
}
default :
Q_ASSERT ( false ) ;
break ;
}
}
m_objectStack . push_back ( PDFObject : : createStream ( std : : make_shared < PDFStream > ( qMove ( processedDictionary ) , qMove ( processedData ) ) ) ) ;
}
void PDFDecryptOrEncryptObjectVisitor : : visitReference ( const PDFObjectReference reference )
{
m_objectStack . push_back ( PDFObject : : createReference ( reference ) ) ;
}
PDFObject PDFDecryptOrEncryptObjectVisitor : : getProcessedObject ( )
{
Q_ASSERT ( m_objectStack . size ( ) = = 1 ) ;
return qMove ( m_objectStack . back ( ) ) ;
}
PDFObject PDFSecurityHandler : : decryptObject ( const PDFObject & object , PDFObjectReference reference ) const
{
PDFDecryptOrEncryptObjectVisitor visitor ( this , reference , PDFDecryptOrEncryptObjectVisitor : : Mode : : Decrypt ) ;
object . accept ( & visitor ) ;
return visitor . getProcessedObject ( ) ;
}
PDFObject PDFSecurityHandler : : encryptObject ( const PDFObject & object , PDFObjectReference reference ) const
{
PDFDecryptOrEncryptObjectVisitor visitor ( this , reference , PDFDecryptOrEncryptObjectVisitor : : Mode : : Encrypt ) ;
object . accept ( & visitor ) ;
return visitor . getProcessedObject ( ) ;
}
PDFSecurityHandlerPointer PDFSecurityHandler : : createSecurityHandler ( const PDFObject & encryptionDictionaryObject , const QByteArray & id )
{
if ( encryptionDictionaryObject . isNull ( ) )
{
return PDFSecurityHandlerPointer ( new PDFNoneSecurityHandler ( ) ) ;
}
if ( ! encryptionDictionaryObject . isDictionary ( ) )
{
throw PDFException ( PDFTranslationContext : : tr ( " Invalid encryption dictionary. " ) ) ;
}
const PDFDictionary * dictionary = encryptionDictionaryObject . getDictionary ( ) ;
auto getName = [ ] ( const PDFDictionary * dictionary , const char * key , bool required , const char * defaultValue = nullptr ) - > QByteArray
{
const PDFObject & nameObject = dictionary - > get ( key ) ;
if ( nameObject . isNull ( ) )
{
return defaultValue ? QByteArray ( defaultValue ) : QByteArray ( ) ;
}
if ( ! nameObject . isName ( ) )
{
if ( required )
{
throw PDFException ( PDFTranslationContext : : tr ( " Invalid value for entry '%1' in encryption dictionary. Name expected. " ) . arg ( QString : : fromLatin1 ( key ) ) ) ;
}
return defaultValue ? QByteArray ( defaultValue ) : QByteArray ( ) ;
}
return nameObject . getString ( ) ;
} ;
auto getInt = [ ] ( const PDFDictionary * dictionary , const char * key , bool required , PDFInteger defaultValue = - 1 ) - > PDFInteger
{
const PDFObject & intObject = dictionary - > get ( key ) ;
if ( ! intObject . isInt ( ) )
{
if ( required )
{
throw PDFException ( PDFTranslationContext : : tr ( " Invalid value for entry '%1' in encryption dictionary. Integer expected. " ) . arg ( QString : : fromLatin1 ( key ) ) ) ;
}
return defaultValue ;
}
return intObject . getInteger ( ) ;
} ;
QByteArray filterName = getName ( dictionary , " Filter " , true ) ;
if ( filterName ! = " Standard " )
{
throw PDFException ( PDFTranslationContext : : tr ( " Unknown security handler. " ) ) ;
}
const int V = getInt ( dictionary , " V " , true ) ;
// Check V
if ( V < 1 | | V > 5 )
{
throw PDFException ( PDFTranslationContext : : tr ( " Unsupported version of document encryption (V = %1) . " ).arg(V)) ;
}
// Only valid for V == 2 or V == 3, otherwise we set file encryption key length manually
int Length = 40 ;
switch ( V )
{
case 1 :
Length = 40 ;
break ;
case 2 :
case 3 :
Length = getInt ( dictionary , " Length " , false , 40 ) ;
break ;
case 4 :
Length = 128 ;
break ;
case 5 :
Length = 256 ;
break ;
default :
Q_ASSERT ( false ) ;
break ;
}
// Create standard security handler
PDFStandardSecurityHandler handler ;
handler . m_V = V ;
handler . m_keyLength = Length ;
// Add "Identity" filter to the filters
CryptFilter identityFilter ;
identityFilter . type = CryptFilterType : : Identity ;
handler . m_cryptFilters [ IDENTITY_FILTER_NAME ] = identityFilter ;
if ( V = = 4 | | V = = 5 )
{
const PDFObject & cryptFilterObjects = dictionary - > get ( " CF " ) ;
if ( cryptFilterObjects . isDictionary ( ) )
{
auto parseCryptFilter = [ Length , & getName , & getInt ] ( const PDFObject & object ) - > CryptFilter
{
if ( ! object . isDictionary ( ) )
{
throw PDFException ( PDFTranslationContext : : tr ( " Crypt filter is not a dictionary! " ) ) ;
}
const PDFDictionary * cryptFilterDictionary = object . getDictionary ( ) ;
CryptFilter filter ;
QByteArray CFMName = getName ( cryptFilterDictionary , " CFM " , false , " None " ) ;
if ( CFMName = = " None " )
{
filter . type = CryptFilterType : : None ;
}
else if ( CFMName = = " V2 " )
{
filter . type = CryptFilterType : : V2 ;
}
else if ( CFMName = = " AESV2 " )
{
filter . type = CryptFilterType : : AESV2 ;
}
else if ( CFMName = = " AESV3 " )
{
filter . type = CryptFilterType : : AESV3 ;
}
else
{
throw PDFException ( PDFTranslationContext : : tr ( " Unsupported encryption algorithm '%1'. " ) . arg ( QString : : fromLatin1 ( CFMName ) ) ) ;
}
QByteArray authEventName = getName ( cryptFilterDictionary , " AuthEvent " , false , " DocOpen " ) ;
if ( authEventName = = " DocOpen " )
{
filter . authEvent = AuthEvent : : DocOpen ;
}
else if ( authEventName = = " EFOpen " )
{
filter . authEvent = AuthEvent : : EFOpen ;
}
else
{
throw PDFException ( PDFTranslationContext : : tr ( " Unsupported authorization event '%1'. " ) . arg ( QString : : fromLatin1 ( authEventName ) ) ) ;
}
filter . keyLength = getInt ( cryptFilterDictionary , " Length " , false , Length / 8 ) ;
return filter ;
} ;
const PDFDictionary * cryptFilters = cryptFilterObjects . getDictionary ( ) ;
for ( size_t i = 0 , cryptFilterCount = cryptFilters - > getCount ( ) ; i < cryptFilterCount ; + + i )
{
handler . m_cryptFilters [ cryptFilters - > getKey ( i ) . getString ( ) ] = parseCryptFilter ( cryptFilters - > getValue ( i ) ) ;
}
}
// Now, add standard filters
auto resolveFilter = [ & handler ] ( const QByteArray & name )
{
auto it = handler . m_cryptFilters . find ( name ) ;
if ( it = = handler . m_cryptFilters . cend ( ) )
{
throw PDFException ( PDFTranslationContext : : tr ( " Unknown crypt filter '%1'. " ) . arg ( QString : : fromLatin1 ( name ) ) ) ;
}
return it - > second ;
} ;
handler . m_filterStreams = resolveFilter ( getName ( dictionary , " StmF " , false , IDENTITY_FILTER_NAME ) ) ;
handler . m_filterStrings = resolveFilter ( getName ( dictionary , " StrF " , false , IDENTITY_FILTER_NAME ) ) ;
if ( dictionary - > hasKey ( " EFF " ) )
{
handler . m_filterEmbeddedFiles = resolveFilter ( getName ( dictionary , " EFF " , true ) ) ;
}
else
{
// According to the PDF specification, if 'EFF' entry is omitted, then filter
// for streams is used.
handler . m_filterEmbeddedFiles = handler . m_filterStreams ;
}
}
int R = getInt ( dictionary , " R " , true ) ;
if ( R < 2 | | R > 6 )
{
throw PDFException ( PDFTranslationContext : : tr ( " Revision %1 of standard security handler is not supported. " ) . arg ( R ) ) ;
}
handler . m_R = R ;
handler . m_filterDefault . authEvent = AuthEvent : : DocOpen ;
handler . m_filterDefault . keyLength = Length / 8 ;
handler . m_filterDefault . type = ( R > 4 ) ? CryptFilterType : : AESV3 : CryptFilterType : : V2 ;
auto readByteArray = [ dictionary ] ( const char * key , int size )
{
QByteArray result ;
const PDFObject & object = dictionary - > get ( key ) ;
if ( object . isString ( ) )
{
result = object . getString ( ) ;
if ( result . size ( ) ! = size )
{
throw PDFException ( PDFTranslationContext : : tr ( " Expected %1 characters long string in entry '%2'. Provided length is %3. " ) . arg ( size ) . arg ( QString : : fromLatin1 ( key ) ) . arg ( result . size ( ) ) ) ;
}
}
else
{
throw PDFException ( PDFTranslationContext : : tr ( " Expected %1 characters long string in entry '%2'. " ) . arg ( size ) . arg ( QString : : fromLatin1 ( key ) ) ) ;
}
return result ;
} ;
handler . m_O = readByteArray ( " O " , ( R ! = 6 & & R ! = 5 ) ? 32 : 48 ) ;
handler . m_U = readByteArray ( " U " , ( R ! = 6 & & R ! = 5 ) ? 32 : 48 ) ;
handler . m_permissions = static_cast < uint32_t > ( static_cast < int > ( getInt ( dictionary , " P " , true ) ) ) ;
if ( R = = 6 | | R = = 5 )
{
handler . m_OE = readByteArray ( " OE " , 32 ) ;
handler . m_UE = readByteArray ( " UE " , 32 ) ;
handler . m_Perms = readByteArray ( " Perms " , 16 ) ;
}
const PDFObject & encryptMetadataObject = dictionary - > get ( " EncryptMetadata " ) ;
if ( encryptMetadataObject . isBool ( ) )
{
handler . m_encryptMetadata = encryptMetadataObject . getBool ( ) ;
}
handler . m_ID = id ;
return PDFSecurityHandlerPointer ( new PDFStandardSecurityHandler ( qMove ( handler ) ) ) ;
}
void PDFSecurityHandler : : fillEncryptionDictionary ( PDFObjectFactory & factory ) const
{
factory . beginDictionaryItem ( " V " ) ;
factory < < PDFInteger ( m_V ) ;
factory . endDictionaryItem ( ) ;
factory . beginDictionaryItem ( " Length " ) ;
factory < < PDFInteger ( m_keyLength ) ;
factory . endDictionaryItem ( ) ;
if ( m_V = = 4 | | m_V = = 5 )
{
factory . beginDictionaryItem ( " CF " ) ;
factory . beginDictionary ( ) ;
QByteArray stmfName = " Identity " ;
QByteArray strfName = stmfName ;
QByteArray effName = stmfName ;
for ( const auto & cryptFilter : m_cryptFilters )
{
factory . beginDictionaryItem ( cryptFilter . first ) ;
factory . beginDictionary ( ) ;
factory . beginDictionaryItem ( " CFM " ) ;
if ( cryptFilter . second = = m_filterStrings )
{
strfName = cryptFilter . first ;
}
if ( cryptFilter . second = = m_filterStreams )
{
stmfName = cryptFilter . first ;
}
if ( cryptFilter . second = = m_filterEmbeddedFiles )
{
effName = cryptFilter . first ;
}
switch ( cryptFilter . second . type )
{
case CryptFilterType : : None :
// The application shall decrypt the data using the security handler
factory < < WrapName ( " None " ) ;
break ;
case CryptFilterType : : V2 :
// Use file encryption key for RC4 algorithm
factory < < WrapName ( " V2 " ) ;
break ;
case CryptFilterType : : AESV2 :
// Use file encryption key for AES algorithm
factory < < WrapName ( " AESV2 " ) ;
break ;
case CryptFilterType : : AESV3 :
// Use file encryption key for AES 256 bit algorithm
factory < < WrapName ( " AESV3 " ) ;
break ;
case CryptFilterType : : Identity :
// Don't decrypt anything, use identity function
factory < < WrapName ( " Identity " ) ;
break ;
default :
Q_ASSERT ( false ) ;
factory < < WrapName ( " None " ) ;
break ;
}
factory . endDictionaryItem ( ) ;
factory . beginDictionaryItem ( " AuthEvent " ) ;
switch ( cryptFilter . second . authEvent )
{
case AuthEvent : : DocOpen :
factory < < WrapName ( " DocOpen " ) ;
break ;
case AuthEvent : : EFOpen :
factory < < WrapName ( " EFOpen " ) ;
break ;
default :
Q_ASSERT ( false ) ;
break ;
}
factory . endDictionaryItem ( ) ;
factory . beginDictionaryItem ( " Length " ) ;
factory < < cryptFilter . second . keyLength ;
factory . endDictionaryItem ( ) ;
factory . endDictionary ( ) ;
factory . endDictionaryItem ( ) ;
}
factory . endDictionary ( ) ;
factory . endDictionaryItem ( ) ;
// Store StmF, StrF, EFF
factory . beginDictionaryItem ( " StmF " ) ;
factory < < WrapName ( stmfName ) ;
factory . endDictionaryItem ( ) ;
factory . beginDictionaryItem ( " StrF " ) ;
factory < < WrapName ( strfName ) ;
factory . endDictionaryItem ( ) ;
factory . beginDictionaryItem ( " EFF " ) ;
factory < < WrapName ( effName ) ;
factory . endDictionaryItem ( ) ;
}
}
PDFSecurityHandler * PDFStandardSecurityHandler : : clone ( ) const
{
return new PDFStandardSecurityHandler ( * this ) ;
}
PDFSecurityHandler : : AuthorizationResult PDFStandardSecurityHandler : : authenticate ( const std : : function < QString ( bool * ) > & getPasswordCallback , bool authorizeOwnerOnly )
{
QByteArray password ;
bool passwordObtained = true ;
// Clear the authorization data
m_authorizationData = AuthorizationData ( ) ;
while ( passwordObtained )
{
switch ( m_R )
{
case 2 :
case 3 :
case 4 :
{
// Try to authorize by owner password
{
QByteArray userPassword = createUserPasswordFromOwnerPassword ( password ) ;
QByteArray fileEncryptionKey = createFileEncryptionKey ( userPassword ) ;
QByteArray U = createEntryValueU_r234 ( fileEncryptionKey ) ;
if ( U = = m_U )
{
// We have authorized owner access
m_authorizationData . authorizationResult = AuthorizationResult : : OwnerAuthorized ;
m_authorizationData . fileEncryptionKey = fileEncryptionKey ;
return AuthorizationResult : : OwnerAuthorized ;
}
}
// Try to authorize user password
if ( ! authorizeOwnerOnly )
{
QByteArray fileEncryptionKey = createFileEncryptionKey ( password ) ;
QByteArray U = createEntryValueU_r234 ( fileEncryptionKey ) ;
if ( U = = m_U )
{
// We have authorized user access
m_authorizationData . authorizationResult = AuthorizationResult : : UserAuthorized ;
m_authorizationData . fileEncryptionKey = fileEncryptionKey ;
return AuthorizationResult : : UserAuthorized ;
}
}
break ;
}
case 5 :
case 6 :
{
UserOwnerData_r6 userData = parseParts ( m_U ) ;
UserOwnerData_r6 ownerData = parseParts ( m_O ) ;
auto createHash_r5 = [ ] ( const QByteArray & inputData )
{
QByteArray result ( SHA256_DIGEST_LENGTH , char ( 0 ) ) ;
SHA256 ( convertByteArrayToUcharPtr ( inputData ) , inputData . size ( ) , convertByteArrayToUcharPtr ( result ) ) ;
return result ;
} ;
auto createHash_r56 = [ this , & createHash_r5 ] ( const QByteArray & input , const QByteArray & inputPassword , bool useUserKey )
{
return ( m_R = = 5 ) ? createHash_r5 ( input ) : createHash_r6 ( input , inputPassword , useUserKey ) ;
} ;
// Try to authorize owner password
{
QByteArray inputData = password + ownerData . validationSalt + m_U ;
QByteArray hash = createHash_r56 ( inputData , password , true ) ;
if ( hash = = ownerData . hash )
{
// We have authorized owner access. Now we must calculate the owner encryption key
QByteArray fileEncryptionKeyInputData = password + ownerData . keySalt + m_U ;
QByteArray fileEncryptionDecryptionKey = createHash_r56 ( fileEncryptionKeyInputData , password , true ) ;
Q_ASSERT ( fileEncryptionDecryptionKey . size ( ) = = 32 ) ;
AES_KEY key = { } ;
AES_set_decrypt_key ( convertByteArrayToUcharPtr ( fileEncryptionDecryptionKey ) , fileEncryptionDecryptionKey . size ( ) * 8 , & key ) ;
unsigned char aesInitializationVector [ AES_BLOCK_SIZE ] = { } ;
m_authorizationData . fileEncryptionKey . resize ( m_OE . size ( ) ) ;
AES_cbc_encrypt ( convertByteArrayToUcharPtr ( m_OE ) , convertByteArrayToUcharPtr ( m_authorizationData . fileEncryptionKey ) , m_OE . size ( ) , & key , aesInitializationVector , AES_DECRYPT ) ;
m_authorizationData . authorizationResult = AuthorizationResult : : OwnerAuthorized ;
}
}
// Try to authorize user password
if ( ! m_authorizationData . isAuthorized ( ) & & ! authorizeOwnerOnly )
{
QByteArray inputData = password + userData . validationSalt ;
QByteArray hash = createHash_r56 ( inputData , password , false ) ;
if ( hash = = userData . hash )
{
QByteArray fileEncryptionKeyInputData = password + userData . keySalt ;
QByteArray fileEncryptionDecryptionKey = createHash_r56 ( fileEncryptionKeyInputData , password , false ) ;
Q_ASSERT ( fileEncryptionDecryptionKey . size ( ) = = 32 ) ;
AES_KEY key = { } ;
AES_set_decrypt_key ( convertByteArrayToUcharPtr ( fileEncryptionDecryptionKey ) , fileEncryptionDecryptionKey . size ( ) * 8 , & key ) ;
unsigned char aesInitializationVector [ AES_BLOCK_SIZE ] = { } ;
m_authorizationData . fileEncryptionKey . resize ( m_UE . size ( ) ) ;
AES_cbc_encrypt ( convertByteArrayToUcharPtr ( m_UE ) , convertByteArrayToUcharPtr ( m_authorizationData . fileEncryptionKey ) , m_UE . size ( ) , & key , aesInitializationVector , AES_DECRYPT ) ;
// We have authorized user access
m_authorizationData . authorizationResult = AuthorizationResult : : UserAuthorized ;
}
}
// Stop, if we authorized the document usage
if ( m_authorizationData . isAuthorized ( ) )
{
// According the PDF specification, we must also check, if flags are not manipulated.
Q_ASSERT ( m_Perms . size ( ) = = AES_BLOCK_SIZE ) ;
AES_KEY key = { } ;
AES_set_decrypt_key ( convertByteArrayToUcharPtr ( m_authorizationData . fileEncryptionKey ) , m_authorizationData . fileEncryptionKey . size ( ) * 8 , & key ) ;
QByteArray decodedPerms ( m_Perms . size ( ) , char ( 0 ) ) ;
AES_ecb_encrypt ( convertByteArrayToUcharPtr ( m_Perms ) , convertByteArrayToUcharPtr ( decodedPerms ) , & key , AES_DECRYPT ) ;
// 1) Checks, if bytes 9, 10, 11 are 'a', 'd', 'b'
if ( decodedPerms [ 9 ] ! = ' a ' | | decodedPerms [ 10 ] ! = ' d ' | | decodedPerms [ 11 ] ! = ' b ' )
{
throw PDFException ( PDFTranslationContext : : tr ( " Permissions entry in the Encryption dictionary is invalid. " ) ) ;
}
// 2) Verify, that bytes 0-3 are valid permissions entry
const uint32_t permissions = qFromLittleEndian ( * reinterpret_cast < const uint32_t * > ( decodedPerms . data ( ) ) ) ;
if ( permissions ! = m_permissions )
{
throw PDFException ( PDFTranslationContext : : tr ( " Security permissions are manipulated. Can't open the document. " ) ) ;
}
// 3) Verify, that byte 8 is 'T' or 'F' and is equal to EncryptMetadata entry
if ( decodedPerms [ 8 ] ! = ' T ' & & decodedPerms [ 8 ] ! = ' F ' )
{
throw PDFException ( PDFTranslationContext : : tr ( " Security permissions are manipulated. Can't open the document. " ) ) ;
}
if ( ( decodedPerms [ 8 ] = = ' T ' ) ! = m_encryptMetadata )
{
throw PDFException ( PDFTranslationContext : : tr ( " Security permissions are manipulated. Can't open the document. " ) ) ;
}
return m_authorizationData . authorizationResult ;
}
break ;
}
default :
return AuthorizationResult : : Failed ;
}
password = adjustPassword ( getPasswordCallback ( & passwordObtained ) , m_R ) ;
}
return AuthorizationResult : : Cancelled ;
}
std : : vector < uint8_t > PDFStandardSecurityHandler : : createV2_ObjectEncryptionKey ( PDFObjectReference reference , CryptFilter filter ) const
{
std : : vector < uint8_t > inputKeyData = convertByteArrayToVector ( m_authorizationData . fileEncryptionKey ) ;
uint32_t objectNumber = qToLittleEndian ( static_cast < uint32_t > ( reference . objectNumber ) ) ;
uint32_t generation = qToLittleEndian ( static_cast < uint32_t > ( reference . generation ) ) ;
inputKeyData . insert ( inputKeyData . cend ( ) , { uint8_t ( objectNumber & 0xFF ) , uint8_t ( ( objectNumber > > 8 ) & 0xFF ) , uint8_t ( ( objectNumber > > 16 ) & 0xFF ) , uint8_t ( generation & 0xFF ) , uint8_t ( ( generation > > 8 ) & 0xFF ) } ) ;
std : : vector < uint8_t > objectEncryptionKey ( MD5_DIGEST_LENGTH , uint8_t ( 0 ) ) ;
MD5 ( inputKeyData . data ( ) , inputKeyData . size ( ) , objectEncryptionKey . data ( ) ) ;
// Use up to (n + 5) bytes, maximally 16, from the digest as object encryption key
size_t objectEncryptionKeySize = qMin ( filter . keyLength + 5 , MD5_DIGEST_LENGTH ) ;
objectEncryptionKey . resize ( objectEncryptionKeySize ) ;
return objectEncryptionKey ;
}
std : : vector < uint8_t > PDFStandardSecurityHandler : : createAESV2_ObjectEncryptionKey ( PDFObjectReference reference ) const
{
std : : vector < uint8_t > inputKeyData = convertByteArrayToVector ( m_authorizationData . fileEncryptionKey ) ;
uint32_t objectNumber = qToLittleEndian ( static_cast < uint32_t > ( reference . objectNumber ) ) ;
uint32_t generation = qToLittleEndian ( static_cast < uint32_t > ( reference . generation ) ) ;
inputKeyData . insert ( inputKeyData . cend ( ) , { uint8_t ( objectNumber & 0xFF ) , uint8_t ( ( objectNumber > > 8 ) & 0xFF ) , uint8_t ( ( objectNumber > > 16 ) & 0xFF ) , uint8_t ( generation & 0xFF ) , uint8_t ( ( generation > > 8 ) & 0xFF ) , 0x73 , 0x41 , 0x6C , 0x54 } ) ;
std : : vector < uint8_t > objectEncryptionKey ( MD5_DIGEST_LENGTH , uint8_t ( 0 ) ) ;
MD5 ( inputKeyData . data ( ) , inputKeyData . size ( ) , objectEncryptionKey . data ( ) ) ;
return objectEncryptionKey ;
}
QByteArray PDFStandardSecurityHandler : : decryptUsingFilter ( const QByteArray & data , CryptFilter filter , PDFObjectReference reference ) const
{
QByteArray decryptedData ;
Q_ASSERT ( m_authorizationData . isAuthorized ( ) ) ;
struct AES_data
{
QByteArray initializationVector ;
QByteArray paddedData ;
} ;
auto prepareAES_data = [ ] ( const QByteArray & data )
{
AES_data result ;
result . initializationVector = data . left ( AES_BLOCK_SIZE ) ;
// This is an error. But to handle it, we resize the vector
// with arbitrary data.
if ( result . initializationVector . size ( ) < AES_BLOCK_SIZE )
{
result . initializationVector . resize ( AES_BLOCK_SIZE ) ;
}
result . paddedData = data . mid ( AES_BLOCK_SIZE ) ;
// Remove errorneous data - we must have a data of multiple of AES_BLOCK_SIZE
const int remainder = result . paddedData . size ( ) % AES_BLOCK_SIZE ;
if ( remainder ! = 0 )
{
result . paddedData = result . paddedData . left ( result . paddedData . size ( ) - remainder ) ;
}
return result ;
} ;
auto removeAES_padding = [ ] ( const QByteArray & data )
{
if ( data . isEmpty ( ) )
{
return data ;
}
// If padding doesnt fit from 1 to AES_BLOCK_SIZE, then it is
// an error, but just clamp the value.
const int padding = data . back ( ) ;
const int clampedPadding = qBound ( 1 , padding , AES_BLOCK_SIZE ) ;
return data . left ( data . size ( ) - clampedPadding ) ;
} ;
switch ( filter . type )
{
case CryptFilterType : : None : // The application shall decrypt the data using the security handler
{
// This shouldn't occur, because in case the used filter has None value, then default filter
// is used and default filter can't have this value.
Q_ASSERT ( false ) ;
break ;
}
case CryptFilterType : : V2 : // Use file encryption key for RC4 algorithm
{
std : : vector < uint8_t > objectEncryptionKey = createV2_ObjectEncryptionKey ( reference , filter ) ;
decryptedData . resize ( data . size ( ) ) ;
RC4_KEY key = { } ;
RC4_set_key ( & key , static_cast < int > ( objectEncryptionKey . size ( ) ) , objectEncryptionKey . data ( ) ) ;
RC4 ( & key , data . size ( ) , convertByteArrayToUcharPtr ( data ) , convertByteArrayToUcharPtr ( decryptedData ) ) ;
break ;
}
case CryptFilterType : : AESV2 : // Use file encryption key for AES algorithm
{
std : : vector < uint8_t > objectEncryptionKey = createAESV2_ObjectEncryptionKey ( reference ) ;
// For AES algorithm, always use 16 bytes key (128 bit encryption mode)
AES_KEY key = { } ;
AES_set_decrypt_key ( objectEncryptionKey . data ( ) , static_cast < int > ( objectEncryptionKey . size ( ) ) * 8 , & key ) ;
AES_data aes_data = prepareAES_data ( data ) ;
if ( ! aes_data . paddedData . isEmpty ( ) )
{
decryptedData . resize ( aes_data . paddedData . size ( ) ) ;
AES_cbc_encrypt ( convertByteArrayToUcharPtr ( aes_data . paddedData ) , convertByteArrayToUcharPtr ( decryptedData ) , aes_data . paddedData . length ( ) , & key , convertByteArrayToUcharPtr ( aes_data . initializationVector ) , AES_DECRYPT ) ;
decryptedData = removeAES_padding ( decryptedData ) ;
}
break ;
}
case CryptFilterType : : AESV3 : // Use file encryption key for AES 256 bit algorithm
{
Q_ASSERT ( m_authorizationData . fileEncryptionKey . size ( ) = = 32 ) ;
AES_KEY key = { } ;
AES_set_decrypt_key ( convertByteArrayToUcharPtr ( m_authorizationData . fileEncryptionKey ) , static_cast < int > ( m_authorizationData . fileEncryptionKey . size ( ) ) * 8 , & key ) ;
AES_data aes_data = prepareAES_data ( data ) ;
if ( ! aes_data . paddedData . isEmpty ( ) )
{
decryptedData . resize ( aes_data . paddedData . size ( ) ) ;
AES_cbc_encrypt ( convertByteArrayToUcharPtr ( aes_data . paddedData ) , convertByteArrayToUcharPtr ( decryptedData ) , aes_data . paddedData . length ( ) , & key , convertByteArrayToUcharPtr ( aes_data . initializationVector ) , AES_DECRYPT ) ;
decryptedData = removeAES_padding ( decryptedData ) ;
}
break ;
}
case CryptFilterType : : Identity : // Don't decrypt anything, use identity function
{
decryptedData = data ;
break ;
}
}
return decryptedData ;
}
QByteArray PDFStandardSecurityHandler : : encryptUsingFilter ( const QByteArray & data , CryptFilter filter , PDFObjectReference reference ) const
{
QByteArray encryptedData ;
Q_ASSERT ( m_authorizationData . isAuthorized ( ) ) ;
struct AES_data
{
QByteArray initializationVector ;
QByteArray paddedData ;
} ;
auto prepareAES_data = [ ] ( const QByteArray & data )
{
AES_data result ;
QRandomGenerator randomNumberGenerator = QRandomGenerator : : securelySeeded ( ) ;
result . initializationVector . resize ( AES_BLOCK_SIZE ) ;
for ( int i = 0 ; i < AES_BLOCK_SIZE ; + + i )
{
result . initializationVector [ i ] = uint8_t ( randomNumberGenerator . generate ( ) ) ;
}
result . paddedData = data ;
// Add padding remainder according to the specification
int size = data . size ( ) ;
const int paddingRemainder = AES_BLOCK_SIZE - ( size % AES_BLOCK_SIZE ) ;
result . initializationVector . reserve ( result . initializationVector . size ( ) + paddingRemainder ) ;
for ( int i = 0 ; i < paddingRemainder ; + + i )
{
result . paddedData . push_back ( paddingRemainder ) ;
}
return result ;
} ;
switch ( filter . type )
{
case CryptFilterType : : None : // The application shall encrypt the data using the security handler
{
// This shouldn't occur, because in case the used filter has None value, then default filter
// is used and default filter can't have this value.
Q_ASSERT ( false ) ;
break ;
}
case CryptFilterType : : V2 : // Use file encryption key for RC4 algorithm
{
// This algorithm is same as the encrypt algorithm, because RC4 cipher is symmetrical
std : : vector < uint8_t > objectEncryptionKey = createV2_ObjectEncryptionKey ( reference , filter ) ;
encryptedData . resize ( data . size ( ) ) ;
RC4_KEY key = { } ;
RC4_set_key ( & key , static_cast < int > ( objectEncryptionKey . size ( ) ) , objectEncryptionKey . data ( ) ) ;
RC4 ( & key , data . size ( ) , convertByteArrayToUcharPtr ( data ) , convertByteArrayToUcharPtr ( encryptedData ) ) ;
break ;
}
case CryptFilterType : : AESV2 : // Use file encryption key for AES algorithm
{
std : : vector < uint8_t > objectEncryptionKey = createAESV2_ObjectEncryptionKey ( reference ) ;
// For AES algorithm, always use 16 bytes key (128 bit encryption mode)
AES_KEY key = { } ;
AES_set_encrypt_key ( objectEncryptionKey . data ( ) , static_cast < int > ( objectEncryptionKey . size ( ) ) * 8 , & key ) ;
AES_data aes_data = prepareAES_data ( data ) ;
if ( ! aes_data . paddedData . isEmpty ( ) )
{
QByteArray initializationVectorCopy = aes_data . initializationVector ;
encryptedData . resize ( aes_data . paddedData . size ( ) ) ;
AES_cbc_encrypt ( convertByteArrayToUcharPtr ( aes_data . paddedData ) , convertByteArrayToUcharPtr ( encryptedData ) , aes_data . paddedData . length ( ) , & key , convertByteArrayToUcharPtr ( aes_data . initializationVector ) , AES_ENCRYPT ) ;
encryptedData . prepend ( initializationVectorCopy ) ;
}
break ;
}
case CryptFilterType : : AESV3 : // Use file encryption key for AES 256 bit algorithm
{
Q_ASSERT ( m_authorizationData . fileEncryptionKey . size ( ) = = 32 ) ;
AES_KEY key = { } ;
AES_set_encrypt_key ( convertByteArrayToUcharPtr ( m_authorizationData . fileEncryptionKey ) , static_cast < int > ( m_authorizationData . fileEncryptionKey . size ( ) ) * 8 , & key ) ;
AES_data aes_data = prepareAES_data ( data ) ;
if ( ! aes_data . paddedData . isEmpty ( ) )
{
QByteArray initializationVectorCopy = aes_data . initializationVector ;
encryptedData . resize ( aes_data . paddedData . size ( ) ) ;
AES_cbc_encrypt ( convertByteArrayToUcharPtr ( aes_data . paddedData ) , convertByteArrayToUcharPtr ( encryptedData ) , aes_data . paddedData . length ( ) , & key , convertByteArrayToUcharPtr ( aes_data . initializationVector ) , AES_ENCRYPT ) ;
encryptedData . prepend ( initializationVectorCopy ) ;
}
break ;
}
case CryptFilterType : : Identity : // Don't decrypt anything, use identity function
{
encryptedData = data ;
break ;
}
}
return encryptedData ;
}
QByteArray PDFStandardSecurityHandler : : decrypt ( const QByteArray & data , PDFObjectReference reference , EncryptionScope encryptionScope ) const
{
CryptFilter filter = getCryptFilter ( encryptionScope ) ;
return decryptUsingFilter ( data , filter , reference ) ;
}
QByteArray PDFStandardSecurityHandler : : decryptByFilter ( const QByteArray & data , const QByteArray & filterName , PDFObjectReference reference ) const
{
auto it = m_cryptFilters . find ( filterName ) ;
if ( it = = m_cryptFilters . cend ( ) )
{
throw PDFException ( PDFTranslationContext : : tr ( " Crypt filter '%1' not found. " ) . arg ( QString : : fromLatin1 ( filterName ) ) ) ;
}
return decryptUsingFilter ( data , it - > second , reference ) ;
}
CryptFilter PDFStandardSecurityHandler : : getCryptFilter ( EncryptionScope encryptionScope ) const
{
CryptFilter filter = m_filterDefault ;
switch ( encryptionScope )
{
case EncryptionScope : : String :
{
if ( m_filterStrings . type ! = CryptFilterType : : None )
{
filter = m_filterStrings ;
}
break ;
}
case EncryptionScope : : Stream :
{
if ( m_filterStreams . type ! = CryptFilterType : : None )
{
filter = m_filterStreams ;
}
break ;
}
case EncryptionScope : : EmbeddedFile :
{
if ( m_filterEmbeddedFiles . type ! = CryptFilterType : : None )
{
filter = m_filterEmbeddedFiles ;
}
break ;
}
}
return filter ;
}
QByteArray PDFStandardSecurityHandler : : encrypt ( const QByteArray & data , PDFObjectReference reference , EncryptionScope encryptionScope ) const
{
CryptFilter filter = getCryptFilter ( encryptionScope ) ;
return encryptUsingFilter ( data , filter , reference ) ;
}
QByteArray PDFStandardSecurityHandler : : encryptByFilter ( const QByteArray & data , const QByteArray & filterName , PDFObjectReference reference ) const
{
auto it = m_cryptFilters . find ( filterName ) ;
if ( it = = m_cryptFilters . cend ( ) )
{
throw PDFException ( PDFTranslationContext : : tr ( " Crypt filter '%1' not found. " ) . arg ( QString : : fromLatin1 ( filterName ) ) ) ;
}
return encryptUsingFilter ( data , it - > second , reference ) ;
}
PDFObject PDFStandardSecurityHandler : : createEncryptionDictionaryObject ( ) const
{
PDFObjectFactory factory ;
factory . beginDictionary ( ) ;
fillEncryptionDictionary ( factory ) ;
factory . beginDictionaryItem ( " Filter " ) ;
factory < < WrapName ( " Standard " ) ;
factory . endDictionaryItem ( ) ;
factory . beginDictionaryItem ( " R " ) ;
factory < < PDFInteger ( m_R ) ;
factory . endDictionaryItem ( ) ;
factory . beginDictionaryItem ( " O " ) ;
factory < < WrapString ( m_O ) ;
factory . endDictionaryItem ( ) ;
factory . beginDictionaryItem ( " U " ) ;
factory < < WrapString ( m_U ) ;
factory . endDictionaryItem ( ) ;
if ( m_R = = 6 )
{
factory . beginDictionaryItem ( " OE " ) ;
factory < < WrapString ( m_OE ) ;
factory . endDictionaryItem ( ) ;
factory . beginDictionaryItem ( " UE " ) ;
factory < < WrapString ( m_UE ) ;
factory . endDictionaryItem ( ) ;
}
factory . beginDictionaryItem ( " P " ) ;
factory < < PDFInteger ( int32_t ( m_permissions ) ) ;
factory . endDictionaryItem ( ) ;
if ( m_R = = 6 )
{
factory . beginDictionaryItem ( " Perms " ) ;
factory < < WrapString ( m_Perms ) ;
factory . endDictionaryItem ( ) ;
}
if ( m_V = = 4 | | m_V = = 5 )
{
factory . beginDictionaryItem ( " EncryptMetadata " ) ;
factory < < m_encryptMetadata ;
factory . endDictionaryItem ( ) ;
}
factory . endDictionary ( ) ;
return factory . takeObject ( ) ;
}
QByteArray PDFStandardSecurityHandler : : createFileEncryptionKey ( const QByteArray & password ) const
{
QByteArray result ;
switch ( m_R )
{
case 2 :
case 3 :
case 4 :
{
std : : array < uint8_t , 32 > paddedPassword = createPaddedPassword32 ( password ) ;
uint32_t transformedPermissions = qToLittleEndian ( m_permissions ) ;
MD5_CTX context = { } ;
MD5_Init ( & context ) ;
MD5_Update ( & context , paddedPassword . data ( ) , paddedPassword . size ( ) ) ;
MD5_Update ( & context , m_O . constData ( ) , m_O . size ( ) ) ;
MD5_Update ( & context , & transformedPermissions , sizeof ( transformedPermissions ) ) ;
MD5_Update ( & context , m_ID . constData ( ) , m_ID . size ( ) ) ;
if ( ! m_encryptMetadata )
{
constexpr uint32_t value = 0xFFFFFFFF ;
MD5_Update ( & context , & value , sizeof ( value ) ) ;
}
std : : array < uint8_t , MD5_DIGEST_LENGTH > fileEncryptionKey = { } ;
MD5_Final ( fileEncryptionKey . data ( ) , & context ) ;
const int keyByteLength = m_keyLength / 8 ;
if ( keyByteLength > MD5_DIGEST_LENGTH )
{
throw PDFException ( PDFTranslationContext : : tr ( " Encryption key length (%1) exceeded maximal value of % 2. " ).arg(keyByteLength).arg(MD5_DIGEST_LENGTH)) ;
}
if ( m_R > = 3 )
{
for ( int i = 0 ; i < 50 ; + + i )
{
MD5_Init ( & context ) ;
MD5_Update ( & context , fileEncryptionKey . data ( ) , keyByteLength ) ;
MD5_Final ( fileEncryptionKey . data ( ) , & context ) ;
}
}
result . resize ( keyByteLength ) ;
std : : copy_n ( fileEncryptionKey . cbegin ( ) , keyByteLength , result . begin ( ) ) ;
break ;
}
case 5 :
case 6 :
{
// This function must not be called with revision 5/6
Q_ASSERT ( false ) ;
break ;
}
default :
{
throw PDFException ( PDFTranslationContext : : tr ( " Revision %1 of standard security handler is not supported. " ) . arg ( m_R ) ) ;
}
}
return result ;
}
QByteArray PDFStandardSecurityHandler : : createEntryValueU_r234 ( const QByteArray & fileEncryptionKey ) const
{
QByteArray result ;
switch ( m_R )
{
case 2 :
{
RC4_KEY key = { } ;
RC4_set_key ( & key , fileEncryptionKey . size ( ) , convertByteArrayToUcharPtr ( fileEncryptionKey ) ) ;
result . resize ( static_cast < int > ( PDFPasswordPadding . size ( ) ) ) ;
RC4 ( & key , PDFPasswordPadding . size ( ) , PDFPasswordPadding . data ( ) , convertByteArrayToUcharPtr ( result ) ) ;
break ;
}
case 3 :
case 4 :
{
std : : array < uint8_t , MD5_DIGEST_LENGTH > hash = { } ;
MD5_CTX context = { } ;
MD5_Init ( & context ) ;
MD5_Update ( & context , PDFPasswordPadding . data ( ) , PDFPasswordPadding . size ( ) ) ;
MD5_Update ( & context , m_ID . data ( ) , m_ID . size ( ) ) ;
MD5_Final ( hash . data ( ) , & context ) ;
RC4_KEY key = { } ;
RC4_set_key ( & key , fileEncryptionKey . size ( ) , convertByteArrayToUcharPtr ( fileEncryptionKey ) ) ;
std : : array < uint8_t , MD5_DIGEST_LENGTH > encryptedHash = { } ;
std : : array < uint8_t , MD5_DIGEST_LENGTH > targetBuffer = { } ;
RC4 ( & key , hash . size ( ) , hash . data ( ) , encryptedHash . data ( ) ) ;
QByteArray transformedKey = fileEncryptionKey ;
for ( int i = 1 ; i < = 19 ; + + i )
{
for ( int j = 0 , keySize = fileEncryptionKey . size ( ) ; j < keySize ; + + j )
{
transformedKey [ j ] = static_cast < uint8_t > ( fileEncryptionKey [ j ] ) ^ static_cast < uint8_t > ( i ) ;
}
RC4_set_key ( & key , transformedKey . size ( ) , convertByteArrayToUcharPtr ( transformedKey ) ) ;
RC4 ( & key , encryptedHash . size ( ) , encryptedHash . data ( ) , targetBuffer . data ( ) ) ;
encryptedHash = targetBuffer ;
}
// We do a hack here. In the PDF's specification, it is written, that arbitrary 16 bytes
// are appended to the 16 bytes result. We use the last 16 bytes of the U entry, because we
// want to compare byte arrays entirely (otherwise we must compare only 16 bytes to authenticate
// user password).
result = m_U ;
result . detach ( ) ;
if ( result . size ( ) ! = 32 )
{
// In case of error, we resize it to correct size. We can't assume, that m_U has correct length.
result . resize ( 32 ) ;
}
std : : copy_n ( encryptedHash . begin ( ) , encryptedHash . size ( ) , result . begin ( ) ) ;
break ;
}
default :
{
throw PDFException ( PDFTranslationContext : : tr ( " Revision %1 of standard security handler is not supported. " ) . arg ( m_R ) ) ;
}
}
return result ;
}
QByteArray PDFStandardSecurityHandler : : createUserPasswordFromOwnerPassword ( const QByteArray & password ) const
{
QByteArray result ;
std : : array < uint8_t , 32 > paddedPassword = createPaddedPassword32 ( password ) ;
std : : array < uint8_t , MD5_DIGEST_LENGTH > hash ;
MD5_CTX context = { } ;
MD5_Init ( & context ) ;
MD5_Update ( & context , paddedPassword . data ( ) , paddedPassword . size ( ) ) ;
MD5_Final ( hash . data ( ) , & context ) ;
const int keyByteLength = m_keyLength / 8 ;
if ( keyByteLength > MD5_DIGEST_LENGTH )
{
throw PDFException ( PDFTranslationContext : : tr ( " Encryption key length (%1) exceeded maximal value of % 2. " ).arg(keyByteLength).arg(MD5_DIGEST_LENGTH)) ;
}
if ( m_R > = 3 )
{
for ( int i = 0 ; i < 50 ; + + i )
{
MD5_Init ( & context ) ;
MD5_Update ( & context , hash . data ( ) , keyByteLength ) ;
MD5_Final ( hash . data ( ) , & context ) ;
}
}
switch ( m_R )
{
case 2 :
{
RC4_KEY key = { } ;
RC4_set_key ( & key , keyByteLength , hash . data ( ) ) ;
result . resize ( m_O . size ( ) ) ;
RC4 ( & key , m_O . size ( ) , convertByteArrayToUcharPtr ( m_O ) , convertByteArrayToUcharPtr ( result ) ) ;
break ;
}
case 3 :
case 4 :
{
QByteArray buffer = m_O ;
QByteArray transformedKey ;
transformedKey . resize ( keyByteLength ) ;
std : : copy_n ( hash . data ( ) , keyByteLength , transformedKey . data ( ) ) ;
for ( int i = 19 ; i > = 0 ; - - i )
{
for ( int j = 0 , keySize = transformedKey . size ( ) ; j < keySize ; + + j )
{
transformedKey [ j ] = static_cast < uint8_t > ( hash [ j ] ) ^ static_cast < uint8_t > ( i ) ;
}
RC4_KEY key = { } ;
RC4_set_key ( & key , transformedKey . size ( ) , convertByteArrayToUcharPtr ( transformedKey ) ) ;
RC4 ( & key , buffer . size ( ) , convertByteArrayToUcharPtr ( buffer ) , convertByteArrayToUcharPtr ( buffer ) ) ;
}
result = buffer ;
break ;
}
default :
{
throw PDFException ( PDFTranslationContext : : tr ( " Revision %1 of standard security handler is not supported. " ) . arg ( m_R ) ) ;
}
}
return result ;
}
std : : array < uint8_t , 32 > PDFStandardSecurityHandler : : createPaddedPassword32 ( const QByteArray & password ) const
{
std : : array < uint8_t , 32 > result = { } ;
int copiedBytes = qMin < int > ( static_cast < int > ( result . size ( ) ) , password . size ( ) ) ;
auto it = result . begin ( ) ;
for ( int i = 0 ; i < copiedBytes ; + + i )
{
* it + + = static_cast < uint8_t > ( password [ i ] ) ;
}
auto itPadding = PDFPasswordPadding . cbegin ( ) ;
for ( ; it ! = result . cend ( ) ; )
{
Q_ASSERT ( itPadding ! = PDFPasswordPadding . cend ( ) ) ;
* it + + = * itPadding + + ;
}
return result ;
}
QByteArray PDFStandardSecurityHandler : : createHash_r6 ( const QByteArray & input , const QByteArray & inputPassword , bool useUserKey ) const
{
QByteArray result ;
// First compute sha-256 digest of the input
std : : array < uint8_t , SHA256_DIGEST_LENGTH > inputDigest = { } ;
SHA256 ( convertByteArrayToUcharPtr ( input ) , input . size ( ) , inputDigest . data ( ) ) ;
std : : vector < uint8_t > K ( inputDigest . cbegin ( ) , inputDigest . cend ( ) ) ;
// Fill the user key, if we use it
std : : vector < uint8_t > userKey ;
if ( useUserKey )
{
userKey . resize ( m_U . size ( ) ) ;
std : : copy_n ( m_U . constData ( ) , m_U . size ( ) , userKey . begin ( ) ) ;
}
const size_t userKeySize = userKey . size ( ) ;
// Fill the input password
std : : vector < uint8_t > password ( inputPassword . constData ( ) , inputPassword . constData ( ) + inputPassword . size ( ) ) ;
const size_t passwordSize = password . size ( ) ;
std : : vector < uint8_t > K1 ;
std : : vector < uint8_t > E ;
int round = 0 ;
while ( round < 64 | | round < E . back ( ) + 32 )
{
const size_t blockCount = 64 ;
const size_t KSize = K . size ( ) ;
const size_t sequenceSize = passwordSize + KSize + userKeySize ;
const size_t totalSize = blockCount * sequenceSize ;
// Resize the arrays
K1 . resize ( totalSize ) ;
E . resize ( totalSize ) ;
// a) fill the input array K1 with data
auto it = K1 . begin ( ) ;
for ( size_t i = 0 ; i < blockCount ; + + i )
{
std : : copy_n ( password . cbegin ( ) , passwordSize , it ) ;
std : : advance ( it , passwordSize ) ;
std : : copy_n ( K . cbegin ( ) , KSize , it ) ;
std : : advance ( it , KSize ) ;
std : : copy_n ( userKey . cbegin ( ) , userKeySize , it ) ;
std : : advance ( it , userKeySize ) ;
}
Q_ASSERT ( it = = K1 . cend ( ) ) ;
Q_ASSERT ( K . size ( ) > = 32 ) ;
// b) encrypt K1 with AES-128 in CBC mode, first 16 bytes of K is key,
// second 16 bytes in K is initialization vector for AES algorithm.
AES_KEY key = { } ;
AES_set_encrypt_key ( K . data ( ) , 128 , & key ) ;
AES_cbc_encrypt ( K1 . data ( ) , E . data ( ) , K1 . size ( ) , & key , K . data ( ) + 16 , AES_ENCRYPT ) ;
// c) we take first 16 bytes from E as unsigned 128 bit big-endian integer and compute
// remainder modulo 3. Then we decide which SHA function we will use.
// We can't directly modulo 128 bit unsigned number, because we do not have 128 bit arithmetic (yet).
// We will use following trick from https://math.stackexchange.com/questions/2727954/bit-representation-and-divisibility-by-3
//
// 2^n mod 3 = 2 for n = 1, 3, 5, 7, 9, ...
// 2^n mod 3 = 1 for n = 0, 2, 4, 6, 8, ...
//
// Also, it doesn't matter the endianity of the numbers, becase for example, when we change endianity of 16 bit
// numbers, then bits 0-7 became 8-15, so even/odd bits become also even/odd.
int remainderAccumulator = 0 ;
for ( size_t i = 0 ; i < 16 ; + + i )
{
uint8_t byte = E [ i ] ;
int currentRemainder = 1 ;
for ( uint8_t i = 0 ; i < 8 ; + + i )
{
if ( ( byte > > i ) & 1 )
{
remainderAccumulator + = currentRemainder ;
}
// We alternate the remainder 1, 2, 1, 2, 1, 2, ...
currentRemainder = 3 - currentRemainder ;
}
}
remainderAccumulator = remainderAccumulator % 3 ;
// d) according to the remainder, decide, which function we will use
switch ( remainderAccumulator )
{
case 0 :
{
K . resize ( SHA256_DIGEST_LENGTH ) ;
SHA256 ( E . data ( ) , E . size ( ) , K . data ( ) ) ;
break ;
}
case 1 :
{
K . resize ( SHA384_DIGEST_LENGTH ) ;
SHA384 ( E . data ( ) , E . size ( ) , K . data ( ) ) ;
break ;
}
case 2 :
{
K . resize ( SHA512_DIGEST_LENGTH ) ;
SHA512 ( E . data ( ) , E . size ( ) , K . data ( ) ) ;
break ;
}
default :
{
// Invalid value, can't occur
Q_ASSERT ( false ) ;
break ;
}
}
+ + round ;
}
Q_ASSERT ( K . size ( ) > = 32 ) ;
// Clamp result to 32 bytes
result . resize ( 32 ) ;
std : : copy_n ( K . data ( ) , 32 , result . data ( ) ) ;
return result ;
}
PDFStandardSecurityHandler : : UserOwnerData_r6 PDFStandardSecurityHandler : : parseParts ( const QByteArray & data ) const
{
UserOwnerData_r6 result ;
Q_ASSERT ( data . size ( ) = = 48 ) ;
result . hash = data . left ( 32 ) ;
result . validationSalt = data . mid ( 32 , 8 ) ;
result . keySalt = data . mid ( 40 , 8 ) ;
return result ;
}
QByteArray PDFStandardSecurityHandler : : adjustPassword ( const QString & password , int revision )
{
QByteArray result ;
switch ( revision )
{
case 2 :
case 3 :
case 4 :
{
// According to the PDF specification, convert string to PDFDocEncoding encoding
result = PDFEncoding : : convertToEncoding ( password , PDFEncoding : : Encoding : : PDFDoc ) ;
break ;
}
case 5 :
case 6 :
{
// According to the PDF specification, use SASLprep profile for stringprep RFC 4013, please see these websites:
// - RFC 4013: https://tools.ietf.org/html/rfc4013 (SASLprep profile for stringprep algorithm)
// - RFC 3454: https://tools.ietf.org/html/rfc3454 (stringprep algorithm - preparation of internationalized strings)
//
// Note: we don't do checks according the RFC 4013, just use the mapping and normalize string in KC
QString preparedPassword ;
preparedPassword . reserve ( password . size ( ) ) ;
// RFC 4013 Section 2.1, use mapping
for ( const QChar character : password )
{
if ( isUnicodeMappedToNothing ( character . unicode ( ) ) )
{
// Mapped to nothing
continue ;
}
if ( isUnicodeNonAsciiSpaceCharacter ( character . unicode ( ) ) )
{
// Map to space character
preparedPassword + = QChar ( QChar : : Space ) ;
}
else
{
preparedPassword + = character ;
}
}
// RFC 4013, Section 2.2, normalization to KC
preparedPassword = preparedPassword . normalized ( QString : : NormalizationForm_KC ) ;
// We don't do other checks. We will transform password to the UTF-8 encoding
// and according the PDF specification, we take only first 127 characters.
result = preparedPassword . toUtf8 ( ) . left ( 127 ) ;
}
default :
break ;
}
return result ;
}
bool PDFStandardSecurityHandler : : isUnicodeNonAsciiSpaceCharacter ( ushort unicode )
{
switch ( unicode )
{
case 0x00A0 :
case 0x1680 :
case 0x2000 :
case 0x2001 :
case 0x2002 :
case 0x2003 :
case 0x2004 :
case 0x2005 :
case 0x2006 :
case 0x2007 :
case 0x2008 :
case 0x2009 :
case 0x200A :
case 0x200B :
case 0x202F :
case 0x205F :
case 0x3000 :
return true ;
default :
return false ;
}
}
bool PDFStandardSecurityHandler : : isUnicodeMappedToNothing ( ushort unicode )
{
switch ( unicode )
{
case 0x00AD :
case 0x034F :
case 0x1806 :
case 0x180B :
case 0x180C :
case 0x180D :
case 0x200B :
case 0x200C :
case 0x200D :
return true ;
default :
return false ;
}
}
PDFSecurityHandlerPointer PDFSecurityHandlerFactory : : createSecurityHandler ( const SecuritySettings & settings )
{
if ( settings . algorithm = = Algorithm : : None )
{
return PDFSecurityHandlerPointer ( new PDFNoneSecurityHandler ) ;
}
// Jakub Melka: create standard security handler, with given settings
PDFStandardSecurityHandler * handler = new PDFStandardSecurityHandler ( ) ;
handler - > m_ID = settings . id ;
const bool isEncryptingEmbeddedFilesOnly = settings . encryptContents = = EncryptContents : : EmbeddedFiles ;
switch ( settings . algorithm )
{
case RC4 :
{
handler - > m_V = 4 ;
handler - > m_keyLength = 128 ;
CryptFilter defaultFilter ;
defaultFilter . type = CryptFilterType : : V2 ;
defaultFilter . authEvent = ! isEncryptingEmbeddedFilesOnly ? AuthEvent : : DocOpen : AuthEvent : : EFOpen ;
defaultFilter . keyLength = handler - > m_keyLength / 8 ;
handler - > m_filterDefault = defaultFilter ;
break ;
}
case AES_128 :
{
handler - > m_V = 4 ;
handler - > m_keyLength = 128 ;
CryptFilter defaultFilter ;
defaultFilter . type = CryptFilterType : : AESV2 ;
defaultFilter . authEvent = ! isEncryptingEmbeddedFilesOnly ? AuthEvent : : DocOpen : AuthEvent : : EFOpen ;
defaultFilter . keyLength = handler - > m_keyLength / 8 ;
handler - > m_filterDefault = defaultFilter ;
break ;
}
case AES_256 :
{
handler - > m_V = 5 ;
handler - > m_keyLength = 256 ;
CryptFilter defaultFilter ;
defaultFilter . type = CryptFilterType : : AESV3 ;
defaultFilter . authEvent = ! isEncryptingEmbeddedFilesOnly ? AuthEvent : : DocOpen : AuthEvent : : EFOpen ;
defaultFilter . keyLength = handler - > m_keyLength / 8 ;
handler - > m_filterDefault = defaultFilter ;
break ;
}
default :
Q_ASSERT ( false ) ;
break ;
}
CryptFilter identityFilter ;
identityFilter . type = CryptFilterType : : Identity ;
switch ( settings . encryptContents )
{
case All :
handler - > m_filterStrings = handler - > m_filterDefault ;
handler - > m_filterStreams = handler - > m_filterDefault ;
handler - > m_filterEmbeddedFiles = handler - > m_filterDefault ;
handler - > m_encryptMetadata = true ;
break ;
case AllExceptMetadata :
handler - > m_filterStrings = handler - > m_filterDefault ;
handler - > m_filterStreams = handler - > m_filterDefault ;
handler - > m_filterEmbeddedFiles = handler - > m_filterDefault ;
handler - > m_encryptMetadata = false ;
break ;
case EmbeddedFiles :
handler - > m_filterStrings = identityFilter ;
handler - > m_filterStreams = identityFilter ;
handler - > m_filterEmbeddedFiles = handler - > m_filterDefault ;
handler - > m_encryptMetadata = false ;
break ;
default :
Q_ASSERT ( false ) ;
break ;
}
handler - > m_cryptFilters [ " StdCF " ] = handler - > m_filterDefault ;
handler - > m_R = getRevisionFromAlgorithm ( settings . algorithm ) ;
handler - > m_permissions = settings . permissions | 0xFFFFF000 ;
QByteArray adjustedOwnerPassword = handler - > adjustPassword ( settings . ownerPassword , handler - > m_R ) ;
QByteArray adjustedUserPassword = handler - > adjustPassword ( settings . userPassword , handler - > m_R ) ;
// Generate encryption entries
switch ( handler - > m_R )
{
case 2 :
case 3 :
case 4 :
{
// Trick for computing "O" entry for revisions 2,3,4: in O entry, there is stored
// user password encrypted by owner password. Because RC4 cipher is symmetric, we
// can store user password in "O" entry and then use standard function to retrieve
// user password, which in fact will be encrypted user password.
std : : array < uint8_t , 32 > paddedUserPasswordArray = handler - > createPaddedPassword32 ( adjustedUserPassword ) ;
QByteArray paddedUserPassword ;
paddedUserPassword . resize ( int ( paddedUserPasswordArray . size ( ) ) ) ;
std : : copy ( paddedUserPasswordArray . cbegin ( ) , paddedUserPasswordArray . cend ( ) , paddedUserPassword . data ( ) ) ;
handler - > m_O = paddedUserPassword ;
QByteArray entryO = handler - > createUserPasswordFromOwnerPassword ( adjustedOwnerPassword ) ;
handler - > m_O = entryO ;
Q_ASSERT ( handler - > createUserPasswordFromOwnerPassword ( adjustedOwnerPassword ) = = paddedUserPassword ) ;
handler - > m_U . resize ( 32 ) ;
QRandomGenerator randomNumberGenerator = QRandomGenerator : : securelySeeded ( ) ;
for ( int i = 0 ; i < handler - > m_U . size ( ) ; + + i )
{
handler - > m_U [ i ] = char ( randomNumberGenerator . generate ( ) ) ;
}
QByteArray fileEncryptionKey = handler - > createFileEncryptionKey ( paddedUserPassword ) ;
QByteArray U = handler - > createEntryValueU_r234 ( fileEncryptionKey ) ;
handler - > m_U = U ;
break ;
}
case 6 :
{
PDFStandardSecurityHandler : : UserOwnerData_r6 userData ;
PDFStandardSecurityHandler : : UserOwnerData_r6 ownerData ;
QRandomGenerator randomNumberGenerator = QRandomGenerator : : securelySeeded ( ) ;
// Generate file encryption key
handler - > m_authorizationData . fileEncryptionKey = generateRandomByteArray ( randomNumberGenerator , 32 ) ;
handler - > m_authorizationData . authorizationResult = PDFSecurityHandler : : AuthorizationResult : : OwnerAuthorized ;
// Compute m_U entry
userData . keySalt = generateRandomByteArray ( randomNumberGenerator , 8 ) ;
userData . validationSalt = generateRandomByteArray ( randomNumberGenerator , 8 ) ;
userData . hash = handler - > createHash_r6 ( adjustedUserPassword + userData . validationSalt , adjustedUserPassword , false ) ;
handler - > m_U = userData . hash + userData . validationSalt + userData . keySalt ;
// Compute m_UE entry
QByteArray userFileEncryptionKeyInputData = adjustedUserPassword + userData . keySalt ;
QByteArray userFileEncryptionKey = handler - > createHash_r6 ( userFileEncryptionKeyInputData , adjustedUserPassword , false ) ;
Q_ASSERT ( userFileEncryptionKey . size ( ) = = 32 ) ;
AES_KEY userKey = { } ;
AES_set_encrypt_key ( convertByteArrayToUcharPtr ( userFileEncryptionKey ) , userFileEncryptionKey . size ( ) * 8 , & userKey ) ;
unsigned char aesUserInitializationVector [ AES_BLOCK_SIZE ] = { } ;
handler - > m_UE . resize ( handler - > m_authorizationData . fileEncryptionKey . size ( ) ) ;
unsigned char * userInputBuffer = convertByteArrayToUcharPtr ( handler - > m_authorizationData . fileEncryptionKey ) ;
unsigned char * userTargetBuffer = convertByteArrayToUcharPtr ( handler - > m_UE ) ;
AES_cbc_encrypt ( userInputBuffer , userTargetBuffer , handler - > m_UE . size ( ) , & userKey , aesUserInitializationVector , AES_ENCRYPT ) ;
// Compute m_O entry
ownerData . keySalt = generateRandomByteArray ( randomNumberGenerator , 8 ) ;
ownerData . validationSalt = generateRandomByteArray ( randomNumberGenerator , 8 ) ;
ownerData . hash = handler - > createHash_r6 ( adjustedOwnerPassword + ownerData . validationSalt + handler - > m_U , adjustedOwnerPassword , true ) ;
handler - > m_O = ownerData . hash + ownerData . validationSalt + ownerData . keySalt ;
// Compute m_OE entry
QByteArray ownerFileEncryptionKeyInputData = adjustedOwnerPassword + ownerData . keySalt + handler - > m_U ;
QByteArray ownerFileEncryptionKey = handler - > createHash_r6 ( ownerFileEncryptionKeyInputData , adjustedOwnerPassword , true ) ;
AES_KEY ownerKey = { } ;
AES_set_encrypt_key ( convertByteArrayToUcharPtr ( ownerFileEncryptionKey ) , ownerFileEncryptionKey . size ( ) * 8 , & ownerKey ) ;
unsigned char aesOwnerInitializationVector [ AES_BLOCK_SIZE ] = { } ;
handler - > m_OE . resize ( handler - > m_authorizationData . fileEncryptionKey . size ( ) ) ;
unsigned char * ownerInputBuffer = convertByteArrayToUcharPtr ( handler - > m_authorizationData . fileEncryptionKey ) ;
unsigned char * ownerTargetBuffer = convertByteArrayToUcharPtr ( handler - > m_OE ) ;
AES_cbc_encrypt ( ownerInputBuffer , ownerTargetBuffer , handler - > m_OE . size ( ) , & ownerKey , aesOwnerInitializationVector , AES_ENCRYPT ) ;
// Perms entry
handler - > m_Perms = QByteArray ( AES_BLOCK_SIZE , char ( 0 ) ) ;
unsigned char * permsData = convertByteArrayToUcharPtr ( handler - > m_Perms ) ;
permsData [ 0 ] = handler - > m_permissions & 0xFF ;
permsData [ 1 ] = ( handler - > m_permissions > > 8 ) & 0xFF ;
permsData [ 2 ] = ( handler - > m_permissions > > 16 ) & 0xFF ;
permsData [ 3 ] = ( handler - > m_permissions > > 24 ) & 0xFF ;
permsData [ 4 ] = 0xFF ;
permsData [ 5 ] = 0xFF ;
permsData [ 6 ] = 0xFF ;
permsData [ 7 ] = 0xFF ;
permsData [ 8 ] = handler - > m_encryptMetadata ? ' T ' : ' F ' ;
permsData [ 9 ] = ' a ' ;
permsData [ 10 ] = ' d ' ;
permsData [ 11 ] = ' b ' ;
permsData [ 12 ] = randomNumberGenerator . generate ( ) & 0xFF ;
permsData [ 13 ] = randomNumberGenerator . generate ( ) & 0xFF ;
permsData [ 14 ] = randomNumberGenerator . generate ( ) & 0xFF ;
permsData [ 15 ] = randomNumberGenerator . generate ( ) & 0xFF ;
Q_ASSERT ( handler - > m_Perms . size ( ) = = AES_BLOCK_SIZE ) ;
AES_KEY key = { } ;
AES_set_encrypt_key ( convertByteArrayToUcharPtr ( handler - > m_authorizationData . fileEncryptionKey ) , handler - > m_authorizationData . fileEncryptionKey . size ( ) * 8 , & key ) ;
AES_ecb_encrypt ( convertByteArrayToUcharPtr ( handler - > m_Perms ) , convertByteArrayToUcharPtr ( handler - > m_Perms ) , & key , AES_ENCRYPT ) ;
break ;
}
default :
{
Q_ASSERT ( false ) ;
break ;
}
}
bool firstTry = true ;
handler - > authenticate ( [ & settings , & firstTry ] ( bool * b ) { * b = firstTry ; firstTry = false ; return settings . ownerPassword ; } , true ) ;
Q_ASSERT ( handler - > getAuthorizationResult ( ) = = PDFSecurityHandler : : AuthorizationResult : : OwnerAuthorized ) ;
return PDFSecurityHandlerPointer ( handler ) ;
}
int PDFSecurityHandlerFactory : : getPasswordOptimalEntropy ( )
{
return 128 ;
}
int PDFSecurityHandlerFactory : : getPasswordEntropy ( const QString & password , Algorithm algorithm )
{
if ( algorithm = = None )
{
return 0 ;
}
QByteArray adjustedPassword = PDFStandardSecurityHandler : : adjustPassword ( password , getRevisionFromAlgorithm ( algorithm ) ) ;
if ( adjustedPassword . isEmpty ( ) )
{
return 0 ;
}
const int length = adjustedPassword . length ( ) ;
std : : sort ( adjustedPassword . begin ( ) , adjustedPassword . end ( ) ) ;
int charCount = 0 ;
char lastChar = adjustedPassword . front ( ) ;
PDFReal entropy = 0.0 ;
for ( int i = 0 ; i < length ; + + i )
{
const char currentChar = adjustedPassword [ i ] ;
if ( currentChar = = lastChar )
{
+ + charCount ;
}
else
{
const PDFReal probability = PDFReal ( charCount ) / PDFReal ( length ) ;
entropy + = - probability * std : : log2 ( probability ) ;
charCount = 1 ;
lastChar = currentChar ;
}
}
// Jakub Melka: last character
const PDFReal probability = PDFReal ( charCount ) / PDFReal ( length ) ;
entropy + = - probability * std : : log2 ( probability ) ;
return entropy * length ;
}
int PDFSecurityHandlerFactory : : getRevisionFromAlgorithm ( Algorithm algorithm )
{
switch ( algorithm )
{
case None :
return 0 ;
case RC4 :
return 4 ;
case AES_128 :
return 4 ;
case AES_256 :
return 6 ;
default :
Q_ASSERT ( false ) ;
break ;
}
return 0 ;
}
QByteArray PDFSecurityHandlerFactory : : generateRandomByteArray ( QRandomGenerator & generator , int size )
{
QByteArray ba ;
ba . reserve ( size ) ;
for ( int i = 0 ; i < size ; + + i )
{
ba . push_back ( static_cast < char > ( generator . generate ( ) ) ) ;
}
return ba ;
}
bool PDFSecurityHandlerFactory : : validate ( const SecuritySettings & settings , QString * errorMessage )
{
switch ( settings . algorithm )
{
case pdf : : PDFSecurityHandlerFactory : : RC4 :
case pdf : : PDFSecurityHandlerFactory : : AES_128 :
{
QString invalidCharacters ;
if ( ! PDFEncoding : : canConvertToEncoding ( settings . userPassword , PDFEncoding : : Encoding : : PDFDoc , & invalidCharacters ) )
{
if ( errorMessage )
{
Q_ASSERT ( ! invalidCharacters . isEmpty ( ) ) ;
* errorMessage = tr ( " User password contains invalid characters: %1. " ) . arg ( invalidCharacters ) ;
}
return false ;
}
if ( ! PDFEncoding : : canConvertToEncoding ( settings . ownerPassword , PDFEncoding : : Encoding : : PDFDoc , & invalidCharacters ) )
{
if ( errorMessage )
{
Q_ASSERT ( ! invalidCharacters . isEmpty ( ) ) ;
* errorMessage = tr ( " Owner password contains invalid characters: %1. " ) . arg ( invalidCharacters ) ;
}
return false ;
}
break ;
}
case pdf : : PDFSecurityHandlerFactory : : None :
case pdf : : PDFSecurityHandlerFactory : : AES_256 :
break ;
default :
Q_ASSERT ( false ) ;
break ;
}
return true ;
}
} // namespace pdf