@auths-dev/sdk 0.0.1 → 0.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/Cargo.toml +45 -0
- package/README.md +163 -4
- package/__test__/client.spec.ts +78 -0
- package/__test__/exports.spec.ts +57 -0
- package/__test__/integration.spec.ts +407 -0
- package/__test__/policy.spec.ts +202 -0
- package/__test__/verify.spec.ts +88 -0
- package/build.rs +5 -0
- package/index.d.ts +259 -0
- package/index.js +622 -1
- package/lib/artifacts.ts +124 -0
- package/lib/attestations.ts +126 -0
- package/lib/audit.ts +189 -0
- package/lib/client.ts +293 -0
- package/lib/commits.ts +70 -0
- package/lib/devices.ts +178 -0
- package/lib/errors.ts +306 -0
- package/lib/identity.ts +280 -0
- package/lib/index.ts +125 -0
- package/lib/native.ts +255 -0
- package/lib/org.ts +235 -0
- package/lib/pairing.ts +271 -0
- package/lib/policy.ts +669 -0
- package/lib/signing.ts +204 -0
- package/lib/trust.ts +152 -0
- package/lib/types.ts +179 -0
- package/lib/verify.ts +241 -0
- package/lib/witness.ts +91 -0
- package/npm/darwin-arm64/README.md +3 -0
- package/npm/darwin-arm64/package.json +23 -0
- package/npm/linux-arm64-gnu/README.md +3 -0
- package/npm/linux-arm64-gnu/package.json +26 -0
- package/npm/linux-x64-gnu/README.md +3 -0
- package/npm/linux-x64-gnu/package.json +26 -0
- package/npm/win32-arm64-msvc/README.md +3 -0
- package/npm/win32-arm64-msvc/package.json +23 -0
- package/npm/win32-x64-msvc/README.md +3 -0
- package/npm/win32-x64-msvc/package.json +23 -0
- package/package.json +51 -16
- package/src/artifact.rs +217 -0
- package/src/attestation_query.rs +104 -0
- package/src/audit.rs +128 -0
- package/src/commit_sign.rs +63 -0
- package/src/device.rs +212 -0
- package/src/diagnostics.rs +106 -0
- package/src/error.rs +5 -0
- package/src/helpers.rs +60 -0
- package/src/identity.rs +467 -0
- package/src/lib.rs +26 -0
- package/src/org.rs +430 -0
- package/src/pairing.rs +454 -0
- package/src/policy.rs +147 -0
- package/src/sign.rs +215 -0
- package/src/trust.rs +189 -0
- package/src/types.rs +205 -0
- package/src/verify.rs +447 -0
- package/src/witness.rs +138 -0
- package/tsconfig.json +19 -0
- package/typedoc.json +18 -0
- package/vitest.config.ts +12 -0
package/src/verify.rs
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
use auths_verifier::core::{
|
|
2
|
+
Attestation, Capability, MAX_ATTESTATION_JSON_SIZE, MAX_JSON_BATCH_SIZE,
|
|
3
|
+
};
|
|
4
|
+
use auths_verifier::error::AuthsErrorInfo;
|
|
5
|
+
use auths_verifier::types::DeviceDID;
|
|
6
|
+
use auths_verifier::verify::{
|
|
7
|
+
verify_at_time as rust_verify_at_time, verify_chain as rust_verify_chain,
|
|
8
|
+
verify_chain_with_capability as rust_verify_chain_with_capability,
|
|
9
|
+
verify_chain_with_witnesses as rust_verify_chain_with_witnesses,
|
|
10
|
+
verify_device_authorization as rust_verify_device_authorization,
|
|
11
|
+
verify_with_capability as rust_verify_with_capability, verify_with_keys,
|
|
12
|
+
};
|
|
13
|
+
use auths_verifier::witness::{WitnessReceipt, WitnessVerifyConfig};
|
|
14
|
+
use chrono::{DateTime, Utc};
|
|
15
|
+
use napi_derive::napi;
|
|
16
|
+
|
|
17
|
+
use crate::error::format_error;
|
|
18
|
+
use crate::types::{NapiVerificationReport, NapiVerificationResult};
|
|
19
|
+
|
|
20
|
+
fn decode_pk_hex(hex_str: &str, label: &str) -> napi::Result<Vec<u8>> {
|
|
21
|
+
let bytes = hex::decode(hex_str)
|
|
22
|
+
.map_err(|e| format_error("AUTHS_INVALID_INPUT", format!("Invalid {label} hex: {e}")))?;
|
|
23
|
+
if bytes.len() != 32 {
|
|
24
|
+
return Err(format_error(
|
|
25
|
+
"AUTHS_INVALID_INPUT",
|
|
26
|
+
format!(
|
|
27
|
+
"Invalid {label} length: expected 32 bytes (64 hex chars), got {}",
|
|
28
|
+
bytes.len()
|
|
29
|
+
),
|
|
30
|
+
));
|
|
31
|
+
}
|
|
32
|
+
Ok(bytes)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn parse_attestations(jsons: &[String]) -> napi::Result<Vec<Attestation>> {
|
|
36
|
+
jsons
|
|
37
|
+
.iter()
|
|
38
|
+
.enumerate()
|
|
39
|
+
.map(|(i, json)| {
|
|
40
|
+
serde_json::from_str(json).map_err(|e| {
|
|
41
|
+
format_error(
|
|
42
|
+
"AUTHS_SERIALIZATION_ERROR",
|
|
43
|
+
format!("Failed to parse attestation {i}: {e}"),
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
.collect()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fn check_batch_size(jsons: &[String]) -> napi::Result<()> {
|
|
51
|
+
let total: usize = jsons.iter().map(|s| s.len()).sum();
|
|
52
|
+
if total > MAX_JSON_BATCH_SIZE {
|
|
53
|
+
return Err(format_error(
|
|
54
|
+
"AUTHS_INVALID_INPUT",
|
|
55
|
+
format!("Total attestation JSON too large: {total} bytes, max {MAX_JSON_BATCH_SIZE}"),
|
|
56
|
+
));
|
|
57
|
+
}
|
|
58
|
+
Ok(())
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#[napi]
|
|
62
|
+
pub async fn verify_attestation(
|
|
63
|
+
attestation_json: String,
|
|
64
|
+
issuer_pk_hex: String,
|
|
65
|
+
) -> napi::Result<NapiVerificationResult> {
|
|
66
|
+
if attestation_json.len() > MAX_ATTESTATION_JSON_SIZE {
|
|
67
|
+
return Err(format_error(
|
|
68
|
+
"AUTHS_INVALID_INPUT",
|
|
69
|
+
format!(
|
|
70
|
+
"Attestation JSON too large: {} bytes, max {}",
|
|
71
|
+
attestation_json.len(),
|
|
72
|
+
MAX_ATTESTATION_JSON_SIZE
|
|
73
|
+
),
|
|
74
|
+
));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let issuer_pk_bytes = decode_pk_hex(&issuer_pk_hex, "issuer public key")?;
|
|
78
|
+
|
|
79
|
+
let att: Attestation = match serde_json::from_str(&attestation_json) {
|
|
80
|
+
Ok(att) => att,
|
|
81
|
+
Err(e) => {
|
|
82
|
+
return Ok(NapiVerificationResult {
|
|
83
|
+
valid: false,
|
|
84
|
+
error: Some(format!("Failed to parse attestation JSON: {e}")),
|
|
85
|
+
error_code: Some("AUTHS_SERIALIZATION_ERROR".to_string()),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
match verify_with_keys(&att, &issuer_pk_bytes).await {
|
|
91
|
+
Ok(_) => Ok(NapiVerificationResult {
|
|
92
|
+
valid: true,
|
|
93
|
+
error: None,
|
|
94
|
+
error_code: None,
|
|
95
|
+
}),
|
|
96
|
+
Err(e) => Ok(NapiVerificationResult {
|
|
97
|
+
valid: false,
|
|
98
|
+
error_code: Some(e.error_code().to_string()),
|
|
99
|
+
error: Some(e.to_string()),
|
|
100
|
+
}),
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#[napi]
|
|
105
|
+
pub async fn verify_chain(
|
|
106
|
+
attestations_json: Vec<String>,
|
|
107
|
+
root_pk_hex: String,
|
|
108
|
+
) -> napi::Result<NapiVerificationReport> {
|
|
109
|
+
check_batch_size(&attestations_json)?;
|
|
110
|
+
let root_pk_bytes = decode_pk_hex(&root_pk_hex, "root public key")?;
|
|
111
|
+
let attestations = parse_attestations(&attestations_json)?;
|
|
112
|
+
|
|
113
|
+
match rust_verify_chain(&attestations, &root_pk_bytes).await {
|
|
114
|
+
Ok(report) => Ok(report.into()),
|
|
115
|
+
Err(e) => Err(format_error(
|
|
116
|
+
e.error_code(),
|
|
117
|
+
format!("Chain verification failed: {e}"),
|
|
118
|
+
)),
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#[napi]
|
|
123
|
+
pub async fn verify_device_authorization(
|
|
124
|
+
identity_did: String,
|
|
125
|
+
device_did: String,
|
|
126
|
+
attestations_json: Vec<String>,
|
|
127
|
+
identity_pk_hex: String,
|
|
128
|
+
) -> napi::Result<NapiVerificationReport> {
|
|
129
|
+
check_batch_size(&attestations_json)?;
|
|
130
|
+
let identity_pk_bytes = decode_pk_hex(&identity_pk_hex, "identity public key")?;
|
|
131
|
+
let attestations = parse_attestations(&attestations_json)?;
|
|
132
|
+
let device =
|
|
133
|
+
DeviceDID::parse(&device_did).map_err(|e| format_error("AUTHS_INVALID_INPUT", e))?;
|
|
134
|
+
|
|
135
|
+
match rust_verify_device_authorization(
|
|
136
|
+
&identity_did,
|
|
137
|
+
&device,
|
|
138
|
+
&attestations,
|
|
139
|
+
&identity_pk_bytes,
|
|
140
|
+
)
|
|
141
|
+
.await
|
|
142
|
+
{
|
|
143
|
+
Ok(report) => Ok(report.into()),
|
|
144
|
+
Err(e) => Err(format_error(
|
|
145
|
+
e.error_code(),
|
|
146
|
+
format!("Device authorization verification failed: {e}"),
|
|
147
|
+
)),
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#[napi]
|
|
152
|
+
pub async fn verify_attestation_with_capability(
|
|
153
|
+
attestation_json: String,
|
|
154
|
+
issuer_pk_hex: String,
|
|
155
|
+
required_capability: String,
|
|
156
|
+
) -> napi::Result<NapiVerificationResult> {
|
|
157
|
+
if attestation_json.len() > MAX_ATTESTATION_JSON_SIZE {
|
|
158
|
+
return Err(format_error(
|
|
159
|
+
"AUTHS_INVALID_INPUT",
|
|
160
|
+
format!(
|
|
161
|
+
"Attestation JSON too large: {} bytes, max {}",
|
|
162
|
+
attestation_json.len(),
|
|
163
|
+
MAX_ATTESTATION_JSON_SIZE
|
|
164
|
+
),
|
|
165
|
+
));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let issuer_pk_bytes = decode_pk_hex(&issuer_pk_hex, "issuer public key")?;
|
|
169
|
+
|
|
170
|
+
let att: Attestation = match serde_json::from_str(&attestation_json) {
|
|
171
|
+
Ok(att) => att,
|
|
172
|
+
Err(e) => {
|
|
173
|
+
return Ok(NapiVerificationResult {
|
|
174
|
+
valid: false,
|
|
175
|
+
error: Some(format!("Failed to parse attestation JSON: {e}")),
|
|
176
|
+
error_code: Some("AUTHS_SERIALIZATION_ERROR".to_string()),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
let cap = Capability::parse(&required_capability).map_err(|e| {
|
|
182
|
+
format_error(
|
|
183
|
+
"AUTHS_INVALID_INPUT",
|
|
184
|
+
format!("Invalid capability '{required_capability}': {e}"),
|
|
185
|
+
)
|
|
186
|
+
})?;
|
|
187
|
+
|
|
188
|
+
match rust_verify_with_capability(&att, &cap, &issuer_pk_bytes).await {
|
|
189
|
+
Ok(_) => Ok(NapiVerificationResult {
|
|
190
|
+
valid: true,
|
|
191
|
+
error: None,
|
|
192
|
+
error_code: None,
|
|
193
|
+
}),
|
|
194
|
+
Err(e) => Ok(NapiVerificationResult {
|
|
195
|
+
valid: false,
|
|
196
|
+
error_code: Some(e.error_code().to_string()),
|
|
197
|
+
error: Some(e.to_string()),
|
|
198
|
+
}),
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
#[napi]
|
|
203
|
+
pub async fn verify_chain_with_capability(
|
|
204
|
+
attestations_json: Vec<String>,
|
|
205
|
+
root_pk_hex: String,
|
|
206
|
+
required_capability: String,
|
|
207
|
+
) -> napi::Result<NapiVerificationReport> {
|
|
208
|
+
check_batch_size(&attestations_json)?;
|
|
209
|
+
let root_pk_bytes = decode_pk_hex(&root_pk_hex, "root public key")?;
|
|
210
|
+
let attestations = parse_attestations(&attestations_json)?;
|
|
211
|
+
|
|
212
|
+
let cap = Capability::parse(&required_capability).map_err(|e| {
|
|
213
|
+
format_error(
|
|
214
|
+
"AUTHS_INVALID_INPUT",
|
|
215
|
+
format!("Invalid capability '{required_capability}': {e}"),
|
|
216
|
+
)
|
|
217
|
+
})?;
|
|
218
|
+
|
|
219
|
+
match rust_verify_chain_with_capability(&attestations, &cap, &root_pk_bytes).await {
|
|
220
|
+
Ok(report) => Ok(report.into()),
|
|
221
|
+
Err(e) => Err(format_error(
|
|
222
|
+
e.error_code(),
|
|
223
|
+
format!("Chain verification with capability failed: {e}"),
|
|
224
|
+
)),
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fn parse_rfc3339_timestamp(at_rfc3339: &str) -> napi::Result<DateTime<Utc>> {
|
|
229
|
+
let at: DateTime<Utc> = at_rfc3339.parse::<DateTime<Utc>>().map_err(|_| {
|
|
230
|
+
if at_rfc3339.contains(' ') && !at_rfc3339.contains('T') {
|
|
231
|
+
format_error(
|
|
232
|
+
"AUTHS_INVALID_INPUT",
|
|
233
|
+
format!(
|
|
234
|
+
"Expected RFC 3339 format like '2024-06-15T00:00:00Z', got '{at_rfc3339}'. \
|
|
235
|
+
Hint: use 'T' between date and time, and append 'Z' or a UTC offset."
|
|
236
|
+
),
|
|
237
|
+
)
|
|
238
|
+
} else {
|
|
239
|
+
format_error(
|
|
240
|
+
"AUTHS_INVALID_INPUT",
|
|
241
|
+
format!(
|
|
242
|
+
"Expected RFC 3339 format like '2024-06-15T00:00:00Z', got '{at_rfc3339}'."
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
})?;
|
|
247
|
+
|
|
248
|
+
#[allow(clippy::disallowed_methods)] // Presentation boundary
|
|
249
|
+
let now = Utc::now();
|
|
250
|
+
let skew_tolerance = chrono::Duration::seconds(60);
|
|
251
|
+
if at > now + skew_tolerance {
|
|
252
|
+
return Err(format_error(
|
|
253
|
+
"AUTHS_INVALID_INPUT",
|
|
254
|
+
format!(
|
|
255
|
+
"Timestamp {at_rfc3339} is in the future. \
|
|
256
|
+
Time-pinned verification requires a past or present timestamp."
|
|
257
|
+
),
|
|
258
|
+
));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
Ok(at)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#[napi]
|
|
265
|
+
pub async fn verify_at_time(
|
|
266
|
+
attestation_json: String,
|
|
267
|
+
issuer_pk_hex: String,
|
|
268
|
+
at_rfc3339: String,
|
|
269
|
+
) -> napi::Result<NapiVerificationResult> {
|
|
270
|
+
if attestation_json.len() > MAX_ATTESTATION_JSON_SIZE {
|
|
271
|
+
return Err(format_error(
|
|
272
|
+
"AUTHS_INVALID_INPUT",
|
|
273
|
+
format!(
|
|
274
|
+
"Attestation JSON too large: {} bytes, max {}",
|
|
275
|
+
attestation_json.len(),
|
|
276
|
+
MAX_ATTESTATION_JSON_SIZE
|
|
277
|
+
),
|
|
278
|
+
));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let at = parse_rfc3339_timestamp(&at_rfc3339)?;
|
|
282
|
+
let issuer_pk_bytes = decode_pk_hex(&issuer_pk_hex, "issuer public key")?;
|
|
283
|
+
|
|
284
|
+
let att: Attestation = match serde_json::from_str(&attestation_json) {
|
|
285
|
+
Ok(att) => att,
|
|
286
|
+
Err(e) => {
|
|
287
|
+
return Ok(NapiVerificationResult {
|
|
288
|
+
valid: false,
|
|
289
|
+
error: Some(format!("Failed to parse attestation JSON: {e}")),
|
|
290
|
+
error_code: Some("AUTHS_SERIALIZATION_ERROR".to_string()),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
match rust_verify_at_time(&att, &issuer_pk_bytes, at).await {
|
|
296
|
+
Ok(_) => Ok(NapiVerificationResult {
|
|
297
|
+
valid: true,
|
|
298
|
+
error: None,
|
|
299
|
+
error_code: None,
|
|
300
|
+
}),
|
|
301
|
+
Err(e) => Ok(NapiVerificationResult {
|
|
302
|
+
valid: false,
|
|
303
|
+
error_code: Some(e.error_code().to_string()),
|
|
304
|
+
error: Some(e.to_string()),
|
|
305
|
+
}),
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
#[napi]
|
|
310
|
+
pub async fn verify_at_time_with_capability(
|
|
311
|
+
attestation_json: String,
|
|
312
|
+
issuer_pk_hex: String,
|
|
313
|
+
at_rfc3339: String,
|
|
314
|
+
required_capability: String,
|
|
315
|
+
) -> napi::Result<NapiVerificationResult> {
|
|
316
|
+
if attestation_json.len() > MAX_ATTESTATION_JSON_SIZE {
|
|
317
|
+
return Err(format_error(
|
|
318
|
+
"AUTHS_INVALID_INPUT",
|
|
319
|
+
format!(
|
|
320
|
+
"Attestation JSON too large: {} bytes, max {}",
|
|
321
|
+
attestation_json.len(),
|
|
322
|
+
MAX_ATTESTATION_JSON_SIZE
|
|
323
|
+
),
|
|
324
|
+
));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
let at = parse_rfc3339_timestamp(&at_rfc3339)?;
|
|
328
|
+
let issuer_pk_bytes = decode_pk_hex(&issuer_pk_hex, "issuer public key")?;
|
|
329
|
+
|
|
330
|
+
let att: Attestation = match serde_json::from_str(&attestation_json) {
|
|
331
|
+
Ok(att) => att,
|
|
332
|
+
Err(e) => {
|
|
333
|
+
return Ok(NapiVerificationResult {
|
|
334
|
+
valid: false,
|
|
335
|
+
error: Some(format!("Failed to parse attestation JSON: {e}")),
|
|
336
|
+
error_code: Some("AUTHS_SERIALIZATION_ERROR".to_string()),
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
let cap = Capability::parse(&required_capability).map_err(|e| {
|
|
342
|
+
format_error(
|
|
343
|
+
"AUTHS_INVALID_INPUT",
|
|
344
|
+
format!("Invalid capability '{required_capability}': {e}"),
|
|
345
|
+
)
|
|
346
|
+
})?;
|
|
347
|
+
|
|
348
|
+
match rust_verify_at_time(&att, &issuer_pk_bytes, at).await {
|
|
349
|
+
Ok(_) => {
|
|
350
|
+
if att.capabilities.contains(&cap) {
|
|
351
|
+
Ok(NapiVerificationResult {
|
|
352
|
+
valid: true,
|
|
353
|
+
error: None,
|
|
354
|
+
error_code: None,
|
|
355
|
+
})
|
|
356
|
+
} else {
|
|
357
|
+
Ok(NapiVerificationResult {
|
|
358
|
+
valid: false,
|
|
359
|
+
error: Some(format!(
|
|
360
|
+
"Attestation does not grant required capability '{required_capability}'"
|
|
361
|
+
)),
|
|
362
|
+
error_code: Some("AUTHS_MISSING_CAPABILITY".to_string()),
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
Err(e) => Ok(NapiVerificationResult {
|
|
367
|
+
valid: false,
|
|
368
|
+
error_code: Some(e.error_code().to_string()),
|
|
369
|
+
error: Some(e.to_string()),
|
|
370
|
+
}),
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
#[napi]
|
|
375
|
+
pub async fn verify_chain_with_witnesses(
|
|
376
|
+
attestations_json: Vec<String>,
|
|
377
|
+
root_pk_hex: String,
|
|
378
|
+
receipts_json: Vec<String>,
|
|
379
|
+
witness_keys_json: Vec<String>,
|
|
380
|
+
threshold: u32,
|
|
381
|
+
) -> napi::Result<NapiVerificationReport> {
|
|
382
|
+
check_batch_size(&attestations_json)?;
|
|
383
|
+
let root_pk_bytes = decode_pk_hex(&root_pk_hex, "root public key")?;
|
|
384
|
+
let attestations = parse_attestations(&attestations_json)?;
|
|
385
|
+
|
|
386
|
+
let receipts: Vec<WitnessReceipt> = receipts_json
|
|
387
|
+
.iter()
|
|
388
|
+
.enumerate()
|
|
389
|
+
.map(|(i, json)| {
|
|
390
|
+
serde_json::from_str(json).map_err(|e| {
|
|
391
|
+
format_error(
|
|
392
|
+
"AUTHS_SERIALIZATION_ERROR",
|
|
393
|
+
format!("Failed to parse witness receipt {i}: {e}"),
|
|
394
|
+
)
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
.collect::<napi::Result<Vec<_>>>()?;
|
|
398
|
+
|
|
399
|
+
#[derive(serde::Deserialize)]
|
|
400
|
+
struct WitnessKeyInput {
|
|
401
|
+
did: String,
|
|
402
|
+
public_key_hex: String,
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
let witness_keys: Vec<(String, Vec<u8>)> = witness_keys_json
|
|
406
|
+
.iter()
|
|
407
|
+
.enumerate()
|
|
408
|
+
.map(|(i, json)| {
|
|
409
|
+
let input: WitnessKeyInput = serde_json::from_str(json).map_err(|e| {
|
|
410
|
+
format_error(
|
|
411
|
+
"AUTHS_SERIALIZATION_ERROR",
|
|
412
|
+
format!("Failed to parse witness key {i}: {e}"),
|
|
413
|
+
)
|
|
414
|
+
})?;
|
|
415
|
+
let pk_bytes = hex::decode(&input.public_key_hex).map_err(|e| {
|
|
416
|
+
format_error(
|
|
417
|
+
"AUTHS_INVALID_INPUT",
|
|
418
|
+
format!("Invalid witness key {i} hex: {e}"),
|
|
419
|
+
)
|
|
420
|
+
})?;
|
|
421
|
+
if pk_bytes.len() != 32 {
|
|
422
|
+
return Err(format_error(
|
|
423
|
+
"AUTHS_INVALID_INPUT",
|
|
424
|
+
format!(
|
|
425
|
+
"Invalid witness key {i} length: expected 32 bytes, got {}",
|
|
426
|
+
pk_bytes.len()
|
|
427
|
+
),
|
|
428
|
+
));
|
|
429
|
+
}
|
|
430
|
+
Ok((input.did, pk_bytes))
|
|
431
|
+
})
|
|
432
|
+
.collect::<napi::Result<Vec<_>>>()?;
|
|
433
|
+
|
|
434
|
+
let config = WitnessVerifyConfig {
|
|
435
|
+
receipts: &receipts,
|
|
436
|
+
witness_keys: &witness_keys,
|
|
437
|
+
threshold: threshold as usize,
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
match rust_verify_chain_with_witnesses(&attestations, &root_pk_bytes, &config).await {
|
|
441
|
+
Ok(report) => Ok(report.into()),
|
|
442
|
+
Err(e) => Err(format_error(
|
|
443
|
+
e.error_code(),
|
|
444
|
+
format!("Chain verification with witnesses failed: {e}"),
|
|
445
|
+
)),
|
|
446
|
+
}
|
|
447
|
+
}
|
package/src/witness.rs
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
use std::path::PathBuf;
|
|
2
|
+
|
|
3
|
+
use auths_id::storage::identity::IdentityStorage;
|
|
4
|
+
use auths_id::witness_config::WitnessConfig;
|
|
5
|
+
use auths_storage::git::RegistryIdentityStorage;
|
|
6
|
+
use napi_derive::napi;
|
|
7
|
+
|
|
8
|
+
use crate::error::format_error;
|
|
9
|
+
|
|
10
|
+
fn resolve_repo(repo_path: &str) -> PathBuf {
|
|
11
|
+
PathBuf::from(shellexpand::tilde(repo_path).as_ref())
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fn load_witness_config(repo_path: &PathBuf) -> napi::Result<WitnessConfig> {
|
|
15
|
+
let storage = RegistryIdentityStorage::new(repo_path);
|
|
16
|
+
let identity = storage
|
|
17
|
+
.load_identity()
|
|
18
|
+
.map_err(|e| format_error("AUTHS_WITNESS_ERROR", e))?;
|
|
19
|
+
|
|
20
|
+
if let Some(wc) = identity
|
|
21
|
+
.metadata
|
|
22
|
+
.as_ref()
|
|
23
|
+
.and_then(|m| m.get("witness_config"))
|
|
24
|
+
{
|
|
25
|
+
let config: WitnessConfig = serde_json::from_value(wc.clone())
|
|
26
|
+
.map_err(|e| format_error("AUTHS_WITNESS_ERROR", e))?;
|
|
27
|
+
return Ok(config);
|
|
28
|
+
}
|
|
29
|
+
Ok(WitnessConfig::default())
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn save_witness_config(repo_path: &PathBuf, config: &WitnessConfig) -> napi::Result<()> {
|
|
33
|
+
let storage = RegistryIdentityStorage::new(repo_path);
|
|
34
|
+
let mut identity = storage
|
|
35
|
+
.load_identity()
|
|
36
|
+
.map_err(|e| format_error("AUTHS_WITNESS_ERROR", e))?;
|
|
37
|
+
|
|
38
|
+
let metadata = identity
|
|
39
|
+
.metadata
|
|
40
|
+
.get_or_insert_with(|| serde_json::json!({}));
|
|
41
|
+
if let Some(obj) = metadata.as_object_mut() {
|
|
42
|
+
obj.insert(
|
|
43
|
+
"witness_config".to_string(),
|
|
44
|
+
serde_json::to_value(config).map_err(|e| format_error("AUTHS_WITNESS_ERROR", e))?,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
storage
|
|
49
|
+
.create_identity(identity.controller_did.as_str(), identity.metadata)
|
|
50
|
+
.map_err(|e| format_error("AUTHS_WITNESS_ERROR", e))?;
|
|
51
|
+
Ok(())
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[napi(object)]
|
|
55
|
+
#[derive(Clone)]
|
|
56
|
+
pub struct NapiWitnessResult {
|
|
57
|
+
pub url: String,
|
|
58
|
+
pub did: Option<String>,
|
|
59
|
+
pub label: Option<String>,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#[napi]
|
|
63
|
+
pub fn add_witness(
|
|
64
|
+
url_str: String,
|
|
65
|
+
repo_path: String,
|
|
66
|
+
label: Option<String>,
|
|
67
|
+
) -> napi::Result<NapiWitnessResult> {
|
|
68
|
+
let repo = resolve_repo(&repo_path);
|
|
69
|
+
let parsed_url: url::Url = url_str.parse().map_err(|e| {
|
|
70
|
+
format_error(
|
|
71
|
+
"AUTHS_WITNESS_ERROR",
|
|
72
|
+
format!("Invalid URL '{}': {}", url_str, e),
|
|
73
|
+
)
|
|
74
|
+
})?;
|
|
75
|
+
|
|
76
|
+
let mut config = load_witness_config(&repo)?;
|
|
77
|
+
|
|
78
|
+
if config.witness_urls.contains(&parsed_url) {
|
|
79
|
+
return Ok(NapiWitnessResult {
|
|
80
|
+
url: url_str,
|
|
81
|
+
did: None,
|
|
82
|
+
label,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
config.witness_urls.push(parsed_url);
|
|
87
|
+
if config.threshold == 0 {
|
|
88
|
+
config.threshold = 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
save_witness_config(&repo, &config)?;
|
|
92
|
+
Ok(NapiWitnessResult {
|
|
93
|
+
url: url_str,
|
|
94
|
+
did: None,
|
|
95
|
+
label,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#[napi]
|
|
100
|
+
pub fn remove_witness(url_str: String, repo_path: String) -> napi::Result<()> {
|
|
101
|
+
let repo = resolve_repo(&repo_path);
|
|
102
|
+
let parsed_url: url::Url = url_str.parse().map_err(|e| {
|
|
103
|
+
format_error(
|
|
104
|
+
"AUTHS_WITNESS_ERROR",
|
|
105
|
+
format!("Invalid URL '{}': {}", url_str, e),
|
|
106
|
+
)
|
|
107
|
+
})?;
|
|
108
|
+
|
|
109
|
+
let mut config = load_witness_config(&repo)?;
|
|
110
|
+
config.witness_urls.retain(|u| u != &parsed_url);
|
|
111
|
+
|
|
112
|
+
if config.threshold > config.witness_urls.len() {
|
|
113
|
+
config.threshold = config.witness_urls.len();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
save_witness_config(&repo, &config)?;
|
|
117
|
+
Ok(())
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[napi]
|
|
121
|
+
pub fn list_witnesses(repo_path: String) -> napi::Result<String> {
|
|
122
|
+
let repo = resolve_repo(&repo_path);
|
|
123
|
+
let config = load_witness_config(&repo)?;
|
|
124
|
+
|
|
125
|
+
let entries: Vec<serde_json::Value> = config
|
|
126
|
+
.witness_urls
|
|
127
|
+
.iter()
|
|
128
|
+
.map(|u| {
|
|
129
|
+
serde_json::json!({
|
|
130
|
+
"url": u.to_string(),
|
|
131
|
+
"did": null,
|
|
132
|
+
"label": null,
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
.collect();
|
|
136
|
+
|
|
137
|
+
serde_json::to_string(&entries).map_err(|e| format_error("AUTHS_WITNESS_ERROR", e))
|
|
138
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "ESNext.Disposable"],
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"declarationMap": true,
|
|
13
|
+
"sourceMap": true,
|
|
14
|
+
"outDir": "./dist",
|
|
15
|
+
"rootDir": "."
|
|
16
|
+
},
|
|
17
|
+
"include": ["lib/**/*.ts"],
|
|
18
|
+
"exclude": ["node_modules", "__tests__", "dist"]
|
|
19
|
+
}
|
package/typedoc.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://typedoc.org/schema.json",
|
|
3
|
+
"entryPoints": ["lib/index.ts"],
|
|
4
|
+
"out": "../../docs/sdk/node/api",
|
|
5
|
+
"plugin": ["typedoc-plugin-markdown"],
|
|
6
|
+
"tsconfig": "tsconfig.json",
|
|
7
|
+
"readme": "none",
|
|
8
|
+
"hideGenerator": true,
|
|
9
|
+
"excludePrivate": true,
|
|
10
|
+
"excludeInternal": true,
|
|
11
|
+
"disableSources": true,
|
|
12
|
+
"outputFileStrategy": "modules",
|
|
13
|
+
"hideBreadcrumbs": true,
|
|
14
|
+
"hidePageHeader": true,
|
|
15
|
+
"useCodeBlocks": true,
|
|
16
|
+
"entryFileName": "index",
|
|
17
|
+
"flattenOutputFiles": true
|
|
18
|
+
}
|