summaryrefslogtreecommitdiff
path: root/xmlsecurity
diff options
context:
space:
mode:
authorMiklos Vajna <vmiklos@collabora.co.uk>2016-10-18 12:27:27 +0200
committerMiklos Vajna <vmiklos@collabora.co.uk>2016-10-18 13:00:51 +0000
commita747ed3ca4f5c9f7478aa723044be844e7581b64 (patch)
tree5a464720cb290e4a3ec642758aa209eca612e563 /xmlsecurity
parent38dadfbc3df41edd3719df0eeda7e8102fcc5818 (diff)
xmlsecurity: add signing stub in pdfverify
It does (almost?) everything to add a valid signature, except the actual signature blob. Change-Id: I727770caef2335473a8dba29d60daf5a049129b9 Reviewed-on: https://gerrit.libreoffice.org/30001 Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk> Tested-by: Jenkins <ci@libreoffice.org>
Diffstat (limited to 'xmlsecurity')
-rw-r--r--xmlsecurity/source/pdfio/pdfdocument.cxx226
1 files changed, 214 insertions, 12 deletions
diff --git a/xmlsecurity/source/pdfio/pdfdocument.cxx b/xmlsecurity/source/pdfio/pdfdocument.cxx
index 0ab997657640..95660839ec28 100644
--- a/xmlsecurity/source/pdfio/pdfdocument.cxx
+++ b/xmlsecurity/source/pdfio/pdfdocument.cxx
@@ -17,6 +17,7 @@
#include <comphelper/processfactory.hxx>
#include <comphelper/scopeguard.hxx>
+#include <comphelper/string.hxx>
#include <rtl/strbuf.hxx>
#include <rtl/string.hxx>
#include <sal/log.hxx>
@@ -71,6 +72,10 @@ class PDFObjectElement : public PDFElement
double m_fObjectValue;
double m_fGenerationValue;
std::map<OString, PDFElement*> m_aDictionary;
+ /// Position after the '<<' token.
+ sal_uInt64 m_nDictionaryOffset;
+ /// Length of the dictionary buffer till (before) the '<<' token.
+ sal_uInt64 m_nDictionaryLength;
public:
PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue);
@@ -79,12 +84,20 @@ public:
PDFObjectElement* LookupObject(const OString& rDictionaryKey);
double GetObjectValue() const;
double GetGenerationValue() const;
+ void SetDictionaryOffset(sal_uInt64 nDictionaryOffset);
+ sal_uInt64 GetDictionaryOffset();
+ void SetDictionaryLength(sal_uInt64 nDictionaryLength);
+ sal_uInt64 GetDictionaryLength();
};
/// Dictionary object: a set key-value pairs.
class PDFDictionaryElement : public PDFElement
{
+ /// Offset after the '>>' token.
+ sal_uInt64 m_nLocation;
+
public:
+ PDFDictionaryElement();
bool Read(SvStream& rStream) override;
static void Parse(const std::vector< std::unique_ptr<PDFElement> >& rElements, PDFElement* pThis, std::map<OString, PDFElement*>& rDictionary);
@@ -94,8 +107,12 @@ public:
/// End of a dictionary: '>>'.
class PDFEndDictionaryElement : public PDFElement
{
+ /// Offset before the '>>' token.
+ sal_uInt64 m_nLocation;
public:
+ PDFEndDictionaryElement();
bool Read(SvStream& rStream) override;
+ sal_uInt64 GetLocation() const;
};
/// Name object: a key string.
@@ -219,14 +236,124 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& /*xCertific
{
m_aEditBuffer.WriteCharPtr("\n");
+ // Write signature object.
+ sal_Int32 nSignatureId = m_aXRef.size();
+ sal_uInt64 nSignatureOffset = m_aEditBuffer.Tell();
+ m_aXRef.push_back(nSignatureOffset);
+ OStringBuffer aSigBuffer;
+ aSigBuffer.append(nSignatureId);
+ aSigBuffer.append(" 0 obj\n");
+ aSigBuffer.append("<</Contents <");
+ sal_Int64 nSignatureContentOffset = nSignatureOffset + aSigBuffer.getLength();
+ // Reserve space for the PKCS#7 object.
+ const int MAX_SIGNATURE_CONTENT_LENGTH = 50000;
+ OStringBuffer aContentFiller(MAX_SIGNATURE_CONTENT_LENGTH);
+ comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0');
+ aSigBuffer.append(aContentFiller.makeStringAndClear());
+ aSigBuffer.append(">\n/Type/Sig/SubFilter/adbe.pkcs7.detached");
+ // 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(nSignatureContentOffset - 1);
+ aSigBuffer.append(" ");
+ aSigBuffer.append(nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
+ aSigBuffer.append(" ");
+ sal_uInt64 nSignatureLastByteRangeOffset = nSignatureOffset + 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");
+ aSigBuffer.append(" >>\nendobj\n\n");
+ m_aEditBuffer.WriteOString(aSigBuffer.toString());
+
// Write appearance object.
- size_t nAppearanceId = m_aXRef.size();
+ sal_Int32 nAppearanceId = m_aXRef.size();
m_aXRef.push_back(m_aEditBuffer.Tell());
m_aEditBuffer.WriteUInt32AsString(nAppearanceId);
m_aEditBuffer.WriteCharPtr(" 0 obj\n");
m_aEditBuffer.WriteCharPtr("<</Type/XObject\n/Subtype/Form\n");
m_aEditBuffer.WriteCharPtr("/BBox[0 0 0 0]\n/Length 0\n>>\n");
- m_aEditBuffer.WriteCharPtr("stream\n\nendstream\nendobj\n");
+ m_aEditBuffer.WriteCharPtr("stream\n\nendstream\nendobj\n\n");
+
+ // Write the Annot object, references nSignatureId and nAppearanceId.
+ sal_Int32 nAnnotId = m_aXRef.size();
+ m_aXRef.push_back(m_aEditBuffer.Tell());
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+ m_aEditBuffer.WriteCharPtr("<</Type/Annot/Subtype/Widget/F 132\n");
+ m_aEditBuffer.WriteCharPtr("/Rect[0 0 0 0]\n");
+ m_aEditBuffer.WriteCharPtr("/FT/Sig\n");
+ std::vector<PDFObjectElement*> aPages = GetPages();
+ if (aPages.empty())
+ {
+ SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: found no pages");
+ return false;
+ }
+ PDFObjectElement* pFirstPage = aPages[0];
+ m_aEditBuffer.WriteCharPtr("/P ");
+ m_aEditBuffer.WriteUInt32AsString(pFirstPage->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" 0 R\n");
+ m_aEditBuffer.WriteCharPtr("/T(Signature1)\n");
+ m_aEditBuffer.WriteCharPtr("/V ");
+ m_aEditBuffer.WriteUInt32AsString(nSignatureId);
+ m_aEditBuffer.WriteCharPtr(" 0 R\n");
+ m_aEditBuffer.WriteCharPtr("/DV ");
+ m_aEditBuffer.WriteUInt32AsString(nSignatureId);
+ m_aEditBuffer.WriteCharPtr(" 0 R\n");
+ m_aEditBuffer.WriteCharPtr("/AP<<\n/N ");
+ m_aEditBuffer.WriteUInt32AsString(nAppearanceId);
+ m_aEditBuffer.WriteCharPtr(" 0 R\n>>\n");
+ m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n");
+
+ // Write the updated first page object, references nAnnotId.
+ sal_uInt32 nFirstPageId = pFirstPage->GetObjectValue();
+ if (nFirstPageId >= m_aXRef.size())
+ {
+ SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid first page obj id");
+ return false;
+ }
+ m_aXRef[nFirstPageId] = m_aEditBuffer.Tell();
+ m_aEditBuffer.WriteUInt32AsString(nFirstPageId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+ m_aEditBuffer.WriteCharPtr("<<");
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + pFirstPage->GetDictionaryOffset(), pFirstPage->GetDictionaryLength());
+ m_aEditBuffer.WriteCharPtr("/Annots[");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R]");
+ m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n");
+
+ // Write the updated Catalog object, references nAnnotId.
+ auto pRoot = dynamic_cast<PDFReferenceElement*>(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;
+ }
+ m_aXRef[nCatalogId] = m_aEditBuffer.Tell();
+ m_aEditBuffer.WriteUInt32AsString(nCatalogId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+ m_aEditBuffer.WriteCharPtr("<<");
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + pCatalog->GetDictionaryOffset(), pCatalog->GetDictionaryLength());
+ m_aEditBuffer.WriteCharPtr("/AcroForm<</Fields[\n");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R\n]/SigFlags 3>>\n");
+ m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n");
// Write the xref table.
sal_uInt64 nXRefOffset = m_aEditBuffer.Tell();
@@ -247,12 +374,6 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& /*xCertific
}
// Write the trailer.
- auto pRoot = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Root"));
- if (!pRoot)
- {
- SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: trailer has no root reference");
- return false;
- }
m_aEditBuffer.WriteCharPtr("trailer\n<</Size ");
m_aEditBuffer.WriteUInt32AsString(m_aXRef.size());
m_aEditBuffer.WriteCharPtr("/Root ");
@@ -291,6 +412,17 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& /*xCertific
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());
+
return true;
}
@@ -1227,7 +1359,9 @@ double PDFNumberElement::GetValue() const
PDFObjectElement::PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue)
: m_rDoc(rDoc),
m_fObjectValue(fObjectValue),
- m_fGenerationValue(fGenerationValue)
+ m_fGenerationValue(fGenerationValue),
+ m_nDictionaryOffset(0),
+ m_nDictionaryLength(0)
{
}
@@ -1237,11 +1371,18 @@ bool PDFObjectElement::Read(SvStream& /*rStream*/)
return true;
}
+PDFDictionaryElement::PDFDictionaryElement()
+ : m_nLocation(0)
+{
+}
+
void PDFDictionaryElement::Parse(const std::vector< std::unique_ptr<PDFElement> >& rElements, PDFElement* pThis, std::map<OString, PDFElement*>& rDictionary)
{
if (!rDictionary.empty())
return;
+ auto pThisObject = dynamic_cast<PDFObjectElement*>(pThis);
+
// Find out where the dictionary for this object starts.
size_t nIndex = 0;
for (size_t i = 0; i < rElements.size(); ++i)
@@ -1257,8 +1398,33 @@ void PDFDictionaryElement::Parse(const std::vector< std::unique_ptr<PDFElement>
std::vector<PDFNumberElement*> 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<PDFDictionaryElement*>(rElements[i].get()))
+ {
+ if (++nDictionaryDepth == 1)
+ {
+ // First dictionary start, track start offset.
+ nDictionaryOffset = pDictionary->m_nLocation;
+ if (pThisObject)
+ pThisObject->SetDictionaryOffset(nDictionaryOffset);
+ }
+ }
+
+ if (auto pEndDictionary = dynamic_cast<PDFEndDictionaryElement*>(rElements[i].get()))
+ {
+ if (--nDictionaryDepth == 0)
+ {
+ // Last dictionary end, track length and stop parsing.
+ if (pThisObject)
+ pThisObject->SetDictionaryLength(pEndDictionary->GetLocation() - nDictionaryOffset);
+ break;
+ }
+ }
+
auto pName = dynamic_cast<PDFNameElement*>(rElements[i].get());
if (pName)
{
@@ -1343,9 +1509,6 @@ void PDFDictionaryElement::Parse(const std::vector< std::unique_ptr<PDFElement>
continue;
}
- if (dynamic_cast<PDFEndDictionaryElement*>(rElements[i].get()))
- break;
-
if (dynamic_cast<PDFEndObjectElement*>(rElements[i].get()))
break;
@@ -1403,6 +1566,32 @@ double PDFObjectElement::GetGenerationValue() const
return m_fGenerationValue;
}
+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 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;
+}
+
PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue)
: m_rDoc(rDoc),
m_fObjectValue(fObjectValue),
@@ -1529,13 +1718,26 @@ bool PDFDictionaryElement::Read(SvStream& rStream)
return false;
}
+ m_nLocation = rStream.Tell();
+
SAL_INFO("xmlsecurity.pdfio", "PDFDictionaryElement::Read: '<<'");
return true;
}
+PDFEndDictionaryElement::PDFEndDictionaryElement()
+ : m_nLocation(0)
+{
+}
+
+sal_uInt64 PDFEndDictionaryElement::GetLocation() const
+{
+ return m_nLocation;
+}
+
bool PDFEndDictionaryElement::Read(SvStream& rStream)
{
+ m_nLocation = rStream.Tell();
char ch;
rStream.ReadChar(ch);
if (ch != '>')