Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c9a4e87
Use public key fingerprint as S/MIME Certificate id #3570
rrrooommmaaa Apr 20, 2021
39688c0
test fix
rrrooommmaaa Apr 20, 2021
5d3e084
updated comment
rrrooommmaaa Apr 21, 2021
45d38a2
Merge remote-tracking branch 'origin/master' into issue-3570-cert-fin…
rrrooommmaaa Apr 21, 2021
fb4ff53
construct IssuerAndSerialNumber as per PKCS7
rrrooommmaaa Apr 24, 2021
757e19a
Merge remote-tracking branch 'origin/master' into issue-3570-cert-fin…
rrrooommmaaa Apr 24, 2021
cbc6967
enable to search by IssuerAndSerialNumber in ContactStore
rrrooommmaaa Apr 25, 2021
cd34692
careful longid calculation for S/MIME
rrrooommmaaa Apr 28, 2021
a7dc997
Merge remote-tracking branch 'origin/master' into issue-3570-cert-fin…
rrrooommmaaa Apr 28, 2021
b190f6c
fix
rrrooommmaaa Apr 28, 2021
0dc8919
fix
rrrooommmaaa Apr 28, 2021
64cade7
nicer longid handling in mock ContactStore
rrrooommmaaa Apr 29, 2021
7323378
Merge remote-tracking branch 'origin/master' into issue-3570-cert-fin…
rrrooommmaaa Apr 29, 2021
2e2732c
tslint fix
rrrooommmaaa Apr 29, 2021
0973000
X.509 longid support for KeyInfo
rrrooommmaaa Apr 29, 2021
1013a6e
fix
rrrooommmaaa Apr 29, 2021
421e405
fix
rrrooommmaaa Apr 29, 2021
5823f79
Added migration to fix fingerprints and longids of X.509 keys
rrrooommmaaa Apr 30, 2021
57869a6
fixed matchingKeyids test
rrrooommmaaa Apr 30, 2021
b69e65e
Merge branch 'master' into issue-3570-cert-fingerprint
May 8, 2021
e3f6105
Merge remote-tracking branch 'origin/master' into issue-3570-cert-fin…
rrrooommmaaa May 8, 2021
844e01a
renamed KeyInfoWithOptionalPp to ExtendedKeyInfo
rrrooommmaaa May 8, 2021
c9df1d9
fixed test ContactStore
rrrooommmaaa May 8, 2021
207cebf
Merge branch 'master' into issue-3570-cert-fingerprint
rrrooommmaaa May 8, 2021
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 @@ -15,6 +15,7 @@ import { Ui } from '../../js/common/browser/ui.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';
import * as forge from 'node-forge';

/**
* importing all libs that are tested in ci tests
Expand All @@ -40,3 +41,4 @@ const libs: any[] = [
for (const lib of libs) {
(window as any)[(lib as any).name] = lib;
}
(window as any).forge = forge;
3 changes: 2 additions & 1 deletion extension/js/background_page/background_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Catch } from '../common/platform/catch.js';
import { GoogleAuth } from '../common/api/email-provider/gmail/google-auth.js';
import { VERSION } from '../common/core/const.js';
import { injectFcIntoWebmail } from './inject.js';
import { migrateGlobal, moveContactsToEmailsAndPubkeys } from './migrations.js';
import { migrateGlobal, moveContactsToEmailsAndPubkeys, updateX509FingerprintsAndLongids } from './migrations.js';
import { opgp } from '../common/core/crypto/pgp/openpgpjs-custom.js';
import { GlobalStoreDict, GlobalStore } from '../common/platform/store/global-store.js';
import { ContactStore } from '../common/platform/store/contact-store.js';
Expand Down Expand Up @@ -41,6 +41,7 @@ opgp.initWorker({ path: '/lib/openpgp.worker.js' });

try {
db = await ContactStore.dbOpen(); // takes 4-10 ms first time
await updateX509FingerprintsAndLongids(db);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small nitpick - maybe this migration updateX509FingerprintsAndLongids should be done after moveContactsToEmailsAndPubkeys is already migrated? (switch line order)

Copy link
Contributor Author

@rrrooommmaaa rrrooommmaaa May 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contacts migration moveContactsToEmailsAndPubkeys sets correct fingerprints and longids, we only need to fix contacts migrated by the previous version

await moveContactsToEmailsAndPubkeys(db);
} catch (e) {
await BgUtils.handleStoreErr(e);
Expand Down
72 changes: 71 additions & 1 deletion extension/js/background_page/migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
'use strict';

import { Key, KeyInfo, KeyUtil } from '../common/core/crypto/key.js';
import { ContactStore, ContactUpdate } from '../common/platform/store/contact-store.js';
import { SmimeKey } from '../common/core/crypto/smime/smime-key.js';
import { ContactStore, ContactUpdate, Email, Pubkey } from '../common/platform/store/contact-store.js';
import { GlobalStore } from '../common/platform/store/global-store.js';
import { KeyStore } from '../common/platform/store/key-store.js';

Expand All @@ -19,6 +20,12 @@ type ContactV3 = {
expiresOn: number | null;
};

type PubkeyMigrationData = {
emailsToUpdate: { [email: string]: Email };
pubkeysToDelete: string[];
pubkeysToSave: Pubkey[];
};

const addKeyInfoFingerprints = async () => {
for (const acctEmail of await GlobalStore.acctEmailsGet()) {
const originalKis = await KeyStore.get(acctEmail);
Expand All @@ -40,6 +47,69 @@ export const migrateGlobal = async () => {
}
};

const processSmimeKey = (pubkey: Pubkey, tx: IDBTransaction, data: PubkeyMigrationData, next: () => void) => {
if (KeyUtil.getKeyType(pubkey.armoredKey) !== 'x509') {
next();
return;
}
Comment on lines +51 to +54
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting - could we instead filter when pulling pubkeys from storage? Eg SELECT FROM pubkey WHERE type = 'x509'. Or we don't store this info?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't store this info yet, only in ids (fingerprints) -- postfixed with "-X509", and in longids -- prefixed with "X509-"

const key = SmimeKey.parse(pubkey.armoredKey);
const newPubkeyEntity = ContactStore.pubkeyObj(key, pubkey.lastCheck);
data.pubkeysToDelete.push(pubkey.fingerprint);
const req = tx.objectStore('emails').index('index_fingerprints').getAll(pubkey.fingerprint!);
ContactStore.setReqPipe(req,
(emailEntities: Email[]) => {
if (emailEntities.length) {
data.pubkeysToSave.push(newPubkeyEntity);
}
for (const emailEntity of emailEntities) {
const cachedEmail = data.emailsToUpdate[emailEntity.email];
if (!cachedEmail) {
data.emailsToUpdate[emailEntity.email] = emailEntity;
}
const entityToUpdate = cachedEmail ?? emailEntity;
entityToUpdate.fingerprints = entityToUpdate.fingerprints.filter(fp => fp !== pubkey.fingerprint && fp !== newPubkeyEntity.fingerprint);
entityToUpdate.fingerprints.push(newPubkeyEntity.fingerprint);
}
next();
});
};

export const updateX509FingerprintsAndLongids = async (db: IDBDatabase): Promise<void> => {
const globalStore = await GlobalStore.get(['contact_store_x509_fingerprints_and_longids_updated']);
if (globalStore.contact_store_x509_fingerprints_and_longids_updated) {
return;
}
console.info('updating ContactStorage to correct longids and fingerprints of X.509 certificates...');
const tx = db.transaction(['emails', 'pubkeys'], 'readwrite');
await new Promise((resolve, reject) => {
ContactStore.setTxHandlers(tx, resolve, reject);
const data: PubkeyMigrationData = { emailsToUpdate: {}, pubkeysToDelete: [], pubkeysToSave: [] };
const search = tx.objectStore('pubkeys').openCursor();
ContactStore.setReqPipe(search,
(cursor: IDBCursorWithValue) => {
if (!cursor) {
// do updates
for (const fp of data.pubkeysToDelete.filter(fp => !data.pubkeysToSave.some(x => x.fingerprint === fp))) {
// console.log(`Deleting pubkey ${fp}`);
tx.objectStore('pubkeys').delete(fp);
}
for (const pubkey of data.pubkeysToSave) {
// console.log(`Updating pubkey ${pubkey.fingerprint}`);
tx.objectStore('pubkeys').put(pubkey);
}
for (const email of Object.values(data.emailsToUpdate)) {
// console.log(`Updating email ${email.email}`);
tx.objectStore('emails').put(email);
}
} else {
processSmimeKey(cursor.value as Pubkey, tx, data, () => cursor.continue());
}
});
});
await GlobalStore.set({ contact_store_x509_fingerprints_and_longids_updated: true });
console.info('done updating');
};

export const moveContactsToEmailsAndPubkeys = async (db: IDBDatabase): Promise<void> => {
if (!db.objectStoreNames.contains('contacts')) {
return;
Expand Down
41 changes: 34 additions & 7 deletions extension/js/common/core/crypto/key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import { MsgBlock } from '../msg-block.js';
*/
export interface Key {
type: 'openpgp' | 'x509';
id: string; // This is a fingerprint for OpenPGP keys and Serial Number for X.509 keys.
allIds: string[]; // a list of fingerprints for OpenPGP key or a Serial Number for X.509 keys.
id: string; // a fingerprint of the primary key in OpenPGP, and similarly a fingerprint of the actual cryptographic key (eg RSA fingerprint) in S/MIME
allIds: string[]; // a list of fingerprints, including those for subkeys
created: number;
lastModified: number | undefined; // date of last signature, or undefined if never had valid signature
expiration: number | undefined; // number of millis of expiration or undefined if never expires
Expand All @@ -43,6 +43,7 @@ export interface Key {
bits?: number,
algorithmId: number
};
issuerAndSerialNumber?: string | undefined; // DER-encoded IssuerAndSerialNumber of X.509 Certificate as raw string
}

export type PubkeyResult = { pubkey: Key, email: string, isMine: boolean };
Expand All @@ -66,8 +67,9 @@ export interface KeyInfo {
emails?: string[]; // todo - used to be missing - but migration was supposed to add it? setting back to optional for now
}

export interface KeyInfoWithOptionalPp extends KeyInfo {
export interface ExtendedKeyInfo extends KeyInfo {
passphrase?: string;
type: 'openpgp' | 'x509'
}

export type KeyAlgo = 'curve25519' | 'rsa2048' | 'rsa4096';
Expand Down Expand Up @@ -130,7 +132,7 @@ export class KeyUtil {
return await OpenPGPKey.parseMany(text);
} else if (keyType === 'x509') {
// TODO: No support for parsing multiple S/MIME keys for now
return [await SmimeKey.parse(text)];
return [SmimeKey.parse(text)];
}
throw new UnexpectedKeyTypeError(`Key type is ${keyType}, expecting OpenPGP or x509 S/MIME`);
}
Expand Down Expand Up @@ -160,7 +162,7 @@ export class KeyUtil {
}
if (!allKeys.length) {
try {
allKeys.push(await SmimeKey.parseDecryptBinary(key, passPhrase ?? ''));
allKeys.push(SmimeKey.parseDecryptBinary(key, passPhrase ?? ''));
return { keys: allKeys, err: [] };
} catch (e) {
allErr.push(e as Error);
Expand Down Expand Up @@ -339,13 +341,38 @@ export class KeyUtil {
if (!prv.isPrivate) {
throw new Error('Key passed into KeyUtil.keyInfoObj must be a Private Key');
}
const pubkey = await KeyUtil.asPublicKey(prv);
return {
private: KeyUtil.armor(prv),
public: KeyUtil.armor(await KeyUtil.asPublicKey(prv)),
longid: OpenPGPKey.fingerprintToLongid(prv.id),
public: KeyUtil.armor(pubkey),
longid: KeyUtil.getPrimaryLongid(pubkey),
emails: prv.emails,
fingerprints: prv.allIds,
};
}

public static getPubkeyLongids = (pubkey: Key): string[] => {
if (pubkey.type !== 'x509') {
return pubkey.allIds.map(id => OpenPGPKey.fingerprintToLongid(id));
}
return [KeyUtil.getPrimaryLongid(pubkey)];
}

public static getPrimaryLongid = (pubkey: Key): string => {
if (pubkey.type !== 'x509') {
return OpenPGPKey.fingerprintToLongid(pubkey.id);
}
const encodedIssuerAndSerialNumber = 'X509-' + Buf.fromRawBytesStr(pubkey.issuerAndSerialNumber!).toBase64Str();
if (!encodedIssuerAndSerialNumber) {
throw new Error(`Cannot extract IssuerAndSerialNumber from the certificate for: ${pubkey.id}`);
}
return encodedIssuerAndSerialNumber;
}

public static getKeyInfoLongids = (ki: ExtendedKeyInfo): string[] => {
if (ki.type !== 'x509') {
return ki.fingerprints.map(fp => OpenPGPKey.fingerprintToLongid(fp));
}
return [ki.longid];
}
}
25 changes: 12 additions & 13 deletions extension/js/common/core/crypto/pgp/msg-util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */

'use strict';
import { Contact, Key, KeyInfo, KeyInfoWithOptionalPp, KeyUtil } from '../key.js';
import { Contact, Key, KeyInfo, ExtendedKeyInfo, KeyUtil } from '../key.js';
import { MsgBlockType, ReplaceableMsgBlockType } from '../../msg-block.js';
import { Value } from '../../common.js';
import { Buf } from '../../buf.js';
Expand All @@ -26,7 +26,7 @@ export namespace PgpMsgMethod {
export namespace Arg {
export type Encrypt = { pubkeys: Key[], signingPrv?: Key, pwd?: string, data: Uint8Array, filename?: string, armor: boolean, date?: Date };
export type Type = { data: Uint8Array | string };
export type Decrypt = { kisWithPp: KeyInfoWithOptionalPp[], encryptedData: Uint8Array, msgPwd?: string };
export type Decrypt = { kisWithPp: ExtendedKeyInfo[], encryptedData: Uint8Array, msgPwd?: string };
export type DiagnosePubkeys = { armoredPubs: string[], message: Uint8Array };
export type VerifyDetached = { plaintext: Uint8Array, sigText: Uint8Array };
}
Expand All @@ -53,9 +53,9 @@ type SortedKeysForDecrypt = {
forVerification: OpenPGP.key.Key[];
encryptedFor: string[];
signedBy: string[];
prvMatching: KeyInfoWithOptionalPp[];
prvForDecrypt: KeyInfoWithOptionalPp[];
prvForDecryptDecrypted: { ki: KeyInfoWithOptionalPp, decrypted: Key }[];
prvMatching: ExtendedKeyInfo[];
prvForDecrypt: ExtendedKeyInfo[];
prvForDecryptDecrypted: { ki: ExtendedKeyInfo, decrypted: Key }[];
prvForDecryptWithoutPassphrases: KeyInfo[];
};

Expand Down Expand Up @@ -269,7 +269,7 @@ export class MsgUtil {
const msgKeyIds = m.getEncryptionKeyIds ? m.getEncryptionKeyIds() : [];
const localKeyIds: string[] = [];
for (const k of await Promise.all(armoredPubs.map(pub => KeyUtil.parse(pub)))) {
localKeyIds.push(...k.allIds.map(id => OpenPGPKey.fingerprintToLongid(id)));
localKeyIds.push(...KeyUtil.getPubkeyLongids(k));
}
const diagnosis = { found_match: false, receivers: msgKeyIds.length };
for (const msgKeyId of msgKeyIds) {
Expand All @@ -296,7 +296,7 @@ export class MsgUtil {
}
}

private static getSortedKeys = async (kiWithPp: KeyInfoWithOptionalPp[], msg: OpenpgpMsgOrCleartext): Promise<SortedKeysForDecrypt> => {
private static getSortedKeys = async (kiWithPp: ExtendedKeyInfo[], msg: OpenpgpMsgOrCleartext): Promise<SortedKeysForDecrypt> => {
const keys: SortedKeysForDecrypt = {
verificationContacts: [],
forVerification: [],
Expand All @@ -311,14 +311,14 @@ export class MsgUtil {
keys.encryptedFor = encryptionKeyids.map(kid => OpenPGPKey.bytesToLongid(kid.bytes));
await MsgUtil.cryptoMsgGetSignedBy(msg, keys);
if (keys.encryptedFor.length) {
keys.prvMatching = kiWithPp.filter(ki => ki.fingerprints.some(
fp => keys.encryptedFor.includes(OpenPGPKey.fingerprintToLongid(fp))));
keys.prvMatching = kiWithPp.filter(ki => KeyUtil.getKeyInfoLongids(ki).some(
longid => keys.encryptedFor.includes(longid)));
keys.prvForDecrypt = keys.prvMatching.length ? keys.prvMatching : kiWithPp;
} else { // prvs not needed for signed msgs
keys.prvForDecrypt = [];
}
for (const ki of keys.prvForDecrypt) {
const matchingKeyids = MsgUtil.matchingKeyids(ki.fingerprints, encryptionKeyids);
const matchingKeyids = MsgUtil.matchingKeyids(KeyUtil.getKeyInfoLongids(ki), encryptionKeyids);
const cachedKey = KeyCache.getDecrypted(ki.longid);
if (cachedKey && MsgUtil.isKeyDecryptedFor(cachedKey, matchingKeyids)) {
keys.prvForDecryptDecrypted.push({ ki, decrypted: cachedKey });
Expand All @@ -338,9 +338,8 @@ export class MsgUtil {
return keys;
}

private static matchingKeyids = (fingerprints: string[], encryptedForKeyids: OpenPGP.Keyid[]): OpenPGP.Keyid[] => {
const allKeyLongids = fingerprints.map(fp => OpenPGPKey.fingerprintToLongid(fp));
return encryptedForKeyids.filter(kid => allKeyLongids.includes(OpenPGPKey.bytesToLongid(kid.bytes)));
private static matchingKeyids = (longids: string[], encryptedForKeyids: OpenPGP.Keyid[]): OpenPGP.Keyid[] => {
return encryptedForKeyids.filter(kid => longids.includes(OpenPGPKey.bytesToLongid(kid.bytes)));
}

private static decryptKeyFor = async (prv: Key, passphrase: string, matchingKeyIds: OpenPGP.Keyid[]): Promise<boolean> => {
Expand Down
3 changes: 0 additions & 3 deletions extension/js/common/core/crypto/pgp/openpgp-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,6 @@ export class OpenPGPKey {
}

public static fingerprintToLongid = (fingerprint: string) => {
if (fingerprint.length === 32) { // s/mime keys
return fingerprint; // leave as is - s/mime has no concept of longids
}
if (fingerprint.length === 40) { // pgp keys
return fingerprint.substr(-16).toUpperCase();
}
Expand Down
54 changes: 23 additions & 31 deletions extension/js/common/core/crypto/smime/smime-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ import { Buf } from '../../buf.js';

export class SmimeKey {

public static parse = async (text: string): Promise<Key> => {
public static parse = (text: string): Key => {
if (text.includes(PgpArmor.headers('certificate').begin)) {
return SmimeKey.parsePemCertificate(text);
} else if (text.includes(PgpArmor.headers('pkcs12').begin)) {
const armoredBytes = text.replace(PgpArmor.headers('pkcs12').begin, '').replace(PgpArmor.headers('pkcs12').end, '').trim();
const emptyPassPhrase = '';
return await SmimeKey.parseDecryptBinary(Buf.fromBase64Str(armoredBytes), emptyPassPhrase);
return SmimeKey.parseDecryptBinary(Buf.fromBase64Str(armoredBytes), emptyPassPhrase);
} else {
throw new Error('Could not parse S/MIME key without known headers');
}
}

public static parseDecryptBinary = async (buffer: Uint8Array, password: string): Promise<Key> => {
public static parseDecryptBinary = (buffer: Uint8Array, password: string): Key => {
const bytes = String.fromCharCode.apply(undefined, new Uint8Array(buffer) as unknown as number[]) as string;
const asn1 = forge.asn1.fromDer(bytes);
let certificate: forge.pki.Certificate | undefined;
Expand All @@ -46,29 +46,8 @@ export class SmimeKey {
if (!certificate) {
throw new Error('No user certificate found.');
}
SmimeKey.removeWeakKeys(certificate);
const emails = SmimeKey.getNormalizedEmailsFromCertificate(certificate);
const key = {
type: 'x509',
id: certificate.serialNumber.toUpperCase(),
allIds: [certificate.serialNumber.toUpperCase()],
usableForEncryption: SmimeKey.isEmailCertificate(certificate),
usableForSigning: SmimeKey.isEmailCertificate(certificate),
usableForEncryptionButExpired: false,
usableForSigningButExpired: false,
emails,
identities: emails,
created: SmimeKey.dateToNumber(certificate.validity.notBefore),
lastModified: SmimeKey.dateToNumber(certificate.validity.notBefore),
expiration: SmimeKey.dateToNumber(certificate.validity.notAfter),
fullyDecrypted: true,
fullyEncrypted: false,
isPublic: certificate.publicKey && !certificate.privateKey,
isPrivate: !!certificate.privateKey,
} as Key;
const headers = PgpArmor.headers('pkcs12');
(key as unknown as { raw: string }).raw = `${headers.begin}\n${forge.util.encode64(bytes)}\n${headers.end}`;
return key;
return SmimeKey.getKeyFromCertificate(certificate, `${headers.begin}\n${forge.util.encode64(bytes)}\n${headers.end}`);
}

/**
Expand Down Expand Up @@ -109,25 +88,38 @@ export class SmimeKey {
}

private static getKeyFromCertificate = (certificate: forge.pki.Certificate, pem: string): Key => {
if (!certificate.publicKey) {
throw new UnreportableError(`This S/MIME x.509 certificate doesn't have a public key`);
}
const fingerprint = forge.pki.getPublicKeyFingerprint(certificate.publicKey, { encoding: 'hex' }).toUpperCase();
SmimeKey.removeWeakKeys(certificate);
const emails = SmimeKey.getNormalizedEmailsFromCertificate(certificate);
const issuerAndSerialNumberAsn1 =
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [
// Name
forge.pki.distinguishedNameToAsn1(certificate.issuer),
// Serial
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.INTEGER, false,
forge.util.hexToBytes(certificate.serialNumber))
]);
const key = {
type: 'x509',
id: certificate.serialNumber.toUpperCase(),
allIds: [certificate.serialNumber.toUpperCase()],
usableForEncryption: certificate.publicKey && SmimeKey.isEmailCertificate(certificate),
usableForSigning: certificate.publicKey && SmimeKey.isEmailCertificate(certificate),
id: fingerprint,
allIds: [fingerprint],
usableForEncryption: SmimeKey.isEmailCertificate(certificate),
usableForSigning: SmimeKey.isEmailCertificate(certificate),
usableForEncryptionButExpired: false,
usableForSigningButExpired: false,
emails,
identities: emails,
created: SmimeKey.dateToNumber(certificate.validity.notBefore),
lastModified: SmimeKey.dateToNumber(certificate.validity.notBefore),
expiration: SmimeKey.dateToNumber(certificate.validity.notAfter),
fullyDecrypted: false,
fullyDecrypted: !!certificate.privateKey,
fullyEncrypted: false,
isPublic: certificate.publicKey && !certificate.privateKey,
isPublic: !certificate.privateKey,
isPrivate: !!certificate.privateKey,
issuerAndSerialNumber: forge.asn1.toDer(issuerAndSerialNumberAsn1).getBytes()
} as Key;
(key as unknown as { rawArmored: string }).rawArmored = pem;
return key;
Expand Down
Loading