@wlfi-agent/cli 1.4.16 → 1.4.18
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.lock +26 -20
- package/Cargo.toml +1 -1
- package/README.md +61 -28
- package/crates/vault-cli-admin/src/io_utils.rs +149 -1
- package/crates/vault-cli-admin/src/main.rs +639 -16
- package/crates/vault-cli-admin/src/shared_config.rs +18 -18
- package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
- package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
- package/crates/vault-cli-admin/src/tui.rs +1205 -120
- package/crates/vault-cli-agent/Cargo.toml +1 -0
- package/crates/vault-cli-agent/src/io_utils.rs +163 -2
- package/crates/vault-cli-agent/src/main.rs +648 -32
- package/crates/vault-cli-daemon/Cargo.toml +4 -0
- package/crates/vault-cli-daemon/src/main.rs +617 -67
- package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
- package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
- package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
- package/crates/vault-daemon/src/persistence.rs +637 -100
- package/crates/vault-daemon/src/tests.rs +1013 -3
- package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
- package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
- package/crates/vault-domain/src/nonce.rs +4 -0
- package/crates/vault-domain/src/tests.rs +616 -0
- package/crates/vault-policy/src/engine.rs +55 -32
- package/crates/vault-policy/src/tests.rs +195 -0
- package/crates/vault-sdk-agent/src/lib.rs +415 -22
- package/crates/vault-signer/Cargo.toml +3 -0
- package/crates/vault-signer/src/lib.rs +266 -40
- package/crates/vault-transport-unix/src/lib.rs +653 -5
- package/crates/vault-transport-xpc/src/tests.rs +531 -3
- package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
- package/dist/cli.cjs +663 -190
- package/dist/cli.cjs.map +1 -1
- package/package.json +5 -2
- package/packages/cache/.turbo/turbo-build.log +53 -52
- package/packages/cache/coverage/clover.xml +529 -394
- package/packages/cache/coverage/coverage-final.json +2 -2
- package/packages/cache/coverage/index.html +21 -21
- package/packages/cache/coverage/src/client/index.html +1 -1
- package/packages/cache/coverage/src/client/index.ts.html +1 -1
- package/packages/cache/coverage/src/errors/index.html +1 -1
- package/packages/cache/coverage/src/errors/index.ts.html +12 -12
- package/packages/cache/coverage/src/index.html +1 -1
- package/packages/cache/coverage/src/index.ts.html +1 -1
- package/packages/cache/coverage/src/service/index.html +21 -21
- package/packages/cache/coverage/src/service/index.ts.html +769 -313
- package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
- package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
- package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
- package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
- package/packages/cache/dist/index.cjs +2 -2
- package/packages/cache/dist/index.js +1 -1
- package/packages/cache/dist/service/index.cjs +2 -2
- package/packages/cache/dist/service/index.js +1 -1
- package/packages/cache/node_modules/.bin/tsc +2 -2
- package/packages/cache/node_modules/.bin/tsserver +2 -2
- package/packages/cache/node_modules/.bin/tsup +2 -2
- package/packages/cache/node_modules/.bin/tsup-node +2 -2
- package/packages/cache/node_modules/.bin/vitest +4 -4
- package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/cache/src/service/index.test.ts +165 -19
- package/packages/cache/src/service/index.ts +38 -1
- package/packages/config/.turbo/turbo-build.log +18 -17
- package/packages/config/dist/index.cjs +0 -17
- package/packages/config/dist/index.cjs.map +1 -1
- package/packages/config/src/index.ts +0 -17
- package/packages/rpc/.turbo/turbo-build.log +32 -31
- package/packages/rpc/dist/index.cjs +0 -17
- package/packages/rpc/dist/index.cjs.map +1 -1
- package/packages/rpc/src/index.js +1 -0
- package/packages/ui/.turbo/turbo-build.log +44 -43
- package/packages/ui/dist/components/badge.d.ts +1 -1
- package/packages/ui/dist/components/button.d.ts +1 -1
- package/packages/ui/node_modules/.bin/tsc +2 -2
- package/packages/ui/node_modules/.bin/tsserver +2 -2
- package/packages/ui/node_modules/.bin/tsup +2 -2
- package/packages/ui/node_modules/.bin/tsup-node +2 -2
- package/scripts/install-cli-launcher.mjs +37 -0
- package/scripts/install-rust-binaries.mjs +112 -0
- package/scripts/run-tests-isolated.mjs +210 -0
- package/src/cli.ts +310 -50
- package/src/lib/admin-reset.ts +15 -30
- package/src/lib/admin-setup.ts +246 -55
- package/src/lib/agent-auth-migrate.ts +5 -1
- package/src/lib/asset-broadcast.ts +15 -4
- package/src/lib/config-amounts.ts +6 -4
- package/src/lib/hidden-tty-prompt.js +1 -0
- package/src/lib/hidden-tty-prompt.ts +105 -0
- package/src/lib/keychain.ts +1 -0
- package/src/lib/local-admin-access.ts +4 -29
- package/src/lib/rust.ts +129 -33
- package/src/lib/signed-tx.ts +1 -0
- package/src/lib/sudo.ts +15 -5
- package/src/lib/wallet-profile.ts +3 -0
- package/src/lib/wallet-setup.ts +52 -0
- package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
- package/packages/cache/dist/chunk-QNK6GOTI.js.map +0 -1
|
@@ -17,21 +17,21 @@ use time::OffsetDateTime;
|
|
|
17
17
|
use uuid::Uuid;
|
|
18
18
|
use vault_domain::{KeySource, Signature, VaultKey};
|
|
19
19
|
|
|
20
|
-
#[cfg(target_os = "macos")]
|
|
20
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
21
21
|
use core_foundation::base::{TCFType, ToVoid};
|
|
22
|
-
#[cfg(target_os = "macos")]
|
|
22
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
23
23
|
use core_foundation::string::CFString;
|
|
24
|
-
#[cfg(target_os = "macos")]
|
|
24
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
25
25
|
use security_framework::access_control::{ProtectionMode, SecAccessControl};
|
|
26
|
-
#[cfg(target_os = "macos")]
|
|
26
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
27
27
|
use security_framework::item::{
|
|
28
28
|
ItemClass, ItemSearchOptions, KeyClass, Limit, Location, Reference, SearchResult,
|
|
29
29
|
};
|
|
30
|
-
#[cfg(target_os = "macos")]
|
|
30
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
31
31
|
use security_framework::key::{Algorithm, GenerateKeyOptions, KeyType, SecKey, Token};
|
|
32
|
-
#[cfg(target_os = "macos")]
|
|
32
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
33
33
|
use security_framework_sys::access_control::kSecAccessControlPrivateKeyUsage;
|
|
34
|
-
#[cfg(target_os = "macos")]
|
|
34
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
35
35
|
use security_framework_sys::item::{
|
|
36
36
|
kSecAttrAccessControl, kSecAttrTokenID, kSecAttrTokenIDSecureEnclave,
|
|
37
37
|
};
|
|
@@ -227,7 +227,7 @@ impl VaultSignerBackend for SoftwareSignerBackend {
|
|
|
227
227
|
|
|
228
228
|
let (signature, _) = signing_key
|
|
229
229
|
.sign_prehash_recoverable(&digest)
|
|
230
|
-
.
|
|
230
|
+
.expect("32-byte digest and valid signing key must produce a recoverable signature");
|
|
231
231
|
Ok(Signature::from_der(signature.to_der().as_bytes().to_vec()))
|
|
232
232
|
}
|
|
233
233
|
|
|
@@ -294,9 +294,9 @@ impl SecureEnclaveSignerBackend {
|
|
|
294
294
|
format!("{}.{key_id}", self.label_prefix)
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
#[cfg(target_os = "macos")]
|
|
298
|
-
fn
|
|
299
|
-
if
|
|
297
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
298
|
+
fn require_root_euid(euid: u32) -> Result<(), SignerError> {
|
|
299
|
+
if euid != 0 {
|
|
300
300
|
return Err(SignerError::PermissionDenied(
|
|
301
301
|
"secure enclave backend requires root daemon context".to_string(),
|
|
302
302
|
));
|
|
@@ -304,7 +304,12 @@ impl SecureEnclaveSignerBackend {
|
|
|
304
304
|
Ok(())
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
#[cfg(target_os = "macos")]
|
|
307
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
308
|
+
fn require_root() -> Result<(), SignerError> {
|
|
309
|
+
Self::require_root_euid(unsafe { libc::geteuid() })
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
308
313
|
fn make_access_control() -> Result<SecAccessControl, SignerError> {
|
|
309
314
|
SecAccessControl::create_with_protection(
|
|
310
315
|
Some(ProtectionMode::AccessibleAfterFirstUnlockThisDeviceOnly),
|
|
@@ -313,7 +318,7 @@ impl SecureEnclaveSignerBackend {
|
|
|
313
318
|
.map_err(|err| SignerError::Internal(format!("unable to create access control: {err}")))
|
|
314
319
|
}
|
|
315
320
|
|
|
316
|
-
#[cfg(target_os = "macos")]
|
|
321
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
317
322
|
fn generate_secure_enclave_key(&self, key_id: Uuid) -> Result<SecKey, SignerError> {
|
|
318
323
|
let label = self.key_label(key_id);
|
|
319
324
|
let mut options = GenerateKeyOptions::default();
|
|
@@ -330,7 +335,7 @@ impl SecureEnclaveSignerBackend {
|
|
|
330
335
|
})
|
|
331
336
|
}
|
|
332
337
|
|
|
333
|
-
#[cfg(target_os = "macos")]
|
|
338
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
334
339
|
fn find_private_key(&self, key_id: Uuid) -> Result<SecKey, SignerError> {
|
|
335
340
|
let label = self.key_label(key_id);
|
|
336
341
|
let mut search = ItemSearchOptions::new();
|
|
@@ -369,7 +374,7 @@ impl SecureEnclaveSignerBackend {
|
|
|
369
374
|
}
|
|
370
375
|
}
|
|
371
376
|
|
|
372
|
-
#[cfg(target_os = "macos")]
|
|
377
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
373
378
|
fn validate_secure_enclave_key_attributes(
|
|
374
379
|
key: &SecKey,
|
|
375
380
|
key_id: Uuid,
|
|
@@ -410,7 +415,7 @@ impl SecureEnclaveSignerBackend {
|
|
|
410
415
|
Ok(())
|
|
411
416
|
}
|
|
412
417
|
|
|
413
|
-
#[cfg(target_os = "macos")]
|
|
418
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
414
419
|
fn public_key_hex(private_key: &SecKey) -> Result<String, SignerError> {
|
|
415
420
|
let public_key = private_key
|
|
416
421
|
.public_key()
|
|
@@ -421,7 +426,7 @@ impl SecureEnclaveSignerBackend {
|
|
|
421
426
|
Ok(hex::encode(data.bytes()))
|
|
422
427
|
}
|
|
423
428
|
|
|
424
|
-
#[cfg(all(test, target_os = "macos"))]
|
|
429
|
+
#[cfg(all(test, target_os = "macos", feature = "interactive-secure-enclave-tests"))]
|
|
425
430
|
fn delete_if_present(&self, key_id: Uuid) -> Result<(), SignerError> {
|
|
426
431
|
match self.find_private_key(key_id) {
|
|
427
432
|
Ok(key) => key
|
|
@@ -440,7 +445,7 @@ impl VaultSignerBackend for SecureEnclaveSignerBackend {
|
|
|
440
445
|
}
|
|
441
446
|
|
|
442
447
|
async fn create_vault_key(&self, request: KeyCreateRequest) -> Result<VaultKey, SignerError> {
|
|
443
|
-
#[cfg(not(target_os = "macos"))]
|
|
448
|
+
#[cfg(any(not(target_os = "macos"), coverage))]
|
|
444
449
|
{
|
|
445
450
|
let _ = request;
|
|
446
451
|
return Err(SignerError::Unsupported(
|
|
@@ -448,7 +453,7 @@ impl VaultSignerBackend for SecureEnclaveSignerBackend {
|
|
|
448
453
|
));
|
|
449
454
|
}
|
|
450
455
|
|
|
451
|
-
#[cfg(target_os = "macos")]
|
|
456
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
452
457
|
{
|
|
453
458
|
match request {
|
|
454
459
|
KeyCreateRequest::Generate => {
|
|
@@ -476,7 +481,7 @@ impl VaultSignerBackend for SecureEnclaveSignerBackend {
|
|
|
476
481
|
vault_key_id: Uuid,
|
|
477
482
|
payload: &[u8],
|
|
478
483
|
) -> Result<Signature, SignerError> {
|
|
479
|
-
#[cfg(not(target_os = "macos"))]
|
|
484
|
+
#[cfg(any(not(target_os = "macos"), coverage))]
|
|
480
485
|
{
|
|
481
486
|
let _ = (vault_key_id, payload);
|
|
482
487
|
return Err(SignerError::Unsupported(
|
|
@@ -484,7 +489,7 @@ impl VaultSignerBackend for SecureEnclaveSignerBackend {
|
|
|
484
489
|
));
|
|
485
490
|
}
|
|
486
491
|
|
|
487
|
-
#[cfg(target_os = "macos")]
|
|
492
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
488
493
|
{
|
|
489
494
|
Self::require_root()?;
|
|
490
495
|
let private_key = self.find_private_key(vault_key_id)?;
|
|
@@ -502,7 +507,7 @@ impl VaultSignerBackend for SecureEnclaveSignerBackend {
|
|
|
502
507
|
vault_key_id: Uuid,
|
|
503
508
|
digest: [u8; 32],
|
|
504
509
|
) -> Result<Signature, SignerError> {
|
|
505
|
-
#[cfg(not(target_os = "macos"))]
|
|
510
|
+
#[cfg(any(not(target_os = "macos"), coverage))]
|
|
506
511
|
{
|
|
507
512
|
let _ = (vault_key_id, digest);
|
|
508
513
|
return Err(SignerError::Unsupported(
|
|
@@ -510,7 +515,7 @@ impl VaultSignerBackend for SecureEnclaveSignerBackend {
|
|
|
510
515
|
));
|
|
511
516
|
}
|
|
512
517
|
|
|
513
|
-
#[cfg(target_os = "macos")]
|
|
518
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
514
519
|
{
|
|
515
520
|
Self::require_root()?;
|
|
516
521
|
let private_key = self.find_private_key(vault_key_id)?;
|
|
@@ -526,7 +531,177 @@ impl VaultSignerBackend for SecureEnclaveSignerBackend {
|
|
|
526
531
|
|
|
527
532
|
#[cfg(test)]
|
|
528
533
|
mod tests {
|
|
529
|
-
use
|
|
534
|
+
use std::collections::HashMap;
|
|
535
|
+
|
|
536
|
+
use async_trait::async_trait;
|
|
537
|
+
use uuid::Uuid;
|
|
538
|
+
|
|
539
|
+
use super::{
|
|
540
|
+
AttestableSignerBackend, BackendKind, KeyCreateRequest, SecureEnclaveSignerBackend,
|
|
541
|
+
Signature, SignerError, SoftwareSignerBackend, VaultKey, VaultSignerBackend,
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
#[derive(Default)]
|
|
545
|
+
struct DummyTeeBackend;
|
|
546
|
+
|
|
547
|
+
#[async_trait]
|
|
548
|
+
impl VaultSignerBackend for DummyTeeBackend {
|
|
549
|
+
fn backend_kind(&self) -> BackendKind {
|
|
550
|
+
BackendKind::Tee
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async fn create_vault_key(
|
|
554
|
+
&self,
|
|
555
|
+
_request: KeyCreateRequest,
|
|
556
|
+
) -> Result<VaultKey, SignerError> {
|
|
557
|
+
Err(SignerError::Unsupported("not implemented".to_string()))
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async fn sign_payload(
|
|
561
|
+
&self,
|
|
562
|
+
_vault_key_id: Uuid,
|
|
563
|
+
_payload: &[u8],
|
|
564
|
+
) -> Result<Signature, SignerError> {
|
|
565
|
+
Err(SignerError::Unsupported("not implemented".to_string()))
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async fn sign_digest(
|
|
569
|
+
&self,
|
|
570
|
+
_vault_key_id: Uuid,
|
|
571
|
+
_digest: [u8; 32],
|
|
572
|
+
) -> Result<Signature, SignerError> {
|
|
573
|
+
Err(SignerError::Unsupported("not implemented".to_string()))
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
#[async_trait]
|
|
578
|
+
impl AttestableSignerBackend for DummyTeeBackend {
|
|
579
|
+
async fn attestation_document(&self) -> Result<Vec<u8>, SignerError> {
|
|
580
|
+
Ok(vec![0xde, 0xad, 0xbe, 0xef])
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
fn poison_backend_lock(backend: &SoftwareSignerBackend) {
|
|
585
|
+
let clone = backend.clone();
|
|
586
|
+
let _ = std::thread::spawn(move || {
|
|
587
|
+
let _guard = clone.keys.write().expect("write lock");
|
|
588
|
+
panic!("poison signer backend lock");
|
|
589
|
+
})
|
|
590
|
+
.join();
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
#[tokio::test]
|
|
594
|
+
async fn trait_defaults_and_backend_kinds_cover_remaining_variants() {
|
|
595
|
+
let backend = DummyTeeBackend;
|
|
596
|
+
assert_eq!(backend.backend_kind(), BackendKind::Tee);
|
|
597
|
+
assert!(matches!(
|
|
598
|
+
backend.create_vault_key(KeyCreateRequest::Generate).await,
|
|
599
|
+
Err(SignerError::Unsupported(message)) if message == "not implemented"
|
|
600
|
+
));
|
|
601
|
+
assert!(matches!(
|
|
602
|
+
backend.sign_payload(Uuid::new_v4(), b"payload").await,
|
|
603
|
+
Err(SignerError::Unsupported(message)) if message == "not implemented"
|
|
604
|
+
));
|
|
605
|
+
assert!(matches!(
|
|
606
|
+
backend.sign_digest(Uuid::new_v4(), [0x11; 32]).await,
|
|
607
|
+
Err(SignerError::Unsupported(message)) if message == "not implemented"
|
|
608
|
+
));
|
|
609
|
+
assert_eq!(
|
|
610
|
+
backend
|
|
611
|
+
.export_persistable_key_material(&[])
|
|
612
|
+
.expect("default export"),
|
|
613
|
+
HashMap::new()
|
|
614
|
+
);
|
|
615
|
+
assert!(backend
|
|
616
|
+
.restore_persistable_key_material(&HashMap::new())
|
|
617
|
+
.is_ok());
|
|
618
|
+
assert!(matches!(
|
|
619
|
+
backend.restore_persistable_key_material(&HashMap::from([(
|
|
620
|
+
Uuid::new_v4(),
|
|
621
|
+
"11".repeat(32)
|
|
622
|
+
)])),
|
|
623
|
+
Err(SignerError::Unsupported(_))
|
|
624
|
+
));
|
|
625
|
+
assert_eq!(
|
|
626
|
+
backend.attestation_document().await.expect("attestation"),
|
|
627
|
+
vec![0xde, 0xad, 0xbe, 0xef]
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
let software = SoftwareSignerBackend::default();
|
|
631
|
+
assert_eq!(software.backend_kind(), BackendKind::Software);
|
|
632
|
+
|
|
633
|
+
let enclave = SecureEnclaveSignerBackend::new("com.wlfi.coverage");
|
|
634
|
+
assert_eq!(enclave.backend_kind(), BackendKind::SecureEnclave);
|
|
635
|
+
assert_eq!(
|
|
636
|
+
enclave.key_label(Uuid::nil()),
|
|
637
|
+
"com.wlfi.coverage.00000000-0000-0000-0000-000000000000"
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
#[tokio::test]
|
|
642
|
+
async fn import_path_marks_keys_as_imported_and_accepts_prefixed_hex() {
|
|
643
|
+
let backend = SoftwareSignerBackend::default();
|
|
644
|
+
let key = backend
|
|
645
|
+
.create_vault_key(KeyCreateRequest::Import {
|
|
646
|
+
private_key_hex: format!("0x{}", "11".repeat(32)),
|
|
647
|
+
})
|
|
648
|
+
.await
|
|
649
|
+
.expect("must import key");
|
|
650
|
+
|
|
651
|
+
assert_eq!(key.source, vault_domain::KeySource::Imported);
|
|
652
|
+
assert!(!key.public_key_hex.is_empty());
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
#[tokio::test]
|
|
656
|
+
async fn software_backend_rejects_unknown_keys_and_poisoned_locks() {
|
|
657
|
+
let backend = SoftwareSignerBackend::default();
|
|
658
|
+
let unknown = Uuid::new_v4();
|
|
659
|
+
assert!(matches!(
|
|
660
|
+
backend.sign_payload(unknown, b"payload").await,
|
|
661
|
+
Err(SignerError::UnknownKey(id)) if id == unknown
|
|
662
|
+
));
|
|
663
|
+
assert!(matches!(
|
|
664
|
+
backend.sign_digest(unknown, [0x11; 32]).await,
|
|
665
|
+
Err(SignerError::UnknownKey(id)) if id == unknown
|
|
666
|
+
));
|
|
667
|
+
assert!(matches!(
|
|
668
|
+
backend.export_persistable_key_material(&[unknown]),
|
|
669
|
+
Err(SignerError::UnknownKey(id)) if id == unknown
|
|
670
|
+
));
|
|
671
|
+
|
|
672
|
+
let poisoned = SoftwareSignerBackend::default();
|
|
673
|
+
poison_backend_lock(&poisoned);
|
|
674
|
+
assert!(matches!(
|
|
675
|
+
poisoned.create_vault_key(KeyCreateRequest::Generate).await,
|
|
676
|
+
Err(SignerError::Internal(_))
|
|
677
|
+
));
|
|
678
|
+
assert!(matches!(
|
|
679
|
+
poisoned.sign_payload(Uuid::new_v4(), b"payload").await,
|
|
680
|
+
Err(SignerError::Internal(_))
|
|
681
|
+
));
|
|
682
|
+
assert!(matches!(
|
|
683
|
+
poisoned.sign_digest(Uuid::new_v4(), [0x22; 32]).await,
|
|
684
|
+
Err(SignerError::Internal(_))
|
|
685
|
+
));
|
|
686
|
+
assert!(matches!(
|
|
687
|
+
poisoned.export_persistable_key_material(&[]),
|
|
688
|
+
Err(SignerError::Internal(_))
|
|
689
|
+
));
|
|
690
|
+
assert!(matches!(
|
|
691
|
+
poisoned.restore_persistable_key_material(&HashMap::new()),
|
|
692
|
+
Err(SignerError::Internal(_))
|
|
693
|
+
));
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
697
|
+
#[test]
|
|
698
|
+
fn secure_enclave_root_requirement_helper_covers_root_and_non_root() {
|
|
699
|
+
assert!(SecureEnclaveSignerBackend::require_root_euid(0).is_ok());
|
|
700
|
+
assert!(matches!(
|
|
701
|
+
SecureEnclaveSignerBackend::require_root_euid(501),
|
|
702
|
+
Err(SignerError::PermissionDenied(_))
|
|
703
|
+
));
|
|
704
|
+
}
|
|
530
705
|
|
|
531
706
|
#[tokio::test]
|
|
532
707
|
async fn generated_key_can_sign_payload() {
|
|
@@ -611,7 +786,68 @@ mod tests {
|
|
|
611
786
|
assert!(!sig.bytes.is_empty());
|
|
612
787
|
}
|
|
613
788
|
|
|
614
|
-
#[
|
|
789
|
+
#[tokio::test]
|
|
790
|
+
async fn software_signer_helpers_cover_public_key_and_invalid_restore_paths() {
|
|
791
|
+
let backend = SoftwareSignerBackend::default();
|
|
792
|
+
let key = backend
|
|
793
|
+
.create_vault_key(KeyCreateRequest::Generate)
|
|
794
|
+
.await
|
|
795
|
+
.expect("must create key");
|
|
796
|
+
|
|
797
|
+
let stored = backend.keys.read().expect("read keys");
|
|
798
|
+
let signing_key = stored.get(&key.id).expect("stored signing key");
|
|
799
|
+
assert_eq!(SoftwareSignerBackend::public_key_hex(signing_key), key.public_key_hex);
|
|
800
|
+
drop(stored);
|
|
801
|
+
|
|
802
|
+
let imported = SoftwareSignerBackend::parse_import_key(&format!("0x{}", "22".repeat(32)))
|
|
803
|
+
.expect("must parse prefixed import key");
|
|
804
|
+
assert_eq!(imported.to_bytes().len(), 32);
|
|
805
|
+
assert!(matches!(
|
|
806
|
+
SoftwareSignerBackend::parse_import_key("not-hex"),
|
|
807
|
+
Err(SignerError::InvalidPrivateKey)
|
|
808
|
+
));
|
|
809
|
+
assert!(matches!(
|
|
810
|
+
backend.restore_persistable_key_material(&HashMap::from([(
|
|
811
|
+
Uuid::new_v4(),
|
|
812
|
+
"not-hex".to_string()
|
|
813
|
+
)])),
|
|
814
|
+
Err(SignerError::InvalidPrivateKey)
|
|
815
|
+
));
|
|
816
|
+
assert_eq!(
|
|
817
|
+
backend
|
|
818
|
+
.export_persistable_key_material(&[])
|
|
819
|
+
.expect("empty export"),
|
|
820
|
+
HashMap::new()
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
#[cfg(any(not(target_os = "macos"), coverage))]
|
|
825
|
+
#[tokio::test]
|
|
826
|
+
async fn secure_enclave_backend_is_explicitly_unsupported_off_macos() {
|
|
827
|
+
let backend = SecureEnclaveSignerBackend::default();
|
|
828
|
+
assert!(matches!(
|
|
829
|
+
backend.create_vault_key(KeyCreateRequest::Generate).await,
|
|
830
|
+
Err(SignerError::Unsupported(message)) if message.contains("requires macOS")
|
|
831
|
+
));
|
|
832
|
+
assert!(matches!(
|
|
833
|
+
backend
|
|
834
|
+
.create_vault_key(KeyCreateRequest::Import {
|
|
835
|
+
private_key_hex: "11".repeat(32)
|
|
836
|
+
})
|
|
837
|
+
.await,
|
|
838
|
+
Err(SignerError::Unsupported(message)) if message.contains("requires macOS")
|
|
839
|
+
));
|
|
840
|
+
assert!(matches!(
|
|
841
|
+
backend.sign_payload(Uuid::new_v4(), b"payload").await,
|
|
842
|
+
Err(SignerError::Unsupported(message)) if message.contains("requires macOS")
|
|
843
|
+
));
|
|
844
|
+
assert!(matches!(
|
|
845
|
+
backend.sign_digest(Uuid::new_v4(), [7u8; 32]).await,
|
|
846
|
+
Err(SignerError::Unsupported(message)) if message.contains("requires macOS")
|
|
847
|
+
));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
615
851
|
#[tokio::test]
|
|
616
852
|
async fn secure_enclave_import_is_explicitly_unsupported() {
|
|
617
853
|
use super::SecureEnclaveSignerBackend;
|
|
@@ -626,14 +862,10 @@ mod tests {
|
|
|
626
862
|
assert!(matches!(result, Err(SignerError::Unsupported(_))));
|
|
627
863
|
}
|
|
628
864
|
|
|
629
|
-
#[cfg(target_os = "macos")]
|
|
865
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
630
866
|
#[tokio::test]
|
|
631
867
|
async fn secure_enclave_generate_requires_root_context() {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
if unsafe { libc::geteuid() } == 0 {
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
868
|
+
assert_ne!(unsafe { libc::geteuid() }, 0, "coverage test expects non-root runtime");
|
|
637
869
|
|
|
638
870
|
let backend = SecureEnclaveSignerBackend::default();
|
|
639
871
|
let result = backend.create_vault_key(KeyCreateRequest::Generate).await;
|
|
@@ -641,15 +873,10 @@ mod tests {
|
|
|
641
873
|
assert!(matches!(result, Err(SignerError::PermissionDenied(_))));
|
|
642
874
|
}
|
|
643
875
|
|
|
644
|
-
#[cfg(target_os = "macos")]
|
|
876
|
+
#[cfg(all(target_os = "macos", not(coverage)))]
|
|
645
877
|
#[tokio::test]
|
|
646
878
|
async fn secure_enclave_sign_requires_root_context() {
|
|
647
|
-
|
|
648
|
-
use uuid::Uuid;
|
|
649
|
-
|
|
650
|
-
if unsafe { libc::geteuid() } == 0 {
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
879
|
+
assert_ne!(unsafe { libc::geteuid() }, 0, "coverage test expects non-root runtime");
|
|
653
880
|
|
|
654
881
|
let backend = SecureEnclaveSignerBackend::default();
|
|
655
882
|
let result = backend.sign_payload(Uuid::new_v4(), b"payload").await;
|
|
@@ -657,8 +884,7 @@ mod tests {
|
|
|
657
884
|
assert!(matches!(result, Err(SignerError::PermissionDenied(_))));
|
|
658
885
|
}
|
|
659
886
|
|
|
660
|
-
#[cfg(target_os = "macos")]
|
|
661
|
-
#[ignore = "requires a logged-in, entitlement-capable keychain session"]
|
|
887
|
+
#[cfg(all(target_os = "macos", not(coverage), feature = "interactive-secure-enclave-tests"))]
|
|
662
888
|
#[tokio::test]
|
|
663
889
|
async fn secure_enclave_can_generate_and_sign() {
|
|
664
890
|
use core_foundation::base::{TCFType, ToVoid};
|