iceberg/spec/
encrypted_key.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::collections::HashMap;
19
20use serde::{Deserialize, Serialize};
21
22/// Keys used for table encryption
23///
24/// Serializing of `encrypted_key_metadata` is done using base64 encoding.
25#[derive(Debug, Clone, PartialEq, Eq, typed_builder::TypedBuilder)]
26pub struct EncryptedKey {
27    /// Unique identifier for the key
28    #[builder(setter(into))]
29    pub(crate) key_id: String,
30    /// Encrypted key metadata as binary data
31    #[builder(setter(into))]
32    pub(crate) encrypted_key_metadata: Vec<u8>,
33    /// Identifier of the entity that encrypted this key
34    #[builder(default, setter(into, strip_option))]
35    pub(crate) encrypted_by_id: Option<String>,
36    /// Additional properties associated with the key
37    #[builder(default)]
38    pub(crate) properties: HashMap<String, String>,
39}
40
41impl EncryptedKey {
42    /// Returns the key ID
43    pub fn key_id(&self) -> &str {
44        &self.key_id
45    }
46
47    /// Returns the encrypted key metadata
48    pub fn encrypted_key_metadata(&self) -> &[u8] {
49        &self.encrypted_key_metadata
50    }
51
52    /// Returns the ID of the entity that encrypted this key
53    pub fn encrypted_by_id(&self) -> Option<&str> {
54        self.encrypted_by_id.as_deref()
55    }
56
57    /// Returns the properties map
58    pub fn properties(&self) -> &HashMap<String, String> {
59        &self.properties
60    }
61}
62
63pub(super) mod _serde {
64    use base64::Engine as _;
65    use base64::engine::general_purpose::STANDARD as BASE64;
66
67    use super::*;
68
69    /// Helper struct for serializing/deserializing EncryptedKey
70    #[derive(Serialize, Deserialize)]
71    #[serde(rename_all = "kebab-case")]
72    pub(super) struct EncryptedKeySerde {
73        pub key_id: String,
74        pub encrypted_key_metadata: String, // Base64 encoded
75        pub encrypted_by_id: Option<String>,
76        #[serde(default, skip_serializing_if = "HashMap::is_empty")]
77        pub properties: HashMap<String, String>,
78    }
79
80    impl From<&EncryptedKey> for EncryptedKeySerde {
81        fn from(key: &EncryptedKey) -> Self {
82            Self {
83                key_id: key.key_id.clone(),
84                encrypted_key_metadata: BASE64.encode(&key.encrypted_key_metadata),
85                encrypted_by_id: key.encrypted_by_id.clone(),
86                properties: key.properties.clone(),
87            }
88        }
89    }
90
91    impl TryFrom<EncryptedKeySerde> for EncryptedKey {
92        type Error = base64::DecodeError;
93
94        fn try_from(serde_key: EncryptedKeySerde) -> Result<Self, Self::Error> {
95            let encrypted_key_metadata = BASE64.decode(&serde_key.encrypted_key_metadata)?;
96
97            Ok(Self {
98                key_id: serde_key.key_id,
99                encrypted_key_metadata,
100                encrypted_by_id: serde_key.encrypted_by_id,
101                properties: serde_key.properties,
102            })
103        }
104    }
105}
106
107impl Serialize for EncryptedKey {
108    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
109    where S: serde::Serializer {
110        let serde_key = _serde::EncryptedKeySerde::from(self);
111        serde_key.serialize(serializer)
112    }
113}
114
115impl<'de> Deserialize<'de> for EncryptedKey {
116    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
117    where D: serde::Deserializer<'de> {
118        let serde_key = _serde::EncryptedKeySerde::deserialize(deserializer)?;
119
120        Self::try_from(serde_key).map_err(serde::de::Error::custom)
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use serde_json::json;
127
128    use super::*;
129
130    #[test]
131    fn test_encrypted_key_serialization() {
132        // Test data
133        let metadata = b"iceberg";
134        let mut properties = HashMap::new();
135        properties.insert("algo".to_string(), "AES-256".to_string());
136        properties.insert("created-at".to_string(), "2023-05-15T10:30:00Z".to_string());
137
138        // Create the encrypted key
139        let key = EncryptedKey::builder()
140            .key_id("5f819b")
141            .encrypted_key_metadata(metadata.to_vec())
142            .encrypted_by_id("user-456")
143            .properties(properties)
144            .build();
145
146        // Serialize to JSON
147        let serialized = serde_json::to_value(&key).unwrap();
148
149        let expected = json!({
150            "key-id": "5f819b",
151            "encrypted-key-metadata": "aWNlYmVyZw==",
152            "encrypted-by-id": "user-456",
153            "properties": {
154                "algo": "AES-256",
155                "created-at": "2023-05-15T10:30:00Z"
156            }
157        });
158        assert_eq!(serialized, expected);
159    }
160
161    #[test]
162    fn test_encrypted_key_round_trip() {
163        // Test data
164        let metadata = b"binary\0data\xff\xfe with special bytes";
165        let mut properties = HashMap::new();
166        properties.insert("algo".to_string(), "AES-256".to_string());
167
168        // Create the original encrypted key
169        let original_key = EncryptedKey::builder()
170            .key_id("key-abc")
171            .encrypted_key_metadata(metadata.to_vec())
172            .encrypted_by_id("service-xyz")
173            .properties(properties)
174            .build();
175
176        // Serialize to JSON string
177        let json_string = serde_json::to_string(&original_key).unwrap();
178
179        // Deserialize back from JSON string
180        let deserialized_key: EncryptedKey = serde_json::from_str(&json_string).unwrap();
181
182        // Verify the keys match
183        assert_eq!(deserialized_key, original_key);
184        assert_eq!(deserialized_key.encrypted_key_metadata(), metadata);
185    }
186
187    #[test]
188    fn test_encrypted_key_empty_properties() {
189        // Create a key without properties
190        let key = EncryptedKey::builder()
191            .key_id("key-123")
192            .encrypted_key_metadata(b"data".to_vec())
193            .encrypted_by_id("user-456")
194            .build();
195
196        // Serialize to JSON
197        let serialized = serde_json::to_value(&key).unwrap();
198
199        // Verify properties field is skipped when empty
200        assert!(!serialized.as_object().unwrap().contains_key("properties"));
201
202        // Deserialize back
203        let deserialized: EncryptedKey = serde_json::from_value(serialized).unwrap();
204        assert_eq!(deserialized.properties().len(), 0);
205    }
206
207    #[test]
208    fn test_invalid_base64() {
209        // Invalid base64 string
210        let json_value = json!({
211            "key-id": "key-123",
212            "encrypted-key-metadata": "invalid@base64",
213            "encrypted-by-id": "user-456"
214        });
215
216        // Attempt to deserialize should fail
217        let result: Result<EncryptedKey, _> = serde_json::from_value(json_value);
218        assert!(result.is_err());
219    }
220}