Bitwarden 贡献文档
⮐ Bitwarden Contributing Documentation我的博客联系我
  • 关于
  • 入门
    • 概述
    • 工具
    • 服务器
      • 设置指南
      • 高级服务器设置
      • 数据库
        • MSSQL
        • 实体框架
      • 事件日志
      • Ingress 隧道
      • SCIM
      • 自托管指南
      • 系统管理门户
      • 单点登录 (SSO)
        • 本地 IdP
        • Okta
      • 故障排除
      • 用户机密
      • 公共 API
    • 网页客户端
      • 网页密码库
        • WebAuthn
      • 浏览器端
        • 生物识别解锁
        • Firefox 隐私模式
      • 桌面端
        • Mac App Store Dev
        • Microsoft Store
        • Native Messaging Test Runner
        • 更新测试
      • CLI
      • 故障排除
    • 移动端
      • Android
        • F-Droid
      • iOS
      • .NET MAUI (legacy)
        • Android
        • iOS
        • watchOS
    • SDK
      • 内部 SDK
      • Secrets Manager
        • Integrations
          • Kubernetes
    • 业务 App
      • 目录连接器
        • JumpCloud
        • OpenLDAP Docker 服务器
      • Key Connector
      • Splunk App
  • 贡献
    • 贡献
    • 代码样式
      • =Android & Kotlin
      • Angular & TypeScript
      • C#
      • =Rust
      • T-SQL
      • =Swift
      • Tailwind
    • 数据库迁移
      • 进化数据库设计
    • 提交签名
    • 拉取请求
      • =贡献审查程序
      • 分支
      • 代码审查
      • UI 审查 - Chromatic
    • 无障碍
    • 依赖管理
    • 功能标记
    • 模板存储库
    • 测试
      • =数据库集成测试
      • 负载测试
      • 单元测试
        • 命名约定
        • 测试结构
    • 修改用户机密
  • 架构
    • 架构
    • 架构决策记录 (ADR)
      • 0001 - Angular Reactive Forms
      • 0002 - Public API for modules
      • 0003 - Adopt Observable Data Services for Angular
      • 0004 - Refactor State Service
      • 0005 - Refactor Api Service
      • 0006 - Clients: Use Jest Mocks
      • 0007 - Manifest V3 sync Observables
      • 0008 - Server: Adopt CQRS
      • 0009 - Composition over inheritance
      • 0010 - Angular Modules
      • 0011 - Scalable Angular Clients folder structure
      • 0012 - Angular Filename convention
      • 0013 - Avoid layered folder structure for request/response models
      • 0014 - Adopt Typescript Strict flag
      • 0015 - Short Lived Browser Services
      • 0016 - Move Decryption and Encryption to Views
      • 0017 - Use Swift to build watchOS app
      • 0018 - Feature management
      • 0019 - Adoption of Web Push
      • 0020 - Observability with OpenTelemetry
      • 0021 - Logging to Standard Output
      • =0022 - Authorization
      • =0023 - Identifying Integrated Clients
    • 移动客户端架构
      • =Android
      • =iOS
        • =推送通知故障排除提示
      • =.NET MAUI (legacy)
        • =概述
        • watchOS
    • =SDK 架构
      • =数据模型
      • =依赖
      • Password Manager
        • Web
          • =互操作性
      • =Secrets Manager
      • =服务器绑定
      • =版本控制和破坏性更改
    • 网络客户端架构
      • 概述
      • 数据模型
      • 表示层
        • Angular
        • CLI
      • =依赖注入
      • 服务层
        • Vision
        • 实现
    • 服务器架构
    • 深度剖析
      • 身份验证
        • 双重身份验证
      • =授权
      • =浏览器自动填充
        • 收集页面详细信息
        • 生成并执行填充脚本
        • 表单提交检测
        • Shadow DOM
        • =内联自动填充菜单
      • Captcha
      • =只读数据库副本
      • 事件日志
      • =FIDO2 和通行密钥
        • =凭据
        • =操作
        • =命名惯例
        • =实现
          • =提供程序
            • =浏览器扩展
          • =依赖方
            • =用于解密的通行密钥
        • =术语表
      • 推送通知
        • 移动端推送通知
        • 其他客户端推送通知
      • =SSH 密钥和代理
        • =SSH 代理
      • =状态提供程序框架
        • =派生状态
    • =安全
      • =定义
      • =原则
        • =P01 - 锁定的密码库是安全的
        • =P02 - 半受损设备密码库的有限安全性
        • =P03 - 完全损坏的系统没有安全性
        • =P04 - 控制密码库数据的访问权限
        • =P05 - 将安全漏洞的影响降至最低
      • =要求
由 GitBook 提供支持
在本页
  • 背景和问题陈述​
  • 目前是如何做的​
  • 存在的问题​
  • 为什么是现在?​
  • 考虑的方案​
  • 决策结果​
  • 积极的后果​
  • 消极的后果​
  • 实施​
  1. 架构
  2. 架构决策记录 (ADR)

0016 - Move Decryption and Encryption to Views

上一页0015 - Short Lived Browser Services下一页0017 - Use Swift to build watchOS app

最后更新于1年前

对应的

背景和问题陈述​

Bitwarden 有几个不同的模型来表示数据,中详细描述了这些数据。本 ADR 中,我们将重点关注以下两种模型:

  • <Domain> - 表示已加密数据状态的域模型。

  • <Domain>View - 表示域模型的已解密状态的视图模型。

由于我们至少有两个不同的模型来表示同一域的已加密和已解密状态,这也意味着我们需要一种在两个模型之间进行转换的方法,即加密和解密数据。

目前是如何做的​

当前完成此操作的方法是让 <Domain>Service 公开包含解密视图的 Observable,或者使用基于 Promise 的方法来解密它。<Domain>Service 通常还公开一个 encrypt 方法,该方法从 View 和 Domain 模型进行转换。

Domain 模型本身通常还有一个 decrypt 方法,用于执行实际的解密逻辑。它通过在 EncString 对象上调用 decrypt 来实现这一点,而对象又依赖于全局容器服务来检索 CryptoService 和 EncryptService 执行实际操作。

存在的问题​

这种方法有几个问题:

  • 域模型与视图模型紧密耦合。

  • 加密和解密被分成两个不同的地方。解密直接发生在域模型上,而加密发生在服务中。从逻辑上讲,它们是紧密耦合的,并且应该彼此相邻。

  • 我们依靠全局容器服务来检索 CryptoService 和 EncryptService。

  • 我们当前的模型充当转型管道。Request -> Data -> Domain -> View。

  • 将来如果有一种方法可以支持每个域的多个 View 模型,那就太好了。

为什么是现在?​

目前,Secret Manager 在加密和解密的管理方式方面遇到了一些摩擦。它不遵循同步本地状态的典型模式,而是依赖于对服务器的直接请求来获取数据。然后需要对数据进行解密。

目前,此加密和解密逻辑由 <Domain>Service 处理,但这违反了单一责任原则。这也使得我们的服务难以跟踪,因为它现在需要了解请求、响应、加密和解密。

考虑的方案​

  • 将解密转移至 <Domain>Service -- 我们已经为不同的域提供了服务,这些服务目前用于处理加密,因此将逻辑转移到这里是合理的。

  • 将逻辑移转移至 <Domain>View 本身 -- 将逻辑移至 View 模型本身,并结合通用服务来加密和解密视图。

决策结果​

选择的方案:将逻辑转移至 <Domain>View 本身。

积极的后果​

  • 域服务不再需要实现定制的加密和解密逻辑。这遵循单一责任原则。

  • 域模型不再与视图紧密耦合。

  • 目前,我们的每个域可以拥有多个视图。

消极的后果​

  • 由于加密和解密现在是在通用 EncryptService 上完成的,因此可以绕过预期的流程。一个例子是 Cipher,CipherService 有一个 updateHistoryAndEncrypt 方法,它在加密之前计算密码历史记录。

实施​

class FolderDomain implements DecryptableDomain {
  id: string;
  name: EncString;
  revisionDate: Date;

  keyIdentifier(): string | null {
    return null;
  }
}

class FolderView implements Encryptable<Folder> {
  id: string = null;
  name: string = null;
  revisionDate: Date = null;

  keyIdentifier(): string | null {
    return null;
  }

  async encrypt(encryptService: EncryptService, key: SymmetricCryptoKey): Promise<Folder> {
    const folder = new Folder();
    folder.id = this.id;
    folder.revisionDate = this.revisionDate;

    folder.name = this.name != null ? await encryptService.encrypt(this.name, key) : null;

    return folder;
  }

  static async decrypt(encryptService: EncryptService, key: SymmetricCryptoKey, model: Folder) {
    const view = new FolderView();
    view.id = model.id;
    view.revisionDate = model.revisionDate;

    view.name = await model.name?.decryptWithEncryptService(encryptService, key);

    return view;
  }
}

它将像这样被使用:

// Fetch from server
const response: FolderResponse = await this.folderApiService.getFolder(id);
const folderData: FolderData = new FolderData(response);
const folder: Folder = new Folder(folderData);

// Decrypt / Encrypt
const folderView: FolderView = this.encryptionService.decryptView(FolderView, folder, key);
folderView.name = "New folder name";
const encryptedFolder: Folder = this.encryptionService.encryptView(folderView, key);

// Update
const request: FolderRequest = new FolderUpdateRequest(encryptedFolder);

示例。

官方页面地址
数据模型
文件夹 PR