@openauditmodel/cli 0.1.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/LICENSE +215 -0
- package/README.md +647 -0
- package/dist/conformance/src/cli.d.ts +20 -0
- package/dist/conformance/src/cli.js +560 -0
- package/dist/conformance/src/cli.js.map +1 -0
- package/dist/conformance/src/format-errors.d.ts +20 -0
- package/dist/conformance/src/format-errors.js +116 -0
- package/dist/conformance/src/format-errors.js.map +1 -0
- package/dist/conformance/src/integrity/canonicalize.d.ts +25 -0
- package/dist/conformance/src/integrity/canonicalize.js +100 -0
- package/dist/conformance/src/integrity/canonicalize.js.map +1 -0
- package/dist/conformance/src/integrity/digest.d.ts +54 -0
- package/dist/conformance/src/integrity/digest.js +126 -0
- package/dist/conformance/src/integrity/digest.js.map +1 -0
- package/dist/conformance/src/integrity/types.d.ts +86 -0
- package/dist/conformance/src/integrity/types.js +36 -0
- package/dist/conformance/src/integrity/types.js.map +1 -0
- package/dist/conformance/src/integrity/verify-chain.d.ts +23 -0
- package/dist/conformance/src/integrity/verify-chain.js +235 -0
- package/dist/conformance/src/integrity/verify-chain.js.map +1 -0
- package/dist/conformance/src/integrity/verify-event.d.ts +29 -0
- package/dist/conformance/src/integrity/verify-event.js +140 -0
- package/dist/conformance/src/integrity/verify-event.js.map +1 -0
- package/dist/conformance/src/privacy/entropy.d.ts +14 -0
- package/dist/conformance/src/privacy/entropy.js +86 -0
- package/dist/conformance/src/privacy/entropy.js.map +1 -0
- package/dist/conformance/src/privacy/field-names.d.ts +36 -0
- package/dist/conformance/src/privacy/field-names.js +121 -0
- package/dist/conformance/src/privacy/field-names.js.map +1 -0
- package/dist/conformance/src/privacy/lint-event.d.ts +18 -0
- package/dist/conformance/src/privacy/lint-event.js +272 -0
- package/dist/conformance/src/privacy/lint-event.js.map +1 -0
- package/dist/conformance/src/privacy/rules.d.ts +44 -0
- package/dist/conformance/src/privacy/rules.js +161 -0
- package/dist/conformance/src/privacy/rules.js.map +1 -0
- package/dist/conformance/src/privacy/safe-formats.d.ts +32 -0
- package/dist/conformance/src/privacy/safe-formats.js +118 -0
- package/dist/conformance/src/privacy/safe-formats.js.map +1 -0
- package/dist/conformance/src/privacy/size-analysis.d.ts +36 -0
- package/dist/conformance/src/privacy/size-analysis.js +87 -0
- package/dist/conformance/src/privacy/size-analysis.js.map +1 -0
- package/dist/conformance/src/privacy/token-patterns.d.ts +24 -0
- package/dist/conformance/src/privacy/token-patterns.js +126 -0
- package/dist/conformance/src/privacy/token-patterns.js.map +1 -0
- package/dist/conformance/src/privacy/traverse.d.ts +33 -0
- package/dist/conformance/src/privacy/traverse.js +61 -0
- package/dist/conformance/src/privacy/traverse.js.map +1 -0
- package/dist/conformance/src/privacy/types.d.ts +58 -0
- package/dist/conformance/src/privacy/types.js +32 -0
- package/dist/conformance/src/privacy/types.js.map +1 -0
- package/dist/conformance/src/privacy/url-analysis.d.ts +40 -0
- package/dist/conformance/src/privacy/url-analysis.js +148 -0
- package/dist/conformance/src/privacy/url-analysis.js.map +1 -0
- package/dist/conformance/src/profiles/check-profile.d.ts +22 -0
- package/dist/conformance/src/profiles/check-profile.js +72 -0
- package/dist/conformance/src/profiles/check-profile.js.map +1 -0
- package/dist/conformance/src/profiles/evaluate-rule.d.ts +27 -0
- package/dist/conformance/src/profiles/evaluate-rule.js +87 -0
- package/dist/conformance/src/profiles/evaluate-rule.js.map +1 -0
- package/dist/conformance/src/profiles/load-profile.d.ts +30 -0
- package/dist/conformance/src/profiles/load-profile.js +95 -0
- package/dist/conformance/src/profiles/load-profile.js.map +1 -0
- package/dist/conformance/src/profiles/resolve-pointer.d.ts +44 -0
- package/dist/conformance/src/profiles/resolve-pointer.js +123 -0
- package/dist/conformance/src/profiles/resolve-pointer.js.map +1 -0
- package/dist/conformance/src/profiles/select-rules.d.ts +13 -0
- package/dist/conformance/src/profiles/select-rules.js +42 -0
- package/dist/conformance/src/profiles/select-rules.js.map +1 -0
- package/dist/conformance/src/profiles/types.d.ts +101 -0
- package/dist/conformance/src/profiles/types.js +31 -0
- package/dist/conformance/src/profiles/types.js.map +1 -0
- package/dist/conformance/src/profiles/validate-profile-definition.d.ts +12 -0
- package/dist/conformance/src/profiles/validate-profile-definition.js +47 -0
- package/dist/conformance/src/profiles/validate-profile-definition.js.map +1 -0
- package/dist/conformance/src/sources.d.ts +53 -0
- package/dist/conformance/src/sources.js +153 -0
- package/dist/conformance/src/sources.js.map +1 -0
- package/dist/conformance/src/validate-core.d.ts +30 -0
- package/dist/conformance/src/validate-core.js +62 -0
- package/dist/conformance/src/validate-core.js.map +1 -0
- package/dist/conformance/src/validate.d.ts +34 -0
- package/dist/conformance/src/validate.js +62 -0
- package/dist/conformance/src/validate.js.map +1 -0
- package/dist/conformance/src/validator-interface.d.ts +35 -0
- package/dist/conformance/src/validator-interface.js +19 -0
- package/dist/conformance/src/validator-interface.js.map +1 -0
- package/package.json +85 -0
- package/profiles/README.md +201 -0
- package/profiles/api-and-integration-management/README.md +271 -0
- package/profiles/api-and-integration-management/profile.json +258 -0
- package/profiles/backup-and-recovery/README.md +318 -0
- package/profiles/backup-and-recovery/profile.json +178 -0
- package/profiles/customer-and-account-management/README.md +340 -0
- package/profiles/customer-and-account-management/profile.json +237 -0
- package/profiles/deployment-and-change-management/README.md +359 -0
- package/profiles/deployment-and-change-management/profile.json +298 -0
- package/profiles/document-management/README.md +120 -0
- package/profiles/document-management/profile.json +170 -0
- package/profiles/financial-transaction-management/README.md +339 -0
- package/profiles/financial-transaction-management/profile.json +247 -0
- package/profiles/identity-and-access-management/README.md +112 -0
- package/profiles/identity-and-access-management/profile.json +120 -0
- package/profiles/incident-management/README.md +338 -0
- package/profiles/incident-management/profile.json +256 -0
- package/profiles/message-broker-management/README.md +344 -0
- package/profiles/message-broker-management/profile.json +399 -0
- package/profiles/profile-definition.schema.json +213 -0
- package/profiles/secrets-and-key-management/README.md +331 -0
- package/profiles/secrets-and-key-management/profile.json +219 -0
- package/schemas/v0.1/audit-event.schema.json +911 -0
- package/semantic-conventions/README.md +52 -0
- package/semantic-conventions/authentication.md +111 -0
- package/semantic-conventions/configuration-and-change.md +122 -0
- package/semantic-conventions/correlation-and-tracing.md +387 -0
- package/semantic-conventions/data-access.md +115 -0
- package/semantic-conventions/event-naming.md +114 -0
- package/semantic-conventions/identity-and-access.md +112 -0
- package/semantic-conventions/privileged-operations.md +110 -0
- package/semantic-conventions/workflow-and-approval.md +109 -0
- package/specification/actor-model.md +135 -0
- package/specification/approval-and-delegation.md +150 -0
- package/specification/authentication.md +116 -0
- package/specification/authorization.md +117 -0
- package/specification/change-model.md +134 -0
- package/specification/delivery.md +136 -0
- package/specification/design-principles.md +137 -0
- package/specification/event-model.md +387 -0
- package/specification/evidence-model.md +109 -0
- package/specification/extension-model.md +159 -0
- package/specification/integrity.md +314 -0
- package/specification/overview.md +172 -0
- package/specification/privacy.md +386 -0
- package/specification/resource-model.md +116 -0
- package/specification/terminology.md +135 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
function paramsOf(error) {
|
|
2
|
+
return (error.params ?? {});
|
|
3
|
+
}
|
|
4
|
+
function asString(value) {
|
|
5
|
+
return typeof value === "string" ? value : undefined;
|
|
6
|
+
}
|
|
7
|
+
/** Escapes a property name so that it is a legal JSON Pointer reference token. */
|
|
8
|
+
function pointerSegment(property) {
|
|
9
|
+
return property.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
10
|
+
}
|
|
11
|
+
function joinPointer(base, property) {
|
|
12
|
+
const pointer = property === undefined ? base : `${base}/${pointerSegment(property)}`;
|
|
13
|
+
return pointer === "" ? "/" : pointer;
|
|
14
|
+
}
|
|
15
|
+
function describe(error) {
|
|
16
|
+
const params = paramsOf(error);
|
|
17
|
+
const fallback = error.message ?? "failed validation";
|
|
18
|
+
switch (error.keyword) {
|
|
19
|
+
case "required":
|
|
20
|
+
return { message: `missing required property "${String(params["missingProperty"])}"` };
|
|
21
|
+
case "dependentRequired":
|
|
22
|
+
return {
|
|
23
|
+
message: `missing property "${String(params["missingProperty"])}", which is required when "${String(params["property"])}" is present`,
|
|
24
|
+
};
|
|
25
|
+
case "additionalProperties":
|
|
26
|
+
return {
|
|
27
|
+
message: `unknown property "${String(params["additionalProperty"])}" is not permitted here`,
|
|
28
|
+
detail: "core objects reject unknown properties; use metadata, extensions or attributes instead",
|
|
29
|
+
};
|
|
30
|
+
case "propertyNames":
|
|
31
|
+
return {
|
|
32
|
+
message: `property name "${String(params["propertyName"])}" does not follow the required naming rule`,
|
|
33
|
+
};
|
|
34
|
+
case "enum": {
|
|
35
|
+
const allowed = Array.isArray(params["allowedValues"])
|
|
36
|
+
? params["allowedValues"].map((value) => JSON.stringify(value)).join(", ")
|
|
37
|
+
: undefined;
|
|
38
|
+
return {
|
|
39
|
+
message: "value is not one of the permitted values",
|
|
40
|
+
...(allowed === undefined ? {} : { detail: `allowed: ${allowed}` }),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
case "const":
|
|
44
|
+
return {
|
|
45
|
+
message: "value does not equal the required constant",
|
|
46
|
+
detail: `expected: ${JSON.stringify(params["allowedValue"])}`,
|
|
47
|
+
};
|
|
48
|
+
case "pattern":
|
|
49
|
+
return {
|
|
50
|
+
message: "value does not match the required pattern",
|
|
51
|
+
detail: `pattern: ${String(params["pattern"])}`,
|
|
52
|
+
};
|
|
53
|
+
case "format":
|
|
54
|
+
return {
|
|
55
|
+
message: `value is not a valid "${String(params["format"])}" value`,
|
|
56
|
+
};
|
|
57
|
+
case "if":
|
|
58
|
+
return {
|
|
59
|
+
message: "value does not satisfy a conditional requirement of the schema",
|
|
60
|
+
detail: "see the accompanying issues for the specific requirement",
|
|
61
|
+
};
|
|
62
|
+
case "not":
|
|
63
|
+
return { message: "value is explicitly forbidden by the schema" };
|
|
64
|
+
default:
|
|
65
|
+
return { message: fallback };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Converts validator errors into issues, deduplicating identical entries. */
|
|
69
|
+
export function toIssues(errors) {
|
|
70
|
+
if (!errors || errors.length === 0) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
const issues = [];
|
|
75
|
+
for (const error of errors) {
|
|
76
|
+
const params = paramsOf(error);
|
|
77
|
+
const base = error.instancePath;
|
|
78
|
+
// `propertyNames` failures report the offending key either on the error
|
|
79
|
+
// itself (for the nested constraint that failed) or in its parameters.
|
|
80
|
+
const propertyName = asString(error.propertyName) ??
|
|
81
|
+
(error.keyword === "propertyNames" ? asString(params["propertyName"]) : undefined);
|
|
82
|
+
let path;
|
|
83
|
+
if (error.keyword === "required" || error.keyword === "dependentRequired") {
|
|
84
|
+
path = joinPointer(base, asString(params["missingProperty"]));
|
|
85
|
+
}
|
|
86
|
+
else if (error.keyword === "additionalProperties") {
|
|
87
|
+
path = joinPointer(base, asString(params["additionalProperty"]));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
path = joinPointer(base, propertyName);
|
|
91
|
+
}
|
|
92
|
+
const { message, detail } = describe(error);
|
|
93
|
+
const key = `${path}|${error.keyword}|${message}|${detail ?? ""}`;
|
|
94
|
+
if (seen.has(key)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
seen.add(key);
|
|
98
|
+
issues.push({
|
|
99
|
+
path,
|
|
100
|
+
message,
|
|
101
|
+
keyword: error.keyword,
|
|
102
|
+
...(detail === undefined ? {} : { detail }),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return issues;
|
|
106
|
+
}
|
|
107
|
+
/** Renders issues as indented report lines. */
|
|
108
|
+
export function formatIssues(issues, indent = " ") {
|
|
109
|
+
return issues
|
|
110
|
+
.map((issue) => {
|
|
111
|
+
const detail = issue.detail === undefined ? "" : `\n${indent} ${issue.detail}`;
|
|
112
|
+
return `${indent}${issue.path} ${issue.message} [${issue.keyword}]${detail}`;
|
|
113
|
+
})
|
|
114
|
+
.join("\n");
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=format-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-errors.js","sourceRoot":"","sources":["../../../conformance/src/format-errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CANONICALIZATION_RFC8785 } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Deepest structure accepted for canonicalization. Canonicalization is
|
|
4
|
+
* recursive, so an arbitrarily deep document would exhaust the stack; audit
|
|
5
|
+
* events are shallow and this bound is far above any realistic event.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MAX_JSON_DEPTH = 200;
|
|
8
|
+
export declare class CanonicalizationError extends Error {
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Rejects anything that is not a value `JSON.parse` could have produced.
|
|
13
|
+
*
|
|
14
|
+
* The pointer in the error identifies *where* the problem is, never what the
|
|
15
|
+
* value was, so that a malformed event cannot leak its content through an error
|
|
16
|
+
* message.
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertJsonValue(value: unknown, pointer?: string, depth?: number): void;
|
|
19
|
+
/** Returns the RFC 8785 canonical form of a JSON value. */
|
|
20
|
+
export declare function canonicalize(value: unknown): string;
|
|
21
|
+
/** Returns the UTF-8 encoding of the RFC 8785 canonical form. */
|
|
22
|
+
export declare function canonicalBytes(value: unknown): Buffer;
|
|
23
|
+
/** True when this verifier implements the declared canonicalization. */
|
|
24
|
+
export declare function isSupportedCanonicalization(identifier: string): boolean;
|
|
25
|
+
export { CANONICALIZATION_RFC8785 };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 8785 JSON Canonicalization Scheme.
|
|
3
|
+
*
|
|
4
|
+
* Canonicalization is delegated to the `canonicalize` package (Apache-2.0, no
|
|
5
|
+
* dependencies), which implements the scheme by sorting object keys with the
|
|
6
|
+
* ECMAScript default string comparison — UTF-16 code unit order, as RFC 8785
|
|
7
|
+
* §3.2.3 requires — and serialising primitives with `JSON.stringify`, whose
|
|
8
|
+
* number and string handling is the behaviour RFC 8785 §3.2.2.2 and §3.2.2.3
|
|
9
|
+
* are defined against.
|
|
10
|
+
*
|
|
11
|
+
* The guard in this module exists because that package is permissive with
|
|
12
|
+
* values JSON cannot represent: it maps `undefined` inside an array to `null`,
|
|
13
|
+
* drops `undefined` object members, and calls `toJSON` on anything that has it.
|
|
14
|
+
* Silently reinterpreting an input would produce a digest over something other
|
|
15
|
+
* than what the caller passed, so those inputs are rejected instead.
|
|
16
|
+
*/
|
|
17
|
+
import canonicalizeValue from "canonicalize";
|
|
18
|
+
import { CANONICALIZATION_RFC8785, SUPPORTED_CANONICALIZATIONS } from "./types.js";
|
|
19
|
+
/**
|
|
20
|
+
* Deepest structure accepted for canonicalization. Canonicalization is
|
|
21
|
+
* recursive, so an arbitrarily deep document would exhaust the stack; audit
|
|
22
|
+
* events are shallow and this bound is far above any realistic event.
|
|
23
|
+
*/
|
|
24
|
+
export const MAX_JSON_DEPTH = 200;
|
|
25
|
+
export class CanonicalizationError extends Error {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "CanonicalizationError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function describePointer(pointer) {
|
|
32
|
+
return pointer === "" ? "the document root" : pointer;
|
|
33
|
+
}
|
|
34
|
+
function isPlainObject(value) {
|
|
35
|
+
const prototype = Object.getPrototypeOf(value);
|
|
36
|
+
return prototype === Object.prototype || prototype === null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Rejects anything that is not a value `JSON.parse` could have produced.
|
|
40
|
+
*
|
|
41
|
+
* The pointer in the error identifies *where* the problem is, never what the
|
|
42
|
+
* value was, so that a malformed event cannot leak its content through an error
|
|
43
|
+
* message.
|
|
44
|
+
*/
|
|
45
|
+
export function assertJsonValue(value, pointer = "", depth = 0) {
|
|
46
|
+
if (depth > MAX_JSON_DEPTH) {
|
|
47
|
+
throw new CanonicalizationError(`structure is nested more than ${MAX_JSON_DEPTH} levels deep at ${describePointer(pointer)}`);
|
|
48
|
+
}
|
|
49
|
+
if (value === null) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
switch (typeof value) {
|
|
53
|
+
case "boolean":
|
|
54
|
+
case "string":
|
|
55
|
+
return;
|
|
56
|
+
case "number":
|
|
57
|
+
if (!Number.isFinite(value)) {
|
|
58
|
+
throw new CanonicalizationError(`number at ${describePointer(pointer)} is not finite and cannot be canonicalized`);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
case "object":
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
throw new CanonicalizationError(`value at ${describePointer(pointer)} has type "${typeof value}", which JSON cannot represent`);
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
for (const [index, item] of value.entries()) {
|
|
68
|
+
assertJsonValue(item, `${pointer}/${index}`, depth + 1);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!isPlainObject(value)) {
|
|
73
|
+
throw new CanonicalizationError(`value at ${describePointer(pointer)} is not a plain JSON object`);
|
|
74
|
+
}
|
|
75
|
+
for (const [key, member] of Object.entries(value)) {
|
|
76
|
+
if (member === undefined) {
|
|
77
|
+
throw new CanonicalizationError(`member "${key}" at ${describePointer(pointer)} is undefined, which JSON cannot represent`);
|
|
78
|
+
}
|
|
79
|
+
assertJsonValue(member, `${pointer}/${key.replaceAll("~", "~0").replaceAll("/", "~1")}`, depth + 1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Returns the RFC 8785 canonical form of a JSON value. */
|
|
83
|
+
export function canonicalize(value) {
|
|
84
|
+
assertJsonValue(value);
|
|
85
|
+
const canonical = canonicalizeValue(value);
|
|
86
|
+
if (typeof canonical !== "string") {
|
|
87
|
+
throw new CanonicalizationError("value could not be canonicalized");
|
|
88
|
+
}
|
|
89
|
+
return canonical;
|
|
90
|
+
}
|
|
91
|
+
/** Returns the UTF-8 encoding of the RFC 8785 canonical form. */
|
|
92
|
+
export function canonicalBytes(value) {
|
|
93
|
+
return Buffer.from(canonicalize(value), "utf8");
|
|
94
|
+
}
|
|
95
|
+
/** True when this verifier implements the declared canonicalization. */
|
|
96
|
+
export function isSupportedCanonicalization(identifier) {
|
|
97
|
+
return SUPPORTED_CANONICALIZATIONS.includes(identifier);
|
|
98
|
+
}
|
|
99
|
+
export { CANONICALIZATION_RFC8785 };
|
|
100
|
+
//# sourceMappingURL=canonicalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonicalize.js","sourceRoot":"","sources":["../../../../conformance/src/integrity/canonicalize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { SUPPORTED_HASH_ALGORITHMS, type SupportedHashAlgorithm } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* True when this verifier implements the declared algorithm. Matching is
|
|
4
|
+
* case-sensitive: `sha256` is not `SHA-256`, and reinterpreting it would mean
|
|
5
|
+
* guessing at what a producer meant.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isSupportedHashAlgorithm(algorithm: string): algorithm is SupportedHashAlgorithm;
|
|
8
|
+
/** Digest length in bytes for a supported algorithm. */
|
|
9
|
+
export declare function digestByteLength(algorithm: SupportedHashAlgorithm): number;
|
|
10
|
+
/** True when a value is a well-formed lowercase hexadecimal digest. */
|
|
11
|
+
export declare function isHexDigest(value: unknown): value is string;
|
|
12
|
+
/**
|
|
13
|
+
* Builds the digest input: a deep clone of the event with the self-referential
|
|
14
|
+
* integrity members removed.
|
|
15
|
+
*
|
|
16
|
+
* Exactly two JSON Pointers are removed, `/integrity/hash` and
|
|
17
|
+
* `/integrity/signature`. No other member is removed and no empty container is
|
|
18
|
+
* pruned, so an `integrity` object left with no members is serialised as `{}`.
|
|
19
|
+
* That rule is arbitrary but it must be fixed, because a producer that pruned
|
|
20
|
+
* it and a verifier that did not would compute different digests.
|
|
21
|
+
*
|
|
22
|
+
* The input event is never mutated.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildDigestInput(event: unknown): unknown;
|
|
25
|
+
/** Returns the RFC 8785 canonical form of the digest input, for diagnostics and tests. */
|
|
26
|
+
export declare function canonicalDigestInput(event: unknown): string;
|
|
27
|
+
/**
|
|
28
|
+
* Calculates an event's digest and returns it as lowercase hexadecimal.
|
|
29
|
+
*
|
|
30
|
+
* Throws for an algorithm this verifier does not implement; callers that need
|
|
31
|
+
* to report rather than throw should check `isSupportedHashAlgorithm` first.
|
|
32
|
+
*/
|
|
33
|
+
export declare function calculateDigest(event: unknown, algorithm: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Returns a copy of an event with `integrity.hash` set to its calculated
|
|
36
|
+
* digest.
|
|
37
|
+
*
|
|
38
|
+
* The event must already declare `integrity.canonicalization` and
|
|
39
|
+
* `integrity.hashAlgorithm`, because both are part of the digest input: a
|
|
40
|
+
* producer that let the tooling choose them would be sealing a claim it never
|
|
41
|
+
* made. This is a producer-side convenience for tests, fixtures and future
|
|
42
|
+
* libraries; it performs no signing and touches no key material.
|
|
43
|
+
*/
|
|
44
|
+
export declare function sealEvent<T>(event: T): T;
|
|
45
|
+
/**
|
|
46
|
+
* Compares two digests as bytes, in constant time for equal-length inputs.
|
|
47
|
+
*
|
|
48
|
+
* A digest is not a secret, so this is defence in depth rather than a
|
|
49
|
+
* requirement; what matters more is that a malformed value is rejected instead
|
|
50
|
+
* of being coerced. `Buffer.from(value, "hex")` truncates silently on invalid
|
|
51
|
+
* input, so both values are checked against the accepted encoding first.
|
|
52
|
+
*/
|
|
53
|
+
export declare function digestsEqual(left: string, right: string): boolean;
|
|
54
|
+
export { SUPPORTED_HASH_ALGORITHMS, type SupportedHashAlgorithm };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event digest calculation and comparison.
|
|
3
|
+
*
|
|
4
|
+
* The procedure implemented here is normative and is specified in
|
|
5
|
+
* specification/integrity.md §4. It is deterministic: the same event produces
|
|
6
|
+
* the same digest in any conforming implementation, in any language.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
9
|
+
import { canonicalBytes, canonicalize } from "./canonicalize.js";
|
|
10
|
+
import { DIGEST_BYTE_LENGTHS, DIGEST_EXCLUDED_POINTERS, SUPPORTED_HASH_ALGORITHMS, } from "./types.js";
|
|
11
|
+
/** Lowercase hexadecimal, an even number of digits. The only accepted digest encoding. */
|
|
12
|
+
const HEX_DIGEST = /^([0-9a-f]{2})+$/;
|
|
13
|
+
/** Node's digest name for each normative OpenAuditModel algorithm identifier. */
|
|
14
|
+
const NODE_HASH_NAMES = {
|
|
15
|
+
"SHA-256": "sha256",
|
|
16
|
+
"SHA-384": "sha384",
|
|
17
|
+
"SHA-512": "sha512",
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* True when this verifier implements the declared algorithm. Matching is
|
|
21
|
+
* case-sensitive: `sha256` is not `SHA-256`, and reinterpreting it would mean
|
|
22
|
+
* guessing at what a producer meant.
|
|
23
|
+
*/
|
|
24
|
+
export function isSupportedHashAlgorithm(algorithm) {
|
|
25
|
+
return SUPPORTED_HASH_ALGORITHMS.includes(algorithm);
|
|
26
|
+
}
|
|
27
|
+
/** Digest length in bytes for a supported algorithm. */
|
|
28
|
+
export function digestByteLength(algorithm) {
|
|
29
|
+
return DIGEST_BYTE_LENGTHS[algorithm];
|
|
30
|
+
}
|
|
31
|
+
/** True when a value is a well-formed lowercase hexadecimal digest. */
|
|
32
|
+
export function isHexDigest(value) {
|
|
33
|
+
return typeof value === "string" && HEX_DIGEST.test(value);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Builds the digest input: a deep clone of the event with the self-referential
|
|
37
|
+
* integrity members removed.
|
|
38
|
+
*
|
|
39
|
+
* Exactly two JSON Pointers are removed, `/integrity/hash` and
|
|
40
|
+
* `/integrity/signature`. No other member is removed and no empty container is
|
|
41
|
+
* pruned, so an `integrity` object left with no members is serialised as `{}`.
|
|
42
|
+
* That rule is arbitrary but it must be fixed, because a producer that pruned
|
|
43
|
+
* it and a verifier that did not would compute different digests.
|
|
44
|
+
*
|
|
45
|
+
* The input event is never mutated.
|
|
46
|
+
*/
|
|
47
|
+
export function buildDigestInput(event) {
|
|
48
|
+
const clone = structuredClone(event);
|
|
49
|
+
if (clone !== null && typeof clone === "object" && !Array.isArray(clone)) {
|
|
50
|
+
const record = clone;
|
|
51
|
+
const integrity = record["integrity"];
|
|
52
|
+
if (integrity !== null && typeof integrity === "object" && !Array.isArray(integrity)) {
|
|
53
|
+
const integrityRecord = integrity;
|
|
54
|
+
for (const pointer of DIGEST_EXCLUDED_POINTERS) {
|
|
55
|
+
const member = pointer.slice("/integrity/".length);
|
|
56
|
+
delete integrityRecord[member];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return clone;
|
|
61
|
+
}
|
|
62
|
+
/** Returns the RFC 8785 canonical form of the digest input, for diagnostics and tests. */
|
|
63
|
+
export function canonicalDigestInput(event) {
|
|
64
|
+
return canonicalize(buildDigestInput(event));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Calculates an event's digest and returns it as lowercase hexadecimal.
|
|
68
|
+
*
|
|
69
|
+
* Throws for an algorithm this verifier does not implement; callers that need
|
|
70
|
+
* to report rather than throw should check `isSupportedHashAlgorithm` first.
|
|
71
|
+
*/
|
|
72
|
+
export function calculateDigest(event, algorithm) {
|
|
73
|
+
if (!isSupportedHashAlgorithm(algorithm)) {
|
|
74
|
+
throw new Error(`unsupported hash algorithm "${algorithm}"; this verifier implements ${SUPPORTED_HASH_ALGORITHMS.join(", ")}`);
|
|
75
|
+
}
|
|
76
|
+
const bytes = canonicalBytes(buildDigestInput(event));
|
|
77
|
+
return createHash(NODE_HASH_NAMES[algorithm]).update(bytes).digest("hex");
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Returns a copy of an event with `integrity.hash` set to its calculated
|
|
81
|
+
* digest.
|
|
82
|
+
*
|
|
83
|
+
* The event must already declare `integrity.canonicalization` and
|
|
84
|
+
* `integrity.hashAlgorithm`, because both are part of the digest input: a
|
|
85
|
+
* producer that let the tooling choose them would be sealing a claim it never
|
|
86
|
+
* made. This is a producer-side convenience for tests, fixtures and future
|
|
87
|
+
* libraries; it performs no signing and touches no key material.
|
|
88
|
+
*/
|
|
89
|
+
export function sealEvent(event) {
|
|
90
|
+
const integrity = event?.["integrity"];
|
|
91
|
+
if (integrity === null || typeof integrity !== "object" || Array.isArray(integrity)) {
|
|
92
|
+
throw new Error("cannot seal an event that declares no integrity object");
|
|
93
|
+
}
|
|
94
|
+
const record = integrity;
|
|
95
|
+
const canonicalization = record["canonicalization"];
|
|
96
|
+
const algorithm = record["hashAlgorithm"];
|
|
97
|
+
if (typeof canonicalization !== "string") {
|
|
98
|
+
throw new Error("cannot seal an event that declares no integrity.canonicalization");
|
|
99
|
+
}
|
|
100
|
+
if (typeof algorithm !== "string") {
|
|
101
|
+
throw new Error("cannot seal an event that declares no integrity.hashAlgorithm");
|
|
102
|
+
}
|
|
103
|
+
const hash = calculateDigest(event, algorithm);
|
|
104
|
+
return { ...event, integrity: { ...record, hash } };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Compares two digests as bytes, in constant time for equal-length inputs.
|
|
108
|
+
*
|
|
109
|
+
* A digest is not a secret, so this is defence in depth rather than a
|
|
110
|
+
* requirement; what matters more is that a malformed value is rejected instead
|
|
111
|
+
* of being coerced. `Buffer.from(value, "hex")` truncates silently on invalid
|
|
112
|
+
* input, so both values are checked against the accepted encoding first.
|
|
113
|
+
*/
|
|
114
|
+
export function digestsEqual(left, right) {
|
|
115
|
+
if (!isHexDigest(left) || !isHexDigest(right)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const leftBytes = Buffer.from(left, "hex");
|
|
119
|
+
const rightBytes = Buffer.from(right, "hex");
|
|
120
|
+
if (leftBytes.length !== rightBytes.length) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return timingSafeEqual(leftBytes, rightBytes);
|
|
124
|
+
}
|
|
125
|
+
export { SUPPORTED_HASH_ALGORITHMS };
|
|
126
|
+
//# sourceMappingURL=digest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../../../../conformance/src/integrity/digest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types and constants for tamper-evidence verification.
|
|
3
|
+
*
|
|
4
|
+
* The tooling detects specific integrity failures. It does not prevent deletion
|
|
5
|
+
* or modification, and it makes no claim that an event is immutable,
|
|
6
|
+
* tamper-proof, legally binding or non-repudiable.
|
|
7
|
+
*/
|
|
8
|
+
/** Canonicalization identifier the v0.1 verifier implements: RFC 8785 JCS. */
|
|
9
|
+
export declare const CANONICALIZATION_RFC8785 = "RFC8785";
|
|
10
|
+
/** Canonicalization identifiers this verifier can execute. Matching is case-sensitive. */
|
|
11
|
+
export declare const SUPPORTED_CANONICALIZATIONS: readonly ["RFC8785"];
|
|
12
|
+
/**
|
|
13
|
+
* Hash algorithms this verifier can execute, as normative identifiers.
|
|
14
|
+
*
|
|
15
|
+
* The schema keeps `integrity.hashAlgorithm` an open vocabulary so that new
|
|
16
|
+
* algorithms can be adopted without a specification change. An identifier the
|
|
17
|
+
* schema accepts is therefore not necessarily one this verifier implements: an
|
|
18
|
+
* event declaring an algorithm outside this list is reported as
|
|
19
|
+
* `unsupported-algorithm` rather than silently treated as verified.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SUPPORTED_HASH_ALGORITHMS: readonly ["SHA-256", "SHA-384", "SHA-512"];
|
|
22
|
+
export type SupportedHashAlgorithm = (typeof SUPPORTED_HASH_ALGORITHMS)[number];
|
|
23
|
+
/** Digest length in bytes for each supported algorithm. */
|
|
24
|
+
export declare const DIGEST_BYTE_LENGTHS: Readonly<Record<SupportedHashAlgorithm, number>>;
|
|
25
|
+
/**
|
|
26
|
+
* JSON Pointers removed from an event before its digest is calculated.
|
|
27
|
+
*
|
|
28
|
+
* Everything else, including `sequence`, `integrity.previousHash`,
|
|
29
|
+
* `integrity.chainId`, `integrity.batchId`, `integrity.hashAlgorithm` and
|
|
30
|
+
* `integrity.canonicalization`, is part of the digest input.
|
|
31
|
+
*/
|
|
32
|
+
export declare const DIGEST_EXCLUDED_POINTERS: readonly ["/integrity/hash", "/integrity/signature"];
|
|
33
|
+
/** Why a single event failed verification. */
|
|
34
|
+
export type EventFindingKind = "schema-invalid" | "integrity-missing" | "hash-missing" | "hash-algorithm-missing" | "canonicalization-missing" | "unsupported-canonicalization" | "unsupported-algorithm" | "malformed-hash" | "digest-length-mismatch" | "hash-mismatch" | "canonicalization-failed";
|
|
35
|
+
/** Why a chain failed verification. */
|
|
36
|
+
export type ChainFindingKind = "chain-id-missing" | "sequence-missing" | "duplicate-sequence" | "previous-hash-missing" | "broken-link" | "algorithm-mismatch";
|
|
37
|
+
export type FindingKind = EventFindingKind | ChainFindingKind;
|
|
38
|
+
/** A single reason verification did not succeed. */
|
|
39
|
+
export interface Finding {
|
|
40
|
+
readonly kind: FindingKind;
|
|
41
|
+
/** One line, safe to print. Never contains event content. */
|
|
42
|
+
readonly message: string;
|
|
43
|
+
/** Ordered detail lines, such as the declared and calculated digests. */
|
|
44
|
+
readonly detail?: readonly string[];
|
|
45
|
+
/** Where the problem was found, when it applies to one event. */
|
|
46
|
+
readonly label?: string;
|
|
47
|
+
}
|
|
48
|
+
/** An informational observation that does not by itself fail verification. */
|
|
49
|
+
export interface Note {
|
|
50
|
+
readonly message: string;
|
|
51
|
+
readonly detail?: readonly string[];
|
|
52
|
+
}
|
|
53
|
+
/** A check that passed, reported so that a successful run says what it proved. */
|
|
54
|
+
export interface PassedCheck {
|
|
55
|
+
readonly message: string;
|
|
56
|
+
}
|
|
57
|
+
/** Outcome of verifying one event's own digest. */
|
|
58
|
+
export interface EventVerificationResult {
|
|
59
|
+
readonly label: string;
|
|
60
|
+
readonly verified: boolean;
|
|
61
|
+
readonly checks: readonly PassedCheck[];
|
|
62
|
+
readonly findings: readonly Finding[];
|
|
63
|
+
readonly canonicalization?: string;
|
|
64
|
+
readonly hashAlgorithm?: string;
|
|
65
|
+
readonly declaredHash?: string;
|
|
66
|
+
readonly calculatedHash?: string;
|
|
67
|
+
}
|
|
68
|
+
/** Outcome of verifying one chain. */
|
|
69
|
+
export interface ChainVerificationResult {
|
|
70
|
+
readonly chainId: string;
|
|
71
|
+
readonly eventCount: number;
|
|
72
|
+
readonly firstSequence?: number;
|
|
73
|
+
readonly lastSequence?: number;
|
|
74
|
+
readonly intact: boolean;
|
|
75
|
+
readonly checks: readonly PassedCheck[];
|
|
76
|
+
readonly findings: readonly Finding[];
|
|
77
|
+
readonly notes: readonly Note[];
|
|
78
|
+
}
|
|
79
|
+
/** Outcome of verifying every chain in a supplied set of events. */
|
|
80
|
+
export interface ChainReport {
|
|
81
|
+
readonly chains: readonly ChainVerificationResult[];
|
|
82
|
+
/** Events that could not be assigned to a chain at all. */
|
|
83
|
+
readonly unassigned: readonly Finding[];
|
|
84
|
+
readonly eventCount: number;
|
|
85
|
+
readonly intact: boolean;
|
|
86
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types and constants for tamper-evidence verification.
|
|
3
|
+
*
|
|
4
|
+
* The tooling detects specific integrity failures. It does not prevent deletion
|
|
5
|
+
* or modification, and it makes no claim that an event is immutable,
|
|
6
|
+
* tamper-proof, legally binding or non-repudiable.
|
|
7
|
+
*/
|
|
8
|
+
/** Canonicalization identifier the v0.1 verifier implements: RFC 8785 JCS. */
|
|
9
|
+
export const CANONICALIZATION_RFC8785 = "RFC8785";
|
|
10
|
+
/** Canonicalization identifiers this verifier can execute. Matching is case-sensitive. */
|
|
11
|
+
export const SUPPORTED_CANONICALIZATIONS = [CANONICALIZATION_RFC8785];
|
|
12
|
+
/**
|
|
13
|
+
* Hash algorithms this verifier can execute, as normative identifiers.
|
|
14
|
+
*
|
|
15
|
+
* The schema keeps `integrity.hashAlgorithm` an open vocabulary so that new
|
|
16
|
+
* algorithms can be adopted without a specification change. An identifier the
|
|
17
|
+
* schema accepts is therefore not necessarily one this verifier implements: an
|
|
18
|
+
* event declaring an algorithm outside this list is reported as
|
|
19
|
+
* `unsupported-algorithm` rather than silently treated as verified.
|
|
20
|
+
*/
|
|
21
|
+
export const SUPPORTED_HASH_ALGORITHMS = ["SHA-256", "SHA-384", "SHA-512"];
|
|
22
|
+
/** Digest length in bytes for each supported algorithm. */
|
|
23
|
+
export const DIGEST_BYTE_LENGTHS = {
|
|
24
|
+
"SHA-256": 32,
|
|
25
|
+
"SHA-384": 48,
|
|
26
|
+
"SHA-512": 64,
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* JSON Pointers removed from an event before its digest is calculated.
|
|
30
|
+
*
|
|
31
|
+
* Everything else, including `sequence`, `integrity.previousHash`,
|
|
32
|
+
* `integrity.chainId`, `integrity.batchId`, `integrity.hashAlgorithm` and
|
|
33
|
+
* `integrity.canonicalization`, is part of the digest input.
|
|
34
|
+
*/
|
|
35
|
+
export const DIGEST_EXCLUDED_POINTERS = ["/integrity/hash", "/integrity/signature"];
|
|
36
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../conformance/src/integrity/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,wBAAwB,GAAG,SAAS,CAAC;AAElD,0FAA0F;AAC1F,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,wBAAwB,CAAU,CAAC;AAE/E;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAU,CAAC;AAIpF,2DAA2D;AAC3D,MAAM,CAAC,MAAM,mBAAmB,GAAqD;IACnF,SAAS,EAAE,EAAE;IACb,SAAS,EAAE,EAAE;IACb,SAAS,EAAE,EAAE;CACd,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,iBAAiB,EAAE,sBAAsB,CAAU,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification of previous-hash chains.
|
|
3
|
+
*
|
|
4
|
+
* Chain verification proves that the *supplied* set of events is internally
|
|
5
|
+
* consistent. It cannot prove that the supplied set is complete: an attacker
|
|
6
|
+
* who removes the tail of a chain, or who never let an event be produced,
|
|
7
|
+
* leaves nothing for a verifier to find. See specification/integrity.md §8.
|
|
8
|
+
*/
|
|
9
|
+
import type { EventValidator } from "../validate-core.js";
|
|
10
|
+
import type { ChainReport } from "./types.js";
|
|
11
|
+
/** One event offered for chain verification. */
|
|
12
|
+
export interface ChainEventInput {
|
|
13
|
+
readonly label: string;
|
|
14
|
+
readonly event: unknown;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Verifies every chain present in a set of events.
|
|
18
|
+
*
|
|
19
|
+
* Events are grouped by `integrity.chainId`; a set containing several chains is
|
|
20
|
+
* verified as several independent chains, which is the intended model — a
|
|
21
|
+
* single global chain is never required.
|
|
22
|
+
*/
|
|
23
|
+
export declare function verifyChains(inputs: readonly ChainEventInput[], validator: EventValidator): ChainReport;
|