1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
22 #include <comphelper/docpasswordhelper.hxx>
23 #include <com/sun/star/beans/PropertyValue.hpp>
24 #include <com/sun/star/task/XInteractionHandler.hpp>
27 #include <osl/diagnose.h>
28 #include <rtl/digest.h>
29 #include <rtl/random.h>
32 using ::com::sun::star::uno::Sequence
;
33 using ::com::sun::star::uno::Exception
;
34 using ::com::sun::star::uno::Reference
;
35 using ::com::sun::star::uno::UNO_SET_THROW
;
36 using ::com::sun::star::task::PasswordRequestMode
;
37 using ::com::sun::star::task::PasswordRequestMode_PASSWORD_ENTER
;
38 using ::com::sun::star::task::PasswordRequestMode_PASSWORD_REENTER
;
39 using ::com::sun::star::task::XInteractionHandler
;
40 using ::com::sun::star::task::XInteractionRequest
;
42 using namespace ::com::sun::star
;
44 namespace comphelper
{
48 static uno::Sequence
< sal_Int8
> GeneratePBKDF2Hash( const OUString
& aPassword
, const uno::Sequence
< sal_Int8
>& aSalt
, sal_Int32 nCount
, sal_Int32 nHashLength
)
50 uno::Sequence
< sal_Int8
> aResult
;
52 if ( !aPassword
.isEmpty() && aSalt
.getLength() && nCount
&& nHashLength
)
54 OString aBytePass
= OUStringToOString( aPassword
, RTL_TEXTENCODING_UTF8
);
55 aResult
.realloc( 16 );
56 rtl_digest_PBKDF2( reinterpret_cast < sal_uInt8
* > ( aResult
.getArray() ),
58 reinterpret_cast < const sal_uInt8
* > ( aBytePass
.getStr() ),
59 aBytePass
.getLength(),
60 reinterpret_cast < const sal_uInt8
* > ( aSalt
.getConstArray() ),
70 IDocPasswordVerifier::~IDocPasswordVerifier()
75 uno::Sequence
< beans::PropertyValue
> DocPasswordHelper::GenerateNewModifyPasswordInfo( const OUString
& aPassword
)
77 uno::Sequence
< beans::PropertyValue
> aResult
;
79 uno::Sequence
< sal_Int8
> aSalt
= GenerateRandomByteSequence( 16 );
80 sal_Int32 nCount
= 1024;
82 uno::Sequence
< sal_Int8
> aNewHash
= GeneratePBKDF2Hash( aPassword
, aSalt
, nCount
, 16 );
83 if ( aNewHash
.getLength() )
86 aResult
[0].Name
= "algorithm-name";
87 aResult
[0].Value
<<= OUString( "PBKDF2" );
88 aResult
[1].Name
= "salt";
89 aResult
[1].Value
<<= aSalt
;
90 aResult
[2].Name
= "iteration-count";
91 aResult
[2].Value
<<= nCount
;
92 aResult
[3].Name
= "hash";
93 aResult
[3].Value
<<= aNewHash
;
100 bool DocPasswordHelper::IsModifyPasswordCorrect( const OUString
& aPassword
, const uno::Sequence
< beans::PropertyValue
>& aInfo
)
102 bool bResult
= false;
103 if ( !aPassword
.isEmpty() && aInfo
.getLength() )
106 uno::Sequence
< sal_Int8
> aSalt
;
107 uno::Sequence
< sal_Int8
> aHash
;
108 sal_Int32 nCount
= 0;
110 for ( sal_Int32 nInd
= 0; nInd
< aInfo
.getLength(); nInd
++ )
112 if ( aInfo
[nInd
].Name
== "algorithm-name" )
113 aInfo
[nInd
].Value
>>= sAlgorithm
;
114 else if ( aInfo
[nInd
].Name
== "salt" )
115 aInfo
[nInd
].Value
>>= aSalt
;
116 else if ( aInfo
[nInd
].Name
== "iteration-count" )
117 aInfo
[nInd
].Value
>>= nCount
;
118 else if ( aInfo
[nInd
].Name
== "hash" )
119 aInfo
[nInd
].Value
>>= aHash
;
122 if ( sAlgorithm
== "PBKDF2" && aSalt
.getLength() && nCount
> 0 && aHash
.getLength() )
124 uno::Sequence
< sal_Int8
> aNewHash
= GeneratePBKDF2Hash( aPassword
, aSalt
, nCount
, aHash
.getLength() );
125 for ( sal_Int32 nInd
= 0; nInd
< aNewHash
.getLength() && nInd
< aHash
.getLength() && aNewHash
[nInd
] == aHash
[nInd
]; nInd
++ )
127 if ( nInd
== aNewHash
.getLength() - 1 && nInd
== aHash
.getLength() - 1 )
137 sal_uInt32
DocPasswordHelper::GetWordHashAsUINT32(
138 const OUString
& aUString
)
140 static const sal_uInt16 pInitialCode
[] = {
158 static const sal_uInt16 pEncryptionMatrix
[15][7] = {
159 { 0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09}, // last-14
160 { 0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF}, // last-13
161 { 0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0}, // last-12
162 { 0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40}, // last-11
163 { 0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5}, // last-10
164 { 0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A}, // last-9
165 { 0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9}, // last-8
166 { 0x47D3, 0x8FA6, 0x8FA6, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0}, // last-7
167 { 0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC}, // last-6
168 { 0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10}, // last-5
169 { 0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168}, // last-4
170 { 0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C}, // last-3
171 { 0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD}, // last-2
172 { 0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC}, // last-1
173 { 0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4} // last
176 sal_uInt32 nResult
= 0;
177 sal_uInt32 nLen
= aUString
.getLength();
184 sal_uInt16 nHighResult
= pInitialCode
[nLen
- 1];
185 sal_uInt16 nLowResult
= 0;
187 const sal_Unicode
* pStr
= aUString
.getStr();
188 for ( sal_uInt32 nInd
= 0; nInd
< nLen
; nInd
++ )
190 // NO Encoding during conversion!
191 // The specification says that the low byte should be used in case it is not NULL
192 char nHighChar
= (char)( pStr
[nInd
] >> 8 );
193 char nLowChar
= (char)( pStr
[nInd
] & 0xFF );
194 char nChar
= nLowChar
? nLowChar
: nHighChar
;
196 for ( int nMatrixInd
= 0; nMatrixInd
< 7; ++nMatrixInd
)
198 if ( ( nChar
& ( 1 << nMatrixInd
) ) != 0 )
199 nHighResult
= nHighResult
^ pEncryptionMatrix
[15 - nLen
+ nInd
][nMatrixInd
];
202 nLowResult
= ( ( ( nLowResult
>> 14 ) & 0x0001 ) | ( ( nLowResult
<< 1 ) & 0x7FFF ) ) ^ nChar
;
205 nLowResult
= (sal_uInt16
)( ( ( ( nLowResult
>> 14 ) & 0x001 ) | ( ( nLowResult
<< 1 ) & 0x7FF ) ) ^ nLen
^ 0xCE4B );
207 nResult
= ( nHighResult
<< 16 ) | nLowResult
;
214 sal_uInt16
DocPasswordHelper::GetXLHashAsUINT16(
215 const OUString
& aUString
,
216 rtl_TextEncoding nEnc
)
218 sal_uInt16 nResult
= 0;
220 OString aString
= OUStringToOString( aUString
, nEnc
);
222 if ( !aString
.isEmpty() && aString
.getLength() <= SAL_MAX_UINT16
)
224 for ( sal_Int32 nInd
= aString
.getLength() - 1; nInd
>= 0; nInd
-- )
226 nResult
= ( ( nResult
>> 14 ) & 0x01 ) | ( ( nResult
<< 1 ) & 0x7FFF );
227 nResult
^= aString
[nInd
];
230 nResult
= ( ( nResult
>> 14 ) & 0x01 ) | ( ( nResult
<< 1 ) & 0x7FFF );
231 nResult
^= ( 0x8000 | ( 'N' << 8 ) | 'K' );
232 nResult
^= aString
.getLength();
239 Sequence
< sal_Int8
> DocPasswordHelper::GetXLHashAsSequence(
240 const OUString
& aUString
,
241 rtl_TextEncoding nEnc
)
243 sal_uInt16 nHash
= GetXLHashAsUINT16( aUString
, nEnc
);
244 Sequence
< sal_Int8
> aResult( 2 );
245 aResult
[0] = ( nHash
>> 8 );
246 aResult
[1] = ( nHash
& 0xFF );
252 /*static*/ uno::Sequence
< sal_Int8
> DocPasswordHelper::GenerateRandomByteSequence( sal_Int32 nLength
)
254 uno::Sequence
< sal_Int8
> aResult( nLength
);
257 osl_getSystemTime( &aTime
);
258 rtlRandomPool aRandomPool
= rtl_random_createPool ();
259 rtl_random_addBytes ( aRandomPool
, &aTime
, 8 );
260 rtl_random_getBytes ( aRandomPool
, aResult
.getArray(), nLength
);
261 rtl_random_destroyPool ( aRandomPool
);
268 /*static*/ uno::Sequence
< sal_Int8
> DocPasswordHelper::GenerateStd97Key( const OUString
& aPassword
, const uno::Sequence
< sal_Int8
>& aDocId
)
270 uno::Sequence
< sal_Int8
> aResultKey
;
271 if ( !aPassword
.isEmpty() && aDocId
.getLength() == 16 )
273 sal_uInt16 pPassData
[16];
274 memset( pPassData
, 0, sizeof(pPassData
) );
276 sal_Int32 nPassLen
= ::std::min
< sal_Int32
>( aPassword
.getLength(), 15 );
277 memcpy( pPassData
, aPassword
.getStr(), nPassLen
* sizeof(pPassData
[0]) );
279 aResultKey
= GenerateStd97Key( pPassData
, aDocId
);
286 /*static*/ uno::Sequence
< sal_Int8
> DocPasswordHelper::GenerateStd97Key( const sal_uInt16 pPassData
[16], const uno::Sequence
< sal_Int8
>& aDocId
)
288 uno::Sequence
< sal_Int8
> aResultKey
;
290 if ( aDocId
.getLength() == 16 )
291 aResultKey
= GenerateStd97Key(pPassData
, reinterpret_cast<const sal_uInt8
*>(aDocId
.getConstArray()));
297 /*static*/ uno::Sequence
< sal_Int8
> DocPasswordHelper::GenerateStd97Key( const sal_uInt16 pPassData
[16], const sal_uInt8 pDocId
[16] )
299 uno::Sequence
< sal_Int8
> aResultKey
;
302 sal_uInt8 pKeyData
[64];
303 memset( pKeyData
, 0, sizeof(pKeyData
) );
307 // Fill PassData into KeyData.
308 for ( nInd
= 0; nInd
< 16 && pPassData
[nInd
]; nInd
++)
310 pKeyData
[2*nInd
] = sal::static_int_cast
< sal_uInt8
>( (pPassData
[nInd
] >> 0) & 0xff );
311 pKeyData
[2*nInd
+ 1] = sal::static_int_cast
< sal_uInt8
>( (pPassData
[nInd
] >> 8) & 0xff );
314 pKeyData
[2*nInd
] = 0x80;
315 pKeyData
[56] = sal::static_int_cast
< sal_uInt8
>( nInd
<< 4 );
317 // Fill raw digest of KeyData into KeyData.
318 rtlDigest hDigest
= rtl_digest_create ( rtl_Digest_AlgorithmMD5
);
319 (void)rtl_digest_updateMD5 (
320 hDigest
, pKeyData
, sizeof(pKeyData
));
321 (void)rtl_digest_rawMD5 (
322 hDigest
, pKeyData
, RTL_DIGEST_LENGTH_MD5
);
324 // Update digest with KeyData and Unique.
325 for ( nInd
= 0; nInd
< 16; nInd
++ )
327 rtl_digest_updateMD5( hDigest
, pKeyData
, 5 );
328 rtl_digest_updateMD5( hDigest
, pDocId
, 16 );
331 // Update digest with padding.
333 memset( pKeyData
+ 17, 0, sizeof(pKeyData
) - 17 );
337 rtl_digest_updateMD5( hDigest
, &(pKeyData
[16]), sizeof(pKeyData
) - 16 );
339 // Fill raw digest of above updates
340 aResultKey
.realloc( RTL_DIGEST_LENGTH_MD5
);
341 rtl_digest_rawMD5 ( hDigest
, reinterpret_cast<sal_uInt8
*>(aResultKey
.getArray()), aResultKey
.getLength() );
343 // Erase KeyData array and leave.
344 rtl_secureZeroMemory (pKeyData
, sizeof(pKeyData
));
353 /*static*/ ::com::sun::star::uno::Sequence
< ::com::sun::star::beans::NamedValue
> DocPasswordHelper::requestAndVerifyDocPassword(
354 IDocPasswordVerifier
& rVerifier
,
355 const ::com::sun::star::uno::Sequence
< ::com::sun::star::beans::NamedValue
>& rMediaEncData
,
356 const OUString
& rMediaPassword
,
357 const Reference
< XInteractionHandler
>& rxInteractHandler
,
358 const OUString
& rDocumentName
,
359 DocPasswordRequestType eRequestType
,
360 const ::std::vector
< OUString
>* pDefaultPasswords
,
361 bool* pbIsDefaultPassword
)
363 ::com::sun::star::uno::Sequence
< ::com::sun::star::beans::NamedValue
> aEncData
;
364 DocPasswordVerifierResult eResult
= DocPasswordVerifierResult_WRONG_PASSWORD
;
366 // first, try provided default passwords
367 if( pbIsDefaultPassword
)
368 *pbIsDefaultPassword
= false;
369 if( pDefaultPasswords
)
371 for( ::std::vector
< OUString
>::const_iterator aIt
= pDefaultPasswords
->begin(), aEnd
= pDefaultPasswords
->end(); (eResult
== DocPasswordVerifierResult_WRONG_PASSWORD
) && (aIt
!= aEnd
); ++aIt
)
373 OSL_ENSURE( !aIt
->isEmpty(), "DocPasswordHelper::requestAndVerifyDocPassword - unexpected empty default password" );
374 if( !aIt
->isEmpty() )
376 eResult
= rVerifier
.verifyPassword( *aIt
, aEncData
);
377 if( pbIsDefaultPassword
)
378 *pbIsDefaultPassword
= eResult
== DocPasswordVerifierResult_OK
;
383 // try media encryption data (skip, if result is OK or ABORT)
384 if( eResult
== DocPasswordVerifierResult_WRONG_PASSWORD
)
386 if( rMediaEncData
.getLength() > 0 )
388 eResult
= rVerifier
.verifyEncryptionData( rMediaEncData
);
389 if( eResult
== DocPasswordVerifierResult_OK
)
390 aEncData
= rMediaEncData
;
394 // try media password (skip, if result is OK or ABORT)
395 if( eResult
== DocPasswordVerifierResult_WRONG_PASSWORD
)
397 if( !rMediaPassword
.isEmpty() )
398 eResult
= rVerifier
.verifyPassword( rMediaPassword
, aEncData
);
401 // request a password (skip, if result is OK or ABORT)
402 if( (eResult
== DocPasswordVerifierResult_WRONG_PASSWORD
) && rxInteractHandler
.is() ) try
404 PasswordRequestMode eRequestMode
= PasswordRequestMode_PASSWORD_ENTER
;
405 while( eResult
== DocPasswordVerifierResult_WRONG_PASSWORD
)
407 DocPasswordRequest
* pRequest
= new DocPasswordRequest( eRequestType
, eRequestMode
, rDocumentName
);
408 Reference
< XInteractionRequest
> xRequest( pRequest
);
409 rxInteractHandler
->handle( xRequest
);
410 if( pRequest
->isPassword() )
412 if( !pRequest
->getPassword().isEmpty() )
413 eResult
= rVerifier
.verifyPassword( pRequest
->getPassword(), aEncData
);
417 eResult
= DocPasswordVerifierResult_ABORT
;
419 eRequestMode
= PasswordRequestMode_PASSWORD_REENTER
;
426 return (eResult
== DocPasswordVerifierResult_OK
) ? aEncData
: uno::Sequence
< beans::NamedValue
>();
429 } // namespace comphelper
431 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */