1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//! Convenience methods for paginating + bundling key secrets via [`VtaClient`].
use super::VtaClient;
use crate::did_secrets::select_secret_kid;
use crate::error::VtaError;
impl VtaClient {
/// Fetch all secrets for a context, paginating through all keys.
///
/// Returns TDK `Secret` objects ready for use with DIDComm or signing.
pub async fn fetch_context_secrets(
&self,
context_id: &str,
) -> Result<Vec<affinidi_tdk::secrets_resolver::secrets::Secret>, VtaError> {
let page_size = 100u64;
let mut offset = 0u64;
let mut secrets = Vec::new();
loop {
let resp = self
.list_keys(offset, page_size, Some("active"), Some(context_id))
.await?;
if resp.keys.is_empty() {
break;
}
for key in &resp.keys {
let secret_resp = self.get_key_secret(&key.key_id).await?;
let secret = crate::did_key::secret_from_key_response(&secret_resp)?;
secrets.push(secret);
}
offset += resp.keys.len() as u64;
if offset >= resp.total {
break;
}
}
Ok(secrets)
}
/// Fetch all secrets for a context as a portable
/// [`DidSecretsBundle`](crate::did_secrets::DidSecretsBundle).
///
/// Resolves the context DID, paginates through all active keys,
/// fetches each secret, and returns a bundle ready for encoding/transport.
pub async fn fetch_did_secrets_bundle(
&self,
context_id: &str,
) -> Result<crate::did_secrets::DidSecretsBundle, VtaError> {
let ctx = self.get_context(context_id).await?;
let did = ctx.did.ok_or_else(|| {
VtaError::Validation(format!("context '{context_id}' has no DID assigned"))
})?;
let page_size = 100u64;
let mut offset = 0u64;
let mut secrets = Vec::new();
loop {
let resp = self
.list_keys(offset, page_size, Some("active"), Some(context_id))
.await?;
if resp.keys.is_empty() {
break;
}
for key in &resp.keys {
let secret_resp = self.get_key_secret(&key.key_id).await?;
let entry = crate::did_secrets::SecretEntry::from(secret_resp);
// The kid a mediator matches inbound JWE recipients against MUST be
// a verification-method id of *this* context's DID. Resolve it from
// the authoritative store key_id (falling back to the label only
// when the label is itself a VM id), and drop anything that isn't a
// VM id of `did` — see [`select_secret_kid`].
match select_secret_kid(&did, &entry.key_id, key.label.as_deref()) {
Some(kid) => secrets.push(crate::did_secrets::SecretEntry {
key_id: kid,
..entry
}),
None => {
tracing::warn!(
context = %context_id,
did = %did,
key_id = %entry.key_id,
label = key.label.as_deref().unwrap_or(""),
"excluding secret from did-secrets bundle: not a verification \
method of the context DID (e.g. an admin did:key minted into \
this context, or a free-text-labelled key). Including it would \
corrupt the DIDComm operating-secret set and break the \
mediator's exact-match recipient lookup."
);
}
}
}
offset += resp.keys.len() as u64;
if offset >= resp.total {
break;
}
}
Ok(crate::did_secrets::DidSecretsBundle { did, secrets })
}
}