fce44ae9 Andy McCall 1 month ago ↶
1
2
3
use super :: format:: { short_id, signature_line, when} ;
75167672 Andy McCall 1 month ago
4
use super :: json;
fce44ae9 Andy McCall 1 month ago ↶
5
use super :: open;
135212de Andy McCall 1 month ago ↶
6
use super :: ui;
fce44ae9 Andy McCall 1 month ago ↶
7
use anyhow:: Result;
8
use oxy_core:: hash:: ChangeHash;
75167672 Andy McCall 1 month ago
9
use oxy_core:: hash:: KeyId;
7ba155f2 Andy McCall 1 month ago ↶
10
use oxy_core:: identity:: { Delegate, Identity, Scope} ;
28fc23d7 Andy McCall 1 month ago ↶
11
use oxy_core:: objects:: Hlc;
12
use oxy_core:: protocol:: SignedPolicy;
c6aa5bd9 Andy McCall 1 month ago ↶
13
use oxy_core:: repo:: { self , IdentityScope, PolicyUpdate, Repo, Rotation, SignatureStatus} ;
83e0677b Andy McCall 1 month ago ↶
14
use std:: path:: { Path, PathBuf} ;
fce44ae9 Andy McCall 1 month ago ↶
15
28fc23d7 Andy McCall 1 month ago ↶
16
const DAY_MS : u64 = 24 * 60 * 60 * 1000 ;
17
18
fn expiry_from_days ( days : u64 ) -> Option< u64 > {
19
( days > 0 ) .then ( | | Hlc:: now( ) .wall_ms + days * DAY_MS )
20
}
21
7ba155f2 Andy McCall 1 month ago ↶
22
23
fn expiry_from_duration ( s : & str ) -> Result< u64 > {
24
let s = s.trim ( ) ;
25
let ( n, unit) = match s.char_indices ( ) .find ( | ( _, c ) | ! c.is_ascii_digit ( ) ) {
26
Some ( ( i, _ ) ) => ( & s[ .. i] , & s[ i.. ] ) ,
27
None => ( s, " d" ) ,
28
} ;
29
let n: u64 = n.parse ( ) .map_err ( | _| {
30
anyhow:: anyhow! ( " --expires takes 30m, 8h, 7d or a number of days, not {s:?}" )
31
} ) ? ;
32
let ms = match unit {
33
" m" => 60_000 ,
34
" h" => 3_600_000 ,
35
" d" => DAY_MS ,
36
_ => anyhow:: bail! ( " --expires takes 30m, 8h, 7d or a number of days, not {s:?}" ) ,
37
} ;
38
if n = = 0 {
39
anyhow:: bail! ( " --expires needs a length of time; leave it out for no expiry" ) ;
40
}
41
Ok ( Hlc:: now( ) .wall_ms + n * ms)
42
}
43
5cde7882 Andy McCall 1 month ago ↶
44
45
46
fn print_delegates ( repo : & Repo, delegates : & [Delegate]) {
47
if delegates.is_empty ( ) {
48
return ;
49
}
50
println! ( " {} " , ui:: kv( " delegates" , delegates.len ( ) .to_string ( ) ) ) ;
51
let now = Hlc:: now( ) .wall_ms;
52
for d in delegates {
53
let who = repo
54
.identity ( & d.key)
55
.map ( | i | format! ( " {} <{} >" , i.name, i.email) )
56
.unwrap_or_else ( | _| " (unknown identity)" .into ( ) ) ;
57
let until = match d.expires {
58
Some ( e) if now > e => ui:: err( format! ( " expired {} " , ui:: ago( e) ) ) ,
59
Some ( e) => ui:: dim( format! ( " until {} " , when ( e) ) ) ,
60
None => String :: new( ) ,
61
} ;
62
println! (
63
" {} {} {} {} " ,
64
ui:: author( who) ,
65
ui:: hash( d.key.short ( 12 ) ) ,
66
ui:: dim( d.scope.to_string ( ) ) ,
67
until
68
) ;
69
}
7ba155f2 Andy McCall 1 month ago ↶
70
}
71
28fc23d7 Andy McCall 1 month ago ↶
72
73
fn resolve_key ( repo : & Repo, prefix : & str ) -> Result< KeyId> {
74
if let Some ( k) = KeyId:: from_hex( prefix) {
75
return Ok ( k) ;
76
}
77
let mut candidates: Vec< KeyId> = Vec :: new( ) ;
78
if let Some ( ( _ , sp) ) = repo.current_policy ( ) ? {
79
candidates.extend ( sp.policy.identities.iter ( ) .map ( | i | i.hash ( ) ) ) ;
80
candidates.extend ( sp.policy.revoked.iter ( ) .copied ( ) ) ;
81
}
82
if let Some ( own) = repo.config.author_key {
83
candidates.push ( own) ;
84
if let Ok ( id) = repo.identity ( & own) {
7ba155f2 Andy McCall 1 month ago ↶
85
candidates.extend ( id.delegates.iter ( ) .map ( | d | d.key ) ) ;
28fc23d7 Andy McCall 1 month ago ↶
86
}
87
}
88
candidates.sort ( ) ;
89
candidates.dedup ( ) ;
75167672 Andy McCall 1 month ago
90
let matches: Vec< _ > = candidates
91
.into_iter ( )
92
.filter ( | k | k.starts_with_hex ( prefix) )
93
.collect ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
94
match matches.len ( ) {
95
1 => Ok ( matches[ 0 ] ) ,
96
0 => anyhow:: bail! ( " no known key starts with {prefix:?}" ) ,
97
_ => anyhow:: bail! ( " {prefix:?} is ambiguous" ) ,
98
}
99
}
100
101
102
103
fn report_update ( u : & PolicyUpdate, out : Option< PathBuf> ) -> Result< ( ) > {
104
if u.applied {
75167672 Andy McCall 1 month ago
105
println! (
106
" {} policy serial {} {} " ,
107
ui:: done( " Applied" ) ,
108
u.policy.serial,
109
ui:: dim( " (push to publish it)" )
110
) ;
28fc23d7 Andy McCall 1 month ago ↶
111
return Ok ( ( ) ) ;
112
}
113
let path = out.unwrap_or_else ( | | PathBuf:: from( format! ( " policy-{} .proposal" , u.policy.serial) ) ) ;
114
std:: fs:: write( & path, u.signed ( ) .encode ( ) ) ? ;
115
println! (
116
" {} policy serial {} needs {} of {} root signatures; has {} . Wrote {} for another root to run {} " ,
117
ui:: caution( ) ,
118
u.policy.serial,
119
u.policy.root_threshold,
120
u.policy.roots.len ( ) ,
121
u.sigs.len ( ) ,
122
ui:: path( path.display ( ) ) ,
123
ui:: dim( format! ( " oxy policy sign {} " , path.display ( ) ) )
124
) ;
125
Ok ( ( ) )
126
}
127
5cde7882 Andy McCall 1 month ago ↶
128
fn report_rotation ( repo : & Repo, r : & Rotation, verb : & str ) -> Result< ( ) > {
75167672 Andy McCall 1 month ago
129
println! (
130
" {} identity {} <{} >" ,
131
ui:: done( verb) ,
132
ui:: author( & r.identity.name) ,
133
r.identity.email
134
) ;
aded4110 Andy McCall 1 day ago ↶
135
136
let from = if repo.config.is_repo_set ( " author.key" ) {
137
" this repository's author.key"
138
} else {
139
" the user config's author.key"
140
} ;
141
println! (
142
" {} " ,
143
ui:: kv(
144
" old key" ,
145
format! (
146
" {} {} " ,
147
ui:: dim( r.old.to_hex ( ) ) ,
148
ui:: dim( format! ( " (from {from} )" ) )
149
)
150
)
151
) ;
28fc23d7 Andy McCall 1 month ago ↶
152
println! ( " {} " , ui:: kv( " new key" , r.new.to_hex ( ) ) ) ;
5cde7882 Andy McCall 1 month ago ↶
153
print_delegates ( repo, & r.identity.delegates) ;
28fc23d7 Andy McCall 1 month ago ↶
154
if let Some ( e) = r.identity.expires {
155
println! ( " {} " , ui:: kv( " expires" , when ( e) ) ) ;
156
}
157
match & r.policy {
158
Some ( u) => report_update ( u, None ) ? ,
75167672 Andy McCall 1 month ago
159
None => println! (
160
" {} {} " ,
161
ui:: note( ) ,
162
ui:: dim( " no policy names the old key; export the new identity for policy holders" )
163
) ,
28fc23d7 Andy McCall 1 month ago ↶
164
}
165
Ok ( ( ) )
166
}
167
75167672 Andy McCall 1 month ago
168
pub fn create (
169
name : Option< String> ,
170
email : Option< String> ,
171
local : bool ,
172
expires : Option< u64 > ,
173
) -> Result< i32 > {
64dc5197 Andy McCall 7 days ago ↶
174
175
176
let mut repo = match open ( ) {
177
Ok ( r) => r,
178
Err ( e) if local => return Err ( e) ,
179
Err ( _ ) => {
180
let ( key, id) = repo:: create_user_identity(
181
name.as_deref ( ) ,
182
email.as_deref ( ) ,
183
expires.and_then ( expiry_from_days) ,
184
) ? ;
185
println! (
186
" {} identity {} <{} >" ,
187
ui:: done( " Created" ) ,
188
ui:: author( & id.name) ,
189
id.email
190
) ;
191
println! ( " {} " , ui:: kv( " key" , key) ) ;
192
println! (
193
" {} " ,
194
ui:: kv( " secret" , ui:: path( repo:: default_keys_dir( ) .display ( ) ) )
195
) ;
196
println! (
197
" {} " ,
198
ui:: kv(
199
" scope" ,
200
format! (
201
" default author in {} " ,
202
ui:: path( repo:: default_user_config( ) .display ( ) )
203
)
204
)
205
) ;
206
println! ( " {} {} " , ui:: note( ) , ui:: dim( " changes in every repository carry this author; run oxy config --user core.sign change to sign each commit" ) ) ;
207
return Ok ( 0 ) ;
208
}
209
} ;
fce44ae9 Andy McCall 1 month ago ↶
210
let name = name.unwrap_or_else ( | | repo.config.author_name.clone ( ) ) ;
211
let email = email.unwrap_or_else ( | | repo.config.author_email.clone ( ) ) ;
75167672 Andy McCall 1 month ago
212
let scope = if local {
213
IdentityScope:: Repo
214
} else {
215
IdentityScope:: User
216
} ;
217
let ( key, _ ) =
218
repo.create_identity_with ( & name, & email, scope, expires.and_then ( expiry_from_days) ) ? ;
219
println! (
220
" {} identity {} <{email} >" ,
221
ui:: done( " Created" ) ,
222
ui:: author( & name)
223
) ;
94084087 Andy McCall 1 month ago ↶
224
println! ( " {} " , ui:: kv( " key" , key) ) ;
75167672 Andy McCall 1 month ago
225
println! (
226
" {} " ,
227
ui:: kv( " secret" , ui:: path( repo:: default_keys_dir( ) .display ( ) ) )
228
) ;
fce44ae9 Andy McCall 1 month ago ↶
229
match scope {
75167672 Andy McCall 1 month ago
230
IdentityScope:: User => println! (
231
" {} " ,
232
ui:: kv(
233
" scope" ,
234
format! (
235
" default author in {} " ,
236
ui:: path( repo.user_config_path ( ) .display ( ) )
237
)
238
)
239
) ,
240
IdentityScope:: Repo => {
241
println! ( " {} " , ui:: kv( " scope" , " this repository's .oxy/config only" ) )
242
}
fce44ae9 Andy McCall 1 month ago ↶
243
}
244
if ! repo.config.sign_changes {
94084087 Andy McCall 1 month ago ↶
245
println! ( " {} {} " , ui:: note( ) , ui:: dim( " changes will carry this author; run oxy config --user core.sign change to sign each commit" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
246
}
247
Ok ( 0 )
248
}
249
250
pub fn show ( ) -> Result< i32 > {
251
let repo = open ( ) ? ;
252
let Some ( key) = repo.config.author_key else {
253
println! ( " No identity configured; run `oxy identity create`." ) ;
254
return Ok ( 0 ) ;
255
} ;
256
let id = repo.identity ( & key) ? ;
75167672 Andy McCall 1 month ago
257
println! (
258
" {} {} <{} >" ,
259
ui:: section( " identity" ) ,
260
ui:: author( & id.name) ,
261
id.email
262
) ;
94084087 Andy McCall 1 month ago ↶
263
println! ( " {} " , ui:: kv( " key" , key) ) ;
75167672 Andy McCall 1 month ago
264
println! (
265
" {} " ,
266
ui:: kv(
267
" created" ,
268
format! (
269
" {} {} " ,
270
when ( id.created) ,
271
ui:: dim( format! ( " ({} )" , ui:: ago( id.created) ) )
272
)
273
)
274
) ;
28fc23d7 Andy McCall 1 month ago ↶
275
if let Some ( e) = id.expires {
276
let expired = id.is_expired_at ( Hlc:: now( ) .wall_ms) ;
75167672 Andy McCall 1 month ago
277
println! (
278
" {} " ,
279
ui:: kv(
280
" expires" ,
281
if expired {
282
ui:: err( format! ( " {} EXPIRED" , when ( e) ) )
283
} else {
284
when ( e)
285
}
286
)
287
) ;
28fc23d7 Andy McCall 1 month ago ↶
288
}
289
if let Some ( p) = id.previous {
290
println! ( " {} " , ui:: kv( " previous" , ui:: dim( p.short ( 12 ) ) ) ) ;
291
}
614f55e1 Andy McCall 1 month ago ↶
292
293
294
if let Some ( pinned) = repo
295
.config
296
.repo_layer ( )
297
.get ( " author.key" )
298
.and_then ( | k | oxy_core:: hash:: KeyId:: from_hex( k) )
299
{
300
let user_key = repo:: read_config_file( & repo:: default_user_config( ) )
301
.ok ( )
302
.and_then ( | m | {
303
m.get ( " author.key" )
304
.and_then ( | k | oxy_core:: hash:: KeyId:: from_hex( k) )
305
} ) ;
306
if let Some ( u) = user_key.filter ( | u | * u ! = pinned) {
307
let related = | from : oxy_core:: hash:: KeyId, to : oxy_core:: hash:: KeyId| {
308
let mut cur = Some ( from) ;
309
for _ in 0 .. 64 {
310
match cur {
311
Some ( k) if k = = to => return true ,
312
Some ( k) => cur = repo.identity ( & k) .ok ( ) .and_then ( | i | i.previous ) ,
313
None => break ,
314
}
315
}
316
false
317
} ;
318
let how = if related ( pinned, u) {
319
" an older key of this identity"
320
} else if related ( u, pinned) {
321
" a newer key of this identity"
322
} else {
323
" another key"
324
} ;
325
println! (
326
" {} {} " ,
327
ui:: caution( ) ,
328
ui:: dim( format! (
329
" this repository pins author.key {} ; your user config names {how} , {} . Other checkouts follow the user config: `oxy config --unset author.key` here, or `oxy config --user author.key {} `" ,
330
pinned.short ( 12 ) ,
331
u.short ( 12 ) ,
332
pinned.to_hex ( )
aded4110 Andy McCall 1 day ago ↶
333
) )
334
) ;
335
}
336
}
337
338
339
let next = repo.successors ( & key) ? ;
340
if let Some ( n) = next.first ( ) {
341
println! (
342
" {} {} " ,
343
ui:: caution( ) ,
344
ui:: warn( format! (
345
" this key is not your identity's newest: {} succeeds it, {} is the newest. Rotating or delegating from it would fork the identity; point author.key at the newest" ,
346
n.short ( 12 ) ,
347
repo.newest ( n) ? .short ( 12 )
348
) )
349
) ;
350
}
351
let mut cur = Some ( key) ;
352
for _ in 0 .. 256 {
353
let Some ( k) = cur else { break } ;
354
let after = repo.successors ( & k) ? ;
355
if after.len ( ) > 1 {
356
println! (
357
" {} {} " ,
358
ui:: caution( ) ,
359
ui:: err( format! (
360
" the identity forks at {} : {} each succeed it. Keep one branch: revoke the others' keys (oxy identity revoke KEY) and point every home's author.key at the one you keep" ,
361
k.short ( 12 ) ,
362
after.iter ( ) .map ( | a | a.short ( 12 ) ) .collect:: < Vec< _ > > ( ) .join ( " and " )
614f55e1 Andy McCall 1 month ago ↶
363
) )
364
) ;
aded4110 Andy McCall 1 day ago ↶
365
break ;
614f55e1 Andy McCall 1 month ago ↶
366
}
aded4110 Andy McCall 1 day ago ↶
367
cur = repo.identity ( & k) .ok ( ) .and_then ( | i | i.previous ) ;
614f55e1 Andy McCall 1 month ago ↶
368
}
5cde7882 Andy McCall 1 month ago ↶
369
print_delegates ( & repo, & id.delegates) ;
28fc23d7 Andy McCall 1 month ago ↶
370
if repo.is_revoked ( & key) ? {
75167672 Andy McCall 1 month ago
371
println! (
372
" {} " ,
373
ui:: kv(
374
" status" ,
375
ui:: err( format! ( " {} REVOKED" , ui:: glyph( " ✖" , " x" ) ) )
376
)
377
) ;
28fc23d7 Andy McCall 1 month ago ↶
378
}
75167672 Andy McCall 1 month ago
379
println! (
380
" {} " ,
381
ui:: kv(
382
" secret" ,
383
if repo.secret_key ( ) ? .is_some ( ) {
384
ui:: ok( format! ( " {} available" , ui:: glyph( " ✔" , " *" ) ) )
385
} else {
386
ui:: err( format! (
387
" {} not found in the key directory" ,
388
ui:: glyph( " ✖" , " x" )
389
) )
390
}
391
)
392
) ;
393
println! (
394
" {} " ,
395
ui:: kv(
396
" signing" ,
397
if repo.config.sign_changes {
398
" every change (core.sign = change)"
399
} else {
400
" at push via seals (core.sign = seal)"
401
}
402
)
403
) ;
fce44ae9 Andy McCall 1 month ago ↶
404
Ok ( 0 )
405
}
406
2c9ddac3 Andy McCall 1 month ago ↶
407
pub fn verify ( change : Option< & str > , revset : Option< & str > , as_json : bool ) -> Result< i32 > {
fce44ae9 Andy McCall 1 month ago ↶
408
let repo = open ( ) ? ;
2c9ddac3 Andy McCall 1 month ago ↶
409
let hashes: Vec< ChangeHash> = match ( change, revset) {
410
( _ , Some ( r) ) => {
411
let wc = repo.working_copy ( ) .ok ( ) .map ( | ( h , _) | h ) ;
412
repo.revset ( r) ?
413
.hashes
414
.into_iter ( )
415
.filter ( | h | Some ( * h) ! = wc)
416
.collect ( )
417
}
418
( Some ( c) , None ) => vec! [ repo.resolve_change ( c) ? ] ,
419
( None , None ) => repo
75167672 Andy McCall 1 month ago
420
.log ( ) ?
421
.into_iter ( )
422
.filter ( | e | ! e.is_working_copy)
423
.map ( | e | e.hash )
424
.collect ( ) ,
fce44ae9 Andy McCall 1 month ago ↶
425
} ;
426
let mut bad = 0 ;
c6aa5bd9 Andy McCall 1 month ago ↶
427
if as_json {
428
let mut items = Vec :: new( ) ;
429
for h in & hashes {
75167672 Andy McCall 1 month ago
430
let Ok ( c) = repo.store ( ) .get_change ( h) else {
431
continue ;
432
} ;
c6aa5bd9 Andy McCall 1 month ago ↶
433
let ( status, key, name, revoked, reason) = match repo.authenticate_change ( h) ? {
434
SignatureStatus:: Unsigned => ( " unsigned" , None , None , false , None ) ,
75167672 Andy McCall 1 month ago
435
SignatureStatus:: Valid {
436
key,
437
name,
438
sealed,
439
revoked,
440
..
441
} => (
442
if sealed { " sealed" } else { " signed" } ,
443
Some ( key.to_hex ( ) ) ,
444
Some ( name) ,
445
revoked,
446
None ,
447
) ,
c6aa5bd9 Andy McCall 1 month ago ↶
448
SignatureStatus:: Invalid( why) => {
449
bad + = 1 ;
450
( " invalid" , None , None , false , Some ( why) )
451
}
452
} ;
453
items.push ( json:: obj( [
454
( " hash" , json:: s( & h.to_hex ( ) ) ) ,
455
( " change_id" , json:: s( & c.change_id.to_hex ( ) ) ) ,
456
( " status" , json:: s( status) ) ,
457
( " key" , json:: opt( key.as_deref ( ) , json:: s) ) ,
458
( " name" , json:: opt( name.as_deref ( ) , json:: s) ) ,
459
( " revoked" , json:: b( revoked) ) ,
460
( " reason" , json:: opt( reason.as_deref ( ) , json:: s) ) ,
461
] ) ) ;
462
}
463
println! ( " {} " , json:: arr( items) ) ;
464
return Ok ( if bad > 0 { 1 } else { 0 } ) ;
465
}
fce44ae9 Andy McCall 1 month ago ↶
466
for h in hashes {
2863456a Andy McCall 1 month ago ↶
467
let Ok ( c) = repo.store ( ) .get_change ( & h) else {
75167672 Andy McCall 1 month ago
468
println! (
469
" {} {} " ,
470
ui:: hash( format! ( " #{} " , h.short ( 12 ) ) ) ,
471
ui:: dim( " not present (shallow)" )
472
) ;
2863456a Andy McCall 1 month ago ↶
473
continue ;
474
} ;
fce44ae9 Andy McCall 1 month ago ↶
475
let line = signature_line ( & repo, & h) ? ;
135212de Andy McCall 1 month ago ↶
476
let styled = if line.starts_with ( " INVALID" ) {
fce44ae9 Andy McCall 1 month ago ↶
477
bad + = 1 ;
94084087 Andy McCall 1 month ago ↶
478
ui:: err( format! ( " {} {line} " , ui:: glyph( " ✖" , " x" ) ) )
135212de Andy McCall 1 month ago ↶
479
} else if line = = " unsigned" {
94084087 Andy McCall 1 month ago ↶
480
ui:: dim( format! ( " {} {line} " , ui:: glyph( " ·" , " -" ) ) )
135212de Andy McCall 1 month ago ↶
481
} else {
94084087 Andy McCall 1 month ago ↶
482
ui:: ok( format! ( " {} {line} " , ui:: glyph( " ✔" , " v" ) ) )
135212de Andy McCall 1 month ago ↶
483
} ;
75167672 Andy McCall 1 month ago
484
println! (
485
" {} {} {styled} " ,
486
ui:: id( short_id ( & c) ) ,
487
ui:: hash( format! ( " #{} " , h.short ( 12 ) ) )
488
) ;
fce44ae9 Andy McCall 1 month ago ↶
489
}
490
Ok ( if bad > 0 { 1 } else { 0 } )
491
}
492
8e9c64d1 Andy McCall 20 days ago ↶
493
494
fn secret_key ( key : & str ) -> bool {
495
let k = key.to_ascii_lowercase ( ) ;
496
k.ends_with ( " .token" ) | | k.contains ( " secret" ) | | k.contains ( " password" )
497
}
498
499
500
501
fn mask ( value : & str ) -> String {
502
let shown: String = value.chars ( ) .take ( 10 ) .collect ( ) ;
503
let rest = value.chars ( ) .count ( ) .saturating_sub ( 10 ) ;
504
if rest = = 0 {
505
" …" .to_string ( )
506
} else {
507
format! ( " {shown} …({rest} more)" )
508
}
509
}
510
614f55e1 Andy McCall 1 month ago ↶
511
pub fn config ( key : & str , value : & str , user : bool , unset : bool ) -> Result< i32 > {
512
8e9c64d1 Andy McCall 20 days ago ↶
513
514
515
let read;
516
let value = if unset {
517
" "
518
} else if value = = " -" {
519
let mut line = String :: new( ) ;
520
std:: io:: stdin( ) .read_line ( & mut line) ? ;
521
read = line.trim ( ) .to_string ( ) ;
522
read.as_str ( )
523
} else {
524
value
525
} ;
614f55e1 Andy McCall 1 month ago ↶
526
let said = if unset {
527
format! ( " {} {key} " , ui:: done( " Unset" ) )
8e9c64d1 Andy McCall 20 days ago ↶
528
} else if secret_key ( key) {
529
format! ( " {} {key} = {} " , ui:: done( " Set" ) , mask ( value) )
614f55e1 Andy McCall 1 month ago ↶
530
} else {
531
format! ( " {} {key} = {value} " , ui:: done( " Set" ) )
532
} ;
fce44ae9 Andy McCall 1 month ago ↶
533
if user {
534
let path = repo:: default_user_config( ) ;
535
repo:: Config:: set_user( & path, key, value) ? ;
614f55e1 Andy McCall 1 month ago ↶
536
println! ( " {said} {} " , ui:: dim( format! ( " (in {} )" , path.display ( ) ) ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
537
} else {
538
let mut repo = open ( ) ? ;
539
repo.config.set ( key, value) ? ;
540
repo.save_config ( ) ? ;
614f55e1 Andy McCall 1 month ago ↶
541
println! ( " {said} {} " , ui:: dim( " (in this repository)" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
542
}
543
Ok ( 0 )
544
}
83e0677b Andy McCall 1 month ago ↶
545
a39754d0 Andy McCall 18 days ago ↶
546
pub fn export ( path : Option< PathBuf> , pem : bool ) -> Result< i32 > {
83e0677b Andy McCall 1 month ago ↶
547
let repo = open ( ) ? ;
a39754d0 Andy McCall 18 days ago ↶
548
549
550
let key = if pem {
551
repo.signing_key ( )
552
} else {
553
repo.config.author_key
554
} ;
555
let Some ( key) = key else {
83e0677b Andy McCall 1 month ago ↶
556
anyhow:: bail! ( " no identity configured; run `oxy identity create`" ) ;
557
} ;
558
let id = repo.identity ( & key) ? ;
a39754d0 Andy McCall 18 days ago ↶
559
let ext = if pem { " pem" } else { " identity" } ;
560
let path = path.unwrap_or_else ( | | PathBuf:: from( format! ( " {} .{ext} " , key.short ( 16 ) ) ) ) ;
561
if pem {
562
std:: fs:: write( & path, oxy_core:: dsse:: public_pem( & id) ) ? ;
563
} else {
564
std:: fs:: write( & path, id.encode ( ) ) ? ;
565
}
75167672 Andy McCall 1 month ago
566
println! (
a39754d0 Andy McCall 18 days ago ↶
567
" {} {} {} <{} > ({} ) to {} " ,
75167672 Andy McCall 1 month ago
568
ui:: done( " Wrote" ) ,
a39754d0 Andy McCall 18 days ago ↶
569
if pem { " the public key of" } else { " identity" } ,
75167672 Andy McCall 1 month ago
570
ui:: author( & id.name) ,
571
id.email,
572
key.short ( 12 ) ,
573
ui:: path( path.display ( ) )
574
) ;
83e0677b Andy McCall 1 month ago ↶
575
Ok ( 0 )
576
}
577
28fc23d7 Andy McCall 1 month ago ↶
578
pub fn rotate ( ) -> Result< i32 > {
579
let mut repo = open ( ) ? ;
580
let r = repo.rotate_identity ( ) ? ;
5cde7882 Andy McCall 1 month ago ↶
581
report_rotation ( & repo, & r, " Rotated" ) ? ;
28fc23d7 Andy McCall 1 month ago ↶
582
Ok ( 0 )
583
}
584
585
pub fn renew ( expires : u64 ) -> Result< i32 > {
586
let mut repo = open ( ) ? ;
587
let r = repo.renew_identity ( expiry_from_days ( expires) ) ? ;
5cde7882 Andy McCall 1 month ago ↶
588
report_rotation ( & repo, & r, " Renewed" ) ? ;
28fc23d7 Andy McCall 1 month ago ↶
589
Ok ( 0 )
590
}
591
592
pub fn revoke ( key : Option< & str > , reason : & str ) -> Result< i32 > {
593
let repo = open ( ) ? ;
594
let key = match key {
595
Some ( k) => resolve_key ( & repo, k) ? ,
75167672 Andy McCall 1 month ago
596
None => repo
597
.config
598
.author_key
599
.ok_or_else ( | | anyhow:: anyhow! ( " no identity configured" ) ) ? ,
28fc23d7 Andy McCall 1 month ago ↶
600
} ;
601
let update = repo.revoke_identity ( & key, reason) ? ;
75167672 Andy McCall 1 month ago
602
println! (
603
" {} key {} {} " ,
604
ui:: done( " Revoked" ) ,
605
ui:: hash( key.short ( 12 ) ) ,
606
if reason.is_empty ( ) {
607
String :: new( )
608
} else {
609
format! ( " : {reason} " )
610
}
611
) ;
28fc23d7 Andy McCall 1 month ago ↶
612
match update {
613
Some ( u) => report_update ( & u, None ) ? ,
75167672 Andy McCall 1 month ago
614
None => println! (
615
" {} {} " ,
616
ui:: note( ) ,
617
ui:: dim( " recorded locally; no policy named the key" )
618
) ,
28fc23d7 Andy McCall 1 month ago ↶
619
}
7ba155f2 Andy McCall 1 month ago ↶
620
621
622
let own = repo.config.author_key;
623
let delegated = own.is_some_and ( | o | {
624
repo.identity ( & o)
625
.is_ok_and ( | id | id.delegate ( & key) .is_some ( ) )
626
} ) ;
627
if delegated {
628
let mut repo = repo;
629
let r = repo.delegate_remove ( & key) ? ;
5cde7882 Andy McCall 1 month ago ↶
630
report_rotation ( & repo, & r, " Undelegated" ) ? ;
7ba155f2 Andy McCall 1 month ago ↶
631
return Ok ( 0 ) ;
632
}
28fc23d7 Andy McCall 1 month ago ↶
633
if repo.config.author_key = = Some ( key) {
75167672 Andy McCall 1 month ago
634
println! (
635
" {} {} " ,
636
ui:: caution( ) ,
637
ui:: warn( " this was your configured key; run oxy identity create or rotate" )
638
) ;
28fc23d7 Andy McCall 1 month ago ↶
639
}
640
Ok ( 0 )
641
}
642
614f55e1 Andy McCall 1 month ago ↶
643
pub fn delegate_add ( identity_files : & [PathBuf], scope : & str , expires : Option< & str > ) -> Result< i32 > {
28fc23d7 Andy McCall 1 month ago ↶
644
let mut repo = open ( ) ? ;
614f55e1 Andy McCall 1 month ago ↶
645
let ids = identity_files
646
.iter ( )
647
.map ( | f | {
648
Identity:: decode(
649
& std:: fs:: read( f) .map_err ( | e | anyhow:: anyhow! ( " {}: {e}" , f.display ( ) ) ) ? ,
650
)
651
.map_err ( Into :: into)
652
} )
653
.collect:: < Result< Vec< _ > > > ( ) ? ;
7ba155f2 Andy McCall 1 month ago ↶
654
let scope = Scope:: parse( scope) .map_err ( | e | anyhow:: anyhow! ( " {e}" ) ) ? ;
655
let expires = expires.map ( expiry_from_duration) .transpose ( ) ? ;
614f55e1 Andy McCall 1 month ago ↶
656
let r = repo.delegate_add_many ( ids, scope, expires) ? ;
5cde7882 Andy McCall 1 month ago ↶
657
report_rotation ( & repo, & r, " Delegated" ) ? ;
614f55e1 Andy McCall 1 month ago ↶
658
println! (
659
" {} {} " ,
660
ui:: note( ) ,
661
ui:: dim( " an agent configured with your author.key needs the new key: oxy config --user author.key NEW in its home" )
662
) ;
7ba155f2 Andy McCall 1 month ago ↶
663
if ! scope.has ( Scope:: REFS ) {
664
println! (
665
" {} {} " ,
666
ui:: note( ) ,
667
ui:: dim( " it may not move references: a push or tag it attests is refused" )
668
) ;
669
}
28fc23d7 Andy McCall 1 month ago ↶
670
Ok ( 0 )
671
}
672
673
pub fn delegate_remove ( key : & str ) -> Result< i32 > {
674
let mut repo = open ( ) ? ;
675
let k = resolve_key ( & repo, key) ? ;
676
let r = repo.delegate_remove ( & k) ? ;
5cde7882 Andy McCall 1 month ago ↶
677
report_rotation ( & repo, & r, " Undelegated" ) ? ;
28fc23d7 Andy McCall 1 month ago ↶
678
Ok ( 0 )
679
}
680
681
pub fn delegate_list ( ) -> Result< i32 > {
682
let repo = open ( ) ? ;
683
let Some ( key) = repo.config.author_key else {
684
println! ( " No identity configured; run `oxy identity create`." ) ;
685
return Ok ( 0 ) ;
686
} ;
687
let id = repo.identity ( & key) ? ;
688
if id.delegates.is_empty ( ) {
689
println! ( " {} " , ui:: dim( " No delegates." ) ) ;
690
}
7ba155f2 Andy McCall 1 month ago ↶
691
let now = Hlc:: now( ) .wall_ms;
28fc23d7 Andy McCall 1 month ago ↶
692
for d in & id.delegates {
75167672 Andy McCall 1 month ago
693
let who = repo
7ba155f2 Andy McCall 1 month ago ↶
694
.identity ( & d.key)
75167672 Andy McCall 1 month ago
695
.map ( | i | format! ( " {} <{} >" , i.name, i.email) )
696
.unwrap_or_else ( | _| " (unknown identity)" .into ( ) ) ;
7ba155f2 Andy McCall 1 month ago ↶
697
let until = match d.expires {
698
Some ( e) if now > e => ui:: err( format! ( " expired {} " , ui:: ago( e) ) ) ,
699
Some ( e) => ui:: dim( format! ( " until {} " , when ( e) ) ) ,
700
None => String :: new( ) ,
701
} ;
75167672 Andy McCall 1 month ago
702
println! (
7ba155f2 Andy McCall 1 month ago ↶
703
" {} {} {} {} {} " ,
75167672 Andy McCall 1 month ago
704
ui:: ok( ui:: glyph( " 🔑" , " *" ) ) ,
7ba155f2 Andy McCall 1 month ago ↶
705
ui:: hash( d.key.short ( 12 ) ) ,
706
ui:: author( who) ,
707
ui:: dim( d.scope.to_string ( ) ) ,
708
until
75167672 Andy McCall 1 month ago
709
) ;
28fc23d7 Andy McCall 1 month ago ↶
710
}
711
Ok ( 0 )
712
}
713
714
pub fn policy_root_add ( identity_file : & Path, out : Option< PathBuf> ) -> Result< i32 > {
715
let repo = open ( ) ? ;
716
let id = Identity:: decode( & std:: fs:: read( identity_file) ? ) ? ;
717
let key = id.hash ( ) ;
718
let u = repo.policy_root_add ( id.clone ( ) ) ? ;
75167672 Andy McCall 1 month ago
719
println! (
720
" {} root {} <{} > ({} )" ,
721
ui:: done( " Added" ) ,
722
ui:: author( & id.name) ,
723
id.email,
724
key.short ( 12 )
725
) ;
28fc23d7 Andy McCall 1 month ago ↶
726
report_update ( & u, out) ? ;
727
Ok ( 0 )
728
}
729
730
pub fn policy_root_remove ( key : & str , out : Option< PathBuf> ) -> Result< i32 > {
731
let repo = open ( ) ? ;
732
let k = resolve_key ( & repo, key) ? ;
733
let u = repo.policy_root_remove ( & k) ? ;
734
println! ( " {} root {} " , ui:: done( " Removed" ) , ui:: hash( k.short ( 12 ) ) ) ;
735
report_update ( & u, out) ? ;
736
Ok ( 0 )
737
}
738
739
pub fn policy_threshold ( n : u32 , out : Option< PathBuf> ) -> Result< i32 > {
740
let repo = open ( ) ? ;
741
let u = repo.policy_set_threshold ( n) ? ;
742
println! ( " {} root threshold to {n} " , ui:: done( " Set" ) ) ;
743
report_update ( & u, out) ? ;
744
Ok ( 0 )
745
}
746
747
pub fn policy_sign ( proposal : & Path, out : Option< PathBuf> ) -> Result< i32 > {
748
let repo = open ( ) ? ;
749
let sp = SignedPolicy:: decode( & std:: fs:: read( proposal) ? ) ? ;
750
let u = repo.policy_sign ( sp) ? ;
751
println! ( " {} policy serial {} " , ui:: done( " Signed" ) , u.policy.serial) ;
752
report_update ( & u, Some ( out.unwrap_or_else ( | | proposal.to_path_buf ( ) ) ) ) ? ;
753
Ok ( 0 )
754
}
755
83e0677b Andy McCall 1 month ago ↶
756
pub fn policy_init ( ) -> Result< i32 > {
757
let repo = open ( ) ? ;
758
let sp = repo.policy_init ( ) ? ;
75167672 Andy McCall 1 month ago
759
println! (
760
" {} policy with root key {} " ,
761
ui:: done( " Created" ) ,
762
sp.policy.roots[ 0 ] .short ( 12 )
763
) ;
28fc23d7 Andy McCall 1 month ago ↶
764
report_update ( & sp, None ) ? ;
83e0677b Andy McCall 1 month ago ↶
765
Ok ( 0 )
766
}
767
c6aa5bd9 Andy McCall 1 month ago ↶
768
pub fn policy_show ( as_json : bool ) -> Result< i32 > {
83e0677b Andy McCall 1 month ago ↶
769
let repo = open ( ) ? ;
770
let Some ( ( r, sp) ) = repo.current_policy ( ) ? else {
c6aa5bd9 Andy McCall 1 month ago ↶
771
if as_json {
772
println! ( " null" ) ;
773
} else {
75167672 Andy McCall 1 month ago
774
println! (
775
" No policy: this repository is unverified. Run `oxy policy init` to create one."
776
) ;
c6aa5bd9 Andy McCall 1 month ago ↶
777
}
83e0677b Andy McCall 1 month ago ↶
778
return Ok ( 0 ) ;
779
} ;
780
let p = & sp.policy;
c6aa5bd9 Andy McCall 1 month ago ↶
781
if as_json {
782
let ident = | k : & oxy_core:: hash:: KeyId| {
75167672 Andy McCall 1 month ago
783
let ( name, email) = p
784
.identity ( k)
785
.map ( | i | ( i.name.clone ( ) , i.email.clone ( ) ) )
786
.unwrap_or_default ( ) ;
787
json:: obj( [
788
( " key" , json:: s( & k.to_hex ( ) ) ) ,
789
( " name" , json:: s( & name) ) ,
790
( " email" , json:: s( & email) ) ,
791
] )
c6aa5bd9 Andy McCall 1 month ago ↶
792
} ;
793
println! (
794
" {} " ,
795
json:: obj( [
796
( " hash" , json:: s( & r.to_hex ( ) ) ) ,
797
( " serial" , json:: n( p.serial) ) ,
798
( " expires_ms" , json:: n( p.expires) ) ,
75167672 Andy McCall 1 month ago
799
(
800
" expired" ,
801
json:: b( p.is_expired ( oxy_core:: objects:: Hlc:: now( ) .wall_ms) )
802
) ,
c6aa5bd9 Andy McCall 1 month ago ↶
803
( " root_threshold" , json:: n( p.root_threshold) ) ,
804
( " roots" , json:: arr( p.roots.iter ( ) .map ( ident) ) ) ,
805
(
806
" rules" ,
807
json:: arr( p.rules.iter ( ) .map ( | rule | {
808
json:: obj( [
809
( " pattern" , json:: s( & rule.pattern) ) ,
810
( " keys" , json:: arr( rule.keys.iter ( ) .map ( ident) ) ) ,
811
( " threshold" , json:: n( rule.threshold) ) ,
812
( " require" , json:: s( rule.require.name ( ) ) ) ,
813
( " allow_conflicts" , json:: b( rule.allow_conflicts) ) ,
3a6effc7 Andy McCall 18 days ago ↶
814
(
815
" subjects" ,
816
json:: arr( rule.subjects.iter ( ) .map ( | g | {
817
json:: obj( [
818
( " subject" , json:: s( & g.pattern) ) ,
819
( " issuer" , ident ( & g.issuer) ) ,
820
( " scope" , json:: s( & g.scope.to_string ( ) ) ) ,
821
] )
822
} ) ) ,
823
) ,
c6aa5bd9 Andy McCall 1 month ago ↶
824
] )
825
} ) ) ,
826
) ,
827
( " revoked" , json:: arr( p.revoked.iter ( ) .map ( ident) ) ) ,
828
] )
829
) ;
830
return Ok ( 0 ) ;
831
}
75167672 Andy McCall 1 month ago
832
let name = | k : & oxy_core:: hash:: KeyId| {
833
p.identity ( k)
834
.map ( | i | format! ( " {} <{} >" , i.name, i.email) )
835
.unwrap_or_else ( | | " (unknown identity)" .into ( ) )
836
} ;
94084087 Andy McCall 1 month ago ↶
837
let expired = p.is_expired ( oxy_core:: objects:: Hlc:: now( ) .wall_ms) ;
75167672 Andy McCall 1 month ago
838
println! (
839
" {} {} {} serial {} {} expires {} {} " ,
840
ui:: section( " policy" ) ,
841
ui:: hash( format! ( " #{} " , r.short ( 12 ) ) ) ,
842
ui:: sep( ) ,
843
p.serial,
844
ui:: sep( ) ,
845
when ( p.expires) ,
846
if expired {
847
ui:: err( format! ( " {} EXPIRED" , ui:: glyph( " ✖" , " x" ) ) )
848
} else {
849
String :: new( )
850
}
851
) ;
852
println! (
853
" {} " ,
854
ui:: section( format! (
855
" roots {} " ,
856
ui:: dim( format! ( " (threshold {} )" , p.root_threshold) )
857
) )
858
) ;
83e0677b Andy McCall 1 month ago ↶
859
for k in & p.roots {
75167672 Andy McCall 1 month ago
860
println! (
861
" {} {} {} " ,
862
ui:: ok( ui:: glyph( " 🔑" , " *" ) ) ,
863
ui:: hash( k.short ( 12 ) ) ,
864
ui:: author( name ( k) )
865
) ;
83e0677b Andy McCall 1 month ago ↶
866
}
94084087 Andy McCall 1 month ago ↶
867
println! ( " {} " , ui:: section( " rules" ) ) ;
83e0677b Andy McCall 1 month ago ↶
868
for rule in & p.rules {
75167672 Andy McCall 1 month ago
869
println! (
870
" {} {:12} {} " ,
871
ui:: ok( ui:: glyph( " ▸" , " >" ) ) ,
872
ui:: bookmark( & rule.pattern) ,
873
ui:: dim( format! (
874
" threshold {} , require {} , conflicts {} " ,
875
rule.threshold,
876
rule.require.name ( ) ,
877
if rule.allow_conflicts {
878
" allowed"
879
} else {
880
" refused"
881
}
882
) )
883
) ;
83e0677b Andy McCall 1 month ago ↶
884
for k in & rule.keys {
94084087 Andy McCall 1 month ago ↶
885
println! ( " {} {} " , ui:: hash( k.short ( 12 ) ) , ui:: author( name ( k) ) ) ;
83e0677b Andy McCall 1 month ago ↶
886
}
3a6effc7 Andy McCall 18 days ago ↶
887
for g in & rule.subjects {
888
println! (
889
" {} {} {} " ,
890
ui:: author( & g.pattern) ,
891
ui:: dim( format! (
892
" certified by {} ({} )," ,
893
name ( & g.issuer) ,
894
g.issuer.short ( 12 )
895
) ) ,
896
ui:: dim( format! ( " scope {} " , g.scope) )
897
) ;
898
}
83e0677b Andy McCall 1 month ago ↶
899
}
900
if ! p.revoked.is_empty ( ) {
94084087 Andy McCall 1 month ago ↶
901
println! ( " {} " , ui:: section( " revoked" ) ) ;
83e0677b Andy McCall 1 month ago ↶
902
for k in & p.revoked {
75167672 Andy McCall 1 month ago
903
println! (
904
" {} {} {} " ,
905
ui:: err( ui:: glyph( " ✖" , " x" ) ) ,
906
ui:: hash( k.short ( 12 ) ) ,
907
ui:: author( name ( k) )
908
) ;
83e0677b Andy McCall 1 month ago ↶
909
}
910
}
911
Ok ( 0 )
912
}
913
75167672 Andy McCall 1 month ago
914
pub fn policy_grant (
915
identity_file : & Path,
916
pattern : Option< & str > ,
917
out : Option< PathBuf> ,
918
) -> Result< i32 > {
83e0677b Andy McCall 1 month ago ↶
919
let repo = open ( ) ? ;
920
let id = Identity:: decode( & std:: fs:: read( identity_file) ? ) ? ;
921
let key = id.hash ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
922
let u = repo.policy_grant ( id.clone ( ) , pattern) ? ;
75167672 Andy McCall 1 month ago
923
println! (
924
" {} {} <{} > ({} ) on {} " ,
925
ui:: done( " Granted" ) ,
926
ui:: author( & id.name) ,
927
id.email,
928
key.short ( 12 ) ,
929
ui:: bookmark( pattern.unwrap_or ( " *" ) )
3a6effc7 Andy McCall 18 days ago ↶
930
) ;
931
report_update ( & u, out) ? ;
932
Ok ( 0 )
933
}
934
935
pub fn policy_grant_subject (
936
subject : & str ,
937
issuer_file : & Path,
938
scope : & str ,
939
pattern : Option< & str > ,
940
out : Option< PathBuf> ,
941
) -> Result< i32 > {
942
let repo = open ( ) ? ;
943
let issuer = Identity:: decode( & std:: fs:: read( issuer_file) ? )
944
.map_err ( | e | anyhow:: anyhow! ( " {}: not an identity file: {e}" , issuer_file.display ( ) ) ) ? ;
945
let scope = oxy_core:: identity:: Scope:: parse( scope) .map_err ( | e | anyhow:: anyhow! ( e) ) ? ;
946
let key = issuer.hash ( ) ;
947
let u = repo.policy_grant_subject ( subject, issuer.clone ( ) , scope, pattern) ? ;
948
println! (
949
" {} {} certified by {} ({} ) on {} , scope {} " ,
950
ui:: done( " Granted" ) ,
951
ui:: author( subject) ,
952
issuer.name,
953
key.short ( 12 ) ,
954
ui:: bookmark( pattern.unwrap_or ( " *" ) ) ,
955
scope
75167672 Andy McCall 1 month ago
956
) ;
28fc23d7 Andy McCall 1 month ago ↶
957
report_update ( & u, out) ? ;
83e0677b Andy McCall 1 month ago ↶
958
Ok ( 0 )
959
}
960
28fc23d7 Andy McCall 1 month ago ↶
961
pub fn policy_revoke ( key : & str , out : Option< PathBuf> ) -> Result< i32 > {
83e0677b Andy McCall 1 month ago ↶
962
let repo = open ( ) ? ;
28fc23d7 Andy McCall 1 month ago ↶
963
if repo.current_policy ( ) ? .is_none ( ) {
83e0677b Andy McCall 1 month ago ↶
964
anyhow:: bail! ( " this repository has no policy" ) ;
28fc23d7 Andy McCall 1 month ago ↶
965
}
966
let full = resolve_key ( & repo, key) ? ;
967
let u = repo.policy_revoke ( & full) ? ;
968
println! ( " {} {} " , ui:: done( " Revoked" ) , ui:: hash( full.short ( 12 ) ) ) ;
969
report_update ( & u, out) ? ;
83e0677b Andy McCall 1 month ago ↶
970
Ok ( 0 )
971
}
05e71ed6 Andy McCall 1 month ago ↶
972
973
974
pub fn seal ( specs : & [String], as_json : bool ) -> Result< i32 > {
975
let repo = Repo:: open( & std:: env:: current_dir( ) ? ) ? ;
976
let hashes: Vec< _ > = if specs.is_empty ( ) {
977
repo.sealable ( ) ?
978
} else {
b0e64e81 Andy McCall 1 month ago ↶
979
specs
980
.iter ( )
981
.map ( | s | repo.resolve_change ( s) )
982
.collect:: < std:: result:: Result< _ , _ > > ( ) ?
05e71ed6 Andy McCall 1 month ago ↶
983
} ;
984
if hashes.is_empty ( ) {
985
if as_json {
986
println! ( " {{ \" sealed\" :[]}} " ) ;
987
} else {
b0e64e81 Andy McCall 1 month ago ↶
988
println! (
989
" {} nothing to seal: every change you can authenticate already is" ,
990
ui:: done( " Sealed" )
991
) ;
05e71ed6 Andy McCall 1 month ago ↶
992
}
993
return Ok ( 0 ) ;
994
}
995
let mut refs = Vec :: new( ) ;
996
for chunk in hashes.chunks ( oxy_core:: attest:: MAX_SEAL_HASHES ) {
997
refs.push ( repo.seal ( chunk) ? ) ;
998
}
999
if as_json {
b0e64e81 Andy McCall 1 month ago ↶
1000
let list: Vec< String> = hashes
1001
.iter ( )
1002
.map ( | h | format! ( " \" {} \" " , h.to_hex ( ) ) )
1003
.collect ( ) ;
05e71ed6 Andy McCall 1 month ago ↶
1004
let seals: Vec< String> = refs.iter ( ) .map ( | r | format! ( " \" {} \" " , r.to_hex ( ) ) ) .collect ( ) ;
b0e64e81 Andy McCall 1 month ago ↶
1005
println! (
1006
" {{ \" sealed\" :[{} ],\" seals\" :[{} ]}} " ,
1007
list.join ( " ," ) ,
1008
seals.join ( " ," )
1009
) ;
05e71ed6 Andy McCall 1 month ago ↶
1010
} else {
b0e64e81 Andy McCall 1 month ago ↶
1011
println! (
1012
" {} {} change{} in {} seal{} ; they travel with your next push" ,
1013
ui:: done( " Sealed" ) ,
1014
hashes.len ( ) ,
1015
if hashes.len ( ) = = 1 { " " } else { " s" } ,
1016
refs.len ( ) ,
1017
if refs.len ( ) = = 1 { " " } else { " s" }
1018
) ;
05e71ed6 Andy McCall 1 month ago ↶
1019
for h in & hashes {
b0e64e81 Andy McCall 1 month ago ↶
1020
let title = repo
1021
.store ( )
1022
.get_change ( h)
1023
.map ( | c | c.message.lines ( ) .next ( ) .unwrap_or ( " " ) .to_string ( ) )
1024
.unwrap_or_default ( ) ;
05e71ed6 Andy McCall 1 month ago ↶
1025
println! ( " {} {} " , ui:: hash( h.short ( 12 ) ) , title) ;
1026
}
1027
}
1028
Ok ( 0 )
1029
}
7aa2dc93 Andy McCall 1 month ago ↶
1030
1031
1032
1033
pub fn sign_challenge ( challenge_hex : & str , as_json : bool ) -> Result< i32 > {
1034
let repo = open ( ) ? ;
1035
let Some ( ( key, sk) ) = repo.secret_key ( ) ? else {
1036
anyhow:: bail! ( " no secret key: run `oxy identity create` first" ) ;
1037
} ;
119bbf50 Andy McCall 1 month ago ↶
1038
let challenge = hex_bytes ( challenge_hex.trim ( ) )
1039
.ok_or_else ( | | anyhow:: anyhow! ( " the challenge must be hex" ) ) ? ;
1040
let sig = sk
1041
.sign_challenge ( & key, & challenge)
1042
.map_err ( | e | anyhow:: anyhow! ( " {e}" ) ) ? ;
7aa2dc93 Andy McCall 1 month ago ↶
1043
let encoded = hex_string ( & oxy_core:: identity:: Signature:: encode_list( & [ sig] ) ) ;
1044
if as_json {
119bbf50 Andy McCall 1 month ago ↶
1045
println! (
1046
" {{ \" key\" :\" {} \" ,\" signature\" :\" {} \" }} " ,
1047
key.to_hex ( ) ,
1048
encoded
1049
) ;
7aa2dc93 Andy McCall 1 month ago ↶
1050
} else {
1051
println! ( " {encoded} " ) ;
1052
}
1053
Ok ( 0 )
1054
}
1055
1056
fn hex_bytes ( s : & str ) -> Option< Vec< u8 > > {
1057
if ! s.len ( ) .is_multiple_of ( 2 ) {
1058
return None ;
1059
}
119bbf50 Andy McCall 1 month ago ↶
1060
( 0 .. s.len ( ) )
1061
.step_by ( 2 )
1062
.map ( | i | u8 :: from_str_radix( & s[ i.. i + 2 ] , 16 ) .ok ( ) )
1063
.collect ( )
7aa2dc93 Andy McCall 1 month ago ↶
1064
}
1065
1066
fn hex_string ( b : & [u8 ]) -> String {
1067
b.iter ( ) .map ( | x | format! ( " {x:02x} " ) ) .collect ( )
8e9c64d1 Andy McCall 20 days ago ↶
1068
}
1069
1070
# [ cfg ( test ) ]
1071
mod tests {
1072
# [ test ]
1073
fn secrets_are_never_printed_whole ( ) {
1074
assert! (
1075
super :: secret_key( " remote.earth.token" )
1076
& & super :: secret_key( " auth.secret" )
1077
& & ! super :: secret_key( " remote.earth.url" )
1078
) ;
1079
assert_eq! (
1080
super :: mask( " oxyhub_6b7dce13409b634d" ) ,
1081
" oxyhub_6b7…(13 more)"
1082
) ;
1083
assert_eq! ( super :: mask( " short" ) , " …" ) ;
1084
}
7aa2dc93 Andy McCall 1 month ago ↶
1085
}