-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkey.js
259 lines (224 loc) · 7.25 KB
/
key.js
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
"use strict";
import * as CTK from './cryptokey.js';
import * as Can from './canon.js';
import * as Coze from './coze.js';
import * as Alg from './alg.js';
import {
isEmpty
} from './coze.js';
export {
NewKey,
Correct,
Valid,
Thumbprint,
Revoke,
IsRevoked,
// RecalcX,
TmbCanon,
}
/**
@typedef {import('./typedef.js').Tmb} Tmb
@typedef {import('./typedef.js').Alg} Alg
@typedef {import('./typedef.js').Use} Use
@typedef {import('./typedef.js').Sig} Sig
@typedef {import('./typedef.js').Key} Key
*/
// Coze key Thumbprint Canons.
const TmbCanon = ["alg", "x"];
/**
NewKey returns a new Coze key.
If no alg is given, the returned key will be an 'ES256' key.
@param {Alg} [alg=ES256] - Alg of the key to generate. (e.g. "ES256")
@returns {Key}
*/
async function NewKey(alg) {
if (isEmpty(alg)) {
alg = Alg.Algs.ES256;
}
if (Alg.Genus(alg) == Alg.GenAlgs.ECDSA) {
var keyPair = await CTK.CryptoKey.New(alg);
} else {
throw new Error("Coze.NewKey: only ECDSA algs are currently supported.");
}
let k = await CTK.CryptoKey.ToCozeKey(keyPair.privateKey);
k.iat = Math.floor(Date.now() / 1000); // To get Unix from js, divide by 1000.
k.tmb = await Thumbprint(k);
k.kid = "My Cyphr.me Key.";
return k;
}
/**
Thumbprint calculates and returns a B64 Coze key thumbprint. Fails on empty
'alg' or 'x'.
@param {Key} cozeKey
@returns {Tmb}
@throws {error}
*/
async function Thumbprint(cozeKey) {
if (isEmpty(cozeKey.alg) || isEmpty(cozeKey.x)) {
throw new Error("Coze.Thumbprint: alg or x is empty.");
}
return Can.CanonicalHash64(cozeKey, await Alg.HashAlg(cozeKey.alg), TmbCanon);
};
/**
Valid returns true only for a valid private Coze key.
@param {Key} privateCozeKey Private Coze key.
@returns {boolean}
*/
async function Valid(privateCozeKey) {
if (isEmpty(privateCozeKey.d)) {
console.error("Coze key missing `d`");
return false;
}
try {
let msg = `7AtyaCHO2BAG06z0W1tOQlZFWbhxGgqej4k9-HWP3DE-zshRbrE-69DIfgY704_FDYez7h_rEI1WQVKhv5Hd5Q`;
let sig = await Coze.SignPay(msg, privateCozeKey);
return Coze.VerifyPay(msg, privateCozeKey, sig);
} catch (e) {
//console.debug("Valid error: " + e);
return false;
}
}
/**
Correct checks for the correct construction of a Coze key, but may return
true on cryptographically invalid public keys. Key must have `alg` and at
least one of `tmb`, `x`, and `d`. Using input information, if it is possible
to definitively know the given key is incorrect, Correct returns false, but
if it's plausible it's correct, Correct returns true. Correct answers the
question: "Is the given Coze key reasonable using the information provided?".
Correct is useful for sanity checking public keys without signed messages,
sanity checking `tmb` only keys, and validating private keys. Use function
"Verify" instead for verifying public keys when a signed message is
available. Correct is considered an advanced function. Please understand it
thoroughly before use.
Correct:
1. Checks the length of `x` and/or `tmb` against `alg`.
2. If `x` and `tmb` are present, verifies correct `tmb`.
3. If `d` is present, verifies correct `tmb` and `x` if present, and verifies
the key by verifying a generated signature.
@param {Key} ck
@returns {boolean}
*/
async function Correct(ck) {
if (typeof ck !== "object") {
console.error("Correct: CozeKey must be passed in as an object.");
return false;
}
if (isEmpty(ck.alg)) {
console.error("Correct: Alg must be set");
return false;
}
let p = Alg.Params(ck.alg);
let isTmbEmpty = isEmpty(ck.tmb);
let isXEmpty = isEmpty(ck.x);
let isDEmpty = isEmpty(ck.d);
if (isTmbEmpty && isXEmpty && isDEmpty) {
console.error("Correct: At least one of [x, tmb, d] must be set");
return false;
}
// tmb only key
if (isXEmpty && isDEmpty) {
if (isTmbEmpty || ck.tmb.length !== p.HashSizeB64) {
console.error("Correct: Incorrect `tmb` size: ", ck.tmb.length);
return false;
}
return true;
}
// d is not set
if (!isXEmpty && ck.x.length !== p.XSizeB64) {
console.error("Correct: Incorrect x size: ", ck.x.length);
return false;
}
// We currently do not support recalculating `x`, as subtle does not provide
// the necessary API for computing the points from the private component.
// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/subtle
//
// See RecalcX docs below
//
// If d and (x and/or tmb) is given, recompute from d and compare:
// let x = RecalcX(ck);
// If tmb is set, recompute and compare.
if (!isTmbEmpty && !isXEmpty) {
let t = await Thumbprint(ck);
if (ck.tmb !== t) {
console.error("Correct: Incorrect given `tmb`: ", ck.tmb);
return false;
}
}
// If private key, validate by signing and verifying.
// `x` must also be populated, for cryptokey, since we do not have RecalcX().
if (!isDEmpty && !isXEmpty) {
let cryptoKey = await CTK.CryptoKey.FromCozeKey(ck);
let mldBuffer = await Coze.SToArrayBuffer("Test Signing")
let sig = await CTK.CryptoKey.SignBuffer(cryptoKey, mldBuffer);
let pubKey = await CTK.CryptoKey.FromCozeKey(ck, true);
let result = await CTK.CryptoKey.VerifyArrayBuffer(ck.alg, pubKey, mldBuffer, sig);
if (!result) {
console.error("Correct: private key invalid.");
return false;
}
}
return true;
};
// TODO Support RecalcX if crypto.subtle provides necessary API for computing
// https://stackoverflow.com/questions/72151096/how-to-derive-public-key-from-private-key-using-webcryptoapi/72153942#72153942
//
// scalar/jacobian/affinity from private component.
// Alternatively, use noble.
// function RecalcX(ck) {
// let x;
// switch (ck.alg) {
// case "ES256":
// case "ES384":
// case "ES512":
// break;
// default:
// x = null;
// }
// return x;
// }
/**
Revoke generates a self revoke message and sets the input key as revoked.
'rvk' will be set on given cozeKey.
@param {Key} cozeKey Private Coze key.
@param {string} [msg] Optional, human readable non programmatic reason for revoking the key.
@returns {Coze} Signed revoke Coze.
@throws {error} Fails if cryptoKeyPrivate is nil or invalid.
*/
async function Revoke(cozeKey, msg) {
if (isEmpty(cozeKey)) {
throw new Error("CozeKey.Revoke: Private key not set. Cannot sign message");
}
var coze = {};
coze.pay = {};
if (!isEmpty(msg)) { // Optional revoke message.
coze.pay.msg = msg;
}
coze.pay.rvk = Math.round((Date.now() / 1000)); // Javascript's Date converted to Unix time.
// SignCoze does not allow revoked keys to sign messages. Temporarily remove
// key.revoke and then set back afterward, otherwise set key with new revoke.
let prevRvk = cozeKey.rvk;
delete cozeKey.rvk;
coze = await Coze.Sign(coze, cozeKey);
if (prevRvk !== undefined) {
cozeKey.rvk = prevRvk;
} else {
cozeKey.rvk = coze.pay.rvk;
}
return coze
};
/**
IsRevoked returns true if a key or a coze is marked as revoked. `rvk` should
be an integer Unix timestamp, however this function also checks for the
string "true" as well as the bool `true`.
Messages self-revoking keys must have `rvk` with an integer value greater
than 0.
@param {Key|Coze} cozeKey Coze key or coze object.
@param {string} [msg] Optional reason for revoking the key.
@returns {boolean}
*/
function IsRevoked(cozeKey) {
if (isEmpty(cozeKey.rvk) || !(parseInt(cozeKey.rvk) > 0)) {
return false;
}
return true;
};