1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_xmlsecurity.hxx" 26 27 #include <xmlsecurity/documentsignaturehelper.hxx> 28 29 #include <com/sun/star/container/XNameAccess.hpp> 30 #include <com/sun/star/lang/XComponent.hpp> 31 #include <com/sun/star/lang/DisposedException.hpp> 32 #include <com/sun/star/embed/XStorage.hpp> 33 #include <com/sun/star/embed/ElementModes.hpp> 34 #include "com/sun/star/beans/XPropertySet.hpp" 35 36 #include "comphelper/documentconstants.hxx" 37 #include <tools/debug.hxx> 38 #include "rtl/uri.hxx" 39 40 using namespace ::com::sun::star::uno; 41 //using namespace ::com::sun::star; 42 namespace css = ::com::sun::star; 43 using rtl::OUString; 44 45 46 namespace 47 { 48 ::rtl::OUString getElement(::rtl::OUString const & version, ::sal_Int32 * index) 49 { 50 while (*index < version.getLength() && version[*index] == '0') { 51 ++*index; 52 } 53 return version.getToken(0, '.', *index); 54 } 55 56 57 58 // Return 1 if version1 is greater then version 2, 0 if they are equal 59 //and -1 if version1 is less version 2 60 int compareVersions( 61 ::rtl::OUString const & version1, ::rtl::OUString const & version2) 62 { 63 for (::sal_Int32 i1 = 0, i2 = 0; i1 >= 0 || i2 >= 0;) { 64 ::rtl::OUString e1(getElement(version1, &i1)); 65 ::rtl::OUString e2(getElement(version2, &i2)); 66 if (e1.getLength() < e2.getLength()) { 67 return -1; 68 } else if (e1.getLength() > e2.getLength()) { 69 return 1; 70 } else if (e1 < e2) { 71 return -1; 72 } else if (e1 > e2) { 73 return 1; 74 } 75 } 76 return 0; 77 } 78 } 79 //If the OOo 3.0 mode is used then we exclude 80 //'mimetype' and all content of 'META-INF'. 81 //If the argument 'bSigning' is true then the element list is created for a signing 82 //operation in which case we use the latest signing algorithm. That is all elements 83 //we find in the zip storage are added to the list. We do not support the old signatures 84 //which did not contain all files. 85 //If 'bSigning' is false, then we validate. If the user enabled validating according to OOo 3.0 86 //then mimetype and all content of META-INF must be excluded. 87 void ImplFillElementList( 88 std::vector< rtl::OUString >& rList, const Reference < css::embed::XStorage >& rxStore, 89 const ::rtl::OUString rRootStorageName, const bool bRecursive, 90 const DocumentSignatureAlgorithm mode) 91 { 92 ::rtl::OUString aMetaInfName( RTL_CONSTASCII_USTRINGPARAM( "META-INF" ) ); 93 ::rtl::OUString sMimeTypeName (RTL_CONSTASCII_USTRINGPARAM("mimetype")); 94 ::rtl::OUString aSep( RTL_CONSTASCII_USTRINGPARAM( "/" ) ); 95 96 Reference < css::container::XNameAccess > xElements( rxStore, UNO_QUERY ); 97 Sequence< ::rtl::OUString > aElements = xElements->getElementNames(); 98 sal_Int32 nElements = aElements.getLength(); 99 const ::rtl::OUString* pNames = aElements.getConstArray(); 100 101 for ( sal_Int32 n = 0; n < nElements; n++ ) 102 { 103 if (mode != OOo3_2Document 104 && (pNames[n] == aMetaInfName 105 || pNames[n] == sMimeTypeName)) 106 { 107 continue; 108 } 109 else 110 { 111 ::rtl::OUString sEncName = ::rtl::Uri::encode( 112 pNames[n], rtl_UriCharClassRelSegment, 113 rtl_UriEncodeStrict, RTL_TEXTENCODING_UTF8); 114 if (sEncName.getLength() == 0 && pNames[n].getLength() != 0) 115 throw css::uno::Exception(::rtl::OUString( 116 RTL_CONSTASCII_USTRINGPARAM("Failed to encode element name of XStorage")), 0); 117 118 if ( rxStore->isStreamElement( pNames[n] ) ) 119 { 120 //Exclude documentsignatures.xml! 121 if (pNames[n].equals( 122 DocumentSignatureHelper::GetDocumentContentSignatureDefaultStreamName())) 123 continue; 124 ::rtl::OUString aFullName( rRootStorageName + sEncName ); 125 rList.push_back(aFullName); 126 } 127 else if ( bRecursive && rxStore->isStorageElement( pNames[n] ) ) 128 { 129 Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( pNames[n], css::embed::ElementModes::READ ); 130 rtl::OUString aFullRootName( rRootStorageName + sEncName + aSep ); 131 ImplFillElementList(rList, xSubStore, aFullRootName, bRecursive, mode); 132 } 133 } 134 } 135 } 136 137 138 bool DocumentSignatureHelper::isODFPre_1_2(const ::rtl::OUString & sVersion) 139 { 140 //The property version exists only if the document is at least version 1.2 141 //That is, if the document has version 1.1 and sVersion is empty. 142 //The constant is defined in comphelper/documentconstants.hxx 143 if (compareVersions(sVersion, ODFVER_012_TEXT) == -1) 144 return true; 145 return false; 146 } 147 148 bool DocumentSignatureHelper::isOOo3_2_Signature(const SignatureInformation & sigInfo) 149 { 150 ::rtl::OUString sManifestURI(RTL_CONSTASCII_USTRINGPARAM("META-INF/manifest.xml")); 151 bool bOOo3_2 = false; 152 typedef ::std::vector< SignatureReferenceInformation >::const_iterator CIT; 153 for (CIT i = sigInfo.vSignatureReferenceInfors.begin(); 154 i < sigInfo.vSignatureReferenceInfors.end(); i++) 155 { 156 if (i->ouURI.equals(sManifestURI)) 157 { 158 bOOo3_2 = true; 159 break; 160 } 161 } 162 return bOOo3_2; 163 } 164 165 DocumentSignatureAlgorithm 166 DocumentSignatureHelper::getDocumentAlgorithm( 167 const ::rtl::OUString & sODFVersion, const SignatureInformation & sigInfo) 168 { 169 OSL_ASSERT(sODFVersion.getLength()); 170 DocumentSignatureAlgorithm mode = OOo3_2Document; 171 if (!isOOo3_2_Signature(sigInfo)) 172 { 173 if (isODFPre_1_2(sODFVersion)) 174 mode = OOo2Document; 175 else 176 mode = OOo3_0Document; 177 } 178 return mode; 179 } 180 181 //The function creates a list of files which are to be signed or for which 182 //the signature is to be validated. The strings are UTF8 encoded URIs which 183 //contain '/' as path separators. 184 // 185 //The algorithm how document signatures are created and validated has 186 //changed over time. The change affects only which files within the document 187 //are changed. Document signatures created by OOo 2.x only used particular files. Since 188 //OOo 3.0 everything except "mimetype" and "META-INF" are signed. As of OOo 3.2 everything 189 //except META-INF/documentsignatures.xml is signed. 190 //Signatures are validated according to the algorithm which was then used for validation. 191 //That is, when validating a signature which was created by OOo 3.0, then mimetype and 192 //META-INF are not used. 193 // 194 //When a signature is created then we always use the latest algorithm. That is, we use 195 //that of OOo 3.2 196 std::vector< rtl::OUString > 197 DocumentSignatureHelper::CreateElementList( 198 const Reference < css::embed::XStorage >& rxStore, 199 const ::rtl::OUString /*rRootStorageName*/, DocumentSignatureMode eMode, 200 const DocumentSignatureAlgorithm mode) 201 { 202 std::vector< rtl::OUString > aElements; 203 ::rtl::OUString aSep( RTL_CONSTASCII_USTRINGPARAM( "/" ) ); 204 205 switch ( eMode ) 206 { 207 case SignatureModeDocumentContent: 208 { 209 if (mode == OOo2Document) //that is, ODF 1.0, 1.1 210 { 211 // 1) Main content 212 ImplFillElementList(aElements, rxStore, ::rtl::OUString(), false, mode); 213 214 // 2) Pictures... 215 rtl::OUString aSubStorageName( rtl::OUString::createFromAscii( "Pictures" ) ); 216 try 217 { 218 Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ ); 219 ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode); 220 } 221 catch(css::io::IOException& ) 222 { 223 ; // Doesn't have to exist... 224 } 225 // 3) OLE.... 226 aSubStorageName = rtl::OUString::createFromAscii( "ObjectReplacements" ); 227 try 228 { 229 Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ ); 230 ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode); 231 xSubStore.clear(); 232 233 // Object folders... 234 rtl::OUString aMatchStr( rtl::OUString::createFromAscii( "Object " ) ); 235 Reference < css::container::XNameAccess > xElements( rxStore, UNO_QUERY ); 236 Sequence< ::rtl::OUString > aElementNames = xElements->getElementNames(); 237 sal_Int32 nElements = aElementNames.getLength(); 238 const ::rtl::OUString* pNames = aElementNames.getConstArray(); 239 for ( sal_Int32 n = 0; n < nElements; n++ ) 240 { 241 if ( ( pNames[n].match( aMatchStr ) ) && rxStore->isStorageElement( pNames[n] ) ) 242 { 243 Reference < css::embed::XStorage > xTmpSubStore = rxStore->openStorageElement( pNames[n], css::embed::ElementModes::READ ); 244 ImplFillElementList(aElements, xTmpSubStore, pNames[n]+aSep, true, mode); 245 } 246 } 247 } 248 catch( com::sun::star::io::IOException& ) 249 { 250 ; // Doesn't have to exist... 251 } 252 } 253 else 254 { 255 // Everything except META-INF 256 ImplFillElementList(aElements, rxStore, ::rtl::OUString(), true, mode); 257 } 258 } 259 break; 260 case SignatureModeMacros: 261 { 262 // 1) Macros 263 rtl::OUString aSubStorageName( rtl::OUString::createFromAscii( "Basic" ) ); 264 try 265 { 266 Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ ); 267 ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode); 268 } 269 catch( com::sun::star::io::IOException& ) 270 { 271 ; // Doesn't have to exist... 272 } 273 274 // 2) Dialogs 275 aSubStorageName = rtl::OUString::createFromAscii( "Dialogs") ; 276 try 277 { 278 Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ ); 279 ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode); 280 } 281 catch( com::sun::star::io::IOException& ) 282 { 283 ; // Doesn't have to exist... 284 } 285 // 3) Scripts 286 aSubStorageName = rtl::OUString::createFromAscii( "Scripts") ; 287 try 288 { 289 Reference < css::embed::XStorage > xSubStore = rxStore->openStorageElement( aSubStorageName, css::embed::ElementModes::READ ); 290 ImplFillElementList(aElements, xSubStore, aSubStorageName+aSep, true, mode); 291 } 292 catch( css::io::IOException& ) 293 { 294 ; // Doesn't have to exist... 295 } 296 } 297 break; 298 case SignatureModePackage: 299 { 300 // Everything except META-INF 301 ImplFillElementList(aElements, rxStore, ::rtl::OUString(), true, mode); 302 } 303 break; 304 } 305 306 return aElements; 307 } 308 309 SignatureStreamHelper DocumentSignatureHelper::OpenSignatureStream( 310 const Reference < css::embed::XStorage >& rxStore, sal_Int32 nOpenMode, DocumentSignatureMode eDocSigMode ) 311 { 312 sal_Int32 nSubStorageOpenMode = css::embed::ElementModes::READ; 313 if ( nOpenMode & css::embed::ElementModes::WRITE ) 314 nSubStorageOpenMode = css::embed::ElementModes::WRITE; 315 316 SignatureStreamHelper aHelper; 317 318 try 319 { 320 ::rtl::OUString aSIGStoreName( RTL_CONSTASCII_USTRINGPARAM( "META-INF" ) ); 321 aHelper.xSignatureStorage = rxStore->openStorageElement( aSIGStoreName, nSubStorageOpenMode ); 322 if ( aHelper.xSignatureStorage.is() ) 323 { 324 ::rtl::OUString aSIGStreamName; 325 if ( eDocSigMode == SignatureModeDocumentContent ) 326 aSIGStreamName = DocumentSignatureHelper::GetDocumentContentSignatureDefaultStreamName(); 327 else if ( eDocSigMode == SignatureModeMacros ) 328 aSIGStreamName = DocumentSignatureHelper::GetScriptingContentSignatureDefaultStreamName(); 329 else 330 aSIGStreamName = DocumentSignatureHelper::GetPackageSignatureDefaultStreamName(); 331 332 aHelper.xSignatureStream = aHelper.xSignatureStorage->openStreamElement( aSIGStreamName, nOpenMode ); 333 } 334 } 335 catch(css::io::IOException& ) 336 { 337 // Doesn't have to exist... 338 DBG_ASSERT( nOpenMode == css::embed::ElementModes::READ, "Error creating signature stream..." ); 339 } 340 341 return aHelper; 342 } 343 344 //sElementList contains all files which are expected to be signed. Only those files must me signed, 345 //no more, no less. 346 //The DocumentSignatureAlgorithm indicates if the document was created with OOo 2.x. Then 347 //the uri s in the Reference elements in the signature, were not properly encoded. 348 // For example: <Reference URI="ObjectReplacements/Object 1"> 349 bool DocumentSignatureHelper::checkIfAllFilesAreSigned( 350 const ::std::vector< ::rtl::OUString > & sElementList, 351 const SignatureInformation & sigInfo, 352 const DocumentSignatureAlgorithm alg) 353 { 354 // Can only be valid if ALL streams are signed, which means real stream count == signed stream count 355 unsigned int nRealCount = 0; 356 for ( int i = sigInfo.vSignatureReferenceInfors.size(); i; ) 357 { 358 const SignatureReferenceInformation& rInf = sigInfo.vSignatureReferenceInfors[--i]; 359 // There is also an extra entry of type TYPE_SAMEDOCUMENT_REFERENCE because of signature date. 360 if ( ( rInf.nType == TYPE_BINARYSTREAM_REFERENCE ) || ( rInf.nType == TYPE_XMLSTREAM_REFERENCE ) ) 361 { 362 ::rtl::OUString sReferenceURI = rInf.ouURI; 363 if (alg == OOo2Document) 364 { 365 //Comparing URIs is a difficult. Therefore we kind of normalize 366 //it before comparing. We assume that our URI do not have a leading "./" 367 //and fragments at the end (...#...) 368 sReferenceURI = ::rtl::Uri::encode( 369 sReferenceURI, rtl_UriCharClassPchar, 370 rtl_UriEncodeCheckEscapes, RTL_TEXTENCODING_UTF8); 371 } 372 373 //find the file in the element list 374 typedef ::std::vector< ::rtl::OUString >::const_iterator CIT; 375 for (CIT aIter = sElementList.begin(); aIter < sElementList.end(); aIter++) 376 { 377 ::rtl::OUString sElementListURI = *aIter; 378 if (alg == OOo2Document) 379 { 380 sElementListURI = 381 ::rtl::Uri::encode( 382 sElementListURI, rtl_UriCharClassPchar, 383 rtl_UriEncodeCheckEscapes, RTL_TEXTENCODING_UTF8); 384 } 385 if (sElementListURI.equals(sReferenceURI)) 386 { 387 nRealCount++; 388 break; 389 } 390 } 391 } 392 } 393 return sElementList.size() == nRealCount; 394 } 395 396 /*Compares the Uri which are obtained from CreateElementList with 397 the path obtained from the manifest.xml. 398 Returns true if both strings are equal. 399 */ 400 bool DocumentSignatureHelper::equalsReferenceUriManifestPath( 401 const OUString & rUri, const OUString & rPath) 402 { 403 bool retVal = false; 404 //split up the uri and path into segments. Both are separated by '/' 405 std::vector<OUString> vUriSegments; 406 sal_Int32 nIndex = 0; 407 do 408 { 409 OUString aToken = rUri.getToken( 0, '/', nIndex ); 410 vUriSegments.push_back(aToken); 411 } 412 while (nIndex >= 0); 413 414 std::vector<OUString> vPathSegments; 415 nIndex = 0; 416 do 417 { 418 OUString aToken = rPath.getToken( 0, '/', nIndex ); 419 vPathSegments.push_back(aToken); 420 } 421 while (nIndex >= 0); 422 423 //Now compare each segment of the uri with its counterpart from the path 424 if (vUriSegments.size() == vPathSegments.size()) 425 { 426 retVal = true; 427 typedef std::vector<OUString>::const_iterator CIT; 428 for (CIT i = vUriSegments.begin(), j = vPathSegments.begin(); 429 i != vUriSegments.end(); i++, j++) 430 { 431 //Decode the uri segment, so that %20 becomes ' ', etc. 432 OUString sDecUri = ::rtl::Uri::decode( 433 *i, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8); 434 if (!sDecUri.equals(*j)) 435 { 436 retVal = false; 437 break; 438 } 439 } 440 } 441 442 return retVal; 443 } 444 445 ::rtl::OUString DocumentSignatureHelper::GetDocumentContentSignatureDefaultStreamName() 446 { 447 return ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "documentsignatures.xml" ) ); 448 } 449 450 ::rtl::OUString DocumentSignatureHelper::GetScriptingContentSignatureDefaultStreamName() 451 { 452 return ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "macrosignatures.xml" ) ); 453 } 454 455 ::rtl::OUString DocumentSignatureHelper::GetPackageSignatureDefaultStreamName() 456 { 457 return ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "packagesignatures.xml" ) ); 458 } 459