Kaydet (Commit) bef9ba5e authored tarafından Miklos Vajna's avatar Miklos Vajna

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/31436Reviewed-by: 's avatarMiklos Vajna <vmiklos@collabora.co.uk>
Tested-by: 's avatarJenkins <ci@libreoffice.org>
üst c038d88c
......@@ -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
......
......@@ -104,12 +104,15 @@ struct SignatureInformation
css::uno::Sequence<sal_Int8> 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;
}
};
......
......@@ -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<SignatureInformation> 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();
......
......@@ -34,6 +34,7 @@
#include <cert.h>
#include <cms.h>
#include <nss.h>
#include <secerr.h>
#include <sechash.h>
#endif
......@@ -1936,6 +1937,161 @@ std::vector<unsigned char> 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<BYTE[]> 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<PCRYPT_ATTRIBUTES>(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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment