encryption integration fixes

This commit is contained in:
Drew Ansbacher
2021-09-14 22:35:46 -06:00
committed by Drew Ansbacher
parent 6d12c4acfb
commit 8d944cddff
4 changed files with 99 additions and 19 deletions

View File

@@ -1,6 +1,7 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { AlertController, iosTransitionAnimation, LoadingController, ModalController, NavController } from '@ionic/angular' import { AlertController, iosTransitionAnimation, LoadingController, ModalController, NavController } from '@ionic/angular'
import { ApiService, DiskInfo } from 'src/app/services/api/api.service' 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 { StateService } from 'src/app/services/state.service'
import { PasswordPage } from '../password/password.page' import { PasswordPage } from '../password/password.page'
@@ -22,11 +23,18 @@ export class EmbassyPage {
private readonly alertCtrl: AlertController, private readonly alertCtrl: AlertController,
private readonly stateService: StateService, private readonly stateService: StateService,
private readonly loadingCtrl: LoadingController, private readonly loadingCtrl: LoadingController,
private readonly errorToastService: ErrorToastService,
) { } ) { }
async ngOnInit () { async ngOnInit () {
this.storageDrives = await this.apiService.getDrives() try {
this.loading = false this.storageDrives = await this.apiService.getDrives()
} catch (e) {
console.log(e)
this.errorToastService.present(e.message)
} finally {
this.loading = false
}
} }
async chooseDrive (drive: DiskInfo) { async chooseDrive (drive: DiskInfo) {

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http' 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 { map, take } from 'rxjs/operators'
import * as aesjs from 'aes-js' import * as aesjs from 'aes-js'
import * as pbkdf2 from 'pbkdf2' import * as pbkdf2 from 'pbkdf2'
@@ -44,24 +44,32 @@ export class HttpService {
this.fullUrl + httpOpts.url : this.fullUrl + httpOpts.url :
httpOpts.url httpOpts.url
const encryptedBody = await AES_CTR.encryptPbkdf2(this.productKey, encodeUtf8( JSON.stringify(httpOpts.body)))
const options = { const options = {
responseType: 'arraybuffer', responseType: 'arraybuffer',
body: await AES_CTR.encryptPbkdf2(this.productKey, encodeUtf8( JSON.stringify(httpOpts.body))), body: encryptedBody.buffer,
observe: 'events', observe: 'events',
reportProgress: false, reportProgress: false,
headers: { headers: {
'Content-Encoding': 'aesctr256', 'Content-Encoding': 'aesctr256',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
} as any } as any
const req = this.http.post(url, httpOpts.body, options) const req = this.http.post(url, options.body, options)
return (withTimeout(req, 60000)) return (withTimeout(req, 60000))
.toPromise() .toPromise()
.then(res => AES_CTR.decryptPbkdf2(this.productKey, new Uint8Array(res))) .then(res => AES_CTR.decryptPbkdf2(this.productKey, (res as any).body as ArrayBuffer))
.then(res => JSON.parse(decodeUtf8(res))) .then(res => JSON.parse(res))
.catch(e => { throw new HttpError(e) }) .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 this.details = null
} }
function EncryptionError (e: HttpErrorResponse): void {
this.code = null
this.message = 'Invalid Key'
this.details = null
}
function isRpcError<Error, Result> (arg: { error: Error } | { result: Result}): arg is { error: Error } { function isRpcError<Error, Result> (arg: { error: Error } | { result: Result}): arg is { error: Error } {
return !!(arg as any).error return !!(arg as any).error
} }
@@ -162,26 +176,32 @@ function withTimeout<U> (req: Observable<U>, timeout: number): Observable<U> {
type AES_CTR = { type AES_CTR = {
encryptPbkdf2: (secretKey: string, messageBuffer: Uint8Array) => Promise<Uint8Array> encryptPbkdf2: (secretKey: string, messageBuffer: Uint8Array) => Promise<Uint8Array>
decryptPbkdf2: (secretKey, arr: Uint8Array) => Promise<Uint8Array> decryptPbkdf2: (secretKey, arr: ArrayBuffer) => Promise<string>
} }
export const AES_CTR: AES_CTR = { export const AES_CTR: AES_CTR = {
encryptPbkdf2: async (secretKey: string, messageBuffer: Uint8Array) => { encryptPbkdf2: async (secretKey: string, messageBuffer: Uint8Array) => {
const salt = window.crypto.getRandomValues(new Uint8Array(16)) const salt = window.crypto.getRandomValues(new Uint8Array(16))
const counter = 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)); const key = pbkdf2.pbkdf2Sync(secretKey, salt, 1000, 256 / 8, 'sha256');
var encryptedBytes = aesCtr.encrypt(messageBuffer);
const aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(counter));
const encryptedBytes = aesCtr.encrypt(messageBuffer);
return new Uint8Array([...counter,...salt,...encryptedBytes]) return new Uint8Array([...counter,...salt,...encryptedBytes])
}, },
decryptPbkdf2: async (secretKey: string, arr: Uint8Array) => { decryptPbkdf2: async (secretKey: string, arr: ArrayBuffer) => {
const counter = arr.slice(0, 16) const buff = new Uint8Array(arr)
const salt = arr.slice(16, 32) const counter = buff.slice(0, 16)
const cipher = arr.slice(32) const salt = buff.slice(16, 32)
const key = pbkdf2.pbkdf2Sync(secretKey, salt, 1, 256 / 8, 'sha256');
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(counter)); const cipher = buff.slice(32)
return aesCtr.decrypt(cipher); 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);
}, },
} }

View File

@@ -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<void> {
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<void> {
if (this.toast) {
await this.toast.dismiss()
this.toast = undefined
}
}
}

View File

@@ -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) { @media (min-width:1000px) {
.alertlike-modal { .alertlike-modal {
.modal-wrapper { .modal-wrapper {