refactor: port over logic from extension background page to implement x25519/ed25519 based login with a random key

This commit is contained in:
Derrick Hammer 2023-07-21 12:05:59 -04:00
parent c027a02650
commit 101748af06
Signed by: pcfreak30
GPG Key ID: C997C339BE476FF2
6 changed files with 210 additions and 18 deletions

10
npm-shrinkwrap.json generated
View File

@ -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",

View File

@ -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"
}, },

View File

@ -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;
}
} }

View File

@ -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);
})(); })();

View File

@ -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!",
);
}
});
});
}

53
src/vars.ts Normal file
View File

@ -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();
}