/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef XMLSEC_CRYPTO_NSS #include #include #include #include #include #endif #ifdef XMLSEC_CRYPTO_MSCRYPTO #include #include #include #include #endif using namespace com::sun::star; namespace xmlsecurity { namespace pdfio { const int MAX_SIGNATURE_CONTENT_LENGTH = 50000; class PDFTrailerElement; /// A one-liner comment. class PDFCommentElement : public PDFElement { PDFDocument& m_rDoc; OString m_aComment; public: explicit PDFCommentElement(PDFDocument& rDoc); bool Read(SvStream& rStream) override; }; /// Numbering object: an integer or a real. class PDFNumberElement : public PDFElement { /// Input file start location. sal_uInt64 m_nOffset = 0; /// Input file token length. sal_uInt64 m_nLength = 0; double m_fValue = 0; public: PDFNumberElement(); bool Read(SvStream& rStream) override; double GetValue() const; sal_uInt64 GetLocation() const; sal_uInt64 GetLength() const; }; class PDFReferenceElement; /// End of a dictionary: '>>'. class PDFEndDictionaryElement : public PDFElement { /// Offset before the '>>' token. sal_uInt64 m_nLocation = 0; public: PDFEndDictionaryElement(); bool Read(SvStream& rStream) override; sal_uInt64 GetLocation() const; }; /// Reference object: something with a unique ID. class PDFReferenceElement : public PDFElement { PDFDocument& m_rDoc; int m_fObjectValue; int m_fGenerationValue; public: PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue); bool Read(SvStream& rStream) override; /// Assuming the reference points to a number object, return its value. double LookupNumber(SvStream& rStream) const; /// Lookup referenced object, without assuming anything about its contents. PDFObjectElement* LookupObject(); int GetObjectValue() const; int GetGenerationValue() const; }; /// Stream object: a byte array with a known length. class PDFStreamElement : public PDFElement { size_t m_nLength; sal_uInt64 m_nOffset; public: explicit PDFStreamElement(size_t nLength); bool Read(SvStream& rStream) override; sal_uInt64 GetOffset() const; }; /// End of a stream: 'endstream' keyword. class PDFEndStreamElement : public PDFElement { public: bool Read(SvStream& rStream) override; }; /// End of a object: 'endobj' keyword. class PDFEndObjectElement : public PDFElement { public: bool Read(SvStream& rStream) override; }; /// Array object: a list. class PDFArrayElement : public PDFElement { /// Location after the '[' token. sal_uInt64 m_nOffset = 0; std::vector m_aElements; public: PDFArrayElement(); bool Read(SvStream& rStream) override; void PushBack(PDFElement* pElement); const std::vector& GetElements(); }; /// End of an array: ']'. class PDFEndArrayElement : public PDFElement { /// Location before the ']' token. sal_uInt64 m_nOffset = 0; public: PDFEndArrayElement(); bool Read(SvStream& rStream) override; sal_uInt64 GetOffset() const; }; /// Boolean object: a 'true' or a 'false'. class PDFBooleanElement : public PDFElement { public: explicit PDFBooleanElement(bool bValue); bool Read(SvStream& rStream) override; }; /// Null object: the 'null' singleton. class PDFNullElement : public PDFElement { public: bool Read(SvStream& rStream) override; }; /// Hex string: in form. class PDFHexStringElement : public PDFElement { OString m_aValue; public: bool Read(SvStream& rStream) override; const OString& GetValue() const; }; /// Literal string: in (asdf) form. class PDFLiteralStringElement : public PDFElement { OString m_aValue; public: bool Read(SvStream& rStream) override; const OString& GetValue() const; }; /// The trailer singleton is at the end of the doc. class PDFTrailerElement : public PDFElement { PDFDocument& m_rDoc; std::map m_aDictionary; public: explicit PDFTrailerElement(PDFDocument& rDoc); bool Read(SvStream& rStream) override; PDFElement* Lookup(const OString& rDictionaryKey); }; XRefEntry::XRefEntry() : m_eType(XRefEntryType::NOT_COMPRESSED), m_nOffset(0), m_nGenerationNumber(0), m_bDirty(false) { } PDFDocument::PDFDocument() : m_pTrailer(nullptr), m_pXRefStream(nullptr) { } bool PDFDocument::RemoveSignature(size_t nPosition) { std::vector aSignatures = GetSignatureWidgets(); if (nPosition >= aSignatures.size()) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::RemoveSignature: invalid nPosition"); return false; } if (aSignatures.size() != m_aEOFs.size() - 1) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::RemoveSignature: no 1:1 mapping between signatures and incremental updates"); return false; } // The EOF offset is the end of the original file, without the signature at // nPosition. m_aEditBuffer.Seek(m_aEOFs[nPosition]); // Drop all bytes after the current position. m_aEditBuffer.SetStreamSize(m_aEditBuffer.Tell() + 1); return m_aEditBuffer.good(); } sal_uInt32 PDFDocument::GetNextSignature() { sal_uInt32 nRet = 0; for (const auto& pSignature : GetSignatureWidgets()) { auto pT = dynamic_cast(pSignature->Lookup("T")); if (!pT) continue; const OString& rValue = pT->GetValue(); const OString aPrefix = "Signature"; if (!rValue.startsWith(aPrefix)) continue; nRet = std::max(nRet, rValue.copy(aPrefix.getLength()).toUInt32()); } return nRet + 1; } sal_Int32 PDFDocument::WriteSignatureObject(const OUString& rDescription, bool bAdES, sal_uInt64& rLastByteRangeOffset, sal_Int64& rContentOffset) { // Write signature object. sal_Int32 nSignatureId = m_aXRef.size(); XRefEntry aSignatureEntry; aSignatureEntry.m_nOffset = m_aEditBuffer.Tell(); aSignatureEntry.m_bDirty = true; m_aXRef[nSignatureId] = aSignatureEntry; OStringBuffer aSigBuffer; aSigBuffer.append(nSignatureId); aSigBuffer.append(" 0 obj\n"); aSigBuffer.append("<\n/Type/Sig/SubFilter"); if (bAdES) aSigBuffer.append("/ETSI.CAdES.detached"); else aSigBuffer.append("/adbe.pkcs7.detached"); // Time of signing. aSigBuffer.append(" /M ("); aSigBuffer.append(vcl::PDFWriter::GetDateTime()); aSigBuffer.append(")"); // Byte range: we can write offset1-length1 and offset2 right now, will // write length2 later. aSigBuffer.append(" /ByteRange [ 0 "); // -1 and +1 is the leading "<" and the trailing ">" around the hex string. aSigBuffer.append(rContentOffset - 1); aSigBuffer.append(" "); aSigBuffer.append(rContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1); aSigBuffer.append(" "); rLastByteRangeOffset = aSignatureEntry.m_nOffset + aSigBuffer.getLength(); // We don't know how many bytes we need for the last ByteRange value, this // should be enough. OStringBuffer aByteRangeFiller; comphelper::string::padToLength(aByteRangeFiller, 100, ' '); aSigBuffer.append(aByteRangeFiller.makeStringAndClear()); // Finish the Sig obj. aSigBuffer.append(" /Filter/Adobe.PPKMS"); if (!rDescription.isEmpty()) { aSigBuffer.append("/Reason<"); vcl::PDFWriter::AppendUnicodeTextString(rDescription, aSigBuffer); aSigBuffer.append(">"); } aSigBuffer.append(" >>\nendobj\n\n"); m_aEditBuffer.WriteOString(aSigBuffer.toString()); return nSignatureId; } sal_Int32 PDFDocument::WriteAppearanceObject() { // Write appearance object. sal_Int32 nAppearanceId = m_aXRef.size(); XRefEntry aAppearanceEntry; aAppearanceEntry.m_nOffset = m_aEditBuffer.Tell(); aAppearanceEntry.m_bDirty = true; m_aXRef[nAppearanceId] = aAppearanceEntry; m_aEditBuffer.WriteUInt32AsString(nAppearanceId); m_aEditBuffer.WriteCharPtr(" 0 obj\n"); m_aEditBuffer.WriteCharPtr("<>\n"); m_aEditBuffer.WriteCharPtr("stream\n\nendstream\nendobj\n\n"); return nAppearanceId; } sal_Int32 PDFDocument::WriteAnnotObject(PDFObjectElement& rFirstPage, sal_Int32 nSignatureId, sal_Int32 nAppearanceId) { // Decide what identifier to use for the new signature. sal_uInt32 nNextSignature = GetNextSignature(); // Write the Annot object, references nSignatureId and nAppearanceId. sal_Int32 nAnnotId = m_aXRef.size(); XRefEntry aAnnotEntry; aAnnotEntry.m_nOffset = m_aEditBuffer.Tell(); aAnnotEntry.m_bDirty = true; m_aXRef[nAnnotId] = aAnnotEntry; m_aEditBuffer.WriteUInt32AsString(nAnnotId); m_aEditBuffer.WriteCharPtr(" 0 obj\n"); m_aEditBuffer.WriteCharPtr("<>\n"); m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n"); return nAnnotId; } bool PDFDocument::WritePageObject(PDFObjectElement& rFirstPage, sal_Int32 nAnnotId) { PDFElement* pAnnots = rFirstPage.Lookup("Annots"); auto pAnnotsReference = dynamic_cast(pAnnots); if (pAnnotsReference) { // Write the updated Annots key of the Page object. PDFObjectElement* pAnnotsObject = pAnnotsReference->LookupObject(); if (!pAnnotsObject) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid Annots reference"); return false; } sal_uInt32 nAnnotsId = pAnnotsObject->GetObjectValue(); m_aXRef[nAnnotsId].m_eType = XRefEntryType::NOT_COMPRESSED; m_aXRef[nAnnotsId].m_nOffset = m_aEditBuffer.Tell(); m_aXRef[nAnnotsId].m_nGenerationNumber = 0; m_aXRef[nAnnotsId].m_bDirty = true; m_aEditBuffer.WriteUInt32AsString(nAnnotsId); m_aEditBuffer.WriteCharPtr(" 0 obj\n["); // Write existing references. PDFArrayElement* pArray = pAnnotsObject->GetArray(); if (!pArray) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: Page Annots is a reference to a non-array"); return false; } for (size_t i = 0; i < pArray->GetElements().size(); ++i) { auto pReference = dynamic_cast(pArray->GetElements()[i]); if (!pReference) continue; if (i) m_aEditBuffer.WriteCharPtr(" "); m_aEditBuffer.WriteUInt32AsString(pReference->GetObjectValue()); m_aEditBuffer.WriteCharPtr(" 0 R"); } // Write our reference. m_aEditBuffer.WriteCharPtr(" "); m_aEditBuffer.WriteUInt32AsString(nAnnotId); m_aEditBuffer.WriteCharPtr(" 0 R"); m_aEditBuffer.WriteCharPtr("]\nendobj\n\n"); } else { // Write the updated first page object, references nAnnotId. sal_uInt32 nFirstPageId = rFirstPage.GetObjectValue(); if (nFirstPageId >= m_aXRef.size()) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid first page obj id"); return false; } m_aXRef[nFirstPageId].m_nOffset = m_aEditBuffer.Tell(); m_aXRef[nFirstPageId].m_bDirty = true; m_aEditBuffer.WriteUInt32AsString(nFirstPageId); m_aEditBuffer.WriteCharPtr(" 0 obj\n"); m_aEditBuffer.WriteCharPtr("<<"); auto pAnnotsArray = dynamic_cast(pAnnots); if (!pAnnotsArray) { // No Annots key, just write the key with a single reference. m_aEditBuffer.WriteBytes(static_cast(m_aEditBuffer.GetData()) + rFirstPage.GetDictionaryOffset(), rFirstPage.GetDictionaryLength()); m_aEditBuffer.WriteCharPtr("/Annots["); m_aEditBuffer.WriteUInt32AsString(nAnnotId); m_aEditBuffer.WriteCharPtr(" 0 R]"); } else { // Annots key is already there, insert our reference at the end. PDFDictionaryElement* pDictionary = rFirstPage.GetDictionary(); // Offset right before the end of the Annots array. sal_uInt64 nAnnotsEndOffset = pDictionary->GetKeyOffset("Annots") + pDictionary->GetKeyValueLength("Annots") - 1; // Length of beginning of the dictionary -> Annots end. sal_uInt64 nAnnotsBeforeEndLength = nAnnotsEndOffset - rFirstPage.GetDictionaryOffset(); m_aEditBuffer.WriteBytes(static_cast(m_aEditBuffer.GetData()) + rFirstPage.GetDictionaryOffset(), nAnnotsBeforeEndLength); m_aEditBuffer.WriteCharPtr(" "); m_aEditBuffer.WriteUInt32AsString(nAnnotId); m_aEditBuffer.WriteCharPtr(" 0 R"); // Length of Annots end -> end of the dictionary. sal_uInt64 nAnnotsAfterEndLength = rFirstPage.GetDictionaryOffset() + rFirstPage.GetDictionaryLength() - nAnnotsEndOffset; m_aEditBuffer.WriteBytes(static_cast(m_aEditBuffer.GetData()) + nAnnotsEndOffset, nAnnotsAfterEndLength); } m_aEditBuffer.WriteCharPtr(">>"); m_aEditBuffer.WriteCharPtr("\nendobj\n\n"); } return true; } bool PDFDocument::WriteCatalogObject(sal_Int32 nAnnotId, PDFReferenceElement*& pRoot) { if (m_pXRefStream) pRoot = dynamic_cast(m_pXRefStream->Lookup("Root")); else { if (!m_pTrailer) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: found no trailer"); return false; } pRoot = dynamic_cast(m_pTrailer->Lookup("Root")); } if (!pRoot) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: trailer has no root reference"); return false; } PDFObjectElement* pCatalog = pRoot->LookupObject(); if (!pCatalog) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid catalog reference"); return false; } sal_uInt32 nCatalogId = pCatalog->GetObjectValue(); if (nCatalogId >= m_aXRef.size()) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid catalog obj id"); return false; } PDFElement* pAcroForm = pCatalog->Lookup("AcroForm"); auto pAcroFormReference = dynamic_cast(pAcroForm); if (pAcroFormReference) { // Write the updated AcroForm key of the Catalog object. PDFObjectElement* pAcroFormObject = pAcroFormReference->LookupObject(); if (!pAcroFormObject) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid AcroForm reference"); return false; } sal_uInt32 nAcroFormId = pAcroFormObject->GetObjectValue(); m_aXRef[nAcroFormId].m_eType = XRefEntryType::NOT_COMPRESSED; m_aXRef[nAcroFormId].m_nOffset = m_aEditBuffer.Tell(); m_aXRef[nAcroFormId].m_nGenerationNumber = 0; m_aXRef[nAcroFormId].m_bDirty = true; m_aEditBuffer.WriteUInt32AsString(nAcroFormId); m_aEditBuffer.WriteCharPtr(" 0 obj\n"); // If this is nullptr, then the AcroForm object is not in an object stream. SvMemoryStream* pStreamBuffer = pAcroFormObject->GetStreamBuffer(); if (!pAcroFormObject->Lookup("Fields")) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm object without required Fields key"); return false; } PDFDictionaryElement* pAcroFormDictionary = pAcroFormObject->GetDictionary(); if (!pAcroFormDictionary) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm object has no dictionary"); return false; } // Offset right before the end of the Fields array. sal_uInt64 nFieldsEndOffset = pAcroFormDictionary->GetKeyOffset("Fields") + pAcroFormDictionary->GetKeyValueLength("Fields") - strlen("]"); // Length of beginning of the object dictionary -> Fields end. sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset; if (pStreamBuffer) m_aEditBuffer.WriteBytes(pStreamBuffer->GetData(), nFieldsBeforeEndLength); else { nFieldsBeforeEndLength -= pAcroFormObject->GetDictionaryOffset(); m_aEditBuffer.WriteCharPtr("<<"); m_aEditBuffer.WriteBytes(static_cast(m_aEditBuffer.GetData()) + pAcroFormObject->GetDictionaryOffset(), nFieldsBeforeEndLength); } // Append our reference at the end of the Fields array. m_aEditBuffer.WriteCharPtr(" "); m_aEditBuffer.WriteUInt32AsString(nAnnotId); m_aEditBuffer.WriteCharPtr(" 0 R"); // Length of Fields end -> end of the object dictionary. if (pStreamBuffer) { sal_uInt64 nFieldsAfterEndLength = pStreamBuffer->GetSize() - nFieldsEndOffset; m_aEditBuffer.WriteBytes(static_cast(pStreamBuffer->GetData()) + nFieldsEndOffset, nFieldsAfterEndLength); } else { sal_uInt64 nFieldsAfterEndLength = pAcroFormObject->GetDictionaryOffset() + pAcroFormObject->GetDictionaryLength() - nFieldsEndOffset; m_aEditBuffer.WriteBytes(static_cast(m_aEditBuffer.GetData()) + nFieldsEndOffset, nFieldsAfterEndLength); m_aEditBuffer.WriteCharPtr(">>"); } m_aEditBuffer.WriteCharPtr("\nendobj\n\n"); } else { // Write the updated Catalog object, references nAnnotId. auto pAcroFormDictionary = dynamic_cast(pAcroForm); m_aXRef[nCatalogId].m_nOffset = m_aEditBuffer.Tell(); m_aXRef[nCatalogId].m_bDirty = true; m_aEditBuffer.WriteUInt32AsString(nCatalogId); m_aEditBuffer.WriteCharPtr(" 0 obj\n"); m_aEditBuffer.WriteCharPtr("<<"); if (!pAcroFormDictionary) { // No AcroForm key, assume no signatures. m_aEditBuffer.WriteBytes(static_cast(m_aEditBuffer.GetData()) + pCatalog->GetDictionaryOffset(), pCatalog->GetDictionaryLength()); m_aEditBuffer.WriteCharPtr("/AcroForm<>\n"); } else { // AcroForm key is already there, insert our reference at the Fields end. auto it = pAcroFormDictionary->GetItems().find("Fields"); if (it == pAcroFormDictionary->GetItems().end()) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm without required Fields key"); return false; } auto pFields = dynamic_cast(it->second); if (!pFields) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm Fields is not an array"); return false; } // Offset right before the end of the Fields array. sal_uInt64 nFieldsEndOffset = pAcroFormDictionary->GetKeyOffset("Fields") + pAcroFormDictionary->GetKeyValueLength("Fields") - 1; // Length of beginning of the Catalog dictionary -> Fields end. sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset - pCatalog->GetDictionaryOffset(); m_aEditBuffer.WriteBytes(static_cast(m_aEditBuffer.GetData()) + pCatalog->GetDictionaryOffset(), nFieldsBeforeEndLength); m_aEditBuffer.WriteCharPtr(" "); m_aEditBuffer.WriteUInt32AsString(nAnnotId); m_aEditBuffer.WriteCharPtr(" 0 R"); // Length of Fields end -> end of the Catalog dictionary. sal_uInt64 nFieldsAfterEndLength = pCatalog->GetDictionaryOffset() + pCatalog->GetDictionaryLength() - nFieldsEndOffset; m_aEditBuffer.WriteBytes(static_cast(m_aEditBuffer.GetData()) + nFieldsEndOffset, nFieldsAfterEndLength); } m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n"); } return true; } void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement* pRoot) { if (m_pXRefStream) { // Write the xref stream. // This is a bit meta: the xref stream stores its own offset. sal_Int32 nXRefStreamId = m_aXRef.size(); XRefEntry aXRefStreamEntry; aXRefStreamEntry.m_nOffset = nXRefOffset; aXRefStreamEntry.m_bDirty = true; m_aXRef[nXRefStreamId] = aXRefStreamEntry; // Write stream data. SvMemoryStream aXRefStream; const size_t nOffsetLen = 3; // 3 additional bytes: predictor, the first and the third field. const size_t nLineLength = nOffsetLen + 3; // This is the line as it appears before tweaking according to the predictor. std::vector aOrigLine(nLineLength); // This is the previous line. std::vector aPrevLine(nLineLength); // This is the line as written to the stream. std::vector aFilteredLine(nLineLength); for (const auto& rXRef : m_aXRef) { const XRefEntry& rEntry = rXRef.second; if (!rEntry.m_bDirty) continue; // Predictor. size_t nPos = 0; // PNG prediction: up (on all rows). aOrigLine[nPos++] = 2; // First field. unsigned char nType = 0; switch (rEntry.m_eType) { case XRefEntryType::FREE: nType = 0; break; case XRefEntryType::NOT_COMPRESSED: nType = 1; break; case XRefEntryType::COMPRESSED: nType = 2; break; } aOrigLine[nPos++] = nType; // Second field. for (size_t i = 0; i < nOffsetLen; ++i) { size_t nByte = nOffsetLen - i - 1; // Fields requiring more than one byte are stored with the // high-order byte first. unsigned char nCh = (rEntry.m_nOffset & (0xff << (nByte * 8))) >> (nByte * 8); aOrigLine[nPos++] = nCh; } // Third field. aOrigLine[nPos++] = 0; // Now apply the predictor. aFilteredLine[0] = aOrigLine[0]; for (size_t i = 1; i < nLineLength; ++i) { // Count the delta vs the previous line. aFilteredLine[i] = aOrigLine[i] - aPrevLine[i]; // Remember the new reference. aPrevLine[i] = aOrigLine[i]; } aXRefStream.WriteBytes(aFilteredLine.data(), aFilteredLine.size()); } m_aEditBuffer.WriteUInt32AsString(nXRefStreamId); m_aEditBuffer.WriteCharPtr(" 0 obj\n<>/Filter/FlateDecode"); // ID. auto pID = dynamic_cast(m_pXRefStream->Lookup("ID")); if (pID) { const std::vector& rElements = pID->GetElements(); m_aEditBuffer.WriteCharPtr("/ID [ <"); for (size_t i = 0; i < rElements.size(); ++i) { auto pIDString = dynamic_cast(rElements[i]); if (!pIDString) continue; m_aEditBuffer.WriteOString(pIDString->GetValue()); if ((i + 1) < rElements.size()) m_aEditBuffer.WriteCharPtr("> <"); } m_aEditBuffer.WriteCharPtr("> ] "); } // Index. m_aEditBuffer.WriteCharPtr("/Index [ "); for (const auto& rXRef : m_aXRef) { if (!rXRef.second.m_bDirty) continue; m_aEditBuffer.WriteUInt32AsString(rXRef.first); m_aEditBuffer.WriteCharPtr(" 1 "); } m_aEditBuffer.WriteCharPtr("] "); // Info. auto pInfo = dynamic_cast(m_pXRefStream->Lookup("Info")); if (pInfo) { m_aEditBuffer.WriteCharPtr("/Info "); m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue()); m_aEditBuffer.WriteCharPtr(" "); m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue()); m_aEditBuffer.WriteCharPtr(" R "); } // Length. m_aEditBuffer.WriteCharPtr("/Length "); { ZCodec aZCodec; aZCodec.BeginCompression(); aXRefStream.Seek(0); SvMemoryStream aStream; aZCodec.Compress(aXRefStream, aStream); aZCodec.EndCompression(); aXRefStream.Seek(0); aXRefStream.SetStreamSize(0); aStream.Seek(0); aXRefStream.WriteStream(aStream); } m_aEditBuffer.WriteUInt32AsString(aXRefStream.GetSize()); if (!m_aStartXRefs.empty()) { // Write location of the previous cross-reference section. m_aEditBuffer.WriteCharPtr("/Prev "); m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back()); } // Root. m_aEditBuffer.WriteCharPtr("/Root "); m_aEditBuffer.WriteUInt32AsString(pRoot->GetObjectValue()); m_aEditBuffer.WriteCharPtr(" "); m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue()); m_aEditBuffer.WriteCharPtr(" R "); // Size. m_aEditBuffer.WriteCharPtr("/Size "); m_aEditBuffer.WriteUInt32AsString(m_aXRef.size()); m_aEditBuffer.WriteCharPtr("/Type/XRef/W[1 3 1]>>\nstream\n"); aXRefStream.Seek(0); m_aEditBuffer.WriteStream(aXRefStream); m_aEditBuffer.WriteCharPtr("\nendstream\nendobj\n\n"); } else { // Write the xref table. m_aEditBuffer.WriteCharPtr("xref\n"); for (const auto& rXRef : m_aXRef) { size_t nObject = rXRef.first; size_t nOffset = rXRef.second.m_nOffset; if (!rXRef.second.m_bDirty) continue; m_aEditBuffer.WriteUInt32AsString(nObject); m_aEditBuffer.WriteCharPtr(" 1\n"); OStringBuffer aBuffer; aBuffer.append(static_cast(nOffset)); while (aBuffer.getLength() < 10) aBuffer.insert(0, "0"); if (nObject == 0) aBuffer.append(" 65535 f \n"); else aBuffer.append(" 00000 n \n"); m_aEditBuffer.WriteOString(aBuffer.toString()); } // Write the trailer. m_aEditBuffer.WriteCharPtr("trailer\n<GetObjectValue()); m_aEditBuffer.WriteCharPtr(" "); m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue()); m_aEditBuffer.WriteCharPtr(" R\n"); auto pInfo = dynamic_cast(m_pTrailer->Lookup("Info")); if (pInfo) { m_aEditBuffer.WriteCharPtr("/Info "); m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue()); m_aEditBuffer.WriteCharPtr(" "); m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue()); m_aEditBuffer.WriteCharPtr(" R\n"); } auto pID = dynamic_cast(m_pTrailer->Lookup("ID")); if (pID) { const std::vector& rElements = pID->GetElements(); m_aEditBuffer.WriteCharPtr("/ID [ <"); for (size_t i = 0; i < rElements.size(); ++i) { auto pIDString = dynamic_cast(rElements[i]); if (!pIDString) continue; m_aEditBuffer.WriteOString(pIDString->GetValue()); if ((i + 1) < rElements.size()) m_aEditBuffer.WriteCharPtr(">\n<"); } m_aEditBuffer.WriteCharPtr("> ]\n"); } if (!m_aStartXRefs.empty()) { // Write location of the previous cross-reference section. m_aEditBuffer.WriteCharPtr("/Prev "); m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back()); } m_aEditBuffer.WriteCharPtr(">>\n"); } } bool PDFDocument::Sign(const uno::Reference& xCertificate, const OUString& rDescription, bool bAdES) { m_aEditBuffer.Seek(STREAM_SEEK_TO_END); m_aEditBuffer.WriteCharPtr("\n"); sal_uInt64 nSignatureLastByteRangeOffset = 0; sal_Int64 nSignatureContentOffset = 0; sal_Int32 nSignatureId = WriteSignatureObject(rDescription, bAdES, nSignatureLastByteRangeOffset, nSignatureContentOffset); sal_Int32 nAppearanceId = WriteAppearanceObject(); std::vector aPages = GetPages(); if (aPages.empty() || !aPages[0]) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: found no pages"); return false; } PDFObjectElement& rFirstPage = *aPages[0]; sal_Int32 nAnnotId = WriteAnnotObject(rFirstPage, nSignatureId, nAppearanceId); if (!WritePageObject(rFirstPage, nAnnotId)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: failed to write the updated Page object"); return false; } PDFReferenceElement* pRoot = nullptr; if (!WriteCatalogObject(nAnnotId, pRoot)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: failed to write the updated Catalog object"); return false; } sal_uInt64 nXRefOffset = m_aEditBuffer.Tell(); WriteXRef(nXRefOffset, pRoot); // Write startxref. m_aEditBuffer.WriteCharPtr("startxref\n"); m_aEditBuffer.WriteUInt32AsString(nXRefOffset); m_aEditBuffer.WriteCharPtr("\n%%EOF\n"); // Finalize the signature, now that we know the total file size. // Calculate the length of the last byte range. sal_uInt64 nFileEnd = m_aEditBuffer.Tell(); sal_Int64 nLastByteRangeLength = nFileEnd - (nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1); // Write the length to the buffer. m_aEditBuffer.Seek(nSignatureLastByteRangeOffset); OStringBuffer aByteRangeBuffer; aByteRangeBuffer.append(nLastByteRangeLength); aByteRangeBuffer.append(" ]"); m_aEditBuffer.WriteOString(aByteRangeBuffer.toString()); // Create the PKCS#7 object. css::uno::Sequence aDerEncoded = xCertificate->getEncoded(); if (!aDerEncoded.hasElements()) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: empty certificate"); return false; } m_aEditBuffer.Seek(0); sal_uInt64 nBufferSize1 = nSignatureContentOffset - 1; std::unique_ptr aBuffer1(new char[nBufferSize1]); m_aEditBuffer.ReadBytes(aBuffer1.get(), nBufferSize1); m_aEditBuffer.Seek(nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1); sal_uInt64 nBufferSize2 = nLastByteRangeLength; std::unique_ptr aBuffer2(new char[nBufferSize2]); m_aEditBuffer.ReadBytes(aBuffer2.get(), nBufferSize2); OStringBuffer aCMSHexBuffer; vcl::PDFWriter::PDFSignContext aSignContext(aCMSHexBuffer); aSignContext.m_pDerEncoded = aDerEncoded.getArray(); aSignContext.m_nDerEncoded = aDerEncoded.getLength(); aSignContext.m_pByteRange1 = aBuffer1.get(); aSignContext.m_nByteRange1 = nBufferSize1; aSignContext.m_pByteRange2 = aBuffer2.get(); aSignContext.m_nByteRange2 = nBufferSize2; if (!vcl::PDFWriter::Sign(aSignContext)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: PDFWriter::Sign() failed"); return false; } assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH); m_aEditBuffer.Seek(nSignatureContentOffset); m_aEditBuffer.WriteOString(aCMSHexBuffer.toString()); return true; } bool PDFDocument::Write(SvStream& rStream) { m_aEditBuffer.Seek(0); rStream.WriteStream(m_aEditBuffer); return rStream.good(); } bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, std::vector< std::unique_ptr >& rElements, PDFObjectElement* pObjectElement) { // Last seen object token. PDFObjectElement* pObject = pObjectElement; PDFNameElement* pObjectKey = nullptr; PDFObjectElement* pObjectStream = nullptr; bool bInXRef = false; // The next number will be an xref offset. bool bInStartXRef = false; // Dictionary depth, so we know when we're outside any dictionaries. int nDictionaryDepth = 0; // Last seen array token that's outside any dictionaries. PDFArrayElement* pArray = nullptr; while (true) { char ch; rStream.ReadChar(ch); if (rStream.IsEof()) break; switch (ch) { case '%': { auto pComment = new PDFCommentElement(*this); rElements.push_back(std::unique_ptr(pComment)); rStream.SeekRel(-1); if (!rElements.back()->Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: PDFCommentElement::Read() failed"); return false; } if (eMode == TokenizeMode::EOF_TOKEN && !m_aEOFs.empty() && m_aEOFs.back() == rStream.Tell()) { // Found EOF and partial parsing requested, we're done. return true; } break; } case '<': { // Dictionary or hex string. rStream.ReadChar(ch); rStream.SeekRel(-2); if (ch == '<') { rElements.push_back(std::unique_ptr(new PDFDictionaryElement())); ++nDictionaryDepth; } else rElements.push_back(std::unique_ptr(new PDFHexStringElement)); if (!rElements.back()->Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: PDFDictionaryElement::Read() failed"); return false; } break; } case '>': { rElements.push_back(std::unique_ptr(new PDFEndDictionaryElement())); --nDictionaryDepth; rStream.SeekRel(-1); if (!rElements.back()->Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: PDFEndDictionaryElement::Read() failed"); return false; } break; } case '[': { auto pArr = new PDFArrayElement(); rElements.push_back(std::unique_ptr(pArr)); if (nDictionaryDepth == 0) { // The array is attached directly, inform the object. pArray = pArr; if (pObject) pObject->SetArray(pArray); } rStream.SeekRel(-1); if (!rElements.back()->Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: PDFArrayElement::Read() failed"); return false; } break; } case ']': { rElements.push_back(std::unique_ptr(new PDFEndArrayElement())); pArray = nullptr; rStream.SeekRel(-1); if (!rElements.back()->Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: PDFEndArrayElement::Read() failed"); return false; } break; } case '/': { auto pNameElement = new PDFNameElement(); rElements.push_back(std::unique_ptr(pNameElement)); rStream.SeekRel(-1); if (!pNameElement->Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: PDFNameElement::Read() failed"); return false; } if (pObject && pObjectKey && pObjectKey->GetValue() == "Type" && pNameElement->GetValue() == "ObjStm") pObjectStream = pObject; else pObjectKey = pNameElement; break; } case '(': { rElements.push_back(std::unique_ptr(new PDFLiteralStringElement)); rStream.SeekRel(-1); if (!rElements.back()->Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: PDFLiteralStringElement::Read() failed"); return false; } break; } default: { if (isdigit(ch) || ch == '-') { // Numbering object: an integer or a real. auto pNumberElement = new PDFNumberElement(); rElements.push_back(std::unique_ptr(pNumberElement)); rStream.SeekRel(-1); if (!pNumberElement->Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: PDFNumberElement::Read() failed"); return false; } if (bInStartXRef) { bInStartXRef = false; m_aStartXRefs.push_back(pNumberElement->GetValue()); auto it = m_aOffsetObjects.find(pNumberElement->GetValue()); if (it != m_aOffsetObjects.end()) m_pXRefStream = it->second; } } else if (isalpha(ch)) { // Possible keyword, like "obj". rStream.SeekRel(-1); OString aKeyword = ReadKeyword(rStream); bool bObj = aKeyword == "obj"; if (bObj || aKeyword == "R") { size_t nElements = rElements.size(); if (nElements < 2) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: expected at least two tokens before 'obj' or 'R' keyword"); return false; } auto pObjectNumber = dynamic_cast(rElements[nElements - 2].get()); auto pGenerationNumber = dynamic_cast(rElements[nElements - 1].get()); if (!pObjectNumber || !pGenerationNumber) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: missing object or generation number before 'obj' or 'R' keyword"); return false; } if (bObj) { pObject = new PDFObjectElement(*this, pObjectNumber->GetValue(), pGenerationNumber->GetValue()); rElements.push_back(std::unique_ptr(pObject)); m_aOffsetObjects[pObjectNumber->GetLocation()] = pObject; m_aIDObjects[pObjectNumber->GetValue()] = pObject; } else { rElements.push_back(std::unique_ptr(new PDFReferenceElement(*this, pObjectNumber->GetValue(), pGenerationNumber->GetValue()))); if (pArray) // Reference is part of a direct (non-dictionary) array, inform the array. pArray->PushBack(rElements.back().get()); } if (!rElements.back()->Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: PDFElement::Read() failed"); return false; } } else if (aKeyword == "stream") { // Look up the length of the stream from the parent object's dictionary. size_t nLength = 0; for (size_t nElement = 0; nElement < rElements.size(); ++nElement) { // Iterate in reverse order. size_t nIndex = rElements.size() - nElement - 1; PDFElement* pElement = rElements[nIndex].get(); auto pObj = dynamic_cast(pElement); if (!pObj) continue; PDFElement* pLookup = pObj->Lookup("Length"); auto pReference = dynamic_cast(pLookup); if (pReference) { // Length is provided as a reference. nLength = pReference->LookupNumber(rStream); break; } auto pNumber = dynamic_cast(pLookup); if (pNumber) { // Length is provided directly. nLength = pNumber->GetValue(); break; } SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: found no Length key for stream keyword"); return false; } PDFDocument::SkipLineBreaks(rStream); auto pStreamElement = new PDFStreamElement(nLength); if (pObject) pObject->SetStream(pStreamElement); rElements.push_back(std::unique_ptr(pStreamElement)); if (!rElements.back()->Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: PDFStreamElement::Read() failed"); return false; } } else if (aKeyword == "endstream") { rElements.push_back(std::unique_ptr(new PDFEndStreamElement)); if (!rElements.back()->Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: PDFEndStreamElement::Read() failed"); return false; } } else if (aKeyword == "endobj") { rElements.push_back(std::unique_ptr(new PDFEndObjectElement)); if (!rElements.back()->Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: PDFEndObjectElement::Read() failed"); return false; } if (eMode == TokenizeMode::END_OF_OBJECT) { // Found endobj and only object parsing was requested, we're done. return true; } if (pObjectStream) { // We're at the end of an object stream, parse the stored objects. pObjectStream->ParseStoredObjects(); pObjectStream = nullptr; pObjectKey = nullptr; } } else if (aKeyword == "true" || aKeyword == "false") rElements.push_back(std::unique_ptr(new PDFBooleanElement(aKeyword.toBoolean()))); else if (aKeyword == "null") rElements.push_back(std::unique_ptr(new PDFNullElement)); else if (aKeyword == "xref") // Allow 'f' and 'n' keywords. bInXRef = true; else if (bInXRef && (aKeyword == "f" || aKeyword == "n")) { } else if (aKeyword == "trailer") { auto pTrailer = new PDFTrailerElement(*this); // When reading till the first EOF token only, remember // just the first trailer token. if (eMode != TokenizeMode::EOF_TOKEN || !m_pTrailer) m_pTrailer = pTrailer; rElements.push_back(std::unique_ptr(pTrailer)); } else if (aKeyword == "startxref") { bInStartXRef = true; } else { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: unexpected '" << aKeyword << "' keyword at byte position " << rStream.Tell()); return false; } } else { if (!isspace(ch)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: unexpected character: " << ch << " at byte position " << rStream.Tell()); return false; } } break; } } } return true; } void PDFDocument::SetIDObject(size_t nID, PDFObjectElement* pObject) { m_aIDObjects[nID] = pObject; } bool PDFDocument::Read(SvStream& rStream) { // Check file magic. std::vector aHeader(5); rStream.Seek(0); rStream.ReadBytes(aHeader.data(), aHeader.size()); if (aHeader[0] != '%' || aHeader[1] != 'P' || aHeader[2] != 'D' || aHeader[3] != 'F' || aHeader[4] != '-') { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: header mismatch"); return false; } // Allow later editing of the contents in-memory. rStream.Seek(0); m_aEditBuffer.WriteStream(rStream); // Look up the offset of the xref table. size_t nStartXRef = FindStartXRef(rStream); SAL_INFO("xmlsecurity.pdfio", "PDFDocument::Read: nStartXRef is " << nStartXRef); if (nStartXRef == 0) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: found no xref start offset"); return false; } while (true) { rStream.Seek(nStartXRef); OString aKeyword = ReadKeyword(rStream); if (aKeyword.isEmpty()) ReadXRefStream(rStream); else { if (aKeyword != "xref") { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: xref is not the first keyword"); return false; } ReadXRef(rStream); if (!Tokenize(rStream, TokenizeMode::EOF_TOKEN, m_aElements, nullptr)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: failed to tokenizer trailer after xref"); return false; } } PDFNumberElement* pPrev = nullptr; if (m_pTrailer) pPrev = dynamic_cast(m_pTrailer->Lookup("Prev")); else if (m_pXRefStream) pPrev = dynamic_cast(m_pXRefStream->Lookup("Prev")); if (pPrev) nStartXRef = pPrev->GetValue(); // Reset state, except object offsets and the edit buffer. m_aElements.clear(); m_aStartXRefs.clear(); m_aEOFs.clear(); m_pTrailer = nullptr; m_pXRefStream = nullptr; if (!pPrev) break; } // Then we can tokenize the stream. rStream.Seek(0); return Tokenize(rStream, TokenizeMode::END_OF_STREAM, m_aElements, nullptr); } OString PDFDocument::ReadKeyword(SvStream& rStream) { OStringBuffer aBuf; char ch; rStream.ReadChar(ch); while (isalpha(ch)) { aBuf.append(ch); rStream.ReadChar(ch); if (rStream.IsEof()) break; } rStream.SeekRel(-1); return aBuf.toString(); } size_t PDFDocument::FindStartXRef(SvStream& rStream) { // Find the "startxref" token, somewhere near the end of the document. std::vector aBuf(1024); rStream.Seek(STREAM_SEEK_TO_END); if (rStream.Tell() > aBuf.size()) rStream.SeekRel(static_cast(-1) * aBuf.size()); else // The document is really short, then just read it from the start. rStream.Seek(0); size_t nBeforePeek = rStream.Tell(); size_t nSize = rStream.ReadBytes(aBuf.data(), aBuf.size()); rStream.Seek(nBeforePeek); if (nSize != aBuf.size()) aBuf.resize(nSize); OString aPrefix("startxref"); // Find the last startxref at the end of the document. std::vector::iterator itLastValid = aBuf.end(); std::vector::iterator it = aBuf.begin(); while (true) { it = std::search(it, aBuf.end(), aPrefix.getStr(), aPrefix.getStr() + aPrefix.getLength()); if (it == aBuf.end()) break; else { itLastValid = it; ++it; } } if (itLastValid == aBuf.end()) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::FindStartXRef: found no startxref"); return 0; } rStream.SeekRel(itLastValid - aBuf.begin() + aPrefix.getLength()); if (rStream.IsEof()) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::FindStartXRef: unexpected end of stream after startxref"); return 0; } PDFDocument::SkipWhitespace(rStream); PDFNumberElement aNumber; if (!aNumber.Read(rStream)) return 0; return aNumber.GetValue(); } void PDFDocument::ReadXRefStream(SvStream& rStream) { // Look up the stream length in the object dictionary. if (!Tokenize(rStream, TokenizeMode::END_OF_OBJECT, m_aElements, nullptr)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: failed to read object"); return; } if (m_aElements.empty()) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: no tokens found"); return; } PDFObjectElement* pObject = nullptr; for (const auto& pElement : m_aElements) { if (auto pObj = dynamic_cast(pElement.get())) { pObject = pObj; break; } } if (!pObject) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: no object token found"); return; } // So that the Prev key can be looked up later. m_pXRefStream = pObject; PDFElement* pLookup = pObject->Lookup("Length"); auto pNumber = dynamic_cast(pLookup); if (!pNumber) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: stream length is not provided"); return; } sal_uInt64 nLength = pNumber->GetValue(); // Look up the stream offset. PDFStreamElement* pStream = nullptr; for (const auto& pElement : m_aElements) { if (auto pS = dynamic_cast(pElement.get())) { pStream = pS; break; } } if (!pStream) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: no stream token found"); return; } // Read and decompress it. rStream.Seek(pStream->GetOffset()); std::vector aBuf(nLength); rStream.ReadBytes(aBuf.data(), aBuf.size()); auto pFilter = dynamic_cast(pObject->Lookup("Filter")); if (!pFilter) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: no Filter found"); return; } if (pFilter->GetValue() != "FlateDecode") { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: unexpected filter: " << pFilter->GetValue()); return; } int nColumns = 1; int nPredictor = 1; if (auto pDecodeParams = dynamic_cast(pObject->Lookup("DecodeParms"))) { const std::map& rItems = pDecodeParams->GetItems(); auto it = rItems.find("Columns"); if (it != rItems.end()) if (auto pColumns = dynamic_cast(it->second)) nColumns = pColumns->GetValue(); it = rItems.find("Predictor"); if (it != rItems.end()) if (auto pPredictor = dynamic_cast(it->second)) nPredictor = pPredictor->GetValue(); } SvMemoryStream aSource(aBuf.data(), aBuf.size(), StreamMode::READ); SvMemoryStream aStream; ZCodec aZCodec; aZCodec.BeginCompression(); aZCodec.Decompress(aSource, aStream); if (!aZCodec.EndCompression()) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: decompression failed"); return; } // Look up the first and the last entry we need to read. auto pIndex = dynamic_cast(pObject->Lookup("Index")); std::vector aFirstObjects; std::vector aNumberOfObjects; if (!pIndex) { auto pSize = dynamic_cast(pObject->Lookup("Size")); if (pSize) { aFirstObjects.push_back(0); aNumberOfObjects.push_back(pSize->GetValue()); } else { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: Index and Size not found"); return; } } else { const std::vector& rIndexElements = pIndex->GetElements(); size_t nFirstObject = 0; for (size_t i = 0; i < rIndexElements.size(); ++i) { if (i % 2 == 0) { auto pFirstObject = dynamic_cast(rIndexElements[i]); if (!pFirstObject) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: Index has no first object"); return; } nFirstObject = pFirstObject->GetValue(); continue; } auto pNumberOfObjects = dynamic_cast(rIndexElements[i]); if (!pNumberOfObjects) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: Index has no number of objects"); return; } aFirstObjects.push_back(nFirstObject); aNumberOfObjects.push_back(pNumberOfObjects->GetValue()); } } // Look up the format of a single entry. const int nWSize = 3; auto pW = dynamic_cast(pObject->Lookup("W")); if (!pW || pW->GetElements().size() < nWSize) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: W not found or has < 3 elements"); return; } int aW[nWSize]; // First character is the (kind of) repeated predictor. int nLineLength = 1; for (size_t i = 0; i < nWSize; ++i) { auto pI = dynamic_cast(pW->GetElements()[i]); if (!pI) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: W contains non-number"); return; } aW[i] = pI->GetValue(); nLineLength += aW[i]; } if (nPredictor > 1 && nLineLength - 1 != nColumns) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: /DecodeParms/Columns is inconsistent with /W"); return; } aStream.Seek(0); for (size_t nSubSection = 0; nSubSection < aFirstObjects.size(); ++nSubSection) { size_t nFirstObject = aFirstObjects[nSubSection]; size_t nNumberOfObjects = aNumberOfObjects[nSubSection]; // This is the line as read from the stream. std::vector aOrigLine(nLineLength); // This is the line as it appears after tweaking according to nPredictor. std::vector aFilteredLine(nLineLength); for (size_t nEntry = 0; nEntry < nNumberOfObjects; ++nEntry) { size_t nIndex = nFirstObject + nEntry; aStream.ReadBytes(aOrigLine.data(), aOrigLine.size()); if (nPredictor > 1 && aOrigLine[0] + 10 != nPredictor) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: in-stream predictor is inconsistent with /DecodeParms/Predictor for object #" << nIndex); return; } for (int i = 0; i < nLineLength; ++i) { switch (nPredictor) { case 1: // No prediction. break; case 12: // PNG prediction: up (on all rows). aFilteredLine[i] = aFilteredLine[i] + aOrigLine[i]; break; default: SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: unexpected predictor: " << nPredictor); return; break; } } // First character is already handled above. int nPos = 1; size_t nType = 0; // Start of the current field in the stream data. int nOffset = nPos; for (; nPos < nOffset + aW[0]; ++nPos) { unsigned char nCh = aFilteredLine[nPos]; nType = (nType << 8) + nCh; } // Start of the object in the file stream. size_t nStreamOffset = 0; nOffset = nPos; for (; nPos < nOffset + aW[1]; ++nPos) { unsigned char nCh = aFilteredLine[nPos]; nStreamOffset = (nStreamOffset << 8) + nCh; } // Generation number of the object. size_t nGenerationNumber = 0; nOffset = nPos; for (; nPos < nOffset + aW[2]; ++nPos) { unsigned char nCh = aFilteredLine[nPos]; nGenerationNumber = (nGenerationNumber << 8) + nCh; } // Ignore invalid nType. if (nType <= 2) { if (m_aXRef.find(nIndex) == m_aXRef.end()) { XRefEntry aEntry; switch (nType) { case 0: aEntry.m_eType = XRefEntryType::FREE; break; case 1: aEntry.m_eType = XRefEntryType::NOT_COMPRESSED; break; case 2: aEntry.m_eType = XRefEntryType::COMPRESSED; break; } aEntry.m_nOffset = nStreamOffset; aEntry.m_nGenerationNumber = nGenerationNumber; m_aXRef[nIndex] = aEntry; } } } } } void PDFDocument::ReadXRef(SvStream& rStream) { PDFDocument::SkipWhitespace(rStream); while (true) { PDFNumberElement aFirstObject; if (!aFirstObject.Read(rStream)) { // Next token is not a number, it'll be the trailer. return; } if (aFirstObject.GetValue() < 0) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: expected first object number >= 0"); return; } PDFDocument::SkipWhitespace(rStream); PDFNumberElement aNumberOfEntries; if (!aNumberOfEntries.Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: failed to read number of entries"); return; } if (aNumberOfEntries.GetValue() < 0) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: expected zero or more entries"); return; } size_t nSize = aNumberOfEntries.GetValue(); for (size_t nEntry = 0; nEntry < nSize; ++nEntry) { size_t nIndex = aFirstObject.GetValue() + nEntry; PDFDocument::SkipWhitespace(rStream); PDFNumberElement aOffset; if (!aOffset.Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: failed to read offset"); return; } PDFDocument::SkipWhitespace(rStream); PDFNumberElement aGenerationNumber; if (!aGenerationNumber.Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: failed to read generation number"); return; } PDFDocument::SkipWhitespace(rStream); OString aKeyword = ReadKeyword(rStream); if (aKeyword != "f" && aKeyword != "n") { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: unexpected keyword"); return; } // xrefs are read in reverse order, so never update an existing // offset with an older one. if (m_aXRef.find(nIndex) == m_aXRef.end()) { XRefEntry aEntry; aEntry.m_nOffset = aOffset.GetValue(); aEntry.m_nGenerationNumber = aGenerationNumber.GetValue(); // Initially only the first entry is dirty. if (nIndex == 0) aEntry.m_bDirty = true; m_aXRef[nIndex] = aEntry; } PDFDocument::SkipWhitespace(rStream); } } } void PDFDocument::SkipWhitespace(SvStream& rStream) { char ch = 0; while (true) { rStream.ReadChar(ch); if (rStream.IsEof()) break; if (!isspace(ch)) { rStream.SeekRel(-1); return; } } } void PDFDocument::SkipLineBreaks(SvStream& rStream) { char ch = 0; while (true) { rStream.ReadChar(ch); if (rStream.IsEof()) break; if (ch != '\n' && ch != '\r') { rStream.SeekRel(-1); return; } } } size_t PDFDocument::GetObjectOffset(size_t nIndex) const { auto it = m_aXRef.find(nIndex); if (it == m_aXRef.end() || it->second.m_eType == XRefEntryType::COMPRESSED) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetObjectOffset: wanted to look up index #" << nIndex << ", but failed"); return 0; } return it->second.m_nOffset; } const std::vector< std::unique_ptr >& PDFDocument::GetElements() { return m_aElements; } std::vector PDFDocument::GetPages() { std::vector aRet; PDFReferenceElement* pRoot = nullptr; if (m_pTrailer) pRoot = dynamic_cast(m_pTrailer->Lookup("Root")); else if (m_pXRefStream) pRoot = dynamic_cast(m_pXRefStream->Lookup("Root")); if (!pRoot) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: trailer has no Root key"); return aRet; } PDFObjectElement* pCatalog = pRoot->LookupObject(); if (!pCatalog) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: trailer has no catalog"); return aRet; } PDFObjectElement* pPages = pCatalog->LookupObject("Pages"); if (!pPages) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: catalog (obj " << pCatalog->GetObjectValue() << ") has no pages"); return aRet; } auto pKids = dynamic_cast(pPages->Lookup("Kids")); if (!pKids) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: pages has no kids"); return aRet; } for (const auto& pKid : pKids->GetElements()) { auto pReference = dynamic_cast(pKid); if (!pReference) continue; aRet.push_back(pReference->LookupObject()); } return aRet; } void PDFDocument::PushBackEOF(size_t nOffset) { m_aEOFs.push_back(nOffset); } std::vector PDFDocument::GetSignatureWidgets() { std::vector aRet; std::vector aPages = GetPages(); for (const auto& pPage : aPages) { if (!pPage) continue; PDFElement* pAnnotsElement = pPage->Lookup("Annots"); auto pAnnots = dynamic_cast(pAnnotsElement); if (!pAnnots) { // Annots is not an array, see if it's a reference to an object // with a direct array. auto pAnnotsRef = dynamic_cast(pAnnotsElement); if (pAnnotsRef) { if (PDFObjectElement* pAnnotsObject = pAnnotsRef->LookupObject()) { pAnnots = pAnnotsObject->GetArray(); } } } if (!pAnnots) continue; for (const auto& pAnnot : pAnnots->GetElements()) { auto pReference = dynamic_cast(pAnnot); if (!pReference) continue; PDFObjectElement* pAnnotObject = pReference->LookupObject(); if (!pAnnotObject) continue; auto pFT = dynamic_cast(pAnnotObject->Lookup("FT")); if (!pFT || pFT->GetValue() != "Sig") continue; aRet.push_back(pAnnotObject); } } return aRet; } int PDFDocument::AsHex(char ch) { int nRet = 0; if (isdigit(ch)) nRet = ch - '0'; else { if (ch >= 'a' && ch <= 'f') nRet = ch - 'a'; else if (ch >= 'A' && ch <= 'F') nRet = ch - 'A'; else return -1; nRet += 10; } return nRet; } std::vector PDFDocument::DecodeHexString(PDFHexStringElement* pElement) { std::vector aRet; const OString& rHex = pElement->GetValue(); size_t nHexLen = rHex.getLength(); { int nByte = 0; int nCount = 2; for (size_t i = 0; i < nHexLen; ++i) { nByte = nByte << 4; sal_Int8 nParsed = AsHex(rHex[i]); if (nParsed == -1) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::DecodeHexString: invalid hex value"); return aRet; } nByte += nParsed; --nCount; if (!nCount) { aRet.push_back(nByte); nCount = 2; nByte = 0; } } } return aRet; } namespace { #ifdef XMLSEC_CRYPTO_NSS /// Similar to NSS_CMSAttributeArray_FindAttrByOidTag(), but works directly with a SECOidData. NSSCMSAttribute* CMSAttributeArray_FindAttrByOidData(NSSCMSAttribute** attrs, SECOidData* oid, PRBool only) { NSSCMSAttribute* attr1, *attr2; if (attrs == nullptr) return nullptr; if (oid == nullptr) return nullptr; while ((attr1 = *attrs++) != nullptr) { if (attr1->type.len == oid->oid.len && PORT_Memcmp(attr1->type.data, oid->oid.data, oid->oid.len) == 0) break; } if (attr1 == nullptr) return nullptr; if (!only) return attr1; while ((attr2 = *attrs++) != nullptr) { if (attr2->type.len == oid->oid.len && PORT_Memcmp(attr2->type.data, oid->oid.data, oid->oid.len) == 0) break; } if (attr2 != nullptr) return nullptr; return attr1; } /// Same as SEC_StringToOID(), which is private to us. SECStatus StringToOID(SECItem* to, const char* from, PRUint32 len) { PRUint32 decimal_numbers = 0; PRUint32 result_bytes = 0; SECStatus rv; PRUint8 result[1024]; static const PRUint32 max_decimal = (0xffffffff / 10); static const char OIDstring[] = {"OID."}; if (!from || !to) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } if (!len) { len = PL_strlen(from); } if (len >= 4 && !PL_strncasecmp(from, OIDstring, 4)) { from += 4; /* skip leading "OID." if present */ len -= 4; } if (!len) { bad_data: PORT_SetError(SEC_ERROR_BAD_DATA); return SECFailure; } do { PRUint32 decimal = 0; while (len > 0 && isdigit(*from)) { PRUint32 addend = (*from++ - '0'); --len; if (decimal > max_decimal) /* overflow */ goto bad_data; decimal = (decimal * 10) + addend; if (decimal < addend) /* overflow */ goto bad_data; } if (len != 0 && *from != '.') { goto bad_data; } if (decimal_numbers == 0) { if (decimal > 2) goto bad_data; result[0] = decimal * 40; result_bytes = 1; } else if (decimal_numbers == 1) { if (decimal > 40) goto bad_data; result[0] += decimal; } else { /* encode the decimal number, */ PRUint8* rp; PRUint32 num_bytes = 0; PRUint32 tmp = decimal; while (tmp) { num_bytes++; tmp >>= 7; } if (!num_bytes) ++num_bytes; /* use one byte for a zero value */ if (static_cast(num_bytes) + result_bytes > sizeof result) goto bad_data; tmp = num_bytes; rp = result + result_bytes - 1; rp[tmp] = (PRUint8)(decimal & 0x7f); decimal >>= 7; while (--tmp > 0) { rp[tmp] = (PRUint8)(decimal | 0x80); decimal >>= 7; } result_bytes += num_bytes; } ++decimal_numbers; if (len > 0) /* skip trailing '.' */ { ++from; --len; } } while (len > 0); /* now result contains result_bytes of data */ if (to->data && to->len >= result_bytes) { PORT_Memcpy(to->data, result, to->len = result_bytes); rv = SECSuccess; } else { SECItem result_item = {siBuffer, nullptr, 0 }; result_item.data = result; result_item.len = result_bytes; rv = SECITEM_CopyItem(nullptr, to, &result_item); } return rv; } #elif defined XMLSEC_CRYPTO_MSCRYPTO /// Verifies a non-detached signature using CryptoAPI. bool VerifyNonDetachedSignature(SvStream& rStream, std::vector>& rByteRanges, std::vector& rExpectedHash) { HCRYPTPROV hProv = 0; if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) { SAL_WARN("xmlsecurity.pdfio", "CryptAcquireContext() failed"); return false; } HCRYPTHASH hHash = 0; if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) { SAL_WARN("xmlsecurity.pdfio", "CryptCreateHash() failed"); return false; } for (const auto& rByteRange : rByteRanges) { rStream.Seek(rByteRange.first); const int nChunkLen = 4096; std::vector aBuffer(nChunkLen); for (size_t nByte = 0; nByte < rByteRange.second;) { size_t nRemainingSize = rByteRange.second - nByte; if (nRemainingSize < nChunkLen) { rStream.ReadBytes(aBuffer.data(), nRemainingSize); if (!CryptHashData(hHash, aBuffer.data(), nRemainingSize, 0)) { SAL_WARN("xmlsecurity.pdfio", "CryptHashData() failed"); return false; } nByte = rByteRange.second; } else { rStream.ReadBytes(aBuffer.data(), nChunkLen); if (!CryptHashData(hHash, aBuffer.data(), nChunkLen, 0)) { SAL_WARN("xmlsecurity.pdfio", "CryptHashData() failed"); return false; } nByte += nChunkLen; } } } DWORD nActualHash = 0; if (!CryptGetHashParam(hHash, HP_HASHVAL, nullptr, &nActualHash, 0)) { SAL_WARN("xmlsecurity.pdfio", "CryptGetHashParam() failed to provide the hash length"); return false; } std::vector aActualHash(nActualHash); if (!CryptGetHashParam(hHash, HP_HASHVAL, aActualHash.data(), &nActualHash, 0)) { SAL_WARN("xmlsecurity.pdfio", "CryptGetHashParam() failed to provide the hash"); return false; } CryptDestroyHash(hHash); CryptReleaseContext(hProv, 0); if (!std::memcmp(aActualHash.data(), rExpectedHash.data(), aActualHash.size()) && aActualHash.size() == rExpectedHash.size()) return true; return false; } #endif } bool PDFDocument::ValidateSignature(SvStream& rStream, PDFObjectElement* pSignature, SignatureInformation& rInformation, bool bLast) { PDFObjectElement* pValue = pSignature->LookupObject("V"); if (!pValue) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: no value"); return false; } auto pContents = dynamic_cast(pValue->Lookup("Contents")); if (!pContents) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: no contents"); return false; } auto pByteRange = dynamic_cast(pValue->Lookup("ByteRange")); if (!pByteRange || pByteRange->GetElements().size() < 2) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: no byte range or too few elements"); return false; } auto pSubFilter = dynamic_cast(pValue->Lookup("SubFilter")); bool bNonDetached = pSubFilter && pSubFilter->GetValue() == "adbe.pkcs7.sha1"; if (!pSubFilter || (pSubFilter->GetValue() != "adbe.pkcs7.detached" && !bNonDetached && pSubFilter->GetValue() != "ETSI.CAdES.detached")) { if (!pSubFilter) SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: missing sub-filter"); else SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: unsupported sub-filter: '"<GetValue()<<"'"); return false; } // Reason / comment / description is optional. auto pReason = dynamic_cast(pValue->Lookup("Reason")); if (pReason) { // See appendUnicodeTextString() for the export equivalent of this. std::vector aReason = PDFDocument::DecodeHexString(pReason); OUStringBuffer aBuffer; sal_uInt16 nByte = 0; for (size_t i = 0; i < aReason.size(); ++i) { if (i % 2 == 0) nByte = aReason[i]; else { sal_Unicode nUnicode; nUnicode = (nByte << 8); nUnicode |= aReason[i]; aBuffer.append(nUnicode); } } if (!aBuffer.isEmpty()) rInformation.ouDescription = aBuffer.makeStringAndClear(); } // Date: used only when the time of signing is not available in the // signature. auto pM = dynamic_cast(pValue->Lookup("M")); if (pM) { // Example: "D:20161027100104". const OString& rM = pM->GetValue(); if (rM.startsWith("D:") && rM.getLength() >= 16) { rInformation.stDateTime.Year = rM.copy(2, 4).toInt32(); rInformation.stDateTime.Month = rM.copy(6, 2).toInt32(); rInformation.stDateTime.Day = rM.copy(8, 2).toInt32(); rInformation.stDateTime.Hours = rM.copy(10, 2).toInt32(); rInformation.stDateTime.Minutes = rM.copy(12, 2).toInt32(); rInformation.stDateTime.Seconds = rM.copy(14, 2).toInt32(); } } // Build a list of offset-length pairs, representing the signed bytes. std::vector> aByteRanges; size_t nByteRangeOffset = 0; const std::vector& rByteRangeElements = pByteRange->GetElements(); for (size_t i = 0; i < rByteRangeElements.size(); ++i) { auto pNumber = dynamic_cast(rByteRangeElements[i]); if (!pNumber) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: signature offset and length has to be a number"); return false; } if (i % 2 == 0) { nByteRangeOffset = pNumber->GetValue(); continue; } size_t nByteRangeLength = pNumber->GetValue(); aByteRanges.push_back(std::make_pair(nByteRangeOffset, nByteRangeLength)); } // Detect if the byte ranges don't cover everything, but the signature itself. if (aByteRanges.size() < 2) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: expected 2 byte ranges"); return false; } if (aByteRanges[0].first != 0) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: first range start is not 0"); return false; } // 2 is the leading "<" and the trailing ">" around the hex string. size_t nSignatureLength = static_cast(pContents->GetValue().getLength()) + 2; if (aByteRanges[1].first != (aByteRanges[0].second + nSignatureLength)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: second range start is not the end of the signature"); return false; } rStream.Seek(STREAM_SEEK_TO_END); size_t nFileEnd = rStream.Tell(); if (bLast && (aByteRanges[1].first + aByteRanges[1].second) != nFileEnd) // Second range end is not the end of the file. rInformation.bPartialDocumentSignature = true; // At this point there is no obviously missing info to validate the // signature. std::vector aSignature = PDFDocument::DecodeHexString(pContents); if (aSignature.empty()) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: empty contents"); return false; } #ifdef XMLSEC_CRYPTO_NSS // Validate the signature. No need to call NSS_Init() here, assume that the // caller did that already. SECItem aSignatureItem; aSignatureItem.data = aSignature.data(); aSignatureItem.len = aSignature.size(); NSSCMSMessage* pCMSMessage = NSS_CMSMessage_CreateFromDER(&aSignatureItem, /*cb=*/nullptr, /*cb_arg=*/nullptr, /*pwfn=*/nullptr, /*pwfn_arg=*/nullptr, /*decrypt_key_cb=*/nullptr, /*decrypt_key_cb_arg=*/nullptr); if (!NSS_CMSMessage_IsSigned(pCMSMessage)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: message is not signed"); return false; } NSSCMSContentInfo* pCMSContentInfo = NSS_CMSMessage_ContentLevel(pCMSMessage, 0); if (!pCMSContentInfo) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_CMSMessage_ContentLevel() failed"); return false; } auto pCMSSignedData = static_cast(NSS_CMSContentInfo_GetContent(pCMSContentInfo)); if (!pCMSSignedData) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_CMSContentInfo_GetContent() failed"); return false; } // Import certificates from the signed data temporarily, so it'll be // possible to verify the signature, even if we didn't have the certificate // previously. std::vector aDocumentCertificates; for (size_t i = 0; pCMSSignedData->rawCerts[i]; ++i) aDocumentCertificates.push_back(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), pCMSSignedData->rawCerts[i], nullptr, 0, 0)); NSSCMSSignerInfo* pCMSSignerInfo = NSS_CMSSignedData_GetSignerInfo(pCMSSignedData, 0); if (!pCMSSignerInfo) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_CMSSignedData_GetSignerInfo() failed"); return false; } SECItem aAlgorithm = NSS_CMSSignedData_GetDigestAlgs(pCMSSignedData)[0]->algorithm; SECOidTag eOidTag = SECOID_FindOIDTag(&aAlgorithm); // Map a sign algorithm to a digest algorithm. // See NSS_CMSUtil_MapSignAlgs(), which is private to us. switch (eOidTag) { case SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION: eOidTag = SEC_OID_SHA1; break; case SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION: eOidTag = SEC_OID_SHA256; break; case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION: eOidTag = SEC_OID_SHA512; break; default: break; } HASH_HashType eHashType = HASH_GetHashTypeByOidTag(eOidTag); HASHContext* pHASHContext = HASH_Create(eHashType); if (!pHASHContext) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: HASH_Create() failed"); return false; } // We have a hash, update it with the byte ranges. for (const auto& rByteRange : aByteRanges) { rStream.Seek(rByteRange.first); // And now hash this byte range. const int nChunkLen = 4096; std::vector aBuffer(nChunkLen); for (size_t nByte = 0; nByte < rByteRange.second;) { size_t nRemainingSize = rByteRange.second - nByte; if (nRemainingSize < nChunkLen) { rStream.ReadBytes(aBuffer.data(), nRemainingSize); HASH_Update(pHASHContext, aBuffer.data(), nRemainingSize); nByte = rByteRange.second; } else { rStream.ReadBytes(aBuffer.data(), nChunkLen); HASH_Update(pHASHContext, aBuffer.data(), nChunkLen); nByte += nChunkLen; } } } // Find out what is the expected length of the hash. unsigned int nMaxResultLen = 0; switch (eOidTag) { case SEC_OID_SHA1: nMaxResultLen = msfilter::SHA1_HASH_LENGTH; rInformation.nDigestID = xml::crypto::DigestID::SHA1; break; case SEC_OID_SHA256: nMaxResultLen = msfilter::SHA256_HASH_LENGTH; rInformation.nDigestID = xml::crypto::DigestID::SHA256; break; case SEC_OID_SHA512: nMaxResultLen = msfilter::SHA512_HASH_LENGTH; break; default: SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: unrecognized algorithm"); return false; } auto pActualResultBuffer = static_cast(PORT_Alloc(nMaxResultLen)); unsigned int nActualResultLen; HASH_End(pHASHContext, pActualResultBuffer, &nActualResultLen, nMaxResultLen); CERTCertificate* pCertificate = NSS_CMSSignerInfo_GetSigningCertificate(pCMSSignerInfo, CERT_GetDefaultCertDB()); if (!pCertificate) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_CMSSignerInfo_GetSigningCertificate() failed"); return false; } else { uno::Sequence aDerCert(pCertificate->derCert.len); for (size_t i = 0; i < pCertificate->derCert.len; ++i) aDerCert[i] = pCertificate->derCert.data[i]; OUStringBuffer aBuffer; sax::Converter::encodeBase64(aBuffer, aDerCert); rInformation.ouX509Certificate = aBuffer.makeStringAndClear(); } PRTime nSigningTime; // This may fail, in which case the date should be taken from the dictionary's "M" key. if (NSS_CMSSignerInfo_GetSigningTime(pCMSSignerInfo, &nSigningTime) == SECSuccess) { // First convert the UNIX timestamp to an ISO8601 string. OUStringBuffer aBuffer; uno::Reference xComponentContext = comphelper::getProcessComponentContext(); CalendarWrapper aCalendarWrapper(xComponentContext); // nSigningTime is in microseconds. SvXMLUnitConverter::convertDateTime(aBuffer, static_cast(nSigningTime) / 1000000 / tools::Time::secondPerDay, aCalendarWrapper.getEpochStart().GetUNODate()); // Then convert this string to a local UNO DateTime. util::DateTime aUNODateTime; try { utl::ISO8601parseDateTime(aBuffer.toString(), aUNODateTime); } catch (const std::length_error&) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: failed to parse signature date string"); return false; } DateTime aDateTime(aUNODateTime); aDateTime.ConvertToLocalTime(); rInformation.stDateTime = aDateTime.GetUNODateTime(); } // Check if we have a signing certificate attribute. SECOidData aOidData; aOidData.oid.data = nullptr; /* * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::= * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) * smime(16) id-aa(2) 47 } */ if (StringToOID(&aOidData.oid, "1.2.840.113549.1.9.16.2.47", 0) != SECSuccess) { SAL_WARN("xmlsecurity.pdfio", "StringToOID() failed"); return false; } aOidData.offset = SEC_OID_UNKNOWN; aOidData.desc = "id-aa-signingCertificateV2"; aOidData.mechanism = CKM_SHA_1; aOidData.supportedExtension = UNSUPPORTED_CERT_EXTENSION; NSSCMSAttribute* pAttribute = CMSAttributeArray_FindAttrByOidData(pCMSSignerInfo->authAttr, &aOidData, PR_TRUE); if (pAttribute) rInformation.bHasSigningCertificate = true; SECItem* pContentInfoContentData = pCMSSignedData->contentInfo.content.data; if (bNonDetached && pContentInfoContentData && pContentInfoContentData->data) { // Not a detached signature. if (!std::memcmp(pActualResultBuffer, pContentInfoContentData->data, nMaxResultLen) && nActualResultLen == pContentInfoContentData->len) rInformation.nStatus = xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED; } else { // Detached, the usual case. SECItem aActualResultItem; aActualResultItem.data = pActualResultBuffer; aActualResultItem.len = nActualResultLen; if (NSS_CMSSignerInfo_Verify(pCMSSignerInfo, &aActualResultItem, nullptr) == SECSuccess) rInformation.nStatus = xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED; } // Everything went fine PORT_Free(pActualResultBuffer); HASH_Destroy(pHASHContext); NSS_CMSSignerInfo_Destroy(pCMSSignerInfo); for (auto pDocumentCertificate : aDocumentCertificates) CERT_DestroyCertificate(pDocumentCertificate); return true; #elif defined XMLSEC_CRYPTO_MSCRYPTO // Open a message for decoding. HCRYPTMSG hMsg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, CMSG_DETACHED_FLAG, 0, NULL, nullptr, nullptr); if (!hMsg) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: CryptMsgOpenToDecode() failed"); return false; } // Update the message with the encoded header blob. if (!CryptMsgUpdate(hMsg, aSignature.data(), aSignature.size(), TRUE)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature, CryptMsgUpdate() for the header failed: " << WindowsErrorString(GetLastError())); return false; } // Update the message with the content blob. for (const auto& rByteRange : aByteRanges) { rStream.Seek(rByteRange.first); const int nChunkLen = 4096; std::vector aBuffer(nChunkLen); for (size_t nByte = 0; nByte < rByteRange.second;) { size_t nRemainingSize = rByteRange.second - nByte; if (nRemainingSize < nChunkLen) { rStream.ReadBytes(aBuffer.data(), nRemainingSize); if (!CryptMsgUpdate(hMsg, aBuffer.data(), nRemainingSize, FALSE)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature, CryptMsgUpdate() for the content failed: " << WindowsErrorString(GetLastError())); return false; } nByte = rByteRange.second; } else { rStream.ReadBytes(aBuffer.data(), nChunkLen); if (!CryptMsgUpdate(hMsg, aBuffer.data(), nChunkLen, FALSE)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature, CryptMsgUpdate() for the content failed: " << WindowsErrorString(GetLastError())); return false; } nByte += nChunkLen; } } } if (!CryptMsgUpdate(hMsg, nullptr, 0, TRUE)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature, CryptMsgUpdate() for the last content failed: " << WindowsErrorString(GetLastError())); return false; } // Get the CRYPT_ALGORITHM_IDENTIFIER from the message. DWORD nDigestID = 0; if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_HASH_ALGORITHM_PARAM, 0, nullptr, &nDigestID)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: CryptMsgGetParam() failed: " << WindowsErrorString(GetLastError())); return false; } std::unique_ptr pDigestBytes(new BYTE[nDigestID]); if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_HASH_ALGORITHM_PARAM, 0, pDigestBytes.get(), &nDigestID)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: CryptMsgGetParam() failed: " << WindowsErrorString(GetLastError())); return false; } auto pDigestID = reinterpret_cast(pDigestBytes.get()); if (OString(szOID_NIST_sha256) == pDigestID->pszObjId) rInformation.nDigestID = xml::crypto::DigestID::SHA256; else if (OString(szOID_RSA_SHA1RSA) == pDigestID->pszObjId || OString(szOID_OIWSEC_sha1) == pDigestID->pszObjId) rInformation.nDigestID = xml::crypto::DigestID::SHA1; else // Don't error out here, we can still verify the message digest correctly, just the digest ID won't be set. SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: unhandled algorithm identifier '"<pszObjId<<"'"); // Get the signer CERT_INFO from the message. DWORD nSignerCertInfo = 0; if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_CERT_INFO_PARAM, 0, nullptr, &nSignerCertInfo)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: CryptMsgGetParam() failed"); return false; } std::unique_ptr pSignerCertInfoBuf(new BYTE[nSignerCertInfo]); if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_CERT_INFO_PARAM, 0, pSignerCertInfoBuf.get(), &nSignerCertInfo)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: CryptMsgGetParam() failed"); return false; } PCERT_INFO pSignerCertInfo = reinterpret_cast(pSignerCertInfoBuf.get()); // Open a certificate store in memory using CERT_STORE_PROV_MSG, which // initializes it with the certificates from the message. HCERTSTORE hStoreHandle = CertOpenStore(CERT_STORE_PROV_MSG, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, NULL, 0, hMsg); if (!hStoreHandle) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: CertOpenStore() failed"); return false; } // Find the signer's certificate in the store. PCCERT_CONTEXT pSignerCertContext = CertGetSubjectCertificateFromStore(hStoreHandle, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, pSignerCertInfo); if (!pSignerCertContext) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: CertGetSubjectCertificateFromStore() failed"); return false; } else { // Write rInformation.ouX509Certificate. uno::Sequence aDerCert(pSignerCertContext->cbCertEncoded); for (size_t i = 0; i < pSignerCertContext->cbCertEncoded; ++i) aDerCert[i] = pSignerCertContext->pbCertEncoded[i]; OUStringBuffer aBuffer; sax::Converter::encodeBase64(aBuffer, aDerCert); rInformation.ouX509Certificate = aBuffer.makeStringAndClear(); } if (bNonDetached) { // Not a detached signature. DWORD nContentParam = 0; if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, nullptr, &nContentParam)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: CryptMsgGetParam() failed"); return false; } std::vector aContentParam(nContentParam); if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, aContentParam.data(), &nContentParam)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: CryptMsgGetParam() failed"); return false; } if (VerifyNonDetachedSignature(rStream, aByteRanges, aContentParam)) rInformation.nStatus = xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED; } else { // Detached, the usual case. // Use the CERT_INFO from the signer certificate to verify the signature. if (CryptMsgControl(hMsg, 0, CMSG_CTRL_VERIFY_SIGNATURE, pSignerCertContext->pCertInfo)) rInformation.nStatus = xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED; } // Check if we have a signing certificate attribute. DWORD nSignedAttributes = 0; if (CryptMsgGetParam(hMsg, CMSG_SIGNER_AUTH_ATTR_PARAM, 0, nullptr, &nSignedAttributes)) { std::unique_ptr pSignedAttributesBuf(new BYTE[nSignedAttributes]); if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_AUTH_ATTR_PARAM, 0, pSignedAttributesBuf.get(), &nSignedAttributes)) { SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: CryptMsgGetParam() failed"); return false; } auto pSignedAttributes = reinterpret_cast(pSignedAttributesBuf.get()); for (size_t nAttr = 0; nAttr < pSignedAttributes->cAttr; ++nAttr) { CRYPT_ATTRIBUTE& rAttr = pSignedAttributes->rgAttr[nAttr]; /* * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::= * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) * smime(16) id-aa(2) 47 } */ OString aOid("1.2.840.113549.1.9.16.2.47"); if (aOid == rAttr.pszObjId) { rInformation.bHasSigningCertificate = true; break; } } } CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_FORCE_FLAG); CryptMsgClose(hMsg); return true; #else // Not implemented. (void)rStream; (void)rInformation; return false; #endif } PDFCommentElement::PDFCommentElement(PDFDocument& rDoc) : m_rDoc(rDoc) { } bool PDFCommentElement::Read(SvStream& rStream) { // Read from (including) the % char till (excluding) the end of the line/stream. OStringBuffer aBuf; char ch; rStream.ReadChar(ch); while (true) { if (ch == '\n' || ch == '\r' || rStream.IsEof()) { m_aComment = aBuf.makeStringAndClear(); if (m_aComment.startsWith("%%EOF")) m_rDoc.PushBackEOF(rStream.Tell()); SAL_INFO("xmlsecurity.pdfio", "PDFCommentElement::Read: m_aComment is '" << m_aComment << "'"); return true; } aBuf.append(ch); rStream.ReadChar(ch); } return false; } PDFNumberElement::PDFNumberElement() = default; bool PDFNumberElement::Read(SvStream& rStream) { OStringBuffer aBuf; m_nOffset = rStream.Tell(); char ch; rStream.ReadChar(ch); if (!isdigit(ch) && ch != '-' && ch != '.') { rStream.SeekRel(-1); return false; } while (!rStream.IsEof()) { if (!isdigit(ch) && ch != '-' && ch != '.') { rStream.SeekRel(-1); m_nLength = rStream.Tell() - m_nOffset; m_fValue = aBuf.makeStringAndClear().toDouble(); SAL_INFO("xmlsecurity.pdfio", "PDFNumberElement::Read: m_fValue is '" << m_fValue << "'"); return true; } aBuf.append(ch); rStream.ReadChar(ch); } return false; } sal_uInt64 PDFNumberElement::GetLocation() const { return m_nOffset; } sal_uInt64 PDFNumberElement::GetLength() const { return m_nLength; } PDFBooleanElement::PDFBooleanElement(bool /*bValue*/) { } bool PDFBooleanElement::Read(SvStream& /*rStream*/) { return true; } bool PDFNullElement::Read(SvStream& /*rStream*/) { return true; } bool PDFHexStringElement::Read(SvStream& rStream) { char ch; rStream.ReadChar(ch); if (ch != '<') { SAL_INFO("xmlsecurity.pdfio", "PDFHexStringElement::Read: expected '<' as first character"); return false; } rStream.ReadChar(ch); OStringBuffer aBuf; while (!rStream.IsEof()) { if (ch == '>') { m_aValue = aBuf.makeStringAndClear(); SAL_INFO("xmlsecurity.pdfio", "PDFHexStringElement::Read: m_aValue length is " << m_aValue.getLength()); return true; } aBuf.append(ch); rStream.ReadChar(ch); } return false; } const OString& PDFHexStringElement::GetValue() const { return m_aValue; } bool PDFLiteralStringElement::Read(SvStream& rStream) { char nPrevCh = 0; char ch = 0; rStream.ReadChar(ch); if (ch != '(') { SAL_INFO("xmlsecurity.pdfio", "PDFHexStringElement::Read: expected '(' as first character"); return false; } nPrevCh = ch; rStream.ReadChar(ch); OStringBuffer aBuf; while (!rStream.IsEof()) { if (ch == ')' && nPrevCh != '\\') { m_aValue = aBuf.makeStringAndClear(); SAL_INFO("xmlsecurity.pdfio", "PDFLiteralStringElement::Read: m_aValue is '" << m_aValue << "'"); return true; } aBuf.append(ch); nPrevCh = ch; rStream.ReadChar(ch); } return false; } const OString& PDFLiteralStringElement::GetValue() const { return m_aValue; } PDFTrailerElement::PDFTrailerElement(PDFDocument& rDoc) : m_rDoc(rDoc) { } bool PDFTrailerElement::Read(SvStream& /*rStream*/) { return true; } PDFElement* PDFTrailerElement::Lookup(const OString& rDictionaryKey) { if (m_aDictionary.empty()) PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary); return PDFDictionaryElement::Lookup(m_aDictionary, rDictionaryKey); } double PDFNumberElement::GetValue() const { return m_fValue; } PDFObjectElement::PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue) : m_rDoc(rDoc), m_fObjectValue(fObjectValue), m_fGenerationValue(fGenerationValue), m_nDictionaryOffset(0), m_nDictionaryLength(0), m_pDictionaryElement(nullptr), m_pArrayElement(nullptr), m_pStreamElement(nullptr) { } bool PDFObjectElement::Read(SvStream& /*rStream*/) { SAL_INFO("xmlsecurity.pdfio", "PDFObjectElement::Read: " << m_fObjectValue << " " << m_fGenerationValue << " obj"); return true; } PDFDictionaryElement::PDFDictionaryElement() = default; size_t PDFDictionaryElement::Parse(const std::vector< std::unique_ptr >& rElements, PDFElement* pThis, std::map& rDictionary) { // The index of last parsed element, in case of nested dictionaries. size_t nRet = 0; if (!rDictionary.empty()) return nRet; auto pThisObject = dynamic_cast(pThis); // This is set to non-nullptr here for nested dictionaries only. auto pThisDictionary = dynamic_cast(pThis); // Find out where the dictionary for this object starts. size_t nIndex = 0; for (size_t i = 0; i < rElements.size(); ++i) { if (rElements[i].get() == pThis) { nIndex = i; break; } } OString aName; sal_uInt64 nNameOffset = 0; std::vector aNumbers; // The array value we're in -- if any. PDFArrayElement* pArray = nullptr; sal_uInt64 nDictionaryOffset = 0; int nDictionaryDepth = 0; for (size_t i = nIndex; i < rElements.size(); ++i) { // Dictionary tokens can be nested, track enter/leave. if (auto pDictionary = dynamic_cast(rElements[i].get())) { if (++nDictionaryDepth == 1) { // First dictionary start, track start offset. nDictionaryOffset = pDictionary->m_nLocation; if (pThisObject) { pThisObject->SetDictionary(pDictionary); pThisDictionary = pDictionary; pThisObject->SetDictionaryOffset(nDictionaryOffset); } } else { // Nested dictionary. nIndex = PDFDictionaryElement::Parse(rElements, pDictionary, pDictionary->m_aItems); rDictionary[aName] = pDictionary; aName.clear(); } } if (auto pEndDictionary = dynamic_cast(rElements[i].get())) { if (--nDictionaryDepth == 0) { // Last dictionary end, track length and stop parsing. if (pThisObject) pThisObject->SetDictionaryLength(pEndDictionary->GetLocation() - nDictionaryOffset); nRet = nIndex; break; } } auto pName = dynamic_cast(rElements[i].get()); if (pName) { if (!aNumbers.empty()) { PDFNumberElement* pNumber = aNumbers.back(); rDictionary[aName] = pNumber; if (pThisDictionary) { pThisDictionary->SetKeyOffset(aName, nNameOffset); pThisDictionary->SetKeyValueLength(aName, pNumber->GetLocation() + pNumber->GetLength() - nNameOffset); } aName.clear(); aNumbers.clear(); } if (aName.isEmpty()) { // Remember key. aName = pName->GetValue(); nNameOffset = pName->GetLocation(); } else { if (pArray) pArray->PushBack(pName); else { // Name-name key-value. rDictionary[aName] = pName; if (pThisDictionary) { pThisDictionary->SetKeyOffset(aName, nNameOffset); pThisDictionary->SetKeyValueLength(aName, pName->GetLocation() + pName->GetLength() - nNameOffset); } aName.clear(); } } continue; } auto pArr = dynamic_cast(rElements[i].get()); if (pArr) { pArray = pArr; continue; } auto pEndArr = dynamic_cast(rElements[i].get()); if (pArray && pEndArr) { if (!aNumbers.empty()) { for (auto& pNumber : aNumbers) pArray->PushBack(pNumber); aNumbers.clear(); } rDictionary[aName] = pArray; if (pThisDictionary) { pThisDictionary->SetKeyOffset(aName, nNameOffset); // Include the ending ']' in the length of the key - (array)value pair length. pThisDictionary->SetKeyValueLength(aName, pEndArr->GetOffset() - nNameOffset + 1); } aName.clear(); pArray = nullptr; continue; } auto pReference = dynamic_cast(rElements[i].get()); if (pReference) { if (!pArray) { rDictionary[aName] = pReference; if (pThisDictionary) pThisDictionary->SetKeyOffset(aName, nNameOffset); aName.clear(); } else { pArray->PushBack(pReference); } aNumbers.clear(); continue; } auto pLiteralString = dynamic_cast(rElements[i].get()); if (pLiteralString) { rDictionary[aName] = pLiteralString; if (pThisDictionary) pThisDictionary->SetKeyOffset(aName, nNameOffset); aName.clear(); continue; } auto pBoolean = dynamic_cast(rElements[i].get()); if (pBoolean) { rDictionary[aName] = pBoolean; if (pThisDictionary) pThisDictionary->SetKeyOffset(aName, nNameOffset); aName.clear(); continue; } auto pHexString = dynamic_cast(rElements[i].get()); if (pHexString) { if (!pArray) { rDictionary[aName] = pHexString; if (pThisDictionary) pThisDictionary->SetKeyOffset(aName, nNameOffset); aName.clear(); } else { pArray->PushBack(pHexString); } continue; } if (dynamic_cast(rElements[i].get())) break; // Just remember this, so that in case it's not a reference parameter, // we can handle it later. auto pNumber = dynamic_cast(rElements[i].get()); if (pNumber) aNumbers.push_back(pNumber); } if (!aNumbers.empty()) { rDictionary[aName] = aNumbers.back(); if (pThisDictionary) pThisDictionary->SetKeyOffset(aName, nNameOffset); aName.clear(); aNumbers.clear(); } return nRet; } PDFElement* PDFDictionaryElement::Lookup(const std::map& rDictionary, const OString& rKey) { auto it = rDictionary.find(rKey); if (it == rDictionary.end()) return nullptr; return it->second; } PDFObjectElement* PDFDictionaryElement::LookupObject(const OString& rDictionaryKey) { auto pKey = dynamic_cast(PDFDictionaryElement::Lookup(m_aItems, rDictionaryKey)); if (!pKey) { SAL_WARN("xmlsecurity.pdfio", "PDFDictionaryElement::LookupObject: no such key with reference value: " << rDictionaryKey); return nullptr; } return pKey->LookupObject(); } PDFElement* PDFObjectElement::Lookup(const OString& rDictionaryKey) { if (m_aDictionary.empty()) { if (!m_aElements.empty()) // This is a stored object in an object stream. PDFDictionaryElement::Parse(m_aElements, this, m_aDictionary); else // Normal object: elements are stored as members of the document itself. PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary); } return PDFDictionaryElement::Lookup(m_aDictionary, rDictionaryKey); } PDFObjectElement* PDFObjectElement::LookupObject(const OString& rDictionaryKey) { auto pKey = dynamic_cast(Lookup(rDictionaryKey)); if (!pKey) { SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::LookupObject: no such key with reference value: " << rDictionaryKey); return nullptr; } return pKey->LookupObject(); } double PDFObjectElement::GetObjectValue() const { return m_fObjectValue; } void PDFObjectElement::SetDictionaryOffset(sal_uInt64 nDictionaryOffset) { m_nDictionaryOffset = nDictionaryOffset; } sal_uInt64 PDFObjectElement::GetDictionaryOffset() { if (m_aDictionary.empty()) PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary); return m_nDictionaryOffset; } void PDFDictionaryElement::SetKeyOffset(const OString& rKey, sal_uInt64 nOffset) { m_aDictionaryKeyOffset[rKey] = nOffset; } void PDFDictionaryElement::SetKeyValueLength(const OString& rKey, sal_uInt64 nLength) { m_aDictionaryKeyValueLength[rKey] = nLength; } sal_uInt64 PDFDictionaryElement::GetKeyOffset(const OString& rKey) const { auto it = m_aDictionaryKeyOffset.find(rKey); if (it == m_aDictionaryKeyOffset.end()) return 0; return it->second; } sal_uInt64 PDFDictionaryElement::GetKeyValueLength(const OString& rKey) const { auto it = m_aDictionaryKeyValueLength.find(rKey); if (it == m_aDictionaryKeyValueLength.end()) return 0; return it->second; } const std::map& PDFDictionaryElement::GetItems() const { return m_aItems; } void PDFObjectElement::SetDictionaryLength(sal_uInt64 nDictionaryLength) { m_nDictionaryLength = nDictionaryLength; } sal_uInt64 PDFObjectElement::GetDictionaryLength() { if (m_aDictionary.empty()) PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary); return m_nDictionaryLength; } PDFDictionaryElement* PDFObjectElement::GetDictionary() const { return m_pDictionaryElement; } void PDFObjectElement::SetDictionary(PDFDictionaryElement* pDictionaryElement) { m_pDictionaryElement = pDictionaryElement; } void PDFObjectElement::SetArray(PDFArrayElement* pArrayElement) { m_pArrayElement = pArrayElement; } void PDFObjectElement::SetStream(PDFStreamElement* pStreamElement) { m_pStreamElement = pStreamElement; } PDFArrayElement* PDFObjectElement::GetArray() const { return m_pArrayElement; } void PDFObjectElement::ParseStoredObjects() { if (!m_pStreamElement) { SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: no stream"); return; } auto pType = dynamic_cast(Lookup("Type")); if (!pType || pType->GetValue() != "ObjStm") { if (!pType) SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: missing unexpected type"); else SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: unexpected type: " << pType->GetValue()); return; } auto pFilter = dynamic_cast(Lookup("Filter")); if (!pFilter || pFilter->GetValue() != "FlateDecode") { if (!pFilter) SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: missing filter"); else SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: unexpected filter: " << pFilter->GetValue()); return; } auto pFirst = dynamic_cast(Lookup("First")); if (!pFirst) { SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: no First"); return; } auto pN = dynamic_cast(Lookup("N")); if (!pN) { SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: no N"); return; } size_t nN = pN->GetValue(); auto pLength = dynamic_cast(Lookup("Length")); if (!pLength) { SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: no length"); return; } size_t nLength = pLength->GetValue(); // Read and decompress it. SvMemoryStream& rEditBuffer = m_rDoc.GetEditBuffer(); rEditBuffer.Seek(m_pStreamElement->GetOffset()); std::vector aBuf(nLength); rEditBuffer.ReadBytes(aBuf.data(), aBuf.size()); SvMemoryStream aSource(aBuf.data(), aBuf.size(), StreamMode::READ); SvMemoryStream aStream; ZCodec aZCodec; aZCodec.BeginCompression(); aZCodec.Decompress(aSource, aStream); if (!aZCodec.EndCompression()) { SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: decompression failed"); return; } aStream.Seek(STREAM_SEEK_TO_END); nLength = aStream.Tell(); aStream.Seek(0); std::vector aObjNums; std::vector aOffsets; std::vector aLengths; // First iterate over and find out the lengths. for (size_t nObject = 0; nObject < nN; ++nObject) { PDFNumberElement aObjNum; if (!aObjNum.Read(aStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: failed to read object number"); return; } aObjNums.push_back(aObjNum.GetValue()); PDFDocument::SkipWhitespace(aStream); PDFNumberElement aByteOffset; if (!aByteOffset.Read(aStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: failed to read byte offset"); return; } aOffsets.push_back(pFirst->GetValue() + aByteOffset.GetValue()); if (aOffsets.size() > 1) aLengths.push_back(aOffsets.back() - aOffsets[aOffsets.size() - 2]); if (nObject + 1 == nN) aLengths.push_back(nLength - aOffsets.back()); PDFDocument::SkipWhitespace(aStream); } // Now create streams with the proper length and tokenize the data. for (size_t nObject = 0; nObject < nN; ++nObject) { size_t nObjNum = aObjNums[nObject]; size_t nOffset = aOffsets[nObject]; size_t nLen = aLengths[nObject]; aStream.Seek(nOffset); m_aStoredElements.push_back(o3tl::make_unique(m_rDoc, nObjNum, 0)); PDFObjectElement* pStored = m_aStoredElements.back().get(); aBuf.clear(); aBuf.resize(nLen); aStream.ReadBytes(aBuf.data(), aBuf.size()); SvMemoryStream aStoredStream(aBuf.data(), aBuf.size(), StreamMode::READ); m_rDoc.Tokenize(aStoredStream, TokenizeMode::STORED_OBJECT, pStored->GetStoredElements(), pStored); // This is how references know the object is stored inside this object stream. m_rDoc.SetIDObject(nObjNum, pStored); // Store the stream of the object in the object stream for later use. std::unique_ptr pStreamBuffer(new SvMemoryStream()); aStoredStream.Seek(0); pStreamBuffer->WriteStream(aStoredStream); pStored->SetStreamBuffer(pStreamBuffer); } } std::vector< std::unique_ptr >& PDFObjectElement::GetStoredElements() { return m_aElements; } SvMemoryStream* PDFObjectElement::GetStreamBuffer() const { return m_pStreamBuffer.get(); } void PDFObjectElement::SetStreamBuffer(std::unique_ptr& pStreamBuffer) { m_pStreamBuffer = std::move(pStreamBuffer); } PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue) : m_rDoc(rDoc), m_fObjectValue(fObjectValue), m_fGenerationValue(fGenerationValue) { } bool PDFReferenceElement::Read(SvStream& /*rStream*/) { SAL_INFO("xmlsecurity.pdfio", "PDFReferenceElement::Read: " << m_fObjectValue << " " << m_fGenerationValue << " R"); return true; } double PDFReferenceElement::LookupNumber(SvStream& rStream) const { size_t nOffset = m_rDoc.GetObjectOffset(m_fObjectValue); if (nOffset == 0) { SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: found no offset for object #" << m_fObjectValue); return 0; } sal_uInt64 nOrigPos = rStream.Tell(); comphelper::ScopeGuard g([&]() { rStream.Seek(nOrigPos); }); rStream.Seek(nOffset); { PDFDocument::SkipWhitespace(rStream); PDFNumberElement aNumber; bool bRet = aNumber.Read(rStream); if (!bRet || aNumber.GetValue() != m_fObjectValue) { SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: offset points to not matching object"); return 0; } } { PDFDocument::SkipWhitespace(rStream); PDFNumberElement aNumber; bool bRet = aNumber.Read(rStream); if (!bRet || aNumber.GetValue() != m_fGenerationValue) { SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: offset points to not matching generation"); return 0; } } { PDFDocument::SkipWhitespace(rStream); OString aKeyword = PDFDocument::ReadKeyword(rStream); if (aKeyword != "obj") { SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: offset doesn't point to an obj keyword"); return 0; } } PDFDocument::SkipWhitespace(rStream); PDFNumberElement aNumber; if (!aNumber.Read(rStream)) { SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: failed to read referenced number"); return 0; } return aNumber.GetValue(); } PDFObjectElement* PDFReferenceElement::LookupObject() { return m_rDoc.LookupObject(m_fObjectValue); } PDFObjectElement* PDFDocument::LookupObject(size_t nObjectNumber) { auto itIDObjects = m_aIDObjects.find(nObjectNumber); if (itIDObjects != m_aIDObjects.end()) return itIDObjects->second; SAL_WARN("xmlsecurity.pdfio", "PDFDocument::LookupObject: can't find obj " << nObjectNumber); return nullptr; } SvMemoryStream& PDFDocument::GetEditBuffer() { return m_aEditBuffer; } int PDFReferenceElement::GetObjectValue() const { return m_fObjectValue; } int PDFReferenceElement::GetGenerationValue() const { return m_fGenerationValue; } bool PDFDictionaryElement::Read(SvStream& rStream) { char ch; rStream.ReadChar(ch); if (ch != '<') { SAL_WARN("xmlsecurity.pdfio", "PDFDictionaryElement::Read: unexpected character: " << ch); return false; } if (rStream.IsEof()) { SAL_WARN("xmlsecurity.pdfio", "PDFDictionaryElement::Read: unexpected end of file"); return false; } rStream.ReadChar(ch); if (ch != '<') { SAL_WARN("xmlsecurity.pdfio", "PDFDictionaryElement::Read: unexpected character: " << ch); return false; } m_nLocation = rStream.Tell(); SAL_INFO("xmlsecurity.pdfio", "PDFDictionaryElement::Read: '<<'"); return true; } PDFEndDictionaryElement::PDFEndDictionaryElement() = default; sal_uInt64 PDFEndDictionaryElement::GetLocation() const { return m_nLocation; } bool PDFEndDictionaryElement::Read(SvStream& rStream) { m_nLocation = rStream.Tell(); char ch; rStream.ReadChar(ch); if (ch != '>') { SAL_WARN("xmlsecurity.pdfio", "PDFEndDictionaryElement::Read: unexpected character: " << ch); return false; } if (rStream.IsEof()) { SAL_WARN("xmlsecurity.pdfio", "PDFEndDictionaryElement::Read: unexpected end of file"); return false; } rStream.ReadChar(ch); if (ch != '>') { SAL_WARN("xmlsecurity.pdfio", "PDFEndDictionaryElement::Read: unexpected character: " << ch); return false; } SAL_INFO("xmlsecurity.pdfio", "PDFEndDictionaryElement::Read: '>>'"); return true; } PDFNameElement::PDFNameElement() : m_nLocation(0), m_nLength(0) { } bool PDFNameElement::Read(SvStream& rStream) { char ch; rStream.ReadChar(ch); if (ch != '/') { SAL_WARN("xmlsecurity.pdfio", "PDFNameElement::Read: unexpected character: " << ch); return false; } m_nLocation = rStream.Tell(); if (rStream.IsEof()) { SAL_WARN("xmlsecurity.pdfio", "PDFNameElement::Read: unexpected end of file"); return false; } // Read till the first white-space. OStringBuffer aBuf; rStream.ReadChar(ch); while (!rStream.IsEof()) { if (isspace(ch) || ch == '/' || ch == '[' || ch == ']' || ch == '<' || ch == '>' || ch == '(') { rStream.SeekRel(-1); m_aValue = aBuf.makeStringAndClear(); SAL_INFO("xmlsecurity.pdfio", "PDFNameElement::Read: m_aValue is '" << m_aValue << "'"); return true; } aBuf.append(ch); rStream.ReadChar(ch); } return false; } const OString& PDFNameElement::GetValue() const { return m_aValue; } sal_uInt64 PDFNameElement::GetLocation() const { return m_nLocation; } sal_uInt64 PDFNameElement::GetLength() const { return m_nLength; } PDFStreamElement::PDFStreamElement(size_t nLength) : m_nLength(nLength), m_nOffset(0) { } bool PDFStreamElement::Read(SvStream& rStream) { SAL_INFO("xmlsecurity.pdfio", "PDFStreamElement::Read: length is " << m_nLength); m_nOffset = rStream.Tell(); rStream.SeekRel(m_nLength); return rStream.good(); } sal_uInt64 PDFStreamElement::GetOffset() const { return m_nOffset; } bool PDFEndStreamElement::Read(SvStream& /*rStream*/) { return true; } bool PDFEndObjectElement::Read(SvStream& /*rStream*/) { return true; } PDFArrayElement::PDFArrayElement() = default; bool PDFArrayElement::Read(SvStream& rStream) { char ch; rStream.ReadChar(ch); if (ch != '[') { SAL_WARN("xmlsecurity.pdfio", "PDFArrayElement::Read: unexpected character: " << ch); return false; } m_nOffset = rStream.Tell(); SAL_INFO("xmlsecurity.pdfio", "PDFArrayElement::Read: '['"); return true; } void PDFArrayElement::PushBack(PDFElement* pElement) { m_aElements.push_back(pElement); } const std::vector& PDFArrayElement::GetElements() { return m_aElements; } PDFEndArrayElement::PDFEndArrayElement() = default; bool PDFEndArrayElement::Read(SvStream& rStream) { m_nOffset = rStream.Tell(); char ch; rStream.ReadChar(ch); if (ch != ']') { SAL_WARN("xmlsecurity.pdfio", "PDFEndArrayElement::Read: unexpected character: " << ch); return false; } SAL_INFO("xmlsecurity.pdfio", "PDFEndArrayElement::Read: ']'"); return true; } sal_uInt64 PDFEndArrayElement::GetOffset() const { return m_nOffset; } } // namespace pdfio } // namespace xmlsecurity /* vim:set shiftwidth=4 softtabstop=4 expandtab: */