Skip to content

Commit

Permalink
Implemented custom caching of obfuscated dlls and disabling Blazor's …
Browse files Browse the repository at this point in the history
…default
  • Loading branch information
stavroskasidis committed Apr 27, 2022
1 parent 81bbbc7 commit c10e15f
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
$(RestoreAdditionalProjectSources);
$(MSBuildThisFileDirectory)../../../artifacts/nuget
</RestoreAdditionalProjectSources>
<!--<BlazorCacheBootResources>false</BlazorCacheBootResources>-->

<!--<ObfuscationMode>Xor</ObfuscationMode>
<XorKey>{903148E6-E3DC-4DED-A887-0061A8028C1C}</XorKey>-->
Expand All @@ -17,7 +18,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.1" PrivateAssets="all" />
<PackageReference Include="BlazorWasmAntivirusProtection" Version="1.6.0-beta" />
<PackageReference Include="BlazorWasmAntivirusProtection" Version="1.7.0-test" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.1" />
<PackageReference Include="BlazorWasmAntivirusProtection" Version="1.6.0-beta" />
<PackageReference Include="BlazorWasmAntivirusProtection" Version="1.7.0-test" />
</ItemGroup>

<ItemGroup>
Expand Down
3 changes: 0 additions & 3 deletions scripts/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ Write-Host "Parameters"
Write-Host "=========="
Write-Host "Version suffix: $VersionSuffix"

Write-Message "Removing existing package from nuget cache ..."
Remove-Item -Recurse -Force $env:USERPROFILE/.nuget/packages/BlazorWasmAntivirusProtection -ErrorAction SilentlyContinue;

Write-Message "Building ..."
dotnet build ../src/BlazorWasmAntivirusProtection.Tasks/BlazorWasmAntivirusProtection.Tasks.csproj -c Release
dotnet build ../src/BlazorWasmAntivirusProtection/BlazorWasmAntivirusProtection.csproj -c Release /p:VersionSuffix="$VersionSuffix"
Expand Down
50 changes: 50 additions & 0 deletions scripts/debug.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Push-Location $PSScriptRoot

function Write-Message{
param([string]$message)
Write-Host
Write-Host $message
Write-Host
}
function Confirm-PreviousCommand {
param([string]$errorMessage)
if ( $LASTEXITCODE -ne 0) {
if( $errorMessage) {
Write-Message $errorMessage
}
exit $LASTEXITCODE
}
}

function Confirm-Process {
param ([System.Diagnostics.Process]$process,[string]$errorMessage)
$process.WaitForExit()
if($process.ExitCode -ne 0){
Write-Host $process.ExitCode
if( $errorMessage) {
Write-Message $errorMessage
}
exit $process.ExitCode
}
}

Write-Message "Removing existing package from nuget cache ..."
Remove-Item -Recurse -Force $env:USERPROFILE/.nuget/packages/BlazorWasmAntivirusProtection -ErrorAction SilentlyContinue;

Write-Message "Kill running dotnet processes"
Get-Process "dotnet" | Stop-Process


Write-Message "Building ..."
dotnet build ../src/BlazorWasmAntivirusProtection.Tasks/BlazorWasmAntivirusProtection.Tasks.csproj -c Debug
dotnet build ../src/BlazorWasmAntivirusProtection/BlazorWasmAntivirusProtection.csproj -c Debug /p:VersionSuffix="test"
Confirm-PreviousCommand

Write-Message "Creating nuget package ..."
dotnet pack ../src/BlazorWasmAntivirusProtection/BlazorWasmAntivirusProtection.csproj -c Debug /p:VersionSuffix="test" -o ../artifacts/nuget
Confirm-PreviousCommand

Write-Message "Build completed successfully"

dotnet clean ../sampleapps/BlazorHostedSampleApp/Server/BlazorHostedSampleApp.Server.csproj -c Release
dotnet publish ../sampleapps/BlazorHostedSampleApp/Server/BlazorHostedSampleApp.Server.csproj -c Release
8 changes: 7 additions & 1 deletion src/BlazorWasmAntivirusProtection.Tasks/ObfuscateDlls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ public class ObfuscateDlls : Task
{
[Required]
public ITaskItem[] PublishBlazorBootStaticWebAsset { get; set; }

[Required]
public string SettingsPath { get; set; }

public string OriginalBlazorCacheBootResources { get; set; }

public string ObfuscationMode { get; set; } = Tasks.ObfuscationMode.Xor.ToString();

public string XorKey { get; set; } = "antiviruses suck!";

[Output]
Expand Down Expand Up @@ -68,7 +73,8 @@ public override bool Execute()
var settings = JsonSerializer.Serialize(new
{
obfuscationMode = obfuscationMode,
xorKey = XorKey
xorKey = XorKey,
cacheBootResources = OriginalBlazorCacheBootResources
});
File.WriteAllText(SettingsPath, settings);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PackageProjectUrl>https://github.com/stavroskasidis/BlazorWasmAntivirusProtection</PackageProjectUrl>
<Description>This package attempts to guard against false positives from antiviruses that flag Blazor Wasm as malware</Description>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<Version>1.6.0</Version>
<Version>1.7.0</Version>
<Version Condition=" '$(VersionSuffix)' != '' ">$(Version)-$(VersionSuffix)</Version>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@

<!-- Runs in the client project -->
<Target Name="_CleanOldDlls" BeforeTargets="PrepareForILLink">
<PropertyGroup>
<OriginalBlazorCacheBootResources>$(BlazorCacheBootResources)</OriginalBlazorCacheBootResources>
<BlazorCacheBootResources>false</BlazorCacheBootResources>
</PropertyGroup>
<CleanOldDlls IntermediateLinkDir="$(IntermediateLinkDir)"></CleanOldDlls>
</Target>

<!-- Runs in the client project -->
<Target Name="_ObfuscateDlls">

<ObfuscateDlls PublishBlazorBootStaticWebAsset="@(PublishBlazorBootStaticWebAsset)"
SettingsPath="$(IntermediateOutputPath)avp-settings.json"
OriginalBlazorCacheBootResources="$(OriginalBlazorCacheBootResources)"
ObfuscationMode="$(ObfuscationMode)"
XorKey="$(XorKey)">
<Output TaskParameter="Extension" ItemName="BlazorPublishExtension"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@

var usedCacheKeys = {};
var cacheIfUsed;

export async function afterStarted(blazor) {
purgeUnusedCacheEntriesAsync();
}


export async function beforeStart(wasmOptions, extensions) {
if (!extensions || !extensions.avpsettings) {
return;
Expand All @@ -6,8 +15,8 @@ export async function beforeStart(wasmOptions, extensions) {
try {
const integrity = extensions.avpsettings['avp-settings.json'];
const settingsResponse = await fetch('avp-settings.json', { integrity: integrity, cache: 'no-cache' });
const settings = await settingsResponse.json();

var settings = await settingsResponse.json();
cacheIfUsed = await getCacheToUseIfEnabled(settings);
//This is to support custom Blazor.start with a custom loadBootResource
var existingLoadBootResouce = wasmOptions.loadBootResource;

Expand All @@ -16,33 +25,27 @@ export async function beforeStart(wasmOptions, extensions) {
if (existingLoadBootResouce) {
existingLoaderResponse = existingLoadBootResouce(type, name, defaultUri, integrity);
}
if (type != "assembly") {
if (existingLoaderResponse) {
return existingLoaderResponse;
}
else {
return defaultUri;
}
}
//if (type != "assembly") {
// if (existingLoaderResponse) {
// return existingLoaderResponse;
// }
// else {
// return defaultUri;
// }
//}


var fetchPromise = null;
if (existingLoaderResponse) {
if (typeof existingLoaderResponse == "string") {
fetchPromise = fetch(existingLoaderResponse, {
cache: 'no-cache',
integrity: integrity,
});
fetchPromise = fetchOrGetFromCache(existingLoaderResponse, integrity);
}
else {
fetchPromise = existingLoaderResponse;
}
}
else {
fetchPromise = fetch(defaultUri, {
cache: 'no-cache',
integrity: integrity,
});
fetchPromise = fetchOrGetFromCache(defaultUri, integrity);
}

var resp = fetchPromise.then(response => {
Expand All @@ -51,15 +54,17 @@ export async function beforeStart(wasmOptions, extensions) {
});
}).then(responseResult => {
var data = new Uint8Array(responseResult.buffer);
if (settings.obfuscationMode == 1) {//Changed Headers
console.log("Restoring binary header: " + name);
data[0] = 77; //This restores header from BZ to MZ
}
else if (settings.obfuscationMode == 2) { //Xored dll
console.log("Restoring binary file Xor: " + name);
var key = settings.xorKey;
for (let i = 0; i < data.length; i++)
data[i] = data[i] ^ key.charCodeAt(i % key.length); //This reverses the Xor'ing of the dll
if (type == "assembly") {
if (settings.obfuscationMode == 1) {//Changed Headers
console.log("Restoring binary header: " + name);
data[0] = 77; //This restores header from BZ to MZ
}
else if (settings.obfuscationMode == 2) { //Xored dll
console.log("Restoring binary file Xor: " + name);
var key = settings.xorKey;
for (let i = 0; i < data.length; i++)
data[i] = data[i] ^ key.charCodeAt(i % key.length); //This reverses the Xor'ing of the dll
}
}
var resp = new Response(data, { "status": 200, headers: responseResult.headers });
return resp;
Expand All @@ -72,7 +77,114 @@ export async function beforeStart(wasmOptions, extensions) {
} catch (error) {
console.log(error);
}
}

export async function afterStarted(blazor) {

async function fetchOrGetFromCache(url, contentHash) {
const response = cacheIfUsed
? loadResourceWithCaching(cacheIfUsed, url, contentHash)
: loadResourceWithoutCaching(url,contentHash);

return response;
}


// DISCLAIMER:
// ======================
// Most of the code below is copied from https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web.JS/src/Platform/WebAssemblyResourceLoader.ts
// Blazor's default caching is disabled and a custom caching of obfuscated dlls is being used.
// This is because by default blazor caches the unobfuscated dlls and some antiviruses flag the cached files that are being stored on the disk by the browser.
// ======================

const networkFetchCacheMode = 'no-cache';

async function loadResourceWithCaching(cache, url, contentHash) {
// Since we are going to cache the response, we require there to be a content hash for integrity
// checking. We don't want to cache bad responses. There should always be a hash, because the build
// process generates this data.
if (!contentHash || contentHash.length === 0) {
throw new Error('Content hash is required');
}

const cacheKey = toAbsoluteUri(`${url}.${contentHash}`);
usedCacheKeys[cacheKey] = true;

let cachedResponse;
try {
cachedResponse = await cache.match(cacheKey);
} catch {
// Be tolerant to errors reading from the cache. This is a guard for https://bugs.chromium.org/p/chromium/issues/detail?id=968444 where
// chromium browsers may sometimes throw when working with the cache.
}

if (cachedResponse) {
// It's in the cache.

//const responseBytes = parseInt(cachedResponse.headers.get('content-length') || '0');
//cacheLoads[name] = { responseBytes };
return cachedResponse;
} else {
// It's not in the cache. Fetch from network.
const networkResponse = await loadResourceWithoutCaching(url, contentHash);
cache.put(cacheKey, networkResponse);
return networkResponse;
}
}

async function loadResourceWithoutCaching(url, contentHash) {
return fetch(url, {
cache: networkFetchCacheMode,
integrity: settings.cacheBootResources ? contentHash : undefined,
});
}

async function getCacheToUseIfEnabled(bootConfig) {
// caches will be undefined if we're running on an insecure origin (secure means https or localhost)
if (!bootConfig.cacheBootResources || typeof caches === 'undefined') {
return null;
}

// cache integrity is compromised if the first request has been served over http (except localhost)
// in this case, we want to disable caching and integrity validation
if (window.isSecureContext === false) {
return null;
}

// Define a separate cache for each base href, so we're isolated from any other
// Blazor application running on the same origin. We need this so that we're free
// to purge from the cache anything we're not using and don't let it keep growing,
// since we don't want to be worst offenders for space usage.
const relativeBaseHref = document.baseURI.substring(document.location.origin.length);
const cacheName = `blazor-resources-${relativeBaseHref}`;

try {
// There's a Chromium bug we need to be aware of here: the CacheStorage APIs say that when
// caches.open(name) returns a promise that succeeds, the value is meant to be a Cache instance.
// However, if the browser was launched with a --user-data-dir param that's "too long" in some sense,
// then even through the promise resolves as success, the value given is `undefined`.
// See https://stackoverflow.com/a/46626574 and https://bugs.chromium.org/p/chromium/issues/detail?id=1054541
// If we see this happening, return "null" to mean "proceed without caching".
return (await caches.open(cacheName)) || null;
} catch {
// There's no known scenario where we should get an exception here, but considering the
// Chromium bug above, let's tolerate it and treat as "proceed without caching".
return null;
}
}

}

async function purgeUnusedCacheEntriesAsync() {
// We want to keep the cache small because, even though the browser will evict entries if it
// gets too big, we don't want to be considered problematic by the end user viewing storage stats
const cache = cacheIfUsed;
if (cache) {
const cachedRequests = await cache.keys();
const deletionPromises = cachedRequests.map(async cachedRequest => {
if (!(cachedRequest.url in usedCacheKeys)) {
await cache.delete(cachedRequest);
}
});

await Promise.all(deletionPromises);
}
}

0 comments on commit c10e15f

Please sign in to comment.