#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 class CArrayBlob { public: typedef BCL::CMutablePointerAndCountPair TRange; typedef BCL::CConstantPointerAndCountPair 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& 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 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 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 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(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> // Element, attributes: <{ns:}name [{atns:}attrib="text"]xN> // Empty element, attributes: <{ns:}name [{atns:}attrib="text"]xN/> // template 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 inline bool __fastcall AddHashData(T *pData, SIZE_T cData) { return AddHashDataInternal((PVOID)pData, cData * sizeof(T)); } template inline bool __fastcall AddHashData(T cSingleData) { return AddHashDataInternal(&cSingleData, sizeof(T)); } bool Digest(XMLDOC_ELEMENT &Element, CAttributeList &Attributes) { bool fSuccess = false; FastHash(); if (Element.NsPrefix.ulCharacters != 0) { if (!Digest(Element.NsPrefix, false)) goto Exit; FastHash(); } 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(); if (!Digest(Attrib)) goto Exit; } FastHash(); // // Empty elements implicitly get a , so do the above stuff again // if (Element.fElementEmpty) { FastHash(); if (Element.NsPrefix.ulCharacters != 0) { if (!Digest(Element.NsPrefix, false)) goto Exit; FastHash(); } if (!Digest(Element.Name, false)) goto Exit; FastHash(); } 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(); } if (!Digest(Attribute.Name, false)) goto Exit; FastHash(); if (!Digest(Attribute.Value, true)) goto Exit; FastHash(); 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: Zero bytes // - ex: Zero bytes (see in the ELEMENT digester above // - ex: f Effectively, "f" // - ex: a b Effectively, "a b" // - ex: a // 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(); } // // 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 // bool Digest(XMLDOC_ENDELEMENT &EndElement) { bool fSuccess = false; FastHash(); if (EndElement.NsPrefix.ulCharacters != 0) { if (!Digest(EndElement.NsPrefix, true)) goto Exit; FastHash(); } if (!Digest(EndElement.Name, true)) goto Exit; FastHash(); 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 element, inside which is a element. You should start another hashing context over the contents of the data. When that's done, you should have found: ... (signed data goes here) ... */ bool Base64EncodeBytes( const CEnv::CConstantByteRegion &Bytes, CEnv::CStringBuffer &Output ) { SIZE_T cCharsNeeded = 0; CArrayBlob 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"\r\n" L" \r\n" L" \r\n" L" \r\n" L" %ls\r\n" L" \r\n" L" \r\n" L"\r\n"; Target.Clear(); return true; } template 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(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; } }