Open Site Pass

用于登记进入工作现场的开放二维码标准。任何应用都能显示通行证,任何终端都能读取。

版本 1,2026 年 11 月。依据 CC BY 4.0 免费使用。

承包商每周都在不同现场登记,每处都要相同的信息:姓名、公司、手机、车辆、Site Safe 卡。Open Site Pass 让他们把这些信息存在应用中,出示二维码即可提交。无论由谁开发,任何应用都能显示通行证,任何登记终端都能读取。

在编写本标准之前,我们寻找过现有标准。使用二维码通行证的访客登记产品各有自己的格式,只能在该产品内使用。通用身份标准(W3C Verifiable Credentials、OpenID for Verifiable Presentations、ISO 18013-5 移动驾照)需要钱包应用和多步交换,超出了现场门口一台平板的需要。因此这是一种小型格式,任何编程语言都能用内置加密功能读取和校验。

1. 二维码中有什么

二维码包含由点连接的三部分纯文本:

OSP1.<pass>.<signature>
  • OSP1 表示这是 Open Site Pass 版本 1。
  • <pass> 是 JSON 格式的通行证,UTF-8 编码,然后是不带填充的 base64url。
  • <signature> 是对 <pass> 的 ASCII 文本(与二维码中完全一致)使用 SHA-256 的 ECDSA P-256 签名,采用 64 字节 r ‖ s 形式,然后是不带填充的 base64url。这是 Web Crypto 的 crypto.subtle.sign 生成的形式。

2. 通行证

{
  "version": 1,
  "issuer": "https://timeclock.kiwi",
  "keyId": "2026-11-3fa9c1",
  "holderId": "b7f3c2e1a94d0c55e1f3b2a8",
  "issuedAt": "2026-11-01T09:42:00.000Z",
  "expiresAt": "2026-11-01T09:43:00.000Z",
  "firstName": "Sam",
  "lastName": "Taylor",
  "company": "Harbour Electrical Ltd",
  "phone": "+64215550142",
  "vehicleRegistration": "QKT482",
  "documents": [
    { "type": "site-safe-passport", "number": "SS-482913", "expiresOn": "2027-03-31" }
  ]
}
version此版本始终为 1。
issuer签署通行证者的 HTTPS 源,例如 https://timeclock.kiwi。由此查找其密钥。
keyId由发行方的哪个密钥签署。
holderId同一个人在同一发行方处相同,在其他发行方处不同。让现场能识别回访的访客。不得是员工编号、电子邮箱或任何能在别处识别其身份的内容。
issuedAt, expiresAtISO 8601 时间。通行证应持续约一分钟,且不得超过 15 分钟,这样其照片很快就会失效。
firstName, lastName姓名。
company其工作单位(如果选择分享)。
phone手机号码,最好使用国际格式。
email电子邮箱。
vehicleRegistration所乘车辆的车牌号。
documents卡和证书:每项都有 type(来自下表),以及可选的 number、issuer 和 expiresOn(YYYY-MM-DD)。

必填:version, issuer, keyId, holderId, issuedAt, expiresAt, firstName。其他内容均为可选,由持有人每次选择。

文件类型

site-safe-passportSite Safe passport
public-liability-insurance公众责任保险
electrical-registration电工注册
plumbing-gasfitting-registration管道、燃气或排水工注册
forklift-licence叉车执照(F endorsement)
first-aid-certificate急救证书
working-at-heights高处作业
confined-spaces受限空间
construction-induction-card建筑入职培训卡(White Card)
cscs-cardCSCS card

如需提议其他类型,请通过下方联系我们。终端会保留未知类型的文件,但不会将其匹配到任何内容。

3. 发行方密钥

发行方在 <issuer>/.well-known/open-site-pass/keys.json 发布公钥:

{
  "keys": [
    {
      "keyId": "2026-11-3fa9c1",
      "publicKey": { "kty": "EC", "crv": "P-256", "x": "…", "y": "…" }
    }
  ]
}

每个 publicKey 都是 JWK,Web Crypto 可直接加载。停止用某密钥签名后,请继续发布至少一天。Timeclock.Kiwi 的密钥位于 https://timeclock.kiwi/.well-known/open-site-pass/keys.json。

4. 终端规则

  1. 用发行方的密钥校验签名,并确认通行证未过期。为时钟差异预留前后最多 2 分钟。
  2. 只从发行方自己的 HTTPS 源获取密钥,并缓存,以便离线校验通行证。
  3. 显示详情和发行方域名,并在保存前让访客确认。
  4. 将文件详情视为持有人输入的内容,而非已核实。如果现场需要核实,请由人工查看实物卡。
  5. 您可以只接受信任的发行方的通行证,或接受任何 HTTPS 发行方并显示“Verified by <domain>”。
  6. 切勿向持有人的应用索取通行证以外的内容。分享什么由持有人决定。

5. 应用规则

  1. 通行证显示期间,至少每分钟生成一个新通行证。
  2. 让持有人选择包含哪些可选信息,可以每次选择,也可以保存为可更改的选择。
  3. 切勿放入持有人未选择的任何内容,包括其雇主、位置或内部 ID。

6. 用 JavaScript 校验通行证

这就是终端所需的全部内容,只使用浏览器内置的加密功能:

async function readPass(text, fetchKeys) {
  const [prefix, body, signature] = text.split('.');
  if (prefix !== 'OSP1') throw new Error('Not a site pass');

  const pass = JSON.parse(atob(body.replace(/-/g, '+').replace(/_/g, '/')));
  const keys = await fetchKeys(pass.issuer); // GET <issuer>/.well-known/open-site-pass/keys.json
  const jwk = keys.find(k => k.keyId === pass.keyId)?.publicKey;

  const key = await crypto.subtle.importKey('jwk', jwk, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
  const fromB64 = s => Uint8Array.from(atob(s.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
  const ok = await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, fromB64(signature), new TextEncoder().encode(body));

  const now = Date.now(), slack = 2 * 60 * 1000;
  if (!ok || Date.parse(pass.expiresAt) + slack < now) throw new Error('Not valid');
  return pass;
}

在 Timeclock.Kiwi 中使用

任何登录 Timeclock.Kiwi 应用的人都可以从“我的现场通行证”显示通行证,所有 Timeclock.Kiwi 现场登记终端都能读取,包括遵循此格式的其他应用的通行证。

我们随时乐意为您提供帮助。
欢迎联系我们,我们会尽力让我们的员工考勤系统满足您的需求。