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, expiresAt | ISO 8601 时间。通行证应持续约一分钟,且不得超过 15 分钟,这样其照片很快就会失效。 |
| firstName, lastName | 姓名。 |
| company | 其工作单位(如果选择分享)。 |
| phone | 手机号码,最好使用国际格式。 |
| 电子邮箱。 | |
| vehicleRegistration | 所乘车辆的车牌号。 |
| documents | 卡和证书:每项都有 type(来自下表),以及可选的 number、issuer 和 expiresOn(YYYY-MM-DD)。 |
必填:version, issuer, keyId, holderId, issuedAt, expiresAt, firstName。其他内容均为可选,由持有人每次选择。
文件类型
site-safe-passport | Site 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-card | CSCS 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. 终端规则
- 用发行方的密钥校验签名,并确认通行证未过期。为时钟差异预留前后最多 2 分钟。
- 只从发行方自己的 HTTPS 源获取密钥,并缓存,以便离线校验通行证。
- 显示详情和发行方域名,并在保存前让访客确认。
- 将文件详情视为持有人输入的内容,而非已核实。如果现场需要核实,请由人工查看实物卡。
- 您可以只接受信任的发行方的通行证,或接受任何 HTTPS 发行方并显示“Verified by <domain>”。
- 切勿向持有人的应用索取通行证以外的内容。分享什么由持有人决定。
5. 应用规则
- 通行证显示期间,至少每分钟生成一个新通行证。
- 让持有人选择包含哪些可选信息,可以每次选择,也可以保存为可更改的选择。
- 切勿放入持有人未选择的任何内容,包括其雇主、位置或内部 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 现场登记终端都能读取,包括遵循此格式的其他应用的通行证。