@openvtc/trust-tasks 0.24.1 → 0.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/dist/_shared/components.d.ts +55 -2
  3. package/dist/_shared/components.d.ts.map +1 -1
  4. package/dist/device/list/0.1/payload.d.ts +4 -4
  5. package/dist/device/list/0.1/payload.d.ts.map +1 -1
  6. package/dist/device/list/0.1/payload.js +4 -0
  7. package/dist/device/list/0.1/payload.js.map +1 -1
  8. package/dist/device/list/0.2/payload.d.ts +4 -4
  9. package/dist/device/list/0.2/payload.d.ts.map +1 -1
  10. package/dist/device/list/0.2/payload.js +4 -0
  11. package/dist/device/list/0.2/payload.js.map +1 -1
  12. package/dist/device/register/0.1/payload.d.ts +4 -4
  13. package/dist/device/register/0.1/payload.d.ts.map +1 -1
  14. package/dist/device/register/0.1/payload.js +4 -0
  15. package/dist/device/register/0.1/payload.js.map +1 -1
  16. package/dist/device/register/0.2/payload.d.ts +4 -4
  17. package/dist/device/register/0.2/payload.d.ts.map +1 -1
  18. package/dist/device/register/0.2/payload.js +4 -0
  19. package/dist/device/register/0.2/payload.js.map +1 -1
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/keys/sign-csr/0.1/payload.d.ts +729 -0
  25. package/dist/keys/sign-csr/0.1/payload.d.ts.map +1 -0
  26. package/dist/keys/sign-csr/0.1/payload.js +280 -0
  27. package/dist/keys/sign-csr/0.1/payload.js.map +1 -0
  28. package/dist/keys/sign-jws/0.1/payload.d.ts +352 -0
  29. package/dist/keys/sign-jws/0.1/payload.d.ts.map +1 -0
  30. package/dist/keys/sign-jws/0.1/payload.js +207 -0
  31. package/dist/keys/sign-jws/0.1/payload.js.map +1 -0
  32. package/dist/rooms/_shared/0.1/blobs.d.ts +1 -1
  33. package/dist/rooms/_shared/0.1/blobs.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/_shared/components.ts +57 -0
  36. package/src/device/list/0.1/payload.ts +4 -0
  37. package/src/device/list/0.2/payload.ts +4 -0
  38. package/src/device/register/0.1/payload.ts +4 -0
  39. package/src/device/register/0.2/payload.ts +4 -0
  40. package/src/index.ts +2 -0
  41. package/src/keys/sign-csr/0.1/payload.ts +646 -0
  42. package/src/keys/sign-jws/0.1/payload.ts +271 -0
  43. package/src/rooms/_shared/0.1/blobs.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -11,6 +11,83 @@ The package versions over **its own API** — what a consumer compiles against
11
11
  not over `SPEC.md`. Below 1.0 a breaking change bumps the leading non-zero
12
12
  component.
13
13
 
14
+ ## 0.24.2 — 2026-10-11
15
+
16
+
17
+ ### Added
18
+
19
+ - **keys**: Sign-jws and sign-csr — protocol signatures from a VTA-held key (#749)
20
+
21
+ A custodian that hardens keys/sign frames every payload under its own
22
+ opaque signing domain (vti.vta.opaque-signing.v1 || 0x00 || payload), so
23
+ nothing signed there verifies as a protocol signature. That is the point
24
+ of the hardening, and it also means nobody can obtain a usable JWS or a
25
+ certificate request for a custodian-held key. A community server acting
26
+ as an OpenID4VP verifier for swiyu and EUDI wallets needs both: a signed
27
+ request object (typ oauth-authz-req+jwt) and proof-of-possession JWTs,
28
+ and a PKCS#10 request to obtain an EUDI relying-party access certificate.
29
+
30
+ Two narrow tasks where the custodian builds the protocol bytes itself, so
31
+ the key stays custodian-held and the custodian signs only what it built:
32
+
33
+ - keys/sign-jws/0.1: the producer sends header members and a payload
34
+ (a JSON object, or payloadBase64Url); the custodian sets alg from the
35
+ key type (EdDSA, ES256, ES256K, ML-DSA-44/65), refuses alg, crit, b64,
36
+ jwk, jku and x5u, checks an x5c leaf against the key, serialises both
37
+ halves with RFC 8785 and signs the bare JWS signing input. Audited by a
38
+ digest of the signing input, never the payload.
39
+ - keys/sign-csr/0.1: the producer names the subject as ordered typed
40
+ attributes from a closed set and optional subject alternative names;
41
+ the custodian builds the CertificationRequestInfo from those and the
42
+ key's own public key (P-256 with ecdsa-with-SHA256, or Ed25519) and
43
+ returns the PEM request with the key's multibase public key. No
44
+ extension but subjectAltName is ever requested.
45
+
46
+ Registers the signJws and signCsr device capabilities (sign-jws and
47
+ sign-csr in 0.1). A custodian must not require sign for either; one that
48
+ frames keys/sign under its own domain should not accept sign in their
49
+ place, since its sign grant cannot yield a JWS or CSR.
50
+
51
+ Bindings regenerated (Rust, TS, Go, Dart); no version bumps.
52
+
53
+ Refs OpenVTC/verifiable-trust-infrastructure#1988
54
+
55
+
56
+
57
+ ### Specifications
58
+
59
+ - **rooms**: Define the sealed record body, signed by its author and bound to its location (#747)
60
+
61
+ * spec(rooms): define the sealed record body, signed by its author and bound to its location
62
+
63
+ records/put 0.2 already relied on "the sealed body the author signed" to
64
+ detect a substituted blob, and blobs.schema.json defined the body's `file`
65
+ member, but nothing defined the body itself. Without it, two clients cannot
66
+ read each other's records, and nobody can check the signature.
67
+
68
+ SealedRecordBody (rooms/_shared/0.1/room.schema.json) is the JSON inside
69
+ SealedRecord.ciphertext on attributed and private rooms:
70
+
71
+ - the open tier's title, description, body and tags;
72
+ - an optional `file` (FileManifest);
73
+ - `author`;
74
+ - a Data Integrity proof by `author` (eddsa-jcs-2022, or ecdsa-jcs-2019 for
75
+ a hardware-backed signer).
76
+
77
+ Readers MUST verify the proof and MUST refuse a record that fails it, not show
78
+ it with a warning.
79
+
80
+ The body also signs its own location (roomId, key, version). The AEAD binds
81
+ those against the host, but any member can open and re-seal a record. Without
82
+ the signed location, a member could copy another member's body to a new key
83
+ or version, and on a private room, where the host records no author, readers
84
+ would attribute it to the original author. On attributed rooms, readers also
85
+ check `author` against the author the host recorded.
86
+
87
+ records/put 0.2 and keys/seal 0.1 point at the definition. keys/seal says a
88
+ key holder seals what it is given and never signs for the writer. No payload
89
+ or response schema changes, so no version moves and no generated type changes.
90
+
14
91
  ## 0.24.1 — 2026-10-11
15
92
 
16
93
 
@@ -392,11 +392,11 @@ export type Capability_AclV0_2 = string;
392
392
  /**
393
393
  * Fine-grained capability flag scoped to the device's allowed contexts. See SPEC.md for the full semantics of each. Capability values are additive: a consumer MUST ignore a value it does not recognise rather than reject the binding, and MUST NOT treat an unrecognised value as conferring anything.
394
394
  */
395
- export type Capability_DeviceV0_1 = "vault-read" | "vault-write" | "proxy-login" | "fill-release" | "policy-admin" | "device-admin" | "sign" | "key-mint" | "sign-trust-task" | "credential-write" | "memory-read" | "memory-write" | "room-present" | "room-open" | "key-export" | "sign-sshsig" | "external-accounts-manage" | "external-auth-use";
395
+ export type Capability_DeviceV0_1 = "vault-read" | "vault-write" | "proxy-login" | "fill-release" | "policy-admin" | "device-admin" | "sign" | "key-mint" | "sign-trust-task" | "credential-write" | "memory-read" | "memory-write" | "room-present" | "room-open" | "key-export" | "sign-sshsig" | "sign-jws" | "sign-csr" | "external-accounts-manage" | "external-auth-use";
396
396
  /**
397
397
  * Fine-grained capability flag scoped to the device's allowed contexts. See SPEC.md for the full semantics of each. Capability values are additive: a consumer MUST ignore a value it does not recognise rather than reject the binding, and MUST NOT treat an unrecognised value as conferring anything.
398
398
  */
399
- export type Capability_DeviceV0_2 = "vaultRead" | "vaultWrite" | "proxyLogin" | "fillRelease" | "policyAdmin" | "deviceAdmin" | "sign" | "keyMint" | "signTrustTask" | "credentialWrite" | "memoryRead" | "memoryWrite" | "roomPresent" | "roomOpen" | "keyExport" | "signSshsig" | "externalAccountsManage" | "externalAuthUse";
399
+ export type Capability_DeviceV0_2 = "vaultRead" | "vaultWrite" | "proxyLogin" | "fillRelease" | "policyAdmin" | "deviceAdmin" | "sign" | "keyMint" | "signTrustTask" | "credentialWrite" | "memoryRead" | "memoryWrite" | "roomPresent" | "roomOpen" | "keyExport" | "signSshsig" | "signJws" | "signCsr" | "externalAccountsManage" | "externalAuthUse";
400
400
  /**
401
401
  * Number of chunks in the bundle, equal to ceil(expectedSizeBytes / chunkSize). Bounded at 4096 so that the manifest itself — one digest per chunk — fits in the single document that carries it under the same message-size reasoning as a chunk.
402
402
  */
@@ -5411,6 +5411,59 @@ export interface Field {
5411
5411
  [k: string]: unknown | undefined;
5412
5412
  };
5413
5413
  }
5414
+ /**
5415
+ * The `file` member of a sealed record body: everything that describes a file, sealed with the record, signed by the record's author, and never seen by the host. Defined here so that clients interoperate; no host task carries it, because the host receives only ciphertext.
5416
+ *
5417
+ * **Byte encodings.** In every derivation below, `fileId` is its **32 decoded bytes**, never its base64url text; `roomId` is the **UTF-8 bytes** of the room identifier exactly as it appears on the wire; string constants are their ASCII bytes; `u8`, `u32be` and `u64be` are unsigned integers of that width, big-endian. Epochs are unsigned 64-bit on the wire. An implementation that holds epochs in a narrower type **MUST** refuse a value it cannot represent with `malformedRequest` (or, reading a manifest, refuse the file), and **MUST NOT** truncate it.
5418
+ *
5419
+ * **The file key.** `file_key = HKDF-SHA256(ikm = storage_key(epoch), salt = fileId, info = "openvtc/room/file/v1" || roomId || u64be(epoch))`, where `storage_key(epoch)` is the room's per-epoch storage key, the one records are sealed under. It is derived per file and never stored. It is bound to the room and the epoch, so a key released for one room cannot open a blob relocated from another, and releasing it exposes one file and nothing else.
5420
+ *
5421
+ * **Padding.** With `padding: "padme"`, the plaintext of length `L = size` is extended with zero bytes to `padme(L)`: for `L < 2`, `padme(L) = L`; otherwise `E = floor(log2 L)`, `S = floor(log2 E) + 1`, `z = E − S`, `mask = 2^z − 1`, and `padme(L) = (L + mask) & ~mask`. The padded length is `padme(size)`; without padding it is `size`. Padding bytes **MUST** be zero.
5422
+ *
5423
+ * **The STREAM construction.** The padded plaintext is cut into `segmentSize`-byte segments, the last holding the remainder; an empty plaintext is one empty final segment. Segment `i` is sealed with ChaCha20-Poly1305 under `file_key`, with nonce `u32be(0) || u64be(i)` and associated data `"openvtc/room/file/v1" || roomId || fileId || u64be(epoch) || u64be(i) || u8(final)`, where `final` is 1 on the last segment and 0 on every other. A host that truncates, reorders or splices ciphertext produces an authentication failure, never a shorter or rearranged file. The nonces are counters, so a `file_key` MUST NOT seal two different files; a fresh random `fileId` is what guarantees it. Each sealed segment is one transfer chunk of the blob (BlobManifest), so `segmentSize + 16` is the blob's `chunks.chunkSize`, and an empty file is one 16-byte chunk.
5424
+ *
5425
+ * **Reading.** A reader opens the record, derives or obtains the file key, and **MUST**, before trusting a byte:
5426
+ * - check that the blob's `chunks.chunkSize` equals `segmentSize + 16` and that `chunks.chunkCount` equals `max(1, ceil(paddedLength / segmentSize))`, and refuse the file otherwise;
5427
+ * - decrypt every segment in order, refusing the file on any authentication failure or on a final flag anywhere but the last segment;
5428
+ * - check that the decrypted length equals the padded length, that every padding byte is zero, and, after removing the padding, that the plaintext matches `digest`.
5429
+ *
5430
+ * A reader **MUST** refuse a file that fails any check, and **MUST NOT** present it with a warning instead. A reader that streams **MUST NOT** treat the plaintext as received — save it under its final name, render it, or hand it to another component — until the digest has verified; it writes to a temporary destination and discards that destination on a mismatch.
5431
+ */
5432
+ export interface FileManifest {
5433
+ /**
5434
+ * 32 random bytes, base64url without padding, minted by the uploader for this file and never reused. The salt of the file key.
5435
+ */
5436
+ fileId: string;
5437
+ /**
5438
+ * The file's name as the uploader gave it. Sealed: the host never sees it. Readers MUST treat it as untrusted text: strip path separators before using it as a local file name, and never interpret it as markup.
5439
+ */
5440
+ name: string;
5441
+ /**
5442
+ * IANA media type, without parameters. Advisory: a reader decides how to render from the bytes and its own policy, never from this alone.
5443
+ */
5444
+ mediaType?: string;
5445
+ /**
5446
+ * Plaintext bytes, before padding. The decrypted length is `padme(size)` with `padme` padding and `size` without.
5447
+ */
5448
+ size: number;
5449
+ /**
5450
+ * Digest of the plaintext bytes. Signed, with the rest of the record body, by the record's author, so a reader that verifies it knows which member put exactly these bytes there.
5451
+ */
5452
+ digest: DigestMultibase;
5453
+ /**
5454
+ * The epoch whose storage key the file key derives from. Usually the record's own epoch; a record rewritten under a later epoch that keeps the same blob keeps the file's original epoch here. Unsigned 64-bit; see Byte encodings.
5455
+ */
5456
+ epoch: number;
5457
+ /**
5458
+ * Plaintext bytes per STREAM segment: the transfer chunk size less the 16-byte tag, so that one sealed segment is one chunk.
5459
+ */
5460
+ segmentSize: number;
5461
+ /**
5462
+ * Whether the plaintext was padded before sealing, so that ciphertext sizes do not fingerprint known documents. `padme`: zero bytes appended to reach `padme(size)`, the formula above. Absent means `none`. The padding is inside the final segments, and a reader checks it is zero and removes it by truncating to `size`.
5463
+ */
5464
+ padding?: "none" | "padme";
5465
+ blobRef: BlobRef;
5466
+ }
5414
5467
  /**
5415
5468
  * A person's account on one forge. `id` is authoritative; `login` is for display only, because logins can be renamed and re-registered.
5416
5469
  */