mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
fix: repair mixed cipher key encryption handling
This commit is contained in:
@@ -47,3 +47,4 @@ AGENTS.md
|
|||||||
settings.json
|
settings.json
|
||||||
.claude/
|
.claude/
|
||||||
NodeWarden-compat/
|
NodeWarden-compat/
|
||||||
|
.codex-upstream/
|
||||||
|
|||||||
@@ -148,6 +148,10 @@
|
|||||||
compatibility: {
|
compatibility: {
|
||||||
// Single source of truth for /config.version and /api/version.
|
// Single source of truth for /config.version and /api/version.
|
||||||
// /config.version 与 /api/version 的统一版本号来源。
|
// /config.version 与 /api/version 的统一版本号来源。
|
||||||
bitwardenServerVersion: '2026.1.0',
|
bitwardenServerVersion: '2026.4.1',
|
||||||
|
// Advertise official per-cipher item-key encryption support only after
|
||||||
|
// NodeWarden can guarantee key/field consistency across all write paths.
|
||||||
|
// 在所有写入路径都能保证 cipher.key 与字段密文一致之前,不向官方客户端声明支持逐项密钥加密。
|
||||||
|
cipherKeyEncryptionFeatureEnabled: false,
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ function buildConfigResponse(origin: string) {
|
|||||||
_icon_service_url: buildIconServiceTemplate(origin),
|
_icon_service_url: buildIconServiceTemplate(origin),
|
||||||
_icon_service_csp: buildIconServiceCsp(origin),
|
_icon_service_csp: buildIconServiceCsp(origin),
|
||||||
featureStates: {
|
featureStates: {
|
||||||
'cipher-key-encryption': true,
|
'cipher-key-encryption': LIMITS.compatibility.cipherKeyEncryptionFeatureEnabled,
|
||||||
'duo-redirect': true,
|
'duo-redirect': true,
|
||||||
'email-verification': true,
|
'email-verification': true,
|
||||||
'pm-19051-send-email-verification': false,
|
'pm-19051-send-email-verification': false,
|
||||||
|
|||||||
+7
-4
@@ -25,7 +25,7 @@ import {
|
|||||||
import { clearAuditLogs, getAuditLogSettings, listAdminInvites, listAdminUsers, listAuditLogs, saveAuditLogSettings, type AuditLogFilters } from '@/lib/api/admin';
|
import { clearAuditLogs, getAuditLogSettings, listAdminInvites, listAdminUsers, listAuditLogs, saveAuditLogSettings, type AuditLogFilters } from '@/lib/api/admin';
|
||||||
import { getDomainRules, saveDomainRules } from '@/lib/api/domains';
|
import { getDomainRules, saveDomainRules } from '@/lib/api/domains';
|
||||||
import { getSends } from '@/lib/api/send';
|
import { getSends } from '@/lib/api/send';
|
||||||
import { repairCipherUriChecksums } from '@/lib/api/vault';
|
import { repairCipherKeyMismatches, repairCipherUriChecksums } from '@/lib/api/vault';
|
||||||
import { getCachedVaultCoreSnapshot, loadVaultCoreSyncSnapshot } from '@/lib/api/vault-sync';
|
import { getCachedVaultCoreSnapshot, loadVaultCoreSyncSnapshot } from '@/lib/api/vault-sync';
|
||||||
import { silentlyRepairBackupSettingsIfNeeded } from '@/lib/backup-settings-repair';
|
import { silentlyRepairBackupSettingsIfNeeded } from '@/lib/backup-settings-repair';
|
||||||
import {
|
import {
|
||||||
@@ -1086,9 +1086,12 @@ export default function App() {
|
|||||||
const repairKey = `${session.accessToken}:${encryptedCiphers.map((cipher) => `${cipher.id}:${cipher.revisionDate || ''}`).join(',')}`;
|
const repairKey = `${session.accessToken}:${encryptedCiphers.map((cipher) => `${cipher.id}:${cipher.revisionDate || ''}`).join(',')}`;
|
||||||
if (uriChecksumRepairAttemptRef.current !== repairKey) {
|
if (uriChecksumRepairAttemptRef.current !== repairKey) {
|
||||||
uriChecksumRepairAttemptRef.current = repairKey;
|
uriChecksumRepairAttemptRef.current = repairKey;
|
||||||
void repairCipherUriChecksums(authedFetch, session, result.ciphers)
|
void Promise.all([
|
||||||
.then((count) => {
|
repairCipherKeyMismatches(authedFetch, session, result.ciphers),
|
||||||
if (count > 0) void refetchVaultCoreData();
|
repairCipherUriChecksums(authedFetch, session, result.ciphers),
|
||||||
|
])
|
||||||
|
.then(([keyMismatchCount, uriChecksumCount]) => {
|
||||||
|
if (keyMismatchCount + uriChecksumCount > 0) void refetchVaultCoreData();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// Best-effort compatibility repair must not interrupt normal vault loading.
|
// Best-effort compatibility repair must not interrupt normal vault loading.
|
||||||
|
|||||||
+287
-1
@@ -496,8 +496,11 @@ async function encryptPasswordHistory(
|
|||||||
const out: CipherPasswordHistoryEntry[] = [];
|
const out: CipherPasswordHistoryEntry[] = [];
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const rawPassword = String(entry?.password || '');
|
const rawPassword = String(entry?.password || '');
|
||||||
|
const hasDecryptedPassword = typeof entry?.decPassword === 'string';
|
||||||
const plainPassword = entry?.decPassword ?? rawPassword;
|
const plainPassword = entry?.decPassword ?? rawPassword;
|
||||||
const encryptedPassword = looksLikeCipherString(rawPassword)
|
const encryptedPassword = hasDecryptedPassword
|
||||||
|
? await encryptTextValue(plainPassword, enc, mac)
|
||||||
|
: looksLikeCipherString(rawPassword)
|
||||||
? rawPassword
|
? rawPassword
|
||||||
: await encryptTextValue(plainPassword, enc, mac);
|
: await encryptTextValue(plainPassword, enc, mac);
|
||||||
if (!encryptedPassword) continue;
|
if (!encryptedPassword) continue;
|
||||||
@@ -510,6 +513,133 @@ async function encryptPasswordHistory(
|
|||||||
return out.length ? out : null;
|
return out.length ? out : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function plainCipherValue(decrypted: unknown, raw: unknown = ''): string {
|
||||||
|
if (typeof decrypted === 'string' && !looksLikeCipherString(decrypted)) return decrypted;
|
||||||
|
const value = String(raw ?? '');
|
||||||
|
return looksLikeCipherString(value) ? '' : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function draftFromDecryptedCipher(cipher: Cipher): VaultDraft {
|
||||||
|
const type = Number(cipher.type || 1) || 1;
|
||||||
|
const draft: VaultDraft = {
|
||||||
|
type,
|
||||||
|
name: plainCipherValue(cipher.decName, cipher.name).trim() || 'Untitled',
|
||||||
|
notes: plainCipherValue(cipher.decNotes, cipher.notes),
|
||||||
|
favorite: !!cipher.favorite,
|
||||||
|
reprompt: Number(cipher.reprompt || 0) === 1,
|
||||||
|
folderId: cipher.folderId || '',
|
||||||
|
loginUsername: '',
|
||||||
|
loginPassword: '',
|
||||||
|
loginTotp: '',
|
||||||
|
loginUris: [{ uri: '', match: null, originalUri: '', extra: {} }],
|
||||||
|
loginFido2Credentials: [],
|
||||||
|
cardholderName: '',
|
||||||
|
cardNumber: '',
|
||||||
|
cardBrand: '',
|
||||||
|
cardExpMonth: '',
|
||||||
|
cardExpYear: '',
|
||||||
|
cardCode: '',
|
||||||
|
identTitle: '',
|
||||||
|
identFirstName: '',
|
||||||
|
identMiddleName: '',
|
||||||
|
identLastName: '',
|
||||||
|
identUsername: '',
|
||||||
|
identCompany: '',
|
||||||
|
identSsn: '',
|
||||||
|
identPassportNumber: '',
|
||||||
|
identLicenseNumber: '',
|
||||||
|
identEmail: '',
|
||||||
|
identPhone: '',
|
||||||
|
identAddress1: '',
|
||||||
|
identAddress2: '',
|
||||||
|
identAddress3: '',
|
||||||
|
identCity: '',
|
||||||
|
identState: '',
|
||||||
|
identPostalCode: '',
|
||||||
|
identCountry: '',
|
||||||
|
sshPrivateKey: '',
|
||||||
|
sshPublicKey: '',
|
||||||
|
sshFingerprint: '',
|
||||||
|
customFields: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
draft.customFields = (cipher.fields || [])
|
||||||
|
.map((field) => ({
|
||||||
|
type: parseFieldType(field.type ?? 0),
|
||||||
|
label: plainCipherValue(field.decName, field.name).trim(),
|
||||||
|
value: plainCipherValue(field.decValue, field.value),
|
||||||
|
}))
|
||||||
|
.filter((field) => field.label);
|
||||||
|
|
||||||
|
if (type === 1 && cipher.login) {
|
||||||
|
draft.loginUsername = plainCipherValue(cipher.login.decUsername, cipher.login.username);
|
||||||
|
draft.loginPassword = plainCipherValue(cipher.login.decPassword, cipher.login.password);
|
||||||
|
draft.loginTotp = plainCipherValue(cipher.login.decTotp, cipher.login.totp);
|
||||||
|
draft.loginFido2Credentials = Array.isArray(cipher.login.fido2Credentials)
|
||||||
|
? cipher.login.fido2Credentials.filter((item): item is Record<string, unknown> => !!item && typeof item === 'object')
|
||||||
|
: [];
|
||||||
|
const seenUris = new Set<string>();
|
||||||
|
const uris = (cipher.login.uris || [])
|
||||||
|
.map((entry) => {
|
||||||
|
const uri = plainCipherValue(entry.decUri, entry.uri).trim();
|
||||||
|
const extra = { ...(entry as Record<string, unknown>) };
|
||||||
|
delete extra.uri;
|
||||||
|
delete extra.uriChecksum;
|
||||||
|
delete extra.match;
|
||||||
|
delete extra.decUri;
|
||||||
|
return {
|
||||||
|
uri,
|
||||||
|
match: typeof entry.match === 'number' && Number.isFinite(entry.match) ? entry.match : null,
|
||||||
|
originalUri: '',
|
||||||
|
extra,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((entry) => {
|
||||||
|
if (!entry.uri) return false;
|
||||||
|
const key = entry.uri.toLowerCase();
|
||||||
|
if (seenUris.has(key)) return false;
|
||||||
|
seenUris.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
draft.loginUris = uris.length ? uris : draft.loginUris;
|
||||||
|
} else if (type === 3 && cipher.card) {
|
||||||
|
draft.cardholderName = plainCipherValue(cipher.card.decCardholderName, cipher.card.cardholderName);
|
||||||
|
draft.cardNumber = plainCipherValue(cipher.card.decNumber, cipher.card.number);
|
||||||
|
draft.cardBrand = plainCipherValue(cipher.card.decBrand, cipher.card.brand);
|
||||||
|
draft.cardExpMonth = plainCipherValue(cipher.card.decExpMonth, cipher.card.expMonth);
|
||||||
|
draft.cardExpYear = plainCipherValue(cipher.card.decExpYear, cipher.card.expYear);
|
||||||
|
draft.cardCode = plainCipherValue(cipher.card.decCode, cipher.card.code);
|
||||||
|
} else if (type === 4 && cipher.identity) {
|
||||||
|
draft.identTitle = plainCipherValue(cipher.identity.decTitle, cipher.identity.title);
|
||||||
|
draft.identFirstName = plainCipherValue(cipher.identity.decFirstName, cipher.identity.firstName);
|
||||||
|
draft.identMiddleName = plainCipherValue(cipher.identity.decMiddleName, cipher.identity.middleName);
|
||||||
|
draft.identLastName = plainCipherValue(cipher.identity.decLastName, cipher.identity.lastName);
|
||||||
|
draft.identUsername = plainCipherValue(cipher.identity.decUsername, cipher.identity.username);
|
||||||
|
draft.identCompany = plainCipherValue(cipher.identity.decCompany, cipher.identity.company);
|
||||||
|
draft.identSsn = plainCipherValue(cipher.identity.decSsn, cipher.identity.ssn);
|
||||||
|
draft.identPassportNumber = plainCipherValue(cipher.identity.decPassportNumber, cipher.identity.passportNumber);
|
||||||
|
draft.identLicenseNumber = plainCipherValue(cipher.identity.decLicenseNumber, cipher.identity.licenseNumber);
|
||||||
|
draft.identEmail = plainCipherValue(cipher.identity.decEmail, cipher.identity.email);
|
||||||
|
draft.identPhone = plainCipherValue(cipher.identity.decPhone, cipher.identity.phone);
|
||||||
|
draft.identAddress1 = plainCipherValue(cipher.identity.decAddress1, cipher.identity.address1);
|
||||||
|
draft.identAddress2 = plainCipherValue(cipher.identity.decAddress2, cipher.identity.address2);
|
||||||
|
draft.identAddress3 = plainCipherValue(cipher.identity.decAddress3, cipher.identity.address3);
|
||||||
|
draft.identCity = plainCipherValue(cipher.identity.decCity, cipher.identity.city);
|
||||||
|
draft.identState = plainCipherValue(cipher.identity.decState, cipher.identity.state);
|
||||||
|
draft.identPostalCode = plainCipherValue(cipher.identity.decPostalCode, cipher.identity.postalCode);
|
||||||
|
draft.identCountry = plainCipherValue(cipher.identity.decCountry, cipher.identity.country);
|
||||||
|
} else if (type === 5 && cipher.sshKey) {
|
||||||
|
draft.sshPrivateKey = plainCipherValue(cipher.sshKey.decPrivateKey, cipher.sshKey.privateKey);
|
||||||
|
draft.sshPublicKey = plainCipherValue(cipher.sshKey.decPublicKey, cipher.sshKey.publicKey);
|
||||||
|
draft.sshFingerprint = plainCipherValue(
|
||||||
|
cipher.sshKey.decFingerprint,
|
||||||
|
cipher.sshKey.keyFingerprint || cipher.sshKey.fingerprint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return draft;
|
||||||
|
}
|
||||||
|
|
||||||
async function buildUpdatedPasswordHistory(
|
async function buildUpdatedPasswordHistory(
|
||||||
cipher: Cipher | null,
|
cipher: Cipher | null,
|
||||||
draft: VaultDraft,
|
draft: VaultDraft,
|
||||||
@@ -798,6 +928,162 @@ export async function repairCipherUriChecksums(
|
|||||||
return repaired;
|
return repaired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCipherKeyMismatchProbes(cipher: Cipher): string[] {
|
||||||
|
const candidates = [
|
||||||
|
cipher.name,
|
||||||
|
cipher.notes,
|
||||||
|
cipher.login?.username,
|
||||||
|
cipher.login?.password,
|
||||||
|
cipher.login?.totp,
|
||||||
|
...(cipher.login?.uris || []).map((uri) => uri.uri),
|
||||||
|
cipher.card?.cardholderName,
|
||||||
|
cipher.card?.number,
|
||||||
|
cipher.identity?.title,
|
||||||
|
cipher.identity?.firstName,
|
||||||
|
cipher.sshKey?.privateKey,
|
||||||
|
...(cipher.fields || []).flatMap((field) => [field.name, field.value]),
|
||||||
|
];
|
||||||
|
const probes: string[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
for (const value of candidates) {
|
||||||
|
const probe = String(value || '').trim();
|
||||||
|
if (!looksLikeCipherString(probe) || seen.has(probe)) continue;
|
||||||
|
seen.add(probe);
|
||||||
|
probes.push(probe);
|
||||||
|
}
|
||||||
|
return probes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isResolvedEncryptedField(raw: unknown, decrypted: unknown): boolean {
|
||||||
|
const encrypted = String(raw || '').trim();
|
||||||
|
if (!looksLikeCipherString(encrypted)) return true;
|
||||||
|
const plain = typeof decrypted === 'string' ? decrypted.trim() : '';
|
||||||
|
return !!plain && !looksLikeCipherString(plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasUnresolvedEncryptedFields(cipher: Cipher): boolean {
|
||||||
|
const fido2EncryptedFields = (cipher.login?.fido2Credentials || []).flatMap((credential) => [
|
||||||
|
credential?.credentialId,
|
||||||
|
credential?.keyType,
|
||||||
|
credential?.keyAlgorithm,
|
||||||
|
credential?.keyCurve,
|
||||||
|
credential?.keyValue,
|
||||||
|
credential?.rpId,
|
||||||
|
credential?.rpName,
|
||||||
|
credential?.userHandle,
|
||||||
|
credential?.userName,
|
||||||
|
credential?.userDisplayName,
|
||||||
|
credential?.counter,
|
||||||
|
credential?.discoverable,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const checks: Array<[unknown, unknown]> = [
|
||||||
|
[cipher.name, cipher.decName],
|
||||||
|
[cipher.notes, cipher.decNotes],
|
||||||
|
[cipher.login?.username, cipher.login?.decUsername],
|
||||||
|
[cipher.login?.password, cipher.login?.decPassword],
|
||||||
|
[cipher.login?.totp, cipher.login?.decTotp],
|
||||||
|
...(cipher.login?.uris || []).map((uri) => [uri.uri, uri.decUri] as [unknown, unknown]),
|
||||||
|
[cipher.card?.cardholderName, cipher.card?.decCardholderName],
|
||||||
|
[cipher.card?.number, cipher.card?.decNumber],
|
||||||
|
[cipher.card?.brand, cipher.card?.decBrand],
|
||||||
|
[cipher.card?.expMonth, cipher.card?.decExpMonth],
|
||||||
|
[cipher.card?.expYear, cipher.card?.decExpYear],
|
||||||
|
[cipher.card?.code, cipher.card?.decCode],
|
||||||
|
[cipher.identity?.title, cipher.identity?.decTitle],
|
||||||
|
[cipher.identity?.firstName, cipher.identity?.decFirstName],
|
||||||
|
[cipher.identity?.middleName, cipher.identity?.decMiddleName],
|
||||||
|
[cipher.identity?.lastName, cipher.identity?.decLastName],
|
||||||
|
[cipher.identity?.username, cipher.identity?.decUsername],
|
||||||
|
[cipher.identity?.company, cipher.identity?.decCompany],
|
||||||
|
[cipher.identity?.ssn, cipher.identity?.decSsn],
|
||||||
|
[cipher.identity?.passportNumber, cipher.identity?.decPassportNumber],
|
||||||
|
[cipher.identity?.licenseNumber, cipher.identity?.decLicenseNumber],
|
||||||
|
[cipher.identity?.email, cipher.identity?.decEmail],
|
||||||
|
[cipher.identity?.phone, cipher.identity?.decPhone],
|
||||||
|
[cipher.identity?.address1, cipher.identity?.decAddress1],
|
||||||
|
[cipher.identity?.address2, cipher.identity?.decAddress2],
|
||||||
|
[cipher.identity?.address3, cipher.identity?.decAddress3],
|
||||||
|
[cipher.identity?.city, cipher.identity?.decCity],
|
||||||
|
[cipher.identity?.state, cipher.identity?.decState],
|
||||||
|
[cipher.identity?.postalCode, cipher.identity?.decPostalCode],
|
||||||
|
[cipher.identity?.country, cipher.identity?.decCountry],
|
||||||
|
[cipher.sshKey?.privateKey, cipher.sshKey?.decPrivateKey],
|
||||||
|
[cipher.sshKey?.publicKey, cipher.sshKey?.decPublicKey],
|
||||||
|
[cipher.sshKey?.keyFingerprint || cipher.sshKey?.fingerprint, cipher.sshKey?.decFingerprint],
|
||||||
|
...(cipher.fields || []).flatMap((field) => [
|
||||||
|
[field.name, field.decName] as [unknown, unknown],
|
||||||
|
[field.value, field.decValue] as [unknown, unknown],
|
||||||
|
]),
|
||||||
|
...(cipher.passwordHistory || []).map((entry) => [entry.password, entry.decPassword] as [unknown, unknown]),
|
||||||
|
...fido2EncryptedFields.map((value) => [value, undefined] as [unknown, unknown]),
|
||||||
|
];
|
||||||
|
|
||||||
|
return checks.some(([raw, decrypted]) => !isResolvedEncryptedField(raw, decrypted));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function hasItemKeyFieldMismatch(
|
||||||
|
cipher: Cipher,
|
||||||
|
userEnc: Uint8Array,
|
||||||
|
userMac: Uint8Array
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (!looksLikeCipherString(cipher.key)) return false;
|
||||||
|
const probes = getCipherKeyMismatchProbes(cipher);
|
||||||
|
if (probes.length === 0) return false;
|
||||||
|
|
||||||
|
let itemKey: Uint8Array;
|
||||||
|
try {
|
||||||
|
itemKey = await decryptBw(String(cipher.key).trim(), userEnc, userMac);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (itemKey.length < 64) return false;
|
||||||
|
|
||||||
|
const itemEnc = itemKey.slice(0, 32);
|
||||||
|
const itemMac = itemKey.slice(32, 64);
|
||||||
|
for (const probe of probes) {
|
||||||
|
try {
|
||||||
|
await decryptStr(probe, itemEnc, itemMac);
|
||||||
|
continue;
|
||||||
|
} catch {
|
||||||
|
// Try the legacy user-key field path below.
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await decryptStr(probe, userEnc, userMac);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
// Keep scanning in case another field reveals a repairable mismatch.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function repairCipherKeyMismatches(
|
||||||
|
authedFetch: AuthedFetch,
|
||||||
|
session: SessionState,
|
||||||
|
ciphers: Cipher[]
|
||||||
|
): Promise<number> {
|
||||||
|
if (!session.symEncKey || !session.symMacKey || !Array.isArray(ciphers) || ciphers.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userEnc = base64ToBytes(session.symEncKey);
|
||||||
|
const userMac = base64ToBytes(session.symMacKey);
|
||||||
|
let repaired = 0;
|
||||||
|
|
||||||
|
for (const cipher of ciphers) {
|
||||||
|
if (!cipher?.id || !looksLikeCipherString(cipher.key)) continue;
|
||||||
|
if (!(await hasItemKeyFieldMismatch(cipher, userEnc, userMac))) continue;
|
||||||
|
if (hasUnresolvedEncryptedFields(cipher)) continue;
|
||||||
|
await updateCipher(authedFetch, session, cipher, draftFromDecryptedCipher(cipher));
|
||||||
|
repaired += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return repaired;
|
||||||
|
}
|
||||||
|
|
||||||
async function buildCipherPayload(
|
async function buildCipherPayload(
|
||||||
session: SessionState,
|
session: SessionState,
|
||||||
draft: VaultDraft,
|
draft: VaultDraft,
|
||||||
|
|||||||
@@ -1,13 +1,28 @@
|
|||||||
import { decryptStr, decryptBw } from './crypto';
|
import { decryptStr, decryptBw } from './crypto';
|
||||||
import type { Cipher } from './types';
|
import type { Cipher } from './types';
|
||||||
|
|
||||||
async function decryptField(
|
async function decryptCipherField(
|
||||||
value: string | null | undefined,
|
value: string | null | undefined,
|
||||||
enc: Uint8Array,
|
itemEnc: Uint8Array,
|
||||||
mac: Uint8Array,
|
itemMac: Uint8Array,
|
||||||
|
userEnc: Uint8Array,
|
||||||
|
userMac: Uint8Array,
|
||||||
|
canFallbackToUserKey: boolean,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (!value || typeof value !== 'string') return '';
|
if (!value || typeof value !== 'string') return '';
|
||||||
try { return await decryptStr(value, enc, mac); } catch { return value; }
|
try {
|
||||||
|
return await decryptStr(value, itemEnc, itemMac);
|
||||||
|
} catch {
|
||||||
|
// Try the legacy user-key path for mixed key/field ciphers.
|
||||||
|
}
|
||||||
|
if (canFallbackToUserKey) {
|
||||||
|
try {
|
||||||
|
return await decryptStr(value, userEnc, userMac);
|
||||||
|
} catch {
|
||||||
|
// Preserve the old raw fallback for fields that are genuinely unreadable.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function decryptSingleCipher(
|
export async function decryptSingleCipher(
|
||||||
@@ -17,29 +32,35 @@ export async function decryptSingleCipher(
|
|||||||
): Promise<Cipher> {
|
): Promise<Cipher> {
|
||||||
let itemEnc = userEnc;
|
let itemEnc = userEnc;
|
||||||
let itemMac = userMac;
|
let itemMac = userMac;
|
||||||
|
let usesItemKey = false;
|
||||||
if (encrypted.key) {
|
if (encrypted.key) {
|
||||||
try {
|
try {
|
||||||
const itemKey = await decryptBw(encrypted.key, userEnc, userMac);
|
const itemKey = await decryptBw(encrypted.key, userEnc, userMac);
|
||||||
itemEnc = itemKey.slice(0, 32);
|
if (itemKey.length >= 64) {
|
||||||
itemMac = itemKey.slice(32, 64);
|
itemEnc = itemKey.slice(0, 32);
|
||||||
|
itemMac = itemKey.slice(32, 64);
|
||||||
|
usesItemKey = true;
|
||||||
|
}
|
||||||
} catch { /* keep user key */ }
|
} catch { /* keep user key */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canFallbackToUserKey = usesItemKey;
|
||||||
|
|
||||||
const decrypted: Cipher = {
|
const decrypted: Cipher = {
|
||||||
...encrypted,
|
...encrypted,
|
||||||
decName: await decryptField(encrypted.name, itemEnc, itemMac),
|
decName: await decryptCipherField(encrypted.name, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decNotes: await decryptField(encrypted.notes, itemEnc, itemMac),
|
decNotes: await decryptCipherField(encrypted.notes, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (encrypted.login) {
|
if (encrypted.login) {
|
||||||
decrypted.login = {
|
decrypted.login = {
|
||||||
...encrypted.login,
|
...encrypted.login,
|
||||||
decUsername: await decryptField(encrypted.login.username, itemEnc, itemMac),
|
decUsername: await decryptCipherField(encrypted.login.username, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decPassword: await decryptField(encrypted.login.password, itemEnc, itemMac),
|
decPassword: await decryptCipherField(encrypted.login.password, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decTotp: await decryptField(encrypted.login.totp, itemEnc, itemMac),
|
decTotp: await decryptCipherField(encrypted.login.totp, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
uris: await Promise.all((encrypted.login.uris || []).map(async (u) => ({
|
uris: await Promise.all((encrypted.login.uris || []).map(async (u) => ({
|
||||||
...u,
|
...u,
|
||||||
decUri: await decryptField(u.uri, itemEnc, itemMac),
|
decUri: await decryptCipherField(u.uri, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
}))),
|
}))),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -48,7 +69,7 @@ export async function decryptSingleCipher(
|
|||||||
decrypted.passwordHistory = await Promise.all(
|
decrypted.passwordHistory = await Promise.all(
|
||||||
encrypted.passwordHistory.map(async (entry) => ({
|
encrypted.passwordHistory.map(async (entry) => ({
|
||||||
...entry,
|
...entry,
|
||||||
decPassword: await decryptField(entry?.password, itemEnc, itemMac),
|
decPassword: await decryptCipherField(entry?.password, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -56,36 +77,36 @@ export async function decryptSingleCipher(
|
|||||||
if (encrypted.card) {
|
if (encrypted.card) {
|
||||||
decrypted.card = {
|
decrypted.card = {
|
||||||
...encrypted.card,
|
...encrypted.card,
|
||||||
decCardholderName: await decryptField(encrypted.card.cardholderName, itemEnc, itemMac),
|
decCardholderName: await decryptCipherField(encrypted.card.cardholderName, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decNumber: await decryptField(encrypted.card.number, itemEnc, itemMac),
|
decNumber: await decryptCipherField(encrypted.card.number, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decBrand: await decryptField(encrypted.card.brand, itemEnc, itemMac),
|
decBrand: await decryptCipherField(encrypted.card.brand, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decExpMonth: await decryptField(encrypted.card.expMonth, itemEnc, itemMac),
|
decExpMonth: await decryptCipherField(encrypted.card.expMonth, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decExpYear: await decryptField(encrypted.card.expYear, itemEnc, itemMac),
|
decExpYear: await decryptCipherField(encrypted.card.expYear, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decCode: await decryptField(encrypted.card.code, itemEnc, itemMac),
|
decCode: await decryptCipherField(encrypted.card.code, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encrypted.identity) {
|
if (encrypted.identity) {
|
||||||
decrypted.identity = {
|
decrypted.identity = {
|
||||||
...encrypted.identity,
|
...encrypted.identity,
|
||||||
decTitle: await decryptField(encrypted.identity.title, itemEnc, itemMac),
|
decTitle: await decryptCipherField(encrypted.identity.title, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decFirstName: await decryptField(encrypted.identity.firstName, itemEnc, itemMac),
|
decFirstName: await decryptCipherField(encrypted.identity.firstName, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decMiddleName: await decryptField(encrypted.identity.middleName, itemEnc, itemMac),
|
decMiddleName: await decryptCipherField(encrypted.identity.middleName, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decLastName: await decryptField(encrypted.identity.lastName, itemEnc, itemMac),
|
decLastName: await decryptCipherField(encrypted.identity.lastName, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decUsername: await decryptField(encrypted.identity.username, itemEnc, itemMac),
|
decUsername: await decryptCipherField(encrypted.identity.username, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decCompany: await decryptField(encrypted.identity.company, itemEnc, itemMac),
|
decCompany: await decryptCipherField(encrypted.identity.company, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decSsn: await decryptField(encrypted.identity.ssn, itemEnc, itemMac),
|
decSsn: await decryptCipherField(encrypted.identity.ssn, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decPassportNumber: await decryptField(encrypted.identity.passportNumber, itemEnc, itemMac),
|
decPassportNumber: await decryptCipherField(encrypted.identity.passportNumber, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decLicenseNumber: await decryptField(encrypted.identity.licenseNumber, itemEnc, itemMac),
|
decLicenseNumber: await decryptCipherField(encrypted.identity.licenseNumber, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decEmail: await decryptField(encrypted.identity.email, itemEnc, itemMac),
|
decEmail: await decryptCipherField(encrypted.identity.email, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decPhone: await decryptField(encrypted.identity.phone, itemEnc, itemMac),
|
decPhone: await decryptCipherField(encrypted.identity.phone, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decAddress1: await decryptField(encrypted.identity.address1, itemEnc, itemMac),
|
decAddress1: await decryptCipherField(encrypted.identity.address1, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decAddress2: await decryptField(encrypted.identity.address2, itemEnc, itemMac),
|
decAddress2: await decryptCipherField(encrypted.identity.address2, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decAddress3: await decryptField(encrypted.identity.address3, itemEnc, itemMac),
|
decAddress3: await decryptCipherField(encrypted.identity.address3, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decCity: await decryptField(encrypted.identity.city, itemEnc, itemMac),
|
decCity: await decryptCipherField(encrypted.identity.city, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decState: await decryptField(encrypted.identity.state, itemEnc, itemMac),
|
decState: await decryptCipherField(encrypted.identity.state, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decPostalCode: await decryptField(encrypted.identity.postalCode, itemEnc, itemMac),
|
decPostalCode: await decryptCipherField(encrypted.identity.postalCode, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decCountry: await decryptField(encrypted.identity.country, itemEnc, itemMac),
|
decCountry: await decryptCipherField(encrypted.identity.country, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,11 +114,11 @@ export async function decryptSingleCipher(
|
|||||||
const fingerprint = encrypted.sshKey.keyFingerprint || encrypted.sshKey.fingerprint || '';
|
const fingerprint = encrypted.sshKey.keyFingerprint || encrypted.sshKey.fingerprint || '';
|
||||||
decrypted.sshKey = {
|
decrypted.sshKey = {
|
||||||
...encrypted.sshKey,
|
...encrypted.sshKey,
|
||||||
decPrivateKey: await decryptField(encrypted.sshKey.privateKey, itemEnc, itemMac),
|
decPrivateKey: await decryptCipherField(encrypted.sshKey.privateKey, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decPublicKey: await decryptField(encrypted.sshKey.publicKey, itemEnc, itemMac),
|
decPublicKey: await decryptCipherField(encrypted.sshKey.publicKey, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
keyFingerprint: fingerprint || null,
|
keyFingerprint: fingerprint || null,
|
||||||
fingerprint: fingerprint || null,
|
fingerprint: fingerprint || null,
|
||||||
decFingerprint: await decryptField(fingerprint, itemEnc, itemMac),
|
decFingerprint: await decryptCipherField(fingerprint, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,8 +126,8 @@ export async function decryptSingleCipher(
|
|||||||
decrypted.fields = await Promise.all(
|
decrypted.fields = await Promise.all(
|
||||||
encrypted.fields.map(async (field) => ({
|
encrypted.fields.map(async (field) => ({
|
||||||
...field,
|
...field,
|
||||||
decName: await decryptField(field.name, itemEnc, itemMac),
|
decName: await decryptCipherField(field.name, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decValue: await decryptField(field.value, itemEnc, itemMac),
|
decValue: await decryptCipherField(field.value, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,30 @@ async function decryptField(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function decryptCipherField(
|
||||||
|
value: string | null | undefined,
|
||||||
|
itemEnc: Uint8Array,
|
||||||
|
itemMac: Uint8Array,
|
||||||
|
userEnc: Uint8Array,
|
||||||
|
userMac: Uint8Array,
|
||||||
|
canFallbackToUserKey: boolean
|
||||||
|
): Promise<string> {
|
||||||
|
if (!value || typeof value !== 'string') return '';
|
||||||
|
try {
|
||||||
|
return await decryptStr(value, itemEnc, itemMac);
|
||||||
|
} catch {
|
||||||
|
// Try the legacy user-key path for mixed key/field ciphers.
|
||||||
|
}
|
||||||
|
if (canFallbackToUserKey) {
|
||||||
|
try {
|
||||||
|
return await decryptStr(value, userEnc, userMac);
|
||||||
|
} catch {
|
||||||
|
// Preserve the old raw fallback for fields that are genuinely unreadable.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
async function decryptFieldWithSource(
|
async function decryptFieldWithSource(
|
||||||
value: string | null | undefined,
|
value: string | null | undefined,
|
||||||
itemEnc: Uint8Array,
|
itemEnc: Uint8Array,
|
||||||
@@ -82,32 +106,38 @@ export async function decryptVaultCore(args: DecryptVaultCoreArgs): Promise<Decr
|
|||||||
args.ciphers.map(async (cipher) => {
|
args.ciphers.map(async (cipher) => {
|
||||||
let itemEnc = userEnc;
|
let itemEnc = userEnc;
|
||||||
let itemMac = userMac;
|
let itemMac = userMac;
|
||||||
|
let usesItemKey = false;
|
||||||
if (cipher.key) {
|
if (cipher.key) {
|
||||||
try {
|
try {
|
||||||
const itemKey = await decryptBw(cipher.key, userEnc, userMac);
|
const itemKey = await decryptBw(cipher.key, userEnc, userMac);
|
||||||
itemEnc = itemKey.slice(0, 32);
|
if (itemKey.length >= 64) {
|
||||||
itemMac = itemKey.slice(32, 64);
|
itemEnc = itemKey.slice(0, 32);
|
||||||
|
itemMac = itemKey.slice(32, 64);
|
||||||
|
usesItemKey = true;
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Keep user key fallback.
|
// Keep user key fallback.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemUsesUserKey = sameBytes(itemEnc, userEnc) && sameBytes(itemMac, userMac);
|
const itemUsesUserKey = sameBytes(itemEnc, userEnc) && sameBytes(itemMac, userMac);
|
||||||
|
const canFallbackToUserKey = usesItemKey;
|
||||||
const nextCipher: Cipher = {
|
const nextCipher: Cipher = {
|
||||||
...cipher,
|
...cipher,
|
||||||
decName: await decryptField(cipher.name || '', itemEnc, itemMac),
|
decName: await decryptCipherField(cipher.name || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decNotes: await decryptField(cipher.notes || '', itemEnc, itemMac),
|
decNotes: await decryptCipherField(cipher.notes || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cipher.login) {
|
if (cipher.login) {
|
||||||
nextCipher.login = {
|
nextCipher.login = {
|
||||||
...cipher.login,
|
...cipher.login,
|
||||||
decUsername: await decryptField(cipher.login.username || '', itemEnc, itemMac),
|
decUsername: await decryptCipherField(cipher.login.username || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decPassword: await decryptField(cipher.login.password || '', itemEnc, itemMac),
|
decPassword: await decryptCipherField(cipher.login.password || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decTotp: await decryptField(cipher.login.totp || '', itemEnc, itemMac),
|
decTotp: await decryptCipherField(cipher.login.totp || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
uris: await Promise.all(
|
uris: await Promise.all(
|
||||||
(cipher.login.uris || []).map(async (uri) => ({
|
(cipher.login.uris || []).map(async (uri) => ({
|
||||||
...uri,
|
...uri,
|
||||||
decUri: await decryptField(uri.uri || '', itemEnc, itemMac),
|
decUri: await decryptCipherField(uri.uri || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -117,7 +147,7 @@ export async function decryptVaultCore(args: DecryptVaultCoreArgs): Promise<Decr
|
|||||||
nextCipher.passwordHistory = await Promise.all(
|
nextCipher.passwordHistory = await Promise.all(
|
||||||
cipher.passwordHistory.map(async (entry) => ({
|
cipher.passwordHistory.map(async (entry) => ({
|
||||||
...entry,
|
...entry,
|
||||||
decPassword: await decryptField(entry?.password || '', itemEnc, itemMac),
|
decPassword: await decryptCipherField(entry?.password || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -125,36 +155,36 @@ export async function decryptVaultCore(args: DecryptVaultCoreArgs): Promise<Decr
|
|||||||
if (cipher.card) {
|
if (cipher.card) {
|
||||||
nextCipher.card = {
|
nextCipher.card = {
|
||||||
...cipher.card,
|
...cipher.card,
|
||||||
decCardholderName: await decryptField(cipher.card.cardholderName || '', itemEnc, itemMac),
|
decCardholderName: await decryptCipherField(cipher.card.cardholderName || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decNumber: await decryptField(cipher.card.number || '', itemEnc, itemMac),
|
decNumber: await decryptCipherField(cipher.card.number || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decBrand: await decryptField(cipher.card.brand || '', itemEnc, itemMac),
|
decBrand: await decryptCipherField(cipher.card.brand || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decExpMonth: await decryptField(cipher.card.expMonth || '', itemEnc, itemMac),
|
decExpMonth: await decryptCipherField(cipher.card.expMonth || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decExpYear: await decryptField(cipher.card.expYear || '', itemEnc, itemMac),
|
decExpYear: await decryptCipherField(cipher.card.expYear || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decCode: await decryptField(cipher.card.code || '', itemEnc, itemMac),
|
decCode: await decryptCipherField(cipher.card.code || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cipher.identity) {
|
if (cipher.identity) {
|
||||||
nextCipher.identity = {
|
nextCipher.identity = {
|
||||||
...cipher.identity,
|
...cipher.identity,
|
||||||
decTitle: await decryptField(cipher.identity.title || '', itemEnc, itemMac),
|
decTitle: await decryptCipherField(cipher.identity.title || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decFirstName: await decryptField(cipher.identity.firstName || '', itemEnc, itemMac),
|
decFirstName: await decryptCipherField(cipher.identity.firstName || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decMiddleName: await decryptField(cipher.identity.middleName || '', itemEnc, itemMac),
|
decMiddleName: await decryptCipherField(cipher.identity.middleName || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decLastName: await decryptField(cipher.identity.lastName || '', itemEnc, itemMac),
|
decLastName: await decryptCipherField(cipher.identity.lastName || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decUsername: await decryptField(cipher.identity.username || '', itemEnc, itemMac),
|
decUsername: await decryptCipherField(cipher.identity.username || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decCompany: await decryptField(cipher.identity.company || '', itemEnc, itemMac),
|
decCompany: await decryptCipherField(cipher.identity.company || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decSsn: await decryptField(cipher.identity.ssn || '', itemEnc, itemMac),
|
decSsn: await decryptCipherField(cipher.identity.ssn || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decPassportNumber: await decryptField(cipher.identity.passportNumber || '', itemEnc, itemMac),
|
decPassportNumber: await decryptCipherField(cipher.identity.passportNumber || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decLicenseNumber: await decryptField(cipher.identity.licenseNumber || '', itemEnc, itemMac),
|
decLicenseNumber: await decryptCipherField(cipher.identity.licenseNumber || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decEmail: await decryptField(cipher.identity.email || '', itemEnc, itemMac),
|
decEmail: await decryptCipherField(cipher.identity.email || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decPhone: await decryptField(cipher.identity.phone || '', itemEnc, itemMac),
|
decPhone: await decryptCipherField(cipher.identity.phone || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decAddress1: await decryptField(cipher.identity.address1 || '', itemEnc, itemMac),
|
decAddress1: await decryptCipherField(cipher.identity.address1 || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decAddress2: await decryptField(cipher.identity.address2 || '', itemEnc, itemMac),
|
decAddress2: await decryptCipherField(cipher.identity.address2 || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decAddress3: await decryptField(cipher.identity.address3 || '', itemEnc, itemMac),
|
decAddress3: await decryptCipherField(cipher.identity.address3 || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decCity: await decryptField(cipher.identity.city || '', itemEnc, itemMac),
|
decCity: await decryptCipherField(cipher.identity.city || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decState: await decryptField(cipher.identity.state || '', itemEnc, itemMac),
|
decState: await decryptCipherField(cipher.identity.state || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decPostalCode: await decryptField(cipher.identity.postalCode || '', itemEnc, itemMac),
|
decPostalCode: await decryptCipherField(cipher.identity.postalCode || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decCountry: await decryptField(cipher.identity.country || '', itemEnc, itemMac),
|
decCountry: await decryptCipherField(cipher.identity.country || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,11 +192,11 @@ export async function decryptVaultCore(args: DecryptVaultCoreArgs): Promise<Decr
|
|||||||
const encryptedFingerprint = cipher.sshKey.keyFingerprint || cipher.sshKey.fingerprint || '';
|
const encryptedFingerprint = cipher.sshKey.keyFingerprint || cipher.sshKey.fingerprint || '';
|
||||||
nextCipher.sshKey = {
|
nextCipher.sshKey = {
|
||||||
...cipher.sshKey,
|
...cipher.sshKey,
|
||||||
decPrivateKey: await decryptField(cipher.sshKey.privateKey || '', itemEnc, itemMac),
|
decPrivateKey: await decryptCipherField(cipher.sshKey.privateKey || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decPublicKey: await decryptField(cipher.sshKey.publicKey || '', itemEnc, itemMac),
|
decPublicKey: await decryptCipherField(cipher.sshKey.publicKey || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
keyFingerprint: encryptedFingerprint || null,
|
keyFingerprint: encryptedFingerprint || null,
|
||||||
fingerprint: encryptedFingerprint || null,
|
fingerprint: encryptedFingerprint || null,
|
||||||
decFingerprint: await decryptField(encryptedFingerprint, itemEnc, itemMac),
|
decFingerprint: await decryptCipherField(encryptedFingerprint, itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,8 +204,8 @@ export async function decryptVaultCore(args: DecryptVaultCoreArgs): Promise<Decr
|
|||||||
nextCipher.fields = await Promise.all(
|
nextCipher.fields = await Promise.all(
|
||||||
cipher.fields.map(async (field) => ({
|
cipher.fields.map(async (field) => ({
|
||||||
...field,
|
...field,
|
||||||
decName: await decryptField(field.name || '', itemEnc, itemMac),
|
decName: await decryptCipherField(field.name || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
decValue: await decryptField(field.value || '', itemEnc, itemMac),
|
decValue: await decryptCipherField(field.value || '', itemEnc, itemMac, userEnc, userMac, canFallbackToUserKey),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user