跳转至

NFC 绑定

sim-hw 每次启动时都会生成 NFC 载荷,模拟嵌入在物理 Inklet 设备中的 NFC 标签。该载荷用于实现触碰配对的设备绑定。

NFC v2 载荷格式

inklet://bind?v=2&kid=<keyId>&hw=<hwId>&cv=<credentialVersion>&p=<proof>
参数 描述
v 协议版本,固定为 2
kid 工厂主密钥 ID
hw 设备硬件 UUID(32 位十六进制,无连字符)
cv 凭证版本号(吊销并重发后递增)
p Base64url 编码的 128-bit truncated HMAC proof(16 字节)

示例:

inklet://bind?v=2&kid=k1&hw=a1b2c3d4e5f60708090a0b0c0d0e0f10&cv=1&p=AAAAAAAAAAAAAAAA

密钥派生与 proof 计算

NFC v2 采用一机一密方案:

deviceKey = HKDF-SHA256(masterKey[kid], salt=hwId, info="inklet:nfc-bind:v2")
proof     = Truncate128(HMAC-SHA256(deviceKey, "v={v}&kid={kid}&hw={hw}&cv={cv}"))
  • masterKey 存储在后端的 Secret Manager,永远不持久化、不记录、不传输到任何客户端。
  • 每台设备的 deviceKey 由 masterKey 和该设备的 hwId 唯一派生,一台设备的密钥泄露不影响其他设备。
  • proof 绑定了载荷的版本、keyId、hwId 和凭证版本,修改任意字段均会使 proof 失效。

v1 格式已废弃

旧版 inklet:1:{hwId}:{signature} 格式(基于单一 FACTORY_SECRET 的 HMAC truncated to 16 hex chars)已不再被后端接受。sim-hw 现在生成 NFC v2 URI 格式载荷。

载荷文件

sim-hw 每次启动时将 NFC 载荷写入 {data-dir}/nfc-payload。你可以读取此文件来模拟扫描设备的 NFC 标签。

cat devices/kitchen/nfc-payload
# inklet://bind?v=2&kid=k1&hw=a1b2c3d4e5f60708090a0b0c0d0e0f10&cv=1&p=AAAAAAAAAAAAAAAA

绑定流程

完整的 NFC 绑定流程如下:

物理设备                               sim-hw 等效操作
────────────────                       ─────────────────
设备上的 NFC 标签             →           数据目录中的 nfc-payload 文件
用户将手机触碰设备            →           用户从文件中复制 URI 载荷
App 读取 NFC 载荷             →           用户在 sim-dashboard 中粘贴载荷
App 解析 URI 参数             →           sim-dashboard 解析 URI
App 调用 bind/nfc API         →           sim-dashboard 调用 bind/nfc API
后端验证 proof                →           后端验证 proof(相同)
设备绑定到用户                →           设备绑定到用户(相同)

详细步骤:

  1. App 读取 NFC --- 在现实中,移动应用读取 NFC 标签得到 inklet://bind?... URI。在模拟器中,你复制 nfc-payload 文件的内容。

  2. App 调用 API --- 应用(或 sim-dashboard)解析 URI 参数并发送 POST /api/devices/bind/nfc 请求:

    curl -X POST https://dev.iminklet.com/api/devices/bind/nfc \
      -H "Authorization: Bearer {accessToken}" \
      -H "Content-Type: application/json" \
      -d '{
        "version": 2,
        "keyId": "k1",
        "hwId": "a1b2c3d4e5f60708090a0b0c0d0e0f10",
        "credentialVersion": 1,
        "proof": "AAAAAAAAAAAAAAAA"
      }'
    
  3. 后端验证 --- 后端用 HKDF-SHA256 派生该设备的 deviceKey,重新计算 proof 并与提交的值常量时间比较。匹配则将设备绑定到当前认证用户,并返回 {"device": {...}, "status": "bound"}

  4. 设备接收通知 --- 后端通过 MQTT 向设备发布 bound 命令。sim-hw 收到后显示绑定确认信息。

安全注意事项

密钥管理

masterKey 由工厂控制台通过 KMS/Secret Manager 管理,不得出现在任何日志、响应或客户端代码中。sim-hw 在本地开发环境中使用 NFC_MASTER_KEYS 环境变量配置测试密钥。

  • 一机一密:每台设备的 proof 是设备专属的,单台设备密钥泄露不影响其他设备。
  • Proof 覆盖载荷完整性:vkidhwcv 任何一个字段被篡改,proof 验证立即失败。
  • credentialVersion 防重放:工厂吊销并重发后 cv 递增,旧版本 proof 立即失效。
  • Proof 长度 128 bit:在设备绑定场景下对在线猜测无实用边际,同时满足 NFC NDEF 记录的空间限制。

测试 NFC 绑定

使用 sim-hw 测试 NFC 绑定流程:

  1. 启动模拟设备:

    python -m eink_hw --data-dir devices/kitchen
    
  2. 读取 NFC 载荷:

    cat devices/kitchen/nfc-payload
    
  3. 在 sim-dashboard 中,点击"Bind Device",选择"NFC Payload"标签,粘贴载荷字符串。

  4. 或者,直接解析 URI 并调用 API:

    # 假设载荷为 inklet://bind?v=2&kid=k1&hw=abc...&cv=1&p=xxx
    curl -X POST http://localhost:4000/api/devices/bind/nfc \
      -H "Authorization: Bearer {your-token}" \
      -H "Content-Type: application/json" \
      -d '{"version":2,"keyId":"k1","hwId":"abc...","credentialVersion":1,"proof":"xxx"}'
    
  5. 设备应显示"Device bound successfully"并出现在你的仪表盘中。