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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 | ;; ported-from: src/selftest.ts @ v1.1.0
;;
;; Dev-only self-test: determinism, binding, certs, wraps, profile clamping.
(ns ardegazu.id.selftest
(:require [ardegazu.id.bridge-host :as host]
[ardegazu.id.crypto :as c]
[ardegazu.id.identity :as identity]
[ardegazu.id.profile :as profile]
[ardegazu.id.protocol :as protocol]
[ardegazu.id.xkey :as xkey]
[shadow.cljs.modern :refer (js-await)]))
(defn- check [cond what]
(when-not cond
(throw (js/Error. (str "id-kit self-test FAILED: " what)))))
(defn- identity-part []
;; identity: seed shape, determinism, binding assertion domain separation
(let [seed (identity/new-seed)]
(check (.test protocol/SEED-RE seed) "seed shape")
(js-await [id1 (identity/from-seed-impl seed)]
(js-await [id2 (identity/from-seed-impl seed)]
(check (identical? (unchecked-get id1 "publicKeyB64")
(unchecked-get id2 "publicKeyB64"))
"identity deterministic from seed")
(js-await [fp (identity/fingerprint-of (unchecked-get id1 "publicKeyB64"))]
(check (and (identical? 64 (.-length (unchecked-get fp "hex")))
(pos? (.-length (unchecked-get fp "emoji"))))
"fingerprint shape")
(js-await [sig (.assert ^js id1 "selftest/v1" "room" "peer")]
(js-await [v1 (identity/verify-assertion "selftest/v1" "room" "peer"
(unchecked-get id1 "publicKeyB64") sig)]
(check (some? v1) "assertion verifies")
(js-await [v2 (identity/verify-assertion "selftest/v1" "room" "other"
(unchecked-get id1 "publicKeyB64") sig)]
(check (nil? v2) "bound to peerId")
(js-await [v3 (identity/verify-assertion "selftest/v2" "room" "peer"
(unchecked-get id1 "publicKeyB64") sig)]
(check (nil? v3) "bound to salt")
(js-obj "seed" seed "id1" id1))))))))))
(defn- xkey-part [seed id1]
;; xkey: deterministic, salt-separated, certs verify, wraps roundtrip
(js-await [xa (xkey/derive-suite-x-key-pair seed)]
(js-await [xb (xkey/derive-suite-x-key-pair seed)]
(check (identical? (unchecked-get xa "pubB64") (unchecked-get xb "pubB64"))
"x25519 deterministic from seed")
(js-await [x-other (xkey/derive-x-key-pair seed "someapp/v1")]
(check (not (identical? (unchecked-get x-other "pubB64") (unchecked-get xa "pubB64")))
"x25519 salt-separated")
(js-await [cert (xkey/sign-x-cert id1 xkey/SUITE-SALT (unchecked-get xa "pubB64"))]
(js-await [cv (xkey/verify-x-cert xkey/SUITE-SALT (unchecked-get id1 "publicKeyB64")
(unchecked-get xa "pubB64") cert)]
(check cv "x cert verifies")
(js-await [cv2 (xkey/verify-x-cert "someapp/v1" (unchecked-get id1 "publicKeyB64")
(unchecked-get xa "pubB64") cert)]
(check (not cv2) "x cert bound to salt")
(let [payload (c/utf8 "hello friend")]
(js-await [w (xkey/wrap-to xkey/SUITE-SALT "ctx-1"
(unchecked-get id1 "publicKeyB64")
(unchecked-get xa "pubB64") payload)]
(js-await [opened (xkey/unwrap xkey/SUITE-SALT "ctx-1" w xa)]
(check (and (some? opened)
(identical? (.decode (js/TextDecoder.) opened) "hello friend"))
"wrap roundtrip")
(js-await [wrong-ctx (xkey/unwrap xkey/SUITE-SALT "ctx-2" w xa)]
(check (nil? wrong-ctx) "wrap bound to ctx")
(js-await [stranger (xkey/derive-suite-x-key-pair (identity/new-seed))]
(js-await [stolen (xkey/unwrap xkey/SUITE-SALT "ctx-1" w stranger)]
(check (nil? stolen) "wrap sealed to recipient")
nil)))))))))))))
(defn- sync-part [seed id1]
;; profile: hue determinism + clamping of hostile input
(check (identical? (profile/hue-of-pub (unchecked-get id1 "publicKeyB64"))
(profile/hue-of-pub (unchecked-get id1 "publicKeyB64")))
"hue deterministic")
(let [hostile (js/Object.assign (js/Object.create (js-obj "evil" 1))
(js-obj "name" (.repeat "x" 99) "hue" 720.5 "glyph" "🦊🦊"))
clamped (profile/clamp-profile hostile)]
(check (and (identical? 32 (.-length (unchecked-get clamped "name")))
(nil? (unchecked-get clamped "hue"))
(identical? (unchecked-get clamped "glyph") "🦊"))
"profile clamped")
(check (not (js-in "evil" clamped)) "no prototype pollution"))
;; record sanitation
(check (nil? (host/sanitize-record (js-obj "seed" "short"))) "bad seed rejected")
(let [rec (host/sanitize-record (js-obj "seed" seed "name" 42 "hue" "red" "createdAt" -5))]
(check (and (some? rec)
(identical? (unchecked-get rec "name") "")
(nil? (unchecked-get rec "hue"))
(> (unchecked-get rec "createdAt") 0))
"record normalized"))
nil)
(defn run-id-kit-self-test []
(js-await [ids (identity-part)]
(let [seed (unchecked-get ids "seed")
id1 (unchecked-get ids "id1")]
(js-await [_ (xkey-part seed id1)]
(sync-part seed id1)
(js/console.info "✅ id-kit self-test passed")
js/undefined))))
|