Merge origin/feature into helpphil and fix a build break on Win32 builds.
This commit is contained in:
Коммит
a3b8592d02
|
@ -395,4 +395,6 @@ __pycache__/
|
|||
# to avoid seeing deleted: ../lib/zlib/zconf.h in the working directory
|
||||
# run git update-index --assume-unchanged lib/zlib/zconf.h
|
||||
lib/zlib
|
||||
src/inc/AppxCerts.hpp
|
||||
# Files generated by CMake
|
||||
src/inc/AppxCerts.hpp
|
||||
src/inc/AppxBlockMapSchemas.hpp
|
|
@ -0,0 +1,124 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.microsoft.com/appx/2017/blockmap"
|
||||
targetNamespace="http://schemas.microsoft.com/appx/2017/blockmap"
|
||||
elementFormDefault="qualified" attributeFormDefault="unqualified">
|
||||
|
||||
<xs:element name="BlockMap" type="CT_BlockMap"/>
|
||||
|
||||
<xs:complexType name="CT_BlockMap">
|
||||
<xs:sequence>
|
||||
<xs:element name="File" type="CT_File" minOccurs="1" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="HashMethod" type="xs:anyURI" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The method used for computing cryptographic hashes.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="PackageEncrypted" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Flag, indicates if the package is encrypted or not.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="CT_File">
|
||||
<xs:sequence>
|
||||
<xs:element name="Block" type="CT_Block" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="FileHash" type="CT_FileHash" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="Name" type="ST_FileName" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Root path and file name.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Id" type="ST_FileId" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Id of the file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Size" type="xs:nonNegativeInteger" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Size, in bytes, of the file's uncompressed data.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Encrypted" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Flag, indicates if file within the package is encrypted or not.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DeltaUpdated" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Flag, indicates if file within the package is delta updated.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="LfhSize" type="ST_HeaderSize" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Size, in bytes, of the file's Local File Header.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="CT_FileHash">
|
||||
<xs:attribute name="Hash" type="xs:base64Binary" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The hash value of the *uncompressed* data.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="ST_FileId">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The Id of the file must be non-empty and no more than 16 characters. It is a HEX encoding of a UINT64 integer, which the max length will be 16 characters "FFFFFFFFFFFFFFFF"</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:pattern value="[0-9a-fA-F]+"/>
|
||||
<xs:minLength value="1"/>
|
||||
<xs:maxLength value="16"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="CT_Block">
|
||||
<xs:attribute name="Hash" type="xs:base64Binary" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The hash value of the *uncompressed* data.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Size" type="xs:positiveInteger" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Size, in bytes, of the *compressed* data sequence for this block.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="PreDeltaUpdateSize" type="xs:positiveInteger" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Size before delta update, in bytes, of the *compressed* data sequence for this block.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="PackageOffset" type="xs:positiveInteger" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The offset value of the block within the package.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="ST_FileName">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The name of the file must be non-empty and no more than 32767 (UNICODE_STRING_MAX_CHARS) characters supported by the APPX package format (even though ZIP format supports up to 65535 bytes).</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:minLength value="1"/>
|
||||
<xs:maxLength value="32767"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="ST_HeaderSize">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The size of the Local File Header for a file must be at least 30 bytes and no more than 64KB as required by the ZIP format.</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:restriction base="xs:positiveInteger">
|
||||
<xs:minInclusive value="30"/>
|
||||
<xs:maxInclusive value="65536"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
|
@ -110,6 +110,14 @@ ENDIF()
|
|||
|
||||
MESSAGE (STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
IF ((CMAKE_BUILD_TYPE MATCHES RelWithDebInfo) OR (CMAKE_BUILD_TYPE MATCHES Release) OR (CMAKE_BUILD_TYPE MATCHES MinSizeRel))
|
||||
ADD_DEFINITIONS(-DNDEBUG)
|
||||
MESSAGE (STATUS "NDEBUG defined, assert should be turned-off" )
|
||||
ELSE()
|
||||
MESSAGE (STATUS "NDEBUG NOT defined, assert should be turned-on" )
|
||||
ENDIF()
|
||||
|
||||
|
||||
IF(MACOS)
|
||||
# Incredibly, for both clang and g++, while a single compile-and-link
|
||||
# invocation will create an executable.dSYM/ dir with debug info,
|
||||
|
|
|
@ -33,6 +33,22 @@
|
|||
included that in the larger CMake project.
|
||||
|
||||
* Make Nuget Package to target the native framework.
|
||||
|
||||
0.8 - Implement Signature validation (AppxSignature.p7x) on Win32, and put down the foundation for
|
||||
blockmap validation. Code now compiles and links against OpenSSL on non-Win32 platforms.
|
||||
Zlib was moved from a git submodule to a subtree so that we can now build on Ubuntu. Also,
|
||||
Zip64 support was added to the underlying OPC layer. Also, multiple fixes to the nuget package
|
||||
were added to address early adopter feedback. Plus, many bug fixes along the way
|
||||
|
||||
0.9 - Implement BlockMap validation and fix a number of various memory leaks. Added additional test
|
||||
collateral to cover negative cases for blockmap validation. Payload file names now come from
|
||||
the appxblockmap.xml file instead of from the underlying zip central directory. Xerces is now
|
||||
fully integrated as the XML parser, which we use to do XML parsing, XSD semantic validation, and
|
||||
Base64 decoding of manifested digest data. We also use Xerces to validate that the content type
|
||||
XML file is well-formed XML. We also implemented a number of the AppxBlockMap related interfaces
|
||||
that correspond to the new functionality provided in this version. Finally, we incorportated the
|
||||
latest Windows XSDs for appxblockmap.xml.
|
||||
|
||||
</releaseNotes>
|
||||
<copyright>Copyright (C) 2017 Microsoft</copyright>
|
||||
<tags>xPlatAppx Appx AppxPackaging native</tags>
|
||||
|
|
|
@ -6,5 +6,5 @@ cd .vs
|
|||
# clean up any old builds of xPlatAppx modules
|
||||
find . -name *xPlatAppx* -d | xargs rm -r
|
||||
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DLINUX=on ..
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DLINUX=on ..
|
||||
make
|
||||
|
|
2
makemac
2
makemac
|
@ -6,5 +6,5 @@ cd .vs
|
|||
# clean up any old builds of xPlatAppx modules
|
||||
find . -name *xPlatAppx* -d | xargs rm -r
|
||||
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMACOS=on ..
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMACOS=on ..
|
||||
make
|
||||
|
|
|
@ -4,5 +4,5 @@ cd .vs
|
|||
if exist CMakeFiles rd /s /q CMakeFiles
|
||||
if exist CMakeCache.txt del CMakeCache.txt
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
|
||||
cmake -DWIN32=on -DCMAKE_BUILD_TYPE=RelWithDebInfo -G"NMake Makefiles" ..
|
||||
cmake -DWIN32=on -DCMAKE_BUILD_TYPE=Release -G"NMake Makefiles" ..
|
||||
nmake
|
|
@ -1,72 +1,233 @@
|
|||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
|
||||
#include "StreamBase.hpp"
|
||||
#include "StorageObject.hpp"
|
||||
#include "VerifierObject.hpp"
|
||||
#include "AppxPackaging.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "UnicodeConversion.hpp"
|
||||
#include "AppxFactory.hpp"
|
||||
#include "XmlObject.hpp"
|
||||
#include "BlockMapStream.hpp"
|
||||
#include "xercesc/util/XMLString.hpp"
|
||||
|
||||
namespace xPlat {
|
||||
|
||||
class AppxBlockMapBlock : public xPlat::ComClass<AppxBlockMapBlock, IAppxBlockMapBlock>
|
||||
{
|
||||
public:
|
||||
AppxBlockMapBlock() :
|
||||
m_offset(0),
|
||||
m_size(0),
|
||||
m_encryptedSize(0),
|
||||
m_hashLength(0),
|
||||
m_packageOffset(0)
|
||||
AppxBlockMapBlock(IxPlatFactory* factory, Block* block) :
|
||||
m_factory(factory),
|
||||
m_block(block)
|
||||
{}
|
||||
|
||||
// IAppxBlockMapBlock
|
||||
HRESULT STDMETHODCALLTYPE GetHash(UINT32* bufferSize, BYTE** buffer);
|
||||
HRESULT STDMETHODCALLTYPE GetCompressedSize(UINT32* size);
|
||||
HRESULT STDMETHODCALLTYPE GetHash(UINT32* bufferSize, BYTE** buffer) override
|
||||
{
|
||||
return ResultOf([&]{
|
||||
ThrowHrIfFailed(m_factory->MarshalOutBytes(m_block->hash, bufferSize, buffer));
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetCompressedSize(UINT32* size) override
|
||||
{
|
||||
return ResultOf([&]{
|
||||
ThrowErrorIf(Error::InvalidParameter, (size == nullptr), "bad pointer");
|
||||
*size = static_cast<UINT32>(m_block->compressedSize);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::uint64_t m_offset;
|
||||
std::uint32_t m_size; // If the block is compressed, means the compressed size of the block. Otherwise, it is the uncompressed size.
|
||||
std::uint32_t m_encryptedSize; // Size of the encrypted block. It is the raw data size of the block if it is encrypted.
|
||||
std::uint32_t m_hashLength;
|
||||
std::uint32_t m_packageOffset;
|
||||
std::vector<unsigned char> hash;
|
||||
IxPlatFactory* m_factory;
|
||||
Block* m_block;
|
||||
};
|
||||
|
||||
class AppxBlockMapFile : public xPlat::ComClass<AppxBlockMapFile, IAppxBlockMapFile, IAppxBlockMapFilesEnumerator>
|
||||
class AppxBlockMapBlocksEnumerator : public xPlat::ComClass<AppxBlockMapBlocksEnumerator, IAppxBlockMapBlocksEnumerator>
|
||||
{
|
||||
protected:
|
||||
std::vector<ComPtr<IAppxBlockMapBlock>>* m_blocks;
|
||||
std::size_t m_cursor = 0;
|
||||
|
||||
public:
|
||||
AppxBlockMapBlocksEnumerator(std::vector<ComPtr<IAppxBlockMapBlock>>* blocks) :
|
||||
m_blocks(blocks)
|
||||
{}
|
||||
|
||||
// IAppxBlockMapBlocksEnumerator
|
||||
HRESULT STDMETHODCALLTYPE GetCurrent(IAppxBlockMapBlock** block) override
|
||||
{
|
||||
return ResultOf([&]{
|
||||
ThrowErrorIf(Error::InvalidParameter, (block == nullptr || *block != nullptr), "bad pointer");
|
||||
*block = m_blocks->at(m_cursor).Get();
|
||||
(*block)->AddRef();
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetHasCurrent(BOOL* hasCurrent) override
|
||||
{ return ResultOf([&]{
|
||||
ThrowErrorIfNot(Error::InvalidParameter, (hasCurrent), "bad pointer");
|
||||
*hasCurrent = (m_cursor != m_blocks->size()) ? TRUE : FALSE;
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE MoveNext(BOOL* hasNext) override
|
||||
{ return ResultOf([&]{
|
||||
ThrowErrorIfNot(Error::InvalidParameter, (hasNext), "bad pointer");
|
||||
*hasNext = (++m_cursor != m_blocks->size()) ? TRUE : FALSE;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class AppxBlockMapFile : public xPlat::ComClass<AppxBlockMapFile, IAppxBlockMapFile>
|
||||
{
|
||||
public:
|
||||
// IAppxBlockMapFile
|
||||
HRESULT STDMETHODCALLTYPE GetBlocks(IAppxBlockMapBlocksEnumerator **blocks) override;
|
||||
HRESULT STDMETHODCALLTYPE GetLocalFileHeaderSize(UINT32* lfhSize) override;
|
||||
HRESULT STDMETHODCALLTYPE GetName(LPWSTR *name) override;
|
||||
HRESULT STDMETHODCALLTYPE GetUncompressedSize(UINT64 *size) override;
|
||||
HRESULT STDMETHODCALLTYPE ValidateFileHash(IStream *fileStream, BOOL *isValid) override;
|
||||
AppxBlockMapFile(
|
||||
IxPlatFactory* factory,
|
||||
std::vector<Block>* blocks,
|
||||
std::uint32_t localFileHeaderSize,
|
||||
const std::string& name,
|
||||
std::uint64_t uncompressedSize
|
||||
) :
|
||||
m_factory(factory),
|
||||
m_blocks(blocks),
|
||||
m_localFileHeaderSize(localFileHeaderSize),
|
||||
m_name(name),
|
||||
m_uncompressedSize(uncompressedSize)
|
||||
{
|
||||
}
|
||||
|
||||
// IAppxBlockMapFilesEnumerator
|
||||
HRESULT STDMETHODCALLTYPE GetCurrent(IAppxBlockMapFile** block) override;
|
||||
HRESULT STDMETHODCALLTYPE GetHasCurrent(BOOL* hasCurrent) override;
|
||||
HRESULT STDMETHODCALLTYPE MoveNext(BOOL* hasNext) override;
|
||||
// IAppxBlockMapFile
|
||||
HRESULT STDMETHODCALLTYPE GetBlocks(IAppxBlockMapBlocksEnumerator **blocks) override
|
||||
{
|
||||
return ResultOf([&]{
|
||||
if (m_blockMapBlocks.empty())
|
||||
{ m_blockMapBlocks.reserve(m_blocks->size());
|
||||
std::transform(
|
||||
m_blocks->begin(),
|
||||
m_blocks->end(),
|
||||
std::back_inserter(m_blockMapBlocks),
|
||||
[&](auto item){
|
||||
return ComPtr<IAppxBlockMapBlock>::Make<AppxBlockMapBlock>(m_factory, &item);
|
||||
}
|
||||
);
|
||||
}
|
||||
ThrowErrorIf(Error::InvalidParameter, (blocks == nullptr || *blocks != nullptr), "bad pointer.");
|
||||
*blocks = ComPtr<IAppxBlockMapBlocksEnumerator>::Make<AppxBlockMapBlocksEnumerator>(&m_blockMapBlocks).Detach();
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetLocalFileHeaderSize(UINT32* lfhSize) override
|
||||
{ // Retrieves the size of the zip local file header of the associated zip file item
|
||||
return ResultOf([&]{
|
||||
ThrowErrorIf(Error::InvalidParameter, (lfhSize == nullptr), "bad pointer");
|
||||
*lfhSize = static_cast<UINT32>(m_localFileHeaderSize);
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetName(LPWSTR *name) override
|
||||
{
|
||||
return ResultOf([&]{
|
||||
ThrowHrIfFailed(m_factory->MarshalOutString(m_name, name));
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetUncompressedSize(UINT64 *size) override
|
||||
{
|
||||
return ResultOf([&]{
|
||||
ThrowErrorIf(Error::InvalidParameter, (size == nullptr), "bad pointer");
|
||||
*size = static_cast<UINT64>(m_uncompressedSize);
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE ValidateFileHash(IStream *fileStream, BOOL *isValid) override
|
||||
{
|
||||
return ResultOf([&]{
|
||||
// TODO: Implement...
|
||||
throw Exception(Error::NotImplemented);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<AppxBlockMapBlock>> m_blocks;
|
||||
|
||||
std::vector<ComPtr<IAppxBlockMapBlock>> m_blockMapBlocks;
|
||||
std::vector<Block>* m_blocks;
|
||||
IxPlatFactory* m_factory;
|
||||
std::uint32_t m_localFileHeaderSize;
|
||||
std::string m_name;
|
||||
std::uint64_t m_uncompressedSize;
|
||||
};
|
||||
|
||||
class AppxBlockMapFilesEnumerator : public xPlat::ComClass<AppxBlockMapFilesEnumerator, IAppxBlockMapFilesEnumerator>
|
||||
{
|
||||
protected:
|
||||
ComPtr<IAppxBlockMapReader> m_reader;
|
||||
std::vector<std::string> m_files;
|
||||
std::size_t m_cursor = 0;
|
||||
|
||||
public:
|
||||
AppxBlockMapFilesEnumerator(IAppxBlockMapReader* reader, std::vector<std::string>&& files) :
|
||||
m_reader(reader),
|
||||
m_files(files)
|
||||
{}
|
||||
|
||||
// IAppxBlockMapFilesEnumerator
|
||||
HRESULT STDMETHODCALLTYPE GetCurrent(IAppxBlockMapFile** block) override
|
||||
{
|
||||
return ResultOf([&]{
|
||||
ThrowErrorIf(Error::Unexpected, (m_cursor >= m_files.size()), "index out of range");
|
||||
ThrowHrIfFailed(m_reader->GetFile(utf8_to_utf16(m_files.at(m_cursor)).c_str(), block));
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetHasCurrent(BOOL* hasCurrent) override
|
||||
{ return ResultOf([&]{
|
||||
ThrowErrorIfNot(Error::InvalidParameter, (hasCurrent), "bad pointer");
|
||||
*hasCurrent = (m_cursor != m_files.size()) ? TRUE : FALSE;
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE MoveNext(BOOL* hasNext) override
|
||||
{ return ResultOf([&]{
|
||||
ThrowErrorIfNot(Error::InvalidParameter, (hasNext), "bad pointer");
|
||||
*hasNext = (++m_cursor != m_files.size()) ? TRUE : FALSE;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Object backed by AppxBlockMap.xml
|
||||
class AppxBlockMapObject : public VerifierObject, public xPlat::ComClass<AppxBlockMapObject, IAppxBlockMapBlocksEnumerator>
|
||||
class AppxBlockMapObject : public xPlat::ComClass<AppxBlockMapObject, IAppxBlockMapReader, IVerifierObject, IStorageObject>
|
||||
{
|
||||
public:
|
||||
AppxBlockMapObject(IStream* stream);
|
||||
AppxBlockMapObject(IxPlatFactory* factory, ComPtr<IStream>& stream);
|
||||
|
||||
IStream* GetValidationStream(const std::string& part, IStream* stream) override;
|
||||
// IVerifierObject
|
||||
bool HasStream() override { return m_stream.Get() != nullptr; }
|
||||
xPlat::ComPtr<IStream> GetStream() override { return m_stream; }
|
||||
xPlat::ComPtr<IStream> GetValidationStream(const std::string& part, IStream* stream) override;
|
||||
|
||||
// IAppxBlockMapBlocksEnumerator
|
||||
HRESULT STDMETHODCALLTYPE GetCurrent(IAppxBlockMapBlock** block) override;
|
||||
HRESULT STDMETHODCALLTYPE GetHasCurrent(BOOL* hasCurrent) override;
|
||||
HRESULT STDMETHODCALLTYPE MoveNext(BOOL* hasNext) override;
|
||||
// IAppxBlockMapReader
|
||||
HRESULT STDMETHODCALLTYPE GetFile(LPCWSTR filename, IAppxBlockMapFile **file) override;
|
||||
HRESULT STDMETHODCALLTYPE GetFiles(IAppxBlockMapFilesEnumerator **enumerator) override;
|
||||
HRESULT STDMETHODCALLTYPE GetHashMethod(IUri **hashMethod) override;
|
||||
HRESULT STDMETHODCALLTYPE GetStream(IStream **blockMapStream) override;
|
||||
|
||||
// IStorageObject methods
|
||||
std::string GetPathSeparator() override;
|
||||
std::vector<std::string> GetFileNames(FileNameOptions options) override;
|
||||
IStream* GetFile(const std::string& fileName) override;
|
||||
void RemoveFile(const std::string& fileName) override;
|
||||
IStream* OpenFile(const std::string& fileName, xPlat::FileStream::Mode mode) override;
|
||||
void CommitChanges() override;
|
||||
|
||||
protected:
|
||||
std::map<std::string, std::shared_ptr<AppxBlockMapFile>> m_blockMapfiles;
|
||||
std::map<std::string, std::vector<Block>> m_blockMap;
|
||||
std::map<std::string, ComPtr<IAppxBlockMapFile>> m_blockMapfiles;
|
||||
IxPlatFactory* m_factory;
|
||||
ComPtr<IStream> m_stream;
|
||||
};
|
||||
}
|
|
@ -3,8 +3,10 @@
|
|||
#include "AppxPackaging.hpp"
|
||||
#include "AppxWindows.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "xercesc/util/PlatformUtils.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// internal interface
|
||||
EXTERN_C const IID IID_IxPlatFactory;
|
||||
|
@ -18,10 +20,9 @@ class IxPlatFactory : public IUnknown
|
|||
#endif
|
||||
{
|
||||
public:
|
||||
#ifdef WIN32
|
||||
virtual ~IxPlatFactory() {}
|
||||
#endif
|
||||
virtual HRESULT MarshalOutString(std::string& internal, LPWSTR *result) = 0;
|
||||
virtual HRESULT MarshalOutBytes(std::vector<std::uint8_t>& data, UINT32* size, BYTE** buffer) = 0;
|
||||
virtual APPX_VALIDATION_OPTION GetValidationOptions() = 0;
|
||||
};
|
||||
|
||||
SpecializeUuidOfImpl(IxPlatFactory);
|
||||
|
@ -34,6 +35,12 @@ namespace xPlat {
|
|||
m_validationOptions(validationOptions), m_memalloc(memalloc), m_memfree(memfree)
|
||||
{
|
||||
ThrowErrorIf(Error::InvalidParameter, (m_memalloc == nullptr || m_memfree == nullptr), "allocator/deallocator pair not specified.")
|
||||
XERCES_CPP_NAMESPACE::XMLPlatformUtils::Initialize();
|
||||
}
|
||||
|
||||
~AppxFactory()
|
||||
{
|
||||
XERCES_CPP_NAMESPACE::XMLPlatformUtils::Terminate();
|
||||
}
|
||||
|
||||
// IAppxFactory
|
||||
|
@ -53,6 +60,8 @@ namespace xPlat {
|
|||
|
||||
// IxPlatFactory
|
||||
HRESULT MarshalOutString(std::string& internal, LPWSTR *result) override;
|
||||
HRESULT MarshalOutBytes(std::vector<std::uint8_t>& data, UINT32* size, BYTE** buffer) override;
|
||||
APPX_VALIDATION_OPTION GetValidationOptions() override { return m_validationOptions; }
|
||||
|
||||
COTASKMEMALLOC* m_memalloc;
|
||||
COTASKMEMFREE* m_memfree;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "XmlObject.hpp"
|
||||
#include "AppxBlockMapObject.hpp"
|
||||
#include "AppxSignature.hpp"
|
||||
#include "AppxFactory.hpp"
|
||||
|
||||
// internal interface
|
||||
EXTERN_C const IID IID_IAppxPackage;
|
||||
|
@ -29,10 +30,6 @@ class IAppxPackage : public IUnknown
|
|||
#endif
|
||||
{
|
||||
public:
|
||||
#ifdef WIN32
|
||||
virtual ~IAppxPackage() {}
|
||||
#endif
|
||||
|
||||
virtual void Pack(APPX_PACKUNPACK_OPTION options, const std::string& certFile, IStorageObject* from) = 0;
|
||||
virtual void Unpack(APPX_PACKUNPACK_OPTION options, IStorageObject* to) = 0;
|
||||
virtual std::vector<std::string>& GetFootprintFiles() = 0;
|
||||
|
@ -69,12 +66,15 @@ namespace xPlat {
|
|||
};
|
||||
|
||||
// Object backed by AppxManifest.xml
|
||||
class AppxManifestObject : public VerifierObject
|
||||
class AppxManifestObject : public ComClass<AppxManifestObject, IVerifierObject>
|
||||
{
|
||||
public:
|
||||
AppxManifestObject(IStream* stream);
|
||||
AppxManifestObject(ComPtr<IStream>& stream);
|
||||
|
||||
IStream* GetValidationStream(const std::string& part, IStream* stream) override
|
||||
// IVerifierObject
|
||||
bool HasStream() override { return m_stream.Get() != nullptr; }
|
||||
xPlat::ComPtr<IStream> GetStream() override { return m_stream; }
|
||||
xPlat::ComPtr<IStream> GetValidationStream(const std::string& part, IStream* stream) override
|
||||
{
|
||||
throw Exception(Error::NotSupported);
|
||||
}
|
||||
|
@ -88,19 +88,16 @@ namespace xPlat {
|
|||
};
|
||||
|
||||
// Storage object representing the entire AppxPackage
|
||||
class AppxPackageObject : public xPlat::ComClass<AppxPackageObject, IAppxPackageReader, IAppxPackage, IStorageObject>
|
||||
class AppxPackageObject : public ComClass<AppxPackageObject, IAppxPackageReader, IAppxPackage, IStorageObject>
|
||||
{
|
||||
public:
|
||||
AppxPackageObject(APPX_VALIDATION_OPTION validation, IStorageObject* container);
|
||||
AppxPackageObject(IxPlatFactory* factory, APPX_VALIDATION_OPTION validation, IStorageObject* container);
|
||||
~AppxPackageObject() {}
|
||||
|
||||
// internal IxPlatAppxPackage methods
|
||||
void Pack(APPX_PACKUNPACK_OPTION options, const std::string& certFile, IStorageObject* from) override;
|
||||
void Unpack(APPX_PACKUNPACK_OPTION options, IStorageObject* to) override;
|
||||
|
||||
AppxSignatureObject* GetAppxSignature() const { return m_appxSignature.get(); }
|
||||
AppxBlockMapObject* GetAppxBlockMap() const { return m_appxBlockMap.get(); }
|
||||
AppxManifestObject* GetAppxManifest() const { return m_appxManifest.get(); }
|
||||
|
||||
// IAppxPackageReader
|
||||
HRESULT STDMETHODCALLTYPE GetBlockMap(IAppxBlockMapReader** blockMapReader) override;
|
||||
HRESULT STDMETHODCALLTYPE GetFootprintFile(APPX_FOOTPRINT_FILE_TYPE type, IAppxFile** file) override;
|
||||
|
@ -121,16 +118,17 @@ namespace xPlat {
|
|||
|
||||
protected:
|
||||
std::map<std::string, ComPtr<IStream>> m_streams;
|
||||
APPX_VALIDATION_OPTION m_validation = APPX_VALIDATION_OPTION::APPX_VALIDATION_OPTION_FULL;
|
||||
std::unique_ptr<AppxSignatureObject> m_appxSignature;
|
||||
std::unique_ptr<AppxBlockMapObject> m_appxBlockMap;
|
||||
std::unique_ptr<AppxManifestObject> m_appxManifest;
|
||||
ComPtr<IStorageObject> m_container;
|
||||
|
||||
std::vector<std::string> m_payloadFiles;
|
||||
std::vector<std::string> m_footprintFiles;
|
||||
|
||||
std::unique_ptr<XmlObject> m_contentType;
|
||||
APPX_VALIDATION_OPTION m_validation = APPX_VALIDATION_OPTION::APPX_VALIDATION_OPTION_FULL;
|
||||
ComPtr<IxPlatFactory> m_factory;
|
||||
ComPtr<IVerifierObject> m_appxSignature;
|
||||
ComPtr<IVerifierObject> m_appxBlockMap;
|
||||
ComPtr<IVerifierObject> m_appxManifest;
|
||||
ComPtr<IVerifierObject> m_contentType;
|
||||
ComPtr<IStorageObject> m_container;
|
||||
|
||||
std::vector<std::string> m_payloadFiles;
|
||||
std::vector<std::string> m_footprintFiles;
|
||||
};
|
||||
|
||||
class AppxFilesEnumerator : public xPlat::ComClass<AppxFilesEnumerator, IAppxFilesEnumerator>
|
||||
|
|
|
@ -35,9 +35,9 @@ namespace xPlat {
|
|||
const DWORD P7X_FILE_ID = 0x58434b50;
|
||||
|
||||
// Object backed by AppxSignature.p7x
|
||||
class AppxSignatureObject : public VerifierObject
|
||||
class AppxSignatureObject : public ComClass<AppxSignatureObject, IVerifierObject>
|
||||
{
|
||||
public:
|
||||
public:
|
||||
enum DigestName : std::uint32_t
|
||||
{
|
||||
HEAD = 0x58505041, // APPX
|
||||
|
@ -50,7 +50,10 @@ namespace xPlat {
|
|||
|
||||
AppxSignatureObject(APPX_VALIDATION_OPTION validationOptions, IStream* stream);
|
||||
|
||||
IStream* GetValidationStream(const std::string& part, IStream* stream) override;
|
||||
// IVerifierObject
|
||||
bool HasStream() override { return m_stream.Get() != nullptr; }
|
||||
xPlat::ComPtr<IStream> GetStream() override { return m_stream; }
|
||||
xPlat::ComPtr<IStream> GetValidationStream(const std::string& part, IStream* stream) override;
|
||||
|
||||
using Digest = std::vector<std::uint8_t>;
|
||||
|
||||
|
@ -66,5 +69,6 @@ namespace xPlat {
|
|||
std::map<DigestName, Digest> m_digests;
|
||||
SignatureOrigin m_signatureOrigin = SignatureOrigin::Unsigned; // assume unsigned until proven otherwise.
|
||||
APPX_VALIDATION_OPTION m_validationOptions;
|
||||
ComPtr<IStream> m_stream;
|
||||
};
|
||||
} // namespace xPlat
|
|
@ -12,29 +12,22 @@
|
|||
#include <map>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace xPlat {
|
||||
|
||||
const std::uint32_t BLOCKMAP_BLOCK_SIZE = 65535;
|
||||
const std::uint64_t BLOCKMAP_BLOCK_SIZE = 65536; // 64KB
|
||||
|
||||
typedef struct Block
|
||||
{
|
||||
std::uint64_t size;
|
||||
std::uint64_t offset;
|
||||
std::uint64_t compressedSize;
|
||||
std::vector<std::uint8_t> hash;
|
||||
} Block;
|
||||
|
||||
typedef struct BlockCompare
|
||||
{
|
||||
bool operator()(const Block& lhs, const Block& rhs) const
|
||||
{
|
||||
return lhs.offset < rhs.offset;
|
||||
}
|
||||
} BlockCompare;
|
||||
|
||||
typedef struct BlockPlusStream : Block
|
||||
{
|
||||
std::uint64_t size;
|
||||
std::uint64_t offset;
|
||||
ComPtr<IStream> stream;
|
||||
} BlockPlusStream;
|
||||
|
||||
|
@ -42,25 +35,8 @@ namespace xPlat {
|
|||
class BlockMapStream : public StreamBase
|
||||
{
|
||||
public:
|
||||
BlockMapStream(/*[In]*/ IStream* stream, /*[In]*/ std::set<Block, BlockCompare>& blocks)
|
||||
BlockMapStream(IStream* stream, std::vector<Block>& blocks)
|
||||
{
|
||||
// Build a vector of all HashStream->RangeStream's for the blocks in the blockmap
|
||||
for (auto block = blocks.begin(); block != blocks.end(); block++)
|
||||
{
|
||||
ThrowErrorIfNot(xPlat::Error::AppxSignatureInvalid,
|
||||
((block->offset % BLOCKMAP_BLOCK_SIZE == 0) && (block->size <= BLOCKMAP_BLOCK_SIZE)),
|
||||
"block size must be less than 65535");
|
||||
auto rangeStream = ComPtr<IStream>::Make<RangeStream>(block->offset, block->size, stream);
|
||||
auto hashStream = ComPtr<IStream>::Make<HashStream>(rangeStream.Get(), block->hash);
|
||||
|
||||
BlockPlusStream bs;
|
||||
bs.offset = block->offset;
|
||||
bs.size = block->size;
|
||||
bs.stream = hashStream;
|
||||
bs.hash.assign(&block->hash[0], &block->hash[block->hash.size()]);
|
||||
m_blockStreams.push_back(bs);
|
||||
}
|
||||
|
||||
// Determine overall stream size
|
||||
ULARGE_INTEGER uli;
|
||||
LARGE_INTEGER li;
|
||||
|
@ -72,6 +48,30 @@ namespace xPlat {
|
|||
// Reset seek position to beginning
|
||||
li.QuadPart = 0;
|
||||
ThrowHrIfFailed(stream->Seek(li, STREAM_SEEK_SET, nullptr));
|
||||
|
||||
// Build a vector of all HashStream->RangeStream's for the blocks in the blockmap
|
||||
std::uint64_t offset = 0;
|
||||
std::uint64_t sizeRemaining = m_streamSize;
|
||||
for (auto block = blocks.begin(); ((sizeRemaining != 0) && (block != blocks.end())); block++)
|
||||
{
|
||||
auto rangeStream = ComPtr<IStream>::Make<RangeStream>(offset, std::min(sizeRemaining, BLOCKMAP_BLOCK_SIZE), stream);
|
||||
auto hashStream = ComPtr<IStream>::Make<HashStream>(rangeStream.Get(), block->hash);
|
||||
std::uint64_t blockSize = std::min(sizeRemaining, BLOCKMAP_BLOCK_SIZE);
|
||||
|
||||
BlockPlusStream bs;
|
||||
bs.offset = offset;
|
||||
bs.size = blockSize;
|
||||
bs.stream = hashStream;
|
||||
bs.hash = block->hash;
|
||||
m_blockStreams.emplace_back(std::move(bs));
|
||||
|
||||
offset += blockSize;
|
||||
sizeRemaining -= blockSize;
|
||||
}
|
||||
|
||||
// Reset seek position to beginning
|
||||
ThrowHrIfFailed(stream->Seek(li, STREAM_SEEK_SET, nullptr));
|
||||
ThrowHrIfFailed(Seek(li, STREAM_SEEK_SET, nullptr));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *newPosition) override
|
||||
|
@ -91,6 +91,8 @@ namespace xPlat {
|
|||
}
|
||||
m_relativePosition = std::max((std::uint64_t)0, std::min(m_relativePosition, m_streamSize));
|
||||
if (newPosition) { newPosition->QuadPart = m_relativePosition; }
|
||||
|
||||
m_currentBlock = m_blockStreams.begin();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
@ -100,25 +102,32 @@ namespace xPlat {
|
|||
if (m_relativePosition < m_streamSize)
|
||||
{
|
||||
std::uint32_t bytesToRead = std::min(static_cast<std::uint32_t>(countBytes), static_cast<std::uint32_t>(m_streamSize - m_relativePosition));
|
||||
|
||||
for (auto block = m_blockStreams.begin(); block != m_blockStreams.end() && bytesToRead > 0; block++)
|
||||
while (m_currentBlock != m_blockStreams.end() && bytesToRead > 0)
|
||||
{
|
||||
if (block->offset <= m_relativePosition)
|
||||
if ((m_currentBlock->offset + m_currentBlock->size) <= m_relativePosition)
|
||||
{
|
||||
LARGE_INTEGER li;
|
||||
Seek(li, STREAM_SEEK_SET, nullptr);
|
||||
li.QuadPart = (m_relativePosition - block->offset);
|
||||
ThrowHrIfFailed(block->stream.Get()->Seek(li, STREAM_SEEK_SET, nullptr));
|
||||
m_currentBlock++;
|
||||
}
|
||||
else if (m_currentBlock->offset <= m_relativePosition)
|
||||
{
|
||||
std::uint64_t positionInBlock = m_relativePosition - m_currentBlock->offset;
|
||||
LARGE_INTEGER li{0};
|
||||
li.QuadPart = positionInBlock;
|
||||
ThrowHrIfFailed(m_currentBlock->stream->Seek(li, STREAM_SEEK_SET, nullptr));
|
||||
|
||||
std::uint32_t count = std::min(bytesToRead, static_cast<std::uint32_t>(block->size - (m_relativePosition - block->offset)));
|
||||
std::uint32_t count = std::min(bytesToRead, static_cast<std::uint32_t>(m_currentBlock->size - positionInBlock));
|
||||
ULONG actual = 0;
|
||||
ThrowHrIfFailed(block->stream.Get()->Read(buffer, count, &actual));
|
||||
ThrowHrIfFailed(m_currentBlock->stream->Read(buffer, count, &actual));
|
||||
|
||||
buffer = static_cast<std::uint8_t*>(buffer) + actual;
|
||||
m_relativePosition += actual;
|
||||
bytesToRead -= actual;
|
||||
bytesRead += actual;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_currentBlock = m_blockStreams.begin();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (actualRead) { *actualRead = bytesRead; }
|
||||
|
@ -126,6 +135,7 @@ namespace xPlat {
|
|||
}
|
||||
|
||||
protected:
|
||||
std::vector<BlockPlusStream>::iterator m_currentBlock;
|
||||
std::vector<BlockPlusStream> m_blockStreams;
|
||||
std::uint64_t m_relativePosition;
|
||||
std::uint64_t m_streamSize;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "Exceptions.hpp"
|
||||
#include "AppxPackaging.hpp"
|
||||
#include "xercesc/util/XMLString.hpp"
|
||||
|
||||
namespace xPlat {
|
||||
|
||||
|
@ -90,7 +91,7 @@ namespace xPlat {
|
|||
// Assignment operator...
|
||||
ComPtr& operator=(const ComPtr& right)
|
||||
{
|
||||
if (m_ptr != right.m_ptr) { ComPtr(right).Swap(*this); }
|
||||
if (m_ptr != right.m_ptr) { ComPtr(right).Swap(*this); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -167,4 +168,136 @@ namespace xPlat {
|
|||
std::atomic<std::uint32_t> m_ref;
|
||||
ComClass() : m_ref(1) {}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class XercesPtr
|
||||
{
|
||||
public:
|
||||
XercesPtr() : m_ptr(nullptr) {}
|
||||
XercesPtr(T* p) : m_ptr(p) {}
|
||||
|
||||
// move ctor
|
||||
XercesPtr(XercesPtr &&right) : m_ptr(nullptr)
|
||||
{
|
||||
if (this != reinterpret_cast<XercesPtr*>(&reinterpret_cast<std::int8_t&>(right)))
|
||||
{ Swap(right);
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~XercesPtr() { InternalRelease(); }
|
||||
|
||||
void InternalRelease()
|
||||
{
|
||||
T* temp = m_ptr;
|
||||
if (temp)
|
||||
{
|
||||
m_ptr = nullptr;
|
||||
temp->release();
|
||||
}
|
||||
}
|
||||
|
||||
XercesPtr& operator=(XercesPtr&& right)
|
||||
{ XercesPtr(std::move(right)).Swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
T* operator->() const { return m_ptr; }
|
||||
T* Get() const { return m_ptr; }
|
||||
protected:
|
||||
inline void Swap(XercesPtr& right ) { std::swap(m_ptr, right.m_ptr); }
|
||||
T* m_ptr = nullptr;
|
||||
};
|
||||
|
||||
class XercesCharPtr
|
||||
{
|
||||
public:
|
||||
XercesCharPtr(char* c) : m_ptr(c) {};
|
||||
~XercesCharPtr() { InternalRelease(); }
|
||||
|
||||
// move ctor
|
||||
XercesCharPtr(XercesCharPtr &&right) : m_ptr(nullptr)
|
||||
{
|
||||
if (this != reinterpret_cast<XercesCharPtr*>(&reinterpret_cast<std::int8_t&>(right)))
|
||||
{ Swap(right);
|
||||
}
|
||||
}
|
||||
|
||||
void InternalRelease()
|
||||
{ XERCES_CPP_NAMESPACE::XMLString::release(&m_ptr);
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
|
||||
XercesCharPtr& operator=(XercesCharPtr&& right)
|
||||
{ XercesCharPtr(std::move(right)).Swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
char* operator->() const { return m_ptr; }
|
||||
char* Get() const { return m_ptr; }
|
||||
protected:
|
||||
inline void Swap(XercesCharPtr& right ) { std::swap(m_ptr, right.m_ptr); }
|
||||
char* m_ptr = nullptr;
|
||||
};
|
||||
|
||||
class XercesXMLChPtr
|
||||
{
|
||||
public:
|
||||
XercesXMLChPtr(XMLCh* c) : m_ptr(c) {}
|
||||
~XercesXMLChPtr() { InternalRelease(); }
|
||||
|
||||
// move ctor
|
||||
XercesXMLChPtr(XercesXMLChPtr &&right) : m_ptr(nullptr)
|
||||
{
|
||||
if (this != reinterpret_cast<XercesXMLChPtr*>(&reinterpret_cast<std::int8_t&>(right)))
|
||||
{ Swap(right);
|
||||
}
|
||||
}
|
||||
|
||||
void InternalRelease()
|
||||
{ XERCES_CPP_NAMESPACE::XMLString::release(&m_ptr);
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
|
||||
XercesXMLChPtr& operator=(XercesXMLChPtr&& right)
|
||||
{ XercesXMLChPtr(std::move(right)).Swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
XMLCh* operator->() const { return m_ptr; }
|
||||
XMLCh* Get() const { return m_ptr; }
|
||||
protected:
|
||||
inline void Swap(XercesXMLChPtr& right ) { std::swap(m_ptr, right.m_ptr); }
|
||||
XMLCh* m_ptr = nullptr;
|
||||
};
|
||||
|
||||
class XercesXMLBytePtr
|
||||
{
|
||||
public:
|
||||
XercesXMLBytePtr(XMLByte* c) : m_ptr(c) {}
|
||||
~XercesXMLBytePtr() { InternalRelease(); }
|
||||
|
||||
// move ctor
|
||||
XercesXMLBytePtr(XercesXMLBytePtr &&right) : m_ptr(nullptr)
|
||||
{
|
||||
if (this != reinterpret_cast<XercesXMLBytePtr*>(&reinterpret_cast<std::int8_t&>(right)))
|
||||
{ Swap(right);
|
||||
}
|
||||
}
|
||||
|
||||
void InternalRelease()
|
||||
{ delete(m_ptr);
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
|
||||
XercesXMLBytePtr& operator=(XercesXMLBytePtr&& right)
|
||||
{ XercesXMLBytePtr(std::move(right)).Swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
XMLByte* operator->() const { return m_ptr; }
|
||||
XMLByte* Get() const { return m_ptr; }
|
||||
protected:
|
||||
inline void Swap(XercesXMLBytePtr& right ) { std::swap(m_ptr, right.m_ptr); }
|
||||
XMLByte* m_ptr = nullptr;
|
||||
};
|
||||
}
|
|
@ -7,10 +7,17 @@
|
|||
#include <functional>
|
||||
|
||||
#include "AppxWindows.hpp"
|
||||
#include "xercesc/util/PlatformUtils.hpp"
|
||||
#include "xercesc/sax/ErrorHandler.hpp"
|
||||
#include "xercesc/sax/SAXParseException.hpp"
|
||||
#include "xercesc/dom/DOM.hpp"
|
||||
|
||||
namespace xPlat {
|
||||
|
||||
static const std::uint32_t ERROR_FACILITY = 0x8BAD0000; // Facility 2989
|
||||
static const std::uint32_t ERROR_FACILITY = 0x8BAD0000; // Facility 2989
|
||||
static const std::uint32_t XERCES_SAX_FACILITY = ERROR_FACILITY + 0x1000; // Xerces XMLException. 0x8BAD1000 + XMLException error code
|
||||
static const std::uint32_t XERCES_XML_FACILITY = ERROR_FACILITY + 0x2000;
|
||||
static const std::uint32_t XERCES_DOM_FACILITY = ERROR_FACILITY + 0x3000;
|
||||
|
||||
// defines error codes
|
||||
enum class Error : std::uint32_t
|
||||
|
@ -60,11 +67,19 @@ namespace xPlat {
|
|||
AppxMissingBlockMapXML = ERROR_FACILITY + 0x0033,
|
||||
AppxMissingAppxManifestXML = ERROR_FACILITY + 0x0034,
|
||||
AppxDuplicateFootprintFile = ERROR_FACILITY + 0x0035,
|
||||
AppxUnknownFileNameEncoding = ERROR_FACILITY + 0x0036,
|
||||
|
||||
// Signature errors
|
||||
AppxSignatureInvalid = ERROR_FACILITY + 0x0041,
|
||||
AppxCertNotTrusted = ERROR_FACILITY + 0x0042,
|
||||
|
||||
|
||||
// Blockmap semantic errors
|
||||
BlockMapSemanticError = ERROR_FACILITY + 0x0051,
|
||||
|
||||
// XML parsing errors
|
||||
XercesWarning = XERCES_SAX_FACILITY + 0x0001,
|
||||
XercesError = XERCES_SAX_FACILITY + 0x0002,
|
||||
XercesFatal = XERCES_SAX_FACILITY + 0x0003,
|
||||
};
|
||||
|
||||
// Defines a common exception type to throw in exceptional cases. DO NOT USE FOR FLOW CONTROL!
|
||||
|
@ -96,7 +111,7 @@ namespace xPlat {
|
|||
Exception(HRESULT error, const char* message) :
|
||||
m_code(error),
|
||||
m_message(message)
|
||||
{}
|
||||
{}
|
||||
|
||||
uint32_t Code() { return m_code; }
|
||||
std::string& Message() { return m_message; }
|
||||
|
@ -118,6 +133,36 @@ namespace xPlat {
|
|||
{}
|
||||
};
|
||||
|
||||
class ParsingException : public XERCES_CPP_NAMESPACE::ErrorHandler
|
||||
{
|
||||
public:
|
||||
ParsingException() {};
|
||||
~ParsingException() {};
|
||||
|
||||
void warning(const XERCES_CPP_NAMESPACE::SAXParseException& exp) override
|
||||
{
|
||||
// TODO: add message, line number and column
|
||||
assert(false);
|
||||
throw Exception(xPlat::Error::XercesWarning);
|
||||
}
|
||||
|
||||
void error(const XERCES_CPP_NAMESPACE::SAXParseException& exp) override
|
||||
{
|
||||
// TODO: add message, line number and column
|
||||
assert(false);
|
||||
throw Exception(xPlat::Error::XercesError);
|
||||
}
|
||||
|
||||
void fatalError(const XERCES_CPP_NAMESPACE::SAXParseException& exp) override
|
||||
{
|
||||
// TODO: add message, line number and column
|
||||
assert(false);
|
||||
throw Exception(xPlat::Error::XercesFatal);
|
||||
}
|
||||
|
||||
void resetErrors() override {}
|
||||
};
|
||||
|
||||
// Provides an ABI exception boundary with parameter validation
|
||||
template <class Lambda>
|
||||
inline HRESULT ResultOf(Lambda lambda)
|
||||
|
@ -139,6 +184,16 @@ namespace xPlat {
|
|||
{
|
||||
hr = static_cast<HRESULT>(xPlat::Error::Unexpected);
|
||||
}
|
||||
catch (const XERCES_CPP_NAMESPACE::XMLException& e)
|
||||
{
|
||||
hr = static_cast<HRESULT>(xPlat::XERCES_XML_FACILITY) +
|
||||
static_cast<HRESULT>(e.getCode());
|
||||
}
|
||||
catch (const XERCES_CPP_NAMESPACE::DOMException& e)
|
||||
{
|
||||
hr = static_cast<HRESULT>(xPlat::XERCES_DOM_FACILITY) +
|
||||
static_cast<HRESULT>(e.code);
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
|
|
@ -16,45 +16,56 @@ namespace xPlat {
|
|||
// This represents a subset of a Stream
|
||||
class HashStream : public StreamBase
|
||||
{
|
||||
protected:
|
||||
bool m_validated;
|
||||
ComPtr<IStream> m_stream;
|
||||
std::vector<std::uint8_t>& m_expectedHash;
|
||||
std::unique_ptr<std::vector<std::uint8_t>> m_cacheBuffer;
|
||||
std::uint64_t m_relativePosition;
|
||||
size_t m_streamSize;
|
||||
|
||||
public:
|
||||
HashStream(IStream* stream, const std::vector<std::uint8_t>& expectedHash) :
|
||||
m_relativePosition(0)
|
||||
HashStream(IStream* stream, std::vector<std::uint8_t>& expectedHash) :
|
||||
m_validated(false),
|
||||
m_stream(stream),
|
||||
m_expectedHash(expectedHash),
|
||||
m_relativePosition(0),
|
||||
m_streamSize(0)
|
||||
{
|
||||
ULARGE_INTEGER uli;
|
||||
LARGE_INTEGER li;
|
||||
std::uint64_t streamSize;
|
||||
|
||||
li.QuadPart = 0;
|
||||
ThrowHrIfFailed(stream->Seek(li, STREAM_SEEK_END, &uli));
|
||||
|
||||
streamSize = uli.u.LowPart;
|
||||
|
||||
ThrowHrIfFailed(stream->Seek(li, STREAM_SEEK_SET, &uli));
|
||||
|
||||
m_cacheBuffer.resize(static_cast<std::uint32_t>(streamSize));
|
||||
ULONG bytesRead = 0;
|
||||
ThrowHrIfFailed(stream->Read(m_cacheBuffer.data(), m_cacheBuffer.size(), &bytesRead));
|
||||
|
||||
ThrowErrorIfNot(xPlat::Error::AppxSignatureInvalid,
|
||||
bytesRead == streamSize,
|
||||
"Invalid signature");
|
||||
|
||||
std::vector<std::uint8_t> hash;
|
||||
ThrowErrorIfNot(xPlat::Error::AppxSignatureInvalid,
|
||||
xPlat::SHA256::ComputeHash(m_cacheBuffer.data(), m_cacheBuffer.size(), hash),
|
||||
"Invalid signature");
|
||||
|
||||
ThrowErrorIfNot(xPlat::Error::AppxSignatureInvalid,
|
||||
expectedHash.size() == hash.size(),
|
||||
"Signature is corrupt");
|
||||
|
||||
ThrowErrorIfNot(
|
||||
xPlat::Error::AppxSignatureInvalid,
|
||||
memcmp(expectedHash.data(), hash.data(), hash.size()) == 0,
|
||||
"Signature hash doesn't match digest hash"); //TODO: better exception
|
||||
ThrowHrIfFailed(m_stream->Seek(li, StreamBase::Reference::END, &uli));
|
||||
ThrowHrIfFailed(m_stream->Seek(li, StreamBase::Reference::START, nullptr));
|
||||
m_streamSize = static_cast<size_t>(uli.u.LowPart);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *newPosition) override
|
||||
void Validate()
|
||||
{
|
||||
if (m_validated) { return; }
|
||||
|
||||
// read stream into cache buffer
|
||||
m_cacheBuffer = std::make_unique<std::vector<std::uint8_t>>(m_streamSize);
|
||||
ULONG bytesRead = 0;
|
||||
ThrowHrIfFailed(m_stream->Read(m_cacheBuffer->data(), m_cacheBuffer->size(), &bytesRead));
|
||||
ThrowErrorIfNot(xPlat::Error::AppxSignatureInvalid, bytesRead == m_streamSize, "read failed");
|
||||
|
||||
// compute digest and compare against expected digest
|
||||
std::vector<std::uint8_t> hash;
|
||||
ThrowErrorIfNot(xPlat::Error::AppxSignatureInvalid,
|
||||
xPlat::SHA256::ComputeHash(m_cacheBuffer->data(), m_cacheBuffer->size(), hash),
|
||||
"Invalid signature");
|
||||
ThrowErrorIfNot(xPlat::Error::AppxSignatureInvalid, m_expectedHash.size() == hash.size(), "Signature is corrupt");
|
||||
ThrowErrorIfNot(
|
||||
xPlat::Error::AppxSignatureInvalid,
|
||||
memcmp(m_expectedHash.data(), hash.data(), hash.size()) == 0,
|
||||
"Signature hash doesn't match digest hash"); //TODO: better exception
|
||||
|
||||
m_validated = true;
|
||||
}
|
||||
|
||||
void CacheSeek(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *newPosition)
|
||||
{
|
||||
LARGE_INTEGER newPos = { 0 };
|
||||
switch (origin)
|
||||
|
@ -66,33 +77,49 @@ namespace xPlat {
|
|||
m_relativePosition = move.u.LowPart;
|
||||
break;
|
||||
case Reference::END:
|
||||
m_relativePosition = m_cacheBuffer.size();
|
||||
m_relativePosition = m_streamSize;
|
||||
break;
|
||||
}
|
||||
m_relativePosition = std::max((std::uint64_t)0, std::min(m_relativePosition, (std::uint64_t)m_cacheBuffer.size()));
|
||||
m_relativePosition = std::max((std::uint64_t)0, std::min(m_relativePosition, static_cast<std::uint64_t>(m_streamSize)));
|
||||
if (newPosition) { newPosition->QuadPart = (std::uint64_t)m_relativePosition; }
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *newPosition) override
|
||||
{
|
||||
return ResultOf([&]{
|
||||
if (m_cacheBuffer.get() == nullptr)
|
||||
{ ThrowHrIfFailed(m_stream->Seek(move, origin, newPosition));
|
||||
}
|
||||
// always call into cache seek to keep cache state aligned with the underlying stream state.
|
||||
CacheSeek(move, origin, newPosition);
|
||||
});
|
||||
}
|
||||
|
||||
void CacheRead(void* buffer, ULONG countBytes, ULONG* actualRead)
|
||||
{
|
||||
ThrowErrorIf(Error::Stg_E_Invalidpointer, (buffer == nullptr), "bad input");
|
||||
ULONG bytesToRead = std::min((std::uint32_t)countBytes, static_cast<std::uint32_t>((std::uint64_t)m_cacheBuffer->size() - m_relativePosition));
|
||||
if (bytesToRead)
|
||||
{
|
||||
memcpy(buffer, reinterpret_cast<BYTE*>(m_cacheBuffer->data()) + m_relativePosition, bytesToRead);
|
||||
}
|
||||
|
||||
m_relativePosition += bytesToRead;
|
||||
if (m_streamSize == m_relativePosition) { m_cacheBuffer = nullptr; }
|
||||
if (actualRead) { *actualRead = bytesToRead; }
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Read(void* buffer, ULONG countBytes, ULONG* actualRead) override
|
||||
{
|
||||
HRESULT hr = static_cast<HRESULT>(Error::Stg_E_Invalidpointer);
|
||||
if (buffer)
|
||||
{
|
||||
ULONG bytesToRead = std::min((std::uint32_t)countBytes, static_cast<std::uint32_t>((std::uint64_t)m_cacheBuffer.size() - m_relativePosition));
|
||||
if (bytesToRead)
|
||||
{
|
||||
memcpy(buffer, reinterpret_cast<BYTE*>(m_cacheBuffer.data()) + m_relativePosition, bytesToRead);
|
||||
return ResultOf([&]{
|
||||
Validate();
|
||||
if (m_cacheBuffer.get() == nullptr)
|
||||
{ ThrowHrIfFailed(m_stream->Read(buffer, countBytes, actualRead));
|
||||
}
|
||||
m_relativePosition += bytesToRead;
|
||||
if (actualRead) { *actualRead = bytesToRead; }
|
||||
hr = (countBytes == bytesToRead) ? S_OK : S_FALSE;
|
||||
}
|
||||
return hr;
|
||||
else
|
||||
{ CacheRead(buffer, countBytes, actualRead);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<std::uint8_t> m_cacheBuffer;
|
||||
std::uint64_t m_relativePosition;
|
||||
};
|
||||
}
|
|
@ -23,7 +23,7 @@ namespace xPlat {
|
|||
{
|
||||
public:
|
||||
InflateStream(IStream* stream, std::uint64_t uncompressedSize);
|
||||
virtual ~InflateStream();
|
||||
~InflateStream();
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *newPosition) override;
|
||||
HRESULT STDMETHODCALLTYPE Read(void* buffer, ULONG countBytes, ULONG* bytesRead) override;
|
||||
|
|
|
@ -38,10 +38,6 @@ class IStorageObject : public IUnknown
|
|||
#endif
|
||||
{
|
||||
public:
|
||||
#ifdef WIN32
|
||||
virtual ~IStorageObject() {}
|
||||
#endif
|
||||
|
||||
virtual std::string GetPathSeparator() = 0;
|
||||
|
||||
// Obtains a vector of UTF-8 formatted string names contained in the storage object
|
||||
|
|
|
@ -4,22 +4,24 @@
|
|||
|
||||
#include "Exceptions.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
|
||||
namespace xPlat {
|
||||
// internal interface
|
||||
EXTERN_C const IID IID_IVerifierObject;
|
||||
#ifndef WIN32
|
||||
MIDL_INTERFACE("cb0a105c-3a6c-4e48-9351-377c4dccd890")
|
||||
interface IVerifierObject : public IUnknown
|
||||
#else
|
||||
#include "Unknwn.h"
|
||||
#include "Objidl.h"
|
||||
class IVerifierObject : public IUnknown
|
||||
#endif
|
||||
// An internal interface for objects that are used to verify structured data represented by an underlying stream.
|
||||
{
|
||||
public:
|
||||
virtual bool HasStream() = 0;
|
||||
virtual xPlat::ComPtr<IStream> GetStream() = 0;
|
||||
virtual xPlat::ComPtr<IStream> GetValidationStream(const std::string& part, IStream* stream) = 0;
|
||||
};
|
||||
|
||||
// A base class for objects that are used to verify structured data represented by an underlying stream.
|
||||
class VerifierObject
|
||||
{
|
||||
public:
|
||||
VerifierObject(IStream* stream) : m_stream(stream) {}
|
||||
|
||||
inline bool HasStream() { return m_stream.Get() != nullptr; }
|
||||
inline IStream* GetStream() { return m_stream.Get(); }
|
||||
|
||||
virtual IStream* GetValidationStream(const std::string& part, IStream* stream) = 0;
|
||||
|
||||
protected:
|
||||
ComPtr<IStream> m_stream;
|
||||
};
|
||||
|
||||
} // namespace xPlat
|
||||
SpecializeUuidOfImpl(IVerifierObject);
|
|
@ -8,38 +8,114 @@
|
|||
#include "VerifierObject.hpp"
|
||||
|
||||
// Mandatory for using any feature of Xerces.
|
||||
#include "xercesc/util/PlatformUtils.hpp"
|
||||
#include "xercesc/dom/DOM.hpp"
|
||||
#include "xercesc/framework/MemBufInputSource.hpp"
|
||||
#include "xercesc/framework/XMLGrammarPoolImpl.hpp"
|
||||
#include "xercesc/parsers/AbstractDOMParser.hpp"
|
||||
#include "xercesc/parsers/XercesDOMParser.hpp"
|
||||
#include "xercesc/util/PlatformUtils.hpp"
|
||||
#include "xercesc/util/XMLString.hpp"
|
||||
|
||||
// internal interface
|
||||
EXTERN_C const IID IID_IXmlObject;
|
||||
#ifndef WIN32
|
||||
MIDL_INTERFACE("0e7a446e-baf7-44c1-b38a-216bfa18a1a8")
|
||||
interface IXmlObject : public IUnknown
|
||||
#else
|
||||
#include "Unknwn.h"
|
||||
#include "Objidl.h"
|
||||
class IXmlObject : public IUnknown
|
||||
#endif
|
||||
// An internal interface for XML
|
||||
{
|
||||
public:
|
||||
virtual void Write() = 0;
|
||||
virtual XERCES_CPP_NAMESPACE::DOMDocument* Document() = 0;
|
||||
};
|
||||
|
||||
SpecializeUuidOfImpl(IXmlObject);
|
||||
|
||||
namespace xPlat {
|
||||
|
||||
// XML de-serialization happens during construction, of this object.
|
||||
// XML serialization happens through the Write method
|
||||
class XmlObject : public VerifierObject
|
||||
class XmlObject : public ComClass<XmlObject, IXmlObject, IVerifierObject>
|
||||
{
|
||||
public:
|
||||
// TODO: Implement actual XML validation....
|
||||
XmlObject(IStream* stream) : VerifierObject(stream) {}
|
||||
XmlObject(ComPtr<IStream>& stream, std::map<std::string, std::string>* schemas = nullptr) : m_stream(stream)
|
||||
{
|
||||
// Create buffer from stream
|
||||
LARGE_INTEGER start = { 0 };
|
||||
ULARGE_INTEGER end = { 0 };
|
||||
ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::END, &end));
|
||||
ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::START, nullptr));
|
||||
|
||||
// TODO: Implement: Writes the contents of the DOM Document to the underlying stream.
|
||||
void Write()
|
||||
{
|
||||
throw Exception(Error::NotImplemented);
|
||||
std::uint32_t streamSize = end.u.LowPart;
|
||||
std::vector<std::uint8_t> buffer(streamSize);
|
||||
ULONG actualRead = 0;
|
||||
ThrowHrIfFailed(stream->Read(buffer.data(), streamSize, &actualRead));
|
||||
ThrowErrorIf(Error::FileRead, (actualRead != streamSize), "read error");
|
||||
|
||||
// move the underlying stream back to the begginning.
|
||||
ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::START, nullptr));
|
||||
|
||||
std::unique_ptr<XERCES_CPP_NAMESPACE::MemBufInputSource> source = std::make_unique<XERCES_CPP_NAMESPACE::MemBufInputSource>(
|
||||
reinterpret_cast<const XMLByte*>(&buffer[0]), actualRead, "XML File");
|
||||
|
||||
// Create parser and grammar pool
|
||||
auto grammarPool = std::make_unique<XERCES_CPP_NAMESPACE::XMLGrammarPoolImpl>(XERCES_CPP_NAMESPACE::XMLPlatformUtils::fgMemoryManager);
|
||||
m_parser = std::make_unique<XERCES_CPP_NAMESPACE::XercesDOMParser>(nullptr, XERCES_CPP_NAMESPACE::XMLPlatformUtils::fgMemoryManager, grammarPool.get());
|
||||
|
||||
bool HasSchemas = ((schemas != nullptr) && (schemas->begin() != schemas->end()));
|
||||
m_parser->setValidationScheme(HasSchemas ?
|
||||
XERCES_CPP_NAMESPACE::AbstractDOMParser::ValSchemes::Val_Always :
|
||||
XERCES_CPP_NAMESPACE::AbstractDOMParser::ValSchemes::Val_Never
|
||||
);
|
||||
m_parser->cacheGrammarFromParse(HasSchemas);
|
||||
m_parser->setDoSchema(HasSchemas);
|
||||
m_parser->setDoNamespaces(HasSchemas);
|
||||
m_parser->setHandleMultipleImports(HasSchemas); // TODO: do we need to handle the case where there aren't multiple schemas with the same namespace?
|
||||
m_parser->setValidationSchemaFullChecking(HasSchemas);
|
||||
|
||||
if (HasSchemas)
|
||||
{ // Disable DTD and prevent XXE attacks. See https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#libxerces-c for additional details.
|
||||
m_parser->setIgnoreCachedDTD(true);
|
||||
m_parser->setSkipDTDValidation(true);
|
||||
m_parser->setCreateEntityReferenceNodes(false);
|
||||
}
|
||||
|
||||
// Add schemas
|
||||
if (schemas != nullptr)
|
||||
{ for (auto index = schemas->begin(); index != schemas->end(); index++)
|
||||
{ auto item = std::make_unique<XERCES_CPP_NAMESPACE::MemBufInputSource>(
|
||||
reinterpret_cast<const XMLByte*>(index->second.c_str()),
|
||||
index->second.length(),
|
||||
index->first.c_str());
|
||||
m_parser->loadGrammar(*item, XERCES_CPP_NAMESPACE::Grammar::GrammarType::SchemaGrammarType, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the error handler for the parser
|
||||
auto errorHandler = std::make_unique<ParsingException>();
|
||||
m_parser->setErrorHandler(errorHandler.get());
|
||||
m_parser->parse(*source);
|
||||
}
|
||||
|
||||
// Returns a shared pointer to the DOMDocument representing the contents of this stream
|
||||
std::shared_ptr<XERCES_CPP_NAMESPACE::DOMDocument> Document() { return m_DOMDocument;}
|
||||
// IXmlObject
|
||||
void Write() override { throw Exception(Error::NotImplemented); }
|
||||
XERCES_CPP_NAMESPACE::DOMDocument* Document() override { return m_parser->getDocument();}
|
||||
|
||||
IStream* GetValidationStream(const std::string& part, IStream* stream) override
|
||||
// IVerifierObject
|
||||
bool HasStream() override { return m_stream.Get() != nullptr; }
|
||||
xPlat::ComPtr<IStream> GetStream() override { return m_stream; }
|
||||
xPlat::ComPtr<IStream> GetValidationStream(const std::string& part, IStream* stream) override
|
||||
{
|
||||
throw Exception(Error::NotSupported);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<XERCES_CPP_NAMESPACE::XercesDOMParser> m_parser;
|
||||
ComPtr<IStream> m_stream;
|
||||
std::shared_ptr<XERCES_CPP_NAMESPACE::DOMImplementation> m_DOMImplementation;
|
||||
std::shared_ptr<XERCES_CPP_NAMESPACE::DOMDocument> m_DOMDocument;
|
||||
};
|
||||
|
||||
} // namespace xPlat
|
||||
|
||||
} // namespace xPlat
|
|
@ -45,9 +45,9 @@ namespace xPlat {
|
|||
inline bool IsCompressed() { return m_isCompressed; }
|
||||
|
||||
protected:
|
||||
ComPtr<IxPlatFactory> m_factory;
|
||||
std::string m_name;
|
||||
std::string m_contentType;
|
||||
bool m_isCompressed = false;
|
||||
IxPlatFactory* m_factory;
|
||||
std::string m_name;
|
||||
std::string m_contentType;
|
||||
bool m_isCompressed = false;
|
||||
};
|
||||
}
|
|
@ -26,7 +26,7 @@ namespace xPlat {
|
|||
void CommitChanges() override;
|
||||
|
||||
protected:
|
||||
ComPtr<IxPlatFactory> m_factory;
|
||||
IxPlatFactory* m_factory;
|
||||
ComPtr<IStream> m_stream;
|
||||
std::map<std::string, ComPtr<IStream>> m_streams;
|
||||
};//class ZipObject
|
||||
|
|
|
@ -1,123 +1,213 @@
|
|||
#include "AppxBlockMapObject.hpp"
|
||||
#include "AppxBlockMapSchemas.hpp"
|
||||
#include "xercesc/framework/MemBufInputSource.hpp"
|
||||
#include "xercesc/framework/XMLGrammarPoolImpl.hpp"
|
||||
#include "xercesc/parsers/XercesDOMParser.hpp"
|
||||
#include "xercesc/util/Base64.hpp"
|
||||
#include "xercesc/util/XMLString.hpp"
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include "BlockMapStream.hpp"
|
||||
|
||||
/* Example XML:
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<BlockMap HashMethod="http://www.w3.org/2001/04/xmlenc#sha256" xmlns="http://schemas.microsoft.com/appx/2010/blockmap">
|
||||
...
|
||||
<File Name="assets\icon150.png" Size="0" LfhSize="48"/>
|
||||
...
|
||||
<File LfhSize="65" Size="187761" Name="Assets\video_offline_demo_page1.jpg">
|
||||
<Block Hash="NQL/PSheCSB3yZzKyZ6nHbsfzJt1EZJxOXLllMVvtEI="/>
|
||||
<Block Hash="2Udxo8Nwie7rvy4g0T5yfz9qccDNMVWh2mfMD1YCQao="/>
|
||||
<Block Hash="MmXnlptT/u+ilMKCIriWR49k99rBqwXKO3s60zGwZKg="/>
|
||||
</File>
|
||||
...
|
||||
<File LfhSize="57" Size="47352" Name="Resources\Fonts\SegMVR2.ttf">
|
||||
<Block Size="27777" Hash="LGaGnk3EtFymriM9cRmeX7eZI+b2hpwOIlJIXdeE1ik="/>
|
||||
</File>
|
||||
...
|
||||
</BlockMap>
|
||||
*/
|
||||
|
||||
XERCES_CPP_NAMESPACE_USE
|
||||
|
||||
namespace xPlat {
|
||||
|
||||
// IAppxBlockMapBlock
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapBlock::GetHash(UINT32* bufferSize, BYTE** buffer)
|
||||
static std::uint32_t GetLocalFileHeaderSize(XERCES_CPP_NAMESPACE::DOMElement* element)
|
||||
{
|
||||
return xPlat::ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
XercesXMLChPtr nameAttr(XERCES_CPP_NAMESPACE::XMLString::transcode("LfhSize"));
|
||||
XercesCharPtr name(XERCES_CPP_NAMESPACE::XMLString::transcode(element->getAttribute(nameAttr.Get())));
|
||||
std::string attributeValue(name.Get());
|
||||
bool hasValue = !attributeValue.empty();
|
||||
std::uint32_t value = 0;
|
||||
if (hasValue) { value = static_cast<std::uint32_t>(std::stoul(attributeValue)); }
|
||||
return value;
|
||||
}
|
||||
|
||||
static std::string GetName(XERCES_CPP_NAMESPACE::DOMElement* element)
|
||||
{
|
||||
XercesXMLChPtr nameAttr(XERCES_CPP_NAMESPACE::XMLString::transcode("Name"));
|
||||
XercesCharPtr name(XERCES_CPP_NAMESPACE::XMLString::transcode(element->getAttribute(nameAttr.Get())));
|
||||
return std::string (name.Get());
|
||||
}
|
||||
|
||||
static std::uint64_t GetSize(XERCES_CPP_NAMESPACE::DOMElement* element)
|
||||
{
|
||||
XercesXMLChPtr nameAttr(XERCES_CPP_NAMESPACE::XMLString::transcode("Size"));
|
||||
XercesCharPtr name(XERCES_CPP_NAMESPACE::XMLString::transcode(element->getAttribute(nameAttr.Get())));
|
||||
std::string attributeValue(name.Get());
|
||||
std::uint64_t value = (BLOCKMAP_BLOCK_SIZE); // size of block not always specified, in which case, it's 64k
|
||||
if (!attributeValue.empty())
|
||||
{ value = static_cast<std::uint64_t>(std::stoull(attributeValue));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static std::vector<std::uint8_t> GetDigestData(XERCES_CPP_NAMESPACE::DOMElement* element)
|
||||
{
|
||||
XercesXMLChPtr nameAttr(XMLString::transcode("Hash"));
|
||||
XMLSize_t len = 0;
|
||||
XercesXMLBytePtr decodedData(XERCES_CPP_NAMESPACE::Base64::decodeToXMLByte(
|
||||
element->getAttribute(nameAttr.Get()),
|
||||
&len));
|
||||
std::vector<std::uint8_t> result(len);
|
||||
for(XMLSize_t index=0; index < len; index++)
|
||||
{ result[index] = static_cast<std::uint8_t>(decodedData.Get()[index]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static Block GetBlock(XERCES_CPP_NAMESPACE::DOMElement* element)
|
||||
{
|
||||
Block result {0};
|
||||
result.compressedSize = GetSize(element);
|
||||
result.hash = GetDigestData(element);
|
||||
return result;
|
||||
}
|
||||
|
||||
AppxBlockMapObject::AppxBlockMapObject(IxPlatFactory* factory, ComPtr<IStream>& stream) : m_factory(factory), m_stream(stream)
|
||||
{
|
||||
auto dom = ComPtr<IXmlObject>::Make<XmlObject>(stream, &blockMapSchema);
|
||||
// Create xPath query over blockmap file.
|
||||
XercesXMLChPtr fileXPath(XMLString::transcode("/BlockMap/File"));
|
||||
XercesPtr<DOMXPathNSResolver> resolver(dom->Document()->createNSResolver(dom->Document()->getDocumentElement()));
|
||||
XercesPtr<DOMXPathResult> fileResult(dom->Document()->evaluate(
|
||||
fileXPath.Get(),
|
||||
dom->Document()->getDocumentElement(),
|
||||
resolver.Get(),
|
||||
DOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE,
|
||||
nullptr));
|
||||
|
||||
// Create IAppxBlockMapFiles
|
||||
for (XMLSize_t i = 0; i < fileResult->getSnapshotLength(); i++)
|
||||
{
|
||||
fileResult->snapshotItem(i);
|
||||
auto fileNode = static_cast<DOMElement*>(fileResult->getNodeValue());
|
||||
|
||||
// Get blocks elements
|
||||
XercesXMLChPtr blockXPath(XMLString::transcode("./Block"));
|
||||
XercesPtr<DOMXPathResult> blockResult = dom->Document()->evaluate(
|
||||
blockXPath.Get(),
|
||||
fileNode,
|
||||
resolver.Get(),
|
||||
DOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE,
|
||||
nullptr);
|
||||
|
||||
// get all the blocks for the file.
|
||||
std::vector<Block> blocks(blockResult->getSnapshotLength());
|
||||
for (XMLSize_t j = 0; j < blockResult->getSnapshotLength(); j++)
|
||||
{
|
||||
blockResult->snapshotItem(j);
|
||||
auto blockNode = static_cast<DOMElement*>(blockResult->getNodeValue());
|
||||
blocks[j] = GetBlock(blockNode);
|
||||
}
|
||||
|
||||
auto name = GetName(fileNode);
|
||||
auto existing = m_blockMap.find(name);
|
||||
ThrowErrorIf(Error::BlockMapSemanticError, (existing != m_blockMap.end()), "duplicate file name specified.");
|
||||
m_blockMap.insert(std::make_pair(name, std::move(blocks)));
|
||||
|
||||
m_blockMapfiles.insert(std::make_pair(name,
|
||||
ComPtr<IAppxBlockMapFile>::Make<AppxBlockMapFile>(
|
||||
factory,
|
||||
&(m_blockMap[name]),
|
||||
GetLocalFileHeaderSize(fileNode),
|
||||
name,
|
||||
GetSize(fileNode))));
|
||||
}
|
||||
}
|
||||
|
||||
xPlat::ComPtr<IStream> AppxBlockMapObject::GetValidationStream(const std::string& part, IStream* stream)
|
||||
{
|
||||
ThrowErrorIf(Error::InvalidParameter, (part.empty() || stream == nullptr), "bad input");
|
||||
auto item = m_blockMap.find(part);
|
||||
ThrowErrorIf(Error::BlockMapSemanticError, item == m_blockMap.end(), "file not tracked by blockmap");
|
||||
return ComPtr<IStream>::Make<BlockMapStream>(stream, item->second);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapObject::GetFile(LPCWSTR filename, IAppxBlockMapFile **file)
|
||||
{
|
||||
return ResultOf([&]{
|
||||
ThrowErrorIf(Error::InvalidParameter, (
|
||||
filename == nullptr || *filename == '\0' || file == nullptr || *file != nullptr
|
||||
), "bad pointer");
|
||||
ComPtr<IStream> stream = GetFile(utf16_to_utf8(filename));
|
||||
*file = stream.As<IAppxBlockMapFile>().Detach();
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapObject::GetFiles(IAppxBlockMapFilesEnumerator **enumerator)
|
||||
{
|
||||
return ResultOf([&]{
|
||||
ThrowErrorIf(Error::InvalidParameter, (enumerator == nullptr || *enumerator != nullptr), "bad pointer");
|
||||
ComPtr<IAppxBlockMapReader> self;
|
||||
ThrowHrIfFailed(QueryInterface(UuidOfImpl<IAppxBlockMapReader>::iid, reinterpret_cast<void**>(&self)));
|
||||
*enumerator = ComPtr<IAppxBlockMapFilesEnumerator>::Make<AppxBlockMapFilesEnumerator>(
|
||||
self.Get(),
|
||||
std::move(GetFileNames(FileNameOptions::All))).Detach();
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapObject::GetHashMethod(IUri **hashMethod)
|
||||
{ // Ultimately, this IUri object represents the HashMethod attribute in the blockmap:
|
||||
return ResultOf([&]{
|
||||
// TODO: Implement...
|
||||
throw Exception(Error::NotImplemented);
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapBlock::GetCompressedSize(UINT32* size)
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapObject::GetStream(IStream **blockMapStream)
|
||||
{
|
||||
return xPlat::ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
return ResultOf([&]{
|
||||
ThrowErrorIf(Error::InvalidParameter, (blockMapStream == nullptr || *blockMapStream != nullptr), "bad pointer");
|
||||
auto stream = GetStream();
|
||||
LARGE_INTEGER li{0};
|
||||
ThrowHrIfFailed(stream->Seek(li, StreamBase::Reference::START, nullptr));
|
||||
*blockMapStream = stream.Detach();
|
||||
});
|
||||
}
|
||||
|
||||
// IAppxBlockMapFile
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapFile::GetBlocks(IAppxBlockMapBlocksEnumerator** blocks)
|
||||
// IStorageObject methods
|
||||
std::string AppxBlockMapObject::GetPathSeparator() { return "\\"; }
|
||||
std::vector<std::string> AppxBlockMapObject::GetFileNames(FileNameOptions)
|
||||
{
|
||||
return xPlat::ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
});
|
||||
std::vector<std::string> fileNames;
|
||||
std::transform(
|
||||
m_blockMapfiles.begin(),
|
||||
m_blockMapfiles.end(),
|
||||
std::back_inserter(fileNames),
|
||||
[](auto keyValuePair){ return keyValuePair.first; }
|
||||
);
|
||||
return fileNames;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapFile::GetLocalFileHeaderSize(UINT32* lfhSize)
|
||||
IStream* AppxBlockMapObject::GetFile(const std::string& fileName)
|
||||
{
|
||||
return xPlat::ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
});
|
||||
auto index = m_blockMapfiles.find(fileName);
|
||||
ThrowErrorIf(Error::FileNotFound, (index == m_blockMapfiles.end()), "named file not in blockmap");
|
||||
return index->second.As<IStream>().Detach();
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapFile::GetName(LPWSTR* name)
|
||||
{
|
||||
return xPlat::ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapFile::GetUncompressedSize(UINT64* size)
|
||||
{
|
||||
return xPlat::ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapFile::ValidateFileHash(IStream* fileStream, BOOL* isValid)
|
||||
{
|
||||
return xPlat::ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
});
|
||||
}
|
||||
|
||||
// IAppxBlockMapFilesEnumerator
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapFile::GetCurrent(IAppxBlockMapFile** block)
|
||||
{
|
||||
return xPlat::ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapFile::GetHasCurrent(BOOL* hasCurrent)
|
||||
{
|
||||
return xPlat::ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapFile::MoveNext(BOOL* hasNext)
|
||||
{
|
||||
return xPlat::ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
});
|
||||
}
|
||||
|
||||
AppxBlockMapObject::AppxBlockMapObject(IStream* stream) : VerifierObject(stream)
|
||||
{
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
IStream* AppxBlockMapObject::GetValidationStream(const std::string& part, IStream* stream)
|
||||
{
|
||||
// TODO: Implement -- for now, just pass through.
|
||||
return stream;
|
||||
}
|
||||
|
||||
// IAppxBlockMapBlocksEnumerator
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapObject::GetCurrent(IAppxBlockMapBlock** block)
|
||||
{
|
||||
return xPlat::ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapObject::GetHasCurrent(BOOL* hasCurrent)
|
||||
{
|
||||
return xPlat::ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapObject::MoveNext(BOOL* hasNext)
|
||||
{
|
||||
return xPlat::ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
});
|
||||
}
|
||||
void AppxBlockMapObject::RemoveFile(const std::string& ) { throw Exception(Error::NotImplemented); }
|
||||
IStream* AppxBlockMapObject::OpenFile(const std::string& ,xPlat::FileStream::Mode) { throw Exception(Error::NotImplemented); }
|
||||
void AppxBlockMapObject::CommitChanges() { throw Exception(Error::NotImplemented); }
|
||||
}
|
|
@ -23,7 +23,7 @@ namespace xPlat {
|
|||
ComPtr<IxPlatFactory> self;
|
||||
ThrowHrIfFailed(QueryInterface(UuidOfImpl<IxPlatFactory>::iid, reinterpret_cast<void**>(&self)));
|
||||
auto zip = ComPtr<IStorageObject>::Make<ZipObject>(self.Get(), inputStream);
|
||||
auto result = ComPtr<IAppxPackageReader>::Make<AppxPackageObject>(m_validationOptions, zip.Get());
|
||||
auto result = ComPtr<IAppxPackageReader>::Make<AppxPackageObject>(self.Get(), m_validationOptions, zip.Get());
|
||||
*packageReader = result.Detach();
|
||||
});
|
||||
}
|
||||
|
@ -43,26 +43,46 @@ namespace xPlat {
|
|||
IAppxBlockMapReader** blockMapReader)
|
||||
{
|
||||
return ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
ThrowErrorIf(Error::InvalidParameter, (
|
||||
inputStream == nullptr ||
|
||||
blockMapReader == nullptr ||
|
||||
*blockMapReader != nullptr
|
||||
),"bad pointer.");
|
||||
|
||||
ComPtr<IxPlatFactory> self;
|
||||
ThrowHrIfFailed(QueryInterface(UuidOfImpl<IxPlatFactory>::iid, reinterpret_cast<void**>(&self)));
|
||||
ComPtr<IStream> stream(inputStream);
|
||||
*blockMapReader = ComPtr<IAppxBlockMapReader>::Make<AppxBlockMapObject>(self.Get(), stream).Detach();
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxFactory::CreateValidatedBlockMapReader (
|
||||
IStream* blockMapStream,
|
||||
IStream* inputStream,
|
||||
LPCWSTR signatureFileName,
|
||||
IAppxBlockMapReader** blockMapReader)
|
||||
{
|
||||
return ResultOf([&]() {
|
||||
// TODO: Implement
|
||||
throw Exception(Error::NotImplemented);
|
||||
ThrowErrorIf(Error::InvalidParameter, (
|
||||
inputStream == nullptr ||
|
||||
signatureFileName == nullptr ||
|
||||
*signatureFileName == '\0' ||
|
||||
blockMapReader == nullptr ||
|
||||
*blockMapReader != nullptr
|
||||
),"bad pointer.");
|
||||
|
||||
ComPtr<IxPlatFactory> self;
|
||||
ThrowHrIfFailed(QueryInterface(UuidOfImpl<IxPlatFactory>::iid, reinterpret_cast<void**>(&self)));
|
||||
auto stream = ComPtr<IStream>::Make<FileStream>(utf16_to_utf8(signatureFileName), FileStream::Mode::READ);
|
||||
auto signature = ComPtr<IVerifierObject>::Make<AppxSignatureObject>(self->GetValidationOptions(), stream.Get());
|
||||
auto validatedStream = signature->GetValidationStream("AppxBlockMap.xml", inputStream);
|
||||
*blockMapReader = ComPtr<IAppxBlockMapReader>::Make<AppxBlockMapObject>(self.Get(), validatedStream).Detach();
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT AppxFactory::MarshalOutString(std::string& internal, LPWSTR *result)
|
||||
{
|
||||
return ResultOf([&]() {
|
||||
ThrowErrorIf(Error::InvalidParameter, (result == nullptr || *result != nullptr), "bad pointer" );
|
||||
ThrowErrorIf(Error::InvalidParameter, (result == nullptr || *result != nullptr), "bad pointer" );
|
||||
auto intermediate = utf8_to_utf16(internal);
|
||||
std::size_t countBytes = sizeof(wchar_t)*(internal.size()+1);
|
||||
*result = reinterpret_cast<LPWSTR>(m_memalloc(countBytes));
|
||||
|
@ -74,4 +94,17 @@ namespace xPlat {
|
|||
});
|
||||
}
|
||||
|
||||
HRESULT AppxFactory::MarshalOutBytes(std::vector<std::uint8_t>& data, UINT32* size, BYTE** buffer)
|
||||
{
|
||||
return ResultOf([&]{
|
||||
ThrowErrorIf(Error::InvalidParameter, (size==nullptr || buffer == nullptr || *buffer != nullptr), "Bad pointer");
|
||||
*size = static_cast<UINT32>(data.size());
|
||||
*buffer = reinterpret_cast<BYTE*>(m_memalloc(data.size()));
|
||||
ThrowErrorIfNot(Error::OutOfMemory, (*buffer), "Allocation failed");
|
||||
std::memcpy(reinterpret_cast<void*>(*buffer),
|
||||
reinterpret_cast<void*>(data.data()),
|
||||
data.size());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace xPlat
|
||||
|
|
|
@ -30,6 +30,83 @@ namespace xPlat {
|
|||
{APPX_FOOTPRINT_FILE_TYPE_CODEINTEGRITY, CODEINTEGRITY_CAT},
|
||||
};
|
||||
|
||||
static const std::uint8_t PercentangeEncodingTableSize = 0x5E;
|
||||
static const std::vector<std::string> PercentangeEncoding = {
|
||||
"", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "",
|
||||
"%20", "%21", "", "%23", "%24", "%25", "%26", "%27", // [space] ! # $ % & '
|
||||
"%28", "%29", "", "%2B", "%2C", "", "", "", // ( ) + ,
|
||||
"", "", "", "", "", "", "", "",
|
||||
"", "", "", "%3B", "", "%3D", "", "", // ; =
|
||||
"%40", "", "", "", "", "", "", "", // @
|
||||
"", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "",
|
||||
"", "", "", "%5B", "", "%5D" // [ ]
|
||||
};
|
||||
|
||||
static const std::map<std::string, char> EncodingToChar =
|
||||
{
|
||||
{"20", ' '}, {"21", '!'}, {"23", '#'}, {"24", '$'},
|
||||
{"25", '%'}, {"26", '&'}, {"27", '\''}, {"28", '('},
|
||||
{"29", ')'}, {"25", '+'}, {"2B", '%'}, {"2C", ','},
|
||||
{"3B", ';'}, {"3D", '='}, {"40", '@'}, {"5B", '['},
|
||||
{"5D", ']'}
|
||||
};
|
||||
|
||||
static std::string EncodeFileName(std::string fileName)
|
||||
{
|
||||
std::string result;
|
||||
for (std::uint32_t position = 0; position < fileName.length(); ++position)
|
||||
{
|
||||
std::uint8_t index = static_cast<std::uint8_t>(fileName[position]);
|
||||
if(fileName[position] < PercentangeEncodingTableSize && index < PercentangeEncoding.size() && !PercentangeEncoding[index].empty())
|
||||
{
|
||||
result += PercentangeEncoding[index];
|
||||
}
|
||||
else if (fileName[position] == '\\') // Remove Windows file name
|
||||
{
|
||||
result += '/';
|
||||
}
|
||||
else
|
||||
{
|
||||
result += fileName[position];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string DecodeFileName(std::string fileName)
|
||||
{
|
||||
std::string result;
|
||||
for (std::uint32_t i = 0; i < fileName.length(); ++i)
|
||||
{
|
||||
if(fileName[i] == '%')
|
||||
{
|
||||
auto found = EncodingToChar.find(fileName.substr(i+1, 2));
|
||||
if (found != EncodingToChar.end())
|
||||
{
|
||||
result += found->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Exception(Error::AppxUnknownFileNameEncoding, fileName);
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
else if (fileName[i] == '/') // Windows file name
|
||||
{
|
||||
result += '\\';
|
||||
}
|
||||
else
|
||||
{
|
||||
result += fileName[i];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
AppxPackageId::AppxPackageId(
|
||||
const std::string& name,
|
||||
const std::string& version,
|
||||
|
@ -41,18 +118,21 @@ namespace xPlat {
|
|||
// TODO: Implement validation?
|
||||
}
|
||||
|
||||
AppxManifestObject::AppxManifestObject(IStream* stream) : VerifierObject(stream)
|
||||
AppxManifestObject::AppxManifestObject(ComPtr<IStream>& stream) : m_stream(stream)
|
||||
{
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
AppxPackageObject::AppxPackageObject(APPX_VALIDATION_OPTION validation, IStorageObject* container) :
|
||||
AppxPackageObject::AppxPackageObject(IxPlatFactory* factory, APPX_VALIDATION_OPTION validation, IStorageObject* container) :
|
||||
m_factory(factory),
|
||||
m_validation(validation),
|
||||
m_container(container)
|
||||
{
|
||||
// 1. Get the appx signature from the container and parse it
|
||||
// TODO: pass validation flags and other necessary goodness through.
|
||||
m_appxSignature = std::make_unique<AppxSignatureObject>(validation, m_container->GetFile(APPXSIGNATURE_P7X));
|
||||
m_appxSignature = ComPtr<IVerifierObject>::Make<AppxSignatureObject>(validation,
|
||||
((validation & APPX_VALIDATION_OPTION_SKIPSIGNATURE) == 0) ? m_container->GetFile(APPXSIGNATURE_P7X) : nullptr
|
||||
);
|
||||
|
||||
if ((validation & APPX_VALIDATION_OPTION_SKIPSIGNATURE) == 0)
|
||||
{ ThrowErrorIfNot(Error::AppxMissingSignatureP7X, (m_appxSignature->HasStream()), "AppxSignature.p7x not in archive!");
|
||||
|
@ -60,24 +140,24 @@ namespace xPlat {
|
|||
|
||||
// 2. Get content type using signature object for validation
|
||||
// TODO: switch underlying type of m_contentType to something more specific.
|
||||
m_contentType = std::make_unique<XmlObject>(m_appxSignature->GetValidationStream(
|
||||
CONTENT_TYPES_XML, m_container->GetFile(CONTENT_TYPES_XML)));
|
||||
auto temp = m_appxSignature->GetValidationStream(CONTENT_TYPES_XML, m_container->GetFile(CONTENT_TYPES_XML));
|
||||
m_contentType = ComPtr<IVerifierObject>::Make<XmlObject>(temp);
|
||||
ThrowErrorIfNot(Error::AppxMissingContentTypesXML, (m_contentType->HasStream()), "[Content_Types].xml not in archive!");
|
||||
|
||||
// 3. Get blockmap object using signature object for validation
|
||||
m_appxBlockMap = std::make_unique<AppxBlockMapObject>(m_appxSignature->GetValidationStream(
|
||||
APPXBLOCKMAP_XML, m_container->GetFile(APPXBLOCKMAP_XML)));
|
||||
temp = m_appxSignature->GetValidationStream(APPXBLOCKMAP_XML, m_container->GetFile(APPXBLOCKMAP_XML));
|
||||
m_appxBlockMap = ComPtr<IVerifierObject>::Make<AppxBlockMapObject>(factory, temp);
|
||||
ThrowErrorIfNot(Error::AppxMissingBlockMapXML, (m_appxBlockMap->HasStream()), "AppxBlockMap.xml not in archive!");
|
||||
|
||||
// 4. Get manifest object using blockmap object for validation
|
||||
// TODO: pass validation flags and other necessary goodness through.
|
||||
m_appxManifest = std::make_unique<AppxManifestObject>(m_appxBlockMap->GetValidationStream(
|
||||
APPXMANIFEST_XML, m_container->GetFile(APPXMANIFEST_XML)));
|
||||
temp = m_appxBlockMap->GetValidationStream(APPXMANIFEST_XML, m_container->GetFile(APPXMANIFEST_XML));
|
||||
m_appxManifest = ComPtr<IVerifierObject>::Make<AppxManifestObject>(temp);
|
||||
ThrowErrorIfNot(Error::AppxMissingAppxManifestXML, (m_appxBlockMap->HasStream()), "AppxManifest.xml not in archive!");
|
||||
|
||||
struct Config
|
||||
{
|
||||
using lambda = std::function<IStream*()>;
|
||||
using lambda = std::function<xPlat::ComPtr<IStream>()>;
|
||||
Config(lambda f) : GetValidationStream(f) {}
|
||||
lambda GetValidationStream;
|
||||
};
|
||||
|
@ -85,35 +165,36 @@ namespace xPlat {
|
|||
std::map<std::string, Config> footPrintFileNames = {
|
||||
{ APPXBLOCKMAP_XML, Config([&](){ m_footprintFiles.push_back(APPXBLOCKMAP_XML); return m_appxBlockMap->GetStream();}) },
|
||||
{ APPXMANIFEST_XML, Config([&](){ m_footprintFiles.push_back(APPXMANIFEST_XML); return m_appxManifest->GetStream();}) },
|
||||
{ APPXSIGNATURE_P7X, Config([&](){ if (m_appxSignature->GetStream()){m_footprintFiles.push_back(APPXSIGNATURE_P7X);} return m_appxSignature->GetStream();}) },
|
||||
{ APPXSIGNATURE_P7X, Config([&](){ if (m_appxSignature->GetStream().Get()){m_footprintFiles.push_back(APPXSIGNATURE_P7X);} return m_appxSignature->GetStream();}) },
|
||||
{ CODEINTEGRITY_CAT, Config([&](){ m_footprintFiles.push_back(CODEINTEGRITY_CAT); return m_appxSignature->GetValidationStream(CODEINTEGRITY_CAT, std::move(m_container->GetFile(CODEINTEGRITY_CAT)));}) },
|
||||
{ CONTENT_TYPES_XML, Config([&]()->IStream*{ return nullptr;}) }, // content types is never implicitly unpacked
|
||||
};
|
||||
|
||||
// 5. Ensure that the stream collection contains streams wired up for their appropriate validation
|
||||
// and partition the container's file names into footprint and payload files.
|
||||
for (const auto& fileName : m_container->GetFileNames(FileNameOptions::All))
|
||||
{
|
||||
ComPtr<IStream> stream;
|
||||
|
||||
auto footPrintFile = footPrintFileNames.find(fileName);
|
||||
// and partition the container's file names into footprint and payload files. First by going through
|
||||
// the footprint files, and then by going through the payload files.
|
||||
auto filesToProcess = m_container->GetFileNames(FileNameOptions::All);
|
||||
for (const auto& fileName : m_container->GetFileNames(FileNameOptions::FootPrintOnly))
|
||||
{ auto footPrintFile = footPrintFileNames.find(fileName);
|
||||
if (footPrintFile != footPrintFileNames.end())
|
||||
{
|
||||
stream = footPrintFile->second.GetValidationStream();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_payloadFiles.push_back(fileName);
|
||||
stream = m_appxBlockMap->GetValidationStream(fileName, m_container->GetFile(fileName));
|
||||
}
|
||||
|
||||
if (stream.Get() != nullptr)
|
||||
{
|
||||
LARGE_INTEGER pos = {0};
|
||||
ThrowHrIfFailed(stream->Seek(pos, StreamBase::Reference::START, nullptr));
|
||||
m_streams[fileName] = stream.Get();
|
||||
{ m_streams[fileName] = footPrintFile->second.GetValidationStream();
|
||||
filesToProcess.erase(std::remove(filesToProcess.begin(), filesToProcess.end(), fileName), filesToProcess.end());
|
||||
}
|
||||
}
|
||||
|
||||
auto blockMapStorage = m_appxBlockMap.As<IStorageObject>();
|
||||
for (const auto& fileName : blockMapStorage->GetFileNames(FileNameOptions::PayloadOnly))
|
||||
{ auto footPrintFile = footPrintFileNames.find(fileName);
|
||||
if (footPrintFile == footPrintFileNames.end())
|
||||
{ std::string containerFileName = EncodeFileName(fileName);
|
||||
m_payloadFiles.push_back(containerFileName);
|
||||
m_streams[containerFileName] = m_appxBlockMap->GetValidationStream(fileName, m_container->GetFile(containerFileName));
|
||||
filesToProcess.erase(std::remove(filesToProcess.begin(), filesToProcess.end(), containerFileName), filesToProcess.end());
|
||||
}
|
||||
}
|
||||
// If the map is not empty, there's a file in the container that didn't go to the footprint or payload
|
||||
// files. (eg. payload file missing in the AppxBlockMap.xml)
|
||||
ThrowErrorIfNot(Error::BlockMapSemanticError, (filesToProcess.empty()), "Package not valid!");
|
||||
}
|
||||
|
||||
void AppxPackageObject::Pack(APPX_PACKUNPACK_OPTION options, const std::string& certFile, IStorageObject* from)
|
||||
|
@ -130,7 +211,8 @@ namespace xPlat {
|
|||
std::string targetName;
|
||||
if (options & APPX_PACKUNPACK_OPTION_CREATEPACKAGESUBFOLDER)
|
||||
{
|
||||
targetName = GetAppxManifest()->GetPackageFullName() + to->GetPathSeparator() + fileName;
|
||||
throw Exception(Error::NotImplemented);
|
||||
//targetName = GetAppxManifest()->GetPackageFullName() + to->GetPathSeparator() + fileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -93,9 +93,11 @@ MIDL_DEFINE_GUID(IID, IID_IAppxEncryptedBundleWriter2,0xE644BE82,0xF0FA,0x42B8,0
|
|||
MIDL_DEFINE_GUID(IID, IID_IAppxPackageEditor,0xE2ADB6DC,0x5E71,0x4416,0x86,0xB6,0x86,0xE5,0xF5,0x29,0x1A,0x6B);
|
||||
|
||||
// internal interfaces.
|
||||
MIDL_DEFINE_GUID(IID, IID_IAppxPackage,0x51B2C456,0xAAA9,0x46D6,0x8E,0xC9,0x29,0x82,0x20,0x55,0x91,0x89);
|
||||
MIDL_DEFINE_GUID(IID, IID_IStorageObject,0xEC25B96E,0x0DB1,0x4483,0xBD,0xB1,0xCA,0xB1,0x10,0x9C,0xB7,0x41);
|
||||
MIDL_DEFINE_GUID(IID, IID_IxPlatFactory, 0x1f850db4,0x32b8,0x4db6,0x8b,0xf4,0x5a,0x89,0x7e,0xb6,0x11,0xf1);
|
||||
MIDL_DEFINE_GUID(IID, IID_IAppxPackage, 0x51B2C456,0xAAA9,0x46D6,0x8E,0xC9,0x29,0x82,0x20,0x55,0x91,0x89);
|
||||
MIDL_DEFINE_GUID(IID, IID_IStorageObject, 0xEC25B96E,0x0DB1,0x4483,0xBD,0xB1,0xCA,0xB1,0x10,0x9C,0xB7,0x41);
|
||||
MIDL_DEFINE_GUID(IID, IID_IxPlatFactory, 0x1f850db4,0x32b8,0x4db6,0x8b,0xf4,0x5a,0x89,0x7e,0xb6,0x11,0xf1);
|
||||
MIDL_DEFINE_GUID(IID, IID_IVerifierObject, 0xcb0a105c,0x3a6c,0x4e48,0x93,0x51,0x37,0x7c,0x4d,0xcc,0xd8,0x90);
|
||||
MIDL_DEFINE_GUID(IID, IID_IXmlObject, 0x0e7a446e,0xbaf7,0x44c1,0xb3,0x8a,0x21,0x6b,0xfa,0x18,0xa1,0xa8);
|
||||
#undef MIDL_DEFINE_GUID
|
||||
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
namespace xPlat {
|
||||
|
||||
AppxSignatureObject::AppxSignatureObject(APPX_VALIDATION_OPTION validationOptions, IStream* stream) :
|
||||
VerifierObject(stream),
|
||||
m_stream(stream),
|
||||
m_validationOptions(validationOptions)
|
||||
{
|
||||
m_hasDigests = SignatureValidator::Validate(validationOptions, stream, m_digests, m_signatureOrigin);
|
||||
|
@ -30,28 +30,22 @@ AppxSignatureObject::AppxSignatureObject(APPX_VALIDATION_OPTION validationOption
|
|||
}
|
||||
}
|
||||
|
||||
IStream* AppxSignatureObject::GetValidationStream(const std::string& part, IStream* stream)
|
||||
xPlat::ComPtr<IStream> AppxSignatureObject::GetValidationStream(const std::string& part, IStream* stream)
|
||||
{
|
||||
if (m_hasDigests)
|
||||
{
|
||||
if (part == std::string("AppxBlockMap.xml"))
|
||||
{
|
||||
// This stream implementation will throw if the underlying stream does not match the digest
|
||||
auto result = ComPtr<IStream>::Make<HashStream>(stream, this->GetAppxBlockMapDigest());
|
||||
return result.Detach();
|
||||
{ // This stream implementation will throw if the underlying stream does not match the digest
|
||||
return ComPtr<IStream>::Make<HashStream>(stream, this->GetAppxBlockMapDigest());
|
||||
}
|
||||
else if (part == std::string("[Content_Types].xml"))
|
||||
{
|
||||
// This stream implementation will throw if the underlying stream does not match the digest'
|
||||
auto result = ComPtr<IStream>::Make<HashStream>(stream, this->GetContentTypesDigest());
|
||||
return result.Detach();
|
||||
{ // This stream implementation will throw if the underlying stream does not match the digest'
|
||||
return ComPtr<IStream>::Make<HashStream>(stream, this->GetContentTypesDigest());
|
||||
}
|
||||
else if (part == std::string("CodeIntegrity.cat"))
|
||||
{
|
||||
// This stream implementation will throw if the underlying stream does not match the digest
|
||||
auto result = ComPtr<IStream>::Make<HashStream>(stream, this->GetCodeIntegrityDigest());
|
||||
return result.Detach();
|
||||
}
|
||||
{ // This stream implementation will throw if the underlying stream does not match the digest
|
||||
return ComPtr<IStream>::Make<HashStream>(stream, this->GetCodeIntegrityDigest());
|
||||
}
|
||||
// TODO: unnamed stream for central directory?
|
||||
}
|
||||
return stream;
|
||||
|
|
|
@ -37,6 +37,21 @@ MESSAGE (STATUS "PAL: DirectoryObject = ${DirectoryObject}")
|
|||
MESSAGE (STATUS "PAL: SHA256 = ${SHA256}")
|
||||
MESSAGE (STATUS "PAL: Signature = ${Signature}")
|
||||
|
||||
# Create header for BlockMap schemas
|
||||
file(READ "${CMAKE_PROJECT_ROOT}/AppxPackaging/BlockMap/schema/BlockMapSchema.xsd" BLOCKMAP_SCHEMA)
|
||||
file(READ "${CMAKE_PROJECT_ROOT}/AppxPackaging/BlockMap/schema/BlockMapSchema2015.xsd" BLOCKMAP_SCHEMA_2015)
|
||||
file(READ "${CMAKE_PROJECT_ROOT}/AppxPackaging/BlockMap/schema/BlockMapSchema2017.xsd" BLOCKMAP_SCHEMA_2017)
|
||||
set(BLOCKMAP_HEADER "// This file is generated by CMake and contains XSDs for parsing the AppxBlockMap.xml. Do not edit!!
|
||||
#include <string>
|
||||
#include <map>
|
||||
std::map<std::string, std::string> blockMapSchema = {
|
||||
{\"blockMapSchemaRaw\", R\"(${BLOCKMAP_SCHEMA})\" },
|
||||
{\"blockMapSchema2015Raw\", R\"(${BLOCKMAP_SCHEMA_2015})\"},
|
||||
{\"blockMapSchema2017Raw\", R\"(${BLOCKMAP_SCHEMA_2017})\"}
|
||||
};
|
||||
")
|
||||
file(WRITE "../inc/AppxBlockMapSchemas.hpp" "${BLOCKMAP_HEADER}")
|
||||
|
||||
set(LIB_PUBLIC_HEADERS
|
||||
../inc/AppxPackaging.hpp
|
||||
../inc/AppxWindows.hpp
|
||||
|
@ -90,7 +105,7 @@ add_library(${LIBRARY_NAME} SHARED ${LIB_SOURCES} ${LIB_PUBLIC_HEADERS} ${LIB_PR
|
|||
set_property(TARGET ${LIBRARY_NAME} PROPERTY CXX_STANDARD 14)
|
||||
|
||||
# Set the build version. It will be used in the name of the lib, with corresponding
|
||||
# symlinks created. SOVERSION could also be specified for api version.
|
||||
# symlinks created. SOVERSION could also be specified for api version.
|
||||
set_target_properties(${LIBRARY_NAME} PROPERTIES
|
||||
VERSION ${VERSION} # ${VERSION} was defined in the main CMakeLists.
|
||||
FRAMEWORK FALSE
|
||||
|
|
|
@ -106,6 +106,11 @@ namespace xPlat {
|
|||
m_fileCurrentPosition += bytesToCopy;
|
||||
}
|
||||
|
||||
if (m_fileCurrentPosition == m_uncompressedSize)
|
||||
{
|
||||
Cleanup();
|
||||
return std::make_pair(false, State::UNINITIALIZED);
|
||||
}
|
||||
return std::make_pair(countBytes != 0, State::READY_TO_COPY);
|
||||
}
|
||||
} // State::READY_TO_COPY
|
||||
|
|
|
@ -131,18 +131,17 @@ namespace xPlat
|
|||
|
||||
DWORD cbExtensionUsage = 0;
|
||||
std::vector<byte> extensionUsage(0);
|
||||
if (!CertGetEnhancedKeyUsage(pCertContext, CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG, NULL, &cbExtensionUsage))
|
||||
{
|
||||
extensionUsage.resize(cbExtensionUsage);
|
||||
ThrowErrorIf(Error::AppxSignatureInvalid, (
|
||||
!CertGetEnhancedKeyUsage(
|
||||
pCertContext,
|
||||
CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG,
|
||||
reinterpret_cast<PCERT_ENHKEY_USAGE>(extensionUsage.data()),
|
||||
&cbExtensionUsage) &&
|
||||
GetLastError() != CRYPT_E_NOT_FOUND
|
||||
), "CertGetEnhacnedKeyUsage on extension usage failed.");
|
||||
}
|
||||
CertGetEnhancedKeyUsage(pCertContext, CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG, NULL, &cbExtensionUsage);
|
||||
extensionUsage.resize(cbExtensionUsage);
|
||||
ThrowErrorIf(Error::AppxSignatureInvalid, (
|
||||
!CertGetEnhancedKeyUsage(
|
||||
pCertContext,
|
||||
CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG,
|
||||
reinterpret_cast<PCERT_ENHKEY_USAGE>(extensionUsage.data()),
|
||||
&cbExtensionUsage) &&
|
||||
GetLastError() != CRYPT_E_NOT_FOUND
|
||||
), "CertGetEnhacnedKeyUsage on extension usage failed.");
|
||||
|
||||
if (extensionUsage.size() > 0)
|
||||
{
|
||||
PCERT_ENHKEY_USAGE pExtensionUsageT = reinterpret_cast<PCERT_ENHKEY_USAGE>(extensionUsage.data());
|
||||
|
@ -154,18 +153,17 @@ namespace xPlat
|
|||
|
||||
DWORD cbPropertyUsage = 0;
|
||||
std::vector<byte> propertyUsage(0);
|
||||
if (!CertGetEnhancedKeyUsage(pCertContext, CERT_FIND_PROP_ONLY_ENHKEY_USAGE_FLAG, NULL, &cbPropertyUsage))
|
||||
{
|
||||
propertyUsage.resize(cbPropertyUsage);
|
||||
ThrowErrorIf(Error::AppxSignatureInvalid, (
|
||||
!CertGetEnhancedKeyUsage(
|
||||
pCertContext,
|
||||
CERT_FIND_PROP_ONLY_ENHKEY_USAGE_FLAG,
|
||||
reinterpret_cast<PCERT_ENHKEY_USAGE>(propertyUsage.data()),
|
||||
&cbPropertyUsage) &&
|
||||
GetLastError() != CRYPT_E_NOT_FOUND
|
||||
), "CertGetEnhancedKeyUsage on property usage failed.");
|
||||
}
|
||||
CertGetEnhancedKeyUsage(pCertContext, CERT_FIND_PROP_ONLY_ENHKEY_USAGE_FLAG, NULL, &cbPropertyUsage);
|
||||
propertyUsage.resize(cbPropertyUsage);
|
||||
ThrowErrorIf(Error::AppxSignatureInvalid, (
|
||||
!CertGetEnhancedKeyUsage(
|
||||
pCertContext,
|
||||
CERT_FIND_PROP_ONLY_ENHKEY_USAGE_FLAG,
|
||||
reinterpret_cast<PCERT_ENHKEY_USAGE>(propertyUsage.data()),
|
||||
&cbPropertyUsage) &&
|
||||
GetLastError() != CRYPT_E_NOT_FOUND
|
||||
), "CertGetEnhancedKeyUsage on property usage failed.");
|
||||
|
||||
if (propertyUsage.size() > 0)
|
||||
{
|
||||
PCERT_ENHKEY_USAGE pPropertyUsageT = reinterpret_cast<PCERT_ENHKEY_USAGE>(propertyUsage.data());
|
||||
|
@ -187,11 +185,15 @@ namespace xPlat
|
|||
policyStatus.cbSize = sizeof(CERT_CHAIN_POLICY_STATUS);
|
||||
policyParameters.dwFlags = MICROSOFT_ROOT_CERT_CHAIN_POLICY_CHECK_APPLICATION_ROOT_FLAG;
|
||||
|
||||
return CertVerifyCertificateChainPolicy(
|
||||
CERT_CHAIN_POLICY_MICROSOFT_ROOT,
|
||||
certChainContext,
|
||||
&policyParameters,
|
||||
&policyStatus);
|
||||
ThrowErrorIfNot(Error::AppxSignatureInvalid,
|
||||
CertVerifyCertificateChainPolicy(
|
||||
CERT_CHAIN_POLICY_MICROSOFT_ROOT,
|
||||
certChainContext,
|
||||
&policyParameters,
|
||||
&policyStatus),
|
||||
"CertVerifyCertificateChainPolicy failed");
|
||||
|
||||
return ERROR_SUCCESS == policyStatus.dwError;
|
||||
}
|
||||
|
||||
static bool IsAuthenticodeTrustedChain(_In_ PCCERT_CHAIN_CONTEXT certChainContext)
|
||||
|
@ -201,11 +203,31 @@ namespace xPlat
|
|||
CERT_CHAIN_POLICY_STATUS policyStatus = { 0 };
|
||||
policyStatus.cbSize = sizeof(CERT_CHAIN_POLICY_STATUS);
|
||||
|
||||
return CertVerifyCertificateChainPolicy(
|
||||
CERT_CHAIN_POLICY_AUTHENTICODE,
|
||||
certChainContext,
|
||||
&policyParameters,
|
||||
&policyStatus);
|
||||
//policyParameters.dwFlags = MICROSOFT_ROOT_CERT_CHAIN_POLICY_CHECK_APPLICATION_ROOT_FLAG;
|
||||
ThrowErrorIfNot(Error::AppxSignatureInvalid,
|
||||
CertVerifyCertificateChainPolicy(
|
||||
CERT_CHAIN_POLICY_AUTHENTICODE,
|
||||
certChainContext,
|
||||
&policyParameters,
|
||||
&policyStatus),
|
||||
"CertVerifyCertificateChainPolicy failed");
|
||||
|
||||
bool isAuthenticode = (ERROR_SUCCESS == policyStatus.dwError);
|
||||
|
||||
policyParameters = { 0 };
|
||||
policyParameters.cbSize = sizeof(CERT_CHAIN_POLICY_PARA);
|
||||
policyStatus = {0};
|
||||
policyStatus.cbSize = sizeof(CERT_CHAIN_POLICY_STATUS);
|
||||
ThrowErrorIfNot(Error::AppxSignatureInvalid,
|
||||
CertVerifyCertificateChainPolicy(
|
||||
CERT_CHAIN_POLICY_BASE,
|
||||
certChainContext,
|
||||
&policyParameters,
|
||||
&policyStatus),
|
||||
"CertVerifyCertificateChainPolicy failed");
|
||||
|
||||
bool chainsToTrustedRoot = (ERROR_SUCCESS == policyStatus.dwError);
|
||||
return isAuthenticode && chainsToTrustedRoot;
|
||||
}
|
||||
|
||||
static bool IsCACert(_In_ PCCERT_CONTEXT pCertContext)
|
||||
|
@ -250,7 +272,7 @@ namespace xPlat
|
|||
return(true);
|
||||
}
|
||||
|
||||
static PCCERT_CONTEXT GetCertContext(BYTE *signatureBuffer, ULONG cbSignatureBuffer)
|
||||
static PCCERT_CONTEXT GetCertContext(BYTE *signatureBuffer, ULONG cbSignatureBuffer)
|
||||
{
|
||||
//get cert context from strCertificate;
|
||||
DWORD dwExpectedContentType = CERT_QUERY_CONTENT_FLAG_CERT |
|
||||
|
@ -460,8 +482,8 @@ static PCCERT_CONTEXT GetCertContext(BYTE *signatureBuffer, ULONG cbSignatureBuf
|
|||
), "CryptDecodeObjectEx failed");
|
||||
|
||||
DigestHeader *header = reinterpret_cast<DigestHeader*>(indirectContent->Digest.pbData);
|
||||
std::uint32_t numberOfHashes = (indirectContent->Digest.cbData - sizeof(DWORD)) / sizeof(Digest);
|
||||
std::uint32_t modHashes = (indirectContent->Digest.cbData - sizeof(DWORD)) % sizeof(Digest);
|
||||
std::uint32_t numberOfHashes = (indirectContent->Digest.cbData - sizeof(DWORD)) / sizeof(DigestHash);
|
||||
std::uint32_t modHashes = (indirectContent->Digest.cbData - sizeof(DWORD)) % sizeof(DigestHash);
|
||||
ThrowErrorIf(Error::AppxSignatureInvalid, (
|
||||
(header->name != xPlat::AppxSignatureObject::DigestName::HEAD) &&
|
||||
(numberOfHashes != 4 && numberOfHashes != 5) &&
|
||||
|
@ -487,45 +509,15 @@ static PCCERT_CONTEXT GetCertContext(BYTE *signatureBuffer, ULONG cbSignatureBuf
|
|||
}
|
||||
}
|
||||
|
||||
// If the caller allows unknown origin certs, don't bother with the certificate check
|
||||
if (!(option & APPX_VALIDATION_OPTION_ALLOWSIGNATUREORIGINUNKNOWN))
|
||||
{
|
||||
// Build wintrust data to pass to WinVerifyTrust in order to validate signature
|
||||
// TODO: we cannot use SIP 5598CFF1-68DB-4340-B57F-1CACF88C9A51 on anything older than Win8!!!
|
||||
GUID P7xSipGuid = { 0x5598cff1, 0x68db, 0x4340,{ 0xb5, 0x7f, 0x1c, 0xac, 0xf8, 0x8c, 0x9a, 0x51 } };
|
||||
|
||||
WINTRUST_BLOB_INFO signatureBlobInfo = { 0 };
|
||||
signatureBlobInfo.cbStruct = sizeof(WINTRUST_BLOB_INFO);
|
||||
signatureBlobInfo.gSubject = P7xSipGuid;
|
||||
signatureBlobInfo.cbMemObject = p7x.size();
|
||||
signatureBlobInfo.pbMemObject = p7x.data();
|
||||
|
||||
WINTRUST_DATA trustData = { 0 };
|
||||
trustData.cbStruct = sizeof(WINTRUST_DATA);
|
||||
trustData.dwUIChoice = WTD_UI_NONE;
|
||||
trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
|
||||
trustData.dwUnionChoice = WTD_CHOICE_BLOB;
|
||||
trustData.dwStateAction = WTD_STATEACTION_VERIFY;
|
||||
trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL | WTD_REVOCATION_CHECK_NONE;
|
||||
trustData.pBlob = &signatureBlobInfo;
|
||||
|
||||
// Verify whether we trust the certificate. If it fails,
|
||||
GUID wintrustActionVerify = WINTRUST_ACTION_GENERIC_VERIFY_V2;
|
||||
ThrowErrorIf(Error::AppxCertNotTrusted, (
|
||||
0 != WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &wintrustActionVerify, &trustData)
|
||||
), "WinVerifyTrust failed");
|
||||
|
||||
// Close trustData.hWVTStateData -- returned by previous WinVerifyTrust call
|
||||
trustData.cbStruct = sizeof(WINTRUST_DATA);
|
||||
trustData.dwStateAction = WTD_STATEACTION_CLOSE;
|
||||
ThrowErrorIf(Error::AppxCertNotTrusted, (
|
||||
0 != WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &wintrustActionVerify, &trustData)
|
||||
), "WinVerifyTrust StateAction Close failed");
|
||||
}
|
||||
|
||||
origin = xPlat::SignatureOrigin::Unknown;
|
||||
if (IsStoreOrigin(p7s, p7sSize)) { origin = xPlat::SignatureOrigin::Store; }
|
||||
else if (IsAuthenticodeOrigin(p7s, p7sSize)) { origin = xPlat::SignatureOrigin::LOB; }
|
||||
|
||||
bool SignatureOriginUnknownAllowed = (option & APPX_VALIDATION_OPTION_ALLOWSIGNATUREORIGINUNKNOWN) == APPX_VALIDATION_OPTION_ALLOWSIGNATUREORIGINUNKNOWN;
|
||||
ThrowErrorIf(Error::AppxCertNotTrusted,
|
||||
((xPlat::SignatureOrigin::Unknown == origin) && !SignatureOriginUnknownAllowed),
|
||||
"Unknown signature origin");
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace xPlat
|
|
@ -287,19 +287,21 @@ namespace xPlat {
|
|||
(v == static_cast<std::uint32_t>(Signatures::CentralFileHeader)),
|
||||
"CDFH Signature");
|
||||
};
|
||||
// 1 - version made by 2 bytes
|
||||
Field<1>().validation = [](std::uint16_t& v)
|
||||
{ ThrowErrorIfNot(Error::ZipCentralDirectoryHeader,
|
||||
(v == static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension)),
|
||||
"unsupported version made by");
|
||||
};
|
||||
// 2 - version needed to extract 2 bytes
|
||||
Field<2>().validation = [](std::uint16_t& v)
|
||||
{ ThrowErrorIfNot(Error::ZipCentralDirectoryHeader,
|
||||
((v == static_cast<std::uint16_t>(ZipVersions::Zip32DefaultVersion)) ||
|
||||
(v == static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension))),
|
||||
"unsupported version needed to extract");
|
||||
};
|
||||
// we actually do not base any decisions on these values, and OPC and Appx both do not
|
||||
// consider the values in these fields to be all that interesting either apparently.
|
||||
// // 1 - version made by 2 bytes
|
||||
// Field<1>().validation = [](std::uint16_t& v)
|
||||
// { ThrowErrorIfNot(Error::ZipCentralDirectoryHeader,
|
||||
// (v == static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension)),
|
||||
// "unsupported version made by");
|
||||
// };
|
||||
// // 2 - version needed to extract 2 bytes
|
||||
// Field<2>().validation = [](std::uint16_t& v)
|
||||
// { ThrowErrorIfNot(Error::ZipCentralDirectoryHeader,
|
||||
// ((v == static_cast<std::uint16_t>(ZipVersions::Zip32DefaultVersion)) ||
|
||||
// (v == static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension))),
|
||||
// "unsupported version needed to extract");
|
||||
// };
|
||||
// 3 - general purpose bit flag 2 bytes
|
||||
Field<3>().validation = [](std::uint16_t& v)
|
||||
{ ThrowErrorIfNot(Error::ZipCentralDirectoryHeader,
|
||||
|
@ -336,10 +338,10 @@ namespace xPlat {
|
|||
Field<13>().validation = [](std::uint16_t& v)
|
||||
{ ThrowErrorIfNot(Error::ZipCentralDirectoryHeader, (v == 0), "unsupported disk number start");
|
||||
};
|
||||
//14 - internal file attributes 2 bytes
|
||||
Field<14>().validation = [](std::uint16_t& v)
|
||||
{ ThrowErrorIfNot(Error::ZipCentralDirectoryHeader, (v == 0), "unsupported internal file attributes");
|
||||
};
|
||||
// //14 - internal file attributes 2 bytes
|
||||
// Field<14>().validation = [](std::uint16_t& v)
|
||||
// { ThrowErrorIfNot(Error::ZipCentralDirectoryHeader, (v == 0), "unsupported internal file attributes");
|
||||
// };
|
||||
//15 - external file attributes 4 bytes
|
||||
//16 - relative offset of local header 4 bytes
|
||||
Field<16>().validation = [&](std::uint32_t& v)
|
||||
|
@ -701,7 +703,7 @@ namespace xPlat {
|
|||
|
||||
std::uint64_t GetSizeOfCD() { return Field<8>().value; }
|
||||
void SetSizeOfCD(std::uint64_t value) { Field<8>().value = value; }
|
||||
std::uint64_t GetOffsetfStartOfCD() { return Field<9>().value; }
|
||||
std::uint64_t GetOffsetStartOfCD() { return Field<9>().value; }
|
||||
void SetOffsetfStartOfCD(std::uint64_t value) { Field<9>().value = value; }
|
||||
|
||||
private:
|
||||
|
@ -803,39 +805,26 @@ namespace xPlat {
|
|||
};
|
||||
// 3 - total number of entries in the central directory on this disk 2 bytes
|
||||
Field<3>().validation = [&](std::uint16_t& v)
|
||||
{ ThrowErrorIfNot(Error::ZipEOCDRecord,
|
||||
((v == 0) || (v == 0xFFFF)),
|
||||
"unsupported total number of entries on this disk");
|
||||
// ThrowErrorIfNot(Error::ZipEOCDRecord, (
|
||||
// v == Field<2>().value
|
||||
// ), "previous field value does not match this field");
|
||||
{
|
||||
if (v != 0 && v != 0xFFFF) { m_archiveHasZip64Locator = false; }
|
||||
};
|
||||
// 4 - total number of entries in the central directory 2 bytes
|
||||
Field<4>().validation = [&](std::uint16_t& v)
|
||||
{ ThrowErrorIfNot(Error::ZipEOCDRecord,
|
||||
((v == 0) || (v == 0xFFFF)),
|
||||
"unsupported total number of entries");
|
||||
ThrowErrorIfNot(Error::ZipEOCDRecord, (
|
||||
{ ThrowErrorIfNot(Error::ZipEOCDRecord, (
|
||||
v == Field<3>().value
|
||||
), "previous field value does not match this field");
|
||||
};
|
||||
// 5 - size of the central directory 4 bytes
|
||||
Field<5>().validation = [&](std::uint32_t& v)
|
||||
{ ThrowErrorIfNot(Error::ZipEOCDRecord,
|
||||
((v == 0) || (v == 0xFFFFFFFF)),
|
||||
{ ThrowErrorIf(Error::ZipEOCDRecord,
|
||||
(m_archiveHasZip64Locator && (v != 0) && (v != 0xFFFFFFFF)),
|
||||
"unsupported size of central directory");
|
||||
ThrowErrorIfNot(Error::ZipEOCDRecord, (
|
||||
v == 0 ? Field<4>().value == 0 : Field<4>().value == 0xFFFF
|
||||
), "previous field value does not match this field");
|
||||
};
|
||||
// 6 - offset of start of central directory with respect to the starting disk number 4 bytes
|
||||
Field<6>().validation = [&](std::uint32_t& v)
|
||||
{ ThrowErrorIfNot(Error::ZipEOCDRecord,
|
||||
((v == 0) || (v == 0xFFFFFFFF)),
|
||||
{ ThrowErrorIf(Error::ZipEOCDRecord,
|
||||
(m_archiveHasZip64Locator && (v != 0) && (v != 0xFFFFFFFF)),
|
||||
"unsupported offset of start of central directory");
|
||||
ThrowErrorIfNot(Error::ZipEOCDRecord, (
|
||||
v == Field<5>().value
|
||||
), "previous field value does not match this field");
|
||||
};
|
||||
// 7 - .ZIP file comment length 2 bytes
|
||||
Field<7>().validation = [&](std::uint16_t& v)
|
||||
|
@ -849,7 +838,7 @@ namespace xPlat {
|
|||
SetSignature(static_cast<std::uint32_t>(Signatures::EndOfCentralDirectory));
|
||||
SetNumberOfDisk(0);
|
||||
SetDiskStart(0);
|
||||
// next 12 bytes need to be: FFFF FFFF FFFF FFFF FFFF FFFF
|
||||
// by default, the next 12 bytes need to be: FFFF FFFF FFFF FFFF FFFF FFFF
|
||||
SetTotalNumberOfEntries (std::numeric_limits<std::uint16_t>::max());
|
||||
SetTotalEntriesInCentralDirectory(std::numeric_limits<std::uint16_t>::max());
|
||||
SetSizeOfCentralDirectory (std::numeric_limits<std::uint32_t>::max());
|
||||
|
@ -858,7 +847,12 @@ namespace xPlat {
|
|||
SetCommentLength(0);
|
||||
}
|
||||
|
||||
bool GetIsZip64() { return m_isZip64; }
|
||||
bool GetArchiveHasZip64Locator() { return m_archiveHasZip64Locator; }
|
||||
bool GetIsZip64() { return m_isZip64; }
|
||||
|
||||
std::uint64_t GetNumberOfCentralDirectoryEntries() { return static_cast<std::uint64_t>(Field<3>().value); }
|
||||
std::uint64_t GetStartOfCentralDirectory() { return static_cast<std::uint64_t>(Field<6>().value); }
|
||||
|
||||
private:
|
||||
void SetSignature(std::uint32_t value) { Field<0>().value = value; }
|
||||
void SetNumberOfDisk(std::uint16_t value) { Field<1>().value = value; }
|
||||
|
@ -872,6 +866,7 @@ namespace xPlat {
|
|||
void SetCommentLength(std::uint16_t value) { Field<7>().value = value; }
|
||||
|
||||
bool m_isZip64 = false;
|
||||
bool m_archiveHasZip64Locator = true;
|
||||
};//class EndOfCentralDirectoryRecord
|
||||
|
||||
std::vector<std::string> ZipObject::GetFileNames(FileNameOptions)
|
||||
|
@ -887,7 +882,9 @@ namespace xPlat {
|
|||
IStream* ZipObject::GetFile(const std::string& fileName)
|
||||
{
|
||||
// TODO: Make this on-demand populate m_streams and then pull from there.
|
||||
return m_streams[fileName].Get();
|
||||
auto result = m_streams.find(fileName);
|
||||
ThrowErrorIf(Error::FileNotFound, (result == m_streams.end()), "file not in archive");
|
||||
return result->second.Get();
|
||||
}
|
||||
|
||||
void ZipObject::RemoveFile(const std::string& fileName)
|
||||
|
@ -917,22 +914,34 @@ namespace xPlat {
|
|||
endCentralDirectoryRecord.Read(m_stream.Get());
|
||||
|
||||
// find where the zip central directory exists.
|
||||
Zip64EndOfCentralDirectoryLocator zip64Locator(m_stream.Get());
|
||||
pos.QuadPart = -1*(endCentralDirectoryRecord.Size() + zip64Locator.Size());
|
||||
ThrowHrIfFailed(m_stream->Seek(pos, StreamBase::Reference::END, nullptr));
|
||||
zip64Locator.Read(m_stream.Get());
|
||||
std::uint64_t offsetStartOfCD = 0;
|
||||
std::uint64_t totalNumberOfEntries = 0;
|
||||
Zip64EndOfCentralDirectoryLocator zip64Locator(m_stream.Get());
|
||||
if (!endCentralDirectoryRecord.GetArchiveHasZip64Locator())
|
||||
{
|
||||
offsetStartOfCD = endCentralDirectoryRecord.GetStartOfCentralDirectory();
|
||||
totalNumberOfEntries = endCentralDirectoryRecord.GetNumberOfCentralDirectoryEntries();
|
||||
}
|
||||
else
|
||||
{ // Make sure that we have a zip64 end of central directory locator
|
||||
pos.QuadPart = -1*(endCentralDirectoryRecord.Size() + zip64Locator.Size());
|
||||
ThrowHrIfFailed(m_stream->Seek(pos, StreamBase::Reference::END, nullptr));
|
||||
zip64Locator.Read(m_stream.Get());
|
||||
|
||||
// now read the zip central directory
|
||||
Zip64EndOfCentralDirectoryRecord zip64EndOfCentralDirectory(m_stream.Get());
|
||||
pos.QuadPart = zip64Locator.GetRelativeOffset();
|
||||
ThrowHrIfFailed(m_stream->Seek(pos, StreamBase::Reference::START, nullptr));
|
||||
zip64EndOfCentralDirectory.Read(m_stream.Get());
|
||||
// now read the end of zip central directory record
|
||||
Zip64EndOfCentralDirectoryRecord zip64EndOfCentralDirectory(m_stream.Get());
|
||||
pos.QuadPart = zip64Locator.GetRelativeOffset();
|
||||
ThrowHrIfFailed(m_stream->Seek(pos, StreamBase::Reference::START, nullptr));
|
||||
zip64EndOfCentralDirectory.Read(m_stream.Get());
|
||||
offsetStartOfCD = zip64EndOfCentralDirectory.GetOffsetStartOfCD();
|
||||
totalNumberOfEntries = zip64EndOfCentralDirectory.GetTotalNumberOfEntries();
|
||||
}
|
||||
|
||||
// read the zip central directory
|
||||
std::map<std::string, std::shared_ptr<CentralDirectoryFileHeader>> centralDirectory;
|
||||
pos.QuadPart = zip64EndOfCentralDirectory.GetOffsetfStartOfCD();
|
||||
pos.QuadPart = offsetStartOfCD;
|
||||
ThrowHrIfFailed(m_stream->Seek(pos, StreamBase::Reference::START, nullptr));
|
||||
for (std::uint32_t index = 0; index < zip64EndOfCentralDirectory.GetTotalNumberOfEntries(); index++)
|
||||
for (std::uint32_t index = 0; index < totalNumberOfEntries; index++)
|
||||
{
|
||||
auto centralFileHeader = std::make_shared<CentralDirectoryFileHeader>(endCentralDirectoryRecord.GetIsZip64(), m_stream.Get());
|
||||
centralFileHeader->Read(m_stream.Get());
|
||||
|
@ -940,14 +949,14 @@ namespace xPlat {
|
|||
centralDirectory.insert(std::make_pair(centralFileHeader->GetFileName(), centralFileHeader));
|
||||
}
|
||||
|
||||
// We should have no data between the end of the last central directory header and the start of the EoCD
|
||||
ULARGE_INTEGER uPos = {0};
|
||||
ThrowHrIfFailed(m_stream->Seek({0}, StreamBase::Reference::CURRENT, &uPos));
|
||||
ThrowErrorIfNot(Error::ZipHiddenData, (uPos.QuadPart == zip64Locator.GetRelativeOffset()), "hidden data unsupported");
|
||||
if (endCentralDirectoryRecord.GetArchiveHasZip64Locator())
|
||||
{ // We should have no data between the end of the last central directory header and the start of the EoCD
|
||||
ULARGE_INTEGER uPos = {0};
|
||||
ThrowHrIfFailed(m_stream->Seek({0}, StreamBase::Reference::CURRENT, &uPos));
|
||||
ThrowErrorIfNot(Error::ZipHiddenData, (uPos.QuadPart == zip64Locator.GetRelativeOffset()), "hidden data unsupported");
|
||||
}
|
||||
|
||||
// TODO: change to uint64_t when adding full zip64 support
|
||||
std::map<std::uint64_t, std::shared_ptr<LocalFileHeader>> fileRepository;
|
||||
|
||||
// TODO: change population of m_streams into cache semantics and move into ZipObject::GetFile
|
||||
// Read the file repository
|
||||
for (const auto& centralFileHeader : centralDirectory)
|
||||
|
@ -963,7 +972,7 @@ namespace xPlat {
|
|||
auto fileStream = ComPtr<IStream>::Make<ZipFileStream>(
|
||||
centralFileHeader.second->GetFileName(),
|
||||
"TODO: Implement", // TODO: put value from content type
|
||||
m_factory.Get(),
|
||||
m_factory,
|
||||
localFileHeader->GetCompressionType() == CompressionType::Deflate,
|
||||
centralFileHeader.second->GetRelativeOffsetOfLocalHeader() + localFileHeader->Size(),
|
||||
localFileHeader->GetCompressedSize(),
|
||||
|
|
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичные данные
test/appx/BlockMap/TODAVIANO/SignedTamperedBlockMap-TRUST_E_BAD_DIGEST.appx
Normal file
Двоичные данные
test/appx/BlockMap/TODAVIANO/SignedTamperedBlockMap-TRUST_E_BAD_DIGEST.appx
Normal file
Двоичный файл не отображается.
Загрузка…
Ссылка в новой задаче