burnledger 0.6.1 → 0.8.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/dist/cjs/verify.d.ts.map +1 -1
- package/dist/cjs/verify.js +178 -24
- package/dist/cjs/verify.js.map +1 -1
- package/dist/cjs/web-verifier.d.ts +13 -0
- package/dist/cjs/web-verifier.d.ts.map +1 -1
- package/dist/cjs/web-verifier.js +2 -1
- package/dist/cjs/web-verifier.js.map +1 -1
- package/dist/esm/cli.d.ts.map +1 -1
- package/dist/esm/cli.js +50 -2
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/verify.d.ts.map +1 -1
- package/dist/esm/verify.js +178 -24
- package/dist/esm/verify.js.map +1 -1
- package/dist/esm/web-verifier.d.ts +13 -0
- package/dist/esm/web-verifier.d.ts.map +1 -1
- package/dist/esm/web-verifier.js +13 -1
- package/dist/esm/web-verifier.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.ts +59 -1
- package/src/verify.ts +215 -21
- package/src/web-verifier.ts +14 -0
package/src/verify.ts
CHANGED
|
@@ -61,6 +61,31 @@ const FORMAT_VERSION_V6 = "6.0";
|
|
|
61
61
|
const PAYLOAD_TYPE_ATTESTATION_V7 = "burnledger.attestation.v7";
|
|
62
62
|
const PAYLOAD_TYPE_VERIFICATION_RECORD_V7 = "burnledger.verification_record.v7";
|
|
63
63
|
const FORMAT_VERSION_V7 = "7.0";
|
|
64
|
+
// v8 adds one per-system field, recoverable_state: whether anything checked for
|
|
65
|
+
// a restorable copy of what the record certifies gone. Its tags move for the
|
|
66
|
+
// reason every earlier version's did, and this time the bytes under them differ
|
|
67
|
+
// by that field. Everything v7 signs, v8 still signs.
|
|
68
|
+
//
|
|
69
|
+
// READ-ONLY IN THIS BUILD, mirroring core: nothing here issues a v8 record. A
|
|
70
|
+
// verifier that can read a format has to ship BEFORE anything signs one, or a
|
|
71
|
+
// genuine record reaches a holder whose SDK rebuilds it under the wrong domain
|
|
72
|
+
// and reports a forgery (runbook invariant 5).
|
|
73
|
+
const PAYLOAD_TYPE_ATTESTATION_V8 = "burnledger.attestation.v8";
|
|
74
|
+
const PAYLOAD_TYPE_VERIFICATION_RECORD_V8 = "burnledger.verification_record.v8";
|
|
75
|
+
const FORMAT_VERSION_V8 = "8.0";
|
|
76
|
+
// v9 adds two per-system fields, authorization and customer_key_id: whether the
|
|
77
|
+
// enclave checked this system against a customer-signed registration
|
|
78
|
+
// certificate (ADR-025 §4), and under which customer key group if it did.
|
|
79
|
+
//
|
|
80
|
+
// The attestation gains NO field at v9. Its tag still moves, because the tag
|
|
81
|
+
// follows the record's version and the record's bytes changed — a v9
|
|
82
|
+
// attestation rebuilt under burnledger.attestation.v8 is a different preimage.
|
|
83
|
+
//
|
|
84
|
+
// READ-ONLY IN THIS BUILD, mirroring core, for the same runbook-invariant-5
|
|
85
|
+
// reason spelled out for v8 above.
|
|
86
|
+
const PAYLOAD_TYPE_ATTESTATION_V9 = "burnledger.attestation.v9";
|
|
87
|
+
const PAYLOAD_TYPE_VERIFICATION_RECORD_V9 = "burnledger.verification_record.v9";
|
|
88
|
+
const FORMAT_VERSION_V9 = "9.0";
|
|
64
89
|
const FORMAT_VERSION_V3 = "3.0";
|
|
65
90
|
|
|
66
91
|
/**
|
|
@@ -86,6 +111,8 @@ export const KNOWN_FORMAT_VERSIONS: readonly string[] = Object.freeze([
|
|
|
86
111
|
FORMAT_VERSION_V5,
|
|
87
112
|
FORMAT_VERSION_V6,
|
|
88
113
|
FORMAT_VERSION_V7,
|
|
114
|
+
FORMAT_VERSION_V8,
|
|
115
|
+
FORMAT_VERSION_V9,
|
|
89
116
|
]);
|
|
90
117
|
|
|
91
118
|
/**
|
|
@@ -106,6 +133,110 @@ function checkFormatVersion(version: unknown): string {
|
|
|
106
133
|
}
|
|
107
134
|
return version;
|
|
108
135
|
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Is `version` the same format as `floor`, or a later one?
|
|
139
|
+
*
|
|
140
|
+
* Ordered by position in KNOWN_FORMAT_VERSIONS, never by string comparison:
|
|
141
|
+
* "10.0" sorts before "9.0". Port of core.formatAtLeast, and it exists for the
|
|
142
|
+
* reason the Go one does — four gates there were written as `v === "7.0"`, read
|
|
143
|
+
* by everyone as "7.0 and later", and behaved as "7.0 only". Three were live
|
|
144
|
+
* defects and one silently downgraded a security property the day v8 was
|
|
145
|
+
* defined.
|
|
146
|
+
*
|
|
147
|
+
* USE THIS, not equality, whenever the question is "does this format sign X".
|
|
148
|
+
* An unknown version answers false for every floor, which is safe only because
|
|
149
|
+
* checkFormatVersion has already refused it before any payload is rebuilt.
|
|
150
|
+
*/
|
|
151
|
+
function formatAtLeast(version: string, floor: string): boolean {
|
|
152
|
+
const vi = KNOWN_FORMAT_VERSIONS.indexOf(version);
|
|
153
|
+
const fi = KNOWN_FORMAT_VERSIONS.indexOf(floor);
|
|
154
|
+
return vi >= 0 && fi >= 0 && vi >= fi;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Ports of the core.SignatureCovers* predicates, one per signed field group. */
|
|
158
|
+
function signatureCoversEnclavePcr0(version: string): boolean {
|
|
159
|
+
return formatAtLeast(version, FORMAT_VERSION_V5);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function signatureCoversAlgorithm(version: string): boolean {
|
|
163
|
+
return formatAtLeast(version, FORMAT_VERSION_V7);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function signatureCoversMeasuredTransport(version: string): boolean {
|
|
167
|
+
return formatAtLeast(version, FORMAT_VERSION_V7);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Does this format sign the per-system recoverable_state?
|
|
172
|
+
*
|
|
173
|
+
* v8 only, and everything after it. Emitting the field for v7 or older would
|
|
174
|
+
* rebuild bytes no signer ever produced and reject every record already issued;
|
|
175
|
+
* omitting it for v8 rebuilds a v7-shaped subset and reports a genuine v8
|
|
176
|
+
* record as forged.
|
|
177
|
+
*/
|
|
178
|
+
function signatureCoversRecoverableState(version: string): boolean {
|
|
179
|
+
return formatAtLeast(version, FORMAT_VERSION_V8);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Does this format sign the per-system authorization and customer_key_id?
|
|
184
|
+
*
|
|
185
|
+
* v9 onward. `authorization` is always present on a v9 record; `customer_key_id`
|
|
186
|
+
* only when it is certified, and OMITTED rather than empty when it is not — a
|
|
187
|
+
* key id of "" would sign as a value, and a reader comparing against their own
|
|
188
|
+
* key id must not have to know that "" means "nobody".
|
|
189
|
+
*/
|
|
190
|
+
function signatureCoversAuthorization(version: string): boolean {
|
|
191
|
+
return formatAtLeast(version, FORMAT_VERSION_V9);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** The two values a v9 record's per-system authorization may take. */
|
|
195
|
+
const AUTHORIZATION_CERTIFIED = "certified";
|
|
196
|
+
const AUTHORIZATION_NONE = "none";
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Read a v9 system's authorization and key id, refusing what core refuses.
|
|
200
|
+
*
|
|
201
|
+
* Port of checkAuthorization in core/payload.go. It runs on the VERIFY side
|
|
202
|
+
* too, and must: an unset or unrecognized value is not something to rebuild
|
|
203
|
+
* bytes from and report as a bad signature — the record is malformed. The two
|
|
204
|
+
* refused combinations are the ones that would be a lie rather than a shape
|
|
205
|
+
* error: certified naming nobody is ADR-025 §2's second-key attack with the
|
|
206
|
+
* evidence removed, and none naming somebody claims an authority that was
|
|
207
|
+
* never checked.
|
|
208
|
+
*/
|
|
209
|
+
function requireAuthorization(
|
|
210
|
+
s: Record<string, unknown>,
|
|
211
|
+
systemName: unknown,
|
|
212
|
+
): { authorization: string; customerKeyId: string } {
|
|
213
|
+
const a = s.authorization;
|
|
214
|
+
if (a !== AUTHORIZATION_CERTIFIED && a !== AUTHORIZATION_NONE) {
|
|
215
|
+
throw new VerificationError(
|
|
216
|
+
`system ${JSON.stringify(systemName)}: authorization is ${JSON.stringify(a ?? null)}, ` +
|
|
217
|
+
`which format ${FORMAT_VERSION_V9} requires to be one of certified, none`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
const raw = s.customer_key_id;
|
|
221
|
+
if (raw !== undefined && raw !== null && typeof raw !== "string") {
|
|
222
|
+
throw new VerificationError(
|
|
223
|
+
`system ${JSON.stringify(systemName)}: customer_key_id is not a string`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
const customerKeyId = typeof raw === "string" ? raw : "";
|
|
227
|
+
if (a === AUTHORIZATION_CERTIFIED && customerKeyId === "") {
|
|
228
|
+
throw new VerificationError(
|
|
229
|
+
`system ${JSON.stringify(systemName)}: authorization is certified but customer_key_id is empty`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
if (a === AUTHORIZATION_NONE && customerKeyId !== "") {
|
|
233
|
+
throw new VerificationError(
|
|
234
|
+
`system ${JSON.stringify(systemName)}: authorization is none but customer_key_id is set`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return { authorization: a, customerKeyId };
|
|
238
|
+
}
|
|
239
|
+
|
|
109
240
|
const PAYLOAD_TYPE_TREE_HEAD = "burnledger.sth.v3";
|
|
110
241
|
// The domain for a tree head that names its log. See buildTreeHeadPayload.
|
|
111
242
|
const PAYLOAD_TYPE_TREE_HEAD_V7 = "burnledger.sth.v7";
|
|
@@ -446,8 +577,9 @@ export async function verifyCertificate(
|
|
|
446
577
|
// Step 0b: can this SDK check the algorithm the record names? Asked before
|
|
447
578
|
// any signature, because verifying an Ed25519 signature over a record that
|
|
448
579
|
// says it was signed with something else answers a question nobody asked.
|
|
449
|
-
// Below v7 no record names one, so there is nothing to check
|
|
450
|
-
|
|
580
|
+
// Below v7 no record names one, so there is nothing to check; from v7 on
|
|
581
|
+
// every record does, which is why this asks "v7 or later" rather than "v7".
|
|
582
|
+
if (signatureCoversAlgorithm(formatVersion) && issuer.algorithm !== ALGORITHM_ED25519) {
|
|
451
583
|
throw new VerificationError(
|
|
452
584
|
`record names signature algorithm ${JSON.stringify(issuer.algorithm)}; ` +
|
|
453
585
|
`this version of the SDK verifies ${ALGORITHM_ED25519}. ` +
|
|
@@ -906,10 +1038,16 @@ function buildAttestationPayload(
|
|
|
906
1038
|
certFormatVersion: string,
|
|
907
1039
|
): Uint8Array {
|
|
908
1040
|
const attestedAt = formatTimestamp(att.attested_at);
|
|
909
|
-
// v7 signs two per-system measurements v6 leaves on a mutable row
|
|
910
|
-
// the record's OWN version:
|
|
911
|
-
//
|
|
912
|
-
|
|
1041
|
+
// v7 signs two per-system measurements v6 leaves on a mutable row, and v8
|
|
1042
|
+
// adds recoverable_state beside them. Both gated on the record's OWN version:
|
|
1043
|
+
// emitting a field for an older format would rebuild bytes no signer ever
|
|
1044
|
+
// produced and reject every record already issued.
|
|
1045
|
+
//
|
|
1046
|
+
// Predicates, not equalities. v8 signs everything v7 signs, so a `=== "7.0"`
|
|
1047
|
+
// here would drop read_only_enforcement and transport_security out of a v8
|
|
1048
|
+
// payload and call every genuine v8 record a forgery.
|
|
1049
|
+
const coversMeasured = signatureCoversMeasuredTransport(certFormatVersion);
|
|
1050
|
+
const coversRecoverable = signatureCoversRecoverableState(certFormatVersion);
|
|
913
1051
|
const systems = (att.systems as Record<string, unknown>[]).map((s) => {
|
|
914
1052
|
const sys: Record<string, unknown> = {
|
|
915
1053
|
canonical_version: (s.canonical_version as string | null) ?? null,
|
|
@@ -925,16 +1063,26 @@ function buildAttestationPayload(
|
|
|
925
1063
|
system_id: s.system_id as string,
|
|
926
1064
|
system_name: s.system_name as string,
|
|
927
1065
|
};
|
|
928
|
-
if (
|
|
1066
|
+
if (coversMeasured) {
|
|
929
1067
|
sys.read_only_enforcement = requireMeasured(
|
|
930
1068
|
s.read_only_enforcement,
|
|
931
1069
|
"read_only_enforcement",
|
|
932
1070
|
s.system_name,
|
|
1071
|
+
certFormatVersion,
|
|
933
1072
|
);
|
|
934
1073
|
sys.transport_security = requireMeasured(
|
|
935
1074
|
s.transport_security,
|
|
936
1075
|
"transport_security",
|
|
937
1076
|
s.system_name,
|
|
1077
|
+
certFormatVersion,
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
if (coversRecoverable) {
|
|
1081
|
+
sys.recoverable_state = requireMeasured(
|
|
1082
|
+
s.recoverable_state,
|
|
1083
|
+
"recoverable_state",
|
|
1084
|
+
s.system_name,
|
|
1085
|
+
certFormatVersion,
|
|
938
1086
|
);
|
|
939
1087
|
}
|
|
940
1088
|
return sys;
|
|
@@ -951,18 +1099,27 @@ function buildAttestationPayload(
|
|
|
951
1099
|
}
|
|
952
1100
|
|
|
953
1101
|
/**
|
|
954
|
-
* Read a
|
|
1102
|
+
* Read a signed measured field that MUST be a non-empty string.
|
|
955
1103
|
*
|
|
956
1104
|
* A missing or non-string value cannot be turned into `""` and canonicalized:
|
|
957
1105
|
* the signer never emits an empty measurement, so an empty one here would
|
|
958
1106
|
* rebuild bytes no signature covers and be reported as forgery. Refusing with a
|
|
959
1107
|
* document-shape message says the true thing — this record is malformed, not
|
|
960
1108
|
* this record is fake.
|
|
1109
|
+
*
|
|
1110
|
+
* The version is passed in rather than written into the message: it was the
|
|
1111
|
+
* literal "7.0" here, which would have told the holder of an incomplete v8
|
|
1112
|
+
* record that their v8 document was a v7 one.
|
|
961
1113
|
*/
|
|
962
|
-
function requireMeasured(
|
|
1114
|
+
function requireMeasured(
|
|
1115
|
+
value: unknown,
|
|
1116
|
+
field: string,
|
|
1117
|
+
systemName: unknown,
|
|
1118
|
+
version: string,
|
|
1119
|
+
): string {
|
|
963
1120
|
if (typeof value !== "string" || value === "") {
|
|
964
1121
|
throw new VerificationError(
|
|
965
|
-
`system ${JSON.stringify(systemName)}: ${field} is missing from a
|
|
1122
|
+
`system ${JSON.stringify(systemName)}: ${field} is missing from a ${version} record, ` +
|
|
966
1123
|
"which signs it; the document is incomplete rather than unverifiable",
|
|
967
1124
|
);
|
|
968
1125
|
}
|
|
@@ -972,6 +1129,8 @@ function requireMeasured(value: unknown, field: string, systemName: unknown): st
|
|
|
972
1129
|
/** Domain separator for the record itself, by certificate format version. */
|
|
973
1130
|
function certificatePayloadType(version: string): string {
|
|
974
1131
|
checkFormatVersion(version);
|
|
1132
|
+
if (version === FORMAT_VERSION_V9) return PAYLOAD_TYPE_VERIFICATION_RECORD_V9;
|
|
1133
|
+
if (version === FORMAT_VERSION_V8) return PAYLOAD_TYPE_VERIFICATION_RECORD_V8;
|
|
975
1134
|
if (version === FORMAT_VERSION_V7) return PAYLOAD_TYPE_VERIFICATION_RECORD_V7;
|
|
976
1135
|
if (version === FORMAT_VERSION_V6) return PAYLOAD_TYPE_VERIFICATION_RECORD_V6;
|
|
977
1136
|
if (version === FORMAT_VERSION_V5) return PAYLOAD_TYPE_CERTIFICATE_V5;
|
|
@@ -987,6 +1146,11 @@ function certificatePayloadType(version: string): string {
|
|
|
987
1146
|
*/
|
|
988
1147
|
function attestationPayloadType(version: string): string {
|
|
989
1148
|
checkFormatVersion(version);
|
|
1149
|
+
// Equality here, deliberately: each format has its OWN separator, so this is
|
|
1150
|
+
// a lookup rather than a "this version onward" question. v3 and v4 share one
|
|
1151
|
+
// because their attestation bytes are identical.
|
|
1152
|
+
if (version === FORMAT_VERSION_V9) return PAYLOAD_TYPE_ATTESTATION_V9;
|
|
1153
|
+
if (version === FORMAT_VERSION_V8) return PAYLOAD_TYPE_ATTESTATION_V8;
|
|
990
1154
|
if (version === FORMAT_VERSION_V7) return PAYLOAD_TYPE_ATTESTATION_V7;
|
|
991
1155
|
if (version === FORMAT_VERSION_V6) return PAYLOAD_TYPE_ATTESTATION_V6;
|
|
992
1156
|
if (version === FORMAT_VERSION_V5) return PAYLOAD_TYPE_ATTESTATION_V5;
|
|
@@ -1002,7 +1166,9 @@ function buildCertificatePayload(cert: Record<string, unknown>): Uint8Array {
|
|
|
1002
1166
|
// and a verification list joined only on the human-editable system_name,
|
|
1003
1167
|
// which made a partial deletion indistinguishable from a complete one.
|
|
1004
1168
|
const version = cert.certificate_format_version as string;
|
|
1005
|
-
const
|
|
1169
|
+
const coversMeasured = signatureCoversMeasuredTransport(version);
|
|
1170
|
+
const coversRecoverable = signatureCoversRecoverableState(version);
|
|
1171
|
+
const coversAuthorization = signatureCoversAuthorization(version);
|
|
1006
1172
|
const systems = ((cert.systems as Record<string, unknown>[]) ?? []).map((s) => {
|
|
1007
1173
|
const sys: Record<string, unknown> = {
|
|
1008
1174
|
attested_at: formatTimestamp(s.attested_at),
|
|
@@ -1017,18 +1183,33 @@ function buildCertificatePayload(cert: Record<string, unknown>): Uint8Array {
|
|
|
1017
1183
|
verified_at: formatTimestamp(s.verified_at),
|
|
1018
1184
|
verified_count: s.verified_count as number,
|
|
1019
1185
|
};
|
|
1020
|
-
if (
|
|
1186
|
+
if (coversMeasured) {
|
|
1021
1187
|
sys.read_only_enforcement = requireMeasured(
|
|
1022
1188
|
s.read_only_enforcement,
|
|
1023
1189
|
"read_only_enforcement",
|
|
1024
1190
|
s.system_name,
|
|
1191
|
+
version,
|
|
1025
1192
|
);
|
|
1026
1193
|
sys.transport_security = requireMeasured(
|
|
1027
1194
|
s.transport_security,
|
|
1028
1195
|
"transport_security",
|
|
1029
1196
|
s.system_name,
|
|
1197
|
+
version,
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
if (coversRecoverable) {
|
|
1201
|
+
sys.recoverable_state = requireMeasured(
|
|
1202
|
+
s.recoverable_state,
|
|
1203
|
+
"recoverable_state",
|
|
1204
|
+
s.system_name,
|
|
1205
|
+
version,
|
|
1030
1206
|
);
|
|
1031
1207
|
}
|
|
1208
|
+
if (coversAuthorization) {
|
|
1209
|
+
const { authorization, customerKeyId } = requireAuthorization(s, s.system_name);
|
|
1210
|
+
sys.authorization = authorization;
|
|
1211
|
+
if (customerKeyId !== "") sys.customer_key_id = customerKeyId;
|
|
1212
|
+
}
|
|
1032
1213
|
return sys;
|
|
1033
1214
|
});
|
|
1034
1215
|
|
|
@@ -1043,12 +1224,12 @@ function buildCertificatePayload(cert: Record<string, unknown>): Uint8Array {
|
|
|
1043
1224
|
// Fields added after v3 are gated on the certificate's OWN version. A v3
|
|
1044
1225
|
// certificate must reconstruct to the same bytes forever; reading these
|
|
1045
1226
|
// unconditionally would break every certificate already issued.
|
|
1046
|
-
// v6 carries v5's shape exactly, so it takes every gate v5 takes.
|
|
1047
|
-
//
|
|
1048
|
-
//
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
const isV4Plus = version
|
|
1227
|
+
// v6 carries v5's shape exactly, so it takes every gate v5 takes. Asking
|
|
1228
|
+
// "this version onward" rather than listing versions at each use is what
|
|
1229
|
+
// stops a new format from silently missing one: these were a chain of
|
|
1230
|
+
// equalities that had to be extended by hand for v6, then v7, then v8.
|
|
1231
|
+
const isV5Plus = formatAtLeast(version, FORMAT_VERSION_V5);
|
|
1232
|
+
const isV4Plus = formatAtLeast(version, FORMAT_VERSION_V4);
|
|
1052
1233
|
|
|
1053
1234
|
const issuerObj: Record<string, unknown> = {
|
|
1054
1235
|
key_id: issuer.key_id as string,
|
|
@@ -1058,15 +1239,24 @@ function buildCertificatePayload(cert: Record<string, unknown>): Uint8Array {
|
|
|
1058
1239
|
// v7 signs the algorithm, and does so unconditionally: unlike legal_entity
|
|
1059
1240
|
// and enclave_pcr0, "which scheme signed this" is never unknown to a signer,
|
|
1060
1241
|
// so a v7 record missing it is malformed rather than merely sparse.
|
|
1061
|
-
if (
|
|
1062
|
-
issuerObj.algorithm = requireMeasured(
|
|
1242
|
+
if (signatureCoversAlgorithm(version)) {
|
|
1243
|
+
issuerObj.algorithm = requireMeasured(
|
|
1244
|
+
issuer.algorithm,
|
|
1245
|
+
"issuer.algorithm",
|
|
1246
|
+
"issuer",
|
|
1247
|
+
version,
|
|
1248
|
+
);
|
|
1063
1249
|
}
|
|
1064
1250
|
if (isV4Plus && typeof issuer.legal_entity === "string" && issuer.legal_entity !== "") {
|
|
1065
1251
|
issuerObj.legal_entity = issuer.legal_entity;
|
|
1066
1252
|
}
|
|
1067
1253
|
// Signed from v5, and omitted when absent or empty: a build with no
|
|
1068
1254
|
// measurement signs none, and "" is a value a reader could mistake for one.
|
|
1069
|
-
if (
|
|
1255
|
+
if (
|
|
1256
|
+
signatureCoversEnclavePcr0(version) &&
|
|
1257
|
+
typeof issuer.enclave_pcr0 === "string" &&
|
|
1258
|
+
issuer.enclave_pcr0 !== ""
|
|
1259
|
+
) {
|
|
1070
1260
|
issuerObj.enclave_pcr0 = issuer.enclave_pcr0;
|
|
1071
1261
|
}
|
|
1072
1262
|
|
|
@@ -1144,6 +1334,10 @@ function attestationSystems(cert: Record<string, unknown>): Record<string, unkno
|
|
|
1144
1334
|
// reads them.
|
|
1145
1335
|
read_only_enforcement: s.read_only_enforcement,
|
|
1146
1336
|
transport_security: s.transport_security,
|
|
1337
|
+
// Same reasoning for v8. It is measured at attest time and carried
|
|
1338
|
+
// unchanged onto the outcome, so projecting it back is a copy; dropping it
|
|
1339
|
+
// here would make every genuine v8 record fail its attestation signature.
|
|
1340
|
+
recoverable_state: s.recoverable_state,
|
|
1147
1341
|
}));
|
|
1148
1342
|
}
|
|
1149
1343
|
|
package/src/web-verifier.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
verifyCertificateWithStatus as _verifyCertificateWithStatus,
|
|
30
30
|
verifyTransparency as _verifyTransparency,
|
|
31
31
|
publicKeyFromHex as _publicKeyFromHex,
|
|
32
|
+
KNOWN_FORMAT_VERSIONS,
|
|
32
33
|
} from "./verify.js";
|
|
33
34
|
import type { PublicKeyInfo, PublicKeyOptions } from "./verify.js";
|
|
34
35
|
import { parseKeyListDocument, verifyKeyList as _verifyKeyList } from "./keys.js";
|
|
@@ -87,6 +88,19 @@ export function checkStatementAnchor(
|
|
|
87
88
|
|
|
88
89
|
export { ANCHOR_CONFIRMED, ANCHOR_INCONSISTENT, ANCHOR_UNVERIFIED };
|
|
89
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Every record format this bundle can read, oldest first.
|
|
93
|
+
*
|
|
94
|
+
* The page needs it to answer "does this record's format predate a field?" —
|
|
95
|
+
* the registration row asks exactly that. Exported rather than restated in
|
|
96
|
+
* verify.js, because a hand-kept copy of this list going stale is the defect
|
|
97
|
+
* scripts/check-pcr0-signed-parity.py exists because of.
|
|
98
|
+
*
|
|
99
|
+
* ORDER IS LOAD-BEARING: "this format or newer" is answered by POSITION, never
|
|
100
|
+
* by comparing the strings, because "10.0" sorts before "7.0".
|
|
101
|
+
*/
|
|
102
|
+
export { KNOWN_FORMAT_VERSIONS };
|
|
103
|
+
|
|
90
104
|
/** Verify the transparency proof embedded in a certificate. */
|
|
91
105
|
export function verifyTransparency(
|
|
92
106
|
certificate: Cert,
|