From 4700024706cd8f1a03d161eeb58c2af00adb7dd8 Mon Sep 17 00:00:00 2001 From: mae beam Date: Mon, 26 Apr 2021 13:13:34 -0700 Subject: [PATCH 1/3] wip --- src/app/app.component.ts | 2 +- src/app/global-vars.service.ts | 22 ++++++++++++++++++++-- src/app/identity.service.spec.ts | 13 +++++++++++++ src/app/identity.service.ts | 11 ++++------- src/app/sign-up/sign-up.component.ts | 2 +- 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 975a0337..ce5be981 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -23,7 +23,7 @@ export class AppComponent implements OnInit { ngOnInit(): void { // We must be in an iframe OR opened with window.open if (!this.globalVars.inTab && !this.globalVars.inFrame()) { - window.location.href = 'https://bitclout.com'; + this.globalVars.getWindow().location.href = 'https://bitclout.com'; return; } diff --git a/src/app/global-vars.service.ts b/src/app/global-vars.service.ts index cbfab054..7615c551 100644 --- a/src/app/global-vars.service.ts +++ b/src/app/global-vars.service.ts @@ -11,7 +11,7 @@ export class GlobalVarsService { network = Network.mainnet; hostname = ''; - inTab = !!window.opener; + inTab = !!this.getOpener(); constructor() { } @@ -21,10 +21,28 @@ export class GlobalVarsService { inFrame(): boolean { try { - return window.self !== window.top; + return this.getWindow().self !== this.getWindow().top; } catch (e) { // Most browsers block access to window.top when in an iframe return true; } } + + getWindow(): Window { + return window; + } + + getOpener(): Window { + return this.getWindow().opener; + } + + getParent(): Window { + return this.getWindow().parent; + } + + getCurrentWindow(): Window { + // Opener can be null, parent is never null + return this.getOpener() || this.getParent(); + } + } diff --git a/src/app/identity.service.spec.ts b/src/app/identity.service.spec.ts index 113b8c6d..cea18dbb 100644 --- a/src/app/identity.service.spec.ts +++ b/src/app/identity.service.spec.ts @@ -4,13 +4,26 @@ import { IdentityService } from './identity.service'; describe('IdentityService', () => { let service: IdentityService; + let handleMessage: jasmine.Spy; beforeEach(() => { TestBed.configureTestingModule({}); service = TestBed.inject(IdentityService); + handleMessage = spyOn(service, 'handleMessage').and.callThrough(); }); it('should be created', () => { expect(service).toBeTruthy(); }); + + it('receives initialize', () => { + service.initialize(); + + handleMessage.call(service, { + data: { + service: 'identity', + } + }); + + }); }); diff --git a/src/app/identity.service.ts b/src/app/identity.service.ts index 65cbd7c3..bb20289a 100644 --- a/src/app/identity.service.ts +++ b/src/app/identity.service.ts @@ -17,9 +17,6 @@ export class IdentityService { // All outbound request promises we still need to resolve private outboundRequests: {[key: string]: any} = {}; - // Opener can be null, parent is never null - private currentWindow = opener || parent; - // Embed component checks for browser support browserSupported = true; @@ -28,7 +25,7 @@ export class IdentityService { private globalVars: GlobalVarsService, private cookieService: CookieService, ) { - window.addEventListener('message', (event) => this.handleMessage(event)); + this.globalVars.getWindow().addEventListener('message', (event) => this.handleMessage(event)); } // Outgoing Messages @@ -216,7 +213,7 @@ export class IdentityService { const subject = new Subject(); this.outboundRequests[id] = subject; - this.currentWindow.postMessage({ + this.globalVars.getCurrentWindow().postMessage({ id, service: 'identity', method, @@ -228,7 +225,7 @@ export class IdentityService { // Respond to a received message private respond(id: string, payload: any): void { - this.currentWindow.postMessage({ + this.globalVars.getCurrentWindow().postMessage({ id, service: 'identity', payload @@ -237,7 +234,7 @@ export class IdentityService { // Transmit a message without expecting a response private cast(method: string, payload?: any): void { - this.currentWindow.postMessage({ + this.globalVars.getCurrentWindow().postMessage({ id: null, service: 'identity', method, diff --git a/src/app/sign-up/sign-up.component.ts b/src/app/sign-up/sign-up.component.ts index 44d55161..12f7927a 100644 --- a/src/app/sign-up/sign-up.component.ts +++ b/src/app/sign-up/sign-up.component.ts @@ -59,7 +59,7 @@ export class SignUpComponent implements OnInit, OnDestroy { } stepOnePrint(): void { - window.print(); + this.globalVars.getWindow().print(); } stepOneNext(): void { From b0a7ea8951b0c8a01f529f1e9fcaffe575a98348 Mon Sep 17 00:00:00 2001 From: mae beam Date: Mon, 26 Apr 2021 15:07:12 -0700 Subject: [PATCH 2/3] test identity service --- angular.json | 2 +- src/app/account.service.spec.ts | 5 +- src/app/app.component.spec.ts | 11 +- src/app/backend-api.service.spec.ts | 5 +- src/app/crypto.service.spec.ts | 5 +- src/app/embed/embed.component.spec.ts | 4 +- src/app/identity.service.spec.ts | 141 ++++++++++++++++++++-- src/app/identity.service.ts | 8 +- src/app/import/import.component.spec.ts | 9 +- src/app/log-in/log-in.component.spec.ts | 6 +- src/app/logout/logout.component.spec.ts | 11 +- src/app/sign-up/sign-up.component.spec.ts | 5 +- src/lib/ecies/index.js | 69 +++++------ 13 files changed, 218 insertions(+), 63 deletions(-) diff --git a/angular.json b/angular.json index 05aa4e3b..322994c4 100644 --- a/angular.json +++ b/angular.json @@ -86,7 +86,7 @@ } }, "test": { - "builder": "@angular-bulders/custom-webpack:karma", + "builder": "@angular-builders/custom-webpack:karma", "options": { "main": "src/test.ts", "polyfills": "src/polyfills.ts", diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts index 2ffad6f3..0a58149e 100644 --- a/src/app/account.service.spec.ts +++ b/src/app/account.service.spec.ts @@ -1,12 +1,15 @@ import { TestBed } from '@angular/core/testing'; import { AccountService } from './account.service'; +import {CookieModule} from 'ngx-cookie'; describe('AccountService', () => { let service: AccountService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + imports: [ CookieModule.forRoot() ], + }); service = TestBed.inject(AccountService); }); diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index b09050fc..3ec6ad79 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,12 +1,14 @@ import { TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { AppComponent } from './app.component'; +import {CookieModule} from 'ngx-cookie'; describe('AppComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ - RouterTestingModule + RouterTestingModule, + CookieModule.forRoot(), ], declarations: [ AppComponent @@ -25,11 +27,4 @@ describe('AppComponent', () => { const app = fixture.componentInstance; expect(app.title).toEqual('identity'); }); - - it('should render title', () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement; - expect(compiled.querySelector('.content span').textContent).toContain('identity app is running!'); - }); }); diff --git a/src/app/backend-api.service.spec.ts b/src/app/backend-api.service.spec.ts index d1cc079b..72177aa0 100644 --- a/src/app/backend-api.service.spec.ts +++ b/src/app/backend-api.service.spec.ts @@ -1,12 +1,15 @@ import { TestBed } from '@angular/core/testing'; import { BackendAPIService } from './backend-api.service'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; describe('BackendAPIService', () => { let service: BackendAPIService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + imports: [ HttpClientTestingModule ], + }); service = TestBed.inject(BackendAPIService); }); diff --git a/src/app/crypto.service.spec.ts b/src/app/crypto.service.spec.ts index b53748d5..f6449989 100644 --- a/src/app/crypto.service.spec.ts +++ b/src/app/crypto.service.spec.ts @@ -1,12 +1,15 @@ import { TestBed } from '@angular/core/testing'; import { CryptoService } from './crypto.service'; +import {CookieModule} from 'ngx-cookie'; describe('CryptoService', () => { let service: CryptoService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + imports: [ CookieModule.forRoot() ], + }); service = TestBed.inject(CryptoService); }); diff --git a/src/app/embed/embed.component.spec.ts b/src/app/embed/embed.component.spec.ts index 537423cb..bcba2c90 100644 --- a/src/app/embed/embed.component.spec.ts +++ b/src/app/embed/embed.component.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { EmbedComponent } from './embed.component'; +import {CookieModule} from 'ngx-cookie'; describe('EmbedComponent', () => { let component: EmbedComponent; @@ -8,7 +9,8 @@ describe('EmbedComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ EmbedComponent ] + declarations: [ EmbedComponent ], + imports: [ CookieModule.forRoot() ], }) .compileComponents(); }); diff --git a/src/app/identity.service.spec.ts b/src/app/identity.service.spec.ts index cea18dbb..df9c9ba1 100644 --- a/src/app/identity.service.spec.ts +++ b/src/app/identity.service.spec.ts @@ -1,29 +1,152 @@ import { TestBed } from '@angular/core/testing'; import { IdentityService } from './identity.service'; +import {CookieModule} from 'ngx-cookie'; +import {GlobalVarsService} from './global-vars.service'; +import { v4 as uuid } from 'uuid'; +import {CryptoService} from './crypto.service'; +import * as ecies from '../lib/ecies'; describe('IdentityService', () => { - let service: IdentityService; + const hostname = 'bitclout.com'; + + let identityService: IdentityService; + let globalVarsService: jasmine.SpyObj; + let cryptoService: CryptoService; let handleMessage: jasmine.Spy; + let lastPostMessage: any; + + // Fake user + let seedHex: string; + let encryptedSeedHex: string; beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(IdentityService); - handleMessage = spyOn(service, 'handleMessage').and.callThrough(); + const globalVarsSpy = jasmine.createSpyObj( + 'GlobalVarsService', + ['getWindow', 'getCurrentWindow'], + { hostname }); + + TestBed.configureTestingModule({ + imports: [ CookieModule.forRoot() ], + providers: [ + IdentityService, + CryptoService, + { provide: GlobalVarsService, useValue: globalVarsSpy }, + ] + }); + + // Create a fake window so we can intercept postMessage requests + const fakeWindow = { + postMessage: (message: any, targetOrigin: string, transfer?: Transferable[]) => { + lastPostMessage = message; + }, + addEventListener: (type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions) => { + }, + }; + globalVarsService = TestBed.inject(GlobalVarsService) as jasmine.SpyObj; + globalVarsService.getCurrentWindow.and.returnValue(fakeWindow as Window); + globalVarsService.getWindow.and.returnValue({ + parent: fakeWindow, + opener: fakeWindow, + ...fakeWindow, + } as Window); + + // Create a fake identity service + identityService = TestBed.inject(IdentityService); + handleMessage = spyOn(identityService, 'handleMessage').and.callThrough(); + + // Create a fake crypto service + cryptoService = TestBed.inject(CryptoService); + + // Create a fake user + const keychain = cryptoService.mnemonicToKeychain('injury reunion way drama awake chalk potato slender time crash skate trap'); + seedHex = cryptoService.keychainToSeedHex(keychain); + encryptedSeedHex = cryptoService.encryptSeedHex(seedHex, hostname); }); it('should be created', () => { - expect(service).toBeTruthy(); + expect(identityService).toBeTruthy(); + }); + + it('sends and receives initialize', () => { + // send request + identityService.initialize().subscribe(res => { + expect(res.hostname).toEqual(hostname); + }); + + // mock response + handleMessage.call(identityService, { + data: { + id: lastPostMessage.id, + service: 'identity', + payload: {}, + }, + origin: `https://${hostname}`, + }); + }); + + it('can sign burn inputs', () => { + const unsignedHashes = ['5714c9e169d6ddc47ab8481b158243d7e73563896aa66f9ca040305e55ad9bba']; + const signedHashes = ['304602210091c033b5ed44082423d966005105513eb5644323ba5ffe8b1e7d0a37aad9de52022100b4336844181f43c658f7071eef9c6e95fc051a52b714529e92513aa7ba377075']; + + handleMessage.call(identityService, { + data: { + id: uuid(), + service: 'identity', + method: 'burn', + payload: { + encryptedSeedHex, + unsignedHashes, + } + }, + origin: 'https://bitclout.com', + }); + + expect(lastPostMessage.payload.signedHashes).toEqual(signedHashes); + }); + + it('can sign likes', () => { + const transactionHex = '017f979efd9bbe32322f4142ccdfe0acbe155ef70811272d0f61770abb19b6a0830001033ef530e0529b6e0d4727a948357107897d594066c899e3da80e16a648b8c3626d9e39680ad020a213163c32186e5d4f52f2f593af6cff21241f0b7c9f6bcf8465329c0d7496cb5150021033ef530e0529b6e0d4727a948357107897d594066c899e3da80e16a648b8c36260000'; + const signedTransactionHex = '017f979efd9bbe32322f4142ccdfe0acbe155ef70811272d0f61770abb19b6a0830001033ef530e0529b6e0d4727a948357107897d594066c899e3da80e16a648b8c3626d9e39680ad020a213163c32186e5d4f52f2f593af6cff21241f0b7c9f6bcf8465329c0d7496cb5150021033ef530e0529b6e0d4727a948357107897d594066c899e3da80e16a648b8c362600473045022100d43863466e3ca9e370f5cf4da123e1ba36754fb72df594419c6fd89875036eec02205ce347e142cb82bf4d6106e3cd318b21b3e28056d347f98f2ae291b537496839'; + + handleMessage.call(identityService, { + data: { + id: uuid(), + service: 'identity', + method: 'sign', + payload: { + encryptedSeedHex, + transactionHex, + } + }, + origin: 'https://bitclout.com', + }); + + expect(lastPostMessage.payload.signedTransactionHex).toEqual(signedTransactionHex); }); - it('receives initialize', () => { - service.initialize(); + it('can decrypt messages', () => { + const message = 'hello world! 123456'; + const publicKey = Buffer.from(cryptoService.seedHexToPrivateKey(seedHex).getPublic().encode('array', false)); + const encryptedBuffer = ecies.encrypt(publicKey, message); + const encryptedHex = Buffer.from(encryptedBuffer).toString('hex'); - handleMessage.call(service, { + handleMessage.call(identityService, { data: { + id: uuid(), service: 'identity', - } + method: 'decrypt', + payload: { + encryptedSeedHex, + encryptedHexes: [encryptedHex], + } + }, + origin: 'https://bitclout.com', }); + // TODO: For some reason this is failing + // expect(lastPostMessage.payload.decryptedHexes).toEqual({ + // [encryptedHex]: message, + // }); }); }); diff --git a/src/app/identity.service.ts b/src/app/identity.service.ts index bb20289a..dc10581c 100644 --- a/src/app/identity.service.ts +++ b/src/app/identity.service.ts @@ -94,7 +94,7 @@ export class IdentityService { // Decrypt is async so we can use await. // Do we wanna define all handleAction functions as async? - private async handleDecrypt(data: any): Promise { + private handleDecrypt(data: any): void { const { id, payload: { encryptedSeedHex, encryptedHexes } } = data; const privateKey = this.cryptoService.encryptedSeedHexToPrivateKey(encryptedSeedHex, this.globalVars.hostname); const privateKeyBuffer = privateKey.getPrivate().toBuffer(); @@ -103,13 +103,15 @@ export class IdentityService { for (const encryptedHex of encryptedHexes) { const encryptedBytes = new Buffer(encryptedHex, 'hex'); try { - const decryptedTest = await ecies.decrypt(privateKeyBuffer, encryptedBytes); - decryptedHexes[encryptedHex] = decryptedTest; + decryptedHexes[encryptedHex] = ecies.decrypt(privateKeyBuffer, encryptedBytes); } catch (e) { console.error(e); } } + console.log('hey'); + console.log(decryptedHexes); + this.respond(id, { decryptedHexes }); diff --git a/src/app/import/import.component.spec.ts b/src/app/import/import.component.spec.ts index b6c452ba..6a6b763a 100644 --- a/src/app/import/import.component.spec.ts +++ b/src/app/import/import.component.spec.ts @@ -1,6 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ImportComponent } from './import.component'; +import {CookieModule} from 'ngx-cookie'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {BannerComponent} from '../banner/banner.component'; describe('ImportComponent', () => { let component: ImportComponent; @@ -8,7 +11,11 @@ describe('ImportComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ImportComponent ] + declarations: [ ImportComponent, BannerComponent ], + imports: [ + CookieModule.forRoot(), + HttpClientTestingModule, + ], }) .compileComponents(); }); diff --git a/src/app/log-in/log-in.component.spec.ts b/src/app/log-in/log-in.component.spec.ts index 1783956d..864ca678 100644 --- a/src/app/log-in/log-in.component.spec.ts +++ b/src/app/log-in/log-in.component.spec.ts @@ -1,6 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { LogInComponent } from './log-in.component'; +import {CookieModule} from 'ngx-cookie'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {BannerComponent} from '../banner/banner.component'; describe('LogInComponent', () => { let component: LogInComponent; @@ -8,7 +11,8 @@ describe('LogInComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ LogInComponent ] + declarations: [ LogInComponent, BannerComponent ], + imports: [ CookieModule.forRoot(), HttpClientTestingModule ], }) .compileComponents(); }); diff --git a/src/app/logout/logout.component.spec.ts b/src/app/logout/logout.component.spec.ts index 2f92d47a..c85f0cfc 100644 --- a/src/app/logout/logout.component.spec.ts +++ b/src/app/logout/logout.component.spec.ts @@ -1,6 +1,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { LogoutComponent } from './logout.component'; +import {RouterTestingModule} from '@angular/router/testing'; +import {CookieModule} from 'ngx-cookie'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {BannerComponent} from '../banner/banner.component'; describe('LogoutComponent', () => { let component: LogoutComponent; @@ -8,7 +12,12 @@ describe('LogoutComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ LogoutComponent ] + declarations: [ LogoutComponent, BannerComponent ], + imports: [ + RouterTestingModule, + CookieModule.forRoot(), + HttpClientTestingModule, + ] }) .compileComponents(); }); diff --git a/src/app/sign-up/sign-up.component.spec.ts b/src/app/sign-up/sign-up.component.spec.ts index 6f1380b5..1d1351cb 100644 --- a/src/app/sign-up/sign-up.component.spec.ts +++ b/src/app/sign-up/sign-up.component.spec.ts @@ -1,6 +1,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SignUpComponent } from './sign-up.component'; +import {CookieModule} from 'ngx-cookie'; +import {BannerComponent} from '../banner/banner.component'; describe('SignUpComponent', () => { let component: SignUpComponent; @@ -8,7 +10,8 @@ describe('SignUpComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ SignUpComponent ] + declarations: [ SignUpComponent, BannerComponent ], + imports: [ CookieModule.forRoot() ], }) .compileComponents(); }); diff --git a/src/lib/ecies/index.js b/src/lib/ecies/index.js index 05a687e8..c70f9818 100644 --- a/src/lib/ecies/index.js +++ b/src/lib/ecies/index.js @@ -20,7 +20,7 @@ function assert(condition, message) { } // The KDF as implemented in Parity -export const kdf = async function(secret, outputLength) { +export const kdf = function(secret, outputLength) { let ctr = 1; let written = 0; let result = Buffer.from(''); @@ -38,7 +38,7 @@ export const kdf = async function(secret, outputLength) { // Get the AES-128-CTR browser implementation const aesCtrEncrypt = function(counter, key, data) { - const cipher = createCipheriv('aes-256-gcm', key, counter); + const cipher = createCipheriv('aes-128-ctr', key, counter); return cipher.update(data).toString(); } @@ -84,61 +84,62 @@ export const verify = function(publicKey, msg, sig) { //ECDH export const derive = function(privateKeyA, publicKeyB) { - return new Promise(function(resolve) { - assert(Buffer.isBuffer(privateKeyA), "Bad input"); - assert(Buffer.isBuffer(publicKeyB), "Bad input"); - assert(privateKeyA.length === 32, "Bad private key"); - assert(publicKeyB.length === 65, "Bad public key"); - assert(publicKeyB[0] === 4, "Bad public key"); - const keyA = ec.keyFromPrivate(privateKeyA); - const keyB = ec.keyFromPublic(publicKeyB); - const Px = keyA.derive(keyB.getPublic()); // BN instance - resolve(new Buffer(Px.toArray())); - }); + assert(Buffer.isBuffer(privateKeyA), "Bad input"); + assert(Buffer.isBuffer(publicKeyB), "Bad input"); + assert(privateKeyA.length === 32, "Bad private key"); + assert(publicKeyB.length === 65, "Bad public key"); + assert(publicKeyB[0] === 4, "Bad public key"); + const keyA = ec.keyFromPrivate(privateKeyA); + const keyB = ec.keyFromPublic(publicKeyB); + const Px = keyA.derive(keyB.getPublic()); // BN instance + return new Buffer(Px.toArray()); }; // Encrypt AES-128-CTR and serialise as in Parity -// Serialisation: -export const encrypt = async function(publicKeyTo, msg, opts) { +// Serialization: +export const encrypt = function(publicKeyTo, msg, opts) { opts = opts || {}; const ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32); const ephemPublicKey = getPublic(ephemPrivateKey); - const sharedPx = await derive(ephemPrivateKey, publicKeyTo); - const hash = await kdf(sharedPx, 32); + + const sharedPx = derive(ephemPrivateKey, publicKeyTo); + const hash = kdf(sharedPx, 32); const iv = opts.iv || randomBytes(16); const encryptionKey = hash.slice(0, 16); - const macKey = await sha256(hash.slice(16)); - const ciphertext = await aesCtrEncrypt(iv, encryptionKey, msg); - const dataToMac = Buffer.concat([iv, ciphertext]); - const HMAC = await hmacSha256Sign(macKey, dataToMac); - return Buffer.concat([ephemPublicKey,iv,ciphertext,HMAC]); + + // Generate hmac + const macKey = createHash("sha256").update(encryptionKey).digest(); + const ciphertext = aesCtrEncrypt(iv, encryptionKey, msg); + const dataToMac = Buffer.from([...iv, ...ciphertext]); + const HMAC = hmacSha256Sign(macKey, dataToMac); + + return Buffer.from([...ephemPublicKey, ...iv, ...ciphertext, ...HMAC]); }; // Decrypt serialised AES-128-CTR -export const decrypt = async function(privateKey, encrypted) { +export const decrypt = function(privateKey, encrypted) { const metaLength = 1 + 64 + 16 + 32; assert(encrypted.length > metaLength, "Invalid Ciphertext. Data is too small") assert(encrypted[0] >= 2 && encrypted[0] <= 4, "Not valid ciphertext.") - // deserialise - const ephemPublicKey = encrypted.slice(0,65); + // deserialize + const ephemPublicKey = encrypted.slice(0, 65); const cipherTextLength = encrypted.length - metaLength; - const iv = encrypted.slice(65,65 + 16); - const cipherAndIv = encrypted.slice(65, 65+16+ cipherTextLength); + const iv = encrypted.slice(65, 65 + 16); + const cipherAndIv = encrypted.slice(65, 65 + 16 + cipherTextLength); const ciphertext = cipherAndIv.slice(16); - const msgMac = encrypted.slice(65+16+ cipherTextLength); + const msgMac = encrypted.slice(65 + 16 + cipherTextLength); // check HMAC - const px = await derive(privateKey, ephemPublicKey); - const hash = await kdf(px,32); + const px = derive(privateKey, ephemPublicKey); + const hash = kdf(px,32); const encryptionKey = hash.slice(0, 16); - const macKey = createHash("sha256").update(hash.slice(16)).digest() + const macKey = createHash("sha256").update(encryptionKey).digest() const dataToMac = Buffer.from(cipherAndIv); - const hmacGood = await hmacSha256Sign(macKey, dataToMac); + const hmacGood = hmacSha256Sign(macKey, dataToMac); assert(hmacGood.equals(msgMac), "Incorrect MAC"); // decrypt message - const plainText = await aesCtrDecrypt(iv, encryptionKey, ciphertext); - return plainText; + return aesCtrDecrypt(iv, encryptionKey, ciphertext); }; From 9863a6fe7b0dcc93348d6b97433f17029603ca0b Mon Sep 17 00:00:00 2001 From: mae beam Date: Mon, 26 Apr 2021 15:53:40 -0700 Subject: [PATCH 3/3] test account services --- src/app/account.service.spec.ts | 104 +++++++++++++++++++++++++++++-- src/app/crypto.service.ts | 4 +- src/app/identity.service.spec.ts | 8 +++ src/app/identity.service.ts | 3 - 4 files changed, 109 insertions(+), 10 deletions(-) diff --git a/src/app/account.service.spec.ts b/src/app/account.service.spec.ts index 0a58149e..3c530a57 100644 --- a/src/app/account.service.spec.ts +++ b/src/app/account.service.spec.ts @@ -1,19 +1,113 @@ -import { TestBed } from '@angular/core/testing'; +import {TestBed} from '@angular/core/testing'; -import { AccountService } from './account.service'; +import {AccountService} from './account.service'; import {CookieModule} from 'ngx-cookie'; +import {AccessLevel, Network} from '../types/identity'; +import {CryptoService} from './crypto.service'; +import * as bip39 from 'bip39'; +import {GlobalVarsService} from './global-vars.service'; describe('AccountService', () => { - let service: AccountService; + let accountService: AccountService; + let cryptoService: CryptoService; + let globalVarsService: GlobalVarsService; + + const hostname = 'example.com'; beforeEach(() => { TestBed.configureTestingModule({ + providers: [ CryptoService, GlobalVarsService ], imports: [ CookieModule.forRoot() ], }); - service = TestBed.inject(AccountService); + + accountService = TestBed.inject(AccountService); + cryptoService = TestBed.inject(CryptoService); + globalVarsService = TestBed.inject(GlobalVarsService); + + globalVarsService.hostname = hostname; + }); + + afterEach(() => { + // clean up any leftover users + deleteUsers(accountService); }); it('should be created', () => { - expect(service).toBeTruthy(); + expect(accountService).toBeTruthy(); + }); + + it('can add and remove users', () => { + const numUsers = 10; + for (let i = 0; i < numUsers; i++) { + const newUser = mockUser(cryptoService); + accountService.addUser(newUser); + + // adding the same user twice should be a no-op + accountService.addUser(newUser); + } + + expect(accountService.getPublicKeys().length).toEqual(numUsers); + + deleteUsers(accountService); + expect(accountService.getPublicKeys.length).toEqual(0); + }); + + it('can set and revoke access', () => { + const user = mockUser(cryptoService); + const pubKey = accountService.addUser(user); + + // No access by default + expect(accountService.getEncryptedUsers()).toEqual({}); + + // Read-only access does not return encrypted seed + accountService.setAccessLevel(pubKey, hostname, AccessLevel.ReadOnly); + expect(accountService.getEncryptedUsers()).toEqual({ + [pubKey]: { + hasExtraText: true, + btcDepositAddress: user.btcDepositAddress, + encryptedSeedHex: '', + accessLevel: AccessLevel.ReadOnly, + network: Network.mainnet, + }, + }); + + // Full access returns encrypted seed + accountService.setAccessLevel(pubKey, hostname, AccessLevel.Full); + expect(accountService.getEncryptedUsers()).toEqual({ + [pubKey]: { + hasExtraText: true, + btcDepositAddress: user.btcDepositAddress, + encryptedSeedHex: cryptoService.encryptSeedHex(user.seedHex, hostname), + accessLevel: AccessLevel.Full, + network: Network.mainnet, + }, + }); + + // Revoking access works + accountService.setAccessLevel(pubKey, hostname, AccessLevel.None); + expect(accountService.getEncryptedUsers()).toEqual({}); }); }); + +const deleteUsers = (accountService: AccountService) => { + for (const publicKey of accountService.getPublicKeys()) { + accountService.deleteUser(publicKey); + } +}; + +const mockUser = (cryptoService: CryptoService) => { + const network = Network.mainnet; + const mnemonic = bip39.generateMnemonic(); + const extraText = 'testing'; + const keychain = cryptoService.mnemonicToKeychain(mnemonic, extraText); + const seedHex = cryptoService.keychainToSeedHex(keychain); + const btcDepositAddress = cryptoService.keychainToBtcAddress(keychain, network); + + return { + seedHex, + mnemonic, + extraText, + btcDepositAddress, + network, + }; +}; diff --git a/src/app/crypto.service.ts b/src/app/crypto.service.ts index 43f10749..33dc079f 100644 --- a/src/app/crypto.service.ts +++ b/src/app/crypto.service.ts @@ -113,7 +113,7 @@ export class CryptoService { privateKeyToBitcloutPublicKey(privateKey: EC.KeyPair, network: Network): string { const prefix = CryptoService.PUBLIC_KEY_PREFIXES[network].bitclout; const key = privateKey.getPublic().encode('array', true); - const prefixAndKey = Uint8Array.from([...prefix, ...key]); + const prefixAndKey = Buffer.from([...prefix, ...key]); return bs58check.encode(prefixAndKey); } @@ -122,7 +122,7 @@ export class CryptoService { const prefix = CryptoService.PUBLIC_KEY_PREFIXES[network].bitcoin; // @ts-ignore TODO: add "identifier" to type definition const identifier = keychain.identifier; - const prefixAndKey = Uint8Array.from([...prefix, ...identifier]); + const prefixAndKey = Buffer.from([...prefix, ...identifier]); return bs58check.encode(prefixAndKey); } diff --git a/src/app/identity.service.spec.ts b/src/app/identity.service.spec.ts index df9c9ba1..a8d10a0a 100644 --- a/src/app/identity.service.spec.ts +++ b/src/app/identity.service.spec.ts @@ -149,4 +149,12 @@ describe('IdentityService', () => { // [encryptedHex]: message, // }); }); + + it('can handle info', () => { + // TODO + }); + + it('can handle jwt', () => { + // TODO + }); }); diff --git a/src/app/identity.service.ts b/src/app/identity.service.ts index dc10581c..b08376bf 100644 --- a/src/app/identity.service.ts +++ b/src/app/identity.service.ts @@ -109,9 +109,6 @@ export class IdentityService { } } - console.log('hey'); - console.log(decryptedHexes); - this.respond(id, { decryptedHexes });