@openentity/sdk 0.4.1 → 0.5.1

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 CHANGED
@@ -19,6 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
-
23
- The specification (`spec/`) and the skill (`skill/`) are dedicated to the
24
- public domain under CC0-1.0; see the LICENSE file in each directory.
package/README.md CHANGED
@@ -10,4 +10,6 @@ Most agents want the CLI instead: `npx openentity@latest adopt`. Start at https:
10
10
 
11
11
  Shared core: `adopt(kp, {name, client})`, `trust(eid)` (a `TrustReport` from registry reads only, every signature verified locally, cached 5 minutes, `ok: false` on any failure), the issuer helpers `signAttestation`, `signRevocation`, `buildDidDocument`, `buildOpenEntityJson`, and in Node `import { Identity } from "@openentity/sdk/node"` for `Identity.loadOrCreate()` (OS keychain, else `~/.openentity/identity.key` at 0600).
12
12
 
13
+ HTTP message signatures (RFC 9421, Web Bot Auth profile): `signRequest({method, url, body, privateKey, did})` returns `Signature-Input`, `Signature` and, for a non-empty body, `Content-Digest` (ed25519, `keyid` = the signer's did:key, covers `@method`, `@target-uri`, `content-digest`; `created`, `expires`, `nonce`). `verifyRequest({method, url, headers, body})` returns `{did, keyid, created, expires, nonce}` or throws `HttpSigError` with a `.code` (`missing_headers`, `unknown_alg`, `bad_keyid`, `digest_mismatch`, `created_in_future`, `created_too_old`, `expired`, `lifetime_too_long`, `missing_nonce`, `bad_signature`). Nonce replay tracking is yours. Shared vectors: `test-vectors/http-signature.json`.
14
+
13
15
  MIT licensed. Source: https://github.com/moltbot-den/openentity
@@ -1946,6 +1946,83 @@ async function readTrust(eid, base, now, get, resolver) {
1946
1946
  }
1947
1947
  return report;
1948
1948
  }
1949
+ var HttpSigError = class extends Error {
1950
+ code;
1951
+ constructor(code) {
1952
+ super(code);
1953
+ this.code = code;
1954
+ this.name = "HttpSigError";
1955
+ }
1956
+ };
1957
+ var HTTPSIG_SKEW = 5;
1958
+ var contentDigest = (body) => `sha-256=:${b64(sha256Sync(body))}:`;
1959
+ function httpSigBase(method, url, components, digest, params) {
1960
+ const value = { "@method": method.toUpperCase(), "@target-uri": url, "content-digest": digest };
1961
+ return utf8([...components.map((c) => `"${c}": ${value[c]}`), `"@signature-params": ${params}`].join("\n"));
1962
+ }
1963
+ async function signRequest(o) {
1964
+ const seed = o.privateKey instanceof Uint8Array ? o.privateKey : unb64url(o.privateKey.privateJwk.d);
1965
+ const created = o.created ?? Math.floor(Date.now() / 1e3);
1966
+ const nonce = o.nonce ?? b64url(crypto.getRandomValues(new Uint8Array(16)));
1967
+ const out = {};
1968
+ const comps = ["@method", "@target-uri"];
1969
+ if (o.body?.length) {
1970
+ out["Content-Digest"] = contentDigest(o.body);
1971
+ comps.push("content-digest");
1972
+ }
1973
+ const params = `(${comps.map((c) => `"${c}"`).join(" ")});created=${created};expires=${created + (o.expiresIn ?? 60)};nonce="${nonce}";keyid="${o.did}";alg="ed25519"`;
1974
+ const sig = ed25519.sign(httpSigBase(o.method, o.url, comps, out["Content-Digest"], params), seed);
1975
+ out["Signature-Input"] = `sig1=${params}`;
1976
+ out["Signature"] = `sig1=:${b64(sig)}:`;
1977
+ return out;
1978
+ }
1979
+ async function verifyRequest(o) {
1980
+ const fail = (code) => {
1981
+ throw new HttpSigError(code);
1982
+ };
1983
+ const h = Object.fromEntries(Object.entries(o.headers).map(([k, v]) => [k.toLowerCase(), v]));
1984
+ const maxAge = o.maxAge ?? 300;
1985
+ const body = o.body ?? new Uint8Array();
1986
+ if (!h["signature-input"] || !h["signature"]) fail("missing_headers");
1987
+ const m = /(?:^|,)\s*sig1=(\(([^)]*)\)((?:;[a-z]+=(?:\d+|"[^"]*"))*))/.exec(h["signature-input"]);
1988
+ const s = /(?:^|,)\s*sig1=:([A-Za-z0-9+/]+={0,2}):/.exec(h["signature"]);
1989
+ if (!m || !s) return fail("missing_headers");
1990
+ const params = m[1];
1991
+ const comps = [...m[2].matchAll(/"([^"]*)"/g)].map((x) => x[1]);
1992
+ const p = {};
1993
+ for (const [, k, v] of m[3].matchAll(/;([a-z]+)=(\d+|"[^"]*")/g)) p[k] = v.startsWith('"') ? v.slice(1, -1) : Number(v);
1994
+ if (p.alg !== "ed25519") fail("unknown_alg");
1995
+ const keyid = String(p.keyid ?? "");
1996
+ let pub;
1997
+ try {
1998
+ pub = publicKeyFromDidKey(keyid);
1999
+ if (didKeyFromPublicKey(pub) !== keyid) throw 0;
2000
+ } catch {
2001
+ return fail("bad_keyid");
2002
+ }
2003
+ if (typeof p.created !== "number" || typeof p.expires !== "number") fail("missing_headers");
2004
+ const created = p.created, expires = p.expires;
2005
+ const nonce = String(p.nonce ?? "");
2006
+ if (nonce.length < 22) fail("missing_nonce");
2007
+ if (comps[0] !== "@method" || comps[1] !== "@target-uri" || comps.length > 3 || comps.length === 3 && comps[2] !== "content-digest") fail("missing_headers");
2008
+ let digest;
2009
+ if (body.length || comps.includes("content-digest")) {
2010
+ digest = h["content-digest"];
2011
+ if (!comps.includes("content-digest") || digest !== contentDigest(body)) fail("digest_mismatch");
2012
+ }
2013
+ const now = o.now ?? Math.floor(Date.now() / 1e3);
2014
+ if (created > now + HTTPSIG_SKEW) fail("created_in_future");
2015
+ if (now - created > maxAge) fail("created_too_old");
2016
+ if (expires < now) fail("expired");
2017
+ if (expires - created > maxAge) fail("lifetime_too_long");
2018
+ let ok = false;
2019
+ try {
2020
+ ok = ed25519.verify(unb64(s[1]), httpSigBase(o.method, o.url, comps, digest, params), pub);
2021
+ } catch {
2022
+ }
2023
+ if (!ok) fail("bad_signature");
2024
+ return { did: keyid, keyid, created, expires, nonce };
2025
+ }
1949
2026
 
1950
2027
  export {
1951
2028
  SPEC,
@@ -2008,5 +2085,9 @@ export {
2008
2085
  adopt,
2009
2086
  clearTrustCache,
2010
2087
  didWebResolver,
2011
- trust
2088
+ trust,
2089
+ HttpSigError,
2090
+ httpSigBase,
2091
+ signRequest,
2092
+ verifyRequest
2012
2093
  };
package/dist/index.d.ts CHANGED
@@ -359,4 +359,35 @@ export declare function trust(eid: string, o?: {
359
359
  fetchJson?: FetchJson;
360
360
  resolver?: Resolver;
361
361
  }): Promise<TrustReport>;
362
+ export declare class HttpSigError extends Error {
363
+ code: string;
364
+ constructor(code: string);
365
+ }
366
+ export declare function httpSigBase(method: string, url: string, components: string[], digest: string | undefined, params: string): Uint8Array;
367
+ /** Sign a request. `privateKey` is a KeyPair or a 32-byte Ed25519 seed. Returns Signature-Input, Signature and, for a non-empty body, Content-Digest. */
368
+ export declare function signRequest(o: {
369
+ method: string;
370
+ url: string;
371
+ body?: Uint8Array;
372
+ privateKey: KeyPair | Uint8Array;
373
+ did: string;
374
+ created?: number;
375
+ expiresIn?: number;
376
+ nonce?: string;
377
+ }): Promise<Record<string, string>>;
378
+ /** Verify a request signed by signRequest. Throws HttpSigError with `.code`. Nonce replay tracking is the caller's job. */
379
+ export declare function verifyRequest(o: {
380
+ method: string;
381
+ url: string;
382
+ headers: Record<string, string>;
383
+ body?: Uint8Array;
384
+ now?: number;
385
+ maxAge?: number;
386
+ }): Promise<{
387
+ did: string;
388
+ keyid: string;
389
+ created: number;
390
+ expires: number;
391
+ nonce: string;
392
+ }>;
362
393
  export {};
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  DECLARATION_SHA256,
5
5
  DECLARATION_TEXT,
6
6
  DECLARATION_VERSION,
7
+ HttpSigError,
7
8
  SPEC,
8
9
  a2aExtension,
9
10
  adopt,
@@ -27,6 +28,7 @@ import {
27
28
  eip712Digest,
28
29
  generateKeyPair,
29
30
  hashData,
31
+ httpSigBase,
30
32
  inclusionProof,
31
33
  keyPairFromJwk,
32
34
  keyPairFromSeed,
@@ -49,6 +51,7 @@ import {
49
51
  signNote,
50
52
  signObject,
51
53
  signProof,
54
+ signRequest,
52
55
  signRevocation,
53
56
  successorDocument,
54
57
  trust,
@@ -59,14 +62,16 @@ import {
59
62
  verifyNote,
60
63
  verifyObject,
61
64
  verifyPresentation,
62
- verifyProof
63
- } from "./chunk-IWY5EQIP.js";
65
+ verifyProof,
66
+ verifyRequest
67
+ } from "./chunk-BYNZITFU.js";
64
68
  export {
65
69
  A2A_EXTENSION_URI,
66
70
  CLIENTS,
67
71
  DECLARATION_SHA256,
68
72
  DECLARATION_TEXT,
69
73
  DECLARATION_VERSION,
74
+ HttpSigError,
70
75
  SPEC,
71
76
  a2aExtension,
72
77
  adopt,
@@ -90,6 +95,7 @@ export {
90
95
  eip712Digest,
91
96
  generateKeyPair,
92
97
  hashData,
98
+ httpSigBase,
93
99
  inclusionProof,
94
100
  keyPairFromJwk,
95
101
  keyPairFromSeed,
@@ -112,6 +118,7 @@ export {
112
118
  signNote,
113
119
  signObject,
114
120
  signProof,
121
+ signRequest,
115
122
  signRevocation,
116
123
  successorDocument,
117
124
  trust,
@@ -122,5 +129,6 @@ export {
122
129
  verifyNote,
123
130
  verifyObject,
124
131
  verifyPresentation,
125
- verifyProof
132
+ verifyProof,
133
+ verifyRequest
126
134
  };
package/dist/node.d.ts CHANGED
@@ -6,10 +6,11 @@ export interface Identity {
6
6
  seed: Uint8Array;
7
7
  source: "keychain" | "file";
8
8
  }
9
- /** The entity's key from the OS keychain, else `~/.openentity/identity.key` (mode 0600). A fresh key goes to the keychain
10
- * when one answers, otherwise to the file. OPENENTITY_KEYCHAIN=off uses the file only. Same key file as the Python SDK. */
9
+ /** The existing identity, or null. Never creates one. */
10
+ declare function load(): Promise<Identity | null>;
11
11
  declare function loadOrCreate(): Promise<Identity>;
12
12
  export declare const Identity: {
13
+ load: typeof load;
13
14
  loadOrCreate: typeof loadOrCreate;
14
15
  };
15
16
  export {};
package/dist/node.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  keyPairFromSeed
3
- } from "./chunk-IWY5EQIP.js";
3
+ } from "./chunk-BYNZITFU.js";
4
4
 
5
5
  // src/node.ts
6
6
  import { execFileSync } from "node:child_process";
@@ -22,15 +22,19 @@ var keychainSet = (seedHex) => !keychainOn() ? false : process.platform === "dar
22
22
  var identityFile = () => join(process.env.OPENENTITY_HOME || join(homedir(), ".openentity"), "identity.key");
23
23
  var unhex = (h) => Uint8Array.from(h.match(/../g), (x) => parseInt(x, 16));
24
24
  var hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
25
- async function loadOrCreate() {
26
- const make = async (seedHex2, source) => {
27
- const seed = unhex(seedHex2), kp = await keyPairFromSeed(seed);
28
- return { kp, did: kp.did, seed, source };
29
- };
30
- const file = identityFile();
25
+ var make = async (seedHex, source) => {
26
+ const seed = unhex(seedHex), kp = await keyPairFromSeed(seed);
27
+ return { kp, did: kp.did, seed, source };
28
+ };
29
+ async function load() {
31
30
  const fromKeychain = keychainGet();
32
31
  if (fromKeychain) return make(fromKeychain, "keychain");
33
- if (existsSync(file)) return make(readFileSync(file, "utf8").trim(), "file");
32
+ return existsSync(identityFile()) ? make(readFileSync(identityFile(), "utf8").trim(), "file") : null;
33
+ }
34
+ async function loadOrCreate() {
35
+ const found = await load();
36
+ if (found) return found;
37
+ const file = identityFile();
34
38
  const seedHex = hex(crypto.getRandomValues(new Uint8Array(32)));
35
39
  if (keychainSet(seedHex)) return make(seedHex, "keychain");
36
40
  mkdirSync(dirname(file), { recursive: true, mode: 448 });
@@ -45,7 +49,7 @@ async function loadOrCreate() {
45
49
  chmodSync(file, 384);
46
50
  return make(seedHex, "file");
47
51
  }
48
- var Identity = { loadOrCreate };
52
+ var Identity = { load, loadOrCreate };
49
53
  export {
50
54
  Identity,
51
55
  identityFile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openentity/sdk",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "Reference TypeScript SDK for the Open Entity Identity Standard (OEIS-1)",
5
5
  "license": "MIT",
6
6
  "type": "module",