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