From 7165031fc8a49b1e472f451155b73e588658daac Mon Sep 17 00:00:00 2001 From: Spencer Ofwiti Date: Wed, 17 Feb 2021 13:00:38 +0300 Subject: [PATCH] Add pgp signer. --- package.json | 1 + src/app/_helpers/index.ts | 2 + src/app/_helpers/pgp-signer.spec.ts | 8 ++ src/app/_helpers/pgp-signer.ts | 102 ++++++++++++++++++ src/app/_services/user.service.ts | 59 +++++++--- .../account-details.component.html | 6 +- .../account-details.component.ts | 4 +- 7 files changed, 162 insertions(+), 20 deletions(-) create mode 100644 src/app/_helpers/pgp-signer.spec.ts create mode 100644 src/app/_helpers/pgp-signer.ts diff --git a/package.json b/package.json index ebec9e7..67a4b34 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "mocha": "^8.2.1", "moolb": "^0.1.0", "ng2-charts": "^2.4.2", + "openpgp": "^4.10.10", "popper.js": "^1.16.1", "rxjs": "~6.6.0", "tslib": "^2.0.0", diff --git a/src/app/_helpers/index.ts b/src/app/_helpers/index.ts index 838d00a..a706a44 100644 --- a/src/app/_helpers/index.ts +++ b/src/app/_helpers/index.ts @@ -3,3 +3,5 @@ export * from './array-sum'; export * from './accountIndex'; export * from './custom-error-state-matcher'; export * from './http-getter'; +export * from './pgp-signer'; + diff --git a/src/app/_helpers/pgp-signer.spec.ts b/src/app/_helpers/pgp-signer.spec.ts new file mode 100644 index 0000000..13e1308 --- /dev/null +++ b/src/app/_helpers/pgp-signer.spec.ts @@ -0,0 +1,8 @@ +import { PGPSigner } from './pgp-signer'; +let keystore; + +describe('PgpSigner', () => { + it('should create an instance', () => { + expect(new PGPSigner(keystore)).toBeTruthy(); + }); +}); diff --git a/src/app/_helpers/pgp-signer.ts b/src/app/_helpers/pgp-signer.ts new file mode 100644 index 0000000..f3eb9a4 --- /dev/null +++ b/src/app/_helpers/pgp-signer.ts @@ -0,0 +1,102 @@ +const openpgp = require('openpgp'); + +interface Signable { + digest(): string; +} + +type Signature = { + engine: string + algo: string + data: string + digest: string; +}; + +interface Signer { + onsign(signature: Signature): void; + onverify(flag: boolean): void; + fingerprint(): string; + prepare(material: Signable): boolean; + verify(digest: string, signature: Signature): void; + sign(digest: string): void; +} + +class PGPSigner implements Signer { + + engine = 'pgp'; + algo = 'sha256'; + dgst: string; + signature: Signature; + keyStore; + onsign: (signature: Signature) => void; + onverify: (flag: boolean) => void; + + constructor(keyStore) { + this.keyStore = keyStore; + this.onsign = (signature: Signature) => {}; + this.onverify = (flag: boolean) => {}; + } + + public fingerprint(): string { + return this.keyStore.getFingerprint(); + } + + public prepare(material: Signable): boolean { + this.dgst = material.digest(); + return true; + } + + public verify(digest: string, signature: Signature): void { + openpgp.signature.readArmored(signature.data).then((sig) => { + const opts = { + message: openpgp.cleartext.fromText(digest), + publicKeys: this.keyStore.getTrustedKeys(), + signature: sig, + }; + openpgp.verify(opts).then((v) => { + let i = 0; + for (i = 0; i < v.signatures.length; i++) { + const s = v.signatures[i]; + if (s.valid) { + this.onverify(s); + return; + } + } + console.error('checked ' + i + ' signature(s) but none valid'); + this.onverify(false); + }); + }).catch((e) => { + console.error(e); + this.onverify(false); + }); + } + + public sign(digest: string): void { + const m = openpgp.cleartext.fromText(digest); + const pk = this.keyStore.getPrivateKey(); + const opts = { + message: m, + privateKeys: [pk], + detached: true, + }; + openpgp.sign(opts).then((s) => { + this.signature = { + engine: this.engine, + algo: this.algo, + data: s.signature, + // TODO: fix for browser later + digest, + }; + this.onsign(this.signature); + }).catch((e) => { + console.error(e); + this.onsign(undefined); + }); + } +} + +export { + Signable, + Signature, + Signer, + PGPSigner +}; diff --git a/src/app/_services/user.service.ts b/src/app/_services/user.service.ts index 560c3bf..58224ec 100644 --- a/src/app/_services/user.service.ts +++ b/src/app/_services/user.service.ts @@ -3,12 +3,17 @@ import {BehaviorSubject, Observable, of} from 'rxjs'; import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http'; import {environment} from '../../environments/environment'; import {first, map} from 'rxjs/operators'; -import { Envelope, Syncable } from '../../assets/js/cic-meta/sync.js'; +import { ArgPair, Envelope, Syncable } from '../../assets/js/cic-meta/sync.js'; +import {PGPSigner, Signer} from '../_helpers'; @Injectable({ providedIn: 'root' }) export class UserService { + keystore = ''; + syncableAccount: Syncable; + signer: Signer = new PGPSigner(this.keystore); + accounts: any = ''; private accountsList = new BehaviorSubject(this.accounts); accountsSubject = this.accountsList.asObservable(); @@ -48,9 +53,9 @@ export class UserService { ); } - changeAccountInfo(address: string, status: string, name: string, phoneNumber: string, type: string, token: string, - failedPinAttempts: string, bio: string, gender: string, businessCategory: string, userLocation: string, - location: string, referrer: string): Observable { + async changeAccountInfo(address: string, status: string, name: string, phoneNumber: string, type: string, token: string, + failedPinAttempts: string, bio: string, gender: string, businessCategory: string, userLocation: string, + location: string, referrer: string): Promise { const accountDetails = { status, name, @@ -65,20 +70,32 @@ export class UserService { location, referrer }; - const addressStub = address.slice(2); - const headers = new HttpHeaders().append('x-cic-automerge', 'client'); - this.http.get(`${environment.cicMetaUrl}/${addressStub}`, { headers }).pipe(first()).subscribe(res => { - console.log(res); - const response = Envelope.fromJSON(res); - response.unwrap(); - console.log(response); - }, error => { + let addressStub = address.slice(2); + addressStub = '0000000000000000000000000000000000000000000000000000000000000000'; + const headers = new HttpHeaders({'x-cic-automerge': 'client'}); + this.http.get(`${environment.cicMetaUrl}/${addressStub}`, { headers }).pipe(first()).subscribe(async res => { + this.syncableAccount = Envelope.fromJSON(res).unwrap(); + let update = []; + for (const prop in accountDetails) { + update.push(new ArgPair(prop, accountDetails[prop])); + } + this.syncableAccount.update(update, 'client-branch'); + await this.updateMeta(addressStub, headers); + }, async error => { console.error('There is an error!', error); + const refName = addressStub + ':cic-person'; + this.syncableAccount = new Syncable(refName, accountDetails); + await this.updateMeta(addressStub, headers); }); - this.http.put(`${environment.cicMetaUrl}/${addressStub}`, { data: accountDetails }, { headers }).pipe(first()).subscribe(res => { + return addressStub; + } + + async updateMeta(addressStub: string, headers: HttpHeaders): Promise { + const envelope = await this.wrap(this.syncableAccount , this.signer); + const reqBody = envelope.toJSON(); + this.http.put(`${environment.cicMetaUrl}/${addressStub}`, { data: reqBody }, { headers }).pipe(first()).subscribe(res => { console.log(res); }); - return of(addressStub); } getAccounts(): void { @@ -136,4 +153,18 @@ export class UserService { return response; })); } + + wrap(syncable: Syncable, signer: Signer): Promise { + return new Promise((whohoo, doh) => { + syncable.setSigner(signer); + syncable.onwrap = async (env) => { + if (env === undefined) { + doh(); + return; + } + whohoo(env); + }; + syncable.sign(); + }); + } } diff --git a/src/app/pages/accounts/account-details/account-details.component.html b/src/app/pages/accounts/account-details/account-details.component.html index e6caa32..ab5e56c 100644 --- a/src/app/pages/accounts/account-details/account-details.component.html +++ b/src/app/pages/accounts/account-details/account-details.component.html @@ -219,14 +219,14 @@
-
-
diff --git a/src/app/pages/accounts/account-details/account-details.component.ts b/src/app/pages/accounts/account-details/account-details.component.ts index 54fa11c..bacc604 100644 --- a/src/app/pages/accounts/account-details/account-details.component.ts +++ b/src/app/pages/accounts/account-details/account-details.component.ts @@ -172,9 +172,7 @@ export class AccountDetailsComponent implements OnInit { this.accountInfoFormStub.userLocation.value, this.accountInfoFormStub.location.value, this.accountInfoFormStub.referrer.value, - ).pipe(first()).subscribe(res => { - console.log(res); - }); + ).then(res => console.log(res)); this.submitted = false; }