1use crate::{SecretResolver, SecuritySecretRef};
14use appcore_dnt::{DntContext, DntKeyError, DntKeyProvider, KeyId, SecretKey};
15use std::fmt;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct DntSecretRefPolicy {
20 prefix: String,
21}
22
23impl DntSecretRefPolicy {
24 pub fn new(prefix: impl Into<String>) -> Self {
26 Self {
27 prefix: prefix.into(),
28 }
29 }
30
31 pub fn provider_default() -> Self {
33 Self::new("provider:dnt-key/")
34 }
35
36 pub fn reference_for(&self, key_id: &KeyId, context: &DntContext) -> SecuritySecretRef {
38 let tenant = context
39 .tenant_id
40 .as_ref()
41 .map(|value| value.as_str())
42 .unwrap_or("_");
43 SecuritySecretRef(format!(
44 "{}{}/{}/{}",
45 self.prefix,
46 context.application_id.as_str(),
47 tenant,
48 key_id.as_str()
49 ))
50 }
51}
52
53impl Default for DntSecretRefPolicy {
54 fn default() -> Self {
55 Self::provider_default()
56 }
57}
58
59pub struct DntSecretKeyProvider<R> {
61 resolver: R,
62 policy: DntSecretRefPolicy,
63}
64
65impl<R> fmt::Debug for DntSecretKeyProvider<R> {
66 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
67 formatter
68 .debug_struct("DntSecretKeyProvider")
69 .field("policy", &self.policy)
70 .finish_non_exhaustive()
71 }
72}
73
74impl<R> DntSecretKeyProvider<R>
75where
76 R: SecretResolver,
77{
78 pub fn new(resolver: R) -> Self {
80 Self {
81 resolver,
82 policy: DntSecretRefPolicy::default(),
83 }
84 }
85
86 pub fn with_policy(resolver: R, policy: DntSecretRefPolicy) -> Self {
88 Self { resolver, policy }
89 }
90
91 pub fn policy(&self) -> &DntSecretRefPolicy {
93 &self.policy
94 }
95}
96
97impl<R> DntKeyProvider for DntSecretKeyProvider<R>
98where
99 R: SecretResolver,
100{
101 fn resolve_key(&self, key_id: &KeyId, context: &DntContext) -> Result<SecretKey, DntKeyError> {
102 let reference = self.policy.reference_for(key_id, context);
103 let secret = self
104 .resolver
105 .resolve(&reference)
106 .map_err(|_| DntKeyError::Unavailable)?;
107 SecretKey::from_slice(secret.as_bytes())
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::{SecretBytes, StaticSecretResolver};
115 use appcore_contracts::ApplicationId;
116 use appcore_dnt::{
117 open, seal, BytesCodec, ContentType, DntOpenOptions, DntSealOptions, DNT_CONTENT_SECRET,
118 };
119 use appcore_types::TenantId;
120 use std::collections::HashMap;
121
122 #[test]
123 fn secret_resolver_key_provider_opens_dnt_secret() {
124 let key_id = KeyId::new("local-root").unwrap();
125 let mut secrets = HashMap::new();
126 secrets.insert(
127 format!("provider:dnt-key/app-a/tenant-a/{}", key_id.as_str()),
128 SecretBytes::new(vec![5; 32]),
129 );
130 let resolver = StaticSecretResolver::new(secrets);
131 let provider = DntSecretKeyProvider::new(resolver);
132 let codec = BytesCodec;
133 let seal_options = DntSealOptions {
134 application_id: ApplicationId::new("app-a").unwrap(),
135 tenant_id: Some(TenantId::new("tenant-a").unwrap()),
136 content_type: ContentType::new(DNT_CONTENT_SECRET).unwrap(),
137 schema_version: 1,
138 key_id,
139 created_at_ms: 1,
140 public_metadata: Vec::new(),
141 encrypted_metadata: Vec::new(),
142 flags: 0,
143 max_payload_bytes: Some(1024),
144 };
145 let open_options = DntOpenOptions {
146 application_id: ApplicationId::new("app-a").unwrap(),
147 tenant_id: Some(TenantId::new("tenant-a").unwrap()),
148 content_type: ContentType::new(DNT_CONTENT_SECRET).unwrap(),
149 max_payload_bytes: Some(1024),
150 };
151
152 let sealed = seal(b"secret", &provider, &codec, seal_options).unwrap();
153 let opened = open(&sealed, &provider, &codec, &open_options).unwrap();
154
155 assert_eq!(opened.payload, b"secret");
156 }
157
158 #[test]
159 fn missing_dnt_key_fails_closed() {
160 let resolver = StaticSecretResolver::new(HashMap::new());
161 let provider = DntSecretKeyProvider::new(resolver);
162 let context = DntContext {
163 application_id: ApplicationId::new("app-a").unwrap(),
164 tenant_id: None,
165 content_type: ContentType::new(DNT_CONTENT_SECRET).unwrap(),
166 codec_id: appcore_dnt::CodecId::new("bytes").unwrap(),
167 schema_version: 1,
168 };
169
170 assert_eq!(
171 provider.resolve_key(&KeyId::new("missing").unwrap(), &context),
172 Err(DntKeyError::Unavailable)
173 );
174 }
175}