Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / xmlsecurity / source / pdfio / pdfdocument.cxx
blobfebcd2e3a1ea61e03e6e0a1f61eb8df5236a5e2b
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <pdfio/pdfdocument.hxx>
12 #include <memory>
13 #include <vector>
15 #include <rtl/string.hxx>
16 #include <rtl/ustrbuf.hxx>
17 #include <sal/log.hxx>
18 #include <sal/types.h>
20 #include <svl/sigstruct.hxx>
21 #include <svl/cryptosign.hxx>
22 #include <vcl/filter/pdfdocument.hxx>
24 using namespace com::sun::star;
26 namespace xmlsecurity
28 namespace pdfio
30 bool ValidateSignature(SvStream& rStream, vcl::filter::PDFObjectElement* pSignature,
31 SignatureInformation& rInformation, bool bLast)
33 vcl::filter::PDFObjectElement* pValue = pSignature->LookupObject("V");
34 if (!pValue)
36 SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: no value");
37 return false;
40 auto pContents = dynamic_cast<vcl::filter::PDFHexStringElement*>(pValue->Lookup("Contents"));
41 if (!pContents)
43 SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: no contents");
44 return false;
47 auto pByteRange = dynamic_cast<vcl::filter::PDFArrayElement*>(pValue->Lookup("ByteRange"));
48 if (!pByteRange || pByteRange->GetElements().size() < 2)
50 SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: no byte range or too few elements");
51 return false;
54 auto pSubFilter = dynamic_cast<vcl::filter::PDFNameElement*>(pValue->Lookup("SubFilter"));
55 const bool bNonDetached = pSubFilter && pSubFilter->GetValue() == "adbe.pkcs7.sha1";
56 if (!pSubFilter
57 || (pSubFilter->GetValue() != "adbe.pkcs7.detached" && !bNonDetached
58 && pSubFilter->GetValue() != "ETSI.CAdES.detached"))
60 if (!pSubFilter)
61 SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: missing sub-filter");
62 else
63 SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: unsupported sub-filter: '"
64 << pSubFilter->GetValue() << "'");
65 return false;
68 // Reason / comment / description is optional.
69 auto pReason = dynamic_cast<vcl::filter::PDFHexStringElement*>(pValue->Lookup("Reason"));
70 if (pReason)
72 // See appendUnicodeTextString() for the export equivalent of this.
73 std::vector<unsigned char> aReason = vcl::filter::PDFDocument::DecodeHexString(pReason);
74 OUStringBuffer aBuffer;
75 sal_uInt16 nByte = 0;
76 for (size_t i = 0; i < aReason.size(); ++i)
78 if (i % 2 == 0)
79 nByte = aReason[i];
80 else
82 sal_Unicode nUnicode;
83 nUnicode = (nByte << 8);
84 nUnicode |= aReason[i];
85 aBuffer.append(nUnicode);
89 if (!aBuffer.isEmpty())
90 rInformation.ouDescription = aBuffer.makeStringAndClear();
93 // Date: used only when the time of signing is not available in the
94 // signature.
95 auto pM = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pValue->Lookup("M"));
96 if (pM)
98 // Example: "D:20161027100104".
99 const OString& rM = pM->GetValue();
100 if (rM.startsWith("D:") && rM.getLength() >= 16)
102 rInformation.stDateTime.Year = rM.copy(2, 4).toInt32();
103 rInformation.stDateTime.Month = rM.copy(6, 2).toInt32();
104 rInformation.stDateTime.Day = rM.copy(8, 2).toInt32();
105 rInformation.stDateTime.Hours = rM.copy(10, 2).toInt32();
106 rInformation.stDateTime.Minutes = rM.copy(12, 2).toInt32();
107 rInformation.stDateTime.Seconds = rM.copy(14, 2).toInt32();
111 // Build a list of offset-length pairs, representing the signed bytes.
112 std::vector<std::pair<size_t, size_t>> aByteRanges;
113 size_t nByteRangeOffset = 0;
114 const std::vector<vcl::filter::PDFElement*>& rByteRangeElements = pByteRange->GetElements();
115 for (size_t i = 0; i < rByteRangeElements.size(); ++i)
117 auto pNumber = dynamic_cast<vcl::filter::PDFNumberElement*>(rByteRangeElements[i]);
118 if (!pNumber)
120 SAL_WARN("xmlsecurity.pdfio",
121 "ValidateSignature: signature offset and length has to be a number");
122 return false;
125 if (i % 2 == 0)
127 nByteRangeOffset = pNumber->GetValue();
128 continue;
130 size_t nByteRangeLength = pNumber->GetValue();
131 aByteRanges.emplace_back(nByteRangeOffset, nByteRangeLength);
134 // Detect if the byte ranges don't cover everything, but the signature itself.
135 if (aByteRanges.size() < 2)
137 SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: expected 2 byte ranges");
138 return false;
140 if (aByteRanges[0].first != 0)
142 SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: first range start is not 0");
143 return false;
145 // 2 is the leading "<" and the trailing ">" around the hex string.
146 size_t nSignatureLength = static_cast<size_t>(pContents->GetValue().getLength()) + 2;
147 if (aByteRanges[1].first != (aByteRanges[0].second + nSignatureLength))
149 SAL_WARN("xmlsecurity.pdfio",
150 "ValidateSignature: second range start is not the end of the signature");
151 return false;
153 rStream.Seek(STREAM_SEEK_TO_END);
154 size_t nFileEnd = rStream.Tell();
155 if (bLast && (aByteRanges[1].first + aByteRanges[1].second) != nFileEnd)
156 // Second range end is not the end of the file.
157 rInformation.bPartialDocumentSignature = true;
159 // At this point there is no obviously missing info to validate the
160 // signature.
161 std::vector<unsigned char> aSignature = vcl::filter::PDFDocument::DecodeHexString(pContents);
162 if (aSignature.empty())
164 SAL_WARN("xmlsecurity.pdfio", "ValidateSignature: empty contents");
165 return false;
168 return svl::crypto::Signing::Verify(rStream, aByteRanges, bNonDetached, aSignature,
169 rInformation);
172 } // namespace pdfio
173 } // namespace xmlsecurity
175 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */