refactor: port over logic from extension background page to implement x25519/ed25519 based login with a random key
This commit is contained in:
parent
c027a02650
commit
101748af06
|
@ -9,6 +9,8 @@
|
||||||
"version": "0.0.2-develop.4",
|
"version": "0.0.2-develop.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lumeweb/libkernel": "^0.1.0-develop.23",
|
"@lumeweb/libkernel": "^0.1.0-develop.23",
|
||||||
|
"@noble/ciphers": "^0.1.4",
|
||||||
|
"p-defer": "^4.0.0",
|
||||||
"puppeteer": "^20.7.4",
|
"puppeteer": "^20.7.4",
|
||||||
"static-server": "^2.2.1"
|
"static-server": "^2.2.1"
|
||||||
},
|
},
|
||||||
|
@ -1782,6 +1784,14 @@
|
||||||
"vite-plugin-optimizer": "^1.4.2"
|
"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": {
|
"node_modules/@noble/curves": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
"readme": "ERROR: No README data found!",
|
"readme": "ERROR: No README data found!",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lumeweb/libkernel": "^0.1.0-develop.23",
|
"@lumeweb/libkernel": "^0.1.0-develop.23",
|
||||||
|
"@noble/ciphers": "^0.1.4",
|
||||||
|
"p-defer": "^4.0.0",
|
||||||
"puppeteer": "^20.7.4",
|
"puppeteer": "^20.7.4",
|
||||||
"static-server": "^2.2.1"
|
"static-server": "^2.2.1"
|
||||||
},
|
},
|
||||||
|
|
25
src/index.ts
25
src/index.ts
|
@ -4,26 +4,11 @@ import * as kernel from "@lumeweb/libkernel/kernel";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import StaticServer from "static-server";
|
import StaticServer from "static-server";
|
||||||
import { Page } from "puppeteer";
|
import { Page } from "puppeteer";
|
||||||
import { bufToHex, ed25519 } from "@lumeweb/libkernel";
|
|
||||||
|
|
||||||
import * as url from "url";
|
import * as url from "url";
|
||||||
|
|
||||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.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) {
|
export async function loadTester(page: Page, port = 8080) {
|
||||||
const server = new StaticServer({
|
const server = new StaticServer({
|
||||||
rootPath: path.resolve(__dirname, "..", "public"),
|
rootPath: path.resolve(__dirname, "..", "public"),
|
||||||
|
@ -42,4 +27,14 @@ export async function loadTester(page: Page, port = 8080) {
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
return kernel.init();
|
return kernel.init();
|
||||||
});
|
});
|
||||||
|
await page.evaluate(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
return window.main.loginRandom();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
declare function loginRandom(): Promise<any>;
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
loginRandom: typeof loginRandom;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { loadTester, login } from "../lib/index.js";
|
import { loadTester } from "../lib/index.js";
|
||||||
|
|
||||||
import puppeteer, { Browser, Page, ProtocolError } from "puppeteer";
|
import puppeteer, { Browser, Page, ProtocolError } from "puppeteer";
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ let browser: Browser;
|
||||||
browser = await puppeteer.launch({ headless: false, devtools: true });
|
browser = await puppeteer.launch({ headless: false, devtools: true });
|
||||||
|
|
||||||
const page = (await browser.pages()).pop() as Page;
|
const page = (await browser.pages()).pop() as Page;
|
||||||
await login(page);
|
|
||||||
await loadTester(page);
|
await loadTester(page);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
135
src/tester.ts
135
src/tester.ts
|
@ -1,6 +1,35 @@
|
||||||
import * as kernel from "@lumeweb/libkernel/kernel";
|
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.kernel = kernel;
|
||||||
|
window.login = login;
|
||||||
|
|
||||||
window.addEventListener("message", (event) => {
|
window.addEventListener("message", (event) => {
|
||||||
const data = event.data?.data;
|
const data = event.data?.data;
|
||||||
|
@ -11,4 +40,108 @@ window.addEventListener("message", (event) => {
|
||||||
}
|
}
|
||||||
console.error(data.message);
|
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<any> {
|
||||||
|
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!",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
Loading…
Reference in New Issue