This repository has been archived by the owner on Sep 21, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
333 additions
and
140 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { ManifestDetectionResult, ServiceWorkerDetectionResult, SiteData } from "../interfaces/validation"; | ||
import { getTabId } from "../utils/chromium"; | ||
|
||
let manifestInfoPromise: Promise<ManifestDetectionResult>; | ||
const messageType = 'manifestMessage'; | ||
|
||
export function getManifestInfo() : Promise<ManifestDetectionResult> { | ||
if (manifestInfoPromise) { | ||
return manifestInfoPromise; | ||
} | ||
|
||
manifestInfoPromise = new Promise(async (resolve, reject) => { | ||
const listener = (message: any, sender: any, sendResponse: any) => { | ||
if (message.type && message.type === messageType) { | ||
// we finally have all the info we need for the service worker | ||
resolve(message.manifestInfo); | ||
chrome.runtime.onMessage.removeListener(listener); | ||
sendResponse(true); | ||
} | ||
} | ||
|
||
chrome.runtime.onMessage.addListener(listener) | ||
|
||
const tabId = await getTabId(); | ||
if (tabId) { | ||
await chrome.scripting.executeScript({ | ||
target: {tabId}, | ||
func: runContentScriptIsolated, | ||
args: [messageType] | ||
}); | ||
} else { | ||
reject("No active tab found"); | ||
} | ||
}); | ||
|
||
return manifestInfoPromise; | ||
} | ||
|
||
// running code isolated so we can use messaging back to extension | ||
// should be run first to setup window message listener for messages from other content script | ||
async function runContentScriptIsolated(messageType: string) { | ||
|
||
const manifestInfo : ManifestDetectionResult = { | ||
hasManifest: false | ||
} | ||
|
||
const link = document.querySelector('link[rel="manifest"]') as HTMLLinkElement; | ||
if (link) { | ||
const manifestUri = link.href; | ||
|
||
const manifestResponse = await fetch(manifestUri, { credentials: "include" }) | ||
const manifest = await manifestResponse.json(); | ||
|
||
manifestInfo.hasManifest = true; | ||
manifestInfo.manifestUri = manifestUri; | ||
manifestInfo.manifest = manifest; | ||
} | ||
|
||
chrome.runtime.sendMessage({type: messageType, manifestInfo}); | ||
return true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { api } from "../endpoints"; | ||
import { TestResult } from "../interfaces/manifest"; | ||
import { SecurityDataResults } from "../interfaces/validation"; | ||
|
||
const default_results = [ | ||
{ | ||
result: false, | ||
infoString: "Uses HTTPS", | ||
category: "required", | ||
}, | ||
{ | ||
result: false, | ||
infoString: "Has a valid SSL certificate", | ||
category: "required", | ||
}, | ||
{ | ||
result: false, | ||
infoString: "No mixed content on page", | ||
category: "required", | ||
}, | ||
]; | ||
|
||
export async function testSecurity(url: string): Promise<Array<TestResult>> { | ||
// We've witnessed this call take a very long time. We're going to time-box it to 20s. | ||
const twentySecondTimeout = new Promise<void>((resolve) => | ||
setTimeout(() => resolve(), 20000) | ||
); | ||
|
||
const encodedUrl = encodeURIComponent(url); | ||
const securityUrl = `${api}/Security?site=${encodedUrl}`; | ||
const fetchResult = fetch(securityUrl); | ||
|
||
const fetchResultOrTimeout: void | Response = await Promise.race([ | ||
twentySecondTimeout, | ||
fetchResult, | ||
]); | ||
|
||
if (!fetchResultOrTimeout) { | ||
console.warn("Security check timed out after 20 seconds."); | ||
return default_results; | ||
} | ||
|
||
if (!fetchResultOrTimeout.ok) { | ||
console.warn( | ||
"Security check failed with non-successful status code", | ||
fetchResultOrTimeout.status, | ||
fetchResultOrTimeout.statusText | ||
); | ||
return default_results; | ||
} | ||
|
||
// we have made it through, yay! | ||
const results: { data: SecurityDataResults } = | ||
await fetchResultOrTimeout.json(); | ||
console.info("Security detection completed successfully", results); | ||
|
||
const organizedResults = [ | ||
{ | ||
result: results.data.isHTTPS, | ||
infoString: "Uses HTTPS", | ||
category: "required", | ||
}, | ||
{ | ||
result: results.data.valid, | ||
infoString: "Has a valid SSL certificate", | ||
category: "required", | ||
}, | ||
{ | ||
result: results.data.validProtocol, | ||
infoString: "No mixed content on page", | ||
category: "required", | ||
}, | ||
]; | ||
|
||
return organizedResults; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import { ManifestDetectionResult, ServiceWorkerDetectionResult, SiteData } from "../interfaces/validation"; | ||
import { getTabId } from "../utils/chromium"; | ||
|
||
let getServiceWorkerInfoPromise: Promise<SiteData>; | ||
const messageType = 'serviceWorkerMessage'; | ||
|
||
export function getSwInfo() : Promise<SiteData> { | ||
if (getServiceWorkerInfoPromise) { | ||
return getServiceWorkerInfoPromise; | ||
} | ||
|
||
getServiceWorkerInfoPromise = new Promise(async (resolve, reject) => { | ||
const listener = (message: any, sender: any, sendResponse: any) => { | ||
if (message.type && message.type === messageType) { | ||
// we finally have all the info we need for the service worker | ||
resolve(message.serviceWorkerRegistration); | ||
chrome.runtime.onMessage.removeListener(listener); | ||
sendResponse(true); | ||
} | ||
} | ||
|
||
chrome.runtime.onMessage.addListener(listener) | ||
|
||
const tabId = await getTabId(); | ||
if (tabId) { | ||
await chrome.scripting.executeScript({ | ||
target: {tabId}, | ||
func: runContentScriptIsolated, | ||
args: [messageType] | ||
}); | ||
|
||
await chrome.scripting.executeScript({ | ||
target: {tabId}, | ||
func: runContentScriptOnPage, | ||
world: "MAIN", | ||
args: [messageType] | ||
}); | ||
} else { | ||
reject("No active tab found"); | ||
} | ||
}); | ||
|
||
return getServiceWorkerInfoPromise; | ||
} | ||
|
||
|
||
// running code on page allowing us to get the service worker registration | ||
async function runContentScriptOnPage(messageType: string) { | ||
|
||
const timeoutPromise = new Promise((resolve, reject) => { | ||
setTimeout(resolve, 10000); | ||
}) | ||
|
||
|
||
const result = await Promise.any([timeoutPromise, navigator.serviceWorker.ready]); | ||
|
||
const swInfo : ServiceWorkerDetectionResult = { | ||
hasSW: false | ||
} | ||
|
||
if (result) { | ||
const registration = result as ServiceWorkerRegistration; | ||
swInfo.hasSW = true; | ||
swInfo.url = registration.active?.scriptURL; | ||
swInfo.scope = registration.scope; | ||
swInfo.isHtmlInCache = false; | ||
|
||
async function fetchSwCode(uri: string) { | ||
const response = await fetch(uri, { credentials: "include" }); | ||
const text = await response.text(); | ||
return text; | ||
} | ||
|
||
async function isHtmlInCache() { | ||
const cacheNames = await caches.keys(); | ||
for (let cacheName of cacheNames) { | ||
const cache = await caches.open(cacheName); | ||
const cacheKeys = await cache.keys(); | ||
for (let key of cacheKeys) { | ||
const cachedObject = await cache.match(key); | ||
if (cachedObject && cachedObject.headers) { | ||
const contentType = cachedObject.headers.get('Content-Type'); | ||
if (contentType && contentType.startsWith('text/html')) { | ||
return true; | ||
} | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
function hasSwEventSubscription(eventName: string, eventMethodName: string, testString: string) { | ||
const addEventListenerRegEx = `(\\n\\s*|.)addEventListener\\(['|\\"]${eventName}['|\\"]`; | ||
const methodnameRegEx = `(\\n\\s*|.)${eventName}\\s*=`; | ||
|
||
return (new RegExp(`(${addEventListenerRegEx}|${methodnameRegEx})`)).test(testString); | ||
} | ||
|
||
const results = await Promise.all([fetchSwCode(registration.active?.scriptURL!), isHtmlInCache()]); | ||
swInfo.rawSW = results[0]; | ||
swInfo.isHtmlInCache = results[1]; | ||
|
||
const pushSubscription = await registration.pushManager.getSubscription(); | ||
swInfo.hasPushRegistration = pushSubscription !== null || hasSwEventSubscription("push", "onpush", swInfo.rawSW); | ||
|
||
try { | ||
const periodicSyncTags : [] = await (registration as any).periodicSync.getTags(); | ||
swInfo.hasPeriodicBackgroundSync = periodicSyncTags.length > 0 || hasSwEventSubscription("periodicsync", "onperiodicsync", swInfo.rawSW); | ||
} catch { | ||
swInfo.hasPeriodicBackgroundSync = hasSwEventSubscription("periodicsync", "onperiodicsync", swInfo.rawSW); | ||
} | ||
|
||
try { | ||
const syncTags : [] = await (registration as any).sync.getTags(); | ||
swInfo.hasBackgroundSync = syncTags.length > 0 || hasSwEventSubscription("sync", "onsync", swInfo.rawSW); | ||
} catch { | ||
swInfo.hasBackgroundSync = hasSwEventSubscription("sync", "onsync", swInfo.rawSW); | ||
} | ||
|
||
} | ||
|
||
window.postMessage({type: messageType, serviceWorkerRegistration: swInfo}, "*"); | ||
} | ||
|
||
// running code isolated so we can use messaging back to extension | ||
// should be run first to setup window message listener for messages from other content script | ||
function runContentScriptIsolated(messageType: string) { | ||
const callback = (event: MessageEvent) => { | ||
console.log('got message there', event.data); | ||
if (event.source !== window) { | ||
return; | ||
} | ||
|
||
if (event.data.type && event.data.type === messageType) { | ||
window.removeEventListener("message", callback); | ||
console.log('here', event.data.serviceWorkerRegistration) | ||
chrome.runtime.sendMessage<ServiceWorkerDetectionResult>(event.data); | ||
} | ||
}; | ||
|
||
window.addEventListener("message", callback) | ||
return true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.