remove product key from setup flow (#1750)

* remove product key flow from setup

* feat: backend turned off encryption + new Id + no package id

* implement new encryption scheme in FE

* decode response string

* crypto not working

* update setup wizard closes #1762

* feat: Get the encryption key

* fix: Get to recovery

* remove old code

* fix build

* fix: Install works for now

* fix bug in config for adding new list items

* dismiss action modal on success

* clear button in config

* wip: Currently broken in avahi mdns

* include headers with req/res and refactor patchDB init and usage

* fix: Can now run in the main

* flatline on failed init

* update patch DB

* add last-wifi-region to data model even though not used by FE

* chore: Fix the start.

* wip: Fix wrong order for getting hostname before sql has been
created

* fix edge case where union keys displayed as new when not new

* fix: Can start

* last backup color, markdown links always new tab, fix bug with login

* refactor to remove WithRevision

* resolve circular dep issue

* update submodule

* fix patch-db

* update patchDB

* update patch again

* escape error

* decodeuricomponent

* increase proxy buffer size

* increase proxy buffer size

* fix nginx

Co-authored-by: BluJ <mogulslayer@gmail.com>
Co-authored-by: BluJ <dragondef@gmail.com>
Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Matt Hill
2022-09-07 09:25:01 -06:00
committed by GitHub
parent 76682ebef0
commit 50111e37da
175 changed files with 11436 additions and 2906 deletions

View File

@@ -1,20 +1,19 @@
export abstract class ApiService {
// unencrypted
abstract getStatus(): Promise<GetStatusRes> // setup.status
abstract getSecret(): Promise<string> // setup.get-secret
abstract getDrives(): Promise<DiskListResponse> // setup.disk.list
abstract set02XDrive(logicalname: string): Promise<void> // setup.recovery.v2.set
abstract getRecoveryStatus(): Promise<RecoveryStatusRes> // setup.recovery.status
// encrypted
abstract verifyCifs(cifs: CifsRecoverySource): Promise<EmbassyOSRecoveryInfo> // setup.cifs.verify
abstract verifyProductKey(): Promise<void> // echo - throws error if invalid
abstract importDrive(importInfo: ImportDriveReq): Promise<SetupEmbassyRes> // setup.attach
abstract setupEmbassy(setupInfo: SetupEmbassyReq): Promise<SetupEmbassyRes> // setup.execute
abstract setupComplete(): Promise<SetupEmbassyRes> // setup.complete
}
export type GetStatusRes = {
'product-key': boolean
migrating: boolean
}

View File

@@ -1,5 +1,10 @@
import { Injectable } from '@angular/core'
import { HttpService } from '@start9labs/shared'
import {
HttpService,
isRpcError,
RpcError,
RPCOptions,
} from '@start9labs/shared'
import {
ApiService,
CifsRecoverySource,
@@ -13,43 +18,71 @@ import {
SetupEmbassyRes,
} from './api.service'
import { RPCEncryptedService } from '../rpc-encrypted.service'
import * as jose from 'node-jose'
@Injectable({
providedIn: 'root',
})
export class LiveApiService extends ApiService {
export class LiveApiService implements ApiService {
constructor(
private readonly unencrypted: HttpService,
private readonly encrypted: RPCEncryptedService,
) {
super()
}
) {}
// ** UNENCRYPTED **
async getStatus() {
return this.unencrypted.rpcRequest<GetStatusRes>({
return this.rpcRequest<GetStatusRes>({
method: 'setup.status',
params: {},
})
}
/**
* We want to update the secret, which means that we will call in clearnet the
* getSecret, and all the information is never in the clear, and only public
* information is sent across the network. We don't want to expose that we do
* this wil all public/private key, which means that there is no information loss
* through the network.
*/
async getSecret() {
const keystore = jose.JWK.createKeyStore()
const key = await keystore.generate('EC', 'P-256')
// const { privateKey, publicKey } =
// jose.generateKeyPair('ECDH-ES', {
// extractable: true,
// })
console.log({ publicKey: key.toJSON() })
const response: string = await this.rpcRequest({
method: 'setup.get-secret',
params: { pubkey: key.toJSON() },
})
// const { plaintext } = await jose.compactDecrypt(response, privateKey)
const decrypted = await jose.JWE.createDecrypt(key).decrypt(response)
const decoded = new TextDecoder().decode(decrypted.plaintext)
console.log({ decoded })
return decoded
}
async getDrives() {
return this.unencrypted.rpcRequest<DiskListResponse>({
return this.rpcRequest<DiskListResponse>({
method: 'setup.disk.list',
params: {},
})
}
async set02XDrive(logicalname: string) {
return this.unencrypted.rpcRequest<void>({
return this.rpcRequest<void>({
method: 'setup.recovery.v2.set',
params: { logicalname },
})
}
async getRecoveryStatus() {
return this.unencrypted.rpcRequest<RecoveryStatusRes>({
return this.rpcRequest<RecoveryStatusRes>({
method: 'setup.recovery.status',
params: {},
})
@@ -65,13 +98,6 @@ export class LiveApiService extends ApiService {
})
}
async verifyProductKey() {
return this.encrypted.rpcRequest<void>({
method: 'echo',
params: { message: 'hello' },
})
}
async importDrive(params: ImportDriveReq) {
const res = await this.encrypted.rpcRequest<SetupEmbassyRes>({
method: 'setup.attach',
@@ -113,6 +139,18 @@ export class LiveApiService extends ApiService {
'root-ca': btoa(res['root-ca']),
}
}
private async rpcRequest<T>(opts: RPCOptions): Promise<T> {
const res = await this.unencrypted.rpcRequest<T>(opts)
const rpcRes = res.body
if (isRpcError(rpcRes)) {
throw new RpcError(rpcRes.error)
}
return rpcRes.result
}
}
function isCifsSource(

View File

@@ -12,21 +12,29 @@ let tries = 0
@Injectable({
providedIn: 'root',
})
export class MockApiService extends ApiService {
constructor() {
super()
}
export class MockApiService implements ApiService {
// ** UNENCRYPTED **
async getStatus() {
await pauseFor(1000)
return {
'product-key': true,
migrating: false,
}
}
async getSecret() {
await pauseFor(1000)
const ascii = 'thisisasecret'
const arr1 = []
for (let n = 0, l = ascii.length; n < l; n++) {
var hex = Number(ascii.charCodeAt(n)).toString(16)
arr1.push(hex)
}
return arr1.join('')
}
async getDrives() {
await pauseFor(1000)
return {
@@ -84,11 +92,6 @@ export class MockApiService extends ApiService {
}
}
async verifyProductKey() {
await pauseFor(1000)
return
}
async importDrive(params: ImportDriveReq) {
await pauseFor(3000)
return setupRes

View File

@@ -15,13 +15,13 @@ import {
providedIn: 'root',
})
export class RPCEncryptedService {
productKey?: string
secret?: string
constructor(private readonly http: HttpService) {}
async rpcRequest<T>(opts: Omit<RPCOptions, 'timeout'>): Promise<T> {
const encryptedBody = await AES_CTR.encryptPbkdf2(
this.productKey || '',
this.secret || '',
encodeUtf8(JSON.stringify(opts)),
)
@@ -36,7 +36,11 @@ export class RPCEncryptedService {
'Content-Type': 'application/json',
},
})
.then(body => AES_CTR.decryptPbkdf2(this.productKey || '', body))
.then(res => AES_CTR.decryptPbkdf2(this.secret || '', res.body))
.then(x => {
console.log(`Network: ${x}`)
return x
})
.then(res => JSON.parse(res))
.catch(e => {
if (!e.status && !e.statusText) {

View File

@@ -11,9 +11,6 @@ import { pauseFor, ErrorToastService } from '@start9labs/shared'
providedIn: 'root',
})
export class StateService {
hasProductKey = false
isMigrating = false
polling = false
embassyLoaded = false