provared 0.5.0 → 0.7.0
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.
- package/CHANGELOG.md +1496 -0
- package/README.md +18 -3
- package/bin/provared-check.mjs +128 -13
- package/docs/guide.md +346 -24
- package/docs/threat-model.md +299 -21
- package/net/headers.js +1 -0
- package/package.json +2 -2
- package/page/check-page.js +18 -32
- package/page/index.html +6 -0
- package/page/render.js +183 -10
- package/spec/provared-format.md +771 -37
- package/src/book.js +544 -19
- package/src/check.d.ts +4 -0
- package/src/check.js +3 -1
- package/src/denial.js +123 -0
- package/src/der.js +56 -6
- package/src/dev.d.ts +1 -1
- package/src/dev.js +1 -1
- package/src/documents.js +8 -3
- package/src/guard.js +12 -1
- package/src/index.js +10 -0
- package/src/jws.js +8 -0
- package/src/keys.js +48 -1
- package/src/recorder.js +131 -5
- package/src/seal.js +32 -4
- package/src/sealer.js +812 -0
- package/src/signatures.js +12 -6
- package/src/slip.js +72 -5
- package/src/timestamp.js +55 -16
- package/src/tools.js +30 -13
- package/src/tree.js +139 -7
- package/src/types.d.ts +272 -11
- package/src/witness.js +63 -0
- package/src/witnessing.js +362 -0
package/docs/threat-model.md
CHANGED
|
@@ -91,6 +91,47 @@ follows the checking steps of the Web Authentication standard.
|
|
|
91
91
|
section 6b. Slips should still be short-lived.
|
|
92
92
|
- Who the issuer is. The slip proves which key signed, not whose key it
|
|
93
93
|
is.
|
|
94
|
+
- Against a page that has the passkey sign a record dressed as a sign-in,
|
|
95
|
+
where the passkey's website name (its `rpId`) serves anything but the
|
|
96
|
+
signing page. A passkey signs only a challenge, and the challenge for a
|
|
97
|
+
slip, an approval or a cancellation is the fingerprint of the record,
|
|
98
|
+
which the person cannot read on the device's prompt. Any page at an
|
|
99
|
+
origin under the slip's `rpId` (the website's own operator, a script
|
|
100
|
+
running on one of its pages, or the operator of any subdomain) can hand
|
|
101
|
+
the device the fingerprint of a record it wrote as an ordinary sign-in
|
|
102
|
+
challenge: the client data of a sign-in and of a record have the same
|
|
103
|
+
shape, and the person sees a sign-in prompt. No device and no page need
|
|
104
|
+
be broken into. The checker compares the origin and the rpId with what
|
|
105
|
+
the slip itself says; and, where the reader names the address they
|
|
106
|
+
expect the person to sign at (`issuerOrigins`, `--origin`, or the box
|
|
107
|
+
on the checking page; format description, section 4.4), with that
|
|
108
|
+
address too, refusing a slip signed anywhere else
|
|
109
|
+
(`issuer-origin-not-expected`). What each measure does:
|
|
110
|
+
- A passkey made for a name that serves only the signing page closes
|
|
111
|
+
this for that passkey: the device lets it sign only for pages under
|
|
112
|
+
that name, so no other page can have it sign anything. Provared's own
|
|
113
|
+
signing page is served at sign.prova.red, a name that serves nothing
|
|
114
|
+
else.
|
|
115
|
+
- A reader who names the address is protected by naming alone against
|
|
116
|
+
every other address under the `rpId`, whatever name the passkey was
|
|
117
|
+
made for: the client data the passkey signs must say that address, so
|
|
118
|
+
a record signed at any other address under the `rpId` is refused. It
|
|
119
|
+
does not protect against another page served at the named address
|
|
120
|
+
itself.
|
|
121
|
+
- A page that shows the record before it asks for the signature helps
|
|
122
|
+
only against a page that asks openly for a record to be signed. It
|
|
123
|
+
does nothing against a prompt dressed as a sign-in, which shows no
|
|
124
|
+
record.
|
|
125
|
+
- Naming an address protects only the reader who names it. It does not
|
|
126
|
+
stop the passkey signing records at other pages, which readers who
|
|
127
|
+
name no address, or name another one, accept.
|
|
128
|
+
|
|
129
|
+
What stays open: a reader who names no address is protected only by
|
|
130
|
+
which passkey they trust; whoever serves the named address (the host of
|
|
131
|
+
the site, or whoever holds its domain name) can serve another page
|
|
132
|
+
there, and a reader's naming does not tell that page from the real one;
|
|
133
|
+
and a passkey made for a name that serves other pages stays exposed to
|
|
134
|
+
every reader who does not name the address.
|
|
94
135
|
|
|
95
136
|
## 4. The Stub and the countersignature
|
|
96
137
|
|
|
@@ -130,8 +171,11 @@ platform gave the call (`provared.event-id`), of one the agent's side
|
|
|
130
171
|
chose and sent (`provared.trace-id`, `provared.request-id`), of part
|
|
131
172
|
of the request and the answer (`provared.http-request`: the method, the
|
|
132
173
|
address and the body; `provared.http-response`: the status and the
|
|
133
|
-
body; no header is covered),
|
|
134
|
-
(`provared.error`)
|
|
174
|
+
body; no header is covered), of the error the agent was given
|
|
175
|
+
(`provared.error`), of the name of the tool that was called
|
|
176
|
+
(`provared.tool-name`), of the identifier the model gave the tool call
|
|
177
|
+
(`provared.tool-call-id`) or of the tool's result (`provared.tool-result`,
|
|
178
|
+
in the one form of RFC 8785, as the arguments are).
|
|
135
179
|
|
|
136
180
|
- *What it shows.* Someone who holds the platform's own log can work the
|
|
137
181
|
fingerprint out again and match the two records, without either
|
|
@@ -144,7 +188,11 @@ body; no header is covered), or of the error the agent was given
|
|
|
144
188
|
share one fingerprint, so a match with one of them does not show that
|
|
145
189
|
it was the same call. Only `provared.trace-id`, `provared.request-id`
|
|
146
190
|
and `provared.event-id` are meant to pick out one call, and only they
|
|
147
|
-
may be named more than once in a stub.
|
|
191
|
+
may be named more than once in a stub. `provared.tool-call-id` picks
|
|
192
|
+
out the model's request for the call, which the agent's software may
|
|
193
|
+
carry out more than once (a retry): two stubs then name one identifier.
|
|
194
|
+
A tool's name and a tool's result are content: every call of one tool
|
|
195
|
+
shares its name's fingerprint, and identical results share one.
|
|
148
196
|
- *The body's bytes* are what RFC 9530 digests: the content after any
|
|
149
197
|
content coding (such as gzip) and before any transfer coding. A
|
|
150
198
|
platform that logs the decoded body of a message sent compressed will
|
|
@@ -158,8 +206,13 @@ body; no header is covered), or of the error the agent was given
|
|
|
158
206
|
at all: which one a platform writes into its log differs from one
|
|
159
207
|
platform to another, and is for whoever relies on it to find out. That
|
|
160
208
|
the fingerprint was made from the text the name says: a checker cannot
|
|
161
|
-
tell; only someone who holds that text can. An identifier
|
|
162
|
-
short or easy to guess gives no privacy
|
|
209
|
+
tell; only someone who holds that text can. An identifier, a tool's
|
|
210
|
+
name or a result that is short or easy to guess gives no privacy
|
|
211
|
+
through its fingerprint: a tool's name is found by trying the
|
|
212
|
+
framework's or the server's tool names, and a short result (`"ok"`,
|
|
213
|
+
`{}`) by trying the likely values. The record holds fingerprints and
|
|
214
|
+
not the result itself so that the record stays small and fixed, not to
|
|
215
|
+
hide the result.
|
|
163
216
|
- *Against A1 and A3* (the agent's own side). The binding is written by
|
|
164
217
|
the agent's side, so a dishonest one can leave it out, or name a
|
|
165
218
|
wrong identifier; only the match with the platform's log, made later,
|
|
@@ -172,10 +225,14 @@ body; no header is covered), or of the error the agent was given
|
|
|
172
225
|
match: whoever writes the stub must give the address as sent.
|
|
173
226
|
- *A reserved name not on the list* is refused (`bad-field`), so that a
|
|
174
227
|
name that may be given a meaning later cannot be used now with another;
|
|
175
|
-
so is one of the
|
|
228
|
+
so is one of the seven once-only names named twice (two `provared.arguments`
|
|
176
229
|
fingerprints in one stub would leave unclear which was the call's). A
|
|
177
230
|
book written before this rule that holds such a name no longer passes
|
|
178
|
-
its check.
|
|
231
|
+
its check. The three names added after version 0.6.0 (`provared.tool-name`,
|
|
232
|
+
`provared.tool-call-id`, `provared.tool-result`) are refused by a
|
|
233
|
+
checker of version 0.5.0 or 0.6.0, which knows the first seven only,
|
|
234
|
+
and read as labels by one of 0.4.0 or earlier: a book that names them
|
|
235
|
+
checks in full only with a later checker.
|
|
179
236
|
|
|
180
237
|
## 4a. Limits, conditions, approvals and the other side's records
|
|
181
238
|
|
|
@@ -479,7 +536,8 @@ unnoticed).
|
|
|
479
536
|
what is left still checks. This can hide the very stub that shows the agent outside its
|
|
480
537
|
slip. A seal closes this for everything before it (section 6a). After
|
|
481
538
|
the last seal, only a copy of a top fingerprint, with its number of
|
|
482
|
-
entries, held by someone else would show it
|
|
539
|
+
entries, or a copy of a later seal, held by someone else would show it
|
|
540
|
+
(section 6a, "Seals held elsewhere").
|
|
483
541
|
- **Writing a different history with the agent's keys.** A1, A3 or A5
|
|
484
542
|
can sign a second, different chain under the same slip. Countersigned
|
|
485
543
|
stubs resist this, because the service's keys are needed. One-sided
|
|
@@ -549,8 +607,38 @@ person checking gives.
|
|
|
549
607
|
- **The end of the book.** The last seal, with everything after it, can
|
|
550
608
|
be removed, and what is left still checks. Only a copy of that seal, or
|
|
551
609
|
of its top fingerprint and number of entries, held by someone else
|
|
552
|
-
shows it. A
|
|
553
|
-
|
|
610
|
+
shows it. A seal held elsewhere can be handed to the checker beside the
|
|
611
|
+
book (below), and a witness that signs for each seal (section 6f)
|
|
612
|
+
holds the latest one it signed for. A public place where seals are
|
|
613
|
+
kept, which gathers the seals of many books, is not built.
|
|
614
|
+
- **Seals held elsewhere.** The person, an auditor, or any copy kept out
|
|
615
|
+
of the recorder's reach can hand the checker, beside the book, up to 64
|
|
616
|
+
seal entries it holds (option `seals`, `--seal` on the command line, files on
|
|
617
|
+
the checking page). A seal at entry n stands at entry n of the
|
|
618
|
+
book, so the checker reports a book that ends before that place
|
|
619
|
+
(`book-cut-short`) and a book that holds another line there
|
|
620
|
+
(`seal-not-in-book`). This closes the end of a book cut off after a seal
|
|
621
|
+
someone else holds, back to any place before that seal, and a second
|
|
622
|
+
history shown to someone who holds a seal of the first. What it does
|
|
623
|
+
not close: a book cut back after the latest seal anyone holds; a book,
|
|
624
|
+
or a part of one, never shown to anyone who keeps a seal; and a holder
|
|
625
|
+
who keeps no seal at all. A seal handed over counts only where the
|
|
626
|
+
person named its recorder (`sealKeys`), and only where the device checked
|
|
627
|
+
its Ed25519 and ML-DSA-87 signatures; on a device without SLH-DSA (the
|
|
628
|
+
Python version, and browsers that have ML-DSA, such as Edge and Brave
|
|
629
|
+
155 when tested) it counts on those two, and says so; on a device
|
|
630
|
+
without ML-DSA it is not used. It is compared with the book by its
|
|
631
|
+
content: the same content with other signatures is the same seal, and
|
|
632
|
+
is said, not reported against the book. It can only show a problem
|
|
633
|
+
with the book, never excuse one, so this widens nothing. Nothing in a
|
|
634
|
+
seal names its book, so a seal of another book by the same recorder
|
|
635
|
+
(one it kept before, when it started a new book) is reported against
|
|
636
|
+
the book being checked: hand over only that book's seals. A seal handed over that does not pass its own check is a
|
|
637
|
+
problem with the whole check, never set aside. Its time-stamps are
|
|
638
|
+
checked and shown, but are not used for the entries of the book: where
|
|
639
|
+
they differ from those beside the same seal in the book, the checker
|
|
640
|
+
says so. A Show does not use a seal held elsewhere, and says so: single
|
|
641
|
+
pages cannot show what the book holds at the place the seal states.
|
|
554
642
|
- **Entries after the last time-stamp.** They are not time-stamped.
|
|
555
643
|
- **A recorder who seals a false book.** A1 holding the agent's keys and
|
|
556
644
|
the recorder's keys can write another history and seal it. A time-stamp
|
|
@@ -659,7 +747,17 @@ person checking gives.
|
|
|
659
747
|
such a key, so the checker refuses the key itself. For the same reason
|
|
660
748
|
it refuses an Ed25519 signature whose point R is of small order, under
|
|
661
749
|
any key: the key's own holder could make one for any message, which one
|
|
662
|
-
library accepts and another refuses.
|
|
750
|
+
library accepts and another refuses. It also refuses, itself, an
|
|
751
|
+
Ed25519 signature whose number S is not below the order L, which RFC
|
|
752
|
+
8032 requires and some libraries do not check, and an ES256 passkey key
|
|
753
|
+
that is not a point on its curve (`bad-key`), which can check nothing
|
|
754
|
+
and which libraries report in different ways. A time-stamp signed with
|
|
755
|
+
Ed25519 must name SHA-512 as its fingerprint method, with no
|
|
756
|
+
parameters, as RFC 8419 requires, and may name the certificate that
|
|
757
|
+
signed it only once in each form (RFC 5035, sections 3 and 5): two
|
|
758
|
+
checkers could otherwise pick different certificates. A time-stamp is
|
|
759
|
+
read as strict DER throughout, every set it holds in the order DER
|
|
760
|
+
requires, so that two checkers cannot read one time-stamp in two ways. The key in a
|
|
663
761
|
certificate must be written exactly as its standard sets out, so that
|
|
664
762
|
two checkers cannot disagree about whether it can be read.
|
|
665
763
|
|
|
@@ -893,6 +991,174 @@ make a pass: it needs the keys of the agent that passes on.
|
|
|
893
991
|
every stub under it, as outside the slip. The record is still sound.
|
|
894
992
|
Only an eleventh pass in a row, which no slip can allow, is refused.
|
|
895
993
|
|
|
994
|
+
## 6e. The denial
|
|
995
|
+
|
|
996
|
+
A denial (format description, section 28) is the agent's side's signed
|
|
997
|
+
statement that it did not take an action that was asked for, and who
|
|
998
|
+
said no: the check before acting, the person, or a rule of the software
|
|
999
|
+
around the agent.
|
|
1000
|
+
|
|
1001
|
+
**Protects.** That a denial in the book was signed by the agent the slip
|
|
1002
|
+
names, or by the helper its pass names, under a slip that is earlier in
|
|
1003
|
+
the book (`slip-missing`, `pass-missing`, `pass-mismatch`,
|
|
1004
|
+
`signature-invalid`). A6 cannot make one. A7 cannot use one to change
|
|
1005
|
+
any other answer: a denial is in no chain, adds to no total, and is never
|
|
1006
|
+
counted against the agent, so a flood of denials, or a denial that names
|
|
1007
|
+
an action far outside the slip, makes no stub read as outside it. A
|
|
1008
|
+
denial changed after it was signed, or under another kind of record's
|
|
1009
|
+
labels, is refused like any record. In this library, `act` writes a
|
|
1010
|
+
denial only where it did not take the action, so one call through `act`,
|
|
1011
|
+
`recordTools` or `record_function` leaves a stub or a denial, never both
|
|
1012
|
+
(the connector for an agent's tools asks for no denial until it knows
|
|
1013
|
+
the person will not approve). `deny` is different: it records whatever
|
|
1014
|
+
its caller states, and a caller can write a denial for a request that
|
|
1015
|
+
also has a stub; the checker shows both. The writer keeps to a set
|
|
1016
|
+
number of denials an hour of its own clock (600 by default), counting
|
|
1017
|
+
each it writes at the clock's time, so that an agent that asks again and
|
|
1018
|
+
again cannot fill its book quickly: at 600 an hour a book's 100,000
|
|
1019
|
+
entries still fill in about a week. A stub is never kept out by that
|
|
1020
|
+
limit.
|
|
1021
|
+
|
|
1022
|
+
**Does not protect.**
|
|
1023
|
+
|
|
1024
|
+
- **It is the agent's side's own word.** A3 can write a denial for an
|
|
1025
|
+
action it then took some other way, or a false reason. Only the other
|
|
1026
|
+
side's records, or the service's own refusal, can show otherwise.
|
|
1027
|
+
- **An agent can simply not write one.** A book with no denial shows
|
|
1028
|
+
nothing about how often the agent was stopped. A1 or A2 can also leave
|
|
1029
|
+
denials out of a copy of the book after the last seal, as any entry.
|
|
1030
|
+
- **What was asked for is in clear.** A denial names the action, the
|
|
1031
|
+
amount and the service. Its documents are fingerprints only, and the
|
|
1032
|
+
library never writes arguments into it; but a fingerprint of a short
|
|
1033
|
+
or guessable value can be guessed.
|
|
1034
|
+
- **The source is not checked.** Nothing shows that the person, or a
|
|
1035
|
+
rule, said no: `person` and `rule` are the agent's side's statement.
|
|
1036
|
+
A denial by the check before acting names the codes the check gave,
|
|
1037
|
+
and nothing in the record shows that the check was made.
|
|
1038
|
+
- **Older checkers** refuse a book that holds a denial (`unknown-entry`).
|
|
1039
|
+
|
|
1040
|
+
## 6f. Witnesses, and a book shown two ways
|
|
1041
|
+
|
|
1042
|
+
A recorder that keeps a book (A1, A2) can show one history to one reader
|
|
1043
|
+
and another to another (a "split view"): each copy checks, and each is
|
|
1044
|
+
sealed, so a reader who holds one copy cannot see the other. A witness
|
|
1045
|
+
(format description, section 29) is a party with a key set of its own
|
|
1046
|
+
that keeps, for each recorder, person and agent, the latest seal it
|
|
1047
|
+
signed for, and signs a statement for a new seal only where the new seal
|
|
1048
|
+
extends that one: in the same book, by a consistency proof (RFC 9162,
|
|
1049
|
+
section 2.1.4), or as the first seal of the next book (section 21.6). The
|
|
1050
|
+
person and the agent are those the permission on a book's first line
|
|
1051
|
+
names: its passkey and the agent's key set. So every permission one
|
|
1052
|
+
person gives one agent under one recorder, renewals included, is one
|
|
1053
|
+
sequence: the independent review showed that a sequence known by the
|
|
1054
|
+
permission alone let a book that started with a renewal fork the
|
|
1055
|
+
history, and a witness now refuses it. The statement sits beside the seal on its line, or is
|
|
1056
|
+
handed to a checker later with a copy of the seal.
|
|
1057
|
+
|
|
1058
|
+
**Protects.** That a statement a checker counts was signed by a witness
|
|
1059
|
+
the person named, by the fingerprint of its key set, and speaks of that
|
|
1060
|
+
very seal: its `seal`, `size` and `root` must be the seal's, both its
|
|
1061
|
+
signatures must check, and it may not be dated more than 300 seconds
|
|
1062
|
+
before the seal (`witness-invalid`). A6 and A7 cannot make one, and a
|
|
1063
|
+
statement from a witness nobody named is shown and not counted. A
|
|
1064
|
+
statement that does not pass is a problem; one handed over beside the
|
|
1065
|
+
book is a problem with the whole check, and the seal it came with is
|
|
1066
|
+
used all the same. Being witnessed excuses nothing: it changes no other
|
|
1067
|
+
answer, and a book with a problem is never said to be witnessed.
|
|
1068
|
+
|
|
1069
|
+
A witness knows which books are one sequence only by the passkey and the
|
|
1070
|
+
agent of the permission on their first line and the keys of their
|
|
1071
|
+
recorder, and the recorder (A1, A2) chooses the first line and its keys: it can start a history with another first line, or
|
|
1072
|
+
seal it with new keys, and the same honest witness signs for it as
|
|
1073
|
+
another sequence. So a statement counts only where the person fixed
|
|
1074
|
+
both: the book's first line is a permission that passed its check,
|
|
1075
|
+
signed by a passkey the person named (`issuerKeys`), and the person
|
|
1076
|
+
named the recorders they expect (`sealKeys`). The independent review of
|
|
1077
|
+
this step showed a history that began with made-up terms, then the
|
|
1078
|
+
permission, signed by the same witness and read as witnessed; it now
|
|
1079
|
+
reads as not witnessed, and the witness itself no longer keeps a book
|
|
1080
|
+
whose first line is not a permission.
|
|
1081
|
+
|
|
1082
|
+
And every permission among the entries a seal covers must name the same
|
|
1083
|
+
passkey and agent as the first line's. The third round of the review
|
|
1084
|
+
showed a fork through a second agent of the same person under the same
|
|
1085
|
+
recorder, the gateway's own set-up: a book that started with agent Y's
|
|
1086
|
+
permission, then held agent X's permission and X's stubs without the
|
|
1087
|
+
one over its limit, was signed by the witness as the first book of the
|
|
1088
|
+
person and agent Y, and read as witnessed. It now reads as not
|
|
1089
|
+
witnessed, with a note saying why; so does one that starts with a
|
|
1090
|
+
second passkey's permission for X. A witness cannot refuse such a book
|
|
1091
|
+
itself: it sees a book's first line, not its entries. A Show counts a
|
|
1092
|
+
statement only where its pages hold every entry the seal covers, so it
|
|
1093
|
+
never counts one that the whole book would not.
|
|
1094
|
+
|
|
1095
|
+
What it narrows, where the witness keeps section 29.3 and the person
|
|
1096
|
+
fixed the start: a second history sealed by the same recorder and
|
|
1097
|
+
starting from any permission that passkey gave the same agent needs that
|
|
1098
|
+
witness to sign for it too, which it does not do. So two readers who name that witness, the
|
|
1099
|
+
same passkey and the same recorder cannot be shown two such histories
|
|
1100
|
+
that both carry its statements and that disagree before the earlier of
|
|
1101
|
+
the two seals it signed for. A new book cannot start from an earlier
|
|
1102
|
+
seal than the latest the witness holds, so the books of one permission
|
|
1103
|
+
cannot fork either. Two witnesses need two to fail. The latest seal a
|
|
1104
|
+
witness holds, handed over beside the book it is of as a seal held
|
|
1105
|
+
elsewhere (section 6a), shows that book cut back to before that seal
|
|
1106
|
+
(`book-cut-short`, `seal-not-in-book`). The witness program in the
|
|
1107
|
+
library (`openWitness`) checks a seal's three signatures, signs nothing
|
|
1108
|
+
on a device that cannot check SLH-DSA (so there is none in the Python
|
|
1109
|
+
version), ties the first line to the seal with an audit path at first
|
|
1110
|
+
contact and at each new book, keeps it tied with each consistency proof,
|
|
1111
|
+
never signs two different seals of one book at one size, never again
|
|
1112
|
+
signs for a book once it has signed for the next, and hands back what it
|
|
1113
|
+
holds with each statement, to be kept before the statement is given
|
|
1114
|
+
out.
|
|
1115
|
+
|
|
1116
|
+
**Does not protect.**
|
|
1117
|
+
|
|
1118
|
+
- **Other recorder keys, or another agent.** A history sealed with
|
|
1119
|
+
another key set, even one the person also named, or that starts from a
|
|
1120
|
+
permission for another agent, is another sequence to a witness, and a
|
|
1121
|
+
fork made that way is not caught. A person who names two recorders
|
|
1122
|
+
should know that a fork sealed with the second reads as witnessed. One
|
|
1123
|
+
recorder key set keeps one sequence for each person and agent, so books
|
|
1124
|
+
kept side by side for one agent need recorder keys of their own, and
|
|
1125
|
+
are then other sequences.
|
|
1126
|
+
- **What a later book holds.** A later book can repeat, or leave out,
|
|
1127
|
+
what an earlier book holds: each book is checked on its own.
|
|
1128
|
+
- **A book sealed again under another number.** A book cut short can be
|
|
1129
|
+
sealed again with another book number. A seal held elsewhere is then of
|
|
1130
|
+
another number than the book, is not compared with it, and gives no
|
|
1131
|
+
pass: the command-line checker says "Seals held elsewhere compared?
|
|
1132
|
+
NO" and does not end with code 0, and the checking page gives no green
|
|
1133
|
+
answer. The reader learns that their seal was not compared, not where
|
|
1134
|
+
the book was cut.
|
|
1135
|
+
- **What is never shown to a witness.** A recorder can keep a book, or
|
|
1136
|
+
the end of one, from every witness. Nothing in a book obliges it to be
|
|
1137
|
+
witnessed, and a checker that is not handed the witness's latest seal
|
|
1138
|
+
does not know of later seals. Where that latest seal is of a later
|
|
1139
|
+
book, a checker of an earlier book cannot use it to show that earlier
|
|
1140
|
+
book cut short: it cannot tell whether a seal of the next book is that
|
|
1141
|
+
book's first (section 21.6).
|
|
1142
|
+
- **A witness that fails.** A witness that signs for two histories, or
|
|
1143
|
+
that loses what it holds or is put back to an older copy of it, can
|
|
1144
|
+
sign a fork, and nothing in either history shows it. The library's
|
|
1145
|
+
witness, opened again from what it kept, signs nothing until it is
|
|
1146
|
+
brought up to date (`update`) and told so (`ready`); it cannot tell
|
|
1147
|
+
whether what it was given is the latest it gave out. Whoever can change
|
|
1148
|
+
what it keeps can make it forget a sequence, which it then signs for
|
|
1149
|
+
again from any seal: its state must be kept as safely as its keys.
|
|
1150
|
+
- **The entries.** A witness sees a seal's size and top fingerprint,
|
|
1151
|
+
not the entries: it shows that the entries existed in that form when
|
|
1152
|
+
it saw the seal, not that they are true.
|
|
1153
|
+
- **Time.** A statement's `when` is the witness's own word, and is not
|
|
1154
|
+
used as a time-stamp.
|
|
1155
|
+
- **Limits across books.** A checker counts a limit within the book it
|
|
1156
|
+
checks, not across the books of a permission.
|
|
1157
|
+
- **A device that cannot check both of a statement's signatures**
|
|
1158
|
+
counts nothing, and the record is not fully checked there.
|
|
1159
|
+
- **Older checkers** refuse a seal's line that holds `witnesses`
|
|
1160
|
+
(`unknown-entry`), and a seal that gives `book` (`bad-field`).
|
|
1161
|
+
|
|
896
1162
|
## 7. The checker and the checking page
|
|
897
1163
|
|
|
898
1164
|
**Protects.** A7 cannot crash the checker, make it run a script, or make
|
|
@@ -923,7 +1189,11 @@ it show a pass for a record that does not check.
|
|
|
923
1189
|
the checker itself put there.
|
|
924
1190
|
- "Within its slip" is said only if every stub and every pass was
|
|
925
1191
|
compared on this device.
|
|
926
|
-
- Records over a fixed size, or nested too deeply, are refused.
|
|
1192
|
+
- Records over a fixed size, or nested too deeply, are refused. A book
|
|
1193
|
+
may hold 100,000 lines of 131,072 bytes, about 13 gigabytes in all;
|
|
1194
|
+
this library takes a book as one text, so it can check no book larger
|
|
1195
|
+
than one text the device can hold, and a checker should bound the size
|
|
1196
|
+
it accepts (format description, section 9).
|
|
927
1197
|
- The content of a record must be written in one canonical form (RFC
|
|
928
1198
|
8785). A record with a repeated field name, stray spaces or an unusual
|
|
929
1199
|
number form is refused, so two checkers cannot read the same record in
|
|
@@ -934,11 +1204,12 @@ it show a pass for a record that does not check.
|
|
|
934
1204
|
It asks for every header from the first block onwards, so a node does
|
|
935
1205
|
not learn which blocks matter to the person.
|
|
936
1206
|
- What the person hands the checking page beside a record (their own
|
|
937
|
-
copies of cancellations, disclosures for covered fields, the blocks
|
|
938
|
-
|
|
939
|
-
A file that is not JSON, or a line that
|
|
940
|
-
|
|
941
|
-
|
|
1207
|
+
copies of cancellations, disclosures for covered fields, the blocks,
|
|
1208
|
+
the fingerprints and the signing page's address they trust) is read in
|
|
1209
|
+
the browser and sent nowhere. A file that is not JSON, or a line that
|
|
1210
|
+
is not a fingerprint or a page address in its plain form, stops the
|
|
1211
|
+
check and says so: it is never set aside silently. The sample record
|
|
1212
|
+
is checked without the files handed over.
|
|
942
1213
|
- The checker depends on no outside code. It uses only the cryptography
|
|
943
1214
|
built into the browser or Node.js.
|
|
944
1215
|
- **The checking page as one file** (`tools/make-single-page.mjs`) holds
|
|
@@ -988,7 +1259,10 @@ it show a pass for a record that does not check.
|
|
|
988
1259
|
## 8. What is refused, and the test that proves it
|
|
989
1260
|
|
|
990
1261
|
Each row is one test in `test/refusals.test.mjs`, named by its code. A
|
|
991
|
-
further test reads this table and fails if a code here has no test.
|
|
1262
|
+
further test reads this table and fails if a code here has no test. This
|
|
1263
|
+
table is also the format's own list of finding codes, from which a denial
|
|
1264
|
+
names its reasons (format description, section 28); `test/denial.test.mjs`
|
|
1265
|
+
fails if the two differ.
|
|
992
1266
|
|
|
993
1267
|
| Code | What is refused or reported |
|
|
994
1268
|
|---|---|
|
|
@@ -1001,8 +1275,8 @@ further test reads this table and fails if a code here has no test.
|
|
|
1001
1275
|
| `payload-not-canonical` | Content that is not in the canonical form, including a repeated field name |
|
|
1002
1276
|
| `payload-type-mismatch` | Content of one kind under the label of another, for example a stub presented as a countersignature |
|
|
1003
1277
|
| `bad-field` | A missing, extra or wrongly typed field |
|
|
1004
|
-
| `bad-key` | A public key that is not exactly of an accepted kind |
|
|
1005
|
-
| `signature-invalid` | A signature that does not fit the key |
|
|
1278
|
+
| `bad-key` | A public key that is not exactly of an accepted kind, an Ed25519 key of small order or written in more than one way, or an ES256 key that is not a point on its curve |
|
|
1279
|
+
| `signature-invalid` | A signature that does not fit the key, or an Ed25519 signature whose R is a point of small order or whose S is not below the order L |
|
|
1006
1280
|
| `passkey-wrong-type` | A passkey answer that was made for creating a passkey, not for signing |
|
|
1007
1281
|
| `passkey-challenge-mismatch` | A passkey signature made for other content |
|
|
1008
1282
|
| `passkey-origin-mismatch` | A passkey used on a website other than the one the slip names |
|
|
@@ -1011,6 +1285,7 @@ further test reads this table and fails if a code here has no test.
|
|
|
1011
1285
|
| `passkey-user-not-verified` | A passkey signature where the device did not confirm the person |
|
|
1012
1286
|
| `passkey-bad-data` | Passkey values that are cut short or malformed |
|
|
1013
1287
|
| `issuer-not-expected` | A slip signed by a key other than the ones the checker was told to expect |
|
|
1288
|
+
| `issuer-origin-not-expected` | A slip signed at a page address other than the ones the checker was told to expect (section 3) |
|
|
1014
1289
|
| `slip-missing` | A stub whose slip is not earlier in the book |
|
|
1015
1290
|
| `slip-unusable` | A stub whose slip failed its own check |
|
|
1016
1291
|
| `chain-broken` | A stub that is missing, moved, repeated or changed |
|
|
@@ -1041,10 +1316,13 @@ further test reads this table and fails if a code here has no test.
|
|
|
1041
1316
|
| `seal-mismatch` | A seal that does not fit the entries before it: an entry was changed, added or removed after the seal was made. In a Show: a seal for another book, or for another length of this one |
|
|
1042
1317
|
| `seal-chain-broken` | A seal that does not name the seal before it: a seal is missing, moved or changed |
|
|
1043
1318
|
| `sealer-not-expected` | A seal signed with keys other than the ones the checker was told to expect |
|
|
1319
|
+
| `book-cut-short` | A book that ends before the place of a seal held elsewhere and handed over beside it: its end was cut off after that seal was made |
|
|
1320
|
+
| `seal-not-in-book` | A book that holds another line at the place of a seal held elsewhere and handed over beside it: it was changed after that seal was made, or the seal is of another book |
|
|
1321
|
+
| `witness-invalid` | A witness statement, beside a seal in the book or handed over with a seal held elsewhere, whose signatures do not fit the keys it gives, that speaks of another seal, or that is dated more than 300 seconds before its seal |
|
|
1044
1322
|
| `dated-after-stamp` | An entry dated later than the outside time-stamp that covers it, or a seal dated later than its own time-stamp |
|
|
1045
1323
|
| `stamp-bad-data` | A time-stamp that is not laid out as its standard sets out |
|
|
1046
1324
|
| `stamp-wrong-data` | A time-stamp made for something other than this seal |
|
|
1047
|
-
| `stamp-invalid` | A time-stamp whose signature does not check, or that is dated outside the time its service's certificate was in force |
|
|
1325
|
+
| `stamp-invalid` | A time-stamp whose signature does not check, that names the certificate that signed it twice in one form, that is signed with Ed25519 and names a fingerprint method other than SHA-512, or that is dated outside the time its service's certificate was in force |
|
|
1048
1326
|
| `headers-invalid` | A chain of block headers in which a header breaks a rule that every Bitcoin node applies, that does not hold the known blocks at their places, or that ends before the last of them (tested in `test/headers.test.mjs`) |
|
|
1049
1327
|
| `record-not-sound` | The check before acting: the record so far has a problem, or could not be checked on this device, so no action is allowed |
|
|
1050
1328
|
| `proof-invalid` | A one-page proof that does not lead to the top fingerprint |
|
package/net/headers.js
CHANGED
|
@@ -232,6 +232,7 @@ export function apart(a, b) {
|
|
|
232
232
|
*/
|
|
233
233
|
export function nodeAddress(text) {
|
|
234
234
|
const line = String(text).split('#')[0].trim();
|
|
235
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- no repetition inside repetition can match the same text two ways (labels split by a full stop the labels cannot match), so matching takes time in proportion to the input.
|
|
235
236
|
const m = line.match(/^\[([0-9a-fA-F:.]+)\](?::(\d{1,5}))?$/) ?? line.match(/^([A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*)\.?(?::(\d{1,5}))?$/);
|
|
236
237
|
if (!m) return null;
|
|
237
238
|
const host = m[1].toLowerCase();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "provared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Proof of what an AI agent was allowed to do, and what it then did. A passkey-signed permission with limits a machine can check, a signed receipt for each action, and a free checker. Evidence, not a verdict.",
|
|
5
5
|
"author": "Pavel Izmaylov",
|
|
6
6
|
"license": "Apache-2.0 AND CC-BY-4.0",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"node": ">=24.7"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
|
-
"test": "node --disable-warning=ExperimentalWarning --test",
|
|
74
|
+
"test": "node --disable-warning=ExperimentalWarning --test \"test/**/*.test.mjs\"",
|
|
75
75
|
"check": "node --disable-warning=ExperimentalWarning bin/provared-check.mjs",
|
|
76
76
|
"headers": "node bin/provared-headers.mjs",
|
|
77
77
|
"demo": "node --disable-warning=ExperimentalWarning demo/server.mjs"
|
package/page/check-page.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// request at all.
|
|
7
7
|
|
|
8
8
|
import { checkBook, checkShow } from '../src/check.js';
|
|
9
|
-
import { readBlocks, readFingerprints, renderResult, shown } from './render.js';
|
|
9
|
+
import { readBlocks, readFingerprints, readHanded, readOrigins, renderResult, shown } from './render.js';
|
|
10
10
|
|
|
11
11
|
const status = document.getElementById('status');
|
|
12
12
|
const result = document.getElementById('result');
|
|
@@ -14,9 +14,6 @@ const result = document.getElementById('result');
|
|
|
14
14
|
// This page reads the whole file into memory. A larger book is checked
|
|
15
15
|
// with the command-line checker.
|
|
16
16
|
const MAX_FILE_BYTES = 200000000;
|
|
17
|
-
// A copy of a cancellation, or a file of disclosures, is small.
|
|
18
|
-
const MAX_HANDED_BYTES = 1000000;
|
|
19
|
-
const MAX_COPIES = 16;
|
|
20
17
|
|
|
21
18
|
// What the person already trusts: one fingerprint to a line. A line that
|
|
22
19
|
// is not a fingerprint stops the check, and the person is told which box.
|
|
@@ -25,6 +22,7 @@ const BOXES = [
|
|
|
25
22
|
['trust-sealer', 'sealKeys', 'whoever keeps and seals the book'],
|
|
26
23
|
['trust-stamps', 'stampServices', 'time-stamp services'],
|
|
27
24
|
['trust-vouchers', 'vouchers', 'organisations whose vouching you trust'],
|
|
25
|
+
['trust-witnesses', 'witnesses', 'witnesses you trust'],
|
|
28
26
|
];
|
|
29
27
|
|
|
30
28
|
function trusted() {
|
|
@@ -39,37 +37,24 @@ function trusted() {
|
|
|
39
37
|
throw new Error('A line in the box for blocks is not the fingerprint of a block. It is 64 characters: the digits 0 to 9 and the letters a to f. Correct it, or empty the box.');
|
|
40
38
|
}
|
|
41
39
|
if (blocks.values.length) options.blocks = blocks.values;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
return JSON.parse(await file.text());
|
|
49
|
-
} catch {
|
|
50
|
-
throw new Error(`The file of ${what}, ${shown(file.name)}, is not JSON. Nothing was checked. Choose another file, or clear these files.`);
|
|
40
|
+
// The addresses at which the person expects the permissions to have been
|
|
41
|
+
// signed: each in the one form the format allows, or the check stops.
|
|
42
|
+
const origins = readOrigins(document.getElementById('trust-origins').value);
|
|
43
|
+
if (origins.bad.length) {
|
|
44
|
+
throw new Error('A line in the box for the address of the signing page is not a page address in its plain form: https://, a website name in lower case, a port only if it is needed, and nothing more, such as https://sign.example.org. Correct it, or empty the box.');
|
|
51
45
|
}
|
|
46
|
+
if (origins.values.length) options.issuerOrigins = origins.values;
|
|
47
|
+
return options;
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
// What the person was handed with the record:
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (copies.length) {
|
|
63
|
-
options.cancellations = [];
|
|
64
|
-
for (const file of copies) options.cancellations.push(await jsonFrom(file, 'a cancellation'));
|
|
65
|
-
said.push(copies.length === 1 ? 'one copy of a cancellation' : `${copies.length} copies of cancellations`);
|
|
66
|
-
}
|
|
67
|
-
const [disclosures] = document.getElementById('disclosures').files;
|
|
68
|
-
if (disclosures) {
|
|
69
|
-
options.disclosures = await jsonFrom(disclosures, 'disclosures');
|
|
70
|
-
said.push('disclosures for covered fields');
|
|
71
|
-
}
|
|
72
|
-
return { options, said };
|
|
50
|
+
// What the person was handed with the record: the files chosen in the
|
|
51
|
+
// panel "What you were handed with the record" (render.js, readHanded).
|
|
52
|
+
function handedOver() {
|
|
53
|
+
return readHanded({
|
|
54
|
+
cancellations: [...document.getElementById('cancellations').files],
|
|
55
|
+
seals: [...document.getElementById('seals').files],
|
|
56
|
+
disclosures: document.getElementById('disclosures').files[0] ?? null,
|
|
57
|
+
});
|
|
73
58
|
}
|
|
74
59
|
|
|
75
60
|
async function check(text, name, { beside = true, trust = null } = {}) {
|
|
@@ -131,6 +116,7 @@ if (chooser.files.length > 0) checkChosen(chooser);
|
|
|
131
116
|
|
|
132
117
|
document.getElementById('clear-beside').addEventListener('click', () => {
|
|
133
118
|
document.getElementById('cancellations').value = '';
|
|
119
|
+
document.getElementById('seals').value = '';
|
|
134
120
|
document.getElementById('disclosures').value = '';
|
|
135
121
|
status.textContent = 'The files you were handed with a record are cleared.';
|
|
136
122
|
});
|
package/page/index.html
CHANGED
|
@@ -20,12 +20,16 @@
|
|
|
20
20
|
<p class="quiet">A check says more when you tell it which keys and services you expect. Put one fingerprint on each line. Use fingerprints you obtained yourself, from the people concerned.</p>
|
|
21
21
|
<label for="trust-issuer">The person's passkey (its thumbprint)</label>
|
|
22
22
|
<textarea id="trust-issuer" rows="2" spellcheck="false"></textarea>
|
|
23
|
+
<label for="trust-origins">The address of the page at which the person signs permissions (such as https://sign.example.org; a permission signed at any other address is refused)</label>
|
|
24
|
+
<textarea id="trust-origins" rows="2" spellcheck="false"></textarea>
|
|
23
25
|
<label for="trust-sealer">Whoever keeps and seals the book (the fingerprint of its key set)</label>
|
|
24
26
|
<textarea id="trust-sealer" rows="2" spellcheck="false"></textarea>
|
|
25
27
|
<label for="trust-stamps">Time-stamp services (the fingerprint of each service's certificate)</label>
|
|
26
28
|
<textarea id="trust-stamps" rows="2" spellcheck="false"></textarea>
|
|
27
29
|
<label for="trust-vouchers">Organisations whose vouching for names you trust (the fingerprint of each one's key set)</label>
|
|
28
30
|
<textarea id="trust-vouchers" rows="2" spellcheck="false"></textarea>
|
|
31
|
+
<label for="trust-witnesses">Witnesses you trust to have seen the seals (the fingerprint of each one's key set)</label>
|
|
32
|
+
<textarea id="trust-witnesses" rows="2" spellcheck="false"></textarea>
|
|
29
33
|
<label for="trust-blocks">Blocks of a public blockchain that you trust to be part of the chain (the fingerprint of each block: 64 characters, the digits 0 to 9 and the letters a to f)</label>
|
|
30
34
|
<textarea id="trust-blocks" rows="2" spellcheck="false"></textarea>
|
|
31
35
|
</details>
|
|
@@ -34,6 +38,8 @@
|
|
|
34
38
|
<p class="quiet">Choose these files first, then choose the record file. They stay chosen, and are used with every record you check, until you clear them. The sample record is checked without them.</p>
|
|
35
39
|
<label for="cancellations">The person's own copies of cancellations: one file for each, up to 16. A copy counts even where the book leaves the cancellation out. A file may also hold what the agent's side signed when it was handed the cancellation.</label>
|
|
36
40
|
<input type="file" id="cancellations" accept=".json,.txt" multiple>
|
|
41
|
+
<label for="seals">Seals of the book held elsewhere, by you, an auditor or a copy kept apart: one file for each, up to 64. The book must hold each at the place it states; one that ends before it was cut short. A seal counts only where you named its recorder above. A file may also hold statements that witnesses signed for the seal.</label>
|
|
42
|
+
<input type="file" id="seals" accept=".json,.txt" multiple>
|
|
37
43
|
<label for="disclosures">Disclosures for covered fields of a slip: one file</label>
|
|
38
44
|
<input type="file" id="disclosures" accept=".json,.txt">
|
|
39
45
|
<p><button type="button" class="second" id="clear-beside">Clear these files</button></p>
|