You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1204 lines
35 KiB
1204 lines
35 KiB
#include "nt.h"
|
|
#include "ntdef.h"
|
|
#include "ntrtl.h"
|
|
#include "nturtl.h"
|
|
#include "stdio.h"
|
|
#include "sxs-rtl.h"
|
|
#include "fasterxml.h"
|
|
#include "skiplist.h"
|
|
#include "namespacemanager.h"
|
|
#include "xmlstructure.h"
|
|
#undef INVALID_HANDLE_VALUE
|
|
#include "windows.h"
|
|
#include "sha.h"
|
|
#include "sha2.h"
|
|
#include "md4.h"
|
|
#include "rsa.h"
|
|
#include "bcl_w32unicodestringbuffer.h"
|
|
#include "bcl_common.h"
|
|
#include "hashers.h"
|
|
#include "environment.h"
|
|
|
|
#pragma warning(disable: 4200)
|
|
|
|
void __cdecl wmain(int argc, wchar_t** argv);
|
|
void GetSignatureOf(PCWSTR pcwsz);
|
|
void ValidateSignature(PCWSTR pcwsz);
|
|
|
|
template <typename TStored, typename TCount>
|
|
class CArrayBlob
|
|
{
|
|
public:
|
|
typedef BCL::CMutablePointerAndCountPair<TStored, TCount> TRange;
|
|
typedef BCL::CConstantPointerAndCountPair<TStored, TCount> TConstantRange;
|
|
|
|
private:
|
|
TRange m_InternalRange;
|
|
|
|
bool ResizeInternal(TCount cNewCount, bool fPreserve = false)
|
|
{
|
|
//
|
|
// No previous allocation or previous too small
|
|
//
|
|
if ((m_InternalRange.GetPointer() == NULL) || (cNewCount > m_InternalRange.GetCount()))
|
|
{
|
|
//
|
|
// Don't bother preserving if there was no original buffer.
|
|
// Allocate, copy, reset pointers
|
|
//
|
|
if (fPreserve && m_InternalRange.GetPointer())
|
|
{
|
|
TRange NewRange(new TStored[cNewCount], cNewCount);
|
|
TStored *pNewSet = NewRange.GetPointer();
|
|
TStored *pOld = m_InternalRange.GetPointer();
|
|
|
|
if (pNewSet == NULL)
|
|
return false;
|
|
|
|
for (TCount c = 0; c < m_InternalRange.GetCount(); c++)
|
|
pNewSet[c] = pOld[c];
|
|
|
|
delete [] m_InternalRange.GetPointer();
|
|
|
|
m_InternalRange = NewRange;
|
|
}
|
|
//
|
|
// Otherwise, don't care - free old, allocate new, swap pointers
|
|
//
|
|
else
|
|
{
|
|
TStored *pOld = m_InternalRange.GetPointer();
|
|
TStored *pNew = new TStored[cNewCount];
|
|
|
|
if (pOld != NULL)
|
|
{
|
|
delete [] pOld;
|
|
pOld = NULL;
|
|
}
|
|
|
|
if (pNew == NULL)
|
|
return false;
|
|
|
|
m_InternalRange.SetPointerAndCount(pNew, cNewCount);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public:
|
|
CArrayBlob() { }
|
|
~CArrayBlob() { if (m_InternalRange.GetPointer()) { delete [] m_InternalRange.GetPointer(); } }
|
|
|
|
bool Initialize(const TConstantRange &src)
|
|
{
|
|
if (ResizeInternal(src.GetCount(), false))
|
|
{
|
|
TStored *pNew = m_InternalRange.GetPointer();
|
|
const TStored *pOld = src.GetPointer();
|
|
|
|
for (TCount c = 0; c < src.GetCount(); c++)
|
|
pNew[c] = pOld[c];
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Initialize(const CArrayBlob<TStored, TCount>& Other)
|
|
{
|
|
if (&Other != this)
|
|
{
|
|
return this->Initialize(Other.m_InternalRange);
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool EnsureSize(TCount c)
|
|
{
|
|
return ResizeInternal(c, true);
|
|
}
|
|
|
|
const TConstantRange &GetRange() const { return m_InternalRange; }
|
|
TRange GetMutableRange() { return m_InternalRange; }
|
|
};
|
|
|
|
typedef CArrayBlob<BYTE, SIZE_T> CByteBlob;
|
|
|
|
template <
|
|
typename TStoredObject,
|
|
int iInitialSize = 0
|
|
>
|
|
class CGrowingList : public RTL_GROWING_LIST
|
|
{
|
|
// Each chunk is therefore half a page.
|
|
enum { eDefaultElementsPerChunk = (2048 / sizeof(TStoredObject)) };
|
|
|
|
TStoredObject m_InternalObjects[iInitialSize];
|
|
|
|
public:
|
|
bool Initialize(PRTL_ALLOCATOR Allocator = &g_DefaultAllocator)
|
|
{
|
|
NTSTATUS status =
|
|
RtlInitializeGrowingList(
|
|
this,
|
|
sizeof(TStoredObject),
|
|
eDefaultElementsPerChunk,
|
|
m_InternalObjects,
|
|
iInitialSize * sizeof(TStoredObject),
|
|
Allocator);
|
|
|
|
return NT_SUCCESS(status);
|
|
}
|
|
|
|
~CGrowingList() { Destroy(); }
|
|
|
|
bool Destroy()
|
|
{
|
|
return NT_SUCCESS(RtlDestroyGrowingList(this));
|
|
}
|
|
|
|
inline TStoredObject &operator[](ULONG i) {
|
|
TStoredObject *pvObject = NULL;
|
|
NTSTATUS status = RtlIndexIntoGrowingList(this, i, (PVOID*)&pvObject, TRUE);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
EXCEPTION_RECORD exr = {STATUS_INVALID_PARAMETER, 0, NULL, NULL, 3 };
|
|
exr.ExceptionInformation[0] = i;
|
|
exr.ExceptionInformation[1] = (ULONG_PTR)this;
|
|
exr.ExceptionInformation[2] = status;
|
|
RtlRaiseException(&exr);
|
|
}
|
|
|
|
return *pvObject;
|
|
}
|
|
};
|
|
|
|
typedef CGrowingList<XMLDOC_ATTRIBUTE, 50> CAttributeList;
|
|
|
|
|
|
class CLogicalXmlParser;
|
|
|
|
class CXmlMiniTokenizer
|
|
{
|
|
XML_RAWTOKENIZATION_STATE RawState;
|
|
NTXML_RAW_TOKEN TokenName;
|
|
ULONG ulCharacter;
|
|
|
|
public:
|
|
CXmlMiniTokenizer() { }
|
|
bool Initialize(XML_EXTENT &Source, CLogicalXmlParser &SourceParser);
|
|
bool More() { return RawState.pvCursor <= RawState.pvDocumentEnd; }
|
|
void Next();
|
|
|
|
ULONG Name() { return TokenName; }
|
|
ULONG Character() { return ulCharacter; }
|
|
};
|
|
|
|
class CLogicalXmlParser
|
|
{
|
|
public:
|
|
typedef CLogicalXmlParser CThis;
|
|
|
|
protected:
|
|
XML_LOGICAL_STATE m_XmlState;
|
|
NS_MANAGER m_Namespaces;
|
|
bool m_fInitialized;
|
|
CAttributeList m_Attributes;
|
|
|
|
friend CXmlMiniTokenizer;
|
|
|
|
public:
|
|
CLogicalXmlParser() : m_fInitialized(false) { }
|
|
~CLogicalXmlParser() { this->Reset(); }
|
|
|
|
bool Reset();
|
|
bool Initialize(PVOID pvXmlBase, SIZE_T cbDocumentSize);
|
|
CAttributeList& Attributes() { return this->m_Attributes; }
|
|
bool More() const;
|
|
bool Next(XMLDOC_THING &XmlDocThing);
|
|
bool SkipElement(XMLDOC_ELEMENT &Element);
|
|
bool IsThisNode(XMLDOC_ELEMENT &Element, PCXML_SPECIAL_STRING pName, PCXML_SPECIAL_STRING pNamespace);
|
|
|
|
template <typename TStringType>
|
|
bool ConvertToString(PXML_EXTENT pExtent, TStringType &Target)
|
|
{
|
|
bool fSuccess = false;
|
|
SIZE_T cch;
|
|
NTSTATUS status;
|
|
|
|
if (!Target.EnsureSizeChars(pExtent->ulCharacters))
|
|
goto Exit;
|
|
|
|
status = RtlXmlExtentToString(&m_XmlState.ParseState.RawTokenState, pExtent, &Target, &cch);
|
|
if (status == STATUS_BUFFER_TOO_SMALL)
|
|
{
|
|
if (!Target.EnsureSizeChars(cch))
|
|
goto Exit;
|
|
|
|
if (!NT_SUCCESS(status = RtlXmlExtentToString(&m_XmlState.ParseState.RawTokenState, pExtent, &Target, &cch)))
|
|
goto Exit;
|
|
}
|
|
|
|
fSuccess = true;
|
|
Exit:
|
|
return fSuccess;
|
|
}
|
|
|
|
|
|
private:
|
|
static NTSTATUS StaticCompareStrings(PVOID pv, PCXML_EXTENT pcLeft, PCXML_EXTENT pcRight, XML_STRING_COMPARE *pfMatching)
|
|
{
|
|
CThis *pThis = reinterpret_cast<CThis*>(pv);
|
|
return RtlXmlDefaultCompareStrings(&pThis->m_XmlState.ParseState, pcLeft, pcRight, pfMatching);
|
|
}
|
|
|
|
static NTSTATUS FASTCALL StaticAllocate(SIZE_T cb, PVOID* ppvOutput, PVOID pvContext)
|
|
{
|
|
return (NULL != (*ppvOutput = HeapAlloc(GetProcessHeap(), 0, cb))) ? STATUS_SUCCESS : STATUS_NO_MEMORY;
|
|
}
|
|
|
|
static NTSTATUS FASTCALL StaticFree(PVOID pvPointer, PVOID pvContext)
|
|
{
|
|
return HeapFree(GetProcessHeap(), 0, pvPointer);
|
|
}
|
|
};
|
|
|
|
bool
|
|
CLogicalXmlParser::IsThisNode(
|
|
XMLDOC_ELEMENT &Element,
|
|
PCXML_SPECIAL_STRING pName,
|
|
PCXML_SPECIAL_STRING pNamespace
|
|
)
|
|
{
|
|
XML_STRING_COMPARE Comparison;
|
|
|
|
if (pNamespace != NULL)
|
|
{
|
|
m_XmlState.ParseState.pfnCompareSpecialString(
|
|
&m_XmlState.ParseState,
|
|
&Element.NsPrefix,
|
|
pNamespace,
|
|
&Comparison);
|
|
|
|
if (Comparison != XML_STRING_COMPARE_EQUALS)
|
|
return false;
|
|
}
|
|
|
|
m_XmlState.ParseState.pfnCompareSpecialString(
|
|
&m_XmlState.ParseState,
|
|
&Element.Name,
|
|
pName,
|
|
&Comparison);
|
|
|
|
return Comparison == XML_STRING_COMPARE_EQUALS;
|
|
}
|
|
|
|
bool CXmlMiniTokenizer::Initialize(
|
|
XML_EXTENT &Source,
|
|
CLogicalXmlParser &BaseParser
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
status = RtlXmlCloneRawTokenizationState(
|
|
&BaseParser.m_XmlState.ParseState.RawTokenState,
|
|
&RawState);
|
|
|
|
ulCharacter = 0;
|
|
RawState.pvLastCursor = RawState.pvCursor = Source.pvData;
|
|
RawState.pvDocumentEnd = (PVOID)(((ULONG_PTR)Source.pvData) + Source.cbData);
|
|
|
|
return NT_SUCCESS(status);
|
|
}
|
|
|
|
void CXmlMiniTokenizer::Next()
|
|
{
|
|
ASSERT(this->More());
|
|
|
|
ASSERT(RawState.cbBytesInLastRawToken == RawState.DefaultCharacterSize);
|
|
ASSERT(RawState.NextCharacterResult == STATUS_SUCCESS);
|
|
|
|
this->ulCharacter = RawState.pfnNextChar(&RawState);
|
|
|
|
if ((ulCharacter == 0) && !NT_SUCCESS(RawState.NextCharacterResult)) {
|
|
this->TokenName = NTXML_RAWTOKEN_ERROR;
|
|
return;
|
|
}
|
|
|
|
this->TokenName = _RtlpDecodeCharacter(this->ulCharacter);
|
|
|
|
RawState.pvCursor = (PVOID)(((ULONG_PTR)RawState.pvCursor) + RawState.cbBytesInLastRawToken);
|
|
|
|
if (RawState.cbBytesInLastRawToken != RawState.DefaultCharacterSize)
|
|
RawState.cbBytesInLastRawToken = RawState.DefaultCharacterSize;
|
|
}
|
|
|
|
//
|
|
// The default digestion operation is to digest with UTF-8 encoding
|
|
// of characters.
|
|
//
|
|
class CUTF8BaseDigester
|
|
{
|
|
CHashObject &m_Context;
|
|
CLogicalXmlParser *m_pXmlParser;
|
|
|
|
protected:
|
|
enum { eMaxCharacterEncodingBytes = 3 };
|
|
|
|
//
|
|
// These are 'special' XML characters that are already UTF-8
|
|
// (or whatever) encoded. These can be hashed as-is
|
|
//
|
|
class XmlSpecialMarkers {
|
|
public:
|
|
static CHAR s_XmlOpenTag[];
|
|
static CHAR s_XmlCloseTag[];
|
|
static CHAR s_XmlCloseEmptyTag[];
|
|
static CHAR s_XmlOpenCloseTag[];
|
|
static CHAR s_XmlNsDelimiter[];
|
|
static CHAR s_XmlWhitespace[];
|
|
static CHAR s_XmlEqualsDQuote[];
|
|
static CHAR s_XmlDQuote[];
|
|
};
|
|
|
|
//
|
|
// This encoder uses UTF-8; feel free to derive from this class and implement
|
|
// your own encoding; do -not- make this virtual, force the compiler to use
|
|
// yours so you get the inline/fastcall benefits. CDige
|
|
//
|
|
inline SIZE_T __fastcall EncodeCharacter(ULONG ucs2Char, PBYTE pbTarget)
|
|
{
|
|
if (ucs2Char <= 0x7f)
|
|
{
|
|
pbTarget[0] = (BYTE)(ucs2Char & 0x7f);
|
|
return 1;
|
|
}
|
|
else if (ucs2Char <= 0x7ff)
|
|
{
|
|
pbTarget[0] = (BYTE)(0xC0 | ((ucs2Char >> 6) & 0x1f));
|
|
pbTarget[1] = (BYTE)(0x80 | (ucs2Char & 0x3F));
|
|
return 2;
|
|
}
|
|
else if (ucs2Char <= 0x7fff)
|
|
{
|
|
pbTarget[0] = (BYTE)(0xE0 | ((ucs2Char >> 12) & 0xF));
|
|
pbTarget[1] = (BYTE)(0x80 | ((ucs2Char >> 6) & 0x3F));
|
|
pbTarget[2] = (BYTE)(0x80 | (ucs2Char & 0x3F));
|
|
return 3;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
inline bool __fastcall EncodeAndHash(const ULONG *ucs2Char, SIZE_T cChars)
|
|
{
|
|
BYTE bDumpArea[eMaxCharacterEncodingBytes];
|
|
SIZE_T cCursor = 0;
|
|
|
|
while (cCursor < cChars)
|
|
{
|
|
const SIZE_T cThisSize = EncodeCharacter(ucs2Char[cCursor++], bDumpArea);
|
|
if (cThisSize == 0)
|
|
return false;
|
|
|
|
AddHashData(bDumpArea, cThisSize);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HashDirectly(XML_EXTENT &eExtent)
|
|
{
|
|
ASSERT(eExtent.Encoding == XMLEF_UTF_8_OR_ASCII);
|
|
|
|
if (eExtent.Encoding != XMLEF_UTF_8_OR_ASCII)
|
|
return false;
|
|
|
|
return AddHashData(eExtent.pvData, eExtent.cbData);
|
|
}
|
|
|
|
//
|
|
// This digests an element open tag as follows:
|
|
//
|
|
// Element, no attributes: <{ns:}name>
|
|
// Empty element, no attributes: <{ns:}name></{ns:}name>
|
|
// Element, attributes: <{ns:}name [{atns:}attrib="text"]xN>
|
|
// Empty element, attributes: <{ns:}name [{atns:}attrib="text"]xN/>
|
|
//
|
|
template <CHAR *szChars>
|
|
FastHash() {
|
|
|
|
#define IS_MARKER(q) if (szChars == XmlSpecialMarkers::q) { AddHashData(XmlSpecialMarkers::q, NUMBER_OF(XmlSpecialMarkers::q)); }
|
|
|
|
IS_MARKER(s_XmlOpenTag) else
|
|
IS_MARKER(s_XmlCloseTag) else
|
|
IS_MARKER(s_XmlCloseEmptyTag) else
|
|
IS_MARKER(s_XmlOpenCloseTag) else
|
|
IS_MARKER(s_XmlNsDelimiter) else
|
|
IS_MARKER(s_XmlWhitespace) else
|
|
IS_MARKER(s_XmlEqualsDQuote) else
|
|
IS_MARKER(s_XmlDQuote);
|
|
}
|
|
|
|
BYTE m_bHashPrebuffer[64];
|
|
SIZE_T m_cHashPrebufferUsed;
|
|
|
|
// This could be more intelligent about ensuring that we fill the buffer up from the input
|
|
// before hashing, but it seems like any sort of buffering at all is a huge win.
|
|
inline bool __fastcall AddHashDataInternal(PVOID pvData, SIZE_T cbData) {
|
|
|
|
// If this would overflow the internal buffer, or the input size is larger than
|
|
// the available buffer, then always flush.
|
|
if (((m_cHashPrebufferUsed + cbData) > NUMBER_OF(m_bHashPrebuffer)) || (cbData > NUMBER_OF(m_bHashPrebuffer))) {
|
|
m_Context.Hash(CEnv::CByteRegion(m_bHashPrebuffer, m_cHashPrebufferUsed));
|
|
m_cHashPrebufferUsed = 0;
|
|
}
|
|
|
|
// The input size was too large to fit in the prebuffer, hash it directly
|
|
if (cbData > NUMBER_OF(m_bHashPrebuffer))
|
|
{
|
|
if (CEnv::DidFail(m_Context.Hash(CEnv::CByteRegion((PBYTE)pvData, m_cHashPrebufferUsed))))
|
|
return false;
|
|
}
|
|
// Otherwise, copy the data into the prebuffer, update the used size
|
|
else
|
|
{
|
|
memcpy(&m_bHashPrebuffer[m_cHashPrebufferUsed], pvData, cbData);
|
|
m_cHashPrebufferUsed += cbData;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
inline bool __fastcall AddHashData(PVOID pvData, SIZE_T cData) { return AddHashDataInternal(pvData, cData); }
|
|
template <typename T> inline bool __fastcall AddHashData(T *pData, SIZE_T cData) { return AddHashDataInternal((PVOID)pData, cData * sizeof(T)); }
|
|
template <typename T> inline bool __fastcall AddHashData(T cSingleData) { return AddHashDataInternal(&cSingleData, sizeof(T)); }
|
|
|
|
bool Digest(XMLDOC_ELEMENT &Element, CAttributeList &Attributes)
|
|
{
|
|
bool fSuccess = false;
|
|
|
|
FastHash<XmlSpecialMarkers::s_XmlOpenTag>();
|
|
|
|
if (Element.NsPrefix.ulCharacters != 0)
|
|
{
|
|
if (!Digest(Element.NsPrefix, false))
|
|
goto Exit;
|
|
|
|
FastHash<XmlSpecialMarkers::s_XmlNsDelimiter>();
|
|
}
|
|
|
|
if (!Digest(Element.Name, false))
|
|
goto Exit;
|
|
|
|
//
|
|
// Now digest the attributes, ensuring that a whitespace appears between them. The
|
|
// initial whitespace ensures one between the element name and the first attribute
|
|
//
|
|
for (ULONG ul = 0; ul < Element.ulAttributeCount; ul++)
|
|
{
|
|
XMLDOC_ATTRIBUTE &Attrib = Attributes[ul];
|
|
|
|
FastHash<XmlSpecialMarkers::s_XmlWhitespace>();
|
|
|
|
if (!Digest(Attrib))
|
|
goto Exit;
|
|
}
|
|
|
|
FastHash<XmlSpecialMarkers::s_XmlCloseTag>();
|
|
|
|
//
|
|
// Empty elements implicitly get a </close>, so do the above stuff again
|
|
//
|
|
if (Element.fElementEmpty)
|
|
{
|
|
FastHash<XmlSpecialMarkers::s_XmlOpenCloseTag>();
|
|
|
|
if (Element.NsPrefix.ulCharacters != 0)
|
|
{
|
|
if (!Digest(Element.NsPrefix, false))
|
|
goto Exit;
|
|
|
|
FastHash<XmlSpecialMarkers::s_XmlNsDelimiter>();
|
|
}
|
|
|
|
if (!Digest(Element.Name, false))
|
|
goto Exit;
|
|
|
|
FastHash<XmlSpecialMarkers::s_XmlCloseTag>();
|
|
}
|
|
|
|
fSuccess = true;
|
|
Exit:
|
|
return fSuccess;
|
|
}
|
|
|
|
|
|
//
|
|
// Attributes get digested as:
|
|
//
|
|
// {attrnamespace:}attribname="attribvalue"
|
|
//
|
|
// where attribvalue is treated like pcdata for whitespace-compression purposes
|
|
//
|
|
bool Digest(XMLDOC_ATTRIBUTE &Attribute)
|
|
{
|
|
bool fSuccess = false;
|
|
|
|
if (Attribute.NsPrefix.ulCharacters != 0)
|
|
{
|
|
if (!Digest(Attribute.NsPrefix, false))
|
|
goto Exit;
|
|
|
|
FastHash<XmlSpecialMarkers::s_XmlNsDelimiter>();
|
|
}
|
|
|
|
if (!Digest(Attribute.Name, false))
|
|
goto Exit;
|
|
|
|
FastHash<XmlSpecialMarkers::s_XmlEqualsDQuote>();
|
|
|
|
if (!Digest(Attribute.Value, true))
|
|
goto Exit;
|
|
|
|
FastHash<XmlSpecialMarkers::s_XmlDQuote>();
|
|
|
|
fSuccess = true;
|
|
Exit:
|
|
return fSuccess;
|
|
}
|
|
|
|
//
|
|
// Digests the xml extent in question. If the xml parser is in UTF-8 mode, and we're
|
|
// doing CData mode (ie: pcdata == false), then we can simply throw the raw bits through
|
|
// the hasher. Otherwise, we have to do whitespace compression and whatnot.
|
|
//
|
|
bool Digest(XML_EXTENT &CData, bool WhitespaceCompression)
|
|
{
|
|
CXmlMiniTokenizer MiniTokenizer;
|
|
bool fFoundSomethingLast = false;
|
|
bool fSuccess = false;
|
|
|
|
if (CData.cbData == 0)
|
|
return true;
|
|
|
|
//
|
|
// PCDATA (the stuff in attributes, between elements, etc.) gets
|
|
// whitespace-compressed by the following rules:
|
|
//
|
|
// Complete-whitespace hyperspace chunk becomes "nothing"
|
|
// - ex: <foo> <bar> Zero bytes
|
|
// - ex: <foo></foo> Zero bytes (see <foo/> in the ELEMENT digester above
|
|
// - ex: <foo> f </foo> Effectively, "f"
|
|
// - ex: <foo> a b </foo> Effectively, "a b"
|
|
// - ex: <foo> a </foo>
|
|
//
|
|
MiniTokenizer.Initialize(CData, *this->m_pXmlParser);
|
|
|
|
#define FLUSH_BUFFER(buff, used) do { \
|
|
if (!EncodeAndHash((buff), (used))) goto Exit; \
|
|
(used) = 0; \
|
|
} while (0)
|
|
|
|
#define CHECK_FLUSH_BUFFER(buff, used, toaddsize) do { \
|
|
if (((used) + (toaddsize)) >= NUMBER_OF(buff)) { \
|
|
FLUSH_BUFFER(buff, used); \
|
|
(used) = 0; \
|
|
} } while (0)
|
|
|
|
#define ADD_BUFFER(buff, used, toadd, toaddsize) do { \
|
|
CHECK_FLUSH_BUFFER(buff, used, toaddsize); \
|
|
ASSERT(toaddsize < NUMBER_OF(buff)); \
|
|
memcpy(&((buff)[used]), (toadd), sizeof(toadd[0]) * toaddsize); \
|
|
} while (0)
|
|
|
|
#define ADD_SINGLE(buff, used, toadd) do { \
|
|
CHECK_FLUSH_BUFFER(buff, used, 1); \
|
|
(buff)[(used)++] = toadd; \
|
|
} while (0)
|
|
|
|
if (WhitespaceCompression)
|
|
{
|
|
ULONG ulDecodedBuffer[128];
|
|
SIZE_T cDecodedUsed = 0;
|
|
|
|
MiniTokenizer.Next();
|
|
|
|
do
|
|
{
|
|
//
|
|
// Skip past all whitespace
|
|
//
|
|
while ((MiniTokenizer.More() && (MiniTokenizer.Name() == NTXML_RAWTOKEN_WHITESPACE)))
|
|
MiniTokenizer.Next();
|
|
|
|
//
|
|
// Stop if we ran out
|
|
//
|
|
if (!MiniTokenizer.More())
|
|
break;
|
|
|
|
//
|
|
// Now, if we'd found something before, add a whitespace marker onto the
|
|
// list of items to encode
|
|
//
|
|
if (fFoundSomethingLast)
|
|
{
|
|
FLUSH_BUFFER(ulDecodedBuffer, cDecodedUsed);
|
|
FastHash<XmlSpecialMarkers::s_XmlWhitespace>();
|
|
}
|
|
|
|
//
|
|
// Spin through the elements that are now present, up to another whitespace
|
|
//
|
|
while (MiniTokenizer.More() && (MiniTokenizer.Name() != NTXML_RAWTOKEN_WHITESPACE))
|
|
{
|
|
if (!fFoundSomethingLast)
|
|
fFoundSomethingLast = true;
|
|
ADD_SINGLE(ulDecodedBuffer, cDecodedUsed, MiniTokenizer.Character());
|
|
|
|
MiniTokenizer.Next();
|
|
}
|
|
}
|
|
while (MiniTokenizer.More());
|
|
|
|
//
|
|
// Flush out leftover elements
|
|
//
|
|
if (cDecodedUsed != 0)
|
|
{
|
|
EncodeAndHash(ulDecodedBuffer, cDecodedUsed);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (CData.Encoding == XMLEF_UTF_8_OR_ASCII)
|
|
{
|
|
HashDirectly(CData);
|
|
}
|
|
else
|
|
{
|
|
ULONG ulBuffer[50];
|
|
SIZE_T cTotal = 0;
|
|
|
|
MiniTokenizer.Next();
|
|
while (MiniTokenizer.More()) {
|
|
ADD_SINGLE(ulBuffer, cTotal, MiniTokenizer.Character());
|
|
MiniTokenizer.Next();
|
|
}
|
|
|
|
if (cTotal > 0)
|
|
{
|
|
EncodeAndHash(ulBuffer, cTotal);
|
|
}
|
|
}
|
|
}
|
|
|
|
fSuccess = true;
|
|
Exit:
|
|
return fSuccess;
|
|
}
|
|
|
|
//
|
|
// To digest an end element, we use </{ns:}element>
|
|
//
|
|
bool Digest(XMLDOC_ENDELEMENT &EndElement)
|
|
{
|
|
bool fSuccess = false;
|
|
|
|
FastHash<XmlSpecialMarkers::s_XmlOpenCloseTag>();
|
|
|
|
if (EndElement.NsPrefix.ulCharacters != 0)
|
|
{
|
|
if (!Digest(EndElement.NsPrefix, true))
|
|
goto Exit;
|
|
|
|
FastHash<XmlSpecialMarkers::s_XmlNsDelimiter>();
|
|
}
|
|
|
|
if (!Digest(EndElement.Name, true))
|
|
goto Exit;
|
|
|
|
FastHash<XmlSpecialMarkers::s_XmlCloseTag>();
|
|
|
|
fSuccess = true;
|
|
Exit:
|
|
return fSuccess;
|
|
}
|
|
|
|
const static CEnv::CConstantUnicodeStringPair DigesterIdentifier;
|
|
|
|
public:
|
|
CUTF8BaseDigester(CHashObject &Hasher) : m_cHashPrebufferUsed(0), m_Context(Hasher), m_pXmlParser(NULL) { }
|
|
|
|
static const CEnv::CConstantUnicodeStringPair &GetDigesterIdentifier() { return DigesterIdentifier; }
|
|
|
|
void SetXmlParser(CLogicalXmlParser *pSourceParser) { this->m_pXmlParser = pSourceParser; }
|
|
|
|
bool Digest(XMLDOC_THING &Thing, CAttributeList &Attributes)
|
|
{
|
|
switch (Thing.ulThingType)
|
|
{
|
|
case XMLDOC_THING_ELEMENT:
|
|
return Digest(Thing.Element, Attributes);
|
|
break;
|
|
|
|
case XMLDOC_THING_HYPERSPACE:
|
|
return Digest(Thing.Hyperspace, true);
|
|
break;
|
|
|
|
case XMLDOC_THING_CDATA:
|
|
return HashDirectly(Thing.CDATA);
|
|
|
|
case XMLDOC_THING_END_ELEMENT:
|
|
return Digest(Thing.EndElement);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Finalize() {
|
|
if (m_cHashPrebufferUsed > 0) {
|
|
return !CEnv::DidFail(m_Context.Hash(CEnv::CByteRegion(m_bHashPrebuffer, m_cHashPrebufferUsed)));
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
const CEnv::CConstantUnicodeStringPair CUTF8BaseDigester::DigesterIdentifier = CEnv::CConstantUnicodeStringPair(L"sxs-dsig-ops#default-digestion", NUMBER_OF(L"default-digestion"));
|
|
CHAR CUTF8BaseDigester::XmlSpecialMarkers::s_XmlOpenTag[] = { '<' };
|
|
CHAR CUTF8BaseDigester::XmlSpecialMarkers::s_XmlCloseTag[] = { '>' };
|
|
CHAR CUTF8BaseDigester::XmlSpecialMarkers::s_XmlCloseEmptyTag[] = { '/', '>' };
|
|
CHAR CUTF8BaseDigester::XmlSpecialMarkers::s_XmlOpenCloseTag[] = { '<', '/' };
|
|
CHAR CUTF8BaseDigester::XmlSpecialMarkers::s_XmlNsDelimiter[] = { ':' };
|
|
CHAR CUTF8BaseDigester::XmlSpecialMarkers::s_XmlWhitespace[] = { ' ' };
|
|
CHAR CUTF8BaseDigester::XmlSpecialMarkers::s_XmlEqualsDQuote[] = { '=', '\"' };
|
|
CHAR CUTF8BaseDigester::XmlSpecialMarkers::s_XmlDQuote[] = { '\"' };
|
|
|
|
const XML_SPECIAL_STRING c_ss_Signature = MAKE_SPECIAL_STRING("Signature");
|
|
const XML_SPECIAL_STRING c_ss_SignedInfo = MAKE_SPECIAL_STRING("SignedInfo");
|
|
const XML_SPECIAL_STRING c_ss_SignatureValue = MAKE_SPECIAL_STRING("SignatureValue");
|
|
const XML_SPECIAL_STRING c_ss_KeyInfo = MAKE_SPECIAL_STRING("KeyInfo");
|
|
const XML_SPECIAL_STRING c_ss_Object = MAKE_SPECIAL_STRING("Object");
|
|
const XML_SPECIAL_STRING c_ss_XmlNsSignature = MAKE_SPECIAL_STRING("http://www.w3.org/2000/09/xmldsig#");
|
|
|
|
bool operator==(const XMLDOC_ELEMENT &left, const XMLDOC_ELEMENT &right)
|
|
{
|
|
return (left.Name.pvData == right.Name.pvData);
|
|
}
|
|
|
|
void ReverseMemCpy(
|
|
PVOID pvTarget,
|
|
const void* pcvSource,
|
|
SIZE_T cbBytes
|
|
)
|
|
{
|
|
const BYTE *pbSource = ((const BYTE*)pcvSource) + cbBytes - 1;
|
|
PBYTE pbTarget = (PBYTE)pvTarget;
|
|
|
|
while (cbBytes--)
|
|
*pbTarget++ = *pbSource--;
|
|
}
|
|
|
|
|
|
CEnv::StatusCode
|
|
EncodePKCS1Hash(
|
|
const CHashObject &SourceHash,
|
|
SIZE_T cbPubKeyDataLen,
|
|
CByteBlob &Output
|
|
)
|
|
{
|
|
const CEnv::CConstantByteRegion &HashOid = SourceHash.GetOid();
|
|
CEnv::CConstantByteRegion HashData;
|
|
CEnv::CByteRegion OutputRange;
|
|
CEnv::StatusCode Result = CEnv::SuccessCode;
|
|
PBYTE pbWorking;
|
|
|
|
if (!Output.EnsureSize(cbPubKeyDataLen)) {
|
|
Result = CEnv::OutOfMemory;
|
|
goto Exit;
|
|
}
|
|
|
|
if (CEnv::DidFail(Result = SourceHash.GetValue(HashData)))
|
|
goto Exit;
|
|
|
|
OutputRange = Output.GetMutableRange();
|
|
pbWorking = OutputRange.GetPointer();
|
|
|
|
//
|
|
// Set the well-known bytes
|
|
//
|
|
pbWorking[cbPubKeyDataLen - 1] = 0x01;
|
|
memset(pbWorking, 0xff, cbPubKeyDataLen - 1);
|
|
|
|
//
|
|
// Copy the source hash data 'backwards'
|
|
//
|
|
ReverseMemCpy(pbWorking, HashData.GetPointer(), HashData.GetCount());
|
|
pbWorking += HashData.GetCount();
|
|
|
|
memcpy(pbWorking, HashOid.GetPointer(), HashOid.GetCount());
|
|
pbWorking += HashOid.GetCount();
|
|
|
|
*pbWorking = 0;
|
|
|
|
Result = CEnv::SuccessCode;
|
|
Exit:
|
|
return Result;
|
|
}
|
|
|
|
|
|
|
|
bool SignHashContext(
|
|
const CHashObject &SourceHash,
|
|
LPBSAFE_PRV_KEY lpPrivateKey,
|
|
LPBSAFE_PUB_KEY lpPublicKey,
|
|
CByteBlob &Output
|
|
)
|
|
{
|
|
CEnv::CByteRegion OutputRange = Output.GetMutableRange();
|
|
SIZE_T cbSignatureSize = (lpPublicKey->bitlen+7)/8;
|
|
PBYTE pbInput, pbWork, pbSigT;
|
|
|
|
CByteBlob WorkingBlob;
|
|
|
|
//
|
|
// Set up and clear the output buffer
|
|
//
|
|
Output.EnsureSize(lpPublicKey->keylen);
|
|
memset(OutputRange.GetPointer(), 0, OutputRange.GetCount());
|
|
|
|
//
|
|
// Put this object into a PKCS1-compliant structure for signing
|
|
//
|
|
if (EncodePKCS1Hash(SourceHash, lpPublicKey->keylen, WorkingBlob))
|
|
{
|
|
if (BSafeDecPrivate(lpPrivateKey, WorkingBlob.GetMutableRange().GetPointer(), OutputRange.GetPointer()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool VerifySignature(
|
|
const CHashObject &SourceHash,
|
|
const CEnv::CConstantByteRegion &Signature,
|
|
LPBSAFE_PUB_KEY lpPublicKey
|
|
)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
|
|
Validating a document signature is pretty easy from our current point of view.
|
|
You spin over the contents of the document, hashing all the hashable stuff.
|
|
At some point, you should find a <Signature> element, inside which is a <SignedInfo>
|
|
element. You should start another hashing context over the contents of the
|
|
<SignedInfo> data. When that's done, you should have found:
|
|
|
|
<Signature>
|
|
<SignedInfo>
|
|
<CanonicalizationMethod Algorithm="sidebyside-manifest-canonicalizer"/>
|
|
<Reference>
|
|
<Transforms Algorithm="sidebyside-manifest-digestion#standard-transform"/>
|
|
<DigestMethod Algorithm="sidebyside-manifest-digestion#sha1"/>
|
|
<DigestValue>...</DigestValue>
|
|
</Reference>
|
|
<SignatureMethod Algorithm="sidebyside-manifest-digestion#dsa-sha1"/>
|
|
</SignedInfo>
|
|
<SignatureValue>(signed data goes here)</SignatureValue>
|
|
<KeyInfo>...</KeyInfo>
|
|
</Signature>
|
|
*/
|
|
|
|
bool Base64EncodeBytes(
|
|
const CEnv::CConstantByteRegion &Bytes,
|
|
CEnv::CStringBuffer &Output
|
|
)
|
|
{
|
|
SIZE_T cCharsNeeded = 0;
|
|
CArrayBlob<WCHAR, SIZE_T> B64Encoding;
|
|
|
|
|
|
RtlBase64Encode((PVOID)Bytes.GetPointer(), Bytes.GetCount(), NULL, &cCharsNeeded);
|
|
|
|
if (!B64Encoding.EnsureSize(cCharsNeeded))
|
|
return false;
|
|
|
|
RtlBase64Encode(
|
|
(PVOID)Bytes.GetPointer(),
|
|
Bytes.GetCount(),
|
|
B64Encoding.GetMutableRange().GetPointer(),
|
|
&cCharsNeeded);
|
|
|
|
if (!Output.Assign(B64Encoding.GetRange()))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
CreateSignatureElement(
|
|
CEnv::CStringBuffer &Target,
|
|
const CEnv::CConstantByteRegion &HashValue,
|
|
const CEnv::CConstantUnicodeStringPair &DigestMethod
|
|
)
|
|
{
|
|
static const WCHAR chFormatString[] =
|
|
L"<SignedInfo>\r\n"
|
|
L" <CanonicalizationMethod Algorithm='%ls'/>\r\n"
|
|
L" <Reference>\r\n"
|
|
L" <DigestMethod Algorithm='%ls'/>\r\n"
|
|
L" <DigestValue>%ls</DigestValue>\r\n"
|
|
L" </Reference>\r\n"
|
|
L" <SignatureMethod Algorithm='%ls'/>\r\n"
|
|
L"</SignedInfo>\r\n";
|
|
|
|
Target.Clear();
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename TDigester, typename THashContext>
|
|
bool HashXmlSection(
|
|
CEnv::CConstantByteRegion &XmlRange,
|
|
CByteBlob &HashValue
|
|
)
|
|
{
|
|
THashContext HashContext;
|
|
TDigester DigestEngine(HashContext);
|
|
CEnv::CConstantByteRegion InternalHashValue;
|
|
CLogicalXmlParser Parser;
|
|
|
|
Parser.Initialize((PVOID)XmlRange.GetPointer(), XmlRange.GetCount());
|
|
DigestEngine.SetXmlParser(&Parser);
|
|
|
|
HashContext.Initialize();
|
|
|
|
do
|
|
{
|
|
XMLDOC_THING ThisThing;
|
|
|
|
if (!Parser.Next(ThisThing))
|
|
break;
|
|
|
|
//
|
|
// If this thing is a "signature" element, then we need
|
|
// to skip its body
|
|
//
|
|
if ((ThisThing.ulThingType == XMLDOC_THING_ELEMENT) &&
|
|
Parser.IsThisNode(ThisThing.Element, &c_ss_Signature, &c_ss_XmlNsSignature))
|
|
{
|
|
Parser.SkipElement(ThisThing.Element);
|
|
}
|
|
//
|
|
// Otherwise, everybody gets hashed
|
|
//
|
|
else
|
|
{
|
|
DigestEngine.Digest(ThisThing, Parser.Attributes());
|
|
}
|
|
}
|
|
while (Parser.More());
|
|
|
|
DigestEngine.Finalize();
|
|
HashContext.Finalize();
|
|
|
|
HashContext.GetValue(InternalHashValue);
|
|
HashValue.Initialize(InternalHashValue);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
void GetSignatureOf(PCWSTR pcwsz)
|
|
{
|
|
PVOID pvFileBase;
|
|
SIZE_T cbFileBase;
|
|
NTSTATUS status;
|
|
UNICODE_STRING usFilePath;
|
|
XMLDOC_THING XmlThing = { XMLDOC_THING_PROCESSINGINSTRUCTION };
|
|
LARGE_INTEGER liStart, liEnd, liTotal;
|
|
DWORD dwBitLength = 2048;
|
|
DWORD dwPubKeySize, dwPrivKeySize;
|
|
LPBSAFE_PUB_KEY pPubKey;
|
|
LPBSAFE_PRV_KEY pPriKey;
|
|
CByteBlob SignedResult;
|
|
const int iCount = 100;
|
|
CEnv::CConstantByteRegion XmlSection;
|
|
|
|
status = RtlOpenAndMapEntireFile(pcwsz, &pvFileBase, &cbFileBase);
|
|
if (!NT_SUCCESS(status)) {
|
|
return;
|
|
}
|
|
|
|
XmlSection.SetPointerAndCount((PBYTE)pvFileBase, cbFileBase);
|
|
|
|
//
|
|
// Print, then sign the hash
|
|
//
|
|
BSafeComputeKeySizes(&dwPubKeySize, &dwPrivKeySize, &dwBitLength);
|
|
pPubKey = (LPBSAFE_PUB_KEY)HeapAlloc(GetProcessHeap(), 0, dwPubKeySize);
|
|
pPriKey = (LPBSAFE_PRV_KEY)HeapAlloc(GetProcessHeap(), 0, dwPrivKeySize);
|
|
BSafeMakeKeyPair(pPubKey, pPriKey, dwBitLength);
|
|
|
|
|
|
liTotal.QuadPart = 0;
|
|
for (int i = 0; i < iCount; i++)
|
|
{
|
|
QueryPerformanceCounter(&liStart);
|
|
CEnv::CStringBuffer SignatureBlob;
|
|
CEnv::CConstantByteRegion HashResults;
|
|
CByteBlob HashValue;
|
|
|
|
HashXmlSection<CUTF8BaseDigester, CSha1HashObject>(XmlSection, HashValue);
|
|
|
|
QueryPerformanceCounter(&liEnd);
|
|
liTotal.QuadPart += liEnd.QuadPart - liStart.QuadPart;
|
|
|
|
if (i == 0)
|
|
{
|
|
HashResults = HashValue.GetRange();
|
|
printf("\r\n");
|
|
for (SIZE_T c = 0; c < HashResults.GetCount(); c++) {
|
|
printf("%02x", HashResults.GetPointer()[c]);
|
|
}
|
|
printf("\r\n");
|
|
}
|
|
}
|
|
|
|
QueryPerformanceFrequency(&liEnd);
|
|
|
|
wprintf(
|
|
L"%I64d cycles, %f seconds",
|
|
liTotal.QuadPart / iCount,
|
|
(double)((((double)liTotal.QuadPart) / iCount) / ((double)liEnd.QuadPart)));
|
|
|
|
RtlUnmapViewOfFile(pvFileBase);
|
|
}
|
|
|
|
void __cdecl wmain(int argc, wchar_t *argv[])
|
|
{
|
|
GetSignatureOf(argv[1]);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CLogicalXmlParser::Reset()
|
|
{
|
|
bool fSuccess = true;
|
|
|
|
if (!m_fInitialized)
|
|
return true;
|
|
|
|
if (!NT_SUCCESS(RtlNsDestroy(&m_Namespaces)))
|
|
fSuccess = false;
|
|
|
|
if (!NT_SUCCESS(RtlXmlDestroyNextLogicalThing(&m_XmlState)))
|
|
fSuccess = false;
|
|
|
|
m_fInitialized = false;
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
bool CLogicalXmlParser::Initialize(PVOID pvXmlBase, SIZE_T cbDocumentSize)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
ASSERT(!m_fInitialized);
|
|
|
|
RTL_ALLOCATOR Alloc = { StaticAllocate, StaticFree, this };
|
|
|
|
status = RtlNsInitialize(
|
|
&m_Namespaces,
|
|
StaticCompareStrings, this,
|
|
&Alloc);
|
|
if (!NT_SUCCESS(status)) {
|
|
return false;
|
|
}
|
|
|
|
status = RtlXmlInitializeNextLogicalThing(
|
|
&m_XmlState,
|
|
pvXmlBase,
|
|
cbDocumentSize,
|
|
&Alloc);
|
|
if (!NT_SUCCESS(status)) {
|
|
RtlNsDestroy(&m_Namespaces);
|
|
return false;
|
|
}
|
|
|
|
if (!m_Attributes.Initialize()) {
|
|
RtlNsDestroy(&m_Namespaces);
|
|
RtlXmlDestroyNextLogicalThing(&m_XmlState);
|
|
return false;
|
|
}
|
|
|
|
m_fInitialized = true;
|
|
return true;
|
|
}
|
|
|
|
bool CLogicalXmlParser::More() const
|
|
{
|
|
return m_XmlState.ParseState.PreviousState != XTSS_STREAM_END;
|
|
}
|
|
|
|
bool CLogicalXmlParser::Next(XMLDOC_THING &XmlDocThing)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
status = RtlXmlNextLogicalThing(&m_XmlState, &m_Namespaces, &XmlDocThing, &m_Attributes);
|
|
return NT_SUCCESS(status);
|
|
}
|
|
|
|
|
|
//
|
|
// This parades through the document looking for the "end" of this element.
|
|
// When this function returns, the following "Next" call will get the document
|
|
// chunklet that's after the closing of the element.
|
|
//
|
|
bool CLogicalXmlParser::SkipElement(XMLDOC_ELEMENT &Element)
|
|
{
|
|
if (Element.fElementEmpty)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
XMLDOC_THING NextThing;
|
|
|
|
do
|
|
{
|
|
if (!this->Next(NextThing))
|
|
return false;
|
|
|
|
if ((NextThing.ulThingType == XMLDOC_THING_END_ELEMENT) &&
|
|
(NextThing.EndElement.OpeningElement == Element))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
while (this->More());
|
|
|
|
return true;
|
|
}
|
|
}
|