@oesp/core 5.0.0 → 6.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 +21 -0
- package/dist/index.d.ts +52 -5
- package/dist/index.js +228 -145
- package/package.json +2 -10
- package/dist/types.d.ts +0 -41
- package/dist/types.js +0 -1
- package/dist/utils.d.ts +0 -6
- package/dist/utils.js +0 -92
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OESP Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
interface CryptoProvider {
|
|
2
|
+
sha256(bytes: Uint8Array): Uint8Array;
|
|
3
|
+
ed25519Sign(priv: Uint8Array, data: Uint8Array): Uint8Array;
|
|
4
|
+
ed25519Verify(pub: Uint8Array, data: Uint8Array, sig: Uint8Array): boolean;
|
|
5
|
+
x25519Seal(recipientPub: Uint8Array, sessionKey: Uint8Array): Uint8Array;
|
|
6
|
+
x25519Open(recipientPriv: Uint8Array, sealed: Uint8Array): Uint8Array;
|
|
7
|
+
aeadEncrypt(key: Uint8Array, plaintext: Uint8Array, aad: Uint8Array): {
|
|
8
|
+
iv: Uint8Array;
|
|
9
|
+
ct: Uint8Array;
|
|
10
|
+
tag?: Uint8Array;
|
|
11
|
+
};
|
|
12
|
+
aeadDecrypt(key: Uint8Array, iv: Uint8Array, ct: Uint8Array, aad: Uint8Array, tag?: Uint8Array): Uint8Array;
|
|
13
|
+
randomBytes(n: number): Uint8Array;
|
|
14
|
+
}
|
|
15
|
+
interface Identity {
|
|
16
|
+
ed25519Priv: Uint8Array;
|
|
17
|
+
ed25519Pub: Uint8Array;
|
|
18
|
+
x25519Priv: Uint8Array;
|
|
19
|
+
x25519Pub: Uint8Array;
|
|
20
|
+
}
|
|
21
|
+
interface KeyStore {
|
|
22
|
+
getOrCreateIdentity(): Promise<Identity>;
|
|
23
|
+
}
|
|
24
|
+
interface ReplayStore {
|
|
25
|
+
seen(fromDid: string, mid: string): Promise<boolean>;
|
|
26
|
+
markSeen(fromDid: string, mid: string): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
interface DecodedMessage {
|
|
29
|
+
mid: string;
|
|
30
|
+
sid: string;
|
|
31
|
+
ts: number;
|
|
32
|
+
exp: number;
|
|
33
|
+
fromDid: string;
|
|
34
|
+
toDid: string;
|
|
35
|
+
plaintext: Uint8Array;
|
|
36
|
+
}
|
|
37
|
+
interface VerifiedEnvelope {
|
|
38
|
+
envelope: Record<string, unknown>;
|
|
39
|
+
verified: boolean;
|
|
40
|
+
signerDid: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
declare function b64urlEncode(bytes: Uint8Array): string;
|
|
44
|
+
declare function b64urlDecode(s: string): Uint8Array;
|
|
45
|
+
declare function canonicalJsonBytes(obj: unknown): Uint8Array;
|
|
46
|
+
declare function base64Encode(bytes: Uint8Array): string;
|
|
47
|
+
declare function base64Decode(s: string): Uint8Array;
|
|
48
|
+
|
|
49
|
+
interface OESPClientDeps {
|
|
3
50
|
crypto: CryptoProvider;
|
|
4
51
|
keystore: KeyStore;
|
|
5
52
|
replay: ReplayStore;
|
|
6
53
|
}
|
|
7
|
-
|
|
54
|
+
declare class OESPClient {
|
|
8
55
|
private deps;
|
|
9
56
|
private identity?;
|
|
10
57
|
constructor(deps: OESPClientDeps);
|
|
@@ -22,5 +69,5 @@ export declare class OESPClient {
|
|
|
22
69
|
}): Promise<DecodedMessage>;
|
|
23
70
|
private ensureIdentity;
|
|
24
71
|
}
|
|
25
|
-
|
|
26
|
-
export {
|
|
72
|
+
|
|
73
|
+
export { type CryptoProvider, type DecodedMessage, type Identity, type KeyStore, OESPClient, type OESPClientDeps, type ReplayStore, type VerifiedEnvelope, b64urlDecode, b64urlEncode, base64Decode, base64Encode, canonicalJsonBytes };
|
package/dist/index.js
CHANGED
|
@@ -1,156 +1,239 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const dataToSign = concatBytes(toSignBase, b64urlDecode(String(env.ct)));
|
|
47
|
-
const sig = crypto.ed25519Sign(id.ed25519Priv, dataToSign);
|
|
48
|
-
env.sig = b64urlEncode(sig);
|
|
49
|
-
const tokenPayload = canonicalJsonBytes(env);
|
|
50
|
-
return `OESP1.${b64urlEncode(tokenPayload)}`;
|
|
51
|
-
}
|
|
52
|
-
async verify(token, opts) {
|
|
53
|
-
const env = parseToken(token);
|
|
54
|
-
const now = opts?.now ?? Math.floor(Date.now() / 1000);
|
|
55
|
-
const { crypto } = this.deps;
|
|
56
|
-
if (!opts?.allowExpired && env.exp < now) {
|
|
57
|
-
throw new Error('ExpiredError');
|
|
58
|
-
}
|
|
59
|
-
// DID match
|
|
60
|
-
const pubBytes = b64urlDecode(env.from.pub);
|
|
61
|
-
const derived = deriveDidWithSha(pubBytes, crypto.sha256);
|
|
62
|
-
if (derived !== env.from.did)
|
|
63
|
-
throw new Error('InvalidDIDError');
|
|
64
|
-
// Signature check: canonical(envelope without sig) + ct
|
|
65
|
-
const envDict = envToDict(env);
|
|
66
|
-
const toSignBase = canonicalJsonBytes(excludeKeys(envDict, ['sig']));
|
|
67
|
-
const ctBytes = b64urlDecode(env.ct);
|
|
68
|
-
const dataToSign = concatBytes(toSignBase, ctBytes);
|
|
69
|
-
const sigBytes = b64urlDecode(env.sig);
|
|
70
|
-
const ok = crypto.ed25519Verify(pubBytes, dataToSign, sigBytes);
|
|
71
|
-
if (!ok)
|
|
72
|
-
throw new Error('InvalidSignatureError');
|
|
73
|
-
const id = await this.ensureIdentity();
|
|
74
|
-
const seen = await this.deps.replay.seen(env.sid, env.mid);
|
|
75
|
-
if (seen)
|
|
76
|
-
throw new Error('ReplayError');
|
|
77
|
-
await this.deps.replay.markSeen(env.sid, env.mid);
|
|
78
|
-
return { envelope: envDict, verified: true, signerDid: env.from.did };
|
|
79
|
-
}
|
|
80
|
-
async unpack(token, opts) {
|
|
81
|
-
const env = parseToken(token);
|
|
82
|
-
await this.verify(token, opts);
|
|
83
|
-
const id = await this.ensureIdentity();
|
|
84
|
-
const ivBytes = b64urlDecode(env.iv);
|
|
85
|
-
const ctBytes = b64urlDecode(env.ct);
|
|
86
|
-
const ekBytes = b64urlDecode(env.ek);
|
|
87
|
-
const sessionKey = this.deps.crypto.x25519Open(id.x25519Priv, ekBytes);
|
|
88
|
-
const aad = canonicalJsonBytes(envFiltered(envToDict(env), ['ct', 'sig', 'iv']));
|
|
89
|
-
const plaintext = this.deps.crypto.aeadDecrypt(sessionKey, ivBytes, ctBytes, aad, env.tag ? b64urlDecode(env.tag) : undefined);
|
|
90
|
-
return {
|
|
91
|
-
mid: env.mid,
|
|
92
|
-
sid: env.sid,
|
|
93
|
-
ts: env.ts,
|
|
94
|
-
exp: env.exp,
|
|
95
|
-
fromDid: env.from.did,
|
|
96
|
-
toDid: env.to.did,
|
|
97
|
-
plaintext
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
async ensureIdentity() {
|
|
101
|
-
if (!this.identity)
|
|
102
|
-
this.identity = await this.deps.keystore.getOrCreateIdentity();
|
|
103
|
-
return this.identity;
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
function b64urlEncode(bytes) {
|
|
3
|
+
const base64 = base64Encode(bytes);
|
|
4
|
+
return base64.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
5
|
+
}
|
|
6
|
+
function b64urlDecode(s) {
|
|
7
|
+
const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
|
|
8
|
+
const base64 = s.replace(/-/g, "+").replace(/_/g, "/") + pad;
|
|
9
|
+
return base64Decode(base64);
|
|
10
|
+
}
|
|
11
|
+
function canonicalJsonBytes(obj) {
|
|
12
|
+
const json = canonicalStringify(obj);
|
|
13
|
+
return new TextEncoder().encode(json);
|
|
14
|
+
}
|
|
15
|
+
function canonicalStringify(obj) {
|
|
16
|
+
if (obj === null || typeof obj !== "object") {
|
|
17
|
+
return JSON.stringify(obj);
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(obj)) {
|
|
20
|
+
const arr = obj.map((v) => JSON.parse(canonicalStringify(v)));
|
|
21
|
+
return JSON.stringify(arr);
|
|
22
|
+
}
|
|
23
|
+
const entries = Object.entries(obj).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
24
|
+
const out = {};
|
|
25
|
+
for (const [k, v] of entries) {
|
|
26
|
+
out[k] = JSON.parse(canonicalStringify(v));
|
|
27
|
+
}
|
|
28
|
+
return JSON.stringify(out);
|
|
29
|
+
}
|
|
30
|
+
function deriveDidWithSha(ed25519Pub, sha256) {
|
|
31
|
+
const sha = sha256(ed25519Pub);
|
|
32
|
+
const b32 = base32encode(sha).toLowerCase().replace(/=+$/g, "");
|
|
33
|
+
return `oesp:did:${b32}`;
|
|
34
|
+
}
|
|
35
|
+
function base32encode(bytes) {
|
|
36
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
37
|
+
let bits = 0;
|
|
38
|
+
let value = 0;
|
|
39
|
+
let output = "";
|
|
40
|
+
for (const byte of bytes) {
|
|
41
|
+
value = value << 8 | byte;
|
|
42
|
+
bits += 8;
|
|
43
|
+
while (bits >= 5) {
|
|
44
|
+
output += alphabet[value >>> bits - 5 & 31];
|
|
45
|
+
bits -= 5;
|
|
104
46
|
}
|
|
47
|
+
}
|
|
48
|
+
if (bits > 0) {
|
|
49
|
+
output += alphabet[value << 5 - bits & 31];
|
|
50
|
+
}
|
|
51
|
+
while (output.length % 8 !== 0) output += "=";
|
|
52
|
+
return output;
|
|
53
|
+
}
|
|
54
|
+
var base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
55
|
+
function base64Encode(bytes) {
|
|
56
|
+
let output = "";
|
|
57
|
+
let i = 0;
|
|
58
|
+
while (i < bytes.length) {
|
|
59
|
+
const a = bytes[i++] ?? 0;
|
|
60
|
+
const b = bytes[i++] ?? 0;
|
|
61
|
+
const c = bytes[i++] ?? 0;
|
|
62
|
+
const triplet = a << 16 | b << 8 | c;
|
|
63
|
+
output += base64Alphabet[triplet >> 18 & 63];
|
|
64
|
+
output += base64Alphabet[triplet >> 12 & 63];
|
|
65
|
+
output += i - 2 < bytes.length ? base64Alphabet[triplet >> 6 & 63] : "=";
|
|
66
|
+
output += i - 1 < bytes.length ? base64Alphabet[triplet & 63] : "=";
|
|
67
|
+
}
|
|
68
|
+
return output;
|
|
69
|
+
}
|
|
70
|
+
function base64Decode(s) {
|
|
71
|
+
s = s.replace(/\s/g, "");
|
|
72
|
+
const len = s.length;
|
|
73
|
+
if (len % 4 !== 0) throw new Error("Invalid base64");
|
|
74
|
+
const output = [];
|
|
75
|
+
let i = 0;
|
|
76
|
+
while (i < len) {
|
|
77
|
+
const a = base64Alphabet.indexOf(s[i++]);
|
|
78
|
+
const b = base64Alphabet.indexOf(s[i++]);
|
|
79
|
+
const c = base64Alphabet.indexOf(s[i++]);
|
|
80
|
+
const d = base64Alphabet.indexOf(s[i++]);
|
|
81
|
+
const triplet = a << 18 | b << 12 | (c & 63) << 6 | d & 63;
|
|
82
|
+
output.push(triplet >> 16 & 255);
|
|
83
|
+
if (s[i - 2] !== "=") output.push(triplet >> 8 & 255);
|
|
84
|
+
if (s[i - 1] !== "=") output.push(triplet & 255);
|
|
85
|
+
}
|
|
86
|
+
return new Uint8Array(output);
|
|
105
87
|
}
|
|
88
|
+
|
|
89
|
+
// src/index.ts
|
|
90
|
+
var OESPClient = class {
|
|
91
|
+
constructor(deps) {
|
|
92
|
+
this.deps = deps;
|
|
93
|
+
}
|
|
94
|
+
async getDid() {
|
|
95
|
+
const id = await this.ensureIdentity();
|
|
96
|
+
return deriveDidWithSha(id.ed25519Pub, this.deps.crypto.sha256);
|
|
97
|
+
}
|
|
98
|
+
async pack(toDid, body, opts) {
|
|
99
|
+
const { crypto } = this.deps;
|
|
100
|
+
const ttlSec = opts?.ttlSec ?? 600;
|
|
101
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
102
|
+
const exp = now + ttlSec;
|
|
103
|
+
const mid = b64urlEncode(crypto.randomBytes(12));
|
|
104
|
+
const id = await this.ensureIdentity();
|
|
105
|
+
const sid = deriveDidWithSha(id.ed25519Pub, crypto.sha256);
|
|
106
|
+
const edPubB64 = b64urlEncode(id.ed25519Pub);
|
|
107
|
+
const sessionKey = crypto.randomBytes(32);
|
|
108
|
+
const sealed = crypto.x25519Seal(id.x25519Pub, sessionKey);
|
|
109
|
+
const env = {
|
|
110
|
+
v: 1,
|
|
111
|
+
typ: "oesp.envelope",
|
|
112
|
+
mid,
|
|
113
|
+
sid,
|
|
114
|
+
ts: now,
|
|
115
|
+
exp,
|
|
116
|
+
from: { did: sid, pub: edPubB64 },
|
|
117
|
+
to: { did: toDid },
|
|
118
|
+
enc: "CHACHA20-POLY1305",
|
|
119
|
+
kex: "X25519",
|
|
120
|
+
ek: b64urlEncode(sealed),
|
|
121
|
+
iv: "",
|
|
122
|
+
ct: "",
|
|
123
|
+
sig_alg: "Ed25519",
|
|
124
|
+
sig: ""
|
|
125
|
+
};
|
|
126
|
+
const aad = canonicalJsonBytes(envFiltered(env, ["ct", "sig", "iv"]));
|
|
127
|
+
const bodyBytes = body instanceof Uint8Array ? body : canonicalJsonBytes(body);
|
|
128
|
+
const { iv, ct, tag } = crypto.aeadEncrypt(sessionKey, bodyBytes, aad);
|
|
129
|
+
env.iv = b64urlEncode(iv);
|
|
130
|
+
env.ct = b64urlEncode(ct);
|
|
131
|
+
if (tag) env["tag"] = b64urlEncode(tag);
|
|
132
|
+
const toSignBase = canonicalJsonBytes(envFiltered(env, ["sig"]));
|
|
133
|
+
const dataToSign = concatBytes(toSignBase, b64urlDecode(String(env.ct)));
|
|
134
|
+
const sig = crypto.ed25519Sign(id.ed25519Priv, dataToSign);
|
|
135
|
+
env.sig = b64urlEncode(sig);
|
|
136
|
+
const tokenPayload = canonicalJsonBytes(env);
|
|
137
|
+
return `OESP1.${b64urlEncode(tokenPayload)}`;
|
|
138
|
+
}
|
|
139
|
+
async verify(token, opts) {
|
|
140
|
+
const env = parseToken(token);
|
|
141
|
+
const now = opts?.now ?? Math.floor(Date.now() / 1e3);
|
|
142
|
+
const { crypto } = this.deps;
|
|
143
|
+
if (!opts?.allowExpired && env.exp < now) {
|
|
144
|
+
throw new Error("ExpiredError");
|
|
145
|
+
}
|
|
146
|
+
const pubBytes = b64urlDecode(env.from.pub);
|
|
147
|
+
const derived = deriveDidWithSha(pubBytes, crypto.sha256);
|
|
148
|
+
if (derived !== env.from.did) throw new Error("InvalidDIDError");
|
|
149
|
+
const envDict = envToDict(env);
|
|
150
|
+
const toSignBase = canonicalJsonBytes(excludeKeys(envDict, ["sig"]));
|
|
151
|
+
const ctBytes = b64urlDecode(env.ct);
|
|
152
|
+
const dataToSign = concatBytes(toSignBase, ctBytes);
|
|
153
|
+
const sigBytes = b64urlDecode(env.sig);
|
|
154
|
+
const ok = crypto.ed25519Verify(pubBytes, dataToSign, sigBytes);
|
|
155
|
+
if (!ok) throw new Error("InvalidSignatureError");
|
|
156
|
+
const id = await this.ensureIdentity();
|
|
157
|
+
const seen = await this.deps.replay.seen(env.sid, env.mid);
|
|
158
|
+
if (seen) throw new Error("ReplayError");
|
|
159
|
+
await this.deps.replay.markSeen(env.sid, env.mid);
|
|
160
|
+
return { envelope: envDict, verified: true, signerDid: env.from.did };
|
|
161
|
+
}
|
|
162
|
+
async unpack(token, opts) {
|
|
163
|
+
const env = parseToken(token);
|
|
164
|
+
await this.verify(token, opts);
|
|
165
|
+
const id = await this.ensureIdentity();
|
|
166
|
+
const ivBytes = b64urlDecode(env.iv);
|
|
167
|
+
const ctBytes = b64urlDecode(env.ct);
|
|
168
|
+
const ekBytes = b64urlDecode(env.ek);
|
|
169
|
+
const sessionKey = this.deps.crypto.x25519Open(id.x25519Priv, ekBytes);
|
|
170
|
+
const aad = canonicalJsonBytes(envFiltered(envToDict(env), ["ct", "sig", "iv"]));
|
|
171
|
+
const plaintext = this.deps.crypto.aeadDecrypt(sessionKey, ivBytes, ctBytes, aad, env.tag ? b64urlDecode(env.tag) : void 0);
|
|
172
|
+
return {
|
|
173
|
+
mid: env.mid,
|
|
174
|
+
sid: env.sid,
|
|
175
|
+
ts: env.ts,
|
|
176
|
+
exp: env.exp,
|
|
177
|
+
fromDid: env.from.did,
|
|
178
|
+
toDid: env.to.did,
|
|
179
|
+
plaintext
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async ensureIdentity() {
|
|
183
|
+
if (!this.identity) this.identity = await this.deps.keystore.getOrCreateIdentity();
|
|
184
|
+
return this.identity;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
106
187
|
function parseToken(token) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return d;
|
|
188
|
+
if (!token.startsWith("OESP1.")) throw new Error("InvalidFormatError");
|
|
189
|
+
const payloadB64 = token.slice("OESP1.".length);
|
|
190
|
+
const jsonBytes = b64urlDecode(payloadB64);
|
|
191
|
+
const json = new TextDecoder().decode(jsonBytes);
|
|
192
|
+
const d = JSON.parse(json);
|
|
193
|
+
return d;
|
|
114
194
|
}
|
|
115
|
-
function envFiltered(env,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return e;
|
|
195
|
+
function envFiltered(env, excludeKeys2) {
|
|
196
|
+
const e = envToDict(env);
|
|
197
|
+
for (const k of excludeKeys2) delete e[k];
|
|
198
|
+
return e;
|
|
120
199
|
}
|
|
121
200
|
function envToDict(env) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return d;
|
|
201
|
+
const d = {
|
|
202
|
+
v: env.v,
|
|
203
|
+
typ: env.typ,
|
|
204
|
+
mid: env.mid,
|
|
205
|
+
sid: env.sid,
|
|
206
|
+
ts: env.ts,
|
|
207
|
+
exp: env.exp,
|
|
208
|
+
from: env.from,
|
|
209
|
+
to: env.to,
|
|
210
|
+
enc: env.enc,
|
|
211
|
+
kex: env.kex,
|
|
212
|
+
ek: env.ek,
|
|
213
|
+
iv: env.iv,
|
|
214
|
+
ct: env.ct,
|
|
215
|
+
sig_alg: env.sig_alg,
|
|
216
|
+
sig: env.sig
|
|
217
|
+
};
|
|
218
|
+
if (env.tag) d["tag"] = env.tag;
|
|
219
|
+
return d;
|
|
142
220
|
}
|
|
143
221
|
function excludeKeys(obj, keys) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return out;
|
|
222
|
+
const out = { ...obj };
|
|
223
|
+
for (const k of keys) delete out[k];
|
|
224
|
+
return out;
|
|
148
225
|
}
|
|
149
226
|
function concatBytes(a, b) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
227
|
+
const out = new Uint8Array(a.length + b.length);
|
|
228
|
+
out.set(a, 0);
|
|
229
|
+
out.set(b, a.length);
|
|
230
|
+
return out;
|
|
154
231
|
}
|
|
155
|
-
export
|
|
156
|
-
|
|
232
|
+
export {
|
|
233
|
+
OESPClient,
|
|
234
|
+
b64urlDecode,
|
|
235
|
+
b64urlEncode,
|
|
236
|
+
base64Decode,
|
|
237
|
+
base64Encode,
|
|
238
|
+
canonicalJsonBytes
|
|
239
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oesp/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "OESP core SDK (agnostique)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,14 +8,6 @@
|
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"module": "dist/index.js",
|
|
10
10
|
"types": "dist/index.d.ts",
|
|
11
|
-
"exports": {
|
|
12
|
-
".": {
|
|
13
|
-
"types": "./dist/index.d.ts",
|
|
14
|
-
"import": "./dist/index.js",
|
|
15
|
-
"require": "./dist/index.cjs"
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
"sideEffects": false,
|
|
19
11
|
"files": [
|
|
20
12
|
"dist"
|
|
21
13
|
],
|
|
@@ -23,7 +15,7 @@
|
|
|
23
15
|
"access": "public"
|
|
24
16
|
},
|
|
25
17
|
"scripts": {
|
|
26
|
-
"build": "
|
|
18
|
+
"build": "tsup src/index.ts --dts --format esm,cjs",
|
|
27
19
|
"test": "vitest run",
|
|
28
20
|
"lint": "tsc -p tsconfig.json --noEmit"
|
|
29
21
|
},
|
package/dist/types.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export interface CryptoProvider {
|
|
2
|
-
sha256(bytes: Uint8Array): Uint8Array;
|
|
3
|
-
ed25519Sign(priv: Uint8Array, data: Uint8Array): Uint8Array;
|
|
4
|
-
ed25519Verify(pub: Uint8Array, data: Uint8Array, sig: Uint8Array): boolean;
|
|
5
|
-
x25519Seal(recipientPub: Uint8Array, sessionKey: Uint8Array): Uint8Array;
|
|
6
|
-
x25519Open(recipientPriv: Uint8Array, sealed: Uint8Array): Uint8Array;
|
|
7
|
-
aeadEncrypt(key: Uint8Array, plaintext: Uint8Array, aad: Uint8Array): {
|
|
8
|
-
iv: Uint8Array;
|
|
9
|
-
ct: Uint8Array;
|
|
10
|
-
tag?: Uint8Array;
|
|
11
|
-
};
|
|
12
|
-
aeadDecrypt(key: Uint8Array, iv: Uint8Array, ct: Uint8Array, aad: Uint8Array, tag?: Uint8Array): Uint8Array;
|
|
13
|
-
randomBytes(n: number): Uint8Array;
|
|
14
|
-
}
|
|
15
|
-
export interface Identity {
|
|
16
|
-
ed25519Priv: Uint8Array;
|
|
17
|
-
ed25519Pub: Uint8Array;
|
|
18
|
-
x25519Priv: Uint8Array;
|
|
19
|
-
x25519Pub: Uint8Array;
|
|
20
|
-
}
|
|
21
|
-
export interface KeyStore {
|
|
22
|
-
getOrCreateIdentity(): Promise<Identity>;
|
|
23
|
-
}
|
|
24
|
-
export interface ReplayStore {
|
|
25
|
-
seen(fromDid: string, mid: string): Promise<boolean>;
|
|
26
|
-
markSeen(fromDid: string, mid: string): Promise<void>;
|
|
27
|
-
}
|
|
28
|
-
export interface DecodedMessage {
|
|
29
|
-
mid: string;
|
|
30
|
-
sid: string;
|
|
31
|
-
ts: number;
|
|
32
|
-
exp: number;
|
|
33
|
-
fromDid: string;
|
|
34
|
-
toDid: string;
|
|
35
|
-
plaintext: Uint8Array;
|
|
36
|
-
}
|
|
37
|
-
export interface VerifiedEnvelope {
|
|
38
|
-
envelope: Record<string, unknown>;
|
|
39
|
-
verified: boolean;
|
|
40
|
-
signerDid: string;
|
|
41
|
-
}
|
package/dist/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/utils.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export declare function b64urlEncode(bytes: Uint8Array): string;
|
|
2
|
-
export declare function b64urlDecode(s: string): Uint8Array;
|
|
3
|
-
export declare function canonicalJsonBytes(obj: unknown): Uint8Array;
|
|
4
|
-
export declare function deriveDidWithSha(ed25519Pub: Uint8Array, sha256: (bytes: Uint8Array) => Uint8Array): string;
|
|
5
|
-
export declare function base64Encode(bytes: Uint8Array): string;
|
|
6
|
-
export declare function base64Decode(s: string): Uint8Array;
|
package/dist/utils.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
export function b64urlEncode(bytes) {
|
|
2
|
-
const base64 = base64Encode(bytes);
|
|
3
|
-
return base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
4
|
-
}
|
|
5
|
-
export function b64urlDecode(s) {
|
|
6
|
-
const pad = s.length % 4 === 0 ? '' : '='.repeat(4 - (s.length % 4));
|
|
7
|
-
const base64 = s.replace(/-/g, '+').replace(/_/g, '/') + pad;
|
|
8
|
-
return base64Decode(base64);
|
|
9
|
-
}
|
|
10
|
-
export function canonicalJsonBytes(obj) {
|
|
11
|
-
const json = canonicalStringify(obj);
|
|
12
|
-
return new TextEncoder().encode(json);
|
|
13
|
-
}
|
|
14
|
-
function canonicalStringify(obj) {
|
|
15
|
-
if (obj === null || typeof obj !== 'object') {
|
|
16
|
-
return JSON.stringify(obj);
|
|
17
|
-
}
|
|
18
|
-
if (Array.isArray(obj)) {
|
|
19
|
-
const arr = obj.map((v) => JSON.parse(canonicalStringify(v)));
|
|
20
|
-
return JSON.stringify(arr);
|
|
21
|
-
}
|
|
22
|
-
const entries = Object.entries(obj).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
23
|
-
const out = {};
|
|
24
|
-
for (const [k, v] of entries) {
|
|
25
|
-
out[k] = JSON.parse(canonicalStringify(v));
|
|
26
|
-
}
|
|
27
|
-
return JSON.stringify(out);
|
|
28
|
-
}
|
|
29
|
-
export function deriveDidWithSha(ed25519Pub, sha256) {
|
|
30
|
-
const sha = sha256(ed25519Pub);
|
|
31
|
-
const b32 = base32encode(sha).toLowerCase().replace(/=+$/g, '');
|
|
32
|
-
return `oesp:did:${b32}`;
|
|
33
|
-
}
|
|
34
|
-
function base32encode(bytes) {
|
|
35
|
-
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
36
|
-
let bits = 0;
|
|
37
|
-
let value = 0;
|
|
38
|
-
let output = '';
|
|
39
|
-
for (const byte of bytes) {
|
|
40
|
-
value = (value << 8) | byte;
|
|
41
|
-
bits += 8;
|
|
42
|
-
while (bits >= 5) {
|
|
43
|
-
output += alphabet[(value >>> (bits - 5)) & 31];
|
|
44
|
-
bits -= 5;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (bits > 0) {
|
|
48
|
-
output += alphabet[(value << (5 - bits)) & 31];
|
|
49
|
-
}
|
|
50
|
-
// pad with '=' to multiple of 8 chars (we'll strip later)
|
|
51
|
-
while (output.length % 8 !== 0)
|
|
52
|
-
output += '=';
|
|
53
|
-
return output;
|
|
54
|
-
}
|
|
55
|
-
const base64Alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
56
|
-
export function base64Encode(bytes) {
|
|
57
|
-
let output = '';
|
|
58
|
-
let i = 0;
|
|
59
|
-
while (i < bytes.length) {
|
|
60
|
-
const a = bytes[i++] ?? 0;
|
|
61
|
-
const b = bytes[i++] ?? 0;
|
|
62
|
-
const c = bytes[i++] ?? 0;
|
|
63
|
-
const triplet = (a << 16) | (b << 8) | c;
|
|
64
|
-
output += base64Alphabet[(triplet >> 18) & 63];
|
|
65
|
-
output += base64Alphabet[(triplet >> 12) & 63];
|
|
66
|
-
output += i - 2 < bytes.length ? base64Alphabet[(triplet >> 6) & 63] : '=';
|
|
67
|
-
output += i - 1 < bytes.length ? base64Alphabet[triplet & 63] : '=';
|
|
68
|
-
}
|
|
69
|
-
return output;
|
|
70
|
-
}
|
|
71
|
-
export function base64Decode(s) {
|
|
72
|
-
// Remove whitespace
|
|
73
|
-
s = s.replace(/\s/g, '');
|
|
74
|
-
const len = s.length;
|
|
75
|
-
if (len % 4 !== 0)
|
|
76
|
-
throw new Error('Invalid base64');
|
|
77
|
-
const output = [];
|
|
78
|
-
let i = 0;
|
|
79
|
-
while (i < len) {
|
|
80
|
-
const a = base64Alphabet.indexOf(s[i++]);
|
|
81
|
-
const b = base64Alphabet.indexOf(s[i++]);
|
|
82
|
-
const c = base64Alphabet.indexOf(s[i++]);
|
|
83
|
-
const d = base64Alphabet.indexOf(s[i++]);
|
|
84
|
-
const triplet = (a << 18) | (b << 12) | ((c & 63) << 6) | (d & 63);
|
|
85
|
-
output.push((triplet >> 16) & 255);
|
|
86
|
-
if (s[i - 2] !== '=')
|
|
87
|
-
output.push((triplet >> 8) & 255);
|
|
88
|
-
if (s[i - 1] !== '=')
|
|
89
|
-
output.push(triplet & 255);
|
|
90
|
-
}
|
|
91
|
-
return new Uint8Array(output);
|
|
92
|
-
}
|