@freedomofpress/webcat 0.0.0-stage → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +45 -0
- package/data/block.json +101 -0
- package/data/list.json +80 -0
- package/dist/hooks/content.js +1 -0
- package/dist/lib/browser/chrome.d.ts +86 -0
- package/dist/lib/browser/chrome.js +138 -0
- package/dist/lib/browser/kvstore.d.ts +70 -0
- package/dist/lib/browser/kvstore.js +241 -0
- package/dist/lib/browser/permissions.d.ts +113 -0
- package/dist/lib/browser/permissions.js +182 -0
- package/dist/lib/browser/requests.d.ts +145 -0
- package/dist/lib/browser/requests.js +292 -0
- package/dist/lib/browser/scripting.d.ts +19 -0
- package/dist/lib/browser/scripting.js +92 -0
- package/dist/lib/browser/sync.d.ts +68 -0
- package/dist/lib/browser/sync.js +114 -0
- package/dist/lib/browser/utils.d.ts +5 -0
- package/dist/lib/browser/utils.js +11 -0
- package/dist/lib/config.d.ts +45 -0
- package/dist/lib/config.js +45 -0
- package/dist/lib/validator_set.d.ts +4 -0
- package/dist/lib/validator_set.js +20 -0
- package/dist/lib/webcat/cache.d.ts +200 -0
- package/dist/lib/webcat/cache.js +308 -0
- package/dist/lib/webcat/canonicalize.d.ts +1 -0
- package/dist/lib/webcat/canonicalize.js +58 -0
- package/dist/lib/webcat/db.d.ts +30 -0
- package/dist/lib/webcat/db.js +131 -0
- package/dist/lib/webcat/encoding.d.ts +8 -0
- package/dist/lib/webcat/encoding.js +57 -0
- package/dist/lib/webcat/handler.d.ts +26 -0
- package/dist/lib/webcat/handler.js +419 -0
- package/dist/lib/webcat/hookbuilder.d.ts +47 -0
- package/dist/lib/webcat/hookbuilder.js +170 -0
- package/dist/lib/webcat/interfaces/bundle.d.ts +46 -0
- package/dist/lib/webcat/interfaces/bundle.js +8 -0
- package/dist/lib/webcat/interfaces/database.d.ts +68 -0
- package/dist/lib/webcat/interfaces/database.js +1 -0
- package/dist/lib/webcat/interfaces/errors.d.ts +451 -0
- package/dist/lib/webcat/interfaces/errors.js +109 -0
- package/dist/lib/webcat/interfaces/log.d.ts +14 -0
- package/dist/lib/webcat/interfaces/log.js +10 -0
- package/dist/lib/webcat/interfaces/originstate.d.ts +40 -0
- package/dist/lib/webcat/interfaces/originstate.js +1 -0
- package/dist/lib/webcat/interfaces/requeststate.d.ts +10 -0
- package/dist/lib/webcat/interfaces/requeststate.js +1 -0
- package/dist/lib/webcat/logger.d.ts +64 -0
- package/dist/lib/webcat/logger.js +112 -0
- package/dist/lib/webcat/originstate.d.ts +45 -0
- package/dist/lib/webcat/originstate.js +302 -0
- package/dist/lib/webcat/parsers.d.ts +21 -0
- package/dist/lib/webcat/parsers.js +36 -0
- package/dist/lib/webcat/request.d.ts +11 -0
- package/dist/lib/webcat/request.js +78 -0
- package/dist/lib/webcat/resources.d.ts +3 -0
- package/dist/lib/webcat/resources.js +41 -0
- package/dist/lib/webcat/response.d.ts +39 -0
- package/dist/lib/webcat/response.js +394 -0
- package/dist/lib/webcat/ui.d.ts +70 -0
- package/dist/lib/webcat/ui.js +282 -0
- package/dist/lib/webcat/updater.d.ts +115 -0
- package/dist/lib/webcat/updater.js +296 -0
- package/dist/lib/webcat/utils.d.ts +8 -0
- package/dist/lib/webcat/utils.js +74 -0
- package/dist/lib/webcat/validators.d.ts +16 -0
- package/dist/lib/webcat/validators.js +457 -0
- package/dist/lib/webcat.d.ts +11 -0
- package/dist/lib/webcat.js +130 -0
- package/icons/dark/webcat-error.SVG +1 -0
- package/icons/dark/webcat-ok.SVG +1 -0
- package/icons/dark/webcat.SVG +1 -0
- package/icons/light/webcat-error.SVG +1 -0
- package/icons/light/webcat-ok.SVG +1 -0
- package/icons/light/webcat.SVG +1 -0
- package/package.json +72 -4
- package/pages/error.css +9 -0
- package/pages/error.html +70 -0
- package/pages/error.js +51 -0
- package/pages/popup.css +16 -0
- package/pages/popup.html +14 -0
- package/pages/popup.js +48 -0
- package/README.md +0 -3
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { KVStore } from "../browser/kvstore";
|
|
2
|
+
/** @internal @inline */
|
|
3
|
+
export type HookBuilderConfig = {
|
|
4
|
+
/**
|
|
5
|
+
* The path to the static hook content script file, relative to the
|
|
6
|
+
* extension root directory.
|
|
7
|
+
*/
|
|
8
|
+
staticHookPath: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Builds hooks using unique cryptographic keys.
|
|
12
|
+
*/
|
|
13
|
+
export declare class HookBuilder {
|
|
14
|
+
#private;
|
|
15
|
+
/**
|
|
16
|
+
* @param store A key-value store for persisting the builder's cryptographic
|
|
17
|
+
* key and salt.
|
|
18
|
+
*/
|
|
19
|
+
constructor(store: KVStore, config: HookBuilderConfig);
|
|
20
|
+
/**
|
|
21
|
+
* @returns The path to the static content script file.
|
|
22
|
+
*/
|
|
23
|
+
getStaticHookPath(): string;
|
|
24
|
+
/**
|
|
25
|
+
* @param wasm An array of hashes to validate WASM modules against.
|
|
26
|
+
* @param firstParty The first-party origin of the associated page.
|
|
27
|
+
* @param sameOrigin true if the target document is same-origin with the
|
|
28
|
+
* first party.
|
|
29
|
+
* @returns A Promise that resolves to hook code ready to be injected
|
|
30
|
+
* directly to a script file.
|
|
31
|
+
*/
|
|
32
|
+
getPageHooks(wasm: string[], firstParty: string, sameOrigin: boolean): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* @param wasm An array of hashes to validate WASM modules against.
|
|
35
|
+
* @param firstParty The first-party origin of the associated page.
|
|
36
|
+
* @param sameOrigin true if the target document is same-origin with the
|
|
37
|
+
* first party.
|
|
38
|
+
* @returns A Promise that resolves to hook code ready to be included in a
|
|
39
|
+
* content script.
|
|
40
|
+
*/
|
|
41
|
+
getContentScriptHooks(wasm: string[], firstParty: string, sameOrigin: boolean): Promise<[func: (data: string) => void, args: [data: string]]>;
|
|
42
|
+
/**
|
|
43
|
+
* @param url A URL with an encrypted fragment added via a hook.
|
|
44
|
+
* @returns The decrypted fragment.
|
|
45
|
+
*/
|
|
46
|
+
decryptFragment(url: string): Promise<string>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
var __typeError = (msg) => {
|
|
2
|
+
throw TypeError(msg);
|
|
3
|
+
};
|
|
4
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
5
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
6
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
7
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
8
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
9
|
+
var _staticHookPath, _firstPartyKey, _firstPartySalt, _HookBuilder_instances, get_fn;
|
|
10
|
+
const pageHooks = '(function(){"use strict";const o=Symbol("WEBCAT internal"),H=Symbol("WEBCAT hooked"),s=globalThis.self||globalThis,S=Reflect.apply,U=typeof globalThis.exportFunction=="function";function n(r){return U?XPCNativeWrapper.unwrap(r):r}function b(r,e=s,t=void 0){if(U){const i={};return t!==void 0&&(i.defineAs=t),exportFunction(r,XPCNativeWrapper.unwrap(e),i)}else t!==void 0&&(e[t]=r);return r}function R(r){const e=n(new s.Object);for(const t of Object.keys(r))e[t]=r[t];return e.listeners=new Map,e}function T(r,e){return function(t,i){const c=t;if(c.hooks||(c.hooks={}),r in c.hooks){console.log(`[WEBCAT] Hook already injected: ${r}`),c.hooks[r].data=i;return}const{promise:l,resolve:a}=s.Promise.withResolvers();c.hooks[r]={ready:l,data:i},Object.defineProperty(c.hooks[r],"data",{set:d=>{i=d,a()},get:()=>i}),typeof i=="object"&&(c.hooks[r].data=i),e(l.then(()=>i),c.hooks[r])}}function $(r,e,t){return r.has(e)?r.get(e):(r.set(e,t),t)}function B(r){const e=n(r);return e&&(typeof e=="object"||typeof e=="function")?e:{capture:!!e}}function _(r,e){return b((...t)=>S(r,e,t))}function q(r,e,t,i){const c=n(e[1]),{once:l}=B(e[2]);return e[1]=b((...a)=>{l&&t[i]===e&&delete t[i];const d=typeof c=="function"?c:c?.handleEvent;return d&&S(d,r,a)}),e}function C(r,e){const{get:t,set:i}=Object.getOwnPropertyDescriptor(r,e);function c(){return o in n(this)?n(this)[o][e]:t&&S(t,this,[])}function l(a){o in n(this)?(n(this)[o][e]=a,n(this)[o].instance&&(n(this)[o].instance[e]=typeof a=="function"?_(a,n(this)):null)):i&&S(i,this,[a])}Object.defineProperty(r,e,{get:b(c),set:b(l)})}function x(r){const e=Object.keys(r[o]).filter(t=>t.startsWith("on"));for(const t of e){const i=r[o][t];r[o].instance[t]=typeof i=="function"?_(i,r):null}for(const t of r[o].listeners.values())for(const{nonCapturingArgs:i,capturingArgs:c}of t.values())i&&r[o].instance.addEventListener(...i),c&&r[o].instance.addEventListener(...c)}const z=T("EventTarget",function(){const{value:r}=Object.getOwnPropertyDescriptor(n(s.EventTarget.prototype),"addEventListener");function e(...a){const[d,m,u]=a;if(this&&o in n(this)){const{capture:f,once:v,passive:k,signal:p}=B(u),h=n(new s.Object);h.capture=!!f,h.once=!!v,h.passive=k===void 0?void 0:!!k,h.signal=p;const W=$($(n(this)[o].listeners,d,new Map),m,{}),P=f?"capturingArgs":"nonCapturingArgs",w=W[P];if(!w||B(w[2]).signal?.aborted){const L=q(n(this),[d,m,h],W,P);W[P]=L,n(this)[o].instance&&n(this)[o].instance.addEventListener(...L)}}else S(r,this,a)}Object.defineProperty(n(s.EventTarget.prototype),"addEventListener",{value:b(e)});const{value:t}=Object.getOwnPropertyDescriptor(n(s.EventTarget.prototype),"removeEventListener");function i(...a){const[d,m,u]=a;if(this&&o in n(this)){const f=n(this)[o].listeners.get(d)?.get(m),v=B(u).capture?"capturingArgs":"nonCapturingArgs";f?.[v]&&(n(this)[o].instance&&n(this)[o].instance.removeEventListener(...f[v]),delete f[v])}else S(t,this,a)}Object.defineProperty(n(s.EventTarget.prototype),"removeEventListener",{value:b(i)});const{value:c}=Object.getOwnPropertyDescriptor(n(s.EventTarget.prototype),"dispatchEvent");function l(...a){return this&&o in n(this)?n(this)[o].instance?n(this)[o].instance.dispatchEvent(...a):!0:S(c,this,a)}Object.defineProperty(n(s.EventTarget.prototype),"dispatchEvent",{value:b(l)})}),G=T("Event",function(){for(const t of["target","currentTarget","originalTarget","explicitOriginalTarget","srcElement"]){let i=function(){const l=n(S(c,this,[]));return l?.[H]??l};const{get:c}=Object.getOwnPropertyDescriptor(n(s.Event.prototype),t)??{};c&&Object.defineProperty(n(s.Event.prototype),t,{get:b(i)})}const{value:r}=Object.getOwnPropertyDescriptor(n(s.Event.prototype),"composedPath");function e(){const t=n(S(r,this,[]));for(let i=0;i<t.length;i++){const c=t[i];t[i]=c?.[H]??c}return t}Object.defineProperty(n(s.Event.prototype),"composedPath",{value:b(e)})}),N=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]);function I(r,e,t,i,c){let l,a,d,m,u,f,v,k,p,h,W,P,w;for(;c>=64;){for(l=e[0],a=e[1],d=e[2],m=e[3],u=e[4],f=e[5],v=e[6],k=e[7],h=0;h<16;h++)W=i+h*4,r[h]=(t[W]&255)<<24|(t[W+1]&255)<<16|(t[W+2]&255)<<8|t[W+3]&255;for(h=16;h<64;h++)p=r[h-2],P=(p>>>17|p<<15)^(p>>>19|p<<13)^p>>>10,p=r[h-15],w=(p>>>7|p<<25)^(p>>>18|p<<14)^p>>>3,r[h]=(P+r[h-7]|0)+(w+r[h-16]|0);for(h=0;h<64;h++)P=(((u>>>6|u<<26)^(u>>>11|u<<21)^(u>>>25|u<<7))+(u&f^~u&v)|0)+(k+(N[h]+r[h]|0)|0)|0,w=((l>>>2|l<<30)^(l>>>13|l<<19)^(l>>>22|l<<10))+(l&a^l&d^a&d)|0,k=v,v=f,f=u,u=m+P|0,m=d,d=a,a=l,l=P+w|0;e[0]+=l,e[1]+=a,e[2]+=d,e[3]+=m,e[4]+=u,e[5]+=f,e[6]+=v,e[7]+=k,i+=64,c-=64}return i}class V{constructor(){this.digestLength=32,this.blockSize=64,this.state=new Int32Array(8),this.temp=new Int32Array(64),this.buffer=new Uint8Array(128),this.bufferLength=0,this.bytesHashed=0,this.finished=!1,this.reset()}reset(){return this.state[0]=1779033703,this.state[1]=3144134277,this.state[2]=1013904242,this.state[3]=2773480762,this.state[4]=1359893119,this.state[5]=2600822924,this.state[6]=528734635,this.state[7]=1541459225,this.bufferLength=0,this.bytesHashed=0,this.finished=!1,this}clean(){for(let e=0;e<this.buffer.length;e++)this.buffer[e]=0;for(let e=0;e<this.temp.length;e++)this.temp[e]=0;this.reset()}update(e,t=e.length){if(this.finished)throw new Error("SHA256: can\'t update because hash was finished.");let i=0;if(this.bytesHashed+=t,this.bufferLength>0){for(;this.bufferLength<64&&t>0;)this.buffer[this.bufferLength++]=e[i++],t--;this.bufferLength===64&&(I(this.temp,this.state,this.buffer,0,64),this.bufferLength=0)}for(t>=64&&(i=I(this.temp,this.state,e,i,t),t%=64);t>0;)this.buffer[this.bufferLength++]=e[i++],t--;return this}finish(e){if(!this.finished){const t=this.bytesHashed,i=this.bufferLength,c=t/536870912|0,l=t<<3,a=t%64<56?64:128;this.buffer[i]=128;for(let d=i+1;d<a-8;d++)this.buffer[d]=0;this.buffer[a-8]=c>>>24&255,this.buffer[a-7]=c>>>16&255,this.buffer[a-6]=c>>>8&255,this.buffer[a-5]=c>>>0&255,this.buffer[a-4]=l>>>24&255,this.buffer[a-3]=l>>>16&255,this.buffer[a-2]=l>>>8&255,this.buffer[a-1]=l>>>0&255,I(this.temp,this.state,this.buffer,0,a),this.finished=!0}for(let t=0;t<8;t++)e[t*4+0]=this.state[t]>>>24&255,e[t*4+1]=this.state[t]>>>16&255,e[t*4+2]=this.state[t]>>>8&255,e[t*4+3]=this.state[t]>>>0&255;return this}digest(){const e=new Uint8Array(this.digestLength);return this.finish(e),e}_saveState(e){for(let t=0;t<this.state.length;t++)e[t]=this.state[t]}_restoreState(e,t){for(let i=0;i<this.state.length;i++)this.state[i]=e[i];this.bytesHashed=t,this.finished=!1,this.bufferLength=0}}function F(r){const e=new V().update(new Uint8Array(r)),t=e.digest();return e.clean(),t}const X=T("WASM",function(r,e){const t=n(s.WebAssembly);if(!t)return;function i(y){const E=y instanceof Uint8Array?y:new Uint8Array(y),g=new s.Object;return g.alphabet="base64url",g.omitPadding=!0,E.toBase64(g)}function c(y){return r.then(({hashes:E})=>{if(!("crypto"in globalThis))return Promise.resolve(l(y));const g=a(y);return crypto.subtle.digest("SHA-256",g).then(A=>{const O=i(A);if(!E?.includes(O))throw new s.Error(`[WEBCAT] Unauthorized WebAssembly bytecode: ${O}`);return console.log(`[WEBCAT] Verified WASM (async) ${O}`),g})})}function l(y){const E=a(y),g=i(F(E));if(!e.data.hashes?.includes(g))throw new s.Error(`[WEBCAT] Unauthorized WebAssembly bytecode: ${g}`);return console.log(`[WEBCAT] Verified WASM (sync) ${g}`),E}function a(y){return s.ArrayBuffer.isView(y)?y.buffer.slice():y.slice()}const d=t.instantiate;function m(y,E,g){return y instanceof t.Module?d.call(this,y,E,g):c(y).then(A=>d.call(this,A,E,g))}b(m,t,"instantiate");const u=t.compile;function f(y,E){return c(y).then(g=>u.call(this,g,E))}b(f,t,"compile");const v=t.validate;function k(y){const E=l(y);return v.call(this,E)}b(k,t,"validate");const p=t.instantiateStreaming;function h(y,E,g){return s.Promise.resolve(y).then(A=>Promise.all([A.clone().arrayBuffer(),A.headers])).then(([A,O])=>Promise.all([c(A),O])).then(([A,O])=>p.call(this,new Response(A,{headers:O}),E,g))}b(h,t,"instantiateStreaming");const W=t.compileStreaming;function P(y,E){return s.Promise.resolve(y).then(g=>Promise.all([g.clone().arrayBuffer(),g.headers])).then(([g,A])=>Promise.all([c(g),A])).then(([g,A])=>W.call(this,new Response(g,{headers:A}),E))}b(P,t,"compileStreaming");const w=t.Module;function L(y){if(!(this instanceof L))throw new s.TypeError("[WEBCAT] Constructor WebAssembly.Module requires \'new\'");const E=l(y);return new w(E)}const D=L;D.customSections=w.customSections.bind(w),D.exports=w.exports.bind(w),D.imports=w.imports.bind(w),b(D,t,"Module"),w.prototype.constructor=t.Module,t.Module.prototype=w.prototype,console.log("[WEBCAT] WebAssembly successfully hooked: all bytecode entry points now require authorization.")}),K=T("SharedWorker",function(r){if(!s.SharedWorker)return;const e=n(s.MessagePort),t=e.prototype.postMessage;function i(...k){if(o in n(this))if(n(this)[o].instance)t.call(n(this)[o].instance,...k);else{let p=k[1];if(p&&s.Symbol.iterator in p){const h=p;p=new s.Object,p.transfer=h}k[0]=s.structuredClone(k[0],p),n(this)[o].messages.push(k)}else t.call(this,...k)}b(i,e.prototype,"postMessage");const c=e.prototype.start;function l(){o in n(this)?n(this)[o].instance?c.call(n(this)[o].instance):n(this)[o].started=!0:c.call(this)}b(l,e.prototype,"start");const a=e.prototype.close;function d(){o in n(this)?n(this)[o].instance?a.call(n(this)[o].instance):n(this)[o].closed=!0:a.call(this)}b(d,e.prototype,"close"),C(e.prototype,"onmessage"),C(e.prototype,"onmessageerror");const m=n(s.SharedWorker);function u(...k){if(!(this instanceof u))throw new s.TypeError("SharedWorker constructor: \'new\' is required");if(k.length===0)throw new s.TypeError("SharedWorker constructor: At least 1 argument required, but only 0 passed");const p=n(s.Object).create(m.prototype),h=n(s.Object).create(e.prototype);return h[o]=R({onmessage:null,messages:[],onmessageerror:null,started:!1,closed:!1}),p[o]=R({port:h,onerror:null}),r.then(({firstParty:W})=>{k[0]=`${k[0]}#${W}`,p[o].instance=new m(...k),p[o].instance[H]=p,x(p),h[o].instance=p[o].instance.port,h[o].instance[H]=h,x(h),h[o].started&&h[o].instance.start(),h[o].closed&&h[o].instance.close(),h[o].messages.forEach(P=>{h[o].instance?.postMessage(...P)})}),p}b(u,s,"SharedWorker"),m.prototype.constructor=n(s.SharedWorker),n(s).SharedWorker.prototype=m.prototype;const{get:f}=Object.getOwnPropertyDescriptor(m.prototype,"port");function v(){return o in this?this[o].port:f?.apply(this)}Object.defineProperty(m.prototype,"port",{get:b(v,s)}),C(m.prototype,"onerror")}),J=T("ServiceWorker",function(r,{data:e}){if(typeof e=="string")try{Object.hasOwn(s.top||{},"name");return}catch{}else if(e===!0)return;delete n((s.Navigator||s.WorkerNavigator||s.Object).prototype).serviceWorker,delete n(s).ServiceWorker,delete n(s).ServiceWorkerContainer,delete n(s).ServiceWorkerRegistration}),Q=T("Worker",function(r){if(!s.SharedWorker)return;const e=n(s.Worker),t=n(s.EventTarget),i=n(s.Reflect).construct.bind(n(s.Reflect));function c(...u){if(!(this instanceof c))throw new TypeError("Worker constructor: \'new\' is required");if(u.length===0)throw new s.TypeError("Worker constructor: At least 1 argument required, but only 0 passed");const f=i(t,new s.Array,e);return f[o]=R({onmessage:null,messages:[],onerror:null}),r.then(({firstParty:v})=>{u[0]=`${u[0]}#${v}`,f[o].instance=new e(...u),f[o].instance[H]=f,f[o].terminated&&f[o].instance.terminate(),x(f),f[o].messages.forEach(k=>{f[o].instance?.postMessage(...k)})}),f}b(c,s,"Worker"),e.prototype.constructor=n(s.Worker),n(s).Worker.prototype=e.prototype;const l=e.prototype.postMessage;function a(...u){if(o in n(this))if(n(this)[o].instance)l.call(n(this)[o].instance,...u);else{let f=u[1];if(f&&s.Symbol.iterator in f){const v=f;f=new s.Object,f.transfer=v}u[0]=s.structuredClone(u[0],f),n(this)[o].messages.push(u)}else l.call(this,...u)}b(a,e.prototype,"postMessage");const d=e.prototype.terminate;function m(){o in n(this)?n(this)[o].instance?d.call(n(this)[o].instance):n(this)[o].terminated=!0:d.call(this)}b(m,e.prototype,"terminate"),C(e.prototype,"onmessage"),C(e.prototype,"onerror"),C(e.prototype,"onmessageerror")});console.log("[WEBCAT] Installing page hook");const j="__DATA_PLACEHOLDER__",M={};X(M,j),K(M,j),J(M,j),Q(M,j),z(M,j),G(M,j)})();\n';
|
|
11
|
+
const hooks = {
|
|
12
|
+
content_script: '"__DATA_PLACEHOLDER__"',
|
|
13
|
+
page: pageHooks
|
|
14
|
+
};
|
|
15
|
+
class HookBuilder {
|
|
16
|
+
/**
|
|
17
|
+
* @param store A key-value store for persisting the builder's cryptographic
|
|
18
|
+
* key and salt.
|
|
19
|
+
*/
|
|
20
|
+
constructor(store, config) {
|
|
21
|
+
__privateAdd(this, _HookBuilder_instances);
|
|
22
|
+
__privateAdd(this, _staticHookPath);
|
|
23
|
+
__privateAdd(this, _firstPartyKey);
|
|
24
|
+
__privateAdd(this, _firstPartySalt);
|
|
25
|
+
__privateSet(this, _staticHookPath, config.staticHookPath);
|
|
26
|
+
__privateSet(this, _firstPartyKey, store.get("firstPartyKey", "session").then(async (raw) => {
|
|
27
|
+
let key;
|
|
28
|
+
const algorithm = { name: "AES-GCM", length: 256 };
|
|
29
|
+
const extractable = true;
|
|
30
|
+
const usages = ["encrypt", "decrypt"];
|
|
31
|
+
if (!raw) {
|
|
32
|
+
key = await crypto.subtle.generateKey(algorithm, extractable, usages);
|
|
33
|
+
store.set(
|
|
34
|
+
{
|
|
35
|
+
firstPartyKey: await crypto.subtle.exportKey("raw", key)
|
|
36
|
+
},
|
|
37
|
+
"session"
|
|
38
|
+
);
|
|
39
|
+
} else {
|
|
40
|
+
key = await crypto.subtle.importKey(
|
|
41
|
+
"raw",
|
|
42
|
+
raw,
|
|
43
|
+
algorithm,
|
|
44
|
+
extractable,
|
|
45
|
+
usages
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return key;
|
|
49
|
+
}));
|
|
50
|
+
__privateSet(this, _firstPartySalt, store.get("firstPartySalt", "session").then((salt) => {
|
|
51
|
+
if (!salt) {
|
|
52
|
+
salt = crypto.getRandomValues(
|
|
53
|
+
new Uint8Array(256 / 8)
|
|
54
|
+
// SHA-256 length
|
|
55
|
+
);
|
|
56
|
+
store.set(
|
|
57
|
+
{
|
|
58
|
+
firstPartySalt: salt
|
|
59
|
+
},
|
|
60
|
+
"session"
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return salt;
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* @returns The path to the static content script file.
|
|
68
|
+
*/
|
|
69
|
+
getStaticHookPath() {
|
|
70
|
+
return __privateGet(this, _staticHookPath);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* @param wasm An array of hashes to validate WASM modules against.
|
|
74
|
+
* @param firstParty The first-party origin of the associated page.
|
|
75
|
+
* @param sameOrigin true if the target document is same-origin with the
|
|
76
|
+
* first party.
|
|
77
|
+
* @returns A Promise that resolves to hook code ready to be injected
|
|
78
|
+
* directly to a script file.
|
|
79
|
+
*/
|
|
80
|
+
async getPageHooks(wasm, firstParty, sameOrigin) {
|
|
81
|
+
return __privateMethod(this, _HookBuilder_instances, get_fn).call(this, "page", wasm, firstParty, sameOrigin);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* @param wasm An array of hashes to validate WASM modules against.
|
|
85
|
+
* @param firstParty The first-party origin of the associated page.
|
|
86
|
+
* @param sameOrigin true if the target document is same-origin with the
|
|
87
|
+
* first party.
|
|
88
|
+
* @returns A Promise that resolves to hook code ready to be included in a
|
|
89
|
+
* content script.
|
|
90
|
+
*/
|
|
91
|
+
async getContentScriptHooks(wasm, firstParty, sameOrigin) {
|
|
92
|
+
const data = await __privateMethod(this, _HookBuilder_instances, get_fn).call(this, "content_script", wasm, firstParty, sameOrigin);
|
|
93
|
+
return [
|
|
94
|
+
function(data2) {
|
|
95
|
+
const scope = window;
|
|
96
|
+
for (const name in scope.hooks) {
|
|
97
|
+
console.log(`[WEBCAT] Updating hook: ${name}`);
|
|
98
|
+
scope.hooks[name].data = JSON.parse(data2);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
[data]
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* @param url A URL with an encrypted fragment added via a hook.
|
|
106
|
+
* @returns The decrypted fragment.
|
|
107
|
+
*/
|
|
108
|
+
async decryptFragment(url) {
|
|
109
|
+
const markerIndex = url.lastIndexOf("#");
|
|
110
|
+
if (markerIndex === -1) {
|
|
111
|
+
throw new Error("no fragment present when decrypting");
|
|
112
|
+
}
|
|
113
|
+
const efpo = Uint8Array.fromBase64(url.substring(markerIndex + 1), {
|
|
114
|
+
alphabet: "base64url"
|
|
115
|
+
});
|
|
116
|
+
const fpo = await crypto.subtle.decrypt(
|
|
117
|
+
{
|
|
118
|
+
name: "AES-GCM",
|
|
119
|
+
iv: efpo.slice(0, 12)
|
|
120
|
+
},
|
|
121
|
+
await __privateGet(this, _firstPartyKey),
|
|
122
|
+
efpo.slice(12)
|
|
123
|
+
);
|
|
124
|
+
return new TextDecoder().decode(fpo);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
_staticHookPath = new WeakMap();
|
|
128
|
+
_firstPartyKey = new WeakMap();
|
|
129
|
+
_firstPartySalt = new WeakMap();
|
|
130
|
+
_HookBuilder_instances = new WeakSet();
|
|
131
|
+
get_fn = async function(type, wasm, firstParty, sameOrigin) {
|
|
132
|
+
const iv = await crypto.subtle.deriveBits(
|
|
133
|
+
{
|
|
134
|
+
name: "HKDF",
|
|
135
|
+
hash: "SHA-256",
|
|
136
|
+
salt: await __privateGet(this, _firstPartySalt),
|
|
137
|
+
info: new ArrayBuffer()
|
|
138
|
+
},
|
|
139
|
+
await crypto.subtle.importKey(
|
|
140
|
+
"raw",
|
|
141
|
+
new TextEncoder().encode(firstParty),
|
|
142
|
+
{ name: "HKDF" },
|
|
143
|
+
false,
|
|
144
|
+
["deriveBits"]
|
|
145
|
+
),
|
|
146
|
+
96
|
|
147
|
+
);
|
|
148
|
+
const ct = new Uint8Array(
|
|
149
|
+
await crypto.subtle.encrypt(
|
|
150
|
+
{ name: "AES-GCM", iv },
|
|
151
|
+
await __privateGet(this, _firstPartyKey),
|
|
152
|
+
new TextEncoder().encode(firstParty)
|
|
153
|
+
)
|
|
154
|
+
);
|
|
155
|
+
const efpo = new Uint8Array(iv.byteLength + ct.byteLength);
|
|
156
|
+
efpo.set(new Uint8Array(iv));
|
|
157
|
+
efpo.set(ct, iv.byteLength);
|
|
158
|
+
const efpoBase64 = efpo.toBase64({ alphabet: "base64url" });
|
|
159
|
+
return hooks[type].replace(
|
|
160
|
+
'"__DATA_PLACEHOLDER__"',
|
|
161
|
+
JSON.stringify({
|
|
162
|
+
hashes: wasm,
|
|
163
|
+
firstParty: efpoBase64,
|
|
164
|
+
sameOriginWithFirstParty: sameOrigin
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
};
|
|
168
|
+
export {
|
|
169
|
+
HookBuilder
|
|
170
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { SigstoreBundle, TrustedRoot } from "@freedomofpress/sigstore-browser";
|
|
2
|
+
export declare enum EnrollmentTypes {
|
|
3
|
+
Sigsum = "sigsum",
|
|
4
|
+
Sigstore = "sigstore"
|
|
5
|
+
}
|
|
6
|
+
export interface BaseEnrollment {
|
|
7
|
+
max_age: number;
|
|
8
|
+
}
|
|
9
|
+
export interface SigsumEnrollment extends BaseEnrollment {
|
|
10
|
+
type: EnrollmentTypes.Sigsum;
|
|
11
|
+
signers: string[];
|
|
12
|
+
threshold: number;
|
|
13
|
+
policy: string;
|
|
14
|
+
cas_url: string;
|
|
15
|
+
logs: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
export interface SigstoreEnrollment extends BaseEnrollment {
|
|
18
|
+
type: EnrollmentTypes.Sigstore;
|
|
19
|
+
trusted_root: TrustedRoot;
|
|
20
|
+
claims: Record<string, string>;
|
|
21
|
+
}
|
|
22
|
+
export type Enrollment = SigsumEnrollment | SigstoreEnrollment;
|
|
23
|
+
export interface Manifest {
|
|
24
|
+
name: string;
|
|
25
|
+
version: string;
|
|
26
|
+
default_csp: string;
|
|
27
|
+
extra_csp: {
|
|
28
|
+
[matchPrefix: string]: string;
|
|
29
|
+
};
|
|
30
|
+
default_index: string;
|
|
31
|
+
default_fallback: string;
|
|
32
|
+
timestamp?: string;
|
|
33
|
+
files: {
|
|
34
|
+
[filePath: string]: string;
|
|
35
|
+
};
|
|
36
|
+
wasm: string[];
|
|
37
|
+
}
|
|
38
|
+
export interface SigsumSignatures {
|
|
39
|
+
[pubKey: string]: string;
|
|
40
|
+
}
|
|
41
|
+
export type SigstoreSignatures = SigstoreBundle[];
|
|
42
|
+
export interface Bundle {
|
|
43
|
+
enrollment: Enrollment;
|
|
44
|
+
manifest: Manifest;
|
|
45
|
+
signatures: SigsumSignatures | SigstoreSignatures;
|
|
46
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { CacheKey, LRUCache, LRUSet } from "../cache";
|
|
2
|
+
import { CachePartition, OriginState } from "./originstate";
|
|
3
|
+
export interface BlockMeta {
|
|
4
|
+
blockTime: number;
|
|
5
|
+
rootHash: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A persistence interface for enrollment information and origin states.
|
|
9
|
+
*/
|
|
10
|
+
export interface Database {
|
|
11
|
+
/**
|
|
12
|
+
* A partitioned LRU cache of origin states.
|
|
13
|
+
*/
|
|
14
|
+
readonly origins: LRUCache<CacheKey<CachePartition>, OriginState>;
|
|
15
|
+
/**
|
|
16
|
+
* A partitioned LRU set of fully-qualified domain names not enrolled in
|
|
17
|
+
* WEBCAT.
|
|
18
|
+
*/
|
|
19
|
+
readonly nonOrigins: LRUSet<CacheKey<CachePartition>>;
|
|
20
|
+
/**
|
|
21
|
+
* Updates the list of enrolled fully-qualified domain names.
|
|
22
|
+
*/
|
|
23
|
+
updateList(leaves: readonly (readonly [string, string])[], meta: BlockMeta): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Reads block metadata from the database.
|
|
26
|
+
*
|
|
27
|
+
* @returns A Promise that resolves the metadata for the most recently
|
|
28
|
+
* applied block.
|
|
29
|
+
*/
|
|
30
|
+
getBlockMeta(): Promise<BlockMeta | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Reads fully-qualified domain names of enrolled origins from the database.
|
|
33
|
+
*
|
|
34
|
+
* @returns A Promise that resolves to an array of the FQDNs whose enrollment
|
|
35
|
+
* is persisted locally.
|
|
36
|
+
*/
|
|
37
|
+
listAllFQDNs(): Promise<string[]>;
|
|
38
|
+
/**
|
|
39
|
+
* Reads an enrollment hash from the database.
|
|
40
|
+
*
|
|
41
|
+
* @param fqdn The fully-qualified domain name whose enrollment hash to read.
|
|
42
|
+
* @param cachePartition The cache partition to use for cache lookups.
|
|
43
|
+
* @returns A Promise that resolves to the locally persisted enrollment hash
|
|
44
|
+
* corresponding to fqdn.
|
|
45
|
+
*/
|
|
46
|
+
getFQDNEnrollment(fqdn: string, cachePartition: CachePartition): Promise<Uint8Array>;
|
|
47
|
+
/**
|
|
48
|
+
* Persists current time as the time of the latest enrollment update attempt.
|
|
49
|
+
*/
|
|
50
|
+
setLastChecked(): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Reads the timestamp of the most recent enrollment update attempt.
|
|
53
|
+
*
|
|
54
|
+
* @returns A Promise that resolves to a time in milliseconds.
|
|
55
|
+
*/
|
|
56
|
+
getLastChecked(): Promise<number | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Persists the current time as the time of the latest successful enrollment
|
|
59
|
+
* update.
|
|
60
|
+
*/
|
|
61
|
+
setLastUpdated(): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Reads the timestamp of the most recent successful enrollment update.
|
|
64
|
+
*
|
|
65
|
+
* @returns A Promise that resolves to a time in milliseconds.
|
|
66
|
+
*/
|
|
67
|
+
getLastUpdated(): Promise<number | null>;
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|