mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-01 21:13:09 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -11,9 +11,6 @@ import { pauseFor, ErrorToastService } from '@start9labs/shared'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class StateService {
|
||||
hasProductKey = false
|
||||
isMigrating = false
|
||||
|
||||
polling = false
|
||||
embassyLoaded = false
|
||||
|
||||
|
||||
Reference in New Issue
Block a user