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