From 4025f6b590ba9c1189bbd47617b6473e3d1112ee Mon Sep 17 00:00:00 2001 From: Radu-Cristian Popa Date: Wed, 21 Feb 2024 17:06:44 +0200 Subject: [PATCH] feat: generate keys when extension is installed (#93) * Generate keys when extension is installed * Add comments --- package.json | 2 ++ pnpm-lock.yaml | 15 ++++++++++++ src/background/Background.ts | 30 +++++++++++++++++++++++ src/background/BackgroundContainer.ts | 12 --------- src/background/container.ts | 14 +++++++++++ src/background/index.ts | 5 ++-- src/utils/crypto.ts | 35 +++++++++++++++++++++++++++ tsconfig.json | 2 +- 8 files changed, 100 insertions(+), 15 deletions(-) delete mode 100644 src/background/BackgroundContainer.ts create mode 100644 src/background/container.ts create mode 100644 src/utils/crypto.ts diff --git a/package.json b/package.json index 7324df8f..3215e3c3 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,8 @@ "test:ci": "pnpm test -- --reporters=default --reporters=github-actions" }, "dependencies": { + "@noble/ed25519": "^2.0.0", + "@noble/hashes": "^1.3.3", "awilix": "^10.0.1", "axios": "^1.5.1", "class-variance-authority": "^0.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0fabcb87..84fb465c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,12 @@ settings: excludeLinksFromLockfile: false dependencies: + '@noble/ed25519': + specifier: ^2.0.0 + version: 2.0.0 + '@noble/hashes': + specifier: ^1.3.3 + version: 1.3.3 awilix: specifier: ^10.0.1 version: 10.0.1 @@ -1153,6 +1159,15 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@noble/ed25519@2.0.0: + resolution: {integrity: sha512-/extjhkwFupyopDrt80OMWKdLgP429qLZj+z6sYJz90rF2Iz0gjZh2ArMKPImUl13Kx+0EXI2hN9T/KJV0/Zng==} + dev: false + + /@noble/hashes@1.3.3: + resolution: {integrity: sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==} + engines: {node: '>= 16'} + dev: false + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} diff --git a/src/background/Background.ts b/src/background/Background.ts index 019cd86c..e45edb12 100644 --- a/src/background/Background.ts +++ b/src/background/Background.ts @@ -1,6 +1,8 @@ +import { bytesToHex } from '@noble/hashes/utils' import browser, { Runtime, runtime, tabs } from 'webextension-polyfill' import { PaymentFlowService } from '@/background/grantFlow' +import { exportJWK, generateEd25519KeyPair } from '@/utils/crypto' import { defaultData } from '@/utils/storage' import getSendingPaymentPointerHandler from '../messageHandlers/getSendingPaymentPointerHandler' @@ -72,6 +74,34 @@ class Background { unsubscribeFromMessages() { this.subscriptions.forEach((sub: any) => sub()) } + + // TODO: Move to storage wrapper once available + private async keyExists(): Promise { + return new Promise(res => { + chrome.storage.local.get(['privateKey', 'publicKey', 'kid'], data => { + if (data.privateKey && data.publicKey && data.kid) { + res(true) + } else { + res(false) + } + }) + }) + } + + async onInstalled() { + chrome.runtime.onInstalled.addListener(async () => { + if (await this.keyExists()) return + const { privateKey, publicKey } = generateEd25519KeyPair() + const kid = crypto.randomUUID() + const jwk = exportJWK(publicKey, kid) + + chrome.storage.local.set({ + privateKey: bytesToHex(privateKey), + publicKey: btoa(JSON.stringify(jwk)), + kid, + }) + }) + } } export default Background diff --git a/src/background/BackgroundContainer.ts b/src/background/BackgroundContainer.ts deleted file mode 100644 index 08d31d49..00000000 --- a/src/background/BackgroundContainer.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { asClass, createContainer } from 'awilix' - -import Background from './Background' - -const BackgroundContainer = createContainer() - -BackgroundContainer.register({ - Background: asClass(Background).singleton(), - // TODO - add injectable services -}) - -export default BackgroundContainer diff --git a/src/background/container.ts b/src/background/container.ts new file mode 100644 index 00000000..646a3ad3 --- /dev/null +++ b/src/background/container.ts @@ -0,0 +1,14 @@ +import { asClass, createContainer } from 'awilix' + +import Background from './Background' + +interface Cradle { + background: Background +} + +export const container = createContainer() + +container.register({ + background: asClass(Background).singleton(), + // TODO - add injectable services +}) diff --git a/src/background/index.ts b/src/background/index.ts index f3bb864a..3db012a8 100755 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -1,10 +1,11 @@ -import BackgroundContainer from './BackgroundContainer' +import { container } from './container' const initialize = () => { console.log('Start initialization') - const background = BackgroundContainer.resolve('Background') + const background = container.resolve('background') + background.onInstalled() background.subscribeToMessages() background.subscribeToTabChanges() diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts new file mode 100644 index 00000000..612fe985 --- /dev/null +++ b/src/utils/crypto.ts @@ -0,0 +1,35 @@ +import * as ed from '@noble/ed25519' +import { sha512 } from '@noble/hashes/sha512' + +ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m)) + +export function generateEd25519KeyPair() { + const rawPrivateKey = ed.utils.randomPrivateKey() + // PKCS#8 format (version + algorithm) + // Adding these values upfront solves the future import of the key using + // `crypto.subtle.importKey` once the WebCrypto API supports the Ed25519 algorithm. + // prettier-ignore + const privateKey = new Uint8Array([ + 48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32, + ...rawPrivateKey, + ]) + const publicKey = ed.getPublicKey(rawPrivateKey) + + return { privateKey, publicKey } +} + +export function exportJWK(key: Uint8Array, kid: string) { + const string = String.fromCharCode.apply(null, key) + + const base64 = btoa(string) + // Based on the JWK Spec - base64url encoded. + // https://datatracker.ietf.org/doc/html/rfc7517#section-3 + const base64Url = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') + + return { + kty: 'OKP', + crv: 'Ed25519', + x: base64Url, + kid, + } +} diff --git a/tsconfig.json b/tsconfig.json index 428a3871..82865ecb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "baseUrl": "./src", "esModuleInterop": true, "module": "commonjs", - "target": "es5", + "target": "es6", "allowJs": true, "jsx": "react", "sourceMap": true,