Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7973cb7
wire EKM for live gmail test
rrrooommmaaa Jun 6, 2022
26155d2
fix tooling scripts
rrrooommmaaa Jun 6, 2022
5c38087
Merge remote-tracking branch 'origin/master' into issue-2602-pull-fro…
rrrooommmaaa Jun 12, 2022
bc092e7
finished merge and refactoring
rrrooommmaaa Jun 13, 2022
b8ed25f
support and implementation of mocked test for setup-webmail-content-s…
rrrooommmaaa Jun 18, 2022
049a790
Added base private key for auto-updates
rrrooommmaaa Jun 18, 2022
068155d
fix
rrrooommmaaa Jun 19, 2022
b16f99a
Merge remote-tracking branch 'origin/master' into issue-2602-pull-fro…
rrrooommmaaa Jun 28, 2022
2de153b
removed live test
rrrooommmaaa Jun 28, 2022
6cc4d64
reverted debug constants
rrrooommmaaa Jun 28, 2022
f49b12a
tidying up
rrrooommmaaa Jun 28, 2022
78421b5
use https://gmail.localhost:8001/gmail to avoid interference with Chr…
rrrooommmaaa Jun 28, 2022
e9f2d5f
Renamed the object for test KM's updating key
rrrooommmaaa Jun 29, 2022
f6bd98a
Merge remote-tracking branch 'origin/master' into issue-2602-pull-fro…
rrrooommmaaa Jul 2, 2022
39228cb
wip
rrrooommmaaa Jul 3, 2022
7a4fe50
fixed regexes
rrrooommmaaa Jul 3, 2022
c2fef29
Displaying 'Account keys updated' toast
rrrooommmaaa Jul 4, 2022
23001b9
Merge remote-tracking branch 'origin/master' into issue-2602-pull-fro…
rrrooommmaaa Jul 5, 2022
aef6a30
more thorough test control
rrrooommmaaa Jul 5, 2022
c87b391
more test cases
rrrooommmaaa Jul 5, 2022
408b9c7
Merge remote-tracking branch 'origin/master' into issue-2602-pull-fro…
rrrooommmaaa Jul 6, 2022
1bd33a3
More test cases and EKM failure test
rrrooommmaaa Jul 8, 2022
d17e970
Merge remote-tracking branch 'origin/master' into issue-2602-pull-fro…
rrrooommmaaa Jul 8, 2022
1c475ee
Merge branch 'master' into issue-2602-pull-from-ekm
rrrooommmaaa Jul 9, 2022
46a5872
refactorings and better test setup
rrrooommmaaa Jul 9, 2022
33adeb7
lint fix
rrrooommmaaa Jul 9, 2022
2296737
added test for EKM down
rrrooommmaaa Jul 10, 2022
b0d8316
niceties
rrrooommmaaa Jul 11, 2022
7529734
Merge remote-tracking branch 'origin/master' into issue-2602-pull-fro…
Jul 16, 2022
dbd86b3
refactoring
Jul 16, 2022
e40a414
updated comments
Jul 16, 2022
b110e9f
renamed `options` to `ppOptions`
Jul 16, 2022
1acca8f
allow overwriting oldKey with undefined lastModified property with a …
Jul 16, 2022
71e9943
Merge branch 'master' into issue-2602-pull-from-ekm
Aug 10, 2022
d1f00af
fix
Aug 10, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions extension/chrome/dev/ci_unit_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Wkd } from '../../js/common/api/key-server/wkd.js';
import { MsgUtil } from '../../js/common/core/crypto/pgp/msg-util.js';
import { Sks } from '../../js/common/api/key-server/sks.js';
import { Ui } from '../../js/common/browser/ui.js';
import { AcctStore } from '../../js/common/platform/store/acct-store.js';
import { ContactStore } from '../../js/common/platform/store/contact-store.js';
import { Debug } from '../../js/common/platform/debug.js';
import { Catch } from '../../js/common/platform/catch.js';
Expand All @@ -33,6 +34,7 @@ const libs: unknown[] = [
Sks,
MsgUtil,
Ui,
AcctStore,
ContactStore,
Debug,
Catch,
Expand Down
11 changes: 9 additions & 2 deletions extension/chrome/elements/passphrase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ View.run(class PassphraseView extends View {
const uncheckedUrlParams = Url.parse(['acctEmail', 'parentTabId', 'longids', 'type', 'initiatorFrameId']);
this.acctEmail = Assert.urlParamRequire.string(uncheckedUrlParams, 'acctEmail');
this.parentTabId = Assert.urlParamRequire.string(uncheckedUrlParams, 'parentTabId');
this.longids = Assert.urlParamRequire.string(uncheckedUrlParams, 'longids').split(',');
this.type = Assert.urlParamRequire.oneof(uncheckedUrlParams, 'type', ['embedded', 'sign', 'message', 'draft', 'attachment', 'quote', 'backup']);
const longidsParam = Assert.urlParamRequire.string(uncheckedUrlParams, 'longids');
this.longids = longidsParam ? longidsParam.split(',') : [];
this.type = Assert.urlParamRequire.oneof(uncheckedUrlParams, 'type',
['embedded', 'sign', 'message', 'draft', 'attachment', 'quote', 'backup', 'update_key']);
this.initiatorFrameId = Assert.urlParamRequire.optionalString(uncheckedUrlParams, 'initiatorFrameId');
}

Expand All @@ -55,6 +57,9 @@ View.run(class PassphraseView extends View {
}
await initPassphraseToggle(['passphrase']);
const allPrivateKeys = await KeyStore.get(this.acctEmail);
if (this.longids.length === 0) {
this.longids.push(...allPrivateKeys.map(ki => ki.longid));
}
this.keysWeNeedPassPhraseFor = allPrivateKeys.filter(ki => this.longids.includes(ki.longid));
if (this.type === 'embedded') {
$('h1').parent().css('display', 'none');
Expand All @@ -71,6 +76,8 @@ View.run(class PassphraseView extends View {
$('h1').text('Enter FlowCrypt pass phrase to load quoted content');
} else if (this.type === 'backup') {
$('h1').text('Enter FlowCrypt pass phrase to back up');
} else if (this.type === 'update_key') {
$('h1').text('Enter FlowCrypt pass phrase to keep your account keys up to date');
}
$('#passphrase').focus();
if (allPrivateKeys.length > 1) {
Expand Down
23 changes: 5 additions & 18 deletions extension/chrome/settings/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Url } from '../../js/common/core/common.js';
import { ApiErr } from '../../js/common/api/shared/api-error.js';
import { Assert } from '../../js/common/assert.js';
import { Catch } from '../../js/common/platform/catch.js';
import { Key, KeyInfoWithIdentity, KeyUtil } from '../../js/common/core/crypto/key.js';
import { KeyInfoWithIdentity, KeyUtil } from '../../js/common/core/crypto/key.js';
import { Gmail } from '../../js/common/api/email-provider/gmail/gmail.js';
import { Google } from '../../js/common/api/email-provider/gmail/google.js';
import { KeyImportUi } from '../../js/common/ui/key-import-ui.js';
Expand All @@ -27,17 +27,18 @@ import { PubLookup } from '../../js/common/api/pub-lookup.js';
import { Scopes, AcctStoreDict, AcctStore } from '../../js/common/platform/store/acct-store.js';
import { KeyStore } from '../../js/common/platform/store/key-store.js';
import { KeyStoreUtil } from "../../js/common/core/crypto/key-store-util.js";
import { PassphraseStore } from '../../js/common/platform/store/passphrase-store.js';
import { ContactStore } from '../../js/common/platform/store/contact-store.js';
import { KeyManager } from '../../js/common/api/key-server/key-manager.js';
import { SetupWithEmailKeyManagerModule } from './setup/setup-key-manager-autogen.js';
import { shouldPassPhraseBeHidden } from '../../js/common/ui/passphrase-ui.js';
import Swal from 'sweetalert2';
import { BackupUi } from '../../js/common/ui/backup-ui/backup-ui.js';

export interface SetupOptions {
export interface PassphraseOptions {
passphrase: string;
passphrase_save: boolean;
}

export interface SetupOptions extends PassphraseOptions {
submit_main: boolean;
submit_all: boolean;
recovered?: boolean;
Expand Down Expand Up @@ -246,20 +247,6 @@ export class SetupView extends View {
await AcctStore.set(this.acctEmail, { setup_date: Date.now(), setup_done: true, cryptup_enabled: true });
};

public saveKeysAndPassPhrase = async (prvs: Key[], options: SetupOptions) => {
for (const prv of prvs) {
await KeyStore.add(this.acctEmail, prv);
await PassphraseStore.set((options.passphrase_save && !this.clientConfiguration.forbidStoringPassPhrase()) ? 'local' : 'session',
this.acctEmail, { longid: KeyUtil.getPrimaryLongid(prv) }, options.passphrase);
}
const { sendAs } = await AcctStore.get(this.acctEmail, ['sendAs']);
const myOwnEmailsAddrs: string[] = [this.acctEmail].concat(Object.keys(sendAs!));
const { full_name: name } = await AcctStore.get(this.acctEmail, ['full_name']);
for (const email of myOwnEmailsAddrs) {
await ContactStore.update(undefined, email, { name, pubkey: KeyUtil.armor(await KeyUtil.asPublicKey(prvs[0])) });
}
};

public shouldSubmitPubkey = (checkboxSelector: string) => {
if (this.clientConfiguration.mustSubmitToAttester() && !this.clientConfiguration.canSubmitPubToAttester()) {
throw new Error('Organisation rules are misconfigured: ENFORCE_ATTESTER_SUBMIT not compatible with NO_ATTESTER_SUBMIT');
Expand Down
3 changes: 2 additions & 1 deletion extension/chrome/settings/setup/setup-create-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Ui } from '../../../js/common/browser/ui.js';
import { Xss } from '../../../js/common/platform/xss.js';
import { AcctStore } from '../../../js/common/platform/store/acct-store.js';
import { OpenPGPKey } from '../../../js/common/core/crypto/pgp/openpgp-key.js';
import { saveKeysAndPassPhrase } from '../../../js/common/helpers.js';

export class SetupCreateKeyModule {

Expand Down Expand Up @@ -81,7 +82,7 @@ export class SetupCreateKeyModule {
const expireMonths = this.view.clientConfiguration.getEnforcedKeygenExpirationMonths();
const key = await OpenPGPKey.create(pgpUids, keyAlgo, options.passphrase, expireMonths);
const prv = await KeyUtil.parse(key.private);
await this.view.saveKeysAndPassPhrase([prv], options);
await saveKeysAndPassPhrase(this.view.acctEmail, [prv], options);
return { id: prv.id, family: prv.family };
};
}
5 changes: 3 additions & 2 deletions extension/chrome/settings/setup/setup-import-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Ui } from '../../../js/common/browser/ui.js';
import { Xss } from '../../../js/common/platform/xss.js';
import { Key, UnexpectedKeyTypeError } from '../../../js/common/core/crypto/key.js';
import { Lang } from '../../../js/common/lang.js';
import { saveKeysAndPassPhrase } from '../../../js/common/helpers.js';

export class SetupImportKeyModule {

Expand Down Expand Up @@ -39,7 +40,7 @@ export class SetupImportKeyModule {
}
}
Xss.sanitizeRender('#step_2b_manual_enter .action_add_private_key', Ui.spinner('white'));
await this.view.saveKeysAndPassPhrase([checked.encrypted], options);
await saveKeysAndPassPhrase(this.view.acctEmail, [checked.encrypted], options);
await this.view.submitPublicKeys(options);
await this.view.finalizeSetup();
await this.view.setupRender.renderSetupDone();
Expand Down Expand Up @@ -70,7 +71,7 @@ export class SetupImportKeyModule {
this.view.setupRender.displayBlock('step_2b_manual_enter');
return;
}
await this.view.saveKeysAndPassPhrase([fixedPrv], options);
await saveKeysAndPassPhrase(this.view.acctEmail, [fixedPrv], options);
await this.view.submitPublicKeys(options);
await this.view.finalizeSetup();
await this.view.setupRender.renderSetupDone();
Expand Down
31 changes: 11 additions & 20 deletions extension/chrome/settings/setup/setup-key-manager-autogen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { SetupOptions, SetupView } from '../setup.js';
import { Ui } from '../../../js/common/browser/ui.js';
import { Url } from '../../../js/common/core/common.js';
import { AcctStore } from '../../../js/common/platform/store/acct-store.js';
import { Buf } from '../../../js/common/core/buf.js';
import { ApiErr } from '../../../js/common/api/shared/api-error.js';
import { Api } from '../../../js/common/api/shared/api.js';
import { Settings } from '../../../js/common/settings.js';
import { KeyUtil } from '../../../js/common/core/crypto/key.js';
import { OpenPGPKey } from '../../../js/common/core/crypto/pgp/openpgp-key.js';
import { Lang } from '../../../js/common/lang.js';
import { processAndStoreKeysFromEkmLocally, saveKeysAndPassPhrase } from '../../../js/common/helpers.js';
import { Xss } from '../../../js/common/platform/xss.js';

export class SetupWithEmailKeyManagerModule {
Expand Down Expand Up @@ -55,7 +55,15 @@ export class SetupWithEmailKeyManagerModule {
const { privateKeys } = await this.view.keyManager!.getPrivateKeys(this.view.idToken!);
if (privateKeys.length) {
// keys already exist on keyserver, auto-import
await this.processAndStoreKeysFromEkmLocally(privateKeys, setupOptions);
try {
await processAndStoreKeysFromEkmLocally({
acctEmail: this.view.acctEmail,
decryptedPrivateKeys: privateKeys.map(entry => entry.decryptedPrivateKey),
ppOptions: setupOptions
});
} catch (e) {
throw new Error(`Could not store keys from EKM due to error: ${e instanceof Error ? e.message : String(e)}`);
}
} else if (this.view.clientConfiguration.canCreateKeys()) {
// generate keys on client and store them on key manager
await this.autoGenerateKeyAndStoreBothLocallyAndToEkm(setupOptions);
Expand All @@ -76,23 +84,6 @@ export class SetupWithEmailKeyManagerModule {
}
};

private processAndStoreKeysFromEkmLocally = async (privateKeys: { decryptedPrivateKey: string }[], setupOptions: SetupOptions) => {
const { keys } = await KeyUtil.readMany(Buf.fromUtfStr(privateKeys.map(pk => pk.decryptedPrivateKey).join('\n')));
if (!keys.length) {
throw new Error(`Could not parse any valid keys from Key Manager response for user ${this.view.acctEmail}`);
}
for (const prv of keys) {
if (!prv.isPrivate) {
throw new Error(`Key ${prv.id} for user ${this.view.acctEmail} is not a private key`);
}
if (!prv.fullyDecrypted) {
throw new Error(`Key ${prv.id} for user ${this.view.acctEmail} from FlowCrypt Email Key Manager is not fully decrypted`);
}
await KeyUtil.encrypt(prv, setupOptions.passphrase);
}
await this.view.saveKeysAndPassPhrase(keys, setupOptions);
};

private autoGenerateKeyAndStoreBothLocallyAndToEkm = async (setupOptions: SetupOptions) => {
const keygenAlgo = this.view.clientConfiguration.getEnforcedKeygenAlgo();
if (!keygenAlgo) {
Expand All @@ -111,7 +102,7 @@ export class SetupWithEmailKeyManagerModule {
}
const storePrvOnKm = async () => this.view.keyManager!.storePrivateKey(this.view.idToken!, KeyUtil.armor(decryptablePrv));
await Settings.retryUntilSuccessful(storePrvOnKm, 'Failed to store newly generated key on FlowCrypt Email Key Manager', Lang.general.contactIfNeedAssistance(this.view.isFesUsed()));
await this.view.saveKeysAndPassPhrase([await KeyUtil.parse(generated.private)], setupOptions); // store encrypted key + pass phrase locally
await saveKeysAndPassPhrase(this.view.acctEmail, [await KeyUtil.parse(generated.private)], setupOptions); // store encrypted key + pass phrase locally
};

}
3 changes: 2 additions & 1 deletion extension/chrome/settings/setup/setup-recover-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Url } from '../../../js/common/core/common.js';
import { Xss } from '../../../js/common/platform/xss.js';
import { AcctStore } from '../../../js/common/platform/store/acct-store.js';
import { KeyStore } from '../../../js/common/platform/store/key-store.js';
import { saveKeysAndPassPhrase } from '../../../js/common/helpers.js';

export class SetupRecoverKeyModule {

Expand Down Expand Up @@ -63,7 +64,7 @@ export class SetupRecoverKeyModule {
passphrase_save: true, // todo - reevaluate saving passphrase when recovering
recovered: true,
};
await this.view.saveKeysAndPassPhrase(newlyMatchingKeys, options);
await saveKeysAndPassPhrase(this.view.acctEmail, newlyMatchingKeys, options);
const { setup_done } = await AcctStore.get(this.view.acctEmail, ['setup_done']);
if (!setup_done) { // normal situation - fresh setup
await this.view.submitPublicKeys(options);
Expand Down
2 changes: 2 additions & 0 deletions extension/js/background_page/background_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ContactStore } from '../common/platform/store/contact-store.js';
import { AcctStore } from '../common/platform/store/acct-store.js';
import { ExpirationCache } from '../common/core/expiration-cache.js';
import { emailKeyIndex } from '../common/core/common.js';
import { processAndStoreKeysFromEkmLocally } from '../common/helpers.js';

console.info('background_process.js starting');

Expand Down Expand Up @@ -61,6 +62,7 @@ opgp.initWorker({ path: '/lib/openpgp.worker.js' });
BrowserMsg.bgAddListener('storeGlobalSet', (r: Bm.StoreGlobalSet) => GlobalStore.set(r.values));
BrowserMsg.bgAddListener('storeAcctGet', (r: Bm.StoreAcctGet) => AcctStore.get(r.acctEmail, r.keys));
BrowserMsg.bgAddListener('storeAcctSet', (r: Bm.StoreAcctSet) => AcctStore.set(r.acctEmail, r.values));
BrowserMsg.bgAddListener('processAndStoreKeysFromEkmLocally', processAndStoreKeysFromEkmLocally);

BrowserMsg.addPgpListeners(); // todo - remove https://github.com/FlowCrypt/flowcrypt-browser/issues/2560 fixed

Expand Down
8 changes: 6 additions & 2 deletions extension/js/common/browser/browser-msg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export namespace Bm {
export type ShowAttachmentPreview = { iframeUrl: string };
export type ReRenderRecipient = { email: string };
export type SaveFetchedPubkeys = { email: string, pubkeys: string[] };
export type ProcessAndStoreKeysFromEkmLocally = { acctEmail: string, decryptedPrivateKeys: string[] };

export namespace Res {
export type GetActiveTabInfo = { provider: 'gmail' | undefined, acctEmail: string | undefined, sameWorld: boolean | undefined };
Expand All @@ -83,13 +84,14 @@ export namespace Bm {
export type AjaxGmailAttachmentGetChunk = { chunk: Buf };
export type _tab_ = { tabId: string | null | undefined };
export type SaveFetchedPubkeys = boolean;
export type ProcessAndStoreKeysFromEkmLocally = { needPassphrase: boolean, updateCount: number };
export type Db = any; // not included in Any below
export type Ajax = any; // not included in Any below

export type Any = GetActiveTabInfo | _tab_ | ReconnectAcctAuthPopup
| PgpMsgDecrypt | PgpMsgDiagnoseMsgPubkeys | PgpMsgVerify | PgpHashChallengeAnswer | PgpMsgType
| InMemoryStoreGet | InMemoryStoreSet | StoreAcctGet | StoreAcctSet | StoreGlobalGet | StoreGlobalSet
| AjaxGmailAttachmentGetChunk | SaveFetchedPubkeys;
| AjaxGmailAttachmentGetChunk | SaveFetchedPubkeys | ProcessAndStoreKeysFromEkmLocally;
}

export type AnyRequest = PassphraseEntry | OpenPage | OpenGoogleAuthDialog | Redirect | Reload |
Expand All @@ -98,7 +100,7 @@ export namespace Bm {
NotificationShow | PassphraseDialog | PassphraseDialog | Settings | SetCss | AddOrRemoveClass | ReconnectAcctAuthPopup |
Db | InMemoryStoreSet | InMemoryStoreGet | StoreGlobalGet | StoreGlobalSet | StoreAcctGet | StoreAcctSet |
PgpMsgDecrypt | PgpMsgDiagnoseMsgPubkeys | PgpMsgVerifyDetached | PgpHashChallengeAnswer | PgpMsgType | Ajax |
ShowAttachmentPreview | ReRenderRecipient | SaveFetchedPubkeys;
ShowAttachmentPreview | ReRenderRecipient | SaveFetchedPubkeys | ProcessAndStoreKeysFromEkmLocally;

// export type RawResponselessHandler = (req: AnyRequest) => Promise<void>;
// export type RawRespoHandler = (req: AnyRequest) => Promise<void>;
Expand Down Expand Up @@ -146,6 +148,8 @@ export class BrowserMsg {
pgpMsgVerifyDetached: (bm: Bm.PgpMsgVerifyDetached) => BrowserMsg.sendAwait(undefined, 'pgpMsgVerifyDetached', bm, true) as Promise<Bm.Res.PgpMsgVerify>,
pgpMsgType: (bm: Bm.PgpMsgType) => BrowserMsg.sendAwait(undefined, 'pgpMsgType', bm, true) as Promise<Bm.Res.PgpMsgType>,
saveFetchedPubkeys: (bm: Bm.SaveFetchedPubkeys) => BrowserMsg.sendAwait(undefined, 'saveFetchedPubkeys', bm, true) as Promise<Bm.Res.SaveFetchedPubkeys>,
processAndStoreKeysFromEkmLocally:
(bm: Bm.ProcessAndStoreKeysFromEkmLocally) => BrowserMsg.sendAwait(undefined, 'processAndStoreKeysFromEkmLocally', bm, true) as Promise<Bm.Res.ProcessAndStoreKeysFromEkmLocally>,
},
},
passphraseEntry: (dest: Bm.Dest, bm: Bm.PassphraseEntry) => BrowserMsg.sendCatch(dest, 'passphrase_entry', bm),
Expand Down
2 changes: 1 addition & 1 deletion extension/js/common/core/crypto/key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ export class KeyUtil {
}
};

public static reformatKey = async (privateKey: Key, passphrase: string, userIds: { email: string | undefined; name: string }[], expireSeconds: number) => {
public static reformatKey = async (privateKey: Key, passphrase: string | undefined, userIds: { email: string | undefined; name: string }[], expireSeconds: number) => {
if (privateKey.family === 'openpgp') {
return await OpenPGPKey.reformatKey(privateKey, passphrase, userIds, expireSeconds);
} else {
Expand Down
2 changes: 1 addition & 1 deletion extension/js/common/core/crypto/pgp/openpgp-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export class OpenPGPKey {
return await Catch.doesReject(opgpPrv.verifyPrimaryKey(), ['No self-certifications']);
};

public static reformatKey = async (privateKey: Key, passphrase: string, userIds: { email: string | undefined; name: string }[], expireSeconds: number) => {
public static reformatKey = async (privateKey: Key, passphrase: string | undefined, userIds: { email: string | undefined; name: string }[], expireSeconds: number) => {
const opgpPrv = OpenPGPKey.extractExternalLibraryObjFromKey(privateKey);
const keyPair = await opgp.reformatKey({ privateKey: opgpPrv, passphrase, userIds, keyExpirationTime: expireSeconds });
return await OpenPGPKey.convertExternalLibraryObjToKey(keyPair.key);
Expand Down
Loading