openzeppelin_relayer/config/config_file/signer/
turnkey.rs

1//! Configuration for Turnkey signer
2//!
3//! This module provides configuration for using Turnkey
4//! as a signing mechanism. Turnkey is a custody platform that offers secure key management
5//! and signing services without exposing private keys.
6//!
7//! The configuration supports:
8//! - API credentials (public key and private key) for authenticating with Turnkey
9//! - Organization ID to identify the Turnkey organization
10//! - Private key ID to identify the specific private key within Turnkey
11//! - Public key representation for verification
12//!
13//! Turnkey allows for secure signing operations where private keys are managed and
14//! protected within Turnkey's secure infrastructure.
15use crate::{
16    config::ConfigFileError,
17    models::{validate_plain_or_env_value, PlainOrEnvValue},
18};
19use serde::{Deserialize, Serialize};
20use validator::Validate;
21
22use super::{validate_with_validator, SignerConfigValidate};
23
24#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Validate)]
25#[serde(deny_unknown_fields)]
26pub struct TurnkeySignerFileConfig {
27    #[validate(length(min = 1, message = "api_public_key field cannot be empty"))]
28    pub api_public_key: String,
29    #[validate(custom(function = "validate_plain_or_env_value"))]
30    pub api_private_key: PlainOrEnvValue,
31    #[validate(length(min = 1, message = "organization_id field cannot be empty"))]
32    pub organization_id: String,
33    #[validate(length(min = 1, message = "private_key_id cannot be empty"))]
34    pub private_key_id: String,
35    #[validate(length(min = 1, message = "public_key cannot be empty"))]
36    pub public_key: String,
37}
38
39impl SignerConfigValidate for TurnkeySignerFileConfig {
40    fn validate(&self) -> Result<(), ConfigFileError> {
41        validate_with_validator(self)
42    }
43}
44#[cfg(test)]
45mod tests {
46    use crate::models::SecretString;
47
48    use super::*;
49
50    #[test]
51    fn test_vault_transit_signer_file_config_valid() {
52        let config = TurnkeySignerFileConfig {
53            api_private_key: PlainOrEnvValue::Plain {
54                value: SecretString::new("api_private_key"),
55            },
56            api_public_key: "public-key".to_string(),
57            organization_id: "org-id".to_string(),
58            private_key_id: "private-key-id".to_string(),
59            public_key: "public-key".to_string(),
60        };
61
62        assert!(Validate::validate(&config).is_ok());
63        assert!(SignerConfigValidate::validate(&config).is_ok());
64    }
65
66    #[test]
67    fn test_turnkey_signer_file_config_empty_api_public_key() {
68        let config = TurnkeySignerFileConfig {
69            api_private_key: PlainOrEnvValue::Plain {
70                value: SecretString::new("api_private_key"),
71            },
72            api_public_key: "".to_string(),
73            organization_id: "org-id".to_string(),
74            private_key_id: "private-key-id".to_string(),
75            public_key: "public-key".to_string(),
76        };
77
78        let result = SignerConfigValidate::validate(&config);
79        assert!(result.is_err());
80        if let Err(e) = result {
81            let error_message = format!("{:?}", e);
82            assert!(error_message.contains("api_public_key"));
83            assert!(error_message.contains("cannot be empty"));
84        }
85    }
86
87    #[test]
88    fn test_turnkey_signer_file_config_empty_api_private_key() {
89        let config = TurnkeySignerFileConfig {
90            api_private_key: PlainOrEnvValue::Plain {
91                value: SecretString::new(""),
92            },
93            api_public_key: "public-key".to_string(),
94            organization_id: "org-id".to_string(),
95            private_key_id: "private-key-id".to_string(),
96            public_key: "public-key".to_string(),
97        };
98
99        let result = SignerConfigValidate::validate(&config);
100        assert!(result.is_err());
101        if let Err(e) = result {
102            let error_message = format!("{:?}", e);
103            assert!(error_message.contains("api_private_key"));
104        }
105    }
106
107    #[test]
108    fn test_turnkey_signer_file_config_empty_organization_id() {
109        let config = TurnkeySignerFileConfig {
110            api_private_key: PlainOrEnvValue::Plain {
111                value: SecretString::new("api_private_key"),
112            },
113            api_public_key: "public-key".to_string(),
114            organization_id: "".to_string(),
115            private_key_id: "private-key-id".to_string(),
116            public_key: "public-key".to_string(),
117        };
118
119        let result = SignerConfigValidate::validate(&config);
120        assert!(result.is_err());
121        if let Err(e) = result {
122            let error_message = format!("{:?}", e);
123            assert!(error_message.contains("organization_id"));
124            assert!(error_message.contains("cannot be empty"));
125        }
126    }
127
128    #[test]
129    fn test_turnkey_signer_file_config_empty_private_key_id() {
130        let config = TurnkeySignerFileConfig {
131            api_private_key: PlainOrEnvValue::Plain {
132                value: SecretString::new("api_private_key"),
133            },
134            api_public_key: "public-key".to_string(),
135            organization_id: "org-id".to_string(),
136            private_key_id: "".to_string(),
137            public_key: "public-key".to_string(),
138        };
139
140        let result = SignerConfigValidate::validate(&config);
141        assert!(result.is_err());
142        if let Err(e) = result {
143            let error_message = format!("{:?}", e);
144            assert!(error_message.contains("private_key_id"));
145            assert!(error_message.contains("cannot be empty"));
146        }
147    }
148
149    #[test]
150    fn test_turnkey_signer_file_config_empty_public_key() {
151        let config = TurnkeySignerFileConfig {
152            api_private_key: PlainOrEnvValue::Plain {
153                value: SecretString::new("api_private_key"),
154            },
155            api_public_key: "public-key".to_string(),
156            organization_id: "org-id".to_string(),
157            private_key_id: "private-key-id".to_string(),
158            public_key: "".to_string(),
159        };
160
161        let result = SignerConfigValidate::validate(&config);
162        assert!(result.is_err());
163        if let Err(e) = result {
164            let error_message = format!("{:?}", e);
165            assert!(error_message.contains("public_key"));
166            assert!(error_message.contains("cannot be empty"));
167        }
168    }
169
170    #[test]
171    fn test_turnkey_signer_file_config_multiple_errors() {
172        let config = TurnkeySignerFileConfig {
173            api_private_key: PlainOrEnvValue::Plain {
174                value: SecretString::new(""),
175            },
176            api_public_key: "".to_string(),
177            organization_id: "".to_string(),
178            private_key_id: "".to_string(),
179            public_key: "".to_string(),
180        };
181
182        let result = validate_with_validator(&config);
183        assert!(result.is_err());
184
185        if let Err(e) = result {
186            if let ConfigFileError::InvalidFormat(msg) = e {
187                assert!(msg.contains("api_public_key"));
188                assert!(msg.contains("api_private_key"));
189                assert!(msg.contains("organization_id"));
190                assert!(msg.contains("private_key_id"));
191                assert!(msg.contains("public_key"));
192            } else {
193                panic!("Expected ConfigFileError::InvalidFormat, got {:?}", e);
194            }
195        }
196    }
197
198    #[test]
199    fn test_serde_deserialize() {
200        let json = r#"
201        {
202            "api_public_key": "turnkey-api-public-key",
203            "api_private_key": {
204                "type": "plain",
205                "value": "turnkey-api-private-key"
206            },
207            "organization_id": "org-123456",
208            "private_key_id": "key-123456",
209            "public_key": "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEd+vn+WOG+lGUiJCzHsj8VItmr7Lmdv/Zr+tIhJM7rM+QT9QEzvEX2jWOPyXrvCwUyvVgWoMwUYIo3hd1PFTy7A=="
210        }
211        "#;
212
213        let config: TurnkeySignerFileConfig = serde_json::from_str(json).unwrap();
214        assert_eq!(config.api_public_key, "turnkey-api-public-key");
215        assert_eq!(
216            config
217                .api_private_key
218                .get_value()
219                .unwrap()
220                .to_str()
221                .as_str(),
222            "turnkey-api-private-key"
223        );
224        assert_eq!(config.organization_id, "org-123456");
225        assert_eq!(config.private_key_id, "key-123456");
226        assert_eq!(config.public_key, "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEd+vn+WOG+lGUiJCzHsj8VItmr7Lmdv/Zr+tIhJM7rM+QT9QEzvEX2jWOPyXrvCwUyvVgWoMwUYIo3hd1PFTy7A==");
227    }
228
229    #[test]
230    fn test_serde_unknown_field() {
231        let json = r#"
232        {
233            "api_public_key": "turnkey-api-public-key",
234            "api_private_key": {
235                "type": "plain",
236                "value": "turnkey-api-private-key"
237            },
238            "organization_id": "org-123456",
239            "private_key_id": "key-123456",
240            "public_key": "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEd+vn+WOG+lGUiJCzHsj8VItmr7Lmdv/Zr+tIhJM7rM+QT9QEzvEX2jWOPyXrvCwUyvVgWoMwUYIo3hd1PFTy7A==",
241            "unknown_field": "should cause error"
242        }
243        "#;
244
245        let result: Result<TurnkeySignerFileConfig, _> = serde_json::from_str(json);
246        assert!(result.is_err());
247    }
248
249    #[test]
250    fn test_serde_serialize_deserialize() {
251        let config = TurnkeySignerFileConfig {
252            api_private_key: PlainOrEnvValue::Plain {
253                value: SecretString::new("api_private_key"),
254            },
255            api_public_key: "public-key".to_string(),
256            organization_id: "org-id".to_string(),
257            private_key_id: "private-key-id".to_string(),
258            public_key: "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEd+vn+WOG+lGUiJCzHsj8VItmr7Lmdv/Zr+tIhJM7rM+QT9QEzvEX2jWOPyXrvCwUyvVgWoMwUYIo3hd1PFTy7A==".to_string(),
259        };
260
261        let serialized = serde_json::to_string(&config).unwrap();
262        let deserialized: TurnkeySignerFileConfig = serde_json::from_str(&serialized).unwrap();
263
264        assert_eq!(config.api_public_key, deserialized.api_public_key);
265        assert_eq!(config.organization_id, deserialized.organization_id);
266        assert_eq!(config.private_key_id, deserialized.private_key_id);
267        assert_eq!(config.public_key, deserialized.public_key);
268    }
269
270    #[test]
271    fn test_turnkey_signer_file_config_env_variable() {
272        let env_var_name = "TEST_API_PRIVATE_KEY";
273        std::env::set_var(env_var_name, "env-api-private-key");
274
275        let config = TurnkeySignerFileConfig {
276            api_private_key: PlainOrEnvValue::Env {
277                value: env_var_name.to_string(),
278            },
279            api_public_key: "public-key".to_string(),
280            organization_id: "org-id".to_string(),
281            private_key_id: "private-key-id".to_string(),
282            public_key: "public-key".to_string(),
283        };
284
285        assert!(SignerConfigValidate::validate(&config).is_ok());
286        assert_eq!(
287            config
288                .api_private_key
289                .get_value()
290                .unwrap()
291                .to_str()
292                .as_str(),
293            "env-api-private-key"
294        );
295
296        std::env::remove_var(env_var_name);
297    }
298}