diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 3c6c68e..6494b79 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -9,6 +9,8 @@ "version": "0.0.2-develop.4", "dependencies": { "@lumeweb/libkernel": "^0.1.0-develop.23", + "@noble/ciphers": "^0.1.4", + "p-defer": "^4.0.0", "puppeteer": "^20.7.4", "static-server": "^2.2.1" }, @@ -1782,6 +1784,14 @@ "vite-plugin-optimizer": "^1.4.2" } }, + "node_modules/@noble/ciphers": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.1.4.tgz", + "integrity": "sha512-d3ZR8vGSpy3v/nllS+bD/OMN5UZqusWiQqkyj7AwzTnhXFH72pF5oB4Ach6DQ50g5kXxC28LdaYBEpsyv9KOUQ==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@noble/curves": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", diff --git a/package.json b/package.json index ba4cc0a..56a8b31 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,8 @@ "readme": "ERROR: No README data found!", "dependencies": { "@lumeweb/libkernel": "^0.1.0-develop.23", + "@noble/ciphers": "^0.1.4", + "p-defer": "^4.0.0", "puppeteer": "^20.7.4", "static-server": "^2.2.1" }, diff --git a/src/index.ts b/src/index.ts index 1aad2b0..a577496 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,26 +4,11 @@ import * as kernel from "@lumeweb/libkernel/kernel"; // @ts-ignore import StaticServer from "static-server"; import { Page } from "puppeteer"; -import { bufToHex, ed25519 } from "@lumeweb/libkernel"; import * as url from "url"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); -export function generateSeedPhrase() { - return ed25519.utils.randomPrivateKey(); -} - -export async function login(page: Page, seed = generateSeedPhrase()) { - await page.goto("https://kernel.lumeweb.com"); - - let seedHex = bufToHex(seed); - - await page.evaluate((seed: string) => { - window.localStorage.setItem("key", seed); - }, seedHex); -} - export async function loadTester(page: Page, port = 8080) { const server = new StaticServer({ rootPath: path.resolve(__dirname, "..", "public"), @@ -42,4 +27,14 @@ export async function loadTester(page: Page, port = 8080) { await page.evaluate(() => { return kernel.init(); }); + await page.evaluate(() => { + // @ts-ignore + return window.main.loginRandom(); + }); +} +declare function loginRandom(): Promise; +declare global { + interface Window { + loginRandom: typeof loginRandom; + } } diff --git a/src/sandbox.ts b/src/sandbox.ts index 02f7bd5..6f6483f 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node // @ts-ignore -import { loadTester, login } from "../lib/index.js"; +import { loadTester } from "../lib/index.js"; import puppeteer, { Browser, Page, ProtocolError } from "puppeteer"; @@ -10,7 +10,6 @@ let browser: Browser; browser = await puppeteer.launch({ headless: false, devtools: true }); const page = (await browser.pages()).pop() as Page; - await login(page); await loadTester(page); })(); diff --git a/src/tester.ts b/src/tester.ts index 5349be2..09076f8 100644 --- a/src/tester.ts +++ b/src/tester.ts @@ -1,6 +1,35 @@ import * as kernel from "@lumeweb/libkernel/kernel"; -// @ts-ignore +import { x25519 } from "@noble/curves/ed25519"; +import { bytesToHex, hexToBytes } from "@noble/curves/abstract/utils"; +import defer from "p-defer"; +import { randomBytes } from "@noble/hashes/utils"; +import { secretbox } from "@noble/ciphers/salsa"; +import { + addQuery, + deleteQuery, + getAuthStatus, + getAuthStatusDefer, + getAuthStatusKnown, + getLoggedInDefer, + getQueries, + getQueriesNonce, + getQuery, + increaseQueriesNonce, + resetLoggedInDefer, + setAuthStatus, + setAuthStatusKnown, +} from "./vars.js"; +import { ed25519 } from "@lumeweb/libkernel"; + +declare global { + interface Window { + kernel: typeof kernel; + login: typeof login; + } +} + window.kernel = kernel; +window.login = login; window.addEventListener("message", (event) => { const data = event.data?.data; @@ -11,4 +40,108 @@ window.addEventListener("message", (event) => { } console.error(data.message); } + + if (event.data.method === "kernelAuthStatus") { + setAuthStatus(data); + if (!getAuthStatusKnown()) { + getAuthStatusDefer().resolve(); + setAuthStatusKnown(true); + console.log("bootloader is now initialized"); + if (!getAuthStatus().loginComplete) { + console.log("user is not logged in: waiting until login is confirmed"); + } else { + getLoggedInDefer().resolve(); + } + if (getAuthStatus().logoutComplete) { + resetLoggedInDefer(); + setAuthStatusKnown(false); + } + } + } + + if (!(event.data.nonce in getQueries())) { + return; + } + + let receiveResult = getQuery(event.data.nonce); + if (event.data.method === "response") { + deleteQuery(event.data.nonce); + } + + receiveResult(event.data); }); + +function getKernelIframe() { + const iframes = Array.from(document.getElementsByTagName("iframe")); + + if (!iframes.length) { + console.error("could not find kernel iframe"); + return; + } + + return iframes[0]; +} + +export async function loginRandom() { + return login(ed25519.utils.randomPrivateKey()); +} + +export async function login(key: Uint8Array) { + let privKey = x25519.utils.randomPrivateKey(); + + const iframe = getKernelIframe(); + + if (!iframe) { + return; + } + + let pubKey: string | Uint8Array = await queryKernel({ + method: "exchangeCommunicationKeys", + data: bytesToHex(x25519.getPublicKey(privKey)), + }); + + if (!pubKey) { + alert(`Failed to login: could not get communication key`); + return; + } + + pubKey = hexToBytes(pubKey as string); + + const secret = x25519.getSharedSecret(privKey, pubKey); + const nonce = randomBytes(24); + const box = secretbox(secret, nonce); + const ciphertext = box.seal(key); + + await queryKernel({ + method: "setLoginKey", + data: { + data: bytesToHex(ciphertext), + nonce: bytesToHex(nonce), + }, + }); +} + +function queryKernel(query: any): Promise { + return new Promise((resolve) => { + let receiveResponse = function (data: any) { + resolve(data.data); + }; + + getAuthStatusDefer().promise.then(() => { + let nonce = getQueriesNonce(); + increaseQueriesNonce(); + query.nonce = nonce; + addQuery(nonce, receiveResponse); + if (getKernelIframe()?.contentWindow !== null) { + getKernelIframe()?.contentWindow?.postMessage( + query, + "https://kernel.lumeweb.com", + ); + } else { + console.error( + "kernelFrame.contentWindow was null, cannot send message!", + ); + } + }); + }); +} diff --git a/src/vars.ts b/src/vars.ts new file mode 100644 index 0000000..639502e --- /dev/null +++ b/src/vars.ts @@ -0,0 +1,53 @@ +import defer from "p-defer"; +import { KernelAuthStatus } from "@lumeweb/libweb"; + +let authStatus: KernelAuthStatus; +let authStatusKnown = false; +let authStatusDefer = defer(); +let queriesNonce = 1; +let queries: any = {}; +let loggedInDefer = defer(); + +export function getAuthStatusKnown() { + return authStatusKnown; +} +export function setAuthStatusKnown(status: boolean) { + authStatusKnown = status; +} + +export function getAuthStatus(): KernelAuthStatus { + return authStatus; +} + +export function setAuthStatus(status: KernelAuthStatus) { + authStatus = status; +} + +export function getAuthStatusDefer() { + return authStatusDefer; +} +export function getQueriesNonce(): number { + return queriesNonce; +} +export function getQueries() { + return queries; +} +export function deleteQuery(nonce: any) { + delete queries[nonce]; +} +export function getQuery(nonce: any) { + return queries[nonce]; +} +export function increaseQueriesNonce() { + queriesNonce++; +} + +export function addQuery(nonce: any, func: Function) { + queries[nonce] = func; +} +export function getLoggedInDefer() { + return loggedInDefer; +} +export function resetLoggedInDefer() { + loggedInDefer = defer(); +}