2416 lines
83 KiB
C++
2416 lines
83 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include <sal/config.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <svl/cryptosign.hxx>
|
|
#include <svl/sigstruct.hxx>
|
|
#include <config_crypto.h>
|
|
|
|
#if USE_CRYPTO_NSS
|
|
#include <systools/curlinit.hxx>
|
|
#endif
|
|
|
|
#include <rtl/character.hxx>
|
|
#include <rtl/strbuf.hxx>
|
|
#include <rtl/string.hxx>
|
|
#include <sal/log.hxx>
|
|
#include <tools/datetime.hxx>
|
|
#include <tools/stream.hxx>
|
|
#include <comphelper/base64.hxx>
|
|
#include <comphelper/hash.hxx>
|
|
#include <comphelper/processfactory.hxx>
|
|
#include <comphelper/random.hxx>
|
|
#include <comphelper/scopeguard.hxx>
|
|
#include <comphelper/lok.hxx>
|
|
#include <com/sun/star/security/XCertificate.hpp>
|
|
#include <com/sun/star/uno/Sequence.hxx>
|
|
#include <o3tl/char16_t2wchar_t.hxx>
|
|
|
|
#if USE_CRYPTO_NSS
|
|
// NSS headers for PDF signing
|
|
#include <cert.h>
|
|
#include <keyhi.h>
|
|
#include <pk11pub.h>
|
|
#include <hasht.h>
|
|
#include <secerr.h>
|
|
#include <sechash.h>
|
|
#include <cms.h>
|
|
#include <cmst.h>
|
|
|
|
// We use curl for RFC3161 time stamp requests
|
|
#include <curl/curl.h>
|
|
|
|
#include <com/sun/star/xml/crypto/DigestID.hpp>
|
|
#include <com/sun/star/xml/crypto/NSSInitializer.hpp>
|
|
#include <mutex>
|
|
#endif
|
|
|
|
#if USE_CRYPTO_MSCAPI
|
|
// WinCrypt headers for PDF signing
|
|
// Note: this uses Windows 7 APIs and requires the relevant data types
|
|
#include <prewin.h>
|
|
#include <wincrypt.h>
|
|
#include <postwin.h>
|
|
#include <comphelper/windowserrorstring.hxx>
|
|
#endif
|
|
|
|
using namespace com::sun::star;
|
|
|
|
namespace {
|
|
#if USE_CRYPTO_NSS
|
|
char *PDFSigningPKCS7PasswordCallback(PK11SlotInfo * /*slot*/, PRBool /*retry*/, void *arg)
|
|
{
|
|
return PL_strdup(static_cast<char *>(arg));
|
|
}
|
|
|
|
// ASN.1 used in the (much simpler) time stamp request. From RFC3161
|
|
// and other sources.
|
|
|
|
/*
|
|
AlgorithmIdentifier ::= SEQUENCE {
|
|
algorithm OBJECT IDENTIFIER,
|
|
parameters ANY DEFINED BY algorithm OPTIONAL }
|
|
-- contains a value of the type
|
|
-- registered for use with the
|
|
-- algorithm object identifier value
|
|
|
|
MessageImprint ::= SEQUENCE {
|
|
hashAlgorithm AlgorithmIdentifier,
|
|
hashedMessage OCTET STRING }
|
|
*/
|
|
|
|
struct MessageImprint {
|
|
SECAlgorithmID hashAlgorithm;
|
|
SECItem hashedMessage;
|
|
};
|
|
|
|
/*
|
|
Extension ::= SEQUENCE {
|
|
extnID OBJECT IDENTIFIER,
|
|
critical BOOLEAN DEFAULT FALSE,
|
|
extnValue OCTET STRING }
|
|
*/
|
|
|
|
struct Extension {
|
|
SECItem extnID;
|
|
SECItem critical;
|
|
SECItem extnValue;
|
|
};
|
|
|
|
/*
|
|
Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
|
|
*/
|
|
|
|
/*
|
|
TSAPolicyId ::= OBJECT IDENTIFIER
|
|
|
|
TimeStampReq ::= SEQUENCE {
|
|
version INTEGER { v1(1) },
|
|
messageImprint MessageImprint,
|
|
--a hash algorithm OID and the hash value of the data to be
|
|
--time-stamped
|
|
reqPolicy TSAPolicyId OPTIONAL,
|
|
nonce INTEGER OPTIONAL,
|
|
certReq BOOLEAN DEFAULT FALSE,
|
|
extensions [0] IMPLICIT Extensions OPTIONAL }
|
|
*/
|
|
|
|
struct TimeStampReq {
|
|
SECItem version;
|
|
MessageImprint messageImprint;
|
|
SECItem reqPolicy;
|
|
SECItem nonce;
|
|
SECItem certReq;
|
|
Extension *extensions;
|
|
};
|
|
|
|
/**
|
|
* General name, defined by RFC 3280.
|
|
*/
|
|
struct GeneralName
|
|
{
|
|
CERTName name;
|
|
};
|
|
|
|
/**
|
|
* List of general names (only one for now), defined by RFC 3280.
|
|
*/
|
|
struct GeneralNames
|
|
{
|
|
GeneralName names;
|
|
};
|
|
|
|
/**
|
|
* Supplies different fields to identify a certificate, defined by RFC 5035.
|
|
*/
|
|
struct IssuerSerial
|
|
{
|
|
GeneralNames issuer;
|
|
SECItem serialNumber;
|
|
};
|
|
|
|
/**
|
|
* Supplies different fields that are used to identify certificates, defined by
|
|
* RFC 5035.
|
|
*/
|
|
struct ESSCertIDv2
|
|
{
|
|
SECAlgorithmID hashAlgorithm;
|
|
SECItem certHash;
|
|
IssuerSerial issuerSerial;
|
|
};
|
|
|
|
/**
|
|
* This attribute uses the ESSCertIDv2 structure, defined by RFC 5035.
|
|
*/
|
|
struct SigningCertificateV2
|
|
{
|
|
ESSCertIDv2** certs;
|
|
|
|
SigningCertificateV2()
|
|
: certs(nullptr)
|
|
{
|
|
}
|
|
};
|
|
|
|
/**
|
|
* GeneralName ::= CHOICE {
|
|
* otherName [0] OtherName,
|
|
* rfc822Name [1] IA5String,
|
|
* dNSName [2] IA5String,
|
|
* x400Address [3] ORAddress,
|
|
* directoryName [4] Name,
|
|
* ediPartyName [5] EDIPartyName,
|
|
* uniformResourceIdentifier [6] IA5String,
|
|
* iPAddress [7] OCTET STRING,
|
|
* registeredID [8] OBJECT IDENTIFIER
|
|
* }
|
|
*/
|
|
const SEC_ASN1Template GeneralNameTemplate[] =
|
|
{
|
|
{SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(GeneralName)},
|
|
{SEC_ASN1_INLINE, offsetof(GeneralName, name), CERT_NameTemplate, 0},
|
|
{0, 0, nullptr, 0}
|
|
};
|
|
|
|
/**
|
|
* GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
|
|
*/
|
|
const SEC_ASN1Template GeneralNamesTemplate[] =
|
|
{
|
|
{SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(GeneralNames)},
|
|
{SEC_ASN1_INLINE | SEC_ASN1_CONTEXT_SPECIFIC | 4, offsetof(GeneralNames, names), GeneralNameTemplate, 0},
|
|
{0, 0, nullptr, 0}
|
|
};
|
|
|
|
/**
|
|
* IssuerSerial ::= SEQUENCE {
|
|
* issuer GeneralNames,
|
|
* serialNumber CertificateSerialNumber
|
|
* }
|
|
*/
|
|
const SEC_ASN1Template IssuerSerialTemplate[] =
|
|
{
|
|
{SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(IssuerSerial)},
|
|
{SEC_ASN1_INLINE, offsetof(IssuerSerial, issuer), GeneralNamesTemplate, 0},
|
|
{SEC_ASN1_INTEGER, offsetof(IssuerSerial, serialNumber), nullptr, 0},
|
|
{0, 0, nullptr, 0}
|
|
};
|
|
|
|
|
|
/**
|
|
* Hash ::= OCTET STRING
|
|
*
|
|
* ESSCertIDv2 ::= SEQUENCE {
|
|
* hashAlgorithm AlgorithmIdentifier DEFAULT {algorithm id-sha256},
|
|
* certHash Hash,
|
|
* issuerSerial IssuerSerial OPTIONAL
|
|
* }
|
|
*/
|
|
|
|
SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
|
|
|
|
const SEC_ASN1Template ESSCertIDv2Template[] =
|
|
{
|
|
{SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(ESSCertIDv2)},
|
|
{SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(ESSCertIDv2, hashAlgorithm), SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate), 0},
|
|
{SEC_ASN1_OCTET_STRING, offsetof(ESSCertIDv2, certHash), nullptr, 0},
|
|
{SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(ESSCertIDv2, issuerSerial), IssuerSerialTemplate, 0},
|
|
{0, 0, nullptr, 0}
|
|
};
|
|
|
|
/**
|
|
* SigningCertificateV2 ::= SEQUENCE {
|
|
* }
|
|
*/
|
|
const SEC_ASN1Template SigningCertificateV2Template[] =
|
|
{
|
|
{SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(SigningCertificateV2)},
|
|
{SEC_ASN1_SEQUENCE_OF, offsetof(SigningCertificateV2, certs), ESSCertIDv2Template, 0},
|
|
{0, 0, nullptr, 0}
|
|
};
|
|
|
|
struct PKIStatusInfo {
|
|
SECItem status;
|
|
SECItem statusString;
|
|
SECItem failInfo;
|
|
};
|
|
|
|
const SEC_ASN1Template PKIStatusInfo_Template[] =
|
|
{
|
|
{ SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(PKIStatusInfo) },
|
|
{ SEC_ASN1_INTEGER, offsetof(PKIStatusInfo, status), nullptr, 0 },
|
|
{ SEC_ASN1_CONSTRUCTED | SEC_ASN1_SEQUENCE | SEC_ASN1_OPTIONAL, offsetof(PKIStatusInfo, statusString), nullptr, 0 },
|
|
{ SEC_ASN1_BIT_STRING | SEC_ASN1_OPTIONAL, offsetof(PKIStatusInfo, failInfo), nullptr, 0 },
|
|
{ 0, 0, nullptr, 0 }
|
|
};
|
|
|
|
const SEC_ASN1Template Any_Template[] =
|
|
{
|
|
{ SEC_ASN1_ANY, 0, nullptr, sizeof(SECItem) }
|
|
};
|
|
|
|
struct TimeStampResp {
|
|
PKIStatusInfo status;
|
|
SECItem timeStampToken;
|
|
};
|
|
|
|
const SEC_ASN1Template TimeStampResp_Template[] =
|
|
{
|
|
{ SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(TimeStampResp) },
|
|
{ SEC_ASN1_INLINE, offsetof(TimeStampResp, status), PKIStatusInfo_Template, 0 },
|
|
{ SEC_ASN1_ANY | SEC_ASN1_OPTIONAL, offsetof(TimeStampResp, timeStampToken), Any_Template, 0 },
|
|
{ 0, 0, nullptr, 0 }
|
|
};
|
|
|
|
const SEC_ASN1Template MessageImprint_Template[] =
|
|
{
|
|
{ SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(MessageImprint) },
|
|
{ SEC_ASN1_INLINE, offsetof(MessageImprint, hashAlgorithm), SECOID_AlgorithmIDTemplate, 0 },
|
|
{ SEC_ASN1_OCTET_STRING, offsetof(MessageImprint, hashedMessage), nullptr, 0 },
|
|
{ 0, 0, nullptr, 0 }
|
|
};
|
|
|
|
const SEC_ASN1Template Extension_Template[] =
|
|
{
|
|
{ SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(Extension) },
|
|
{ SEC_ASN1_OBJECT_ID, offsetof(Extension, extnID), nullptr, 0 },
|
|
{ SEC_ASN1_BOOLEAN, offsetof(Extension, critical), nullptr, 0 },
|
|
{ SEC_ASN1_OCTET_STRING, offsetof(Extension, extnValue), nullptr, 0 },
|
|
{ 0, 0, nullptr, 0 }
|
|
};
|
|
|
|
const SEC_ASN1Template Extensions_Template[] =
|
|
{
|
|
{ SEC_ASN1_SEQUENCE_OF, 0, Extension_Template, 0 }
|
|
};
|
|
|
|
const SEC_ASN1Template TimeStampReq_Template[] =
|
|
{
|
|
{ SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(TimeStampReq) },
|
|
{ SEC_ASN1_INTEGER, offsetof(TimeStampReq, version), nullptr, 0 },
|
|
{ SEC_ASN1_INLINE, offsetof(TimeStampReq, messageImprint), MessageImprint_Template, 0 },
|
|
{ SEC_ASN1_OBJECT_ID | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, reqPolicy), nullptr, 0 },
|
|
{ SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, nonce), nullptr, 0 },
|
|
{ SEC_ASN1_BOOLEAN | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, certReq), nullptr, 0 },
|
|
{ SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | 0, offsetof(TimeStampReq, extensions), Extensions_Template, 0 },
|
|
{ 0, 0, nullptr, 0 }
|
|
};
|
|
|
|
size_t AppendToBuffer(char const *ptr, size_t size, size_t nmemb, void *userdata)
|
|
{
|
|
OStringBuffer *pBuffer = static_cast<OStringBuffer*>(userdata);
|
|
pBuffer->append(ptr, size*nmemb);
|
|
|
|
return size*nmemb;
|
|
}
|
|
|
|
OUString PKIStatusToString(int n)
|
|
{
|
|
switch (n)
|
|
{
|
|
case 0: return u"granted"_ustr;
|
|
case 1: return u"grantedWithMods"_ustr;
|
|
case 2: return u"rejection"_ustr;
|
|
case 3: return u"waiting"_ustr;
|
|
case 4: return u"revocationWarning"_ustr;
|
|
case 5: return u"revocationNotification"_ustr;
|
|
default: return "unknown (" + OUString::number(n) + ")";
|
|
}
|
|
}
|
|
|
|
OUString PKIStatusInfoToString(const PKIStatusInfo& rStatusInfo)
|
|
{
|
|
OUString result = u"{status="_ustr;
|
|
if (rStatusInfo.status.len == 1)
|
|
result += PKIStatusToString(rStatusInfo.status.data[0]);
|
|
else
|
|
result += "unknown (len=" + OUString::number(rStatusInfo.status.len);
|
|
|
|
// FIXME: Perhaps look at rStatusInfo.statusString.data but note
|
|
// that we of course can't assume it contains proper UTF-8. After
|
|
// all, it is data from an external source. Also, RFC3161 claims
|
|
// it should be a SEQUENCE (1..MAX) OF UTF8String, but another
|
|
// source claimed it would be a single UTF8String, hmm?
|
|
|
|
// FIXME: Worth it to decode failInfo to cleartext, probably not at least as long as this is only for a SAL_INFO
|
|
|
|
result += "}";
|
|
|
|
return result;
|
|
}
|
|
|
|
// SEC_StringToOID() and NSS_CMSSignerInfo_AddUnauthAttr() are
|
|
// not exported from libsmime, so copy them here. Sigh.
|
|
|
|
SECStatus
|
|
my_SEC_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 && rtl::isAsciiDigit(static_cast<unsigned char>(*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] = static_cast<PRUint8>(decimal & 0x7f);
|
|
decimal >>= 7;
|
|
while (--tmp > 0) {
|
|
rp[tmp] = static_cast<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) {
|
|
to->len = result_bytes;
|
|
PORT_Memcpy(to->data, result, to->len);
|
|
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;
|
|
}
|
|
|
|
NSSCMSAttribute *
|
|
my_NSS_CMSAttributeArray_FindAttrByOidTag(NSSCMSAttribute **attrs, SECOidTag oidtag, PRBool only)
|
|
{
|
|
SECOidData *oid;
|
|
NSSCMSAttribute *attr1, *attr2;
|
|
|
|
if (attrs == nullptr)
|
|
return nullptr;
|
|
|
|
oid = SECOID_FindOIDByTag(oidtag);
|
|
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;
|
|
}
|
|
|
|
SECStatus
|
|
my_NSS_CMSArray_Add(PLArenaPool *poolp, void ***array, void *obj)
|
|
{
|
|
int n = 0;
|
|
void **dest;
|
|
|
|
PORT_Assert(array != NULL);
|
|
if (array == nullptr)
|
|
return SECFailure;
|
|
|
|
if (*array == nullptr) {
|
|
dest = static_cast<void **>(PORT_ArenaAlloc(poolp, 2 * sizeof(void *)));
|
|
} else {
|
|
void **p = *array;
|
|
while (*p++)
|
|
n++;
|
|
dest = static_cast<void **>(PORT_ArenaGrow (poolp,
|
|
*array,
|
|
(n + 1) * sizeof(void *),
|
|
(n + 2) * sizeof(void *)));
|
|
}
|
|
|
|
if (dest == nullptr)
|
|
return SECFailure;
|
|
|
|
dest[n] = obj;
|
|
dest[n+1] = nullptr;
|
|
*array = dest;
|
|
return SECSuccess;
|
|
}
|
|
|
|
SECOidTag
|
|
my_NSS_CMSAttribute_GetType(const NSSCMSAttribute *attr)
|
|
{
|
|
SECOidData *typetag;
|
|
|
|
typetag = SECOID_FindOID(&(attr->type));
|
|
if (typetag == nullptr)
|
|
return SEC_OID_UNKNOWN;
|
|
|
|
return typetag->offset;
|
|
}
|
|
|
|
SECStatus
|
|
my_NSS_CMSAttributeArray_AddAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, NSSCMSAttribute *attr)
|
|
{
|
|
NSSCMSAttribute *oattr;
|
|
void *mark;
|
|
SECOidTag type;
|
|
|
|
mark = PORT_ArenaMark(poolp);
|
|
|
|
/* find oidtag of attr */
|
|
type = my_NSS_CMSAttribute_GetType(attr);
|
|
|
|
/* see if we have one already */
|
|
oattr = my_NSS_CMSAttributeArray_FindAttrByOidTag(*attrs, type, PR_FALSE);
|
|
PORT_Assert (oattr == NULL);
|
|
if (oattr != nullptr)
|
|
goto loser; /* XXX or would it be better to replace it? */
|
|
|
|
/* no, shove it in */
|
|
if (my_NSS_CMSArray_Add(poolp, reinterpret_cast<void ***>(attrs), static_cast<void *>(attr)) != SECSuccess)
|
|
goto loser;
|
|
|
|
PORT_ArenaUnmark(poolp, mark);
|
|
return SECSuccess;
|
|
|
|
loser:
|
|
PORT_ArenaRelease(poolp, mark);
|
|
return SECFailure;
|
|
}
|
|
|
|
SECStatus
|
|
my_NSS_CMSSignerInfo_AddUnauthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr)
|
|
{
|
|
return my_NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->unAuthAttr), attr);
|
|
}
|
|
|
|
SECStatus
|
|
my_NSS_CMSSignerInfo_AddAuthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr)
|
|
{
|
|
return my_NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->authAttr), attr);
|
|
}
|
|
|
|
NSSCMSMessage *CreateCMSMessage(const PRTime* time,
|
|
NSSCMSSignedData **cms_sd,
|
|
NSSCMSSignerInfo **cms_signer,
|
|
CERTCertificate *cert,
|
|
SECItem *digest)
|
|
{
|
|
NSSCMSMessage *result = NSS_CMSMessage_Create(nullptr);
|
|
if (!result)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSMessage_Create failed");
|
|
return nullptr;
|
|
}
|
|
|
|
*cms_sd = NSS_CMSSignedData_Create(result);
|
|
if (!*cms_sd)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSSignedData_Create failed");
|
|
NSS_CMSMessage_Destroy(result);
|
|
return nullptr;
|
|
}
|
|
|
|
NSSCMSContentInfo *cms_cinfo = NSS_CMSMessage_GetContentInfo(result);
|
|
if (NSS_CMSContentInfo_SetContent_SignedData(result, cms_cinfo, *cms_sd) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSContentInfo_SetContent_SignedData failed");
|
|
NSS_CMSSignedData_Destroy(*cms_sd);
|
|
NSS_CMSMessage_Destroy(result);
|
|
return nullptr;
|
|
}
|
|
|
|
cms_cinfo = NSS_CMSSignedData_GetContentInfo(*cms_sd);
|
|
|
|
// Attach NULL data as detached data
|
|
if (NSS_CMSContentInfo_SetContent_Data(result, cms_cinfo, nullptr, PR_TRUE) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSContentInfo_SetContent_Data failed");
|
|
NSS_CMSSignedData_Destroy(*cms_sd);
|
|
NSS_CMSMessage_Destroy(result);
|
|
return nullptr;
|
|
}
|
|
|
|
// workaround: with legacy "dbm:", NSS can't find the private key - try out
|
|
// if it works, and fallback if it doesn't.
|
|
if (SECKEYPrivateKey * pPrivateKey = PK11_FindKeyByAnyCert(cert, nullptr))
|
|
{
|
|
if (!comphelper::LibreOfficeKit::isActive())
|
|
{
|
|
// pPrivateKey only exists in the memory in the LOK case, don't delete it.
|
|
SECKEY_DestroyPrivateKey(pPrivateKey);
|
|
}
|
|
*cms_signer = NSS_CMSSignerInfo_Create(result, cert, SEC_OID_SHA256);
|
|
}
|
|
else
|
|
{
|
|
pPrivateKey = PK11_FindKeyByDERCert(cert->slot, cert, nullptr);
|
|
SECKEYPublicKey *const pPublicKey = CERT_ExtractPublicKey(cert);
|
|
if (pPublicKey && pPrivateKey)
|
|
{
|
|
*cms_signer = NSS_CMSSignerInfo_CreateWithSubjKeyID(result, &cert->subjectKeyID, pPublicKey, pPrivateKey, SEC_OID_SHA256);
|
|
SECKEY_DestroyPrivateKey(pPrivateKey);
|
|
SECKEY_DestroyPublicKey(pPublicKey);
|
|
if (*cms_signer)
|
|
{
|
|
// this is required in NSS_CMSSignerInfo_IncludeCerts()
|
|
// (and NSS_CMSSignerInfo_GetSigningCertificate() doesn't work)
|
|
(**cms_signer).cert = CERT_DupCertificate(cert);
|
|
}
|
|
}
|
|
}
|
|
if (!*cms_signer)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSSignerInfo_Create failed");
|
|
NSS_CMSSignedData_Destroy(*cms_sd);
|
|
NSS_CMSMessage_Destroy(result);
|
|
return nullptr;
|
|
}
|
|
|
|
if (time && NSS_CMSSignerInfo_AddSigningTime(*cms_signer, *time) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSSignerInfo_AddSigningTime failed");
|
|
NSS_CMSSignedData_Destroy(*cms_sd);
|
|
NSS_CMSMessage_Destroy(result);
|
|
return nullptr;
|
|
}
|
|
|
|
if (NSS_CMSSignerInfo_IncludeCerts(*cms_signer, NSSCMSCM_CertChain, certUsageEmailSigner) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSSignerInfo_IncludeCerts failed");
|
|
NSS_CMSSignedData_Destroy(*cms_sd);
|
|
NSS_CMSMessage_Destroy(result);
|
|
return nullptr;
|
|
}
|
|
|
|
if (NSS_CMSSignedData_AddCertificate(*cms_sd, cert) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSSignedData_AddCertificate failed");
|
|
NSS_CMSSignedData_Destroy(*cms_sd);
|
|
NSS_CMSMessage_Destroy(result);
|
|
return nullptr;
|
|
}
|
|
|
|
if (NSS_CMSSignedData_AddSignerInfo(*cms_sd, *cms_signer) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSSignedData_AddSignerInfo failed");
|
|
NSS_CMSSignedData_Destroy(*cms_sd);
|
|
NSS_CMSMessage_Destroy(result);
|
|
return nullptr;
|
|
}
|
|
|
|
if (NSS_CMSSignedData_SetDigestValue(*cms_sd, SEC_OID_SHA256, digest) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSSignedData_SetDigestValue failed");
|
|
NSS_CMSSignedData_Destroy(*cms_sd);
|
|
NSS_CMSMessage_Destroy(result);
|
|
return nullptr;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#elif USE_CRYPTO_MSCAPI // ends USE_CRYPTO_NSS
|
|
|
|
/// Counts how many bytes are needed to encode a given length.
|
|
size_t GetDERLengthOfLength(size_t nLength)
|
|
{
|
|
size_t nRet = 1;
|
|
|
|
if(nLength > 127)
|
|
{
|
|
while (nLength >> (nRet * 8))
|
|
++nRet;
|
|
// Long form means one additional byte: the length of the length and
|
|
// the length itself.
|
|
++nRet;
|
|
}
|
|
return nRet;
|
|
}
|
|
|
|
/// Writes the length part of the header.
|
|
void WriteDERLength(SvStream& rStream, size_t nLength)
|
|
{
|
|
size_t nLengthOfLength = GetDERLengthOfLength(nLength);
|
|
if (nLengthOfLength == 1)
|
|
{
|
|
// We can use the short form.
|
|
rStream.WriteUInt8(nLength);
|
|
return;
|
|
}
|
|
|
|
// 0x80 means that the we use the long form: the first byte is the length
|
|
// of length with the highest bit set to 1, not the actual length.
|
|
rStream.WriteUInt8(0x80 | (nLengthOfLength - 1));
|
|
for (size_t i = 1; i < nLengthOfLength; ++i)
|
|
rStream.WriteUInt8(nLength >> ((nLengthOfLength - i - 1) * 8));
|
|
}
|
|
|
|
const unsigned nASN1_INTEGER = 0x02;
|
|
const unsigned nASN1_OCTET_STRING = 0x04;
|
|
const unsigned nASN1_NULL = 0x05;
|
|
const unsigned nASN1_OBJECT_IDENTIFIER = 0x06;
|
|
const unsigned nASN1_SEQUENCE = 0x10;
|
|
/// An explicit tag on a constructed value.
|
|
const unsigned nASN1_TAGGED_CONSTRUCTED = 0xa0;
|
|
const unsigned nASN1_CONSTRUCTED = 0x20;
|
|
|
|
/// Create payload for the 'signing-certificate' signed attribute.
|
|
bool CreateSigningCertificateAttribute(void const * pDerEncoded, int nDerEncoded, PCCERT_CONTEXT pCertContext, 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 (!CryptAcquireContextW(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptAcquireContext() failed");
|
|
return false;
|
|
}
|
|
|
|
HCRYPTHASH hHash = 0;
|
|
if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptCreateHash() failed");
|
|
return false;
|
|
}
|
|
|
|
if (!CryptHashData(hHash, static_cast<const BYTE*>(pDerEncoded), nDerEncoded, 0))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptHashData() failed");
|
|
return false;
|
|
}
|
|
|
|
DWORD nHash = 0;
|
|
if (!CryptGetHashParam(hHash, HP_HASHVAL, nullptr, &nHash, 0))
|
|
{
|
|
SAL_WARN("svl.crypto", "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("svl.crypto", "CryptGetHashParam() failed to provide the hash");
|
|
return false;
|
|
}
|
|
|
|
CryptDestroyHash(hHash);
|
|
CryptReleaseContext(hProv, 0);
|
|
|
|
// Collect info for IssuerSerial.
|
|
BYTE* pIssuer = pCertContext->pCertInfo->Issuer.pbData;
|
|
DWORD nIssuer = pCertContext->pCertInfo->Issuer.cbData;
|
|
BYTE* pSerial = pCertContext->pCertInfo->SerialNumber.pbData;
|
|
DWORD nSerial = pCertContext->pCertInfo->SerialNumber.cbData;
|
|
// pSerial is LE, aSerial is BE.
|
|
std::vector<BYTE> aSerial(nSerial);
|
|
for (size_t i = 0; i < nSerial; ++i)
|
|
aSerial[i] = *(pSerial + nSerial - i - 1);
|
|
|
|
// 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
|
|
// SEQUENCE: IssuerSerial
|
|
// SEQUENCE: GeneralNames
|
|
// cont [ 4 ]: Name
|
|
// SEQUENCE: Issuer blob
|
|
// INTEGER: CertificateSerialNumber
|
|
|
|
size_t nAlgorithm = 1 + GetDERLengthOfLength(aSHA256.size()) + aSHA256.size();
|
|
size_t nParameters = 1 + GetDERLengthOfLength(1);
|
|
size_t nAlgorithmIdentifier = 1 + GetDERLengthOfLength(nAlgorithm + nParameters) + nAlgorithm + nParameters;
|
|
size_t nCertHash = 1 + GetDERLengthOfLength(aHash.size()) + aHash.size();
|
|
size_t nName = 1 + GetDERLengthOfLength(nIssuer) + nIssuer;
|
|
size_t nGeneralNames = 1 + GetDERLengthOfLength(nName) + nName;
|
|
size_t nCertificateSerialNumber = 1 + GetDERLengthOfLength(nSerial) + nSerial;
|
|
size_t nIssuerSerial = 1 + GetDERLengthOfLength(nGeneralNames + nCertificateSerialNumber) + nGeneralNames + nCertificateSerialNumber;
|
|
size_t nESSCertIDv2 = 1 + GetDERLengthOfLength(nAlgorithmIdentifier + nCertHash + nIssuerSerial) + nAlgorithmIdentifier + nCertHash + nIssuerSerial;
|
|
size_t nESSCertIDv2s = 1 + GetDERLengthOfLength(nESSCertIDv2) + nESSCertIDv2;
|
|
|
|
// Write SigningCertificateV2.
|
|
rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
|
|
WriteDERLength(rEncodedCertificate, nESSCertIDv2s);
|
|
// Write SEQUENCE OF ESSCertIDv2.
|
|
rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
|
|
WriteDERLength(rEncodedCertificate, nESSCertIDv2);
|
|
// Write ESSCertIDv2.
|
|
rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
|
|
WriteDERLength(rEncodedCertificate, nAlgorithmIdentifier + nCertHash + nIssuerSerial);
|
|
// Write AlgorithmIdentifier.
|
|
rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
|
|
WriteDERLength(rEncodedCertificate, nAlgorithm + nParameters);
|
|
// Write algorithm.
|
|
rEncodedCertificate.WriteUInt8(nASN1_OBJECT_IDENTIFIER);
|
|
WriteDERLength(rEncodedCertificate, aSHA256.size());
|
|
rEncodedCertificate.WriteBytes(aSHA256.data(), aSHA256.size());
|
|
// Write parameters.
|
|
rEncodedCertificate.WriteUInt8(nASN1_NULL);
|
|
rEncodedCertificate.WriteUInt8(0);
|
|
// Write certHash.
|
|
rEncodedCertificate.WriteUInt8(nASN1_OCTET_STRING);
|
|
WriteDERLength(rEncodedCertificate, aHash.size());
|
|
rEncodedCertificate.WriteBytes(aHash.data(), aHash.size());
|
|
// Write IssuerSerial.
|
|
rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
|
|
WriteDERLength(rEncodedCertificate, nGeneralNames + nCertificateSerialNumber);
|
|
// Write GeneralNames.
|
|
rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
|
|
WriteDERLength(rEncodedCertificate, nName);
|
|
// Write Name.
|
|
rEncodedCertificate.WriteUInt8(nASN1_TAGGED_CONSTRUCTED | 4);
|
|
WriteDERLength(rEncodedCertificate, nIssuer);
|
|
rEncodedCertificate.WriteBytes(pIssuer, nIssuer);
|
|
// Write CertificateSerialNumber.
|
|
rEncodedCertificate.WriteUInt8(nASN1_INTEGER);
|
|
WriteDERLength(rEncodedCertificate, nSerial);
|
|
rEncodedCertificate.WriteBytes(aSerial.data(), aSerial.size());
|
|
|
|
return true;
|
|
}
|
|
#endif // USE_CRYPTO_MSCAPI
|
|
|
|
} // anonymous namespace
|
|
|
|
namespace svl::crypto {
|
|
|
|
static int AsHex(char ch)
|
|
{
|
|
int nRet = 0;
|
|
if (rtl::isAsciiDigit(static_cast<unsigned char>(ch)))
|
|
nRet = ch - '0';
|
|
else
|
|
{
|
|
if (ch >= 'a' && ch <= 'f')
|
|
nRet = ch - 'a';
|
|
else if (ch >= 'A' && ch <= 'F')
|
|
nRet = ch - 'A';
|
|
else
|
|
return -1;
|
|
nRet += 10;
|
|
}
|
|
return nRet;
|
|
}
|
|
|
|
std::vector<unsigned char> DecodeHexString(std::string_view rHex)
|
|
{
|
|
std::vector<unsigned char> aRet;
|
|
size_t nHexLen = rHex.size();
|
|
{
|
|
int nByte = 0;
|
|
int nCount = 2;
|
|
for (size_t i = 0; i < nHexLen; ++i)
|
|
{
|
|
nByte = nByte << 4;
|
|
sal_Int8 nParsed = AsHex(rHex[i]);
|
|
if (nParsed == -1)
|
|
{
|
|
SAL_WARN("svl.crypto", "DecodeHexString: invalid hex value");
|
|
return aRet;
|
|
}
|
|
nByte += nParsed;
|
|
--nCount;
|
|
if (!nCount)
|
|
{
|
|
aRet.push_back(nByte);
|
|
nCount = 2;
|
|
nByte = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return aRet;
|
|
}
|
|
|
|
bool Signing::Sign(OStringBuffer& rCMSHexBuffer)
|
|
{
|
|
#if !USE_CRYPTO_ANY
|
|
(void)rCMSHexBuffer;
|
|
return false;
|
|
#else
|
|
// Create the PKCS#7 object.
|
|
css::uno::Sequence<sal_Int8> aDerEncoded;
|
|
if (m_rSigningContext.m_xCertificate.is())
|
|
{
|
|
aDerEncoded = m_rSigningContext.m_xCertificate->getEncoded();
|
|
if (!aDerEncoded.hasElements())
|
|
{
|
|
SAL_WARN("svl.crypto", "Crypto::Signing: empty certificate");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#if USE_CRYPTO_NSS
|
|
std::vector<unsigned char> aHashResult;
|
|
{
|
|
comphelper::Hash aHash(comphelper::HashType::SHA256);
|
|
|
|
for (const auto& pair : m_dataBlocks)
|
|
aHash.update(static_cast<const unsigned char*>(pair.first), pair.second);
|
|
|
|
aHashResult = aHash.finalize();
|
|
}
|
|
SECItem digest;
|
|
digest.data = aHashResult.data();
|
|
digest.len = aHashResult.size();
|
|
|
|
PRTime now = PR_Now();
|
|
|
|
// The context unit is milliseconds, PR_Now() unit is microseconds.
|
|
if (m_rSigningContext.m_nSignatureTime)
|
|
{
|
|
now = m_rSigningContext.m_nSignatureTime * 1000;
|
|
}
|
|
else
|
|
{
|
|
m_rSigningContext.m_nSignatureTime = now / 1000;
|
|
}
|
|
|
|
if (!m_rSigningContext.m_xCertificate.is())
|
|
{
|
|
m_rSigningContext.m_aDigest = std::move(aHashResult);
|
|
// No certificate is provided: don't actually sign -- just update the context with the
|
|
// parameters for the signing and return.
|
|
return false;
|
|
}
|
|
|
|
CERTCertificate *cert = CERT_DecodeCertFromPackage(reinterpret_cast<char *>(aDerEncoded.getArray()), aDerEncoded.getLength());
|
|
|
|
if (!cert)
|
|
{
|
|
SAL_WARN("svl.crypto", "CERT_DecodeCertFromPackage failed");
|
|
return false;
|
|
}
|
|
|
|
NSSCMSSignedData *cms_sd(nullptr);
|
|
NSSCMSSignerInfo *cms_signer(nullptr);
|
|
NSSCMSMessage *cms_msg = CreateCMSMessage(nullptr, &cms_sd, &cms_signer, cert, &digest);
|
|
if (!cms_msg)
|
|
return false;
|
|
|
|
OString pass(OUStringToOString( m_aSignPassword, RTL_TEXTENCODING_UTF8 ));
|
|
|
|
// Add the signing certificate as a signed attribute.
|
|
ESSCertIDv2* aCertIDs[2];
|
|
ESSCertIDv2 aCertID;
|
|
// Write ESSCertIDv2.hashAlgorithm.
|
|
aCertID.hashAlgorithm.algorithm.data = nullptr;
|
|
aCertID.hashAlgorithm.parameters.data = nullptr;
|
|
SECOID_SetAlgorithmID(nullptr, &aCertID.hashAlgorithm, SEC_OID_SHA256, nullptr);
|
|
comphelper::ScopeGuard aAlgoGuard(
|
|
[&aCertID] () { SECOID_DestroyAlgorithmID(&aCertID.hashAlgorithm, false); } );
|
|
// Write ESSCertIDv2.certHash.
|
|
SECItem aCertHashItem;
|
|
auto pDerEncoded = reinterpret_cast<const unsigned char *>(aDerEncoded.getArray());
|
|
std::vector<unsigned char> aCertHashResult = comphelper::Hash::calculateHash(pDerEncoded, aDerEncoded.getLength(), comphelper::HashType::SHA256);
|
|
aCertHashItem.type = siBuffer;
|
|
aCertHashItem.data = aCertHashResult.data();
|
|
aCertHashItem.len = aCertHashResult.size();
|
|
aCertID.certHash = aCertHashItem;
|
|
// Write ESSCertIDv2.issuerSerial.
|
|
IssuerSerial aSerial;
|
|
GeneralName aName;
|
|
aName.name = cert->issuer;
|
|
aSerial.issuer.names = aName;
|
|
aSerial.serialNumber = cert->serialNumber;
|
|
aCertID.issuerSerial = aSerial;
|
|
// Write SigningCertificateV2.certs.
|
|
aCertIDs[0] = &aCertID;
|
|
aCertIDs[1] = nullptr;
|
|
SigningCertificateV2 aCertificate;
|
|
aCertificate.certs = &aCertIDs[0];
|
|
SECItem* pEncodedCertificate = SEC_ASN1EncodeItem(nullptr, nullptr, &aCertificate, SigningCertificateV2Template);
|
|
if (!pEncodedCertificate)
|
|
{
|
|
SAL_WARN("svl.crypto", "SEC_ASN1EncodeItem() failed");
|
|
return false;
|
|
}
|
|
|
|
NSSCMSAttribute aAttribute;
|
|
SECItem aAttributeValues[2];
|
|
SECItem* pAttributeValues[2];
|
|
pAttributeValues[0] = aAttributeValues;
|
|
pAttributeValues[1] = nullptr;
|
|
aAttributeValues[0] = *pEncodedCertificate;
|
|
aAttributeValues[1].type = siBuffer;
|
|
aAttributeValues[1].data = nullptr;
|
|
aAttributeValues[1].len = 0;
|
|
aAttribute.values = pAttributeValues;
|
|
|
|
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 (my_SEC_StringToOID(&aOidData.oid, "1.2.840.113549.1.9.16.2.47", 0) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "my_SEC_StringToOID() failed");
|
|
return false;
|
|
}
|
|
comphelper::ScopeGuard aGuard(
|
|
[&aOidData] () { SECITEM_FreeItem(&aOidData.oid, false); } );
|
|
aOidData.offset = SEC_OID_UNKNOWN;
|
|
aOidData.desc = "id-aa-signingCertificateV2";
|
|
aOidData.mechanism = CKM_SHA_1;
|
|
aOidData.supportedExtension = UNSUPPORTED_CERT_EXTENSION;
|
|
aAttribute.typeTag = &aOidData;
|
|
aAttribute.type = aOidData.oid;
|
|
aAttribute.encoded = PR_TRUE;
|
|
|
|
if (my_NSS_CMSSignerInfo_AddAuthAttr(cms_signer, &aAttribute) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "my_NSS_CMSSignerInfo_AddAuthAttr() failed");
|
|
return false;
|
|
}
|
|
|
|
TimeStampReq src;
|
|
OStringBuffer response_buffer;
|
|
TimeStampResp response;
|
|
SECItem response_item;
|
|
NSSCMSAttribute timestamp;
|
|
SECItem values[2];
|
|
SECItem *valuesp[2];
|
|
valuesp[0] = values;
|
|
valuesp[1] = nullptr;
|
|
SECOidData typetag;
|
|
|
|
//NOTE: All signed/authenticated attributes are to be added before the following hash computation
|
|
if( !m_aSignTSA.isEmpty() )
|
|
{
|
|
// Create another CMS message with the same contents as cms_msg, because it doesn't seem
|
|
// possible to encode a message twice (once to get something to timestamp, and then after
|
|
// adding the timestamp attribute).
|
|
|
|
NSSCMSSignedData *ts_cms_sd;
|
|
NSSCMSSignerInfo *ts_cms_signer;
|
|
NSSCMSMessage *ts_cms_msg = CreateCMSMessage(&now, &ts_cms_sd, &ts_cms_signer, cert, &digest);
|
|
if (!ts_cms_msg)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
PORT_Memcpy(ts_cms_signer, cms_signer, sizeof(NSSCMSSignerInfo));
|
|
|
|
SECItem ts_cms_output;
|
|
ts_cms_output.data = nullptr;
|
|
ts_cms_output.len = 0;
|
|
PLArenaPool *ts_arena = PORT_NewArena(10000);
|
|
NSSCMSEncoderContext *ts_cms_ecx;
|
|
ts_cms_ecx = NSS_CMSEncoder_Start(ts_cms_msg, nullptr, nullptr, &ts_cms_output, ts_arena, PDFSigningPKCS7PasswordCallback,
|
|
const_cast<char*>(pass.getStr()), nullptr, nullptr, nullptr, nullptr);
|
|
|
|
if (NSS_CMSEncoder_Finish(ts_cms_ecx) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSEncoder_Finish failed");
|
|
return false;
|
|
}
|
|
|
|
// I have compared the ts_cms_output produced here with the cms_output produced below when
|
|
// not actually calling my_NSS_CMSSignerInfo_AddUnauthAttr()), and they are identical.
|
|
|
|
std::vector<unsigned char> aTsHashResult = comphelper::Hash::calculateHash(ts_cms_signer->encDigest.data, ts_cms_signer->encDigest.len, comphelper::HashType::SHA256);
|
|
SECItem ts_digest;
|
|
ts_digest.type = siBuffer;
|
|
ts_digest.data = aTsHashResult.data();
|
|
ts_digest.len = aTsHashResult.size();
|
|
|
|
unsigned char cOne = 1;
|
|
unsigned char cTRUE = 0xff; // under DER rules true is 0xff, false is 0x00
|
|
src.version.type = siUnsignedInteger;
|
|
src.version.data = &cOne;
|
|
src.version.len = sizeof(cOne);
|
|
|
|
src.messageImprint.hashAlgorithm.algorithm.data = nullptr;
|
|
src.messageImprint.hashAlgorithm.parameters.data = nullptr;
|
|
SECOID_SetAlgorithmID(nullptr, &src.messageImprint.hashAlgorithm, SEC_OID_SHA256, nullptr);
|
|
src.messageImprint.hashedMessage = ts_digest;
|
|
|
|
src.reqPolicy.type = siBuffer;
|
|
src.reqPolicy.data = nullptr;
|
|
src.reqPolicy.len = 0;
|
|
|
|
unsigned int nNonce = comphelper::rng::uniform_uint_distribution(0, SAL_MAX_UINT32);
|
|
src.nonce.type = siUnsignedInteger;
|
|
src.nonce.data = reinterpret_cast<unsigned char*>(&nNonce);
|
|
src.nonce.len = sizeof(nNonce);
|
|
|
|
src.certReq.type = siUnsignedInteger;
|
|
src.certReq.data = &cTRUE;
|
|
src.certReq.len = sizeof(cTRUE);
|
|
|
|
src.extensions = nullptr;
|
|
|
|
SECItem* timestamp_request = SEC_ASN1EncodeItem(nullptr, nullptr, &src, TimeStampReq_Template);
|
|
if (timestamp_request == nullptr)
|
|
{
|
|
SAL_WARN("svl.crypto", "SEC_ASN1EncodeItem failed");
|
|
return false;
|
|
}
|
|
|
|
if (timestamp_request->data == nullptr)
|
|
{
|
|
SAL_WARN("svl.crypto", "SEC_ASN1EncodeItem succeeded but got NULL data");
|
|
SECITEM_FreeItem(timestamp_request, PR_TRUE);
|
|
return false;
|
|
}
|
|
|
|
SAL_INFO("svl.crypto", "request length=" << timestamp_request->len);
|
|
|
|
// Send time stamp request to TSA server, receive response
|
|
|
|
CURL* curl = curl_easy_init();
|
|
CURLcode rc;
|
|
struct curl_slist* slist = nullptr;
|
|
|
|
if (!curl)
|
|
{
|
|
SAL_WARN("svl.crypto", "curl_easy_init failed");
|
|
SECITEM_FreeItem(timestamp_request, PR_TRUE);
|
|
return false;
|
|
}
|
|
|
|
::InitCurl_easy(curl);
|
|
|
|
SAL_INFO("svl.crypto", "Setting curl to verbose: " << (curl_easy_setopt(curl, CURLOPT_VERBOSE, 1) == CURLE_OK ? "OK" : "FAIL"));
|
|
|
|
if ((rc = curl_easy_setopt(curl, CURLOPT_URL, OUStringToOString(m_aSignTSA, RTL_TEXTENCODING_UTF8).getStr())) != CURLE_OK)
|
|
{
|
|
SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_URL) failed: " << curl_easy_strerror(rc));
|
|
curl_easy_cleanup(curl);
|
|
SECITEM_FreeItem(timestamp_request, PR_TRUE);
|
|
return false;
|
|
}
|
|
|
|
slist = curl_slist_append(slist, "Content-Type: application/timestamp-query");
|
|
slist = curl_slist_append(slist, "Accept: application/timestamp-reply");
|
|
|
|
if ((rc = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist)) != CURLE_OK)
|
|
{
|
|
SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_HTTPHEADER) failed: " << curl_easy_strerror(rc));
|
|
curl_slist_free_all(slist);
|
|
curl_easy_cleanup(curl);
|
|
SECITEM_FreeItem(timestamp_request, PR_TRUE);
|
|
return false;
|
|
}
|
|
|
|
if ((rc = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<tools::Long>(timestamp_request->len))) != CURLE_OK ||
|
|
(rc = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, timestamp_request->data)) != CURLE_OK)
|
|
{
|
|
SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_POSTFIELDSIZE or CURLOPT_POSTFIELDS) failed: " << curl_easy_strerror(rc));
|
|
curl_easy_cleanup(curl);
|
|
SECITEM_FreeItem(timestamp_request, PR_TRUE);
|
|
return false;
|
|
}
|
|
|
|
if ((rc = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_buffer)) != CURLE_OK ||
|
|
(rc = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, AppendToBuffer)) != CURLE_OK)
|
|
{
|
|
SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_WRITEDATA or CURLOPT_WRITEFUNCTION) failed: " << curl_easy_strerror(rc));
|
|
curl_easy_cleanup(curl);
|
|
SECITEM_FreeItem(timestamp_request, PR_TRUE);
|
|
return false;
|
|
}
|
|
|
|
if ((rc = curl_easy_setopt(curl, CURLOPT_POST, 1)) != CURLE_OK)
|
|
{
|
|
SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_POST) failed: " << curl_easy_strerror(rc));
|
|
curl_easy_cleanup(curl);
|
|
SECITEM_FreeItem(timestamp_request, PR_TRUE);
|
|
return false;
|
|
}
|
|
|
|
char error_buffer[CURL_ERROR_SIZE];
|
|
if ((rc = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buffer)) != CURLE_OK)
|
|
{
|
|
SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_ERRORBUFFER) failed: " << curl_easy_strerror(rc));
|
|
curl_easy_cleanup(curl);
|
|
SECITEM_FreeItem(timestamp_request, PR_TRUE);
|
|
return false;
|
|
}
|
|
|
|
// Use a ten second timeout
|
|
if ((rc = curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10)) != CURLE_OK ||
|
|
(rc = curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10)) != CURLE_OK)
|
|
{
|
|
SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_TIMEOUT or CURLOPT_CONNECTTIMEOUT) failed: " << curl_easy_strerror(rc));
|
|
curl_easy_cleanup(curl);
|
|
SECITEM_FreeItem(timestamp_request, PR_TRUE);
|
|
return false;
|
|
}
|
|
|
|
if (curl_easy_perform(curl) != CURLE_OK)
|
|
{
|
|
SAL_WARN("svl.crypto", "curl_easy_perform failed: " << error_buffer);
|
|
curl_easy_cleanup(curl);
|
|
SECITEM_FreeItem(timestamp_request, PR_TRUE);
|
|
return false;
|
|
}
|
|
|
|
SAL_INFO("svl.crypto", "PDF signing: got response, length=" << response_buffer.getLength());
|
|
|
|
curl_slist_free_all(slist);
|
|
curl_easy_cleanup(curl);
|
|
SECITEM_FreeItem(timestamp_request, PR_TRUE);
|
|
|
|
memset(&response, 0, sizeof(response));
|
|
|
|
response_item.type = siBuffer;
|
|
response_item.data = reinterpret_cast<unsigned char*>(const_cast<char*>(response_buffer.getStr()));
|
|
response_item.len = response_buffer.getLength();
|
|
|
|
if (SEC_ASN1DecodeItem(nullptr, &response, TimeStampResp_Template, &response_item) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "SEC_ASN1DecodeItem failed");
|
|
return false;
|
|
}
|
|
|
|
SAL_INFO("svl.crypto", "TimeStampResp received and decoded, status=" << PKIStatusInfoToString(response.status));
|
|
|
|
if (response.status.status.len != 1 ||
|
|
(response.status.status.data[0] != 0 && response.status.status.data[0] != 1))
|
|
{
|
|
SAL_WARN("svl.crypto", "Timestamp request was not granted");
|
|
return false;
|
|
}
|
|
|
|
// timestamp.type filled in below
|
|
|
|
// Not sure if we actually need two entries in the values array, now when valuesp is an
|
|
// array too, the pointer to the values array followed by a null pointer. But I don't feel
|
|
// like experimenting.
|
|
values[0] = response.timeStampToken;
|
|
values[1].type = siBuffer;
|
|
values[1].data = nullptr;
|
|
values[1].len = 0;
|
|
|
|
timestamp.values = valuesp;
|
|
|
|
typetag.oid.data = nullptr;
|
|
// id-aa-timeStampToken OBJECT IDENTIFIER ::= { iso(1)
|
|
// member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9)
|
|
// smime(16) aa(2) 14 }
|
|
if (my_SEC_StringToOID(&typetag.oid, "1.2.840.113549.1.9.16.2.14", 0) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "SEC_StringToOID failed");
|
|
return false;
|
|
}
|
|
typetag.offset = SEC_OID_UNKNOWN; // ???
|
|
typetag.desc = "id-aa-timeStampToken";
|
|
typetag.mechanism = CKM_SHA_1; // ???
|
|
typetag.supportedExtension = UNSUPPORTED_CERT_EXTENSION; // ???
|
|
timestamp.typeTag = &typetag;
|
|
|
|
timestamp.type = typetag.oid; // ???
|
|
|
|
timestamp.encoded = PR_TRUE; // ???
|
|
|
|
if (my_NSS_CMSSignerInfo_AddUnauthAttr(cms_signer, ×tamp) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSSignerInfo_AddUnauthAttr failed");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
SECItem cms_output;
|
|
cms_output.data = nullptr;
|
|
cms_output.len = 0;
|
|
PLArenaPool *arena = PORT_NewArena(10000);
|
|
const ::comphelper::ScopeGuard aScopeGuard(
|
|
[&arena]() mutable { PORT_FreeArena(arena, true); } );
|
|
NSSCMSEncoderContext *cms_ecx;
|
|
|
|
// Possibly it would work to even just pass NULL for the password callback function and its
|
|
// argument here. After all, at least with the hardware token and associated software I tested
|
|
// with, the software itself pops up a dialog asking for the PIN (password). But I am not going
|
|
// to test it and risk locking up my token...
|
|
|
|
cms_ecx = NSS_CMSEncoder_Start(cms_msg, nullptr, nullptr, &cms_output, arena, PDFSigningPKCS7PasswordCallback,
|
|
const_cast<char*>(pass.getStr()), nullptr, nullptr, nullptr, nullptr);
|
|
|
|
if (!cms_ecx)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSEncoder_Start failed");
|
|
return false;
|
|
}
|
|
|
|
if (NSS_CMSEncoder_Finish(cms_ecx) != SECSuccess)
|
|
{
|
|
SAL_WARN("svl.crypto", "NSS_CMSEncoder_Finish failed");
|
|
return false;
|
|
}
|
|
|
|
if (cms_output.len*2 > MAX_SIGNATURE_CONTENT_LENGTH)
|
|
{
|
|
SAL_WARN("svl.crypto", "Signature requires more space (" << cms_output.len*2 << ") than we reserved (" << MAX_SIGNATURE_CONTENT_LENGTH << ")");
|
|
NSS_CMSMessage_Destroy(cms_msg);
|
|
return false;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < cms_output.len ; i++)
|
|
appendHex(cms_output.data[i], rCMSHexBuffer);
|
|
|
|
SECITEM_FreeItem(pEncodedCertificate, PR_TRUE);
|
|
NSS_CMSMessage_Destroy(cms_msg);
|
|
|
|
return true;
|
|
|
|
#elif USE_CRYPTO_MSCAPI // ends USE_CRYPTO_NSS
|
|
|
|
PCCERT_CONTEXT pCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, reinterpret_cast<const BYTE*>(aDerEncoded.getArray()), aDerEncoded.getLength());
|
|
if (pCertContext == nullptr)
|
|
{
|
|
SAL_WARN("svl.crypto", "CertCreateCertificateContext failed: " << WindowsErrorString(GetLastError()));
|
|
return false;
|
|
}
|
|
|
|
CRYPT_SIGN_MESSAGE_PARA aPara = {};
|
|
aPara.cbSize = sizeof(aPara);
|
|
aPara.dwMsgEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING;
|
|
aPara.pSigningCert = pCertContext;
|
|
aPara.HashAlgorithm.pszObjId = const_cast<LPSTR>(szOID_NIST_sha256);
|
|
aPara.HashAlgorithm.Parameters.cbData = 0;
|
|
aPara.cMsgCert = 1;
|
|
aPara.rgpMsgCert = &pCertContext;
|
|
|
|
NCRYPT_KEY_HANDLE hCryptKey = 0;
|
|
DWORD dwFlags = CRYPT_ACQUIRE_CACHE_FLAG | CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG;
|
|
HCRYPTPROV_OR_NCRYPT_KEY_HANDLE* phCryptProvOrNCryptKey = &hCryptKey;
|
|
DWORD nKeySpec;
|
|
BOOL bFreeNeeded;
|
|
|
|
if (!CryptAcquireCertificatePrivateKey(pCertContext,
|
|
dwFlags,
|
|
nullptr,
|
|
phCryptProvOrNCryptKey,
|
|
&nKeySpec,
|
|
&bFreeNeeded))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptAcquireCertificatePrivateKey failed: " << WindowsErrorString(GetLastError()));
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
assert(!bFreeNeeded);
|
|
|
|
CMSG_SIGNER_ENCODE_INFO aSignerInfo = {};
|
|
aSignerInfo.cbSize = sizeof(aSignerInfo);
|
|
aSignerInfo.pCertInfo = pCertContext->pCertInfo;
|
|
aSignerInfo.hNCryptKey = hCryptKey;
|
|
aSignerInfo.dwKeySpec = nKeySpec;
|
|
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(aDerEncoded.getArray(), aDerEncoded.getLength(), pCertContext, aEncodedCertificate))
|
|
{
|
|
SAL_WARN("svl.crypto", "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 = {};
|
|
aSignedInfo.cbSize = sizeof(aSignedInfo);
|
|
aSignedInfo.cSigners = 1;
|
|
aSignedInfo.rgSigners = &aSignerInfo;
|
|
|
|
CERT_BLOB aCertBlob;
|
|
|
|
aCertBlob.cbData = pCertContext->cbCertEncoded;
|
|
aCertBlob.pbData = pCertContext->pbCertEncoded;
|
|
|
|
aSignedInfo.cCertEncoded = 1;
|
|
aSignedInfo.rgCertEncoded = &aCertBlob;
|
|
|
|
HCRYPTMSG hMsg = CryptMsgOpenToEncode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
|
|
CMSG_DETACHED_FLAG,
|
|
CMSG_SIGNED,
|
|
&aSignedInfo,
|
|
nullptr,
|
|
nullptr);
|
|
if (!hMsg)
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptMsgOpenToEncode failed: " << WindowsErrorString(GetLastError()));
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0; i < m_dataBlocks.size(); ++i)
|
|
{
|
|
const bool last = (i == m_dataBlocks.size() - 1);
|
|
if (!CryptMsgUpdate(hMsg, static_cast<const BYTE *>(m_dataBlocks[i].first), m_dataBlocks[i].second, last))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptMsgUpdate failed: " << WindowsErrorString(GetLastError()));
|
|
CryptMsgClose(hMsg);
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
PCRYPT_TIMESTAMP_CONTEXT pTsContext = nullptr;
|
|
|
|
if( !m_aSignTSA.isEmpty() )
|
|
{
|
|
HCRYPTMSG hDecodedMsg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
|
|
CMSG_DETACHED_FLAG,
|
|
CMSG_SIGNED,
|
|
0,
|
|
nullptr,
|
|
nullptr);
|
|
if (!hDecodedMsg)
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptMsgOpenToDecode failed: " << WindowsErrorString(GetLastError()));
|
|
CryptMsgClose(hMsg);
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
|
|
DWORD nTsSigLen = 0;
|
|
|
|
if (!CryptMsgGetParam(hMsg, CMSG_BARE_CONTENT_PARAM, 0, nullptr, &nTsSigLen))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_BARE_CONTENT_PARAM) failed: " << WindowsErrorString(GetLastError()));
|
|
CryptMsgClose(hDecodedMsg);
|
|
CryptMsgClose(hMsg);
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
|
|
SAL_INFO("svl.crypto", "nTsSigLen=" << nTsSigLen);
|
|
|
|
std::unique_ptr<BYTE[]> pTsSig(new BYTE[nTsSigLen]);
|
|
|
|
if (!CryptMsgGetParam(hMsg, CMSG_BARE_CONTENT_PARAM, 0, pTsSig.get(), &nTsSigLen))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_BARE_CONTENT_PARAM) failed: " << WindowsErrorString(GetLastError()));
|
|
CryptMsgClose(hDecodedMsg);
|
|
CryptMsgClose(hMsg);
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
|
|
if (!CryptMsgUpdate(hDecodedMsg, pTsSig.get(), nTsSigLen, TRUE))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptMsgUpdate failed: " << WindowsErrorString(GetLastError()));
|
|
CryptMsgClose(hDecodedMsg);
|
|
CryptMsgClose(hMsg);
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
|
|
DWORD nDecodedSignerInfoLen = 0;
|
|
if (!CryptMsgGetParam(hDecodedMsg, CMSG_SIGNER_INFO_PARAM, 0, nullptr, &nDecodedSignerInfoLen))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_SIGNER_INFO_PARAM) failed: " << WindowsErrorString(GetLastError()));
|
|
CryptMsgClose(hDecodedMsg);
|
|
CryptMsgClose(hMsg);
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
|
|
std::unique_ptr<BYTE[]> pDecodedSignerInfoBuf(new BYTE[nDecodedSignerInfoLen]);
|
|
|
|
if (!CryptMsgGetParam(hDecodedMsg, CMSG_SIGNER_INFO_PARAM, 0, pDecodedSignerInfoBuf.get(), &nDecodedSignerInfoLen))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_SIGNER_INFO_PARAM) failed: " << WindowsErrorString(GetLastError()));
|
|
CryptMsgClose(hDecodedMsg);
|
|
CryptMsgClose(hMsg);
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
|
|
CMSG_SIGNER_INFO *pDecodedSignerInfo = reinterpret_cast<CMSG_SIGNER_INFO *>(pDecodedSignerInfoBuf.get());
|
|
|
|
CRYPT_TIMESTAMP_PARA aTsPara;
|
|
unsigned int nNonce = comphelper::rng::uniform_uint_distribution(0, SAL_MAX_UINT32);
|
|
|
|
aTsPara.pszTSAPolicyId = nullptr;
|
|
aTsPara.fRequestCerts = TRUE;
|
|
aTsPara.Nonce.cbData = sizeof(nNonce);
|
|
aTsPara.Nonce.pbData = reinterpret_cast<BYTE *>(&nNonce);
|
|
aTsPara.cExtension = 0;
|
|
aTsPara.rgExtension = nullptr;
|
|
|
|
if (!CryptRetrieveTimeStamp(o3tl::toW(m_aSignTSA.getStr()),
|
|
0,
|
|
10000,
|
|
szOID_NIST_sha256,
|
|
&aTsPara,
|
|
pDecodedSignerInfo->EncryptedHash.pbData,
|
|
pDecodedSignerInfo->EncryptedHash.cbData,
|
|
&pTsContext,
|
|
nullptr,
|
|
nullptr))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptRetrieveTimeStamp failed: " << WindowsErrorString(GetLastError()));
|
|
CryptMsgClose(hDecodedMsg);
|
|
CryptMsgClose(hMsg);
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
|
|
SAL_INFO("svl.crypto", "Time stamp size is " << pTsContext->cbEncoded << " bytes");
|
|
|
|
// I tried to use CryptMsgControl() with CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR to add the
|
|
// timestamp, but that failed with "The parameter is incorrect". Probably it is too late to
|
|
// modify the message once its data has already been encoded as part of the
|
|
// CryptMsgGetParam() with CMSG_BARE_CONTENT_PARAM above. So close the message and re-do its
|
|
// creation steps, but now with an amended aSignerInfo.
|
|
|
|
CRYPT_INTEGER_BLOB aTimestampBlob;
|
|
aTimestampBlob.cbData = pTsContext->cbEncoded;
|
|
aTimestampBlob.pbData = pTsContext->pbEncoded;
|
|
|
|
CRYPT_ATTRIBUTE aTimestampAttribute;
|
|
aTimestampAttribute.pszObjId = const_cast<LPSTR>(
|
|
"1.2.840.113549.1.9.16.2.14");
|
|
aTimestampAttribute.cValue = 1;
|
|
aTimestampAttribute.rgValue = &aTimestampBlob;
|
|
|
|
aSignerInfo.cUnauthAttr = 1;
|
|
aSignerInfo.rgUnauthAttr = &aTimestampAttribute;
|
|
|
|
CryptMsgClose(hMsg);
|
|
|
|
hMsg = CryptMsgOpenToEncode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
|
|
CMSG_DETACHED_FLAG,
|
|
CMSG_SIGNED,
|
|
&aSignedInfo,
|
|
nullptr,
|
|
nullptr);
|
|
|
|
for (size_t i = 0; i < m_dataBlocks.size(); ++i)
|
|
{
|
|
const bool last = (i == m_dataBlocks.size() - 1);
|
|
if (!hMsg ||
|
|
!CryptMsgUpdate(hMsg, static_cast<const BYTE *>(m_dataBlocks[i].first), m_dataBlocks[i].second, last))
|
|
{
|
|
SAL_WARN("svl.crypto", "Re-creating the message failed: " << WindowsErrorString(GetLastError()));
|
|
CryptMemFree(pTsContext);
|
|
CryptMsgClose(hDecodedMsg);
|
|
CryptMsgClose(hMsg);
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CryptMsgClose(hDecodedMsg);
|
|
}
|
|
|
|
DWORD nSigLen = 0;
|
|
|
|
if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, nullptr, &nSigLen))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_CONTENT_PARAM) failed: " << WindowsErrorString(GetLastError()));
|
|
if (pTsContext)
|
|
CryptMemFree(pTsContext);
|
|
CryptMsgClose(hMsg);
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
|
|
if (nSigLen*2 > MAX_SIGNATURE_CONTENT_LENGTH)
|
|
{
|
|
SAL_WARN("svl.crypto", "Signature requires more space (" << nSigLen*2 << ") than we reserved (" << MAX_SIGNATURE_CONTENT_LENGTH << ")");
|
|
if (pTsContext)
|
|
CryptMemFree(pTsContext);
|
|
CryptMsgClose(hMsg);
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
|
|
SAL_INFO("svl.crypto", "Signature size is " << nSigLen << " bytes");
|
|
std::unique_ptr<BYTE[]> pSig(new BYTE[nSigLen]);
|
|
|
|
if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, pSig.get(), &nSigLen))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_CONTENT_PARAM) failed: " << WindowsErrorString(GetLastError()));
|
|
if (pTsContext)
|
|
CryptMemFree(pTsContext);
|
|
CryptMsgClose(hMsg);
|
|
CertFreeCertificateContext(pCertContext);
|
|
return false;
|
|
}
|
|
|
|
// Release resources
|
|
if (pTsContext)
|
|
CryptMemFree(pTsContext);
|
|
CryptMsgClose(hMsg);
|
|
CertFreeCertificateContext(pCertContext);
|
|
|
|
for (unsigned int i = 0; i < nSigLen ; i++)
|
|
appendHex(pSig[i], rCMSHexBuffer);
|
|
|
|
return true;
|
|
#endif // USE_CRYPTO_MSCAPI
|
|
#endif // USE_CRYPTO_ANY
|
|
}
|
|
|
|
namespace
|
|
{
|
|
#if USE_CRYPTO_NSS
|
|
/// Similar to NSS_CMSAttributeArray_FindAttrByOidTag(), but works directly with a SECOidData.
|
|
NSSCMSAttribute* CMSAttributeArray_FindAttrByOidData(NSSCMSAttribute** attrs, SECOidData const * 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 && rtl::isAsciiDigit(static_cast<unsigned char>(*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 (static_cast<size_t>(num_bytes) + result_bytes > sizeof result)
|
|
goto bad_data;
|
|
tmp = num_bytes;
|
|
rp = result + result_bytes - 1;
|
|
rp[tmp] = static_cast<PRUint8>(decimal & 0x7f);
|
|
decimal >>= 7;
|
|
while (--tmp > 0)
|
|
{
|
|
rp[tmp] = static_cast<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)
|
|
{
|
|
to->len = result_bytes;
|
|
PORT_Memcpy(to->data, result, to->len);
|
|
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;
|
|
}
|
|
|
|
#elif USE_CRYPTO_MSCAPI // ends USE_CRYPTO_NSS
|
|
|
|
/// Verifies a non-detached signature using CryptoAPI.
|
|
bool VerifyNonDetachedSignature(const std::vector<unsigned char>& aData, const std::vector<BYTE>& rExpectedHash)
|
|
{
|
|
HCRYPTPROV hProv = 0;
|
|
if (!CryptAcquireContextW(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptAcquireContext() failed");
|
|
return false;
|
|
}
|
|
|
|
HCRYPTHASH hHash = 0;
|
|
if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptCreateHash() failed");
|
|
return false;
|
|
}
|
|
|
|
if (!CryptHashData(hHash, aData.data(), aData.size(), 0))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptHashData() failed");
|
|
return false;
|
|
}
|
|
|
|
DWORD nActualHash = 0;
|
|
if (!CryptGetHashParam(hHash, HP_HASHVAL, nullptr, &nActualHash, 0))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptGetHashParam() failed to provide the hash length");
|
|
return false;
|
|
}
|
|
|
|
std::vector<unsigned char> aActualHash(nActualHash);
|
|
if (!CryptGetHashParam(hHash, HP_HASHVAL, aActualHash.data(), &nActualHash, 0))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptGetHashParam() failed to provide the hash");
|
|
return false;
|
|
}
|
|
|
|
CryptDestroyHash(hHash);
|
|
CryptReleaseContext(hProv, 0);
|
|
|
|
return aActualHash.size() == rExpectedHash.size() &&
|
|
!std::memcmp(aActualHash.data(), rExpectedHash.data(), aActualHash.size());
|
|
}
|
|
|
|
OUString GetSubjectName(PCCERT_CONTEXT pCertContext)
|
|
{
|
|
OUString subjectName;
|
|
|
|
// Get Subject name size.
|
|
DWORD dwData = CertGetNameStringW(pCertContext,
|
|
CERT_NAME_SIMPLE_DISPLAY_TYPE,
|
|
0,
|
|
nullptr,
|
|
nullptr,
|
|
0);
|
|
if (!dwData)
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: CertGetNameString failed");
|
|
return subjectName;
|
|
}
|
|
|
|
// Allocate memory for subject name.
|
|
LPWSTR szName = static_cast<LPWSTR>(
|
|
LocalAlloc(LPTR, dwData * sizeof(WCHAR)));
|
|
if (!szName)
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: Unable to allocate memory for subject name");
|
|
return subjectName;
|
|
}
|
|
|
|
// Get subject name.
|
|
if (!CertGetNameStringW(pCertContext,
|
|
CERT_NAME_SIMPLE_DISPLAY_TYPE,
|
|
0,
|
|
nullptr,
|
|
szName,
|
|
dwData))
|
|
{
|
|
LocalFree(szName);
|
|
SAL_WARN("svl.crypto", "ValidateSignature: CertGetNameString failed");
|
|
return subjectName;
|
|
}
|
|
|
|
subjectName = o3tl::toU(szName);
|
|
LocalFree(szName);
|
|
|
|
return subjectName;
|
|
}
|
|
#endif // USE_CRYPTO_MSCAPI
|
|
|
|
#if USE_CRYPTO_NSS
|
|
void ensureNssInit()
|
|
{
|
|
// e.g. tdf#122599 ensure NSS library is initialized for NSS_CMSMessage_CreateFromDER
|
|
css::uno::Reference<css::xml::crypto::XNSSInitializer>
|
|
xNSSInitializer = css::xml::crypto::NSSInitializer::create(comphelper::getProcessComponentContext());
|
|
|
|
// this calls NSS_Init
|
|
xNSSInitializer->getDigestContext(css::xml::crypto::DigestID::SHA256,
|
|
uno::Sequence<beans::NamedValue>());
|
|
}
|
|
#endif
|
|
} // anonymous namespace
|
|
|
|
bool Signing::Verify(const std::vector<unsigned char>& aData,
|
|
const bool bNonDetached,
|
|
const std::vector<unsigned char>& aSignature,
|
|
SignatureInformation& rInformation)
|
|
{
|
|
#if USE_CRYPTO_NSS
|
|
// ensure NSS_Init() is called before using NSS_CMSMessage_CreateFromDER
|
|
static std::once_flag aInitOnce;
|
|
std::call_once(aInitOnce, ensureNssInit);
|
|
|
|
// Validate the signature.
|
|
SECItem aSignatureItem;
|
|
aSignatureItem.data = const_cast<unsigned char*>(aSignature.data());
|
|
aSignatureItem.len = aSignature.size();
|
|
NSSCMSMessage* pCMSMessage = NSS_CMSMessage_CreateFromDER(&aSignatureItem,
|
|
/*cb=*/nullptr,
|
|
/*cb_arg=*/nullptr,
|
|
/*pwfn=*/nullptr,
|
|
/*pwfn_arg=*/nullptr,
|
|
/*decrypt_key_cb=*/nullptr,
|
|
/*decrypt_key_cb_arg=*/nullptr);
|
|
if (!NSS_CMSMessage_IsSigned(pCMSMessage))
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: message is not signed");
|
|
return false;
|
|
}
|
|
|
|
NSSCMSContentInfo* pCMSContentInfo = NSS_CMSMessage_ContentLevel(pCMSMessage, 0);
|
|
if (!pCMSContentInfo)
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: NSS_CMSMessage_ContentLevel() failed");
|
|
return false;
|
|
}
|
|
|
|
auto pCMSSignedData = static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(pCMSContentInfo));
|
|
if (!pCMSSignedData)
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: NSS_CMSContentInfo_GetContent() failed");
|
|
return false;
|
|
}
|
|
|
|
// Import certificates from the signed data temporarily, so it'll be
|
|
// possible to verify the signature, even if we didn't have the certificate
|
|
// previously.
|
|
std::vector<CERTCertificate*> aDocumentCertificates;
|
|
for (size_t i = 0; pCMSSignedData->rawCerts[i]; ++i)
|
|
aDocumentCertificates.push_back(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), pCMSSignedData->rawCerts[i], nullptr, 0, 0));
|
|
|
|
NSSCMSSignerInfo* pCMSSignerInfo = NSS_CMSSignedData_GetSignerInfo(pCMSSignedData, 0);
|
|
if (!pCMSSignerInfo)
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: NSS_CMSSignedData_GetSignerInfo() failed");
|
|
return false;
|
|
}
|
|
|
|
SECItem aAlgorithm = NSS_CMSSignedData_GetDigestAlgs(pCMSSignedData)[0]->algorithm;
|
|
SECOidTag eOidTag = SECOID_FindOIDTag(&aAlgorithm);
|
|
|
|
// Map a sign algorithm to a digest algorithm.
|
|
// See NSS_CMSUtil_MapSignAlgs(), which is private to us.
|
|
switch (eOidTag)
|
|
{
|
|
case SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION:
|
|
eOidTag = SEC_OID_SHA1;
|
|
break;
|
|
case SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION:
|
|
eOidTag = SEC_OID_SHA256;
|
|
break;
|
|
case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION:
|
|
eOidTag = SEC_OID_SHA512;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
HASH_HashType eHashType = HASH_GetHashTypeByOidTag(eOidTag);
|
|
HASHContext* pHASHContext = HASH_Create(eHashType);
|
|
if (!pHASHContext)
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: HASH_Create() failed");
|
|
return false;
|
|
}
|
|
|
|
// We have a hash, update it with the byte ranges.
|
|
HASH_Update(pHASHContext, aData.data(), aData.size());
|
|
|
|
// Find out what is the expected length of the hash.
|
|
unsigned int nMaxResultLen = 0;
|
|
switch (eOidTag)
|
|
{
|
|
case SEC_OID_SHA1:
|
|
nMaxResultLen = comphelper::SHA1_HASH_LENGTH;
|
|
rInformation.nDigestID = xml::crypto::DigestID::SHA1;
|
|
break;
|
|
case SEC_OID_SHA256:
|
|
nMaxResultLen = comphelper::SHA256_HASH_LENGTH;
|
|
rInformation.nDigestID = xml::crypto::DigestID::SHA256;
|
|
break;
|
|
case SEC_OID_SHA512:
|
|
nMaxResultLen = comphelper::SHA512_HASH_LENGTH;
|
|
rInformation.nDigestID = xml::crypto::DigestID::SHA512;
|
|
break;
|
|
default:
|
|
SAL_WARN("svl.crypto", "ValidateSignature: unrecognized algorithm");
|
|
return false;
|
|
}
|
|
|
|
auto pActualResultBuffer = static_cast<unsigned char*>(PORT_Alloc(nMaxResultLen));
|
|
unsigned int nActualResultLen;
|
|
HASH_End(pHASHContext, pActualResultBuffer, &nActualResultLen, nMaxResultLen);
|
|
|
|
CERTCertificate* pCertificate = NSS_CMSSignerInfo_GetSigningCertificate(pCMSSignerInfo, CERT_GetDefaultCertDB());
|
|
if (!pCertificate)
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: NSS_CMSSignerInfo_GetSigningCertificate() failed");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
uno::Sequence<sal_Int8> aDerCert(pCertificate->derCert.len);
|
|
auto aDerCertRange = asNonConstRange(aDerCert);
|
|
for (size_t i = 0; i < pCertificate->derCert.len; ++i)
|
|
aDerCertRange[i] = pCertificate->derCert.data[i];
|
|
OUStringBuffer aBuffer;
|
|
comphelper::Base64::encode(aBuffer, aDerCert);
|
|
SignatureInformation::X509Data temp;
|
|
temp.emplace_back();
|
|
temp.back().X509Certificate = aBuffer.makeStringAndClear();
|
|
temp.back().X509Subject = OUString(pCertificate->subjectName, PL_strlen(pCertificate->subjectName), RTL_TEXTENCODING_UTF8);
|
|
rInformation.X509Datas.clear();
|
|
rInformation.X509Datas.emplace_back(temp);
|
|
}
|
|
|
|
PRTime nSigningTime;
|
|
// This may fail, in which case the date should be taken from the PDF's dictionary's "M" key,
|
|
// so not critical for PDF at least.
|
|
if (NSS_CMSSignerInfo_GetSigningTime(pCMSSignerInfo, &nSigningTime) == SECSuccess)
|
|
{
|
|
// First convert the UTC UNIX timestamp to a tools::DateTime.
|
|
// nSigningTime is in microseconds.
|
|
DateTime aDateTime = DateTime::CreateFromUnixTime(static_cast<double>(nSigningTime) / 1000000);
|
|
|
|
// Then convert to a local UNO DateTime.
|
|
aDateTime.ConvertToLocalTime();
|
|
rInformation.stDateTime = aDateTime.GetUNODateTime();
|
|
if (rInformation.ouDateTime.isEmpty())
|
|
{
|
|
OUStringBuffer rBuffer;
|
|
rBuffer.append(static_cast<sal_Int32>(aDateTime.GetYear()));
|
|
rBuffer.append('-');
|
|
if (aDateTime.GetMonth() < 10)
|
|
rBuffer.append('0');
|
|
rBuffer.append(static_cast<sal_Int32>(aDateTime.GetMonth()));
|
|
rBuffer.append('-');
|
|
if (aDateTime.GetDay() < 10)
|
|
rBuffer.append('0');
|
|
rBuffer.append(static_cast<sal_Int32>(aDateTime.GetDay()));
|
|
rInformation.ouDateTime = rBuffer.makeStringAndClear();
|
|
}
|
|
}
|
|
|
|
// 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("svl.crypto", "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 aSignedDigestItem {siBuffer, nullptr, 0};
|
|
|
|
SECItem* pContentInfoContentData = pCMSSignedData->contentInfo.content.data;
|
|
if (bNonDetached && pContentInfoContentData && pContentInfoContentData->data)
|
|
{
|
|
// Not a detached signature.
|
|
if (nActualResultLen == pContentInfoContentData->len &&
|
|
!std::memcmp(pActualResultBuffer, pContentInfoContentData->data, nMaxResultLen) &&
|
|
HASH_HashBuf(eHashType, pActualResultBuffer, pContentInfoContentData->data, nActualResultLen) == SECSuccess)
|
|
{
|
|
aSignedDigestItem.data = pActualResultBuffer;
|
|
aSignedDigestItem.len = nActualResultLen;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Detached, the usual case.
|
|
aSignedDigestItem.data = pActualResultBuffer;
|
|
aSignedDigestItem.len = nActualResultLen;
|
|
}
|
|
|
|
if (aSignedDigestItem.data && NSS_CMSSignerInfo_Verify(pCMSSignerInfo, &aSignedDigestItem, nullptr) == SECSuccess)
|
|
rInformation.nStatus = xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;
|
|
|
|
// Everything went fine
|
|
SECITEM_FreeItem(&aOidData.oid, false);
|
|
PORT_Free(pActualResultBuffer);
|
|
HASH_Destroy(pHASHContext);
|
|
NSS_CMSSignerInfo_Destroy(pCMSSignerInfo);
|
|
for (auto pDocumentCertificate : aDocumentCertificates)
|
|
CERT_DestroyCertificate(pDocumentCertificate);
|
|
|
|
return true;
|
|
|
|
#elif USE_CRYPTO_MSCAPI // ends USE_CRYPTO_NSS
|
|
|
|
// Open a message for decoding.
|
|
HCRYPTMSG hMsg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
|
|
CMSG_DETACHED_FLAG,
|
|
0,
|
|
0,
|
|
nullptr,
|
|
nullptr);
|
|
if (!hMsg)
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgOpenToDecode() failed");
|
|
return false;
|
|
}
|
|
|
|
// Update the message with the encoded header blob.
|
|
if (!CryptMsgUpdate(hMsg, aSignature.data(), aSignature.size(), TRUE))
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature, CryptMsgUpdate() for the header failed: " << WindowsErrorString(GetLastError()));
|
|
return false;
|
|
}
|
|
|
|
if (!bNonDetached)
|
|
{
|
|
// Update the message with the content blob.
|
|
if (!CryptMsgUpdate(hMsg, aData.data(), aData.size(), FALSE))
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature, CryptMsgUpdate() for the content failed: " << WindowsErrorString(GetLastError()));
|
|
return false;
|
|
}
|
|
|
|
if (!CryptMsgUpdate(hMsg, nullptr, 0, TRUE))
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature, CryptMsgUpdate() for the last content failed: " << WindowsErrorString(GetLastError()));
|
|
return false;
|
|
}
|
|
}
|
|
// Get the CRYPT_ALGORITHM_IDENTIFIER from the message.
|
|
DWORD nDigestID = 0;
|
|
if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_HASH_ALGORITHM_PARAM, 0, nullptr, &nDigestID))
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed: " << WindowsErrorString(GetLastError()));
|
|
return false;
|
|
}
|
|
std::unique_ptr<BYTE[]> pDigestBytes(new BYTE[nDigestID]);
|
|
if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_HASH_ALGORITHM_PARAM, 0, pDigestBytes.get(), &nDigestID))
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed: " << WindowsErrorString(GetLastError()));
|
|
return false;
|
|
}
|
|
auto pDigestID = reinterpret_cast<CRYPT_ALGORITHM_IDENTIFIER*>(pDigestBytes.get());
|
|
if (std::string_view(szOID_NIST_sha256) == pDigestID->pszObjId)
|
|
rInformation.nDigestID = xml::crypto::DigestID::SHA256;
|
|
else if (std::string_view(szOID_RSA_SHA1RSA) == pDigestID->pszObjId || std::string_view(szOID_OIWSEC_sha1) == pDigestID->pszObjId)
|
|
rInformation.nDigestID = xml::crypto::DigestID::SHA1;
|
|
else
|
|
// Don't error out here, we can still verify the message digest correctly, just the digest ID won't be set.
|
|
SAL_WARN("svl.crypto", "ValidateSignature: unhandled algorithm identifier '"<<pDigestID->pszObjId<<"'");
|
|
|
|
// Get the signer CERT_INFO from the message.
|
|
DWORD nSignerCertInfo = 0;
|
|
if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_CERT_INFO_PARAM, 0, nullptr, &nSignerCertInfo))
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed");
|
|
return false;
|
|
}
|
|
std::unique_ptr<BYTE[]> pSignerCertInfoBuf(new BYTE[nSignerCertInfo]);
|
|
if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_CERT_INFO_PARAM, 0, pSignerCertInfoBuf.get(), &nSignerCertInfo))
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed");
|
|
return false;
|
|
}
|
|
PCERT_INFO pSignerCertInfo = reinterpret_cast<PCERT_INFO>(pSignerCertInfoBuf.get());
|
|
|
|
// Open a certificate store in memory using CERT_STORE_PROV_MSG, which
|
|
// initializes it with the certificates from the message.
|
|
HCERTSTORE hStoreHandle = CertOpenStore(CERT_STORE_PROV_MSG,
|
|
PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
|
|
0,
|
|
0,
|
|
hMsg);
|
|
if (!hStoreHandle)
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: CertOpenStore() failed");
|
|
return false;
|
|
}
|
|
|
|
// Find the signer's certificate in the store.
|
|
PCCERT_CONTEXT pSignerCertContext = CertGetSubjectCertificateFromStore(hStoreHandle,
|
|
PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
|
|
pSignerCertInfo);
|
|
if (!pSignerCertContext)
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: CertGetSubjectCertificateFromStore() failed");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Write rInformation.ouX509Certificate.
|
|
uno::Sequence<sal_Int8> aDerCert(pSignerCertContext->cbCertEncoded);
|
|
std::copy_n(pSignerCertContext->pbCertEncoded, pSignerCertContext->cbCertEncoded,
|
|
aDerCert.getArray());
|
|
OUStringBuffer aBuffer;
|
|
comphelper::Base64::encode(aBuffer, aDerCert);
|
|
SignatureInformation::X509Data temp;
|
|
temp.emplace_back();
|
|
temp.back().X509Certificate = aBuffer.makeStringAndClear();
|
|
temp.back().X509Subject = GetSubjectName(pSignerCertContext);
|
|
rInformation.X509Datas.clear();
|
|
rInformation.X509Datas.emplace_back(temp);
|
|
}
|
|
|
|
std::vector<BYTE> aContentParam;
|
|
|
|
if (bNonDetached)
|
|
{
|
|
// Not a detached signature.
|
|
DWORD nContentParam = 0;
|
|
if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, nullptr, &nContentParam))
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed");
|
|
return false;
|
|
}
|
|
|
|
aContentParam.resize(nContentParam);
|
|
if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, aContentParam.data(), &nContentParam))
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!bNonDetached || VerifyNonDetachedSignature(aData, aContentParam))
|
|
{
|
|
// Use the CERT_INFO from the signer certificate to verify the signature.
|
|
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("svl.crypto", "ValidateSignature: CryptMsgGetParam() authenticated 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 }
|
|
*/
|
|
if (std::string_view("1.2.840.113549.1.9.16.2.47") == rAttr.pszObjId)
|
|
{
|
|
rInformation.bHasSigningCertificate = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get the unauthorized attributes.
|
|
nSignedAttributes = 0;
|
|
if (CryptMsgGetParam(hMsg, CMSG_SIGNER_UNAUTH_ATTR_PARAM, 0, nullptr, &nSignedAttributes))
|
|
{
|
|
std::unique_ptr<BYTE[]> pSignedAttributesBuf(new BYTE[nSignedAttributes]);
|
|
if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_UNAUTH_ATTR_PARAM, 0, pSignedAttributesBuf.get(), &nSignedAttributes))
|
|
{
|
|
SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() unauthenticated 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];
|
|
// Timestamp blob
|
|
if (std::string_view("1.2.840.113549.1.9.16.2.14") == rAttr.pszObjId)
|
|
{
|
|
PCRYPT_TIMESTAMP_CONTEXT pTsContext;
|
|
if (!CryptVerifyTimeStampSignature(rAttr.rgValue->pbData, rAttr.rgValue->cbData, nullptr, 0, nullptr, &pTsContext, nullptr, nullptr))
|
|
{
|
|
SAL_WARN("svl.crypto", "CryptMsgUpdate failed: " << WindowsErrorString(GetLastError()));
|
|
break;
|
|
}
|
|
|
|
DateTime aDateTime = DateTime::CreateFromWin32FileDateTime(pTsContext->pTimeStamp->ftTime.dwLowDateTime, pTsContext->pTimeStamp->ftTime.dwHighDateTime);
|
|
|
|
// Then convert to a local UNO DateTime.
|
|
aDateTime.ConvertToLocalTime();
|
|
rInformation.stDateTime = aDateTime.GetUNODateTime();
|
|
if (rInformation.ouDateTime.isEmpty())
|
|
{
|
|
OUStringBuffer rBuffer;
|
|
rBuffer.append(static_cast<sal_Int32>(aDateTime.GetYear()));
|
|
rBuffer.append('-');
|
|
if (aDateTime.GetMonth() < 10)
|
|
rBuffer.append('0');
|
|
rBuffer.append(static_cast<sal_Int32>(aDateTime.GetMonth()));
|
|
rBuffer.append('-');
|
|
if (aDateTime.GetDay() < 10)
|
|
rBuffer.append('0');
|
|
rBuffer.append(static_cast<sal_Int32>(aDateTime.GetDay()));
|
|
rInformation.ouDateTime = rBuffer.makeStringAndClear();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_FORCE_FLAG);
|
|
CryptMsgClose(hMsg);
|
|
return true;
|
|
#else
|
|
// Not implemented.
|
|
(void)aData;
|
|
(void)bNonDetached;
|
|
(void)aSignature;
|
|
(void)rInformation;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool Signing::Verify(SvStream& rStream,
|
|
const std::vector<std::pair<size_t, size_t>>& aByteRanges,
|
|
const bool bNonDetached,
|
|
const std::vector<unsigned char>& aSignature,
|
|
SignatureInformation& rInformation)
|
|
{
|
|
#if USE_CRYPTO_ANY
|
|
std::vector<unsigned char> buffer;
|
|
|
|
// Copy the byte ranges into a single buffer.
|
|
for (const auto& rByteRange : aByteRanges)
|
|
{
|
|
rStream.Seek(rByteRange.first);
|
|
const size_t size = buffer.size();
|
|
buffer.resize(size + rByteRange.second);
|
|
rStream.ReadBytes(buffer.data() + size, rByteRange.second);
|
|
}
|
|
|
|
return Verify(buffer, bNonDetached, aSignature, rInformation);
|
|
|
|
#else
|
|
// Not implemented.
|
|
(void)rStream;
|
|
(void)aByteRanges;
|
|
(void)bNonDetached;
|
|
(void)aSignature;
|
|
(void)rInformation;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void Signing::appendHex(sal_Int8 nInt, OStringBuffer& rBuffer)
|
|
{
|
|
static const char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
|
|
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
|
rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] );
|
|
rBuffer.append( pHexDigits[ nInt & 15 ] );
|
|
}
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|