From bef9ba5e062b340b1835db94620f7ea4fa0b123b Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Wed, 30 Nov 2016 17:00:58 +0100 Subject: xmlsecurity PDF verify: look for the signingCertificateV2 attribute This is a required part of the PAdES spec, but so far we only wrote it. As a start just expose if the attribute exists or not. Change-Id: Iae3815f764973a2fd29d72593236c2f484172101 Reviewed-on: https://gerrit.libreoffice.org/31436 Reviewed-by: Miklos Vajna Tested-by: Jenkins --- xmlsecurity/Library_xmlsecurity.mk | 1 + xmlsecurity/inc/sigstruct.hxx | 3 + xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx | 21 +++ xmlsecurity/source/pdfio/pdfdocument.cxx | 205 ++++++++++++++++++++++++++ 4 files changed, 230 insertions(+) (limited to 'xmlsecurity') diff --git a/xmlsecurity/Library_xmlsecurity.mk b/xmlsecurity/Library_xmlsecurity.mk index af561230c6aa..aee3bcb34994 100644 --- a/xmlsecurity/Library_xmlsecurity.mk +++ b/xmlsecurity/Library_xmlsecurity.mk @@ -82,6 +82,7 @@ $(eval $(call gb_Library_add_defs,xmlsecurity,\ )) $(eval $(call gb_Library_use_externals,xmlsecurity,\ nss3 \ + plc4 \ )) endif # BUILD_TYPE=DESKTOP endif diff --git a/xmlsecurity/inc/sigstruct.hxx b/xmlsecurity/inc/sigstruct.hxx index ab455d555953..c217352e5c7f 100644 --- a/xmlsecurity/inc/sigstruct.hxx +++ b/xmlsecurity/inc/sigstruct.hxx @@ -104,12 +104,15 @@ struct SignatureInformation css::uno::Sequence aSignatureBytes; /// For PDF: digest format, from css::xml::crypto::DigestID sal_Int32 nDigestID; + /// For PDF: has id-aa-signingCertificateV2 as a signed attribute. + bool bHasSigningCertificate; SignatureInformation( sal_Int32 nId ) { nSecurityId = nId; nStatus = css::xml::crypto::SecurityOperationStatus_UNKNOWN; nDigestID = 0; + bHasSigningCertificate = false; } }; diff --git a/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx b/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx index dbe33191782f..8932d6fc8d7f 100644 --- a/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx +++ b/xmlsecurity/qa/unit/pdfsigning/pdfsigning.cxx @@ -64,6 +64,7 @@ public: void testPDF14LOWin(); /// Test a PAdES document, signed by LO on Linux. void testPDFPAdESGood(); + void testSigningCertificateAttribute(); CPPUNIT_TEST_SUITE(PDFSigningTest); CPPUNIT_TEST(testPDFAdd); @@ -75,6 +76,7 @@ public: CPPUNIT_TEST(testPDF16Add); CPPUNIT_TEST(testPDF14LOWin); CPPUNIT_TEST(testPDFPAdESGood); + CPPUNIT_TEST(testSigningCertificateAttribute); CPPUNIT_TEST_SUITE_END(); }; @@ -322,6 +324,25 @@ void PDFSigningTest::testPDFPAdESGood() verify(m_directories.getURLFromSrc(DATA_DIRECTORY) + "good-pades.pdf", 1, "ETSI.CAdES.detached"); } +void PDFSigningTest::testSigningCertificateAttribute() +{ + // Create a new signature. + OUString aSourceDir = m_directories.getURLFromSrc(DATA_DIRECTORY); + OUString aInURL = aSourceDir + "no.pdf"; + OUString aTargetDir = m_directories.getURLFromWorkdir("/CppunitTest/xmlsecurity_pdfsigning.test.user/"); + OUString aOutURL = aTargetDir + "signing-certificate-attribute.pdf"; + bool bHadCertificates = sign(aInURL, aOutURL, 0); + if (!bHadCertificates) + return; + + // Verify it. + std::vector aInfos = verify(aOutURL, 1, "ETSI.CAdES.detached"); + CPPUNIT_ASSERT(!aInfos.empty()); + SignatureInformation& rInformation = aInfos[0]; + // Assert that it has a signed signingCertificateV2 attribute. + CPPUNIT_ASSERT(rInformation.bHasSigningCertificate); +} + CPPUNIT_TEST_SUITE_REGISTRATION(PDFSigningTest); CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/xmlsecurity/source/pdfio/pdfdocument.cxx b/xmlsecurity/source/pdfio/pdfdocument.cxx index aeea58d48267..2092369404ac 100644 --- a/xmlsecurity/source/pdfio/pdfdocument.cxx +++ b/xmlsecurity/source/pdfio/pdfdocument.cxx @@ -34,6 +34,7 @@ #include #include #include +#include #include #endif @@ -1936,6 +1937,161 @@ std::vector PDFDocument::DecodeHexString(PDFHexStringElement* pEl return aRet; } +#ifdef XMLSEC_CRYPTO_NSS +namespace +{ +/// 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 (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; +} +} +#endif + bool PDFDocument::ValidateSignature(SvStream& rStream, PDFObjectElement* pSignature, SignatureInformation& rInformation, bool bLast) { PDFObjectElement* pValue = pSignature->LookupObject("V"); @@ -2235,6 +2391,27 @@ bool PDFDocument::ValidateSignature(SvStream& rStream, PDFObjectElement* pSignat 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 (pContentInfoContentData && pContentInfoContentData->data) { @@ -2389,6 +2566,34 @@ bool PDFDocument::ValidateSignature(SvStream& rStream, PDFObjectElement* pSignat 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; -- cgit