This repository has been archived on 2023-12-17. You can view files and clone it, but cannot push or open issues or pull requests.
extension/src/main/bootloader.ts

478 lines
11 KiB
TypeScript

import {
addContextToErr,
b64ToBuf,
bufToHex,
bufToStr,
computeRegistrySignature,
defaultPortalList,
deriveChildSeed,
deriveRegistryEntryID,
downloadSkylink,
Ed25519Keypair,
entryIDToSkylink,
Err,
hexToBuf,
progressiveFetch,
progressiveFetchResult,
taggedRegistryEntryKeys,
objAsString,
verifyRegistryWriteResponse,
} from "libskynet";
var browser: any; // tsc
const defaultKernelResolverLink =
"AQBFjXpEBwbMwkBwYg0gdkeAM-yy9vlajfLtZSee9f-MDg";
document.title = "kernel.skynet";
let header = document.createElement("h1");
header.textContent =
"Something went wrong! You should not be visiting this page, this page should only be accessed via an invisible iframe.";
document.body.appendChild(header);
function bootloaderWLog(isErr: boolean, ...inputs: any) {
// Build the message, each item gets its own line. We do this because items
// are often full objects.
let message = "[lumeweb-kernel-bootloader]";
for (let i = 0; i < inputs.length; i++) {
message += "\n";
message += objAsString(inputs[i]);
}
// Create the log by sending it to the parent.
window.parent.postMessage(
{
method: "log",
data: {
isErr,
message,
},
},
"*"
);
}
function log(...inputs: any) {
bootloaderWLog(false, ...inputs);
}
function logErr(...inputs: any) {
bootloaderWLog(true, ...inputs);
}
var handleIncomingMessage = function (event: MessageEvent) {
if (event.source === null) {
return;
}
if (event.source === window) {
return;
}
if (!("nonce" in event.data)) {
(event.source as WindowProxy).postMessage(
{
nonce: "N/A",
method: "response",
err: "message sent to kernel with no nonce",
},
event.origin
);
return;
}
if (!("method" in event.data)) {
(event.source as WindowProxy).postMessage(
{
nonce: event.data.nonce,
method: "response",
err: "message sent to kernel with no method",
},
event.origin
);
return;
}
if (event.data.method === "requestOverride") {
handleSkynetKernelRequestOverride(event);
return;
}
(event.source as WindowProxy).postMessage(
{
nonce: event.data.nonce,
method: "response",
err:
"unrecognized method (user may need to log in): " + event.data.method,
},
event.origin
);
return;
};
window.addEventListener("message", (event: MessageEvent) => {
handleIncomingMessage(event);
});
let kernelFavicon: Uint8Array;
let blockForFavicon: Promise<void> = new Promise((resolve) => {
try {
let faviconURL = browser.runtime.getURL("icon@2x.png");
fetch(faviconURL).then((response) => {
response.arrayBuffer().then((faviconData) => {
kernelFavicon = new Uint8Array(faviconData);
resolve();
});
});
} catch {
kernelFavicon = new Uint8Array(0);
resolve();
}
});
let kernelAuthPage: Uint8Array;
let blockForAuthPage: Promise<void> = new Promise((resolve) => {
try {
let authURL = browser.runtime.getURL("auth.html");
fetch(authURL).then((response) => {
response.arrayBuffer().then((authData) => {
kernelAuthPage = new Uint8Array(authData);
resolve();
});
});
} catch (err: any) {
kernelAuthPage = new TextEncoder().encode(
addContextToErr(err, "unable to load the kernel auth page")
);
resolve();
}
});
function handleSkynetKernelRequestOverride(event: MessageEvent) {
if (event.source === null) {
return;
}
if (!event.origin.startsWith("moz")) {
return;
}
if (event.data.data.method !== "GET") {
return;
}
let respondOverride = function (headers: any, body: Uint8Array) {
(event.source as WindowProxy).postMessage(
{
nonce: event.data.nonce,
method: "response",
err: null,
data: {
override: true,
headers,
body,
},
},
event.origin
);
};
if (event.data.data.url === "http://kernel.skynet/favicon.ico") {
blockForFavicon.then(() => {
let headers = [
{
name: "content-type",
value: "image/png",
},
];
respondOverride(headers, kernelFavicon);
});
return;
}
if (event.data.data.url === "http://kernel.skynet/auth.html") {
blockForAuthPage.then(() => {
let headers = [
{
name: "content-type",
value: "text/html; charset=utf8",
},
];
respondOverride(headers, kernelAuthPage);
});
return;
}
(event.source as WindowProxy).postMessage(
{
nonce: event.data.nonce,
method: "response",
err: null,
data: {
override: false,
},
},
event.origin
);
}
var handleStorage = function (event: StorageEvent) {
if (event.key !== null && event.key !== "v1-seed") {
return;
}
if (logoutComplete === true) {
window.location.reload();
return;
}
if (event.key === null && loginComplete === false) {
return;
}
if (event.key === "v1-seed" && loginComplete === false) {
let userSeedString = window.localStorage.getItem("v1-seed");
if (userSeedString === null) {
sendAuthUpdate();
return;
}
let [decodedSeed, errHTB] = hexToBuf(userSeedString);
if (errHTB !== null) {
logErr(addContextToErr(errHTB, "seed could not be decoded from hex"));
sendAuthUpdate();
return;
}
userSeed = decodedSeed;
log("user is now logged in, attempting to load kernel");
loginComplete = true;
loadKernel();
sendAuthUpdate();
return;
}
logoutComplete = true;
sendAuthUpdate();
log("attempting to do a page reload");
window.location.reload();
};
window.addEventListener("storage", (event) => handleStorage(event));
function downloadKernel(
kernelSkylink: string
): Promise<[kernelCode: string, err: Err]> {
return new Promise((resolve) => {
downloadSkylink(kernelSkylink).then(([fileData, err]) => {
if (err === "404") {
resolve(["", err]);
return;
}
if (err !== null) {
resolve([
"",
addContextToErr(err, "unable to download the default kernel"),
]);
return;
}
let [kernelCode, errBBTS] = bufToStr(fileData);
if (errBBTS !== null) {
resolve([
"",
addContextToErr(err, "unable to decode the default kernel"),
]);
return;
}
resolve([kernelCode, null]);
});
});
}
function downloadDefaultKernel(): Promise<[kernelCode: string, err: Err]> {
return downloadKernel(defaultKernelResolverLink);
}
function setUserKernelAsDefault(keypair: Ed25519Keypair, dataKey: Uint8Array) {
log(
"user kernel not found, setting user kernel to " + defaultKernelResolverLink
);
let [defaultKernelSkylink, err64] = b64ToBuf(defaultKernelResolverLink);
if (err64 !== null) {
log("unable to convert default kernel link to a Uint8Array");
return;
}
let [sig, errCRS] = computeRegistrySignature(
keypair.secretKey,
dataKey,
defaultKernelSkylink,
0n
);
if (errCRS !== null) {
log(
addContextToErr(
errCRS,
"unable to compute registry signature to set user kernel"
)
);
return;
}
let dataKeyHex = bufToHex(dataKey);
let endpoint = "/skynet/registry";
let postBody = {
publickey: {
algorithm: "ed25519",
key: Array.from(keypair.publicKey),
},
datakey: dataKeyHex,
revision: 0,
data: Array.from(defaultKernelSkylink),
signature: Array.from(sig),
};
let fetchOpts = {
method: "post",
body: JSON.stringify(postBody),
};
progressiveFetch(
endpoint,
fetchOpts,
defaultPortalList,
verifyRegistryWriteResponse
).then((result: progressiveFetchResult) => {
if (result.success !== true) {
log("unable to update the user kernel registry entry\n", result.logs);
return;
}
log(
"successfully updated the user kernel registry entry to the default kernel"
);
});
}
function downloadUserKernel(): Promise<[kernelCode: string, err: Err]> {
return new Promise((resolve) => {
let kernelEntrySeed = deriveChildSeed(userSeed, "userPreferredKernel2");
let [keypair, dataKey, errTREK] = taggedRegistryEntryKeys(
kernelEntrySeed,
"user kernel"
);
if (errTREK !== null) {
resolve([
"",
addContextToErr(errTREK, "unable to create user kernel registry keys"),
]);
return;
}
let [entryID, errREID] = deriveRegistryEntryID(keypair.publicKey, dataKey);
if (errREID !== null) {
resolve([
"",
addContextToErr(errREID, "unable to derive registry entry id"),
]);
return;
}
let userKernelSkylink = entryIDToSkylink(entryID);
downloadKernel(userKernelSkylink).then(([kernelCode, err]) => {
if (err === "404") {
downloadDefaultKernel().then(([defaultCode, errDefault]) => {
if (errDefault === null) {
setUserKernelAsDefault(keypair, dataKey);
}
resolve([defaultCode, errDefault]);
return;
});
return;
}
log("found user kernel, using: " + userKernelSkylink);
resolve([kernelCode, err]);
});
});
}
function loadKernel() {
downloadUserKernel().then(([kernelCode, err]) => {
if (err !== null) {
let extErr = addContextToErr(err, "unable to download kernel");
kernelLoaded = extErr;
logErr(extErr);
sendAuthUpdate();
return;
}
try {
window.eval(kernelCode);
kernelLoaded = "success";
sendAuthUpdate();
log("kernel successfully loaded");
return;
} catch (err: any) {
let extErr = addContextToErr(err, "unable to eval kernel");
kernelLoaded = extErr;
logErr(extErr);
logErr(err.toString());
console.error(extErr);
console.error(err);
sendAuthUpdate();
return;
}
});
}
let loginComplete = false;
let logoutComplete = false;
let kernelLoaded = "not yet";
function sendAuthUpdate() {
window.parent.postMessage(
{
method: "kernelAuthStatus",
data: {
loginComplete: loginComplete,
kernelLoaded: kernelLoaded,
logoutComplete: logoutComplete,
},
},
"*"
);
}
sendAuthUpdate();
let userSeed: Uint8Array;
function checkForLoadKernel() {
let userSeedString = window.localStorage.getItem("v1-seed");
if (userSeedString === null) {
sendAuthUpdate();
return;
}
let [decodedSeed, errHTB] = hexToBuf(userSeedString);
if (errHTB !== null) {
logErr(addContextToErr(errHTB, "seed could not be decoded from hex"));
sendAuthUpdate();
return;
}
userSeed = decodedSeed;
log("user is already logged in, attempting to load kernel");
loginComplete = true;
sendAuthUpdate();
loadKernel();
}
let accessFailedStr =
"unable to get access to localStorage, user may need to reduce their privacy settings";
if (
Object.prototype.hasOwnProperty.call(document, "requestStorageAccess") &&
window.origin === "https://skt.us"
) {
document
.requestStorageAccess()
.then(() => {
checkForLoadKernel();
})
.catch((err) => {
log(addContextToErr(err, accessFailedStr));
sendAuthUpdate();
});
} else {
checkForLoadKernel();
}