Skip to content

Commit

Permalink
[WIP] Add decrypt bytes
Browse files Browse the repository at this point in the history
public request
  • Loading branch information
LaurentTrk committed Sep 28, 2021
1 parent 32ae1a9 commit 29df8d2
Show file tree
Hide file tree
Showing 20 changed files with 1,597 additions and 22 deletions.
33 changes: 33 additions & 0 deletions packages/extension-base/src/background/RequestBytesDecrypt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2019-2021 @polkadot/extension authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { KeyringPair } from '@polkadot/keyring/types';
import type { DecryptPayloadRaw } from '@polkadot/types/types';
import type { RequestDecrypt } from './types';
import type { HexString } from '@polkadot/util/types';

import { wrapBytes } from '@polkadot/extension-dapp/wrapBytes';
import { TypeRegistry } from '@polkadot/types';
import { u8aToHex, hexToU8a } from '@polkadot/util';

export default class RequestBytesDecrypt implements RequestDecrypt {
public readonly payload: DecryptPayloadRaw;

constructor (payload: DecryptPayloadRaw) {
this.payload = payload;
}

decrypt (_registry: TypeRegistry, pair: KeyringPair, recipientPublicKey: HexString | string | Uint8Array): { decrypted: string } {
console.log("pair", pair);
console.log("this.payload.data", this.payload.data);
console.log("wrapBytes(this.payload.data)", wrapBytes(this.payload.data));
console.log("recipientPublicKey", recipientPublicKey);
return {
decrypted: u8aToHex(
pair.decryptMessage(
hexToU8a(this.payload.data), recipientPublicKey
)
)
};
}
}
132 changes: 125 additions & 7 deletions packages/extension-base/src/background/handlers/Extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { MetadataDef } from '@polkadot/extension-inject/types';
import type { KeyringPair, KeyringPair$Json, KeyringPair$Meta } from '@polkadot/keyring/types';
import type { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
import type { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import type { AccountJson, AllowedPath, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountBatchExport, RequestAccountChangePassword, RequestAccountCreateExternal, RequestAccountCreateHardware, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountForget, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestAuthorizeApprove, RequestAuthorizeReject, RequestBatchRestore, RequestDeriveCreate, RequestDeriveValidate, RequestJsonRestore, RequestMetadataApprove, RequestMetadataReject, RequestSeedCreate, RequestSeedValidate, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSigningIsLocked, RequestTypes, ResponseAccountExport, ResponseAccountsExport, ResponseAuthorizeList, ResponseDeriveValidate, ResponseJsonGetAccountInfo, ResponseSeedCreate, ResponseSeedValidate, ResponseSigningIsLocked, ResponseType, SigningRequest } from '../types';
import type { AccountJson, AllowedPath, AuthorizeRequest, DecryptingRequest, MessageTypes, MetadataRequest, RequestAccountBatchExport, RequestAccountChangePassword, RequestAccountCreateExternal, RequestAccountCreateHardware, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountForget, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestAuthorizeApprove, RequestAuthorizeReject, RequestBatchRestore, RequestDecryptingApprove, RequestDeriveCreate, RequestDeriveValidate, RequestJsonRestore, RequestMetadataApprove, RequestMetadataReject, RequestSeedCreate, RequestSeedValidate, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSigningIsLocked, RequestTypes, ResponseAccountExport, ResponseAccountsExport, ResponseAuthorizeList, ResponseDeriveValidate, ResponseJsonGetAccountInfo, ResponseSeedCreate, ResponseSeedValidate, ResponseSigningIsLocked, ResponseType, SigningRequest, RequestDecryptingCancel, RequestDecryptingApprovePassword } from '../types';

import { ALLOWED_PATH, PASSWORD_EXPIRY_MS } from '@polkadot/extension-base/defaults';
import chrome from '@polkadot/extension-inject/chrome';
Expand Down Expand Up @@ -409,19 +409,23 @@ export default class Extension {

private signingIsLocked ({ id }: RequestSigningIsLocked): ResponseSigningIsLocked {
const queued = this.#state.getSignRequest(id);
let address;
if (queued) {
address = queued.request.payload.address;
}else{
const decryptRequest = this.#state.getDecryptingRequest(id);
address = decryptRequest.request.payload.address;
}
assert(address, 'Unable to find address from request');

assert(queued, 'Unable to find request');

const address = queued.request.payload.address;
const pair = keyring.getPair(address);

assert(pair, 'Unable to find pair');

const remainingTime = this.refreshAccountPasswordCache(pair);

return {
isLocked: pair.isLocked,
remainingTime
remainingTime
};
}

Expand All @@ -440,6 +444,108 @@ export default class Extension {
return true;
}

private decryptingSubscribe (id: string, port: chrome.runtime.Port): boolean {
console.log('decryptingSubscribe');
const cb = createSubscription<'pri(decrypting.requests)'>(id, port);
const subscription = this.#state.decryptSubject.subscribe((requests: DecryptingRequest[]): void =>
cb(requests)
);

port.onDisconnect.addListener((): void => {
unsubscribe(id);
subscription.unsubscribe();
});

return true;
}

private decryptingApprove ({ id, decrypted }: RequestDecryptingApprove): boolean {
const queued = this.#state.getDecryptingRequest(id);

assert(queued, 'Unable to find request');

const { resolve } = queued;

resolve({ id, decrypted});

return true;
}

private decryptingApprovePassword ({ id, password, savePass }: RequestDecryptingApprovePassword): boolean {
const queued = this.#state.getDecryptingRequest(id);

assert(queued, 'Unable to find request');

const { reject, request, resolve } = queued;
const pair = keyring.getPair(queued.account.address);

// unlike queued.account.address the following
// address is encoded with the default prefix
// which what is used for password caching mapping
const { address } = pair;

if (!pair) {
reject(new Error('Unable to find pair'));

return false;
}

this.refreshAccountPasswordCache(pair);

// if the keyring pair is locked, the password is needed
if (pair.isLocked && !password) {
reject(new Error('Password needed to unlock the account'));
}

if (pair.isLocked) {
pair.decodePkcs8(password);
}

// const { payload } = request;

// if (isJsonPayload(payload)) {
// // Get the metadata for the genesisHash
// const currentMetadata = this.#state.knownMetadata.find((meta: MetadataDef) =>
// meta.genesisHash === payload.genesisHash);

// // set the registry before calling the sign function
// registry.setSignedExtensions(payload.signedExtensions, currentMetadata?.userExtensions);

// if (currentMetadata) {
// registry.register(currentMetadata?.types);
// }
// }

const result = request.decrypt(registry, pair, request.payload.recipientPublicKey);

if (savePass) {
this.#cachedUnlocks[address] = Date.now() + PASSWORD_EXPIRY_MS;
} else {
pair.lock();
}

resolve({
id,
...result
});

return true;
}


private decryptingCancel ({ id }: RequestDecryptingCancel): boolean {
const queued = this.#state.getDecryptingRequest(id);

assert(queued, 'Unable to find request');

const { reject } = queued;

reject(new Error('Cancelled'));

return true;
}


private windowOpen (path: AllowedPath): boolean {
const url = `${chrome.extension.getURL('index.html')}#${path}`;

Expand All @@ -448,7 +554,7 @@ export default class Extension {

return false;
}

console.log('open', url);

// eslint-disable-next-line no-void
Expand Down Expand Up @@ -608,6 +714,18 @@ export default class Extension {
case 'pri(signing.requests)':
return this.signingSubscribe(id, port);

case 'pri(decrypting.requests)':
return this.decryptingSubscribe(id, port);

case 'pri(decrypting.approve)':
return this.decryptingApprove(request as RequestDecryptingApprove);

case 'pri(decrypting.approve.password)':
return this.decryptingApprovePassword(request as RequestDecryptingApprovePassword);

case 'pri(decrypting.cancel)':
return this.decryptingCancel(request as RequestDecryptingCancel);

case 'pri(window.open)':
return this.windowOpen(request as AllowedPath);

Expand Down
73 changes: 70 additions & 3 deletions packages/extension-base/src/background/handlers/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import type { MetadataDef, ProviderMeta } from '@polkadot/extension-inject/types';
import type { JsonRpcResponse, ProviderInterface, ProviderInterfaceCallback } from '@polkadot/rpc-provider/types';
import type { AccountJson, AuthorizeRequest, MetadataRequest, RequestAuthorizeTab, RequestRpcSend, RequestRpcSubscribe, RequestRpcUnsubscribe, RequestSign, ResponseRpcListProviders, ResponseSigning, SigningRequest } from '../types';
import type { AccountJson, AuthorizeRequest, MetadataRequest, RequestAuthorizeTab, RequestDecrypt, RequestRpcSend, RequestRpcSubscribe, RequestRpcUnsubscribe, RequestSign, ResponseRpcListProviders, ResponseSigning, SigningRequest, DecryptingRequest, ResponseDecrypting } from '../types';

import { BehaviorSubject } from 'rxjs';

Expand Down Expand Up @@ -58,6 +58,13 @@ interface SignRequest extends Resolver<ResponseSigning> {
url: string;
}

interface DecryptRequest extends Resolver<ResponseDecrypting> {
account: AccountJson;
id: string;
request: RequestDecrypt;
url: string;
}

let idCounter = 0;

const NOTIFICATION_URL = chrome.extension.getURL('notification.html');
Expand Down Expand Up @@ -108,6 +115,8 @@ export default class State {
readonly #providers: Providers;

readonly #signRequests: Record<string, SignRequest> = {};

readonly #decryptRequests: Record<string, DecryptRequest> = {};

#windows: number[] = [];

Expand All @@ -116,6 +125,8 @@ export default class State {
public readonly metaSubject: BehaviorSubject<MetadataRequest[]> = new BehaviorSubject<MetadataRequest[]>([]);

public readonly signSubject: BehaviorSubject<SigningRequest[]> = new BehaviorSubject<SigningRequest[]>([]);

public readonly decryptSubject: BehaviorSubject<DecryptingRequest[]> = new BehaviorSubject<DecryptingRequest[]>([]);

constructor (providers: Providers = {}) {
this.#providers = providers;
Expand Down Expand Up @@ -147,6 +158,11 @@ export default class State {
return Object.keys(this.#signRequests).length;
}

public get numDecryptRequests (): number {
return Object.keys(this.#decryptRequests).length;
}


public get allAuthRequests (): AuthorizeRequest[] {
return Object
.values(this.#authRequests)
Expand All @@ -165,6 +181,12 @@ export default class State {
.map(({ account, id, request, url }): SigningRequest => ({ account, id, request, url }));
}

public get allDecryptRequests (): DecryptingRequest[] {
return Object
.values(this.#decryptRequests)
.map(({ account, id, request, url }): DecryptingRequest => ({ account, id, request, url }));
}

public get authUrls (): AuthUrls {
return this.#authUrls;
}
Expand Down Expand Up @@ -260,8 +282,26 @@ export default class State {
};
}

private decryptComplete = (id: string, resolve: (result: ResponseDecrypting) => void, reject: (error: Error) => void): Resolver<ResponseDecrypting> => {
const complete = (): void => {
delete this.#decryptRequests[id];
this.updateIconSign(true);
};

return {
reject: (error: Error): void => {
complete();
reject(error);
},
resolve: (result: ResponseDecrypting): void => {
complete();
resolve(result);
}
};
}

private stripUrl (url: string): string {
assert(url && (url.startsWith('http:') || url.startsWith('https:') || url.startsWith('ipfs:') || url.startsWith('ipns:')), `Invalid url ${url}, expected to start with http: or https: or ipfs: or ipns:`);
assert(url && (url.startsWith('http:') || url.startsWith('https:') || url.startsWith('ipfs:') || url.startsWith('ipns:') || url.startsWith('chrome-extension:')), `Invalid url ${url}, expected to start with http: or https: or ipfs: or ipns:`);

const parts = url.split('/');

Expand All @@ -272,12 +312,13 @@ export default class State {
const authCount = this.numAuthRequests;
const metaCount = this.numMetaRequests;
const signCount = this.numSignRequests;
const decryptCount = this.numDecryptRequests;
const text = (
authCount
? 'Auth'
: metaCount
? 'Meta'
: (signCount ? `${signCount}` : '')
: (signCount ? `${signCount}` : decryptCount ? `${decryptCount}` : '')
);

// eslint-disable-next-line no-void
Expand Down Expand Up @@ -314,6 +355,11 @@ export default class State {
this.updateIcon(shouldClose);
}

private updateIconDecrypt (shouldClose?: boolean): void {
this.decryptSubject.next(this.allDecryptRequests);
this.updateIcon(shouldClose);
}

public async authorizeUrl (url: string, request: RequestAuthorizeTab): Promise<boolean> {
const idStr = this.stripUrl(url);

Expand Down Expand Up @@ -383,6 +429,10 @@ export default class State {
return this.#signRequests[id];
}

public getDecryptingRequest (id: string): DecryptRequest {
return this.#decryptRequests[id];
}

// List all providers the extension is exposing
public rpcListProviders (): Promise<ResponseRpcListProviders> {
return Promise.resolve(Object.keys(this.#providers).reduce((acc, key) => {
Expand Down Expand Up @@ -479,4 +529,21 @@ export default class State {
this.popupOpen();
});
}

public decrypt (url: string, request: RequestDecrypt, account: AccountJson): Promise<ResponseDecrypting> {
const id = getId();

return new Promise((resolve, reject): void => {
this.#decryptRequests[id] = {
...this.decryptComplete(id, resolve, reject),
account,
id,
request,
url
};

this.updateIconDecrypt();
this.popupOpen();
});
}
}
Loading

0 comments on commit 29df8d2

Please sign in to comment.