@ai-dossier/core 1.4.1 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/signature.d.ts +87 -0
- package/dist/signature.d.ts.map +1 -1
- package/dist/signature.js +189 -28
- package/dist/signature.js.map +1 -1
- package/dist/signing-payload.d.ts +19 -1
- package/dist/signing-payload.d.ts.map +1 -1
- package/dist/signing-payload.js +90 -30
- package/dist/signing-payload.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,6 +105,9 @@ import {
|
|
|
105
105
|
verifyWithEd25519,
|
|
106
106
|
verifyWithKms,
|
|
107
107
|
loadTrustedKeys,
|
|
108
|
+
findTrustedIdentifier,
|
|
109
|
+
normalizePublicKey,
|
|
110
|
+
isSupportedPublicKey,
|
|
108
111
|
} from '@ai-dossier/core';
|
|
109
112
|
```
|
|
110
113
|
|
|
@@ -119,7 +122,7 @@ console.log(result.valid); // true | false
|
|
|
119
122
|
|
|
120
123
|
#### `verifyWithEd25519(content: string, signature: string, publicKey: string): VerifyResult`
|
|
121
124
|
|
|
122
|
-
Verify an Ed25519 signature directly.
|
|
125
|
+
Verify an Ed25519 signature directly. `publicKey` may be SPKI PEM, raw 32-byte base64, or base64 SPKI DER.
|
|
123
126
|
|
|
124
127
|
#### `verifyWithKms(content: string, signature: string, keyId: string, region?: string): Promise<VerifyResult>`
|
|
125
128
|
|
|
@@ -127,7 +130,17 @@ Verify an ECDSA-SHA-256 signature using AWS KMS.
|
|
|
127
130
|
|
|
128
131
|
#### `loadTrustedKeys(filePath?: string): Map<string, string>`
|
|
129
132
|
|
|
130
|
-
Load trusted public keys from a file (default: `~/.dossier/trusted-keys.txt`). Returns a map of public key to
|
|
133
|
+
Load trusted public keys from a file (default: `~/.dossier/trusted-keys.txt`), format `<public-key> <identifier>` one per line. Returns a map of public key to identifier, indexed under both the written and the normalized form. Unreadable lines are reported to stderr, not dropped silently.
|
|
134
|
+
|
|
135
|
+
#### `findTrustedIdentifier(trustedKeys, signature): string | undefined`
|
|
136
|
+
|
|
137
|
+
Resolve the identifier a signature is trusted under. Use this rather than probing the map by hand — it consults `key_id` only when there is no `public_key`, which is what stops a dossier from claiming a trusted signer's identity while verifying under a different key.
|
|
138
|
+
|
|
139
|
+
#### `normalizePublicKey(key) / isSupportedPublicKey(key)`
|
|
140
|
+
|
|
141
|
+
Reduce an Ed25519 key to its canonical raw 32-byte base64 form, and test whether a key is one this project can verify against. Validate with `isSupportedPublicKey` before *storing* a key: `normalizePublicKey` deliberately passes uninterpretable input through.
|
|
142
|
+
|
|
143
|
+
See [core API reference](../../docs/reference/core-api.md#key-format-and-trust-file-helpers) for the full trust-file helper set.
|
|
131
144
|
|
|
132
145
|
### Linting
|
|
133
146
|
|
package/dist/index.d.ts
CHANGED
|
@@ -20,10 +20,11 @@ export type { ChecksumStatus, ContentRiskResult, SignatureStatus, VerificationRi
|
|
|
20
20
|
export { assessContentRisk, assessVerificationRisk } from './risk-assessment';
|
|
21
21
|
export type { SecurityCategory, SecurityFinding, SecurityReport, SecurityRule, SecurityRuleContext, SecurityRuleSeverityOverride, SecurityScanConfig, SecuritySeverity, } from './security-scanner';
|
|
22
22
|
export { buildReport, defaultSecurityRules, SecurityRuleRegistry, scanDossier, scanDossierFile, scanMarkdown, } from './security-scanner';
|
|
23
|
-
export {
|
|
23
|
+
export type { TrustedKeyEntry, TrustedKeyProblem } from './signature';
|
|
24
|
+
export { findTrustedIdentifier, isKmsKeyIdentifier, loadTrustedKeys, parseTrustedKeys, reportTrustedKeyProblems, trustedKeysFromContent, verifySignature, verifyWithEd25519, verifyWithKms, } from './signature';
|
|
24
25
|
export { Ed25519Signer, Ed25519Verifier, getVerifierRegistry, KmsSigner, KmsVerifier, SignatureResult, Signer, Verifier, VerifierRegistry, VerifyResult, } from './signers';
|
|
25
26
|
export type { SignatureCoverage } from './signing-payload';
|
|
26
|
-
export { buildSignedPayload, canonicalizeFrontmatter, normalizePublicKey, publicKeysMatch, signatureCoverage, toSpkiPem, } from './signing-payload';
|
|
27
|
+
export { buildSignedPayload, canonicalizeFrontmatter, isSupportedPublicKey, normalizePublicKey, publicKeysMatch, signatureCoverage, toSpkiPem, } from './signing-payload';
|
|
27
28
|
export type { ConfigLayer as TraceConfigLayer, ResolvedTraceConfig, TraceConfigOptions, TraceConfigSource, } from './trace-config';
|
|
28
29
|
export { resolveTraceConfig } from './trace-config';
|
|
29
30
|
export type { DossierTraceInfo, StepInput as TraceStepInput, TraceInput, TraceRecorder, TraceRecorderOptions, TraceStatus, TraceUpdate, } from './trace-recorder';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEhE,YAAY,EACV,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE/D,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACtE,YAAY,EACV,UAAU,EACV,cAAc,EACd,UAAU,EACV,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,cAAc,GACf,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,mBAAmB,GACpB,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE9E,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,4BAA4B,EAC5B,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,oBAAoB,EACpB,WAAW,EACX,eAAe,EACf,YAAY,GACb,MAAM,oBAAoB,CAAC;AAE5B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -24,8 +24,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
24
24
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
25
25
|
};
|
|
26
26
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
-
exports.
|
|
28
|
-
exports.createDefaultVerificationResult = exports.scanBodyForUrls = exports.isUrlCoveredByDeclared = exports.isPlaceholderUrl = exports.findUndeclaredUrls = exports.findStaleReferences = exports.collectDeclaredUrls = void 0;
|
|
27
|
+
exports.createTraceRecorder = exports.resolveTraceConfig = exports.toSpkiPem = exports.signatureCoverage = exports.publicKeysMatch = exports.normalizePublicKey = exports.isSupportedPublicKey = exports.canonicalizeFrontmatter = exports.buildSignedPayload = exports.VerifierRegistry = exports.KmsVerifier = exports.KmsSigner = exports.getVerifierRegistry = exports.Ed25519Verifier = exports.Ed25519Signer = exports.verifyWithKms = exports.verifyWithEd25519 = exports.verifySignature = exports.trustedKeysFromContent = exports.reportTrustedKeyProblems = exports.parseTrustedKeys = exports.loadTrustedKeys = exports.isKmsKeyIdentifier = exports.findTrustedIdentifier = exports.scanMarkdown = exports.scanDossierFile = exports.scanDossier = exports.SecurityRuleRegistry = exports.defaultSecurityRules = exports.buildReport = exports.assessVerificationRisk = exports.assessContentRisk = exports.validateFrontmatter = exports.VALID_STATUSES = exports.VALID_RISK_LEVELS = exports.REQUIRED_FIELDS = exports.RECOMMENDED_FIELDS = exports.parseDossierFile = exports.parseDossierContent = exports.loadLintConfig = exports.lintDossierFile = exports.lintDossier = exports.LintRuleRegistry = exports.defaultRules = exports.formatDossierFile = exports.formatDossierContent = exports.validateStepCoherence = exports.validateCoherence = exports.verifyIntegrity = exports.calculateChecksum = void 0;
|
|
28
|
+
exports.createDefaultVerificationResult = exports.scanBodyForUrls = exports.isUrlCoveredByDeclared = exports.isPlaceholderUrl = exports.findUndeclaredUrls = exports.findStaleReferences = exports.collectDeclaredUrls = exports.readFileIfExists = exports.getErrorStack = exports.getErrorMessage = exports.sha256Hex = exports.sha256Hash = exports.VALID_TRACE_STATUSES = void 0;
|
|
29
29
|
// Checksum exports
|
|
30
30
|
var checksum_1 = require("./checksum");
|
|
31
31
|
Object.defineProperty(exports, "calculateChecksum", { enumerable: true, get: function () { return checksum_1.calculateChecksum; } });
|
|
@@ -64,9 +64,13 @@ Object.defineProperty(exports, "SecurityRuleRegistry", { enumerable: true, get:
|
|
|
64
64
|
Object.defineProperty(exports, "scanDossier", { enumerable: true, get: function () { return security_scanner_1.scanDossier; } });
|
|
65
65
|
Object.defineProperty(exports, "scanDossierFile", { enumerable: true, get: function () { return security_scanner_1.scanDossierFile; } });
|
|
66
66
|
Object.defineProperty(exports, "scanMarkdown", { enumerable: true, get: function () { return security_scanner_1.scanMarkdown; } });
|
|
67
|
-
// Signature exports
|
|
68
67
|
var signature_1 = require("./signature");
|
|
68
|
+
Object.defineProperty(exports, "findTrustedIdentifier", { enumerable: true, get: function () { return signature_1.findTrustedIdentifier; } });
|
|
69
|
+
Object.defineProperty(exports, "isKmsKeyIdentifier", { enumerable: true, get: function () { return signature_1.isKmsKeyIdentifier; } });
|
|
69
70
|
Object.defineProperty(exports, "loadTrustedKeys", { enumerable: true, get: function () { return signature_1.loadTrustedKeys; } });
|
|
71
|
+
Object.defineProperty(exports, "parseTrustedKeys", { enumerable: true, get: function () { return signature_1.parseTrustedKeys; } });
|
|
72
|
+
Object.defineProperty(exports, "reportTrustedKeyProblems", { enumerable: true, get: function () { return signature_1.reportTrustedKeyProblems; } });
|
|
73
|
+
Object.defineProperty(exports, "trustedKeysFromContent", { enumerable: true, get: function () { return signature_1.trustedKeysFromContent; } });
|
|
70
74
|
Object.defineProperty(exports, "verifySignature", { enumerable: true, get: function () { return signature_1.verifySignature; } });
|
|
71
75
|
Object.defineProperty(exports, "verifyWithEd25519", { enumerable: true, get: function () { return signature_1.verifyWithEd25519; } });
|
|
72
76
|
Object.defineProperty(exports, "verifyWithKms", { enumerable: true, get: function () { return signature_1.verifyWithKms; } });
|
|
@@ -81,6 +85,7 @@ Object.defineProperty(exports, "VerifierRegistry", { enumerable: true, get: func
|
|
|
81
85
|
var signing_payload_1 = require("./signing-payload");
|
|
82
86
|
Object.defineProperty(exports, "buildSignedPayload", { enumerable: true, get: function () { return signing_payload_1.buildSignedPayload; } });
|
|
83
87
|
Object.defineProperty(exports, "canonicalizeFrontmatter", { enumerable: true, get: function () { return signing_payload_1.canonicalizeFrontmatter; } });
|
|
88
|
+
Object.defineProperty(exports, "isSupportedPublicKey", { enumerable: true, get: function () { return signing_payload_1.isSupportedPublicKey; } });
|
|
84
89
|
Object.defineProperty(exports, "normalizePublicKey", { enumerable: true, get: function () { return signing_payload_1.normalizePublicKey; } });
|
|
85
90
|
Object.defineProperty(exports, "publicKeysMatch", { enumerable: true, get: function () { return signing_payload_1.publicKeysMatch; } });
|
|
86
91
|
Object.defineProperty(exports, "signatureCoverage", { enumerable: true, get: function () { return signing_payload_1.signatureCoverage; } });
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;;;;;;;;;;;;;;;;AAEH,mBAAmB;AACnB,uCAAgE;AAAvD,6GAAA,iBAAiB,OAAA;AAAE,2GAAA,eAAe,OAAA;AAU3C,yCAAuE;AAA9D,8GAAA,iBAAiB,OAAA;AAAE,kHAAA,qBAAqB,OAAA;AAEjD,oBAAoB;AACpB,yCAAsE;AAA7D,iHAAA,oBAAoB,OAAA;AAAE,8GAAA,iBAAiB,OAAA;AAUhD,iBAAiB;AACjB,mCAMkB;AALhB,sGAAA,YAAY,OAAA;AACZ,0GAAA,gBAAgB,OAAA;AAChB,qGAAA,WAAW,OAAA;AACX,yGAAA,eAAe,OAAA;AACf,wGAAA,cAAc,OAAA;AAEhB,iBAAiB;AACjB,mCAQkB;AAPhB,6GAAA,mBAAmB,OAAA;AACnB,0GAAA,gBAAgB,OAAA;AAChB,4GAAA,kBAAkB,OAAA;AAClB,yGAAA,eAAe,OAAA;AACf,2GAAA,iBAAiB,OAAA;AACjB,wGAAA,cAAc,OAAA;AACd,6GAAA,mBAAmB,OAAA;AASrB,0BAA0B;AAC1B,qDAA8E;AAArE,oHAAA,iBAAiB,OAAA;AAAE,yHAAA,sBAAsB,OAAA;AAYlD,uDAO4B;AAN1B,+GAAA,WAAW,OAAA;AACX,wHAAA,oBAAoB,OAAA;AACpB,wHAAA,oBAAoB,OAAA;AACpB,+GAAA,WAAW,OAAA;AACX,mHAAA,eAAe,OAAA;AACf,gHAAA,YAAY,OAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;;;;;;;;;;;;;;;;AAEH,mBAAmB;AACnB,uCAAgE;AAAvD,6GAAA,iBAAiB,OAAA;AAAE,2GAAA,eAAe,OAAA;AAU3C,yCAAuE;AAA9D,8GAAA,iBAAiB,OAAA;AAAE,kHAAA,qBAAqB,OAAA;AAEjD,oBAAoB;AACpB,yCAAsE;AAA7D,iHAAA,oBAAoB,OAAA;AAAE,8GAAA,iBAAiB,OAAA;AAUhD,iBAAiB;AACjB,mCAMkB;AALhB,sGAAA,YAAY,OAAA;AACZ,0GAAA,gBAAgB,OAAA;AAChB,qGAAA,WAAW,OAAA;AACX,yGAAA,eAAe,OAAA;AACf,wGAAA,cAAc,OAAA;AAEhB,iBAAiB;AACjB,mCAQkB;AAPhB,6GAAA,mBAAmB,OAAA;AACnB,0GAAA,gBAAgB,OAAA;AAChB,4GAAA,kBAAkB,OAAA;AAClB,yGAAA,eAAe,OAAA;AACf,2GAAA,iBAAiB,OAAA;AACjB,wGAAA,cAAc,OAAA;AACd,6GAAA,mBAAmB,OAAA;AASrB,0BAA0B;AAC1B,qDAA8E;AAArE,oHAAA,iBAAiB,OAAA;AAAE,yHAAA,sBAAsB,OAAA;AAYlD,uDAO4B;AAN1B,+GAAA,WAAW,OAAA;AACX,wHAAA,oBAAoB,OAAA;AACpB,wHAAA,oBAAoB,OAAA;AACpB,+GAAA,WAAW,OAAA;AACX,mHAAA,eAAe,OAAA;AACf,gHAAA,YAAY,OAAA;AAId,yCAUqB;AATnB,kHAAA,qBAAqB,OAAA;AACrB,+GAAA,kBAAkB,OAAA;AAClB,4GAAA,eAAe,OAAA;AACf,6GAAA,gBAAgB,OAAA;AAChB,qHAAA,wBAAwB,OAAA;AACxB,mHAAA,sBAAsB,OAAA;AACtB,4GAAA,eAAe,OAAA;AACf,8GAAA,iBAAiB,OAAA;AACjB,0GAAA,aAAa,OAAA;AAEf,iDAAiD;AACjD,qCAWmB;AAVjB,wGAAA,aAAa,OAAA;AACb,0GAAA,eAAe,OAAA;AACf,8GAAA,mBAAmB,OAAA;AACnB,oGAAA,SAAS,OAAA;AACT,sGAAA,WAAW,OAAA;AAIX,2GAAA,gBAAgB,OAAA;AAKlB,qDAQ2B;AAPzB,qHAAA,kBAAkB,OAAA;AAClB,0HAAA,uBAAuB,OAAA;AACvB,uHAAA,oBAAoB,OAAA;AACpB,qHAAA,kBAAkB,OAAA;AAClB,kHAAA,eAAe,OAAA;AACf,oHAAA,iBAAiB,OAAA;AACjB,4GAAA,SAAS,OAAA;AASX,+CAAoD;AAA3C,kHAAA,kBAAkB,OAAA;AAW3B,mDAA6E;AAApE,qHAAA,mBAAmB,OAAA;AAAE,sHAAA,oBAAoB,OAAA;AAClD,eAAe;AACf,0CAAwB;AACxB,mBAAmB;AACnB,yCAAuD;AAA9C,oGAAA,UAAU,OAAA;AAAE,mGAAA,SAAS,OAAA;AAC9B,kBAAkB;AAClB,yCAAgE;AAAvD,yGAAA,eAAe,OAAA;AAAE,uGAAA,aAAa,OAAA;AACvC,wBAAwB;AACxB,iCAA8C;AAArC,sGAAA,gBAAgB,OAAA;AACzB,yBAAyB;AACzB,mDAO6B;AAN3B,kHAAA,mBAAmB,OAAA;AACnB,kHAAA,mBAAmB,OAAA;AACnB,iHAAA,kBAAkB,OAAA;AAClB,+GAAA,gBAAgB,OAAA;AAChB,qHAAA,sBAAsB,OAAA;AACtB,8GAAA,eAAe,OAAA;AAEjB,yBAAyB;AACzB,qDAAuE;AAA9D,+HAAA,+BAA+B,OAAA"}
|
package/dist/signature.d.ts
CHANGED
|
@@ -5,12 +5,99 @@
|
|
|
5
5
|
* supporting multiple signature schemes (Ed25519 and AWS KMS).
|
|
6
6
|
*/
|
|
7
7
|
import type { SignatureResult, VerifyResult } from './signers';
|
|
8
|
+
/** One `<public-key> <key-id>` entry from a trusted-keys file, as written. */
|
|
9
|
+
export interface TrustedKeyEntry {
|
|
10
|
+
publicKey: string;
|
|
11
|
+
keyId: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A line of a trusted-keys file that could not be read as an entry.
|
|
15
|
+
*
|
|
16
|
+
* Reported rather than silently dropped: a skipped entry means a key the user
|
|
17
|
+
* believes is trusted is not, and the only downstream symptom is `dossier verify`
|
|
18
|
+
* saying "not trusted" with nothing pointing back at the file.
|
|
19
|
+
*/
|
|
20
|
+
export interface TrustedKeyProblem {
|
|
21
|
+
/** 1-based line number in the trusted-keys file. */
|
|
22
|
+
line: number;
|
|
23
|
+
message: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Split a trusted-keys file into `<public-key> <key-id>` pairs.
|
|
27
|
+
*
|
|
28
|
+
* One entry per line, except for PEM blocks: `dossier keys add` used to write its
|
|
29
|
+
* argument verbatim, so passing a PEM appended four lines that a line-oriented
|
|
30
|
+
* parse then shredded into entries matching nothing. Those blocks are rejoined so
|
|
31
|
+
* an already written file works without hand-editing — this only reassembles what
|
|
32
|
+
* the user wrote, it cannot widen a match.
|
|
33
|
+
*
|
|
34
|
+
* This is the single definition of what the trusted-key file format is: anything
|
|
35
|
+
* that lists, counts, or matches trusted keys must go through it, so `keys list`
|
|
36
|
+
* can never disagree with what verification actually trusts.
|
|
37
|
+
*
|
|
38
|
+
* Skipped lines come back alongside the entries rather than being dropped: every
|
|
39
|
+
* skip is a key the user thinks is trusted and isn't, so callers that can show
|
|
40
|
+
* the user something — `keys list`, and verification via `loadTrustedKeys` —
|
|
41
|
+
* report them instead of failing mute.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseTrustedKeys(content: string): {
|
|
44
|
+
entries: TrustedKeyEntry[];
|
|
45
|
+
problems: TrustedKeyProblem[];
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Print unusable trusted-key entries to stderr, bounded.
|
|
49
|
+
*
|
|
50
|
+
* `loadTrustedKeys` runs on every `dossier verify`, so this stays silent unless
|
|
51
|
+
* the file is genuinely malformed — and a malformed trust file is precisely when
|
|
52
|
+
* the user needs to hear about it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function reportTrustedKeyProblems(problems: TrustedKeyProblem[], source?: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Build the trusted-key map from the contents of a trusted-keys file.
|
|
57
|
+
*
|
|
58
|
+
* Keys are indexed under both the form they were written in and their normalized
|
|
59
|
+
* form, so a key registered in one encoding still matches a signature that
|
|
60
|
+
* carries another.
|
|
61
|
+
*
|
|
62
|
+
* @param source - Path named in warnings about unusable entries. Omit to stay silent.
|
|
63
|
+
*/
|
|
64
|
+
export declare function trustedKeysFromContent(content: string, source?: string): Map<string, string>;
|
|
8
65
|
/**
|
|
9
66
|
* Load trusted keys from file
|
|
10
67
|
* Default location: ~/.dossier/trusted-keys.txt
|
|
11
68
|
* Format: <public-key> <key-id>
|
|
12
69
|
*/
|
|
13
70
|
export declare function loadTrustedKeys(filePath?: string): Map<string, string>;
|
|
71
|
+
/**
|
|
72
|
+
* Whether a string identifies an AWS KMS key, and so is a usable trust-list entry
|
|
73
|
+
* for a KMS-signed dossier.
|
|
74
|
+
*/
|
|
75
|
+
export declare function isKmsKeyIdentifier(value: string): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Resolve the identifier a signature is trusted under, or `undefined` if none of
|
|
78
|
+
* its key forms appear in the trust list.
|
|
79
|
+
*
|
|
80
|
+
* The signature and the trust list need not agree on encoding, so every form the
|
|
81
|
+
* *verifying* key carries is tried: the public key as written and normalized to
|
|
82
|
+
* raw base64. Every trust decision goes through here so "is it trusted" and "who
|
|
83
|
+
* is it" can never drift apart.
|
|
84
|
+
*
|
|
85
|
+
* Only the key material the verifier actually used is eligible, which is decided
|
|
86
|
+
* by the algorithm, not by which fields happen to be populated. `KmsVerifier`
|
|
87
|
+
* verifies against `key_id` and ignores `public_key` — which KMS signatures carry
|
|
88
|
+
* anyway — so for KMS only `key_id` can confer trust. Everything else verifies
|
|
89
|
+
* against `public_key`, so only that may.
|
|
90
|
+
*
|
|
91
|
+
* Letting a field the verifier ignored confer trust would hand it out for free:
|
|
92
|
+
* the trust list is keyed by public key and public keys are public, so an Ed25519
|
|
93
|
+
* dossier carrying `key_id: "<any trusted public key>"` would be trusted as that
|
|
94
|
+
* signer while verifying under an attacker's `public_key`.
|
|
95
|
+
*/
|
|
96
|
+
export declare function findTrustedIdentifier(trustedKeys: Map<string, string>, signature: {
|
|
97
|
+
algorithm?: string;
|
|
98
|
+
key_id?: string;
|
|
99
|
+
public_key?: string;
|
|
100
|
+
}): string | undefined;
|
|
14
101
|
/**
|
|
15
102
|
* Verify signature using Ed25519
|
|
16
103
|
* @param content - The content to verify
|
package/dist/signature.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../src/signature.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAM/D;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../src/signature.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAM/D,8EAA8E;AAC9E,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAQD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IACjD,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B,CAsEA;AAKD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAgB7F;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAkB5F;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAKtE;AASD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,SAAS,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACtE,MAAM,GAAG,SAAS,CAkBpB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,YAAY,CAkBd;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,SAAc,GACnB,OAAO,CAAC,YAAY,CAAC,CAsBvB;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,eAAe,GACzB,OAAO,CAAC,YAAY,CAAC,CAIvB"}
|
package/dist/signature.js
CHANGED
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
* supporting multiple signature schemes (Ed25519 and AWS KMS).
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.parseTrustedKeys = parseTrustedKeys;
|
|
10
|
+
exports.reportTrustedKeyProblems = reportTrustedKeyProblems;
|
|
11
|
+
exports.trustedKeysFromContent = trustedKeysFromContent;
|
|
9
12
|
exports.loadTrustedKeys = loadTrustedKeys;
|
|
13
|
+
exports.isKmsKeyIdentifier = isKmsKeyIdentifier;
|
|
14
|
+
exports.findTrustedIdentifier = findTrustedIdentifier;
|
|
10
15
|
exports.verifyWithEd25519 = verifyWithEd25519;
|
|
11
16
|
exports.verifyWithKms = verifyWithKms;
|
|
12
17
|
exports.verifySignature = verifySignature;
|
|
@@ -18,6 +23,141 @@ const signers_1 = require("./signers");
|
|
|
18
23
|
const signing_payload_1 = require("./signing-payload");
|
|
19
24
|
const crypto_1 = require("./utils/crypto");
|
|
20
25
|
const fs_1 = require("./utils/fs");
|
|
26
|
+
/** Whether a line reads as an entry in its own right. */
|
|
27
|
+
function looksLikeKeyEntry(line) {
|
|
28
|
+
const parts = line.trim().split(/\s+/);
|
|
29
|
+
return parts.length >= 2 && (0, signing_payload_1.isSupportedPublicKey)(parts[0]);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Split a trusted-keys file into `<public-key> <key-id>` pairs.
|
|
33
|
+
*
|
|
34
|
+
* One entry per line, except for PEM blocks: `dossier keys add` used to write its
|
|
35
|
+
* argument verbatim, so passing a PEM appended four lines that a line-oriented
|
|
36
|
+
* parse then shredded into entries matching nothing. Those blocks are rejoined so
|
|
37
|
+
* an already written file works without hand-editing — this only reassembles what
|
|
38
|
+
* the user wrote, it cannot widen a match.
|
|
39
|
+
*
|
|
40
|
+
* This is the single definition of what the trusted-key file format is: anything
|
|
41
|
+
* that lists, counts, or matches trusted keys must go through it, so `keys list`
|
|
42
|
+
* can never disagree with what verification actually trusts.
|
|
43
|
+
*
|
|
44
|
+
* Skipped lines come back alongside the entries rather than being dropped: every
|
|
45
|
+
* skip is a key the user thinks is trusted and isn't, so callers that can show
|
|
46
|
+
* the user something — `keys list`, and verification via `loadTrustedKeys` —
|
|
47
|
+
* report them instead of failing mute.
|
|
48
|
+
*/
|
|
49
|
+
function parseTrustedKeys(content) {
|
|
50
|
+
const entries = [];
|
|
51
|
+
const problems = [];
|
|
52
|
+
const lines = content.split('\n');
|
|
53
|
+
for (let i = 0; i < lines.length; i++) {
|
|
54
|
+
const line = lines[i].trim();
|
|
55
|
+
if (!line || line.startsWith('#')) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!line.startsWith('-----BEGIN')) {
|
|
59
|
+
const parts = line.split(/\s+/);
|
|
60
|
+
if (parts.length >= 2) {
|
|
61
|
+
entries.push({ publicKey: parts[0], keyId: parts.slice(1).join(' ') });
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
problems.push({
|
|
65
|
+
line: i + 1,
|
|
66
|
+
message: 'public key has no identifier — expected "<public-key> <identifier>"',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const blockStart = i;
|
|
72
|
+
const block = [line];
|
|
73
|
+
while (i + 1 < lines.length && !block[block.length - 1].includes('-----END')) {
|
|
74
|
+
i++;
|
|
75
|
+
block.push(lines[i].trim());
|
|
76
|
+
}
|
|
77
|
+
// The identifier trails the END marker, or sits on the next line when the
|
|
78
|
+
// PEM carried its own trailing newline.
|
|
79
|
+
const endLine = block[block.length - 1];
|
|
80
|
+
const endMarker = /-----END [^-]*-----/.exec(endLine);
|
|
81
|
+
if (!endMarker) {
|
|
82
|
+
// The scan above ran to end-of-file looking for the END marker, so every
|
|
83
|
+
// later entry was swallowed with it. Say so explicitly: otherwise one
|
|
84
|
+
// truncated paste revokes trust for the whole file with no visible cause.
|
|
85
|
+
problems.push({
|
|
86
|
+
line: blockStart + 1,
|
|
87
|
+
message: 'public key block has no "-----END" marker — this block and every entry after it were ignored',
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
let keyId = endLine.slice(endMarker.index + endMarker[0].length).trim();
|
|
92
|
+
// Drop the identifier back off the END line, so the reassembled block is a
|
|
93
|
+
// bare PEM. Key parsing requires one block and nothing around it — trailing
|
|
94
|
+
// text would make this key unreadable and therefore untrusted.
|
|
95
|
+
block[block.length - 1] = endLine.slice(0, endMarker.index + endMarker[0].length);
|
|
96
|
+
// Only adopt the next line as the identifier when it is not plainly an entry
|
|
97
|
+
// of its own, or a bare PEM block would swallow the key that follows it.
|
|
98
|
+
if (!keyId && i + 1 < lines.length && !looksLikeKeyEntry(lines[i + 1])) {
|
|
99
|
+
i++;
|
|
100
|
+
keyId = lines[i].trim();
|
|
101
|
+
}
|
|
102
|
+
if (keyId) {
|
|
103
|
+
entries.push({ publicKey: block.join('\n'), keyId });
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
problems.push({
|
|
107
|
+
line: blockStart + 1,
|
|
108
|
+
message: 'public key block has no identifier after the "-----END" marker — entry ignored',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { entries, problems };
|
|
113
|
+
}
|
|
114
|
+
/** Cap on printed problems, so a mangled file cannot flood every verify run. */
|
|
115
|
+
const MAX_REPORTED_TRUSTED_KEY_PROBLEMS = 3;
|
|
116
|
+
/**
|
|
117
|
+
* Print unusable trusted-key entries to stderr, bounded.
|
|
118
|
+
*
|
|
119
|
+
* `loadTrustedKeys` runs on every `dossier verify`, so this stays silent unless
|
|
120
|
+
* the file is genuinely malformed — and a malformed trust file is precisely when
|
|
121
|
+
* the user needs to hear about it.
|
|
122
|
+
*/
|
|
123
|
+
function reportTrustedKeyProblems(problems, source) {
|
|
124
|
+
if (problems.length === 0) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const where = source ? ` in ${source}` : '';
|
|
128
|
+
console.warn(`Warning: ${problems.length} unusable trusted-key entry(s)${where}:`);
|
|
129
|
+
for (const problem of problems.slice(0, MAX_REPORTED_TRUSTED_KEY_PROBLEMS)) {
|
|
130
|
+
console.warn(` line ${problem.line}: ${problem.message}`);
|
|
131
|
+
}
|
|
132
|
+
if (problems.length > MAX_REPORTED_TRUSTED_KEY_PROBLEMS) {
|
|
133
|
+
console.warn(` ...and ${problems.length - MAX_REPORTED_TRUSTED_KEY_PROBLEMS} more`);
|
|
134
|
+
}
|
|
135
|
+
console.warn(' Those keys are NOT trusted. Re-add each with: ai-dossier keys add <public-key> <identifier>');
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Build the trusted-key map from the contents of a trusted-keys file.
|
|
139
|
+
*
|
|
140
|
+
* Keys are indexed under both the form they were written in and their normalized
|
|
141
|
+
* form, so a key registered in one encoding still matches a signature that
|
|
142
|
+
* carries another.
|
|
143
|
+
*
|
|
144
|
+
* @param source - Path named in warnings about unusable entries. Omit to stay silent.
|
|
145
|
+
*/
|
|
146
|
+
function trustedKeysFromContent(content, source) {
|
|
147
|
+
const keys = new Map();
|
|
148
|
+
const { entries, problems } = parseTrustedKeys(content);
|
|
149
|
+
for (const { publicKey, keyId } of entries) {
|
|
150
|
+
keys.set(publicKey, keyId);
|
|
151
|
+
const normalized = (0, signing_payload_1.normalizePublicKey)(publicKey);
|
|
152
|
+
if (normalized !== publicKey) {
|
|
153
|
+
keys.set(normalized, keyId);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (source) {
|
|
157
|
+
reportTrustedKeyProblems(problems, source);
|
|
158
|
+
}
|
|
159
|
+
return keys;
|
|
160
|
+
}
|
|
21
161
|
/**
|
|
22
162
|
* Load trusted keys from file
|
|
23
163
|
* Default location: ~/.dossier/trusted-keys.txt
|
|
@@ -25,37 +165,58 @@ const fs_1 = require("./utils/fs");
|
|
|
25
165
|
*/
|
|
26
166
|
function loadTrustedKeys(filePath) {
|
|
27
167
|
const keysPath = filePath || (0, node_path_1.join)((0, node_os_1.homedir)(), '.dossier', 'trusted-keys.txt');
|
|
28
|
-
const keys = new Map();
|
|
29
168
|
const content = (0, fs_1.readFileIfExists)(keysPath);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
169
|
+
// Pass the path so unusable entries name the file the user has to fix.
|
|
170
|
+
return content ? trustedKeysFromContent(content, keysPath) : new Map();
|
|
171
|
+
}
|
|
172
|
+
/** The algorithm `KmsVerifier` claims, whose key material is the KMS key ARN. */
|
|
173
|
+
const KMS_ALGORITHM = 'ECDSA-SHA-256';
|
|
174
|
+
function isKmsAlgorithm(algorithm) {
|
|
175
|
+
return algorithm === KMS_ALGORITHM;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Whether a string identifies an AWS KMS key, and so is a usable trust-list entry
|
|
179
|
+
* for a KMS-signed dossier.
|
|
180
|
+
*/
|
|
181
|
+
function isKmsKeyIdentifier(value) {
|
|
182
|
+
return /^arn:aws[a-z-]*:kms:/.test(value.trim());
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Resolve the identifier a signature is trusted under, or `undefined` if none of
|
|
186
|
+
* its key forms appear in the trust list.
|
|
187
|
+
*
|
|
188
|
+
* The signature and the trust list need not agree on encoding, so every form the
|
|
189
|
+
* *verifying* key carries is tried: the public key as written and normalized to
|
|
190
|
+
* raw base64. Every trust decision goes through here so "is it trusted" and "who
|
|
191
|
+
* is it" can never drift apart.
|
|
192
|
+
*
|
|
193
|
+
* Only the key material the verifier actually used is eligible, which is decided
|
|
194
|
+
* by the algorithm, not by which fields happen to be populated. `KmsVerifier`
|
|
195
|
+
* verifies against `key_id` and ignores `public_key` — which KMS signatures carry
|
|
196
|
+
* anyway — so for KMS only `key_id` can confer trust. Everything else verifies
|
|
197
|
+
* against `public_key`, so only that may.
|
|
198
|
+
*
|
|
199
|
+
* Letting a field the verifier ignored confer trust would hand it out for free:
|
|
200
|
+
* the trust list is keyed by public key and public keys are public, so an Ed25519
|
|
201
|
+
* dossier carrying `key_id: "<any trusted public key>"` would be trusted as that
|
|
202
|
+
* signer while verifying under an attacker's `public_key`.
|
|
203
|
+
*/
|
|
204
|
+
function findTrustedIdentifier(trustedKeys, signature) {
|
|
205
|
+
const candidates = isKmsAlgorithm(signature.algorithm)
|
|
206
|
+
? [signature.key_id]
|
|
207
|
+
: signature.public_key
|
|
208
|
+
? [signature.public_key, (0, signing_payload_1.normalizePublicKey)(signature.public_key)]
|
|
209
|
+
: [];
|
|
210
|
+
for (const candidate of candidates) {
|
|
211
|
+
if (!candidate) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const identifier = trustedKeys.get(candidate);
|
|
215
|
+
if (identifier !== undefined) {
|
|
216
|
+
return identifier;
|
|
53
217
|
}
|
|
54
218
|
}
|
|
55
|
-
|
|
56
|
-
console.error(`Warning: failed to parse trusted keys: ${err.message}`);
|
|
57
|
-
}
|
|
58
|
-
return keys;
|
|
219
|
+
return undefined;
|
|
59
220
|
}
|
|
60
221
|
/**
|
|
61
222
|
* Verify signature using Ed25519
|
package/dist/signature.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../src/signature.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;
|
|
1
|
+
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../src/signature.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,9 +35,27 @@ export declare function signatureCoverage(signature: {
|
|
|
35
35
|
* comparison against whatever the source actually contained.
|
|
36
36
|
*/
|
|
37
37
|
export declare function normalizePublicKey(publicKey: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Whether a public key is one this project can actually verify against.
|
|
40
|
+
*
|
|
41
|
+
* True for anything that normalizes to a raw 32-byte Ed25519 key (raw base64,
|
|
42
|
+
* SPKI PEM, base64 SPKI DER) and for minisign keys, which are passed through.
|
|
43
|
+
*
|
|
44
|
+
* `normalizePublicKey` deliberately returns uninterpretable input unchanged so
|
|
45
|
+
* exact-match comparison still works on the read path. On the *write* path —
|
|
46
|
+
* `dossier keys add` — that same leniency would silently store a typo, a
|
|
47
|
+
* truncated key, or a file path as if it were a trusted key, and the only
|
|
48
|
+
* symptom would be `dossier verify` reporting "not trusted" forever after. Use
|
|
49
|
+
* this to reject before writing.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isSupportedPublicKey(publicKey: string): boolean;
|
|
38
52
|
/**
|
|
39
53
|
* Build an SPKI PEM from any accepted public-key form, for use with node:crypto.
|
|
40
|
-
*
|
|
54
|
+
*
|
|
55
|
+
* Always rebuilt from the parsed key rather than passed through, so the bytes a
|
|
56
|
+
* signature is verified against are the bytes the trust check matched. Returns
|
|
57
|
+
* the input unchanged only when it denotes no Ed25519 key at all, leaving
|
|
58
|
+
* `crypto` to report the error.
|
|
41
59
|
*/
|
|
42
60
|
export declare function toSpkiPem(publicKey: string): string;
|
|
43
61
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signing-payload.d.ts","sourceRoot":"","sources":["../src/signing-payload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,kBAAkB,CAAC;AAE5D;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAAG,iBAAiB,CAE/F;
|
|
1
|
+
{"version":3,"file":"signing-payload.d.ts","sourceRoot":"","sources":["../src/signing-payload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,kBAAkB,CAAC;AAE5D;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAAG,iBAAiB,CAE/F;AAmFD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAG/D;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAYnD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAKrF;AAuBD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAGpF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,iBAAsC,GAC/C,MAAM,CAKR"}
|
package/dist/signing-payload.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
24
|
exports.signatureCoverage = signatureCoverage;
|
|
25
25
|
exports.normalizePublicKey = normalizePublicKey;
|
|
26
|
+
exports.isSupportedPublicKey = isSupportedPublicKey;
|
|
26
27
|
exports.toSpkiPem = toSpkiPem;
|
|
27
28
|
exports.publicKeysMatch = publicKeysMatch;
|
|
28
29
|
exports.canonicalizeFrontmatter = canonicalizeFrontmatter;
|
|
@@ -38,57 +39,116 @@ function signatureCoverage(signature) {
|
|
|
38
39
|
return signature?.covers === 'frontmatter+body' ? 'frontmatter+body' : 'body';
|
|
39
40
|
}
|
|
40
41
|
/**
|
|
41
|
-
*
|
|
42
|
+
* One PEM block and nothing else: no content before BEGIN or after END, matching
|
|
43
|
+
* labels, and only base64 in between.
|
|
42
44
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
45
|
+
* Anchoring both ends is load-bearing. OpenSSL — so `crypto.createPublicKey` —
|
|
46
|
+
* skips whatever precedes the BEGIN line and ignores whatever follows END, so a
|
|
47
|
+
* blob with padding around a real block still parses, as the key inside the
|
|
48
|
+
* block. If normalization read that same blob more loosely it could conclude the
|
|
49
|
+
* string denotes a *different* key than the one `createPublicKey` will hand to
|
|
50
|
+
* `verify`, and a trust check is exactly the place where those two answers must
|
|
51
|
+
* never differ.
|
|
46
52
|
*/
|
|
47
|
-
|
|
53
|
+
const SINGLE_PEM_BLOCK = /^-----BEGIN ([A-Za-z0-9 ]+)-----([A-Za-z0-9+/=\s]*)-----END \1-----$/;
|
|
54
|
+
/**
|
|
55
|
+
* Decode base64 only when the input is exactly what re-encoding those bytes gives.
|
|
56
|
+
*
|
|
57
|
+
* Node's base64 decoder is lenient by design: it silently discards characters it
|
|
58
|
+
* does not recognize and stops at the first `=` padding. That leniency is a
|
|
59
|
+
* key-substitution primitive here, because every raw Ed25519 key is 44 base64
|
|
60
|
+
* characters ending in `=` — meaning `<any key><arbitrary trailing text>` decodes
|
|
61
|
+
* to that key. Requiring a byte-exact round trip collapses each string onto at
|
|
62
|
+
* most one key.
|
|
63
|
+
*/
|
|
64
|
+
function decodeExactBase64(value) {
|
|
65
|
+
const decoded = Buffer.from(value, 'base64');
|
|
66
|
+
return decoded.toString('base64') === value ? decoded : undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The raw 32 key bytes a public-key string denotes, or `undefined` when it does
|
|
70
|
+
* not unambiguously denote an Ed25519 key.
|
|
71
|
+
*
|
|
72
|
+
* The single source of truth behind `normalizePublicKey`, `toSpkiPem` and
|
|
73
|
+
* `isSupportedPublicKey`, so the key a trust check matches on and the key a
|
|
74
|
+
* signature is verified against are derived from one parse and cannot drift.
|
|
75
|
+
*/
|
|
76
|
+
function ed25519RawKey(publicKey) {
|
|
48
77
|
const trimmed = publicKey.trim();
|
|
49
78
|
if (!trimmed) {
|
|
50
|
-
return
|
|
79
|
+
return undefined;
|
|
51
80
|
}
|
|
52
81
|
// minisign keys are a different encoding entirely (and carry a key id); nothing
|
|
53
82
|
// has produced them since 2025-11-18. Leave them untouched.
|
|
54
83
|
if (trimmed.startsWith('RWT')) {
|
|
55
|
-
return
|
|
84
|
+
return undefined;
|
|
56
85
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
86
|
+
// `-` is not in the base64 alphabet, so this cannot misread a raw key as PEM —
|
|
87
|
+
// whereas testing for the word "BEGIN" would, since B, E, G, I and N all are.
|
|
88
|
+
let base64Body;
|
|
89
|
+
if (trimmed.includes('-----')) {
|
|
90
|
+
const block = SINGLE_PEM_BLOCK.exec(trimmed);
|
|
91
|
+
if (!block) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
base64Body = block[2].replace(/\s+/g, '');
|
|
66
95
|
}
|
|
67
|
-
|
|
68
|
-
|
|
96
|
+
else {
|
|
97
|
+
base64Body = trimmed.replace(/\s+/g, '');
|
|
98
|
+
}
|
|
99
|
+
const decoded = decodeExactBase64(base64Body);
|
|
100
|
+
if (!decoded) {
|
|
101
|
+
return undefined;
|
|
69
102
|
}
|
|
70
103
|
if (decoded.length === ED25519_RAW_KEY_BYTES) {
|
|
71
|
-
return decoded
|
|
104
|
+
return decoded;
|
|
72
105
|
}
|
|
73
106
|
if (decoded.length === ED25519_SPKI_PREFIX.length + ED25519_RAW_KEY_BYTES &&
|
|
74
107
|
decoded.subarray(0, ED25519_SPKI_PREFIX.length).equals(ED25519_SPKI_PREFIX)) {
|
|
75
|
-
return decoded.subarray(ED25519_SPKI_PREFIX.length)
|
|
108
|
+
return decoded.subarray(ED25519_SPKI_PREFIX.length);
|
|
76
109
|
}
|
|
77
|
-
return
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Normalize an Ed25519 public key to raw 32-byte base64.
|
|
114
|
+
*
|
|
115
|
+
* Accepts SPKI PEM, raw base64, and base64 SPKI DER. Returns the input trimmed
|
|
116
|
+
* when it cannot be interpreted, so callers can still do an exact-match
|
|
117
|
+
* comparison against whatever the source actually contained.
|
|
118
|
+
*/
|
|
119
|
+
function normalizePublicKey(publicKey) {
|
|
120
|
+
const raw = ed25519RawKey(publicKey);
|
|
121
|
+
return raw ? raw.toString('base64') : publicKey.trim();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Whether a public key is one this project can actually verify against.
|
|
125
|
+
*
|
|
126
|
+
* True for anything that normalizes to a raw 32-byte Ed25519 key (raw base64,
|
|
127
|
+
* SPKI PEM, base64 SPKI DER) and for minisign keys, which are passed through.
|
|
128
|
+
*
|
|
129
|
+
* `normalizePublicKey` deliberately returns uninterpretable input unchanged so
|
|
130
|
+
* exact-match comparison still works on the read path. On the *write* path —
|
|
131
|
+
* `dossier keys add` — that same leniency would silently store a typo, a
|
|
132
|
+
* truncated key, or a file path as if it were a trusted key, and the only
|
|
133
|
+
* symptom would be `dossier verify` reporting "not trusted" forever after. Use
|
|
134
|
+
* this to reject before writing.
|
|
135
|
+
*/
|
|
136
|
+
function isSupportedPublicKey(publicKey) {
|
|
137
|
+
const trimmed = publicKey.trim();
|
|
138
|
+
return trimmed.startsWith('RWT') || ed25519RawKey(trimmed) !== undefined;
|
|
78
139
|
}
|
|
79
140
|
/**
|
|
80
141
|
* Build an SPKI PEM from any accepted public-key form, for use with node:crypto.
|
|
81
|
-
*
|
|
142
|
+
*
|
|
143
|
+
* Always rebuilt from the parsed key rather than passed through, so the bytes a
|
|
144
|
+
* signature is verified against are the bytes the trust check matched. Returns
|
|
145
|
+
* the input unchanged only when it denotes no Ed25519 key at all, leaving
|
|
146
|
+
* `crypto` to report the error.
|
|
82
147
|
*/
|
|
83
148
|
function toSpkiPem(publicKey) {
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
return
|
|
87
|
-
}
|
|
88
|
-
const raw = Buffer.from(normalizePublicKey(trimmed), 'base64');
|
|
89
|
-
if (raw.length !== ED25519_RAW_KEY_BYTES) {
|
|
90
|
-
// Not something we can rebuild; hand it back and let crypto report the error.
|
|
91
|
-
return trimmed;
|
|
149
|
+
const raw = ed25519RawKey(publicKey);
|
|
150
|
+
if (!raw) {
|
|
151
|
+
return publicKey.trim();
|
|
92
152
|
}
|
|
93
153
|
const der = Buffer.concat([ED25519_SPKI_PREFIX, raw]);
|
|
94
154
|
const body = der
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signing-payload.js","sourceRoot":"","sources":["../src/signing-payload.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;AAaH,8CAEC;
|
|
1
|
+
{"version":3,"file":"signing-payload.js","sourceRoot":"","sources":["../src/signing-payload.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED