| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the tools applications of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "appxengine.h" |
| #include "appxengine_p.h" |
| |
| #include <QtCore/QDateTime> |
| #include <QtCore/QDir> |
| #include <QtCore/QDirIterator> |
| #include <QtCore/QFile> |
| #include <QtCore/QFileInfo> |
| #include <QtCore/QLoggingCategory> |
| #include <QtCore/QStandardPaths> |
| |
| #include <ShlObj.h> |
| #include <Shlwapi.h> |
| #include <wsdevlicensing.h> |
| #include <AppxPackaging.h> |
| #include <wrl.h> |
| #include <windows.applicationmodel.h> |
| #include <windows.management.deployment.h> |
| #include <wincrypt.h> |
| |
| using namespace Microsoft::WRL; |
| using namespace Microsoft::WRL::Wrappers; |
| using namespace ABI::Windows::Foundation; |
| using namespace ABI::Windows::Management::Deployment; |
| using namespace ABI::Windows::ApplicationModel; |
| using namespace ABI::Windows::System; |
| |
| QT_USE_NAMESPACE |
| |
| // *********** Taken from MSDN Example code |
| // https://msdn.microsoft.com/en-us/library/windows/desktop/jj835834%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 |
| |
| #define SIGNER_SUBJECT_FILE 0x01 |
| #define SIGNER_NO_ATTR 0x00 |
| #define SIGNER_CERT_POLICY_CHAIN_NO_ROOT 0x08 |
| #define SIGNER_CERT_STORE 0x02 |
| |
| typedef struct _SIGNER_FILE_INFO |
| { |
| DWORD cbSize; |
| LPCWSTR pwszFileName; |
| HANDLE hFile; |
| } SIGNER_FILE_INFO; |
| |
| typedef struct _SIGNER_BLOB_INFO |
| { |
| DWORD cbSize; |
| GUID *pGuidSubject; |
| DWORD cbBlob; |
| BYTE *pbBlob; |
| LPCWSTR pwszDisplayName; |
| } SIGNER_BLOB_INFO; |
| |
| typedef struct _SIGNER_SUBJECT_INFO |
| { |
| DWORD cbSize; |
| DWORD *pdwIndex; |
| DWORD dwSubjectChoice; |
| union |
| { |
| SIGNER_FILE_INFO *pSignerFileInfo; |
| SIGNER_BLOB_INFO *pSignerBlobInfo; |
| }; |
| } SIGNER_SUBJECT_INFO, *PSIGNER_SUBJECT_INFO; |
| |
| typedef struct _SIGNER_ATTR_AUTHCODE |
| { |
| DWORD cbSize; |
| BOOL fCommercial; |
| BOOL fIndividual; |
| LPCWSTR pwszName; |
| LPCWSTR pwszInfo; |
| } SIGNER_ATTR_AUTHCODE; |
| |
| typedef struct _SIGNER_SIGNATURE_INFO |
| { |
| DWORD cbSize; |
| ALG_ID algidHash; |
| DWORD dwAttrChoice; |
| union |
| { |
| SIGNER_ATTR_AUTHCODE *pAttrAuthcode; |
| }; |
| PCRYPT_ATTRIBUTES psAuthenticated; |
| PCRYPT_ATTRIBUTES psUnauthenticated; |
| } SIGNER_SIGNATURE_INFO, *PSIGNER_SIGNATURE_INFO; |
| |
| typedef struct _SIGNER_PROVIDER_INFO |
| { |
| DWORD cbSize; |
| LPCWSTR pwszProviderName; |
| DWORD dwProviderType; |
| DWORD dwKeySpec; |
| DWORD dwPvkChoice; |
| union |
| { |
| LPWSTR pwszPvkFileName; |
| LPWSTR pwszKeyContainer; |
| }; |
| } SIGNER_PROVIDER_INFO, *PSIGNER_PROVIDER_INFO; |
| |
| typedef struct _SIGNER_SPC_CHAIN_INFO |
| { |
| DWORD cbSize; |
| LPCWSTR pwszSpcFile; |
| DWORD dwCertPolicy; |
| HCERTSTORE hCertStore; |
| } SIGNER_SPC_CHAIN_INFO; |
| |
| typedef struct _SIGNER_CERT_STORE_INFO |
| { |
| DWORD cbSize; |
| PCCERT_CONTEXT pSigningCert; |
| DWORD dwCertPolicy; |
| HCERTSTORE hCertStore; |
| } SIGNER_CERT_STORE_INFO; |
| |
| typedef struct _SIGNER_CERT |
| { |
| DWORD cbSize; |
| DWORD dwCertChoice; |
| union |
| { |
| LPCWSTR pwszSpcFile; |
| SIGNER_CERT_STORE_INFO *pCertStoreInfo; |
| SIGNER_SPC_CHAIN_INFO *pSpcChainInfo; |
| }; |
| HWND hwnd; |
| } SIGNER_CERT, *PSIGNER_CERT; |
| |
| typedef struct _SIGNER_CONTEXT |
| { |
| DWORD cbSize; |
| DWORD cbBlob; |
| BYTE *pbBlob; |
| } SIGNER_CONTEXT, *PSIGNER_CONTEXT; |
| |
| typedef struct _SIGNER_SIGN_EX2_PARAMS |
| { |
| DWORD dwFlags; |
| PSIGNER_SUBJECT_INFO pSubjectInfo; |
| PSIGNER_CERT pSigningCert; |
| PSIGNER_SIGNATURE_INFO pSignatureInfo; |
| PSIGNER_PROVIDER_INFO pProviderInfo; |
| DWORD dwTimestampFlags; |
| PCSTR pszAlgorithmOid; |
| PCWSTR pwszTimestampURL; |
| PCRYPT_ATTRIBUTES pCryptAttrs; |
| PVOID pSipData; |
| PSIGNER_CONTEXT *pSignerContext; |
| PVOID pCryptoPolicy; |
| PVOID pReserved; |
| } SIGNER_SIGN_EX2_PARAMS, *PSIGNER_SIGN_EX2_PARAMS; |
| |
| typedef struct _APPX_SIP_CLIENT_DATA |
| { |
| PSIGNER_SIGN_EX2_PARAMS pSignerParams; |
| IUnknown* pAppxSipState; |
| } APPX_SIP_CLIENT_DATA, *PAPPX_SIP_CLIENT_DATA; |
| |
| bool signAppxPackage(PCCERT_CONTEXT signingCertContext, LPCWSTR packageFilePath) |
| { |
| HRESULT hr = S_OK; |
| |
| DWORD signerIndex = 0; |
| |
| SIGNER_FILE_INFO fileInfo = {}; |
| fileInfo.cbSize = sizeof(SIGNER_FILE_INFO); |
| fileInfo.pwszFileName = packageFilePath; |
| |
| SIGNER_SUBJECT_INFO subjectInfo = {}; |
| subjectInfo.cbSize = sizeof(SIGNER_SUBJECT_INFO); |
| subjectInfo.pdwIndex = &signerIndex; |
| subjectInfo.dwSubjectChoice = SIGNER_SUBJECT_FILE; |
| subjectInfo.pSignerFileInfo = &fileInfo; |
| |
| SIGNER_CERT_STORE_INFO certStoreInfo = {}; |
| certStoreInfo.cbSize = sizeof(SIGNER_CERT_STORE_INFO); |
| certStoreInfo.dwCertPolicy = SIGNER_CERT_POLICY_CHAIN_NO_ROOT; |
| certStoreInfo.pSigningCert = signingCertContext; |
| |
| SIGNER_CERT cert = {}; |
| cert.cbSize = sizeof(SIGNER_CERT); |
| cert.dwCertChoice = SIGNER_CERT_STORE; |
| cert.pCertStoreInfo = &certStoreInfo; |
| |
| // The algidHash of the signature to be created must match the |
| // hash algorithm used to create the app package |
| SIGNER_SIGNATURE_INFO signatureInfo = {}; |
| signatureInfo.cbSize = sizeof(SIGNER_SIGNATURE_INFO); |
| signatureInfo.algidHash = CALG_SHA_512; |
| signatureInfo.dwAttrChoice = SIGNER_NO_ATTR; |
| |
| SIGNER_SIGN_EX2_PARAMS signerParams = {}; |
| signerParams.pSubjectInfo = &subjectInfo; |
| signerParams.pSigningCert = &cert; |
| signerParams.pSignatureInfo = &signatureInfo; |
| |
| APPX_SIP_CLIENT_DATA sipClientData = {}; |
| sipClientData.pSignerParams = &signerParams; |
| signerParams.pSipData = &sipClientData; |
| |
| // Type definition for invoking SignerSignEx2 via GetProcAddress |
| typedef HRESULT (WINAPI *SignerSignEx2Function)( |
| DWORD, |
| PSIGNER_SUBJECT_INFO, |
| PSIGNER_CERT, |
| PSIGNER_SIGNATURE_INFO, |
| PSIGNER_PROVIDER_INFO, |
| DWORD, |
| PCSTR, |
| PCWSTR, |
| PCRYPT_ATTRIBUTES, |
| PVOID, |
| PSIGNER_CONTEXT *, |
| PVOID, |
| PVOID); |
| |
| // Load the SignerSignEx2 function from MSSign32.dll |
| HMODULE msSignModule = LoadLibraryEx( |
| L"MSSign32.dll", |
| NULL, |
| LOAD_LIBRARY_SEARCH_SYSTEM32); |
| |
| if (!msSignModule) { |
| qCWarning(lcWinRtRunner) << "LoadLibraryEx failed to load MSSign32.dll."; |
| return false; |
| } |
| |
| SignerSignEx2Function SignerSignEx2 = reinterpret_cast<SignerSignEx2Function>( |
| GetProcAddress(msSignModule, "SignerSignEx2")); |
| if (!SignerSignEx2) { |
| qCWarning(lcWinRtRunner) << "Could not resolve SignerSignEx2"; |
| FreeLibrary(msSignModule); |
| return false; |
| } |
| hr = SignerSignEx2(signerParams.dwFlags, |
| signerParams.pSubjectInfo, |
| signerParams.pSigningCert, |
| signerParams.pSignatureInfo, |
| signerParams.pProviderInfo, |
| signerParams.dwTimestampFlags, |
| signerParams.pszAlgorithmOid, |
| signerParams.pwszTimestampURL, |
| signerParams.pCryptAttrs, |
| signerParams.pSipData, |
| signerParams.pSignerContext, |
| signerParams.pCryptoPolicy, |
| signerParams.pReserved); |
| |
| FreeLibrary(msSignModule); |
| |
| RETURN_FALSE_IF_FAILED("Could not sign package."); |
| |
| if (sipClientData.pAppxSipState) |
| sipClientData.pAppxSipState->Release(); |
| |
| return true; |
| } |
| // ************ MSDN |
| |
| bool AppxEngine::getManifestFile(const QString &fileName, QString *manifest) |
| { |
| if (!QFile::exists(fileName)) { |
| qCWarning(lcWinRtRunner) << fileName << "does not exist."; |
| return false; |
| } |
| |
| // If it looks like an appx manifest, we're done |
| if (fileName.endsWith(QStringLiteral("AppxManifest.xml"))) { |
| |
| if (manifest) |
| *manifest = fileName; |
| return true; |
| } |
| |
| // If it looks like an executable, check that manifest is next to it |
| if (fileName.endsWith(QLatin1String(".exe"))) { |
| QDir appDir = QFileInfo(fileName).absoluteDir(); |
| QString manifestFileName = appDir.absoluteFilePath(QStringLiteral("AppxManifest.xml")); |
| if (!QFile::exists(manifestFileName)) { |
| qCWarning(lcWinRtRunner) << manifestFileName << "does not exist."; |
| return false; |
| } |
| |
| if (manifest) |
| *manifest = manifestFileName; |
| return true; |
| } |
| |
| if (fileName.endsWith(QLatin1String(".appx"))) { |
| // For existing appx packages the manifest reader will be |
| // instantiated later. |
| return true; |
| } |
| |
| qCWarning(lcWinRtRunner) << "Appx: unable to determine manifest for" << fileName << "."; |
| return false; |
| } |
| |
| #define CHECK_RESULT(errorMessage, action)\ |
| do {\ |
| if (FAILED(hr)) {\ |
| qCWarning(lcWinRtRunner).nospace() << errorMessage " (0x"\ |
| << QByteArray::number(hr, 16).constData()\ |
| << ' ' << qt_error_string(hr) << ')';\ |
| action;\ |
| }\ |
| } while (false) |
| |
| #define CHECK_RESULT_FATAL(errorMessage, action)\ |
| do {CHECK_RESULT(errorMessage, d->hasFatalError = true; action;);} while (false) |
| |
| static ProcessorArchitecture toProcessorArchitecture(APPX_PACKAGE_ARCHITECTURE appxArch) |
| { |
| switch (appxArch) { |
| case APPX_PACKAGE_ARCHITECTURE_X86: |
| return ProcessorArchitecture_X86; |
| case APPX_PACKAGE_ARCHITECTURE_ARM: |
| return ProcessorArchitecture_Arm; |
| case APPX_PACKAGE_ARCHITECTURE_X64: |
| return ProcessorArchitecture_X64; |
| case APPX_PACKAGE_ARCHITECTURE_NEUTRAL: |
| // fall-through intended |
| default: |
| return ProcessorArchitecture_Neutral; |
| } |
| } |
| |
| AppxEngine::AppxEngine(Runner *runner, AppxEnginePrivate *dd) |
| : d_ptr(dd) |
| { |
| Q_D(AppxEngine); |
| if (d->hasFatalError) |
| return; |
| |
| d->runner = runner; |
| d->processHandle = NULL; |
| d->pid = -1; |
| d->exitCode = UINT_MAX; |
| |
| HRESULT hr; |
| hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Foundation_Uri).Get(), |
| IID_PPV_ARGS(&d->uriFactory)); |
| CHECK_RESULT_FATAL("Failed to instantiate URI factory.", return); |
| |
| hr = CoCreateInstance(CLSID_AppxFactory, nullptr, CLSCTX_INPROC_SERVER, |
| IID_IAppxFactory, &d->packageFactory); |
| CHECK_RESULT_FATAL("Failed to instantiate package factory.", return); |
| |
| bool existingPackage = runner->app().endsWith(QLatin1String(".appx")); |
| |
| if (existingPackage) { |
| ComPtr<IStream> appxStream; |
| hr = SHCreateStreamOnFile(wchar(runner->app()), STGM_READ, &appxStream); |
| CHECK_RESULT_FATAL("Failed to open appx stream.", return); |
| |
| ComPtr<IAppxPackageReader> packageReader; |
| hr = d->packageFactory->CreatePackageReader(appxStream.Get(), &packageReader); |
| if (FAILED(hr)) { |
| qCWarning(lcWinRtRunner).nospace() << "Failed to instantiate package reader. (0x" |
| << QByteArray::number(hr, 16).constData() |
| << ' ' << qt_error_string(hr) << ')'; |
| d->hasFatalError = true; |
| return; |
| } |
| |
| hr = packageReader->GetManifest(&d->manifestReader); |
| if (FAILED(hr)) { |
| qCWarning(lcWinRtRunner).nospace() << "Failed to query manifext reader from package"; |
| d->hasFatalError = true; |
| return; |
| } |
| } else { |
| if (!getManifestFile(runner->app(), &d->manifest)) { |
| qCWarning(lcWinRtRunner) << "Unable to determine manifest file from" << runner->app(); |
| d->hasFatalError = true; |
| return; |
| } |
| |
| ComPtr<IStream> manifestStream; |
| hr = SHCreateStreamOnFile(wchar(d->manifest), STGM_READ, &manifestStream); |
| CHECK_RESULT_FATAL("Failed to open manifest stream.", return); |
| |
| hr = d->packageFactory->CreateManifestReader(manifestStream.Get(), &d->manifestReader); |
| if (FAILED(hr)) { |
| qCWarning(lcWinRtRunner).nospace() << "Failed to instantiate manifest reader. (0x" |
| << QByteArray::number(hr, 16).constData() |
| << ' ' << qt_error_string(hr) << ')'; |
| // ### TODO: read detailed error from event log directly |
| if (hr == APPX_E_INVALID_MANIFEST) { |
| qCWarning(lcWinRtRunner) << "More information on the error can " |
| "be found in the event log under " |
| "Microsoft\\Windows\\AppxPackagingOM"; |
| } |
| d->hasFatalError = true; |
| return; |
| } |
| } |
| ComPtr<IAppxManifestPackageId> packageId; |
| hr = d->manifestReader->GetPackageId(&packageId); |
| CHECK_RESULT_FATAL("Unable to obtain the package ID from the manifest.", return); |
| |
| APPX_PACKAGE_ARCHITECTURE arch; |
| hr = packageId->GetArchitecture(&arch); |
| CHECK_RESULT_FATAL("Failed to retrieve the app's architecture.", return); |
| d->packageArchitecture = toProcessorArchitecture(arch); |
| |
| LPWSTR packageFullName; |
| hr = packageId->GetPackageFullName(&packageFullName); |
| CHECK_RESULT_FATAL("Unable to obtain the package full name from the manifest.", return); |
| d->packageFullName = QString::fromWCharArray(packageFullName); |
| CoTaskMemFree(packageFullName); |
| |
| LPWSTR packageFamilyName; |
| hr = packageId->GetPackageFamilyName(&packageFamilyName); |
| CHECK_RESULT_FATAL("Unable to obtain the package full family name from the manifest.", return); |
| d->packageFamilyName = QString::fromWCharArray(packageFamilyName); |
| CoTaskMemFree(packageFamilyName); |
| |
| LPWSTR publisher; |
| packageId->GetPublisher(&publisher); |
| CHECK_RESULT_FATAL("Failed to retrieve publisher name from package.", return); |
| d->publisherName = QString::fromWCharArray(publisher); |
| CoTaskMemFree(publisher); |
| |
| ComPtr<IAppxManifestApplicationsEnumerator> applications; |
| hr = d->manifestReader->GetApplications(&applications); |
| CHECK_RESULT_FATAL("Failed to get a list of applications from the manifest.", return); |
| |
| BOOL hasCurrent; |
| hr = applications->GetHasCurrent(&hasCurrent); |
| CHECK_RESULT_FATAL("Failed to iterate over applications in the manifest.", return); |
| |
| // For now, we are only interested in the first application |
| ComPtr<IAppxManifestApplication> application; |
| hr = applications->GetCurrent(&application); |
| CHECK_RESULT_FATAL("Failed to access the first application in the manifest.", return); |
| |
| LPWSTR executable; |
| application->GetStringValue(L"Executable", &executable); |
| CHECK_RESULT_FATAL("Failed to retrieve the application executable from the manifest.", return); |
| d->executable = QFileInfo(runner->app()).absoluteDir() |
| .absoluteFilePath(QString::fromWCharArray(executable)); |
| CoTaskMemFree(executable); |
| |
| ComPtr<IAppxManifestPackageDependenciesEnumerator> dependencies; |
| hr = d->manifestReader->GetPackageDependencies(&dependencies); |
| CHECK_RESULT_FATAL("Failed to retrieve the package dependencies from the manifest.", return); |
| |
| hr = dependencies->GetHasCurrent(&hasCurrent); |
| CHECK_RESULT_FATAL("Failed to iterate over dependencies in the manifest.", return); |
| while (SUCCEEDED(hr) && hasCurrent) { |
| ComPtr<IAppxManifestPackageDependency> dependency; |
| hr = dependencies->GetCurrent(&dependency); |
| CHECK_RESULT_FATAL("Failed to access dependency in the manifest.", return); |
| |
| LPWSTR name; |
| hr = dependency->GetName(&name); |
| CHECK_RESULT_FATAL("Failed to access dependency name.", return); |
| d->dependencies.insert(QString::fromWCharArray(name)); |
| CoTaskMemFree(name); |
| hr = dependencies->MoveNext(&hasCurrent); |
| } |
| } |
| |
| AppxEngine::~AppxEngine() |
| { |
| Q_D(const AppxEngine); |
| CloseHandle(d->processHandle); |
| } |
| |
| qint64 AppxEngine::pid() const |
| { |
| Q_D(const AppxEngine); |
| qCDebug(lcWinRtRunner) << __FUNCTION__; |
| |
| return d->pid; |
| } |
| |
| int AppxEngine::exitCode() const |
| { |
| Q_D(const AppxEngine); |
| qCDebug(lcWinRtRunner) << __FUNCTION__; |
| |
| return d->exitCode == UINT_MAX ? -1 : HRESULT_CODE(d->exitCode); |
| } |
| |
| QString AppxEngine::executable() const |
| { |
| Q_D(const AppxEngine); |
| qCDebug(lcWinRtRunner) << __FUNCTION__; |
| |
| return d->executable; |
| } |
| |
| bool AppxEngine::installDependencies() |
| { |
| Q_D(AppxEngine); |
| qCDebug(lcWinRtRunner) << __FUNCTION__; |
| |
| QSet<QString> toInstall; |
| for (const QString &dependencyName : qAsConst(d->dependencies)) { |
| toInstall.insert(dependencyName); |
| qCDebug(lcWinRtRunner).nospace() |
| << "dependency to be installed: " << dependencyName; |
| } |
| |
| if (toInstall.isEmpty()) |
| return true; |
| |
| const QString extensionSdkDir = extensionSdkPath(); |
| if (!QFile::exists(extensionSdkDir)) { |
| qCWarning(lcWinRtRunner).nospace().noquote() |
| << QStringLiteral("The directory \"%1\" does not exist.").arg( |
| QDir::toNativeSeparators(extensionSdkDir)); |
| return false; |
| } |
| qCDebug(lcWinRtRunner).nospace().noquote() |
| << "looking for dependency packages in \"" |
| << QDir::toNativeSeparators(extensionSdkDir) << '"'; |
| QDirIterator dit(extensionSdkDir, QStringList() << QStringLiteral("*.appx"), |
| QDir::Files, |
| QDirIterator::Subdirectories); |
| while (dit.hasNext()) { |
| dit.next(); |
| |
| HRESULT hr; |
| ComPtr<IStream> inputStream; |
| forever { |
| hr = SHCreateStreamOnFileEx(wchar(dit.filePath()), |
| STGM_READ | STGM_SHARE_EXCLUSIVE, |
| 0, FALSE, NULL, &inputStream); |
| if (HRESULT_CODE(hr) == ERROR_SHARING_VIOLATION) { |
| qCWarning(lcWinRtRunner).nospace() |
| << "Input stream is locked by another process. Will retry..."; |
| Sleep(1000); |
| } else { |
| break; |
| } |
| } |
| CHECK_RESULT("Failed to create input stream for package in ExtensionSdkDir.", continue); |
| |
| ComPtr<IAppxPackageReader> packageReader; |
| hr = d->packageFactory->CreatePackageReader(inputStream.Get(), &packageReader); |
| CHECK_RESULT("Failed to create package reader for package in ExtensionSdkDir.", continue); |
| |
| ComPtr<IAppxManifestReader> manifestReader; |
| hr = packageReader->GetManifest(&manifestReader); |
| CHECK_RESULT("Failed to create manifest reader for package in ExtensionSdkDir.", continue); |
| |
| ComPtr<IAppxManifestPackageId> packageId; |
| hr = manifestReader->GetPackageId(&packageId); |
| CHECK_RESULT("Failed to retrieve package id for package in ExtensionSdkDir.", continue); |
| |
| LPWSTR sz; |
| hr = packageId->GetName(&sz); |
| CHECK_RESULT("Failed to retrieve name from package in ExtensionSdkDir.", continue); |
| const QString name = QString::fromWCharArray(sz); |
| CoTaskMemFree(sz); |
| |
| if (!toInstall.contains(name)) |
| continue; |
| |
| APPX_PACKAGE_ARCHITECTURE arch; |
| hr = packageId->GetArchitecture(&arch); |
| CHECK_RESULT("Failed to retrieve architecture from package in ExtensionSdkDir.", continue); |
| if (d->packageArchitecture != arch) |
| continue; |
| |
| qCDebug(lcWinRtRunner).nospace().noquote() |
| << "installing dependency \"" << name << "\" from \"" |
| << QDir::toNativeSeparators(dit.filePath()) << '"'; |
| if (!installPackage(manifestReader.Get(), dit.filePath())) { |
| qCWarning(lcWinRtRunner) << "Failed to install package:" << name; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool AppxEngine::createPackage(const QString &packageFileName) |
| { |
| Q_D(AppxEngine); |
| |
| static QHash<QString, QString> contentTypes; |
| if (contentTypes.isEmpty()) { |
| contentTypes.insert(QStringLiteral("dll"), QStringLiteral("application/x-msdownload")); |
| contentTypes.insert(QStringLiteral("exe"), QStringLiteral("application/x-msdownload")); |
| contentTypes.insert(QStringLiteral("png"), QStringLiteral("image/png")); |
| contentTypes.insert(QStringLiteral("xml"), QStringLiteral("vnd.ms-appx.manifest+xml")); |
| } |
| |
| // Check for package map, or create one if needed |
| QDir base = QFileInfo(d->manifest).absoluteDir(); |
| QFile packageFile(packageFileName); |
| |
| QHash<QString, QString> files; |
| QFile mappingFile(base.absoluteFilePath(QStringLiteral("AppxManifest.map"))); |
| if (mappingFile.exists()) { |
| qCWarning(lcWinRtRunner) << "Creating package from mapping file:" << mappingFile.fileName(); |
| if (!mappingFile.open(QFile::ReadOnly)) { |
| qCWarning(lcWinRtRunner) << "Unable to read mapping file:" << mappingFile.errorString(); |
| return false; |
| } |
| |
| QRegExp pattern(QStringLiteral("^\"([^\"]*)\"\\s*\"([^\"]*)\"$")); |
| bool inFileSection = false; |
| while (!mappingFile.atEnd()) { |
| const QString line = QString::fromUtf8(mappingFile.readLine()).trimmed(); |
| if (line.startsWith(QLatin1Char('['))) { |
| inFileSection = line == QStringLiteral("[Files]"); |
| continue; |
| } |
| if (pattern.cap(2).compare(QStringLiteral("AppxManifest.xml"), Qt::CaseInsensitive) == 0) |
| continue; |
| if (inFileSection && pattern.indexIn(line) >= 0 && pattern.captureCount() == 2) { |
| QString inputFile = pattern.cap(1); |
| if (!QFile::exists(inputFile)) |
| inputFile = base.absoluteFilePath(inputFile); |
| files.insert(QDir::toNativeSeparators(inputFile), QDir::toNativeSeparators(pattern.cap(2))); |
| } |
| } |
| } else { |
| qCWarning(lcWinRtRunner) << "No mapping file exists. Only recognized files will be packaged."; |
| // Add executable |
| files.insert(QDir::toNativeSeparators(d->executable), QFileInfo(d->executable).fileName()); |
| // Add all files but filtered artifacts |
| const QStringList excludeFileTypes = QStringList() |
| << QStringLiteral("ilk") << QStringLiteral("pdb") << QStringLiteral("obj") |
| << QStringLiteral("appx"); |
| |
| QDirIterator dirIterator(base.absolutePath(), QDir::Files, QDirIterator::Subdirectories); |
| while (dirIterator.hasNext()) { |
| const QString filePath = dirIterator.next(); |
| if (filePath.endsWith(QLatin1String("AppxManifest.xml"), Qt::CaseInsensitive)) |
| continue; |
| const QFileInfo fileInfo(filePath); |
| if (!excludeFileTypes.contains(fileInfo.suffix())) |
| files.insert(QDir::toNativeSeparators(filePath), QDir::toNativeSeparators(base.relativeFilePath(filePath))); |
| } |
| } |
| |
| ComPtr<IStream> outputStream; |
| HRESULT hr = SHCreateStreamOnFile(wchar(packageFile.fileName()), STGM_WRITE|STGM_CREATE, &outputStream); |
| RETURN_FALSE_IF_FAILED("Failed to create package file output stream"); |
| |
| ComPtr<IUri> hashMethod; |
| hr = CreateUri(L"http://www.w3.org/2001/04/xmlenc#sha512", Uri_CREATE_CANONICALIZE, 0, &hashMethod); |
| RETURN_FALSE_IF_FAILED("Failed to create the has method URI"); |
| |
| APPX_PACKAGE_SETTINGS packageSettings = { FALSE, hashMethod.Get() }; |
| ComPtr<IAppxPackageWriter> packageWriter; |
| hr = d->packageFactory->CreatePackageWriter(outputStream.Get(), &packageSettings, &packageWriter); |
| RETURN_FALSE_IF_FAILED("Failed to create package writer"); |
| |
| for (QHash<QString, QString>::const_iterator i = files.begin(); i != files.end(); ++i) { |
| qCDebug(lcWinRtRunner) << "Packaging" << i.key() << i.value(); |
| ComPtr<IStream> inputStream; |
| hr = SHCreateStreamOnFile(wchar(i.key()), STGM_READ, &inputStream); |
| RETURN_FALSE_IF_FAILED("Failed to open file"); |
| const QString contentType = contentTypes.value(QFileInfo(i.key()).suffix().toLower(), |
| QStringLiteral("application/octet-stream")); |
| hr = packageWriter->AddPayloadFile(wchar(i.value()), wchar(contentType), |
| APPX_COMPRESSION_OPTION_NORMAL, inputStream.Get()); |
| RETURN_FALSE_IF_FAILED("Failed to add payload file"); |
| } |
| |
| // Write out the manifest |
| ComPtr<IStream> manifestStream; |
| hr = SHCreateStreamOnFile(wchar(d->manifest), STGM_READ, &manifestStream); |
| RETURN_FALSE_IF_FAILED("Failed to open manifest for packaging"); |
| hr = packageWriter->Close(manifestStream.Get()); |
| RETURN_FALSE_IF_FAILED("Failed to finalize package."); |
| |
| return true; |
| } |
| |
| bool AppxEngine::sign(const QString &fileName) |
| { |
| Q_D(const AppxEngine); |
| BYTE buffer[256]; |
| DWORD bufferSize = 256; |
| |
| if (!CertStrToName(X509_ASN_ENCODING, wchar(d->publisherName), CERT_X500_NAME_STR, 0, buffer, &bufferSize, 0)) { |
| qCWarning(lcWinRtRunner) << "CertStrToName failed"; |
| return false; |
| } |
| CERT_NAME_BLOB certBlob; |
| certBlob.cbData = bufferSize; |
| certBlob.pbData = buffer; |
| |
| CRYPT_ALGORITHM_IDENTIFIER identifier; |
| identifier.pszObjId = strdup(szOID_RSA_SHA256RSA); |
| identifier.Parameters.cbData = 0; |
| identifier.Parameters.pbData = NULL; |
| |
| CERT_EXTENSIONS extensions; |
| extensions.cExtension = 2; |
| extensions.rgExtension = new CERT_EXTENSION[2]; |
| |
| // Basic Constraints |
| CERT_BASIC_CONSTRAINTS2_INFO constraintsInfo; |
| constraintsInfo.fCA = FALSE; |
| constraintsInfo.fPathLenConstraint = FALSE; |
| constraintsInfo.dwPathLenConstraint = 0; |
| |
| BYTE *constraintsEncoded = NULL; |
| DWORD encodedSize = 0; |
| CryptEncodeObject(X509_ASN_ENCODING, X509_BASIC_CONSTRAINTS2, &constraintsInfo, |
| constraintsEncoded, &encodedSize); |
| constraintsEncoded = new BYTE[encodedSize]; |
| if (!CryptEncodeObject(X509_ASN_ENCODING, X509_BASIC_CONSTRAINTS2, &constraintsInfo, |
| constraintsEncoded, &encodedSize)) { |
| qCWarning(lcWinRtRunner) << "Could not encode basic constraints."; |
| delete [] constraintsEncoded; |
| return false; |
| } |
| |
| extensions.rgExtension[0].pszObjId = strdup(szOID_BASIC_CONSTRAINTS2); |
| extensions.rgExtension[0].fCritical = TRUE; |
| extensions.rgExtension[0].Value.cbData = encodedSize; |
| extensions.rgExtension[0].Value.pbData = constraintsEncoded; |
| |
| // Code Signing |
| char *codeSign = strdup(szOID_PKIX_KP_CODE_SIGNING); |
| CERT_ENHKEY_USAGE enhancedUsage; |
| enhancedUsage.cUsageIdentifier = 1; |
| enhancedUsage.rgpszUsageIdentifier = &codeSign; |
| |
| BYTE *enhancedKeyEncoded = 0; |
| encodedSize = 0; |
| CryptEncodeObject(X509_ASN_ENCODING, X509_ENHANCED_KEY_USAGE, &enhancedUsage, |
| enhancedKeyEncoded, &encodedSize); |
| enhancedKeyEncoded = new BYTE[encodedSize]; |
| if (!CryptEncodeObject(X509_ASN_ENCODING, X509_ENHANCED_KEY_USAGE, &enhancedUsage, |
| enhancedKeyEncoded, &encodedSize)) { |
| qCWarning(lcWinRtRunner) << "Could not encode enhanced key usage."; |
| delete [] constraintsEncoded; |
| return false; |
| } |
| |
| extensions.rgExtension[1].pszObjId = strdup(szOID_ENHANCED_KEY_USAGE); |
| extensions.rgExtension[1].fCritical = TRUE; |
| extensions.rgExtension[1].Value.cbData = encodedSize; |
| extensions.rgExtension[1].Value.pbData = enhancedKeyEncoded; |
| |
| PCCERT_CONTEXT context = CertCreateSelfSignCertificate(NULL, &certBlob, NULL, NULL, |
| &identifier, NULL, NULL, &extensions); |
| |
| delete [] constraintsEncoded; |
| |
| if (!context) { |
| qCWarning(lcWinRtRunner) << "Failed to create self sign certificate:" << GetLastError(); |
| return false; |
| } |
| |
| return signAppxPackage(context, wchar(fileName)); |
| } |