1use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9use crate::manifest::{KeyAccess, PolicyBinding};
10
11#[derive(Debug, Error)]
13pub enum KasError {
14 #[error("HTTP error: {status} - {message}")]
15 HttpError { status: u16, message: String },
16
17 #[error("Access denied for resource '{resource}': {reason}")]
18 AccessDenied { resource: String, reason: String },
19
20 #[error("Authentication failed: {reason}")]
21 AuthenticationFailed { reason: String },
22
23 #[error("Invalid response from KAS: {reason}")]
24 InvalidResponse {
25 reason: String,
26 expected: Option<String>,
27 },
28
29 #[error("Key unwrapping failed for algorithm '{algorithm}': {reason}")]
30 UnwrapError { algorithm: String, reason: String },
31
32 #[error("Cryptographic error: {operation} failed - {reason}")]
33 CryptoError { operation: String, reason: String },
34
35 #[error("JSON serialization error: {0}")]
36 SerializationError(#[from] serde_json::Error),
37
38 #[error("Base64 decode error: {0}")]
39 Base64Error(#[from] base64::DecodeError),
40
41 #[error("JWT error: {operation} - {reason}")]
42 JwtError { operation: String, reason: String },
43
44 #[error("PKCS8 error: {0}")]
45 Pkcs8Error(String),
46
47 #[error("HTTP request failed: {method} {url} - {reason}")]
48 RequestError {
49 method: String,
50 url: String,
51 reason: String,
52 },
53
54 #[error("Network timeout after {timeout_ms}ms")]
55 Timeout { timeout_ms: u64 },
56
57 #[error("Invalid KAS configuration: {reason}")]
58 ConfigError { reason: String },
59
60 #[error("Invalid KAS URL: {0}")]
61 InvalidUrl(String),
62}
63
64impl KasError {
65 pub fn is_retryable(&self) -> bool {
67 matches!(
68 self,
69 KasError::HttpError { status, .. } if *status >= 500 && *status < 600,
70 ) || matches!(self, KasError::Timeout { .. })
71 }
72
73 pub fn suggestion(&self) -> Option<&str> {
75 match self {
76 KasError::AuthenticationFailed { .. } => {
77 Some("Verify OAuth token is valid and not expired")
78 }
79 KasError::AccessDenied { .. } => {
80 Some("Check that the user has permissions for this resource")
81 }
82 KasError::Timeout { .. } => {
83 Some("Check network connectivity or increase timeout value")
84 }
85 KasError::InvalidResponse { .. } => Some("Verify KAS server version compatibility"),
86 KasError::InvalidUrl(_) => {
87 Some("Use an HTTPS URL for the KAS endpoint (HTTP allowed only for localhost)")
88 }
89 _ => None,
90 }
91 }
92
93 pub fn error_code(&self) -> &'static str {
95 match self {
96 KasError::HttpError { .. } => "HTTP_ERROR",
97 KasError::AccessDenied { .. } => "ACCESS_DENIED",
98 KasError::AuthenticationFailed { .. } => "AUTHENTICATION_FAILED",
99 KasError::InvalidResponse { .. } => "INVALID_RESPONSE",
100 KasError::UnwrapError { .. } => "UNWRAP_ERROR",
101 KasError::CryptoError { .. } => "CRYPTO_ERROR",
102 KasError::SerializationError(_) => "SERIALIZATION_ERROR",
103 KasError::Base64Error(_) => "BASE64_ERROR",
104 KasError::JwtError { .. } => "JWT_ERROR",
105 KasError::Pkcs8Error(_) => "PKCS8_ERROR",
106 KasError::RequestError { .. } => "REQUEST_ERROR",
107 KasError::Timeout { .. } => "TIMEOUT",
108 KasError::ConfigError { .. } => "CONFIG_ERROR",
109 KasError::InvalidUrl(_) => "INVALID_URL",
110 }
111 }
112}
113
114#[derive(Debug, Serialize, Deserialize)]
116pub struct UnsignedRewrapRequest {
117 #[serde(rename = "clientPublicKey")]
118 pub client_public_key: String,
119 pub requests: Vec<PolicyRequest>,
120}
121
122#[derive(Debug, Serialize, Deserialize)]
124pub struct PolicyRequest {
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub algorithm: Option<String>,
127 pub policy: Policy,
128 #[serde(rename = "keyAccessObjects")]
129 pub key_access_objects: Vec<KeyAccessObjectWrapper>,
130}
131
132#[derive(Debug, Serialize, Deserialize)]
134pub struct Policy {
135 pub id: String,
136 pub body: String, }
138
139#[derive(Debug, Serialize, Deserialize)]
141pub struct KeyAccessObjectWrapper {
142 #[serde(rename = "keyAccessObjectId")]
143 pub key_access_object_id: String,
144 #[serde(rename = "keyAccessObject")]
145 pub key_access_object: KeyAccessObject,
146}
147
148#[derive(Debug, Serialize, Deserialize)]
150pub struct KeyAccessObject {
151 #[serde(rename = "type")]
152 pub key_type: String,
153 pub url: String,
154 pub protocol: String,
155 #[serde(rename = "wrappedKey")]
156 pub wrapped_key: String,
157 #[serde(rename = "policyBinding")]
158 pub policy_binding: KasPolicyBinding,
159 #[serde(rename = "encryptedMetadata", skip_serializing_if = "Option::is_none")]
160 pub encrypted_metadata: Option<String>,
161 #[serde(skip_serializing_if = "Option::is_none")]
162 pub kid: Option<String>,
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub header: Option<String>,
166 #[serde(rename = "ephemeralPublicKey", skip_serializing_if = "Option::is_none")]
168 pub ephemeral_public_key: Option<String>,
169}
170
171pub mod algorithm {
173 pub const RSA_2048: &str = "rsa:2048";
175 pub const EC_P256: &str = "ec:secp256r1";
177 pub const EC_P384: &str = "ec:secp384r1";
179 pub const EC_P521: &str = "ec:secp521r1";
181}
182
183impl KeyAccessObject {
184 pub fn detect_algorithm(&self) -> &'static str {
186 use crate::manifest::key_access_type;
187 if self.key_type == key_access_type::EC_WRAPPED {
188 algorithm::EC_P256 } else {
190 algorithm::RSA_2048
191 }
192 }
193
194 pub fn is_ec_wrapped(&self) -> bool {
196 use crate::manifest::key_access_type;
197 self.key_type == key_access_type::EC_WRAPPED
198 }
199
200 pub fn is_rsa_wrapped(&self) -> bool {
202 use crate::manifest::key_access_type;
203 self.key_type == key_access_type::WRAPPED
204 }
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct KasPolicyBinding {
210 pub hash: String,
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub algorithm: Option<String>,
213}
214
215#[derive(Debug, Serialize, Deserialize)]
221pub struct SignedRewrapRequest {
222 #[serde(rename = "signedRequestToken")]
223 pub signed_request_token: String,
224}
225
226#[derive(Debug, Serialize, Deserialize)]
228#[non_exhaustive]
229pub struct RewrapResponse {
230 pub responses: Vec<PolicyRewrapResult>,
231 #[serde(rename = "sessionPublicKey")]
232 pub session_public_key: Option<String>,
233}
234
235#[derive(Debug, Serialize, Deserialize)]
237#[non_exhaustive]
238pub struct PolicyRewrapResult {
239 #[serde(rename = "policyId")]
240 pub policy_id: String,
241 pub results: Vec<KeyAccessRewrapResult>,
242}
243
244#[derive(Debug, Serialize, Deserialize)]
246#[non_exhaustive]
247pub struct KeyAccessRewrapResult {
248 #[serde(rename = "keyAccessObjectId")]
249 pub key_access_object_id: String,
250 pub status: String,
251 #[serde(rename = "kasWrappedKey")]
252 pub kas_wrapped_key: Option<String>,
253 #[serde(rename = "entityWrappedKey")]
254 pub entity_wrapped_key: Option<String>, pub error: Option<String>,
256}
257
258impl From<PolicyBinding> for KasPolicyBinding {
263 fn from(pb: PolicyBinding) -> Self {
264 Self {
265 hash: pb.hash,
266 algorithm: Some(pb.alg),
267 }
268 }
269}
270
271impl From<KasPolicyBinding> for PolicyBinding {
272 fn from(kpb: KasPolicyBinding) -> Self {
273 Self {
274 alg: kpb.algorithm.unwrap_or_else(|| "HS256".to_string()),
275 hash: kpb.hash,
276 }
277 }
278}
279
280impl From<&KeyAccess> for KeyAccessObject {
281 fn from(ka: &KeyAccess) -> Self {
282 Self {
283 key_type: ka.access_type.clone(),
284 url: ka.url.clone(),
285 protocol: ka.protocol.clone(),
286 wrapped_key: ka.wrapped_key.clone(),
287 policy_binding: ka.policy_binding.clone().into(),
288 encrypted_metadata: ka.encrypted_metadata.clone(),
289 kid: ka.kid.clone(),
290 header: None,
291 ephemeral_public_key: ka.ephemeral_public_key.clone(),
292 }
293 }
294}
295
296impl From<KeyAccess> for KeyAccessObject {
297 fn from(ka: KeyAccess) -> Self {
298 Self {
299 key_type: ka.access_type,
300 url: ka.url,
301 protocol: ka.protocol,
302 wrapped_key: ka.wrapped_key,
303 policy_binding: ka.policy_binding.into(),
304 encrypted_metadata: ka.encrypted_metadata,
305 kid: ka.kid,
306 header: None,
307 ephemeral_public_key: ka.ephemeral_public_key,
308 }
309 }
310}
311
312impl From<&KeyAccessObject> for KeyAccess {
313 fn from(kao: &KeyAccessObject) -> Self {
314 Self {
315 access_type: kao.key_type.clone(),
316 url: kao.url.clone(),
317 kid: kao.kid.clone(),
318 protocol: kao.protocol.clone(),
319 wrapped_key: kao.wrapped_key.clone(),
320 policy_binding: kao.policy_binding.clone().into(),
321 encrypted_metadata: kao.encrypted_metadata.clone(),
322 schema_version: Some("1.0".to_string()),
323 ephemeral_public_key: kao.ephemeral_public_key.clone(),
324 }
325 }
326}
327
328impl From<KeyAccessObject> for KeyAccess {
329 fn from(kao: KeyAccessObject) -> Self {
330 Self {
331 access_type: kao.key_type,
332 url: kao.url,
333 kid: kao.kid,
334 protocol: kao.protocol,
335 wrapped_key: kao.wrapped_key,
336 policy_binding: kao.policy_binding.into(),
337 encrypted_metadata: kao.encrypted_metadata,
338 schema_version: Some("1.0".to_string()),
339 ephemeral_public_key: kao.ephemeral_public_key,
340 }
341 }
342}