解释在 grunt 中实现双因子签名流程

解读

面试官问“双因子签名”,并不是让候选人背密码学公式,而是考察三件事:

  1. 前端构建安全水位的认知——国内监管、企业内控、客户合规都要求“构建产物不可抵赖”;
  2. Grunt 插件生态的熟悉程度——能否把“签名”这种看似 DevOps 的工作无缝嫁接到 Grunt 任务流;
  3. 工程化闭环的落地能力——签名不是生成一串 hash 就结束,还要验证、存档、告警、回滚。

因此,回答时要先给出“双因子”的国内合规定义(SM2+RSA、证书+密钥、人+机),再落到 Grunt 的 task、template、event 三层 API,最后讲清“签名验签”如何嵌入 CI/CD 并满足等保 2.0 对“不可否认性”的审计要求。

知识点

  1. 双因子签名模型

    • 因子 A:国密 SM2 证书(或 RSA-2048 硬件密钥),由 CA 签发,存于 USBKey/HSM;
    • 因子 B:静态口令 + 动态令牌(企业微信 OTP、钉钉 OTP、腾讯 TOTP),确保“人”在场;
    • 签名对象:构建产物(js、css、wasm、docker image tar)的摘要 SM3/SHA-256
    • 输出格式:CMS/PKCS#7 detached 签名,附带证书链,文件名统一 .sig
    • 验签端:运维侧 Jenkins、GitLab CI、K8s admission webhook,任意验签失败即阻断部署
  2. Grunt 侧关键插件与 API

    • grunt-contrib-clean:清理历史 .sig,防止重放;
    • grunt-hash-crypto:计算 SM3/SHA-256 摘要,生成 manifest.json
    • grunt-exec:调用gmsslopenssl 命令行完成 SM2/RSA 签名;
    • grunt-prompt:在 CI 环境中弹出“OTP”输入框,把令牌作为环境变量传入签名脚本;
    • grunt-eventify:监听 sign:done 事件,自动把 .sigmanifest.json 上传到阿里云 OSS 版本目录,并写一条审计日志到企业钉钉群。
  3. 合规与审计

    • 等保 2.0 “安全区域边界”要求:构建服务器与签名服务器物理隔离,Grunt 只扮演“客户端”,真正的私钥在 HSM 中;
    • 日志留存:6 个月以上,包含 taskName、filePath、signerCN、OTP、timestamp、sigFileUrl
    • 回滚策略:若验签失败,Grunt 触发 grunt-rollback 任务,自动把上一个已签版本标记为 latest,并给值班经理发语音告警。

答案

“双因子签名”在 Grunt 中的落地可以拆成 5 个任务,顺序写在 Gruntfile.js 里:

  1. 摘要生成
    先用 grunt-hash-crypto 遍历 dist/ 目录,输出 manifest.json,内容示例:
    { "app.3e21.js": "sm3:de90bc…", "vendor.css": "sm3:ab12ef…" }

  2. 人机挑战
    通过 grunt-prompt 让构建者在终端输入6 位 OTP(企业微信令牌),Grunt 把值注入 process.env.OTP_TOKEN;同时读取插在本地 USB 口上的国密 USBKey 证书别名(如 CN=frontend-builder)。

  3. 双因子签名
    grunt-exec 里调用:
    gmssl sm2sign -key "$USBKEY_ALIAS" -pass "$OTP_TOKEN" -in manifest.json -out manifest.json.sig
    这条命令同时用到“证书私钥”(因子 A)与“OTP”(因子 B),缺一则命令返回非 0,Grunt 立即 fail

  4. 验签闭环
    同一任务链里再执行:
    gmssl sm2verify -cert "$CA_CERT" -in manifest.json.sig -content manifest.json
    验签失败 Grunt 触发 grunt.fail.fatal('签名验证未通过,部署已阻断'),并调用 grunt-rollback 把上一个带 .sig 的可用版本标记为 latest

  5. 审计与上传
    监听 sign:done 事件,把 manifest.jsonmanifest.json.sig、证书 SN、OTP 前两位掩码、时间戳打成一条 JSON,POST 到企业审计系统,同时上传到阿里云 OSS 版本桶 /project/v1.2.3/ 目录,对象 ACL 为私有,仅供运维拉取。

通过以上 5 步,Grunt 在不改动原有构建流的前提下,把“双因子签名”变成一条普通任务,既满足国密合规,又能在 CI 日志里留下完整证据链。

拓展思考

  1. 混合云场景:如果构建在GitHub Actions 海外 runner,而 HSM 在阿里云加密服务 VPC,可用 Grunt 触发 grunt-oss-utilmanifest.json 上传到 OSS 后,调用函数计算签名网关(VPC 内)完成 SM2 签名,再把 .sig 回写 OSS,实现“构建与签名跨云隔离”。

  2. 密钥轮换:每 90 天通过 grunt-x509-rotate 任务自动调用中国金融认证中心 CFCA 的 RESTful 接口重签证书;新证书生效后,旧证书保留在 CRL 但不再用于签名,Grunt 会在 sign 任务前检查证书有效期,小于 30 天即告警

  3. 前端运行时验签:把 manifest.json.sig 随构建产物一起下发,页面加载时通过浏览器 WebAssembly 版 gmssl 对本地计算出的摘要做验签,失败则阻断 Vue 路由初始化,防止 CDN 投毒,实现“端到端不可否认”。