diff --git a/setup-wizard/src/app/pages/embassy/embassy.page.ts b/setup-wizard/src/app/pages/embassy/embassy.page.ts index 1be256c84..ac5c6f07a 100644 --- a/setup-wizard/src/app/pages/embassy/embassy.page.ts +++ b/setup-wizard/src/app/pages/embassy/embassy.page.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core' import { AlertController, iosTransitionAnimation, LoadingController, ModalController, NavController } from '@ionic/angular' import { ApiService, DiskInfo } from 'src/app/services/api/api.service' +import { ErrorToastService } from 'src/app/services/error-toast.service' import { StateService } from 'src/app/services/state.service' import { PasswordPage } from '../password/password.page' @@ -22,11 +23,18 @@ export class EmbassyPage { private readonly alertCtrl: AlertController, private readonly stateService: StateService, private readonly loadingCtrl: LoadingController, + private readonly errorToastService: ErrorToastService, ) { } async ngOnInit () { - this.storageDrives = await this.apiService.getDrives() - this.loading = false + try { + this.storageDrives = await this.apiService.getDrives() + } catch (e) { + console.log(e) + this.errorToastService.present(e.message) + } finally { + this.loading = false + } } async chooseDrive (drive: DiskInfo) { diff --git a/setup-wizard/src/app/services/api/http.service.ts b/setup-wizard/src/app/services/api/http.service.ts index be5985104..7866832e6 100644 --- a/setup-wizard/src/app/services/api/http.service.ts +++ b/setup-wizard/src/app/services/api/http.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core' import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http' -import { Observable, from, interval, race, Subject } from 'rxjs' +import { Observable, from, interval, race } from 'rxjs' import { map, take } from 'rxjs/operators' import * as aesjs from 'aes-js' import * as pbkdf2 from 'pbkdf2' @@ -44,24 +44,32 @@ export class HttpService { this.fullUrl + httpOpts.url : httpOpts.url + const encryptedBody = await AES_CTR.encryptPbkdf2(this.productKey, encodeUtf8( JSON.stringify(httpOpts.body))) const options = { responseType: 'arraybuffer', - body: await AES_CTR.encryptPbkdf2(this.productKey, encodeUtf8( JSON.stringify(httpOpts.body))), + body: encryptedBody.buffer, observe: 'events', reportProgress: false, + headers: { 'Content-Encoding': 'aesctr256', 'Content-Type': 'application/json' }, } as any - const req = this.http.post(url, httpOpts.body, options) + const req = this.http.post(url, options.body, options) return (withTimeout(req, 60000)) .toPromise() - .then(res => AES_CTR.decryptPbkdf2(this.productKey, new Uint8Array(res))) - .then(res => JSON.parse(decodeUtf8(res))) - .catch(e => { throw new HttpError(e) }) + .then(res => AES_CTR.decryptPbkdf2(this.productKey, (res as any).body as ArrayBuffer)) + .then(res => JSON.parse(res)) + .catch(e => { + if(!e.status && !e.statusText) { + throw new EncryptionError(e) + } else { + throw new HttpError(e) + } + }) } } @@ -86,6 +94,12 @@ function HttpError (e: HttpErrorResponse): void { this.details = null } +function EncryptionError (e: HttpErrorResponse): void { + this.code = null + this.message = 'Invalid Key' + this.details = null +} + function isRpcError (arg: { error: Error } | { result: Result}): arg is { error: Error } { return !!(arg as any).error } @@ -162,26 +176,32 @@ function withTimeout (req: Observable, timeout: number): Observable { type AES_CTR = { encryptPbkdf2: (secretKey: string, messageBuffer: Uint8Array) => Promise - decryptPbkdf2: (secretKey, arr: Uint8Array) => Promise + decryptPbkdf2: (secretKey, arr: ArrayBuffer) => Promise } export const AES_CTR: AES_CTR = { encryptPbkdf2: async (secretKey: string, messageBuffer: Uint8Array) => { const salt = window.crypto.getRandomValues(new Uint8Array(16)) const counter = window.crypto.getRandomValues(new Uint8Array(16)) - const key = pbkdf2.pbkdf2Sync(secretKey, salt, 1, 256 / 8, 'sha256'); - var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(counter)); - var encryptedBytes = aesCtr.encrypt(messageBuffer); + + const key = pbkdf2.pbkdf2Sync(secretKey, salt, 1000, 256 / 8, 'sha256'); + + const aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(counter)); + const encryptedBytes = aesCtr.encrypt(messageBuffer); return new Uint8Array([...counter,...salt,...encryptedBytes]) }, - decryptPbkdf2: async (secretKey: string, arr: Uint8Array) => { - const counter = arr.slice(0, 16) - const salt = arr.slice(16, 32) - const cipher = arr.slice(32) - const key = pbkdf2.pbkdf2Sync(secretKey, salt, 1, 256 / 8, 'sha256'); + decryptPbkdf2: async (secretKey: string, arr: ArrayBuffer) => { + const buff = new Uint8Array(arr) + const counter = buff.slice(0, 16) + const salt = buff.slice(16, 32) - var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(counter)); - return aesCtr.decrypt(cipher); + const cipher = buff.slice(32) + const key = pbkdf2.pbkdf2Sync(secretKey, salt, 1000, 256 / 8, 'sha256'); + + const aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(counter)); + const decryptedBytes = aesCtr.decrypt(cipher); + + return aesjs.utils.utf8.fromBytes(decryptedBytes); }, } diff --git a/setup-wizard/src/app/services/error-toast.service.ts b/setup-wizard/src/app/services/error-toast.service.ts new file mode 100644 index 000000000..c21e0e947 --- /dev/null +++ b/setup-wizard/src/app/services/error-toast.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core' +import { ToastController } from '@ionic/angular' + +@Injectable({ + providedIn: 'root', +}) +export class ErrorToastService { + private toast: HTMLIonToastElement + + constructor ( + private readonly toastCtrl: ToastController, + ) { } + + async present (message: string): Promise { + if (this.toast) return + + this.toast = await this.toastCtrl.create({ + header: 'Error', + message, + duration: 0, + position: 'top', + cssClass: 'error-toast', + buttons: [ + { + side: 'end', + icon: 'close', + handler: () => { + this.dismiss() + }, + }, + ], + }) + await this.toast.present() + } + + async dismiss (): Promise { + if (this.toast) { + await this.toast.dismiss() + this.toast = undefined + } + } +} \ No newline at end of file diff --git a/setup-wizard/src/global.scss b/setup-wizard/src/global.scss index b75879201..74be8558f 100644 --- a/setup-wizard/src/global.scss +++ b/setup-wizard/src/global.scss @@ -53,6 +53,16 @@ ion-item { } } +.error-toast { + --border-color: var(--ion-color-danger); + width: 40%; + min-width: 400px; + --end: 8px; + right: 8px; + left: unset; + top: 64px; +} + @media (min-width:1000px) { .alertlike-modal { .modal-wrapper {