summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiklos Vajna <vmiklos@collabora.co.uk>2016-11-23 11:27:32 +0100
committerMiklos Vajna <vmiklos@collabora.co.uk>2016-11-29 08:08:50 +0000
commitd02c3ba8c5e723561edb694e7ed8b2f2c33604af (patch)
treeb7ada52cc1c914f7d94028f8ddc5a0bac5ead847
parentb992da28c595f40a75ddf3237edec10d73e56d91 (diff)
vcl mscrypto PDF sign: bring it up to date with NSS, part 1
This is a combination of 6 commits: 1) vcl mscrypto PDF sign: add initial 'signing-certificate' signed attribute Equivalent of the earlier NSS commit, payload is just an empty sequence at the moment. (cherry picked from commit cb851cbb09adc637bb6e8095050292f7a8c6a7b1) 2) vcl mscrypto PDF sign: write ESSCertIDv2 With this, the value of signing-certificate conforms to the RFC on Windows as well. (cherry picked from commit b12410f212658996fdb5fb291a06038e9ac39b2e) 3) xmlsecurity mscrypto PDF sign: conditionally add back CAdES SubFilter We can now write that on Windows as well when requested, after the signing-certificate attribute is implemented using mscrypto. With this, the PAdES validator at <http://signatures-conformance-checker.etsi.org/protected/upload.php?sigtype=padesconf> finds our Windows signature valid. (cherry picked from commit 8a279d7de4cf94c99f655f6edd0da0c24ab4003c) 4) CppunitTest_xmlsecurity_signing: don't assume we always have a signing cert This makes this suite in sync with CppunitTest_xmlsecurity_pdfsigning. A signing certificate is available on 64bit NSS platforms, as there we provide a pre-created NSS db, but on other platforms by default there is just no signing certificate. The certificate.crt I added earlier is not enough, that's just the certificate, but it doesn't provide a private key. (cherry picked from commit 748f778d0f42f2cbb78a7ca7e013bfbd77cdf2b7) 5) CppunitTest_xmlsecurity_signing: add XAdES testcase Assert the two user-visible changes: SHA-256 hashes and the digest of the signing certificate. (cherry picked from commit 426495cb441e6a83cd0d1f74b0ddf656322815b5) 6) CppunitTest_xmlsecurity_pdfsigning: add PAdES testcase Assert the two user-visible changes: SHA-256 hashes and the SubFilter of the signature. (cherry picked from commit 5cb580144c286117db485e605c79ce1139cb94fb) Change-Id: I12a2355e2ddfc368bed4430a7b5ad244b5778afe Reviewed-on: https://gerrit.libreoffice.org/31316 Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk> Tested-by: Miklos Vajna <vmiklos@collabora.co.uk>
-rw-r--r--vcl/source/gdi/pdfwriter_impl.cxx117
-rw-r--r--xmlsecurity/CppunitTest_xmlsecurity_signing.mk1
-rw-r--r--xmlsecurity/inc/pdfio/pdfdocument.hxx66
-rw-r--r--xmlsecurity/inc/sigstruct.hxx3
-rw-r--r--xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx28
-rw-r--r--xmlsecurity/qa/unit/signing/data/certificate.crt27
-rw-r--r--xmlsecurity/qa/unit/signing/data/key3.dbbin0 -> 16384 bytes
-rw-r--r--xmlsecurity/qa/unit/signing/signing.cxx94
-rw-r--r--xmlsecurity/source/pdfio/pdfdocument.cxx74
9 files changed, 286 insertions, 124 deletions
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index e5af4d4d0ff9..372f08e1768d 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -6805,6 +6805,101 @@ typedef BOOL (WINAPI *PointerTo_CryptRetrieveTimeStamp)(LPCWSTR wszUrl,
PCCERT_CONTEXT *ppTsSigner,
HCERTSTORE phStore);
+namespace
+{
+/// Create payload for the 'signing-certificate' signed attribute.
+bool CreateSigningCertificateAttribute(vcl::PDFWriter::PDFSignContext& rContext, SvStream& rEncodedCertificate)
+{
+ // CryptEncodeObjectEx() does not support encoding arbitrary ASN.1
+ // structures, like SigningCertificateV2 from RFC 5035, so let's build it
+ // manually.
+
+ // Count the certificate hash and put it to aHash.
+ // 2.16.840.1.101.3.4.2.1, i.e. sha256.
+ std::vector<unsigned char> aSHA256{0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01};
+
+ HCRYPTPROV hProv = 0;
+ if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
+ {
+ SAL_WARN("vcl.pdfwriter", "CryptAcquireContext() failed");
+ return false;
+ }
+
+ HCRYPTHASH hHash = 0;
+ if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash))
+ {
+ SAL_WARN("vcl.pdfwriter", "CryptCreateHash() failed");
+ return false;
+ }
+
+ if (!CryptHashData(hHash, reinterpret_cast<const BYTE*>(rContext.m_pDerEncoded), rContext.m_nDerEncoded, 0))
+ {
+ SAL_WARN("vcl.pdfwriter", "CryptHashData() failed");
+ return false;
+ }
+
+ DWORD nHash = 0;
+ if (!CryptGetHashParam(hHash, HP_HASHVAL, nullptr, &nHash, 0))
+ {
+ SAL_WARN("vcl.pdfwriter", "CryptGetHashParam() failed to provide the hash length");
+ return false;
+ }
+
+ std::vector<unsigned char> aHash(nHash);
+ if (!CryptGetHashParam(hHash, HP_HASHVAL, aHash.data(), &nHash, 0))
+ {
+ SAL_WARN("vcl.pdfwriter", "CryptGetHashParam() failed to provide the hash");
+ return false;
+ }
+
+ CryptDestroyHash(hHash);
+ CryptReleaseContext(hProv, 0);
+
+ // We now have all the info to count the lengths.
+ // The layout of the payload is:
+ // SEQUENCE: SigningCertificateV2
+ // SEQUENCE: SEQUENCE OF ESSCertIDv2
+ // SEQUENCE: ESSCertIDv2
+ // SEQUENCE: AlgorithmIdentifier
+ // OBJECT: algorithm
+ // NULL: parameters
+ // OCTET STRING: certHash
+
+ size_t nAlgorithm = aSHA256.size() + 2;
+ size_t nParameters = 2;
+ size_t nAlgorithmIdentifier = nAlgorithm + nParameters + 2;
+ size_t nCertHash = aHash.size() + 2;
+ size_t nESSCertIDv2 = nAlgorithmIdentifier + nCertHash + 2;
+ size_t nESSCertIDv2s = nESSCertIDv2 + 2;
+
+ // Write SigningCertificateV2.
+ rEncodedCertificate.WriteUInt8(0x30);
+ rEncodedCertificate.WriteUInt8(nESSCertIDv2s);
+ // Write SEQUENCE OF ESSCertIDv2.
+ rEncodedCertificate.WriteUInt8(0x30);
+ rEncodedCertificate.WriteUInt8(nESSCertIDv2);
+ // Write ESSCertIDv2.
+ rEncodedCertificate.WriteUInt8(0x30);
+ rEncodedCertificate.WriteUInt8(nAlgorithmIdentifier + nCertHash);
+ // Write AlgorithmIdentifier.
+ rEncodedCertificate.WriteUInt8(0x30);
+ rEncodedCertificate.WriteUInt8(nAlgorithm + nParameters);
+ // Write algorithm.
+ rEncodedCertificate.WriteUInt8(0x06);
+ rEncodedCertificate.WriteUInt8(aSHA256.size());
+ rEncodedCertificate.WriteBytes(aSHA256.data(), aSHA256.size());
+ // Write parameters.
+ rEncodedCertificate.WriteUInt8(0x05);
+ rEncodedCertificate.WriteUInt8(0x00);
+ // Write certHash.
+ rEncodedCertificate.WriteUInt8(0x04);
+ rEncodedCertificate.WriteUInt8(aHash.size());
+ rEncodedCertificate.WriteBytes(aHash.data(), aHash.size());
+
+ return true;
+}
+} // anonymous namespace
+
#endif
bool PDFWriter::Sign(PDFSignContext& rContext)
@@ -7338,6 +7433,28 @@ bool PDFWriter::Sign(PDFSignContext& rContext)
aSignerInfo.HashAlgorithm.pszObjId = const_cast<LPSTR>(szOID_NIST_sha256);
aSignerInfo.HashAlgorithm.Parameters.cbData = 0;
+ // Add the signing certificate as a signed attribute.
+ CRYPT_INTEGER_BLOB aCertificateBlob;
+ SvMemoryStream aEncodedCertificate;
+ if (!CreateSigningCertificateAttribute(rContext, aEncodedCertificate))
+ {
+ SAL_WARN("vcl.pdfwriter", "CreateSigningCertificateAttribute() failed");
+ return false;
+ }
+ aCertificateBlob.pbData = const_cast<BYTE*>(static_cast<const BYTE*>(aEncodedCertificate.GetData()));
+ aCertificateBlob.cbData = aEncodedCertificate.GetSize();
+ CRYPT_ATTRIBUTE aCertificateAttribute;
+ /*
+ * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::=
+ * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9)
+ * smime(16) id-aa(2) 47 }
+ */
+ aCertificateAttribute.pszObjId = const_cast<LPSTR>("1.2.840.113549.1.9.16.2.47");
+ aCertificateAttribute.cValue = 1;
+ aCertificateAttribute.rgValue = &aCertificateBlob;
+ aSignerInfo.cAuthAttr = 1;
+ aSignerInfo.rgAuthAttr = &aCertificateAttribute;
+
CMSG_SIGNED_ENCODE_INFO aSignedInfo;
memset(&aSignedInfo, 0, sizeof(aSignedInfo));
aSignedInfo.cbSize = sizeof(aSignedInfo);
diff --git a/xmlsecurity/CppunitTest_xmlsecurity_signing.mk b/xmlsecurity/CppunitTest_xmlsecurity_signing.mk
index 24713cbe8e24..f7bbb0e83753 100644
--- a/xmlsecurity/CppunitTest_xmlsecurity_signing.mk
+++ b/xmlsecurity/CppunitTest_xmlsecurity_signing.mk
@@ -32,6 +32,7 @@ $(eval $(call gb_CppunitTest_use_libraries,xmlsecurity_signing, \
$(eval $(call gb_CppunitTest_use_externals,xmlsecurity_signing,\
boost_headers \
+ libxml2 \
))
$(eval $(call gb_CppunitTest_set_include,xmlsecurity_signing,\
diff --git a/xmlsecurity/inc/pdfio/pdfdocument.hxx b/xmlsecurity/inc/pdfio/pdfdocument.hxx
index 31a0546deb38..e2f2913e863c 100644
--- a/xmlsecurity/inc/pdfio/pdfdocument.hxx
+++ b/xmlsecurity/inc/pdfio/pdfdocument.hxx
@@ -27,9 +27,12 @@ namespace pdfio
{
class PDFTrailerElement;
-class PDFObjectElement;
class PDFHexStringElement;
class PDFReferenceElement;
+class PDFDocument;
+class PDFDictionaryElement;
+class PDFArrayElement;
+class PDFStreamElement;
/// A byte range in a PDF file.
class PDFElement
@@ -39,6 +42,67 @@ public:
virtual ~PDFElement() { }
};
+/// Indirect object: something with a unique ID.
+class XMLSECURITY_DLLPUBLIC PDFObjectElement : public PDFElement
+{
+ PDFDocument& m_rDoc;
+ 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;
+ PDFDictionaryElement* m_pDictionaryElement;
+ /// The contained direct array, if any.
+ PDFArrayElement* m_pArrayElement;
+ /// The stream of this object, used when this is an object stream.
+ PDFStreamElement* m_pStreamElement;
+ /// Objects of an object stream.
+ std::vector< std::unique_ptr<PDFObjectElement> > m_aStoredElements;
+ /// Elements of an object in an object stream.
+ std::vector< std::unique_ptr<PDFElement> > m_aElements;
+ /// Uncompressed buffer of an object in an object stream.
+ std::unique_ptr<SvMemoryStream> m_pStreamBuffer;
+
+public:
+ PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue);
+ bool Read(SvStream& rStream) override;
+ PDFElement* Lookup(const OString& rDictionaryKey);
+ PDFObjectElement* LookupObject(const OString& rDictionaryKey);
+ double GetObjectValue() const;
+ void SetDictionaryOffset(sal_uInt64 nDictionaryOffset);
+ sal_uInt64 GetDictionaryOffset();
+ void SetDictionaryLength(sal_uInt64 nDictionaryLength);
+ sal_uInt64 GetDictionaryLength();
+ PDFDictionaryElement* GetDictionary() const;
+ void SetDictionary(PDFDictionaryElement* pDictionaryElement);
+ void SetArray(PDFArrayElement* pArrayElement);
+ void SetStream(PDFStreamElement* pStreamElement);
+ PDFArrayElement* GetArray() const;
+ /// Parse objects stored in this object stream.
+ void ParseStoredObjects();
+ std::vector< std::unique_ptr<PDFElement> >& GetStoredElements();
+ SvMemoryStream* GetStreamBuffer() const;
+ void SetStreamBuffer(std::unique_ptr<SvMemoryStream>& pStreamBuffer);
+};
+
+/// Name object: a key string.
+class XMLSECURITY_DLLPUBLIC PDFNameElement : public PDFElement
+{
+ OString m_aValue;
+ /// Offset after the '/' token.
+ sal_uInt64 m_nLocation;
+ /// Length till the next token start.
+ sal_uInt64 m_nLength;
+public:
+ PDFNameElement();
+ bool Read(SvStream& rStream) override;
+ const OString& GetValue() const;
+ sal_uInt64 GetLocation() const;
+ sal_uInt64 GetLength() const;
+};
+
enum class TokenizeMode
{
/// Full file.
diff --git a/xmlsecurity/inc/sigstruct.hxx b/xmlsecurity/inc/sigstruct.hxx
index 6dd4f7f206e2..ab455d555953 100644
--- a/xmlsecurity/inc/sigstruct.hxx
+++ b/xmlsecurity/inc/sigstruct.hxx
@@ -102,11 +102,14 @@ struct SignatureInformation
OUString ouCertDigest;
/// A full OOXML signguature for unchanged roundtrip, empty for ODF.
css::uno::Sequence<sal_Int8> aSignatureBytes;
+ /// For PDF: digest format, from css::xml::crypto::DigestID
+ sal_Int32 nDigestID;
SignatureInformation( sal_Int32 nId )
{
nSecurityId = nId;
nStatus = css::xml::crypto::SecurityOperationStatus_UNKNOWN;
+ nDigestID = 0;
}
};
diff --git a/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx b/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx
index 99e176b03b07..4d0ce52c3f7f 100644
--- a/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx
+++ b/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx
@@ -40,7 +40,7 @@ class PDFSigningTest : public test::BootstrapFixture
* Read a pdf and make sure that it has the expected number of valid
* signatures.
*/
- std::vector<SignatureInformation> verify(const OUString& rURL, size_t nCount);
+ std::vector<SignatureInformation> verify(const OUString& rURL, size_t nCount, const OString& rExpectedSubFilter);
public:
PDFSigningTest();
@@ -98,7 +98,7 @@ void PDFSigningTest::setUp()
#endif
}
-std::vector<SignatureInformation> PDFSigningTest::verify(const OUString& rURL, size_t nCount)
+std::vector<SignatureInformation> PDFSigningTest::verify(const OUString& rURL, size_t nCount, const OString& rExpectedSubFilter)
{
uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(mxComponentContext);
uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString());
@@ -115,6 +115,15 @@ std::vector<SignatureInformation> PDFSigningTest::verify(const OUString& rURL, s
bool bLast = i == aSignatures.size() - 1;
CPPUNIT_ASSERT(xmlsecurity::pdfio::PDFDocument::ValidateSignature(aStream, aSignatures[i], aInfo, bLast));
aRet.push_back(aInfo);
+
+ if (!rExpectedSubFilter.isEmpty())
+ {
+ xmlsecurity::pdfio::PDFObjectElement* pValue = aSignatures[i]->LookupObject("V");
+ CPPUNIT_ASSERT(pValue);
+ auto pSubFilter = dynamic_cast<xmlsecurity::pdfio::PDFNameElement*>(pValue->Lookup("SubFilter"));
+ CPPUNIT_ASSERT(pSubFilter);
+ CPPUNIT_ASSERT_EQUAL(rExpectedSubFilter, pSubFilter->GetValue());
+ }
}
return aRet;
@@ -148,7 +157,7 @@ bool PDFSigningTest::sign(const OUString& rInURL, const OUString& rOutURL, size_
}
// This was nOriginalSignatureCount when PDFDocument::Sign() silently returned success, without doing anything.
- verify(rOutURL, nOriginalSignatureCount + 1);
+ verify(rOutURL, nOriginalSignatureCount + 1, /*rExpectedSubFilter=*/OString());
return true;
}
@@ -163,11 +172,14 @@ void PDFSigningTest::testPDFAdd()
if (bHadCertificates)
{
+ // Assert that the SubFilter is not adbe.pkcs7.detached in the bAdES case.
+ std::vector<SignatureInformation> aInfos = verify(aOutURL, 1, "ETSI.CAdES.detached");
// Make sure the timestamp is correct.
- std::vector<SignatureInformation> aInfos = verify(aOutURL, 1);
DateTime aDateTime(DateTime::SYSTEM);
// This was 0 (on Windows), as neither the /M key nor the PKCS#7 blob contained a timestamp.
CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(aDateTime.GetYear()), aInfos[0].stDateTime.Year);
+ // Assert that the digest algorithm is not SHA-1 in the bAdES case.
+ CPPUNIT_ASSERT_EQUAL(xml::crypto::DigestID::SHA256, aInfos[0].nDigestID);
}
}
@@ -218,7 +230,7 @@ void PDFSigningTest::testPDFRemove()
// Read back the pdf and make sure that it no longer has signatures.
// This failed when PDFDocument::RemoveSignature() silently returned
// success, without doing anything.
- verify(aOutURL, 0);
+ verify(aOutURL, 0, /*rExpectedSubFilter=*/OString());
}
void PDFSigningTest::testPDFRemoveAll()
@@ -259,7 +271,7 @@ void PDFSigningTest::testPDF14Adobe()
// Two signatures, first is SHA1, the second is SHA256.
// This was 0, as we failed to find the Annots key's value when it was a
// reference-to-array, not an array.
- std::vector<SignatureInformation> aInfos = verify(m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf14adobe.pdf", 2);
+ std::vector<SignatureInformation> aInfos = verify(m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf14adobe.pdf", 2, /*rExpectedSubFilter=*/OString());
// This was 0, out-of-PKCS#7 signature date wasn't read.
CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(2016), aInfos[1].stDateTime.Year);
}
@@ -270,7 +282,7 @@ void PDFSigningTest::testPDF16Adobe()
// stream with a predictor. And a valid signature.
// Found signatures was 0, as parsing failed due to lack of support for
// these features.
- verify(m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf16adobe.pdf", 1);
+ verify(m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf16adobe.pdf", 1, /*rExpectedSubFilter=*/OString());
}
void PDFSigningTest::testPDF16Add()
@@ -299,7 +311,7 @@ void PDFSigningTest::testPDF14LOWin()
// algorithm when it meant SEC_OID_SHA1, make sure we tolerate that on all
// platforms.
// This failed, as NSS HASH_Create() didn't handle the sign algorithm.
- verify(m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf14lowin.pdf", 1);
+ verify(m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf14lowin.pdf", 1, /*rExpectedSubFilter=*/OString());
}
CPPUNIT_TEST_SUITE_REGISTRATION(PDFSigningTest);
diff --git a/xmlsecurity/qa/unit/signing/data/certificate.crt b/xmlsecurity/qa/unit/signing/data/certificate.crt
deleted file mode 100644
index f3f34b7c65ab..000000000000
--- a/xmlsecurity/qa/unit/signing/data/certificate.crt
+++ /dev/null
@@ -1,27 +0,0 @@
-MIIE7jCCAtagAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwVzELMAkGA1UEBhMCVUsx
-EDAOBgNVBAgMB0VuZ2xhbmQxEjAQBgNVBAoMCVRTQ1AgVGVzdDEiMCAGA1UEAwwZ
-VFNDUCBJbnRlcm1lZGlhdGUgUm9vdCBDQTAeFw0xNTEyMTgwNzU4MTlaFw0xNjEy
-MjcwNzU4MTlaMFUxCzAJBgNVBAYTAlVLMRAwDgYDVQQIDAdFbmdsYW5kMRIwEAYD
-VQQKDAlUU0NQIFRlc3QxIDAeBgNVBAMMF1RTQ1AgVGVzdCBleGFtcGxlIEFsaWNl
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3m2YNdX+nc1LkhlrNrcI
-PI3yCWnv0/0k9zDKpKiwjMH4vjWM46M6ptAiupxVpAMW5ojnhEyxaNHvZNsCwddY
-E6778hut2SJvz0szSBuHUuedcALI2EhVwdM0yLqfGo6WGeOIBDId49TemdNCMhk2
-zOpb1BqYhKls0LfdbxT/an3JaDmmLhPjvgYMJNYVX86L199OQFLJ1zLqQ0YirkKq
-XL9cSPmyYBKjgnqQ4Z5YfPL63EP0TsEfa5oQmy/0gS5FB2Wz9CqIptB130v0GR4X
-ObTpOkhPFfC5RDBFTMZoi4NCK10wn2NCbr7qZ3aMrOlfeKbsNIifwu0KYFHXyxL5
-AwIDAQABo4HFMIHCMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMDMGCWCG
-SAGG+EIBDQQmFiRPcGVuU1NMIEdlbmVyYXRlZCBDbGllbnQgQ2VydGlmaWNhdGUw
-HQYDVR0OBBYEFCL6DzsuAbni8475Z+HkX5tv8iiWMB8GA1UdIwQYMBaAFMuejS1r
-WjUf3x1+2QbPSVpuXFl+MA4GA1UdDwEB/wQEAwIF4DAdBgNVHSUEFjAUBggrBgEF
-BQcDAgYIKwYBBQUHAwQwDQYJKoZIhvcNAQELBQADggIBAFs0DeCDjttHQ0UHsYcn
-hfBCWRdOFdIr3F/IEbN2BL/grScGXoXRaYMIQJv/s5dKgZIuH7xMCVKazoftPVqU
-4bOEduAv0IJ6hQF/wEMBueA0UjvQQVYZgsOALi7TD3gYpFqYcH2Wfx5/5Ln6dllL
-8UsHoP+6gSLaYwjJd7FQ+IlNTzR65dRMLoJhoKqqyuM6cf/PM8sbK2NH2r8toypj
-fPixvD/w3wP7xn4oo/IGXcRK4DTHBF/rSMqeR6ePwXm5tVHrQBfnxN3dsGsXkQgq
-zBvvbPY0raraO4CPR7mZp4GVFHOsUNh5TI1SlfxWZ49HU3F5jWeiI9jPuw1RmuAy
-ZdFEt403Wi67v6revXe1By6UqIZjq3b2pJGBKZH+60P1cJScawzrN8pi1qQFV8Ji
-iJM6/MSciqplTT5F7SG0XZx1CjnBz5rMdYNhI9NNtF3oy9Xy9RvgYehFaC43ZlBB
-UMDmZFj5a78hOOkkq1UnrHUdeXyWhiEFzv5d8My2i0kWGq8r0HuC25BmOa17lHVx
-Q2o7Rdu9jDFP9oNizC7kQfA5QVRTfBFcWH7jml69RmVgfM+X+wdQgen9hJAILhBz
-mDfeteJ5ZEaoEYtw3isOGkpSyg7odjgYq7I+bOiN1toDg07vzfIkvF9KxlkDeRLX
-bmcFIvQsqFeF6cUwlZQYLOHA
diff --git a/xmlsecurity/qa/unit/signing/data/key3.db b/xmlsecurity/qa/unit/signing/data/key3.db
new file mode 100644
index 000000000000..8ab32c28d584
--- /dev/null
+++ b/xmlsecurity/qa/unit/signing/data/key3.db
Binary files differ
diff --git a/xmlsecurity/qa/unit/signing/signing.cxx b/xmlsecurity/qa/unit/signing/signing.cxx
index 51e536614ad6..5bcf0c26f441 100644
--- a/xmlsecurity/qa/unit/signing/signing.cxx
+++ b/xmlsecurity/qa/unit/signing/signing.cxx
@@ -15,6 +15,7 @@
#include <test/bootstrapfixture.hxx>
#include <unotest/macros_test.hxx>
+#include <test/xmltesttools.hxx>
#include <com/sun/star/document/XStorageBasedDocument.hpp>
#include <com/sun/star/embed/XStorage.hpp>
@@ -52,7 +53,7 @@ const char* DATA_DIRECTORY = "/xmlsecurity/qa/unit/signing/data/";
}
/// Testsuite for the document signing feature.
-class SigningTest : public test::BootstrapFixture, public unotest::MacrosTest
+class SigningTest : public test::BootstrapFixture, public unotest::MacrosTest, public XmlTestTools
{
uno::Reference<uno::XComponentContext> mxComponentContext;
uno::Reference<lang::XComponent> mxComponent;
@@ -61,6 +62,7 @@ public:
SigningTest();
virtual void setUp() override;
virtual void tearDown() override;
+ void registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx) override;
void testDescription();
/// Test a typical ODF where all streams are signed.
@@ -88,6 +90,7 @@ public:
#endif
void test96097Calc();
void test96097Doc();
+ void testXAdES();
CPPUNIT_TEST_SUITE(SigningTest);
CPPUNIT_TEST(testDescription);
@@ -107,6 +110,7 @@ public:
#endif
CPPUNIT_TEST(test96097Calc);
CPPUNIT_TEST(test96097Doc);
+ CPPUNIT_TEST(testXAdES);
CPPUNIT_TEST_SUITE_END();
private:
@@ -132,6 +136,7 @@ void SigningTest::setUp()
OUString aTargetDir = m_directories.getURLFromWorkdir(
"/CppunitTest/xmlsecurity_signing.test.user/");
osl::File::copy(aSourceDir + "cert8.db", aTargetDir + "cert8.db");
+ osl::File::copy(aSourceDir + "key3.db", aTargetDir + "key3.db");
OUString aTargetPath;
osl::FileBase::getSystemPathFromFileURL(aTargetDir, aTargetPath);
setenv("MOZILLA_CERTIFICATE_FOLDER", aTargetPath.toUtf8().getStr(), 1);
@@ -168,20 +173,14 @@ void SigningTest::createCalc(const OUString& rURL)
uno::Reference<security::XCertificate> SigningTest::getCertificate(DocumentSignatureManager& rSignatureManager)
{
+ uno::Reference<security::XCertificate> xCertificate;
+
uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = rSignatureManager.getSecurityEnvironment();
- OUString aCertificate;
- {
- SvFileStream aStream(m_directories.getURLFromSrc(DATA_DIRECTORY) + "certificate.crt", StreamMode::READ);
- OString aLine;
- bool bMore = aStream.ReadLine(aLine);
- while (bMore)
- {
- aCertificate += OUString::fromUtf8(aLine);
- aCertificate += "\n";
- bMore = aStream.ReadLine(aLine);
- }
- }
- return xSecurityEnvironment->createCertificateFromAscii(aCertificate);
+ uno::Sequence<uno::Reference<security::XCertificate>> aCertificates = xSecurityEnvironment->getPersonalCertificates();
+ if (!aCertificates.hasElements())
+ return xCertificate;
+
+ return aCertificates[0];
}
void SigningTest::testDescription()
@@ -205,7 +204,8 @@ void SigningTest::testDescription()
// Then add a signature document.
uno::Reference<security::XCertificate> xCertificate = getCertificate(aManager);
- CPPUNIT_ASSERT(xCertificate.is());
+ if (!xCertificate.is())
+ return;
OUString aDescription("SigningTest::testDescription");
sal_Int32 nSecurityId;
aManager.add(xCertificate, aDescription, nSecurityId, false);
@@ -238,7 +238,8 @@ void SigningTest::testOOXMLDescription()
// Then add a document signature.
uno::Reference<security::XCertificate> xCertificate = getCertificate(aManager);
- CPPUNIT_ASSERT(xCertificate.is());
+ if (!xCertificate.is())
+ return;
OUString aDescription("SigningTest::testDescription");
sal_Int32 nSecurityId;
aManager.add(xCertificate, aDescription, nSecurityId, false);
@@ -271,7 +272,8 @@ void SigningTest::testOOXMLAppend()
// Then add a second document signature.
uno::Reference<security::XCertificate> xCertificate = getCertificate(aManager);
- CPPUNIT_ASSERT(xCertificate.is());
+ if (!xCertificate.is())
+ return;
sal_Int32 nSecurityId;
aManager.add(xCertificate, OUString(), nSecurityId, false);
@@ -297,7 +299,8 @@ void SigningTest::testOOXMLRemove()
// Then remove the last added signature.
uno::Reference<security::XCertificate> xCertificate = getCertificate(aManager);
- CPPUNIT_ASSERT(xCertificate.is());
+ if (!xCertificate.is())
+ return;
aManager.remove(0);
// Read back the signatures and make sure that only purpose1 is left.
@@ -327,7 +330,8 @@ void SigningTest::testOOXMLRemoveAll()
// Then remove the only signature in the document.
uno::Reference<security::XCertificate> xCertificate = getCertificate(aManager);
- CPPUNIT_ASSERT(xCertificate.is());
+ if (!xCertificate.is())
+ return;
aManager.remove(0);
aManager.read(/*bUseTempStream=*/true);
aManager.write(/*bXAdESCompliantIfODF=*/false);
@@ -542,6 +546,58 @@ void SigningTest::test96097Doc()
}
}
+void SigningTest::testXAdES()
+{
+ // Create an empty document, store it to a tempfile and load it as a storage.
+ createDoc(OUString());
+
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer8");
+ xStorable->storeAsURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ DocumentSignatureManager aManager(mxComponentContext, DocumentSignatureMode::Content);
+ CPPUNIT_ASSERT(aManager.init());
+ uno::Reference <embed::XStorage> xStorage = comphelper::OStorageHelper::GetStorageOfFormatFromURL(ZIP_STORAGE_FORMAT_STRING, aTempFile.GetURL(), embed::ElementModes::READWRITE);
+ CPPUNIT_ASSERT(xStorage.is());
+ aManager.mxStore = xStorage;
+ aManager.maSignatureHelper.SetStorage(xStorage, "1.2");
+
+ // Create a signature.
+ uno::Reference<security::XCertificate> xCertificate = getCertificate(aManager);
+ if (!xCertificate.is())
+ return;
+ sal_Int32 nSecurityId;
+ aManager.add(xCertificate, /*rDescription=*/OUString(), nSecurityId, /*bAdESCompliant=*/true);
+
+ // Write to storage.
+ aManager.read(/*bUseTempStream=*/true);
+ aManager.write(/*bXAdESCompliantIfODF=*/true);
+ uno::Reference<embed::XTransactedObject> xTransactedObject(xStorage, uno::UNO_QUERY);
+ xTransactedObject->commit();
+
+ // Parse the resulting XML.
+ uno::Reference<embed::XStorage> xMetaInf = xStorage->openStorageElement("META-INF", embed::ElementModes::READ);
+ uno::Reference<io::XInputStream> xInputStream(xMetaInf->openStreamElement("documentsignatures.xml", embed::ElementModes::READ), uno::UNO_QUERY);
+ std::shared_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true));
+ xmlDocPtr pXmlDoc = parseXmlStream(pStream.get());
+
+ // Assert that the digest algorithm is SHA-256 in the bAdESCompliant case, not SHA-1.
+ assertXPath(pXmlDoc, "/odfds:document-signatures/dsig:Signature/dsig:SignedInfo/dsig:Reference[@URI='content.xml']/dsig:DigestMethod", "Algorithm", ALGO_XMLDSIGSHA256);
+
+ // Assert that the digest of the signing certificate is included.
+ assertXPath(pXmlDoc, "//xd:CertDigest", 1);
+}
+
+void SigningTest::registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx)
+{
+ xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("odfds"), BAD_CAST("urn:oasis:names:tc:opendocument:xmlns:digitalsignature:1.0"));
+ xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("dsig"), BAD_CAST("http://www.w3.org/2000/09/xmldsig#"));
+ xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("xd"), BAD_CAST("http://uri.etsi.org/01903/v1.3.2#"));
+}
+
CPPUNIT_TEST_SUITE_REGISTRATION(SigningTest);
CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/xmlsecurity/source/pdfio/pdfdocument.cxx b/xmlsecurity/source/pdfio/pdfdocument.cxx
index ef9900c13f3b..29b4a026cf2b 100644
--- a/xmlsecurity/source/pdfio/pdfdocument.cxx
+++ b/xmlsecurity/source/pdfio/pdfdocument.cxx
@@ -54,7 +54,6 @@ namespace pdfio
const int MAX_SIGNATURE_CONTENT_LENGTH = 50000;
class PDFTrailerElement;
-class PDFObjectElement;
/// A one-liner comment.
class PDFCommentElement : public PDFElement
@@ -85,54 +84,6 @@ public:
};
class PDFReferenceElement;
-class PDFDictionaryElement;
-class PDFArrayElement;
-class PDFStreamElement;
-
-/// Indirect object: something with a unique ID.
-class PDFObjectElement : public PDFElement
-{
- PDFDocument& m_rDoc;
- 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;
- PDFDictionaryElement* m_pDictionaryElement;
- /// The contained direct array, if any.
- PDFArrayElement* m_pArrayElement;
- /// The stream of this object, used when this is an object stream.
- PDFStreamElement* m_pStreamElement;
- /// Objects of an object stream.
- std::vector< std::unique_ptr<PDFObjectElement> > m_aStoredElements;
- /// Elements of an object in an object stream.
- std::vector< std::unique_ptr<PDFElement> > m_aElements;
- /// Uncompressed buffer of an object in an object stream.
- std::unique_ptr<SvMemoryStream> m_pStreamBuffer;
-
-public:
- PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue);
- bool Read(SvStream& rStream) override;
- PDFElement* Lookup(const OString& rDictionaryKey);
- PDFObjectElement* LookupObject(const OString& rDictionaryKey);
- double GetObjectValue() const;
- void SetDictionaryOffset(sal_uInt64 nDictionaryOffset);
- sal_uInt64 GetDictionaryOffset();
- void SetDictionaryLength(sal_uInt64 nDictionaryLength);
- sal_uInt64 GetDictionaryLength();
- PDFDictionaryElement* GetDictionary() const;
- void SetDictionary(PDFDictionaryElement* pDictionaryElement);
- void SetArray(PDFArrayElement* pArrayElement);
- void SetStream(PDFStreamElement* pStreamElement);
- PDFArrayElement* GetArray() const;
- /// Parse objects stored in this object stream.
- void ParseStoredObjects();
- std::vector< std::unique_ptr<PDFElement> >& GetStoredElements();
- SvMemoryStream* GetStreamBuffer() const;
- void SetStreamBuffer(std::unique_ptr<SvMemoryStream>& pStreamBuffer);
-};
/// Dictionary object: a set key-value pairs.
class PDFDictionaryElement : public PDFElement
@@ -170,22 +121,6 @@ public:
sal_uInt64 GetLocation() const;
};
-/// Name object: a key string.
-class PDFNameElement : public PDFElement
-{
- OString m_aValue;
- /// Offset after the '/' token.
- sal_uInt64 m_nLocation;
- /// Length till the next token start.
- sal_uInt64 m_nLength;
-public:
- PDFNameElement();
- bool Read(SvStream& rStream) override;
- const OString& GetValue() const;
- sal_uInt64 GetLocation() const;
- sal_uInt64 GetLength() const;
-};
-
/// Reference object: something with a unique ID.
class PDFReferenceElement : public PDFElement
{
@@ -375,13 +310,9 @@ sal_Int32 PDFDocument::WriteSignatureObject(const OUString& rDescription, bool b
comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0');
aSigBuffer.append(aContentFiller.makeStringAndClear());
aSigBuffer.append(">\n/Type/Sig/SubFilter");
-#ifdef XMLSEC_CRYPTO_NSS
if (bAdES)
aSigBuffer.append("/ETSI.CAdES.detached");
else
-#else
- (void)bAdES;
-#endif
aSigBuffer.append("/adbe.pkcs7.detached");
// Time of signing.
@@ -2243,9 +2174,14 @@ bool PDFDocument::ValidateSignature(SvStream& rStream, PDFObjectElement* pSignat
{
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");