拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Bitwarden 客户端 Direct Keeper 导入器深度解析:设备审批、2FA 流程与 Vault 解密管线

Bitwarden 客户端 Direct Keeper 导入器深度解析设备审批、2FA 流程与 Vault 解密管线【免费下载链接】clientsBitwarden client apps (web, browser extension, desktop, and cli).项目地址: https://gitcode.com/GitHub_Trending/cl/clientsDirect Keeper importer 是 Bitwarden 客户端为 Keeper 用户提供的无导出文件迁移方案用户只需输入 Keeper 账号邮箱与所在区域Bitwarden 客户端便直接通过 Keeper API 完成设备注册、设备审批、2FA 验证、Vault 数据同步与解密再转换为 Bitwarden 的ImportResult导入管线数据。本文以 关联文档 为核心骨架结合libs/importer/src/importers/keeper/下的源码与测试完整还原该功能的设计约束、交互流程、支持的认证方式与已知边界帮助你理解直接导入与既有 CSV/JSON 导入在代码层面的真实差异。一、功能概述从文件导入到直接连接导入Keeper 导入器在libs/importer/src/importers/keeper/目录下共有三套实现对应三种数据来源keeper-csv-importer.ts解析 Keeper 导出的 CSV 文件keeper-json-importer.ts解析 Keeper 导出的 JSON 文件keeper-direct-importer.ts不解析文件而是直接通过 Keeper SDK 客户端 登录并同步 Keeper 账号把同步下来的加密 Vault 解密后再转为 Bitwarden 数据结构。keeper-direct-importer.ts定义了与通用导入管线对接的核心类型// Keeper 直接导入同时产出给通用导入管线的 ImportResult 供 Keeper UI 处理的逐条记录错误列表 export type KeeperDirectImportResult { result: ImportResult; errors: ImportRecordError[]; };也就是说直接导入的产物是一条ImportResult可被后续导入流程继续使用外加一份 Keeper 特有的ImportRecordError[]由 Keeper 专属 UI 在把结果交给导入管线之前先解析错误、向用户确认部分导入。直接导入的适用平台从 import-keeper.component.ts 的源码可以看到直接导入只在桌面端Desktop与浏览器扩展Browser可用Web 端与 CLI 端不支持// Direct import requires platform APIs (deep-linking, native window) that // only exist on desktop and the browser extension. CSV/Json work everywhere. private readonly directSupported this.platformUtilsService.getClientType() ClientType.Desktop || this.platformUtilsService.getClientType() ClientType.Browser;由此Keeper 导入方法下拉框在不同平台上呈现出不同的选项组合平台可选方法Desktop / Browserdirect直接导入、csv、jsonWeb / CLIcsv、json并且桌面端与浏览器端默认选中directWeb 端默认选中csv见defaultKeeperImportMethod的实现。CLI 端不提供 direct 方法只会根据文件扩展名在 CSV/JSON 之间切换详见下文CLI 兼容行为。二、导入方法入口的变化keepercsv / keeperjson 的保留与合并原文档第一条评审要点描述了一次面向用户的入口调整代码中也确实保留了兼容路径Web 下拉框中keepercsv/keeperjson两个独立入口被合并为一个keeper入口旁边增加一个小下拉框用来选择方法direct / CSV / JSON。原有两个 ID 在代码中仍然保留。CLI 行为保持一致bw import keepercsv与bw import keeperjson依旧可用同时 CLI 也接受统一的keeperID并根据文件扩展名自动推断格式。CLI 端的推断逻辑位于 apps/cli/src/tools/import.command.ts// The web UI exposes the Keeper method via a dropdown // The CLI infers it from the file extension when the user passes the unified keeper ID let resolvedFormat: ImportType format; if (format keeper) { const lower filepath.toLowerCase(); if (lower.endsWith(.csv)) { resolvedFormat keepercsv; } else if (lower.endsWith(.json)) { resolvedFormat keeperjson; } else { return Response.badRequest(Cannot determine Keeper file type. Use a .csv or .json file.); } }因此 CLI 使用方式为# 旧用法仍然有效 bw import keepercsv /path/to/keeper-export.csv bw import keeperjson /path/to/keeper-export.json # 新用法统一 ID扩展名决定格式 bw import keeper /path/to/keeper-export.csv bw import keeper /path/to/keeper-export.json扩展Web/扩展端/import路由在扩展中改为弹出窗口原文档第二条评审要点涉及一个看似与 Keeper 无关、实则影响所有导入格式的改动扩展端所有平台的/import路由现在以弹出窗口popout方式打开。此前 Windows 上/import停留在 popup 内部而 macOS/Linux 已经是弹出窗口本次改动统一了行为。这样做的原因在文档中写得很清楚direct 流程会运行一个 websocket 以及一批监听器socket listener如果页面还停留在 popup 中用户只要一点击别处popup 就会被销毁websocket 与监听器随之被拆除导入流程即告中断。以弹出窗口方式承载/import可以保证长生命周期的认证交互稳定执行。该改动作用于所有导入格式而非仅限 Keeper。三、直接导入的完整执行链路从源码看直接导入的调用链为ImportKeeperComponent.submitDirect()→KeeperDirectImportService.handleImport()→Vault.open()登录 SyncDown 解密→KeeperDirectImporter.convertVaultToImportResult()。3.1 组件层表单与提交import-keeper.component.ts 的表单包含三个控件控件校验说明method无direct/csv/json变更时立即触发 UI 更新updateOn: changeemailrequiredemail仅 direct 模式启用切换为 csv/json 时自动禁用region无默认KeeperRegion.Us可选全球 6 个区域区域列表与 keeper-region.ts 一一对应枚举对应服务域名Uskeepersecurity.comEukeepersecurity.euAukeepersecurity.com.auCakeepersecurity.caJpkeepersecurity.jpUsGovgovcloud.keepersecurity.us提交时如果method不是directsubmitDirect直接返回让父组件走文件导入路径否则用邮箱与区域调用KeeperDirectImportService.handleImport()const { result, errors } await this.keeperDirectImportService.handleImport( email.value!, this.formGroup.controls.region.value as KeeperRegion, organizationId, );拿到结果后组件调用confirmPartialImport如果存在逐条记录错误则通过PartialImportDialogComponent弹出部分导入确认对话框由用户决定是继续导入干净结果还是放弃相关门控逻辑在 keeper-import-gate.ts。3.2 服务层登录去重与 Vault 打开keeper-direct-import.service.ts 是一个providedIn: root的 Injectable负责组装ClientOptions并调用Vault.openconst options: ClientOptions { ui: this.keeperDirectImportUIService, region, }; this.inFlight (async () { try { const vault await Vault.open(email, options); const importer new KeeperDirectImporter(); if (organizationId ! undefined) { importer.organizationId organizationId; } return importer.convertVaultToImportResult(vault); } finally { this.inFlight undefined; this.keeperDirectImportUIService.reset(); } })();值得注意的实现细节inFlight字段保存了进行中的 Promise避免用户在导入过程中重复点击提交导致并发登录finally中会重置 UI 服务状态保证下一次导入从干净状态开始。ClientOptions只有两个字段见 client-options.tsregionKeeper 区域与ui认证交互回调集合见下文第四节。3.3 数据层Vault 的解密管线Vault.openaccess/vault.ts内部依次执行static async open(username: string, options: ClientOptions): PromiseVault { const client new Client(options); const loginResult await client.login(username); const pages await client.syncDown(loginResult.sessionToken); const merged Vault.mergeSyncDownPages(pages); return await Vault.processMergedSyncDownPages(merged, loginResult.dataKey); }整个解密流程在processMergedSyncDownPages中按严格依赖顺序执行共 11 步每一步都在 access/vault.ts 中有明确注释解密普通文件夹名每个文件夹用各自的 folder key 加密folder key 又用 master key 加密这里只需要文件夹名解密共享文件夹的 key共享文件夹有自己的 key这些 key 后续用于解密共享文件夹内的记录解密共享文件夹名共享文件夹名用共享文件夹 key 加密解密非共享记录 key存在 record metadata 中用 master key 加密解密共享记录 key存在 shared folder records 中用共享文件夹 key 加密解密关联子记录 key存在 record links 中用父记录 key 加密由于父记录本身也可能是某个关联子记录decryptLinkedRecordKeys采用迭代直至无可解析项的算法见 access/vault.ts解密所有记录汇总前 4-6 步的全部 key逐条解密 record data解密共享文件夹子文件夹名构建完整文件夹路径从childToParent映射向上回溯sanitizeFolderName会把路径中的\与/替换为-以规避路径歧义见 access/vault.ts为每条记录收集其全部文件夹路径记录可同时属于普通文件夹、共享文件夹根与共享文件夹子文件夹组合成VaultItem列表并把解密失败的错误统一收集为VaultRecordError[]。注意第 7 步中的版本判断if (record.version 3) { errors.push({ id: uid, reason: VaultRecordErrorReason.UnsupportedVersion }); continue; }这就是原文档 TODO 中Legacy RecordV2 格式不受支持且没有测试数据的代码出处——Keeper 早期 V2 记录会以UnsupportedVersion错误上报而不是被静默丢弃。SyncDown接口返回的数据可能分多页hasMore/continuationTokenmergeSyncDownPages会把所有页面的字段逐项拼接成一个响应见 access/vault.ts。3.4 转换层KeeperDirectImporter 的记录映射解密后的Vault交给KeeperDirectImporter.convertVaultToImportResult做最终转换keeper-direct-importer.ts流程为解析记录 → 解析引用 → 映射 Vault 错误 → 组织态下把文件夹提升为集合 → 标记成功。不支持的记录类型Keeper 的file与photo记录本质是附件包装器其字节内容只能通过单独的下载流程获取而该流程当前导入器无法使用因此被UNSUPPORTED_RECORD_TYPES集合标记并在解析时直接跳过、报告为UnsupportedType错误const UNSUPPORTED_RECORD_TYPES new Set([file, photo]);特殊记录类型的映射parseRecord中的 switch见 keeper-direct-importer.tsKeeper 记录类型Bitwarden 转换结果bankCardCipherType.Card卡号、有效期、安全码、持卡人姓名、PINdriverLicenseCipherType.IdentitylicenseNumberssnCardCipherType.IdentityssnpassportCipherType.IdentitypassportNumbersshKeysCipherType.SshKey通过 SDK 的import_ssh_key校验密钥失败则回退为安全笔记passphrase 转为 Hidden 字段其余类型保持 Login 或按字段内容转为 SecureNote通用字段处理管线importFieldsimportField数组字段tryImportArrayFieldlogin用户名、password密码、oneTimeCodeTOTP首个进入cipher.login.totp其余转为 Hidden 字段、url多个 URI 全部进入login.uris展开字段tryImportExpandingFieldhost主机名 端口、keyPair公私钥、securityQuestion问题 答案、appFillerKeeper 内部字段直接忽略单值字段importSingleFielddate/birthDate/expirationDate解析为本地化日期字符串name、address、phone、bankAccount组装成可读文本pinCode、secret以 Hidden 类型导入。引用Record Link解析Keeper 记录之间通过以Ref结尾的字段类型互相关联如addressRef。collectReferences收集所有引用resolveReferences在所有记录解析完成后再解析引用因为被引用的目标记录可能排在引用方之后把目标记录对应字段的值复制到引用方字段中见 keeper-direct-importer.ts。测试中 Amazon Account 的address字段来自两条被引用地址记录正是该机制的验证。错误映射mapVaultErrorReasonUnsupportedVersion→UnsupportedFeatureFolderDecryptionFailed→FolderDecryptionFailedDecryptionFailed及其余 →Error。文件夹解密失败时记录本身仍会导入只是失去文件夹归属进入根目录。四、认证交互设备审批、2FA 与 UI 回调直接导入绕不开 Keeper 自身的账号安全流程。Client.login()access/services/client.ts是一个状态机驱动的登录过程支持的状态包括REQUIRES_AUTH_HASH、REGION_REDIRECT区域跳转会切换服务器地址并重新注册设备、DEVICE_APPROVAL_REQUIRED、REQUIRES_DEVICE_ENCRYPTED_DATA_KEY、2FA 验证、Cloud SSO 等。所有需要人机交互的环节都通过 ui/ui.ts 中定义的Ui接口回调给上层 UI实现与协议逻辑解耦export interface Ui { // 设备审批 selectApprovalMethod(method: DeviceApprovalChannel[]): PromiseDeviceApprovalChannel | Cancel; provideApprovalCode(method: DeviceApprovalChannel, options?): Promisestring | Cancel | Resend | TryAnother; // 2FA selectTwoFactorMethod(channels: TwoFactorMethod[]): PromiseTwoFactorMethod | Cancel; provideTwoFactorCode(method: TwoFactorMethod, options?): Promisestring | Cancel | Resend | TryAnother; // DUO selectDuoMethod(methods: DuoMethod[], phoneNumber: string): PromiseDuoMethod | Cancel; waitForDuoPush(method: DuoMethod): Promisetypeof Cancel | typeof TryAnother | void; // Keeper DNA selectDnaMethod(methods: DnaMethod[]): PromiseDnaMethod | Cancel; waitForDnaPush(): Promisetypeof Cancel | typeof TryAnother | void; // Cloud SSO ssoLogin(url: string): Promisestring | Cancel; // 密码提示延迟到服务端要求时 promptForPassword(options?): Promisestring | Cancel; // 错误展示 showError(message: string): Promisevoid; }4.1 设备审批渠道device-approval-channel.ts 定义了四种审批渠道原文档标记为全部已实现[x]枚举含义Email(1)邮箱链接点击 / 邮箱验证码KeeperPush(2)Keeper 推送TwoFactor(3)通过 2FA 完成设备审批AdminApproval(4)管理员审批从login()的设备审批处理代码看当前实际向用户提供的是前三种Email、KeeperPush、TwoFactor见 client.ts。ProvideApprovalCodeOptions与ProvideTwoFactorCodeOptions还支持hiddenKeeper 在设备审批走 2FA流程中隐藏已配置的具体方法、canResend如 SMS 是否支持重发与previousCodeRejected服务端拒绝上一次验证码后的重试提示等状态标识用于驱动Resend/TryAnother交互。4.2 2FA 方法支持矩阵two-factor-method.ts 定义了 Keeper 的 2FA 方法枚举与原文档勾选状态逐条对应枚举含义文档状态Totp(1)TOTP✅Sms(2)SMS 验证码✅Duo(3)Duo细分见下✅Rsa(4)RSA SecurID❌ 未实现Backup(5)Backup code✅U2f(6)U2F未提及WebAuthn(7)WebAuthn⚠️ 部分[-]KeeperPush(8)Keeper Push✅KeeperDna(9)Keeper DNA细分见下✅其中Duo在 duo-method.ts 中细分为四种方式均已在Ui接口中提供对应回调DuoMethod含义文档状态Push(1)Duo/Push✅Sms(2)Duo/SMS✅Voice(3)Duo/Voice✅Passcode(4)Duo/Passcode✅Keeper DNA在 dna-method.ts 中细分为DnaMethod含义文档状态Push(1)Keeper DNA Push✅Code(2)Keeper DNA Code✅4.3 错误提示的 i18n 映射原文档 TODO 中要求核对 getValidationErrorI18nKey 中的错误文案是否与真实错误对应。该方法的实际映射关系为错误类型i18n keyKeeperAuthErrorCancelledmultifactorAuthenticationCancelledKeeperAuthErrorMfaFailedmultifactorAuthenticationFailedKeeperAuthErrorUnsupportedTwoFactorMethodkeeperUnsupported2faMethodKeeperAuthErrorSocketErrorkeeperConnectionError其他errorOccurred错误码定义在 errors/keeper-auth-error.tsUI 会在submitDirect的 catch 中把错误映射为表单级错误并标记邮箱控件 touched。原文档中当用户只有不支持的 2FA 类型时测试错误提示是否正常弹出这一项已标记完成[x]。五、原文档 TODO 的源码对照原文档的 TODO 列表与代码逐条对应便于理解当前实现边界TODO源码线索状态空文件夹可能因文件夹由记录添加而被忽略keeper-direct-importer.tsprocessFolder只在parseRecord循环中被调用Vault 侧也只把有记录的文件夹路径收集进recordFolders未决是否需要includeSharedFolders标志Client.syncDown的请求参数中可见未决Bitwarden 中记录能否同时存在于多个文件夹测试已覆盖一条记录同时在两个文件夹的场景Production MySQL Database同时位于两个Development/...路径下已通过测试验证可行Legacy RecordV2 不支持vault.tsrecord.version 3直接报UnsupportedVersion未实现无测试数据名称以Ref结尾的自定义字段是否会被忽略keeper-direct-importer.tsimportField对endsWith(Ref)的字段直接 return只通过引用解析机制处理未决导入成功但仍有错误弹窗confirmPartialImport在无错误时直接返回true不再弹窗已解决 ✅只有不支持的 2FA 类型时的表现keeperUnsupported2faMethod错误文案 UnsupportedTwoFactorMethod错误码已测试 ✅六、测试验证机器无关的断言与错误处理直接导入器的测试位于 keeper-direct-importer.spec.tsjest node 环境。它使用spec-data/keeper-direct/sync-down-fixture.json中 base64 编码的 SyncDown 响应与 master key 构造 Vault再运行完整转换。测试中有两个值得注意的做法钉住 locale 与 timezone由于日期字段birthDate、expirationDate等在生产环境中使用用户的 locale 与时区格式化测试通过 mockDate.prototype.toLocaleString固定为en-USUTC保证断言与机器环境无关见 keeper-direct-importer.spec.ts错误处理粒度为单条记录parseRecords对每条记录 try/catch单条记录解析抛错只产生一条ImportRecordError不会中断其余记录且失败记录的文件夹不会被创建见 keeper-direct-importer.spec.ts 的模拟测试。测试覆盖的记录类型包括address、bankAccount、bankCard、birthCertificate、contact、databaseCredentials、driverLicense、encryptedNotes、general、healthInsurance、login含 17 个字段的完整映射与引用解析、membership、passport、serverCredentials、softwareLicense、sshKeys含 passphrase 与非法密钥回退、ssnCard同时验证了file/photo记录被报告为UnsupportedType以及三种 Vault 错误到ImportRecordErrorReason的映射。七、底层协议基础设施protobuf 与 SDK 风格客户端access/目录下的协议层为直接导入提供了完整的 Keeper API 通信能力access/proto/10 个.proto文件定义 Keeper API 消息类型api-request、client、sync-down、record、push、ssocloud、enterprise、graph-sync、breachwatch、notification-centeraccess/generated/由 proto 文件通过bufbuild/protoc-gen-es生成的 TypeScript 代码直接提交到仓库常规开发无需重新生成proto 变更后的重新生成命令见 proto/README.mdcd libs/importer npx -p bufbuild/protoc-gen-es protoc --es_out src/importers/keeper/access/generated --es_opt ts_nochecktrue,targetts --proto_path src/importers/keeper/access/proto src/importers/keeper/access/proto/*.protoaccess/services/http.tsHTTP 传输、crypto.tsAES-ECB/AES-CBC、RSA-ECB、key 派生等、keys.tsKeeperKey 加解密、socket.tsPush Socket 连接用于 Keeper Push / DNA 审批推送、client.ts登录状态机clientVersion为ts17.0.0。从目录结构与ClientOptions { region, ui }的设计可以看出access/层是一套按 Keeper SDK 风格组织的迷你客户端协议与 UI 完全解耦任何上层当前是 Bitwarden 导入器未来也可能是其他调用方只需实现Ui接口即可复用。八、总结与实践建议直接导入是文件之外的全新迁移路径适用于 Desktop 与浏览器扩展端Web 端与 CLI 端继续走 CSV/JSON。直接导入流程依赖长生命周期 websocket因此扩展端/import必须使用弹出窗口承载。CLI 的keeper统一 ID 只是入口合并底层仍是keepercsv/keeperjson两个 importer按扩展名分派bw import keepercsv/bw import keeperjson完全兼容。认证覆盖全面设备审批支持 Email链接/验证码、Keeper Push、经 2FA 审批2FA 支持 SMS、TOTP、DuoPush/SMS/Voice/Passcode、Keeper DNAPush/Code与 Backup codeWebAuthn 与 RSA 尚未完整实现仅 WebAuthn 有部分支持。已知边界清晰file/photo记录不导入报告为 UnsupportedTypeRecordV2 解密失败报告为 UnsupportedVersion文件夹解密失败时记录仍导入但归入根目录单条记录失败不影响整体导入由 UI 弹部分导入确认框。阅读顺序建议想了解交互与入口看 import-keeper.component.ts 与 keeper-direct-import.service.ts想了解解密与转换看 vault.ts 与 keeper-direct-importer.ts想了解认证枚举看 access/enums/想验证行为直接运行 keeper-direct-importer.spec.ts。【免费下载链接】clientsBitwarden client apps (web, browser extension, desktop, and cli).项目地址: https://gitcode.com/GitHub_Trending/cl/clients创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门