openzeppelin_relayer/models/signer/
request.rs

1//! API request models and validation for signer endpoints.
2//!
3//! This module handles incoming HTTP requests for signer operations, providing:
4//!
5//! - **Request Models**: Structures for creating and updating signers via API
6//! - **Input Validation**: Sanitization and validation of user-provided data
7//! - **Domain Conversion**: Transformation from API requests to domain objects
8//!
9//! Serves as the entry point for signer data from external clients, ensuring
10//! all input is properly validated before reaching the core business logic.
11
12use crate::models::{
13    ApiError, AwsKmsSignerConfig, GoogleCloudKmsSignerConfig, GoogleCloudKmsSignerKeyConfig,
14    GoogleCloudKmsSignerServiceAccountConfig, LocalSignerConfig, SecretString, Signer,
15    SignerConfig, TurnkeySignerConfig, VaultSignerConfig, VaultTransitSignerConfig,
16};
17use secrets::SecretVec;
18use serde::{Deserialize, Serialize};
19use utoipa::ToSchema;
20use zeroize::Zeroize;
21
22/// Local signer configuration for API requests
23#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
24#[serde(deny_unknown_fields)]
25pub struct LocalSignerRequestConfig {
26    pub key: String,
27}
28
29/// AWS KMS signer configuration for API requests
30#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
31#[serde(deny_unknown_fields)]
32pub struct AwsKmsSignerRequestConfig {
33    pub region: String,
34    pub key_id: String,
35}
36
37/// Vault signer configuration for API requests
38#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
39#[serde(deny_unknown_fields)]
40pub struct VaultSignerRequestConfig {
41    pub address: String,
42    #[schema(nullable = false)]
43    pub namespace: Option<String>,
44    pub role_id: String,
45    pub secret_id: String,
46    pub key_name: String,
47    #[schema(nullable = false)]
48    pub mount_point: Option<String>,
49}
50
51/// Vault Transit signer configuration for API requests
52#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
53#[serde(deny_unknown_fields)]
54pub struct VaultTransitSignerRequestConfig {
55    pub key_name: String,
56    pub address: String,
57    #[schema(nullable = false)]
58    pub namespace: Option<String>,
59    pub role_id: String,
60    pub secret_id: String,
61    pub pubkey: String,
62    #[schema(nullable = false)]
63    pub mount_point: Option<String>,
64}
65
66/// Turnkey signer configuration for API requests
67#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
68#[serde(deny_unknown_fields)]
69pub struct TurnkeySignerRequestConfig {
70    pub api_public_key: String,
71    pub api_private_key: String,
72    pub organization_id: String,
73    pub private_key_id: String,
74    pub public_key: String,
75}
76
77/// Google Cloud KMS service account configuration for API requests
78#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
79#[serde(deny_unknown_fields)]
80pub struct GoogleCloudKmsSignerServiceAccountRequestConfig {
81    pub private_key: String,
82    pub private_key_id: String,
83    pub project_id: String,
84    pub client_email: String,
85    pub client_id: String,
86    pub auth_uri: String,
87    pub token_uri: String,
88    pub auth_provider_x509_cert_url: String,
89    pub client_x509_cert_url: String,
90    pub universe_domain: String,
91}
92
93/// Google Cloud KMS key configuration for API requests
94#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
95#[serde(deny_unknown_fields)]
96pub struct GoogleCloudKmsSignerKeyRequestConfig {
97    pub location: String,
98    pub key_ring_id: String,
99    pub key_id: String,
100    pub key_version: u32,
101}
102
103/// Google Cloud KMS signer configuration for API requests
104#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
105#[serde(deny_unknown_fields)]
106pub struct GoogleCloudKmsSignerRequestConfig {
107    pub service_account: GoogleCloudKmsSignerServiceAccountRequestConfig,
108    pub key: GoogleCloudKmsSignerKeyRequestConfig,
109}
110
111/// Signer configuration enum for API requests (without type discriminator)
112#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
113#[serde(untagged)]
114pub enum SignerConfigRequest {
115    Local(LocalSignerRequestConfig),
116    AwsKms(AwsKmsSignerRequestConfig),
117    Vault(VaultSignerRequestConfig),
118    VaultTransit(VaultTransitSignerRequestConfig),
119    Turnkey(TurnkeySignerRequestConfig),
120    GoogleCloudKms(GoogleCloudKmsSignerRequestConfig),
121}
122
123/// Signer type enum for API requests
124#[derive(Debug, Serialize, Deserialize, ToSchema)]
125#[serde(rename_all = "lowercase")]
126pub enum SignerTypeRequest {
127    #[serde(rename = "plain")]
128    Local,
129    #[serde(rename = "aws_kms")]
130    AwsKms,
131    Vault,
132    #[serde(rename = "vault_transit")]
133    VaultTransit,
134    Turnkey,
135    #[serde(rename = "google_cloud_kms")]
136    GoogleCloudKms,
137}
138
139impl zeroize::Zeroize for SignerTypeRequest {
140    fn zeroize(&mut self) {
141        // No sensitive data to zeroize in this enum
142    }
143}
144
145/// Request model for creating a new signer
146#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
147#[serde(deny_unknown_fields)]
148pub struct SignerCreateRequest {
149    /// Optional ID - if not provided, a UUID will be generated
150    #[schema(nullable = false)]
151    pub id: Option<String>,
152    /// The type of signer
153    #[serde(rename = "type")]
154    pub signer_type: SignerTypeRequest,
155    /// The signer configuration
156    pub config: SignerConfigRequest,
157}
158
159/// Request model for updating an existing signer
160/// At the moment, we don't allow updating signers
161#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
162#[serde(deny_unknown_fields)]
163pub struct SignerUpdateRequest {}
164
165impl From<GoogleCloudKmsSignerServiceAccountRequestConfig>
166    for GoogleCloudKmsSignerServiceAccountConfig
167{
168    fn from(config: GoogleCloudKmsSignerServiceAccountRequestConfig) -> Self {
169        Self {
170            private_key: SecretString::new(&config.private_key),
171            private_key_id: SecretString::new(&config.private_key_id),
172            project_id: config.project_id,
173            client_email: SecretString::new(&config.client_email),
174            client_id: config.client_id,
175            auth_uri: config.auth_uri,
176            token_uri: config.token_uri,
177            auth_provider_x509_cert_url: config.auth_provider_x509_cert_url,
178            client_x509_cert_url: config.client_x509_cert_url,
179            universe_domain: config.universe_domain,
180        }
181    }
182}
183
184impl From<GoogleCloudKmsSignerKeyRequestConfig> for GoogleCloudKmsSignerKeyConfig {
185    fn from(config: GoogleCloudKmsSignerKeyRequestConfig) -> Self {
186        Self {
187            location: config.location,
188            key_ring_id: config.key_ring_id,
189            key_id: config.key_id,
190            key_version: config.key_version,
191        }
192    }
193}
194
195impl TryFrom<SignerConfigRequest> for SignerConfig {
196    type Error = ApiError;
197
198    fn try_from(config: SignerConfigRequest) -> Result<Self, Self::Error> {
199        let domain_config = match config {
200            SignerConfigRequest::Local(local_config) => {
201                // Decode hex string to raw bytes for cryptographic key
202                let key_bytes = hex::decode(&local_config.key)
203                    .map_err(|e| ApiError::BadRequest(format!(
204                        "Invalid hex key format: {}. Key must be a 64-character hex string (32 bytes).", e
205                    )))?;
206
207                let raw_key = SecretVec::new(key_bytes.len(), |buffer| {
208                    buffer.copy_from_slice(&key_bytes);
209                });
210
211                SignerConfig::Local(LocalSignerConfig { raw_key })
212            }
213            SignerConfigRequest::AwsKms(aws_config) => SignerConfig::AwsKms(AwsKmsSignerConfig {
214                region: Some(aws_config.region),
215                key_id: aws_config.key_id,
216            }),
217            SignerConfigRequest::Vault(vault_config) => SignerConfig::Vault(VaultSignerConfig {
218                address: vault_config.address,
219                namespace: vault_config.namespace,
220                role_id: SecretString::new(&vault_config.role_id),
221                secret_id: SecretString::new(&vault_config.secret_id),
222                key_name: vault_config.key_name,
223                mount_point: vault_config.mount_point,
224            }),
225            SignerConfigRequest::VaultTransit(vault_transit_config) => {
226                SignerConfig::VaultTransit(VaultTransitSignerConfig {
227                    key_name: vault_transit_config.key_name,
228                    address: vault_transit_config.address,
229                    namespace: vault_transit_config.namespace,
230                    role_id: SecretString::new(&vault_transit_config.role_id),
231                    secret_id: SecretString::new(&vault_transit_config.secret_id),
232                    pubkey: vault_transit_config.pubkey,
233                    mount_point: vault_transit_config.mount_point,
234                })
235            }
236            SignerConfigRequest::Turnkey(turnkey_config) => {
237                SignerConfig::Turnkey(TurnkeySignerConfig {
238                    api_public_key: turnkey_config.api_public_key,
239                    api_private_key: SecretString::new(&turnkey_config.api_private_key),
240                    organization_id: turnkey_config.organization_id,
241                    private_key_id: turnkey_config.private_key_id,
242                    public_key: turnkey_config.public_key,
243                })
244            }
245            SignerConfigRequest::GoogleCloudKms(gcp_kms_config) => {
246                SignerConfig::GoogleCloudKms(GoogleCloudKmsSignerConfig {
247                    service_account: gcp_kms_config.service_account.into(),
248                    key: gcp_kms_config.key.into(),
249                })
250            }
251        };
252
253        // Validate the configuration using domain model validation
254        domain_config.validate().map_err(ApiError::from)?;
255
256        Ok(domain_config)
257    }
258}
259
260impl TryFrom<SignerCreateRequest> for Signer {
261    type Error = ApiError;
262
263    fn try_from(request: SignerCreateRequest) -> Result<Self, Self::Error> {
264        // Generate UUID if no ID provided
265        let id = request
266            .id
267            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
268
269        // Validate that the signer type matches the config variant
270        let config_matches_type = matches!(
271            (&request.signer_type, &request.config),
272            (SignerTypeRequest::Local, SignerConfigRequest::Local(_))
273                | (SignerTypeRequest::AwsKms, SignerConfigRequest::AwsKms(_))
274                | (SignerTypeRequest::Vault, SignerConfigRequest::Vault(_))
275                | (
276                    SignerTypeRequest::VaultTransit,
277                    SignerConfigRequest::VaultTransit(_)
278                )
279                | (SignerTypeRequest::Turnkey, SignerConfigRequest::Turnkey(_))
280                | (
281                    SignerTypeRequest::GoogleCloudKms,
282                    SignerConfigRequest::GoogleCloudKms(_)
283                )
284        );
285
286        if !config_matches_type {
287            return Err(ApiError::BadRequest(format!(
288                "Signer type '{:?}' does not match the provided configuration",
289                request.signer_type
290            )));
291        }
292
293        // Convert request config to domain config (with validation)
294        let config = SignerConfig::try_from(request.config)?;
295
296        // Create the signer
297        let signer = Signer::new(id, config);
298
299        // Validate using domain model validation (this will also validate the config)
300        signer.validate().map_err(ApiError::from)?;
301
302        Ok(signer)
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309    use crate::models::signer::SignerType;
310
311    #[test]
312    fn test_json_deserialization_local_signer() {
313        let json = r#"{
314            "id": "test-local-signer",
315            "type": "plain",
316            "config": {
317                "key": "1111111111111111111111111111111111111111111111111111111111111111"
318            }
319        }"#;
320
321        let result: Result<SignerCreateRequest, _> = serde_json::from_str(json);
322
323        assert!(
324            result.is_ok(),
325            "Failed to deserialize local signer: {:?}",
326            result.err()
327        );
328
329        let request = result.unwrap();
330        assert_eq!(request.id, Some("test-local-signer".to_string()));
331
332        match request.config {
333            SignerConfigRequest::Local(local_config) => {
334                assert_eq!(
335                    local_config.key,
336                    "1111111111111111111111111111111111111111111111111111111111111111"
337                );
338            }
339            _ => panic!("Expected Local config variant"),
340        }
341    }
342
343    #[test]
344    fn test_json_deserialization_aws_kms_signer() {
345        let json = r#"{
346            "id": "test-aws-signer",
347            "type": "aws_kms",
348            "config": {
349                "region": "us-east-1",
350                "key_id": "test-key-id"
351            }
352        }"#;
353
354        let result: Result<SignerCreateRequest, _> = serde_json::from_str(json);
355
356        assert!(
357            result.is_ok(),
358            "Failed to deserialize AWS KMS signer: {:?}",
359            result.err()
360        );
361
362        let request = result.unwrap();
363        assert_eq!(request.id, Some("test-aws-signer".to_string()));
364
365        match request.config {
366            SignerConfigRequest::AwsKms(aws_config) => {
367                assert_eq!(aws_config.region, "us-east-1");
368                assert_eq!(aws_config.key_id, "test-key-id");
369            }
370            _ => panic!("Expected AwsKms config variant"),
371        }
372    }
373
374    #[test]
375    fn test_json_deserialization_vault_signer() {
376        let json = r#"{
377            "id": "test-vault-signer",
378            "type": "vault",
379            "config": {
380                "address": "https://vault.example.com",
381                "namespace": null,
382                "role_id": "test-role-id",
383                "secret_id": "test-secret-id",
384                "key_name": "test-key",
385                "mount_point": null
386            }
387        }"#;
388
389        let result: Result<SignerCreateRequest, _> = serde_json::from_str(json);
390
391        assert!(
392            result.is_ok(),
393            "Failed to deserialize Vault signer: {:?}",
394            result.err()
395        );
396
397        let request = result.unwrap();
398        assert_eq!(request.id, Some("test-vault-signer".to_string()));
399
400        match request.config {
401            SignerConfigRequest::Vault(vault_config) => {
402                assert_eq!(vault_config.address, "https://vault.example.com");
403                assert_eq!(vault_config.namespace, None);
404                assert_eq!(vault_config.role_id, "test-role-id");
405                assert_eq!(vault_config.secret_id, "test-secret-id");
406                assert_eq!(vault_config.key_name, "test-key");
407                assert_eq!(vault_config.mount_point, None);
408            }
409            _ => panic!("Expected Vault config variant"),
410        }
411    }
412
413    #[test]
414    fn test_json_deserialization_turnkey_signer() {
415        let json = r#"{
416            "id": "test-turnkey-signer",
417            "type": "turnkey",
418            "config": {
419                "api_public_key": "test-public-key",
420                "api_private_key": "test-private-key",
421                "organization_id": "test-org",
422                "private_key_id": "test-private-key-id",
423                "public_key": "test-public-key"
424            }
425        }"#;
426
427        let result: Result<SignerCreateRequest, _> = serde_json::from_str(json);
428
429        assert!(
430            result.is_ok(),
431            "Failed to deserialize Turnkey signer: {:?}",
432            result.err()
433        );
434
435        let request = result.unwrap();
436        assert_eq!(request.id, Some("test-turnkey-signer".to_string()));
437
438        match request.config {
439            SignerConfigRequest::Turnkey(turnkey_config) => {
440                assert_eq!(turnkey_config.api_public_key, "test-public-key");
441                assert_eq!(turnkey_config.api_private_key, "test-private-key");
442                assert_eq!(turnkey_config.organization_id, "test-org");
443                assert_eq!(turnkey_config.private_key_id, "test-private-key-id");
444                assert_eq!(turnkey_config.public_key, "test-public-key");
445            }
446            _ => panic!("Expected Turnkey config variant"),
447        }
448    }
449
450    #[test]
451    fn test_json_serialization_local_signer() {
452        let request = SignerCreateRequest {
453            id: Some("test-local-signer".to_string()),
454            signer_type: SignerTypeRequest::Local,
455            config: SignerConfigRequest::Local(LocalSignerRequestConfig {
456                key: "1111111111111111111111111111111111111111111111111111111111111111".to_string(),
457            }),
458        };
459
460        let json_result = serde_json::to_string_pretty(&request);
461
462        assert!(
463            json_result.is_ok(),
464            "Failed to serialize local signer: {:?}",
465            json_result.err()
466        );
467
468        let json = json_result.unwrap();
469
470        // Verify it can be deserialized back
471        let deserialize_result: Result<SignerCreateRequest, _> = serde_json::from_str(&json);
472        assert!(
473            deserialize_result.is_ok(),
474            "Failed to deserialize back: {:?}",
475            deserialize_result.err()
476        );
477    }
478
479    #[test]
480    fn test_json_serialization_aws_kms_signer() {
481        let request = SignerCreateRequest {
482            id: Some("test-aws-signer".to_string()),
483            signer_type: SignerTypeRequest::AwsKms,
484            config: SignerConfigRequest::AwsKms(AwsKmsSignerRequestConfig {
485                region: "us-east-1".to_string(),
486                key_id: "test-key-id".to_string(),
487            }),
488        };
489
490        let json_result = serde_json::to_string_pretty(&request);
491
492        assert!(
493            json_result.is_ok(),
494            "Failed to serialize AWS KMS signer: {:?}",
495            json_result.err()
496        );
497
498        let json = json_result.unwrap();
499
500        // Verify it can be deserialized back
501        let deserialize_result: Result<SignerCreateRequest, _> = serde_json::from_str(&json);
502        assert!(
503            deserialize_result.is_ok(),
504            "Failed to deserialize back: {:?}",
505            deserialize_result.err()
506        );
507    }
508
509    #[test]
510    fn test_type_config_mismatch_validation() {
511        // Create a request where the type doesn't match the config
512        let json = r#"{
513            "id": "test-mismatch-signer",
514            "type": "aws_kms",
515            "config": {
516                "key": "1111111111111111111111111111111111111111111111111111111111111111"
517            }
518        }"#;
519
520        let result: Result<SignerCreateRequest, _> = serde_json::from_str(json);
521
522        // This should deserialize successfully due to untagged enum
523        assert!(result.is_ok(), "JSON deserialization should succeed");
524
525        let request = result.unwrap();
526
527        // But the conversion to Signer should fail due to type mismatch validation
528        let signer_result = Signer::try_from(request);
529        assert!(
530            signer_result.is_err(),
531            "Type mismatch should cause validation error"
532        );
533
534        if let Err(ApiError::BadRequest(msg)) = signer_result {
535            assert!(
536                msg.contains("does not match"),
537                "Error should mention type mismatch: {}",
538                msg
539            );
540        } else {
541            panic!("Expected BadRequest error for type mismatch");
542        }
543    }
544
545    // Keep existing tests for backward compatibility
546    #[test]
547    fn test_valid_aws_kms_create_request() {
548        let request = SignerCreateRequest {
549            id: Some("test-aws-signer".to_string()),
550            signer_type: SignerTypeRequest::AwsKms,
551            config: SignerConfigRequest::AwsKms(AwsKmsSignerRequestConfig {
552                region: "us-east-1".to_string(),
553                key_id: "test-key-id".to_string(),
554            }),
555        };
556
557        let result = Signer::try_from(request);
558        assert!(result.is_ok());
559
560        let signer = result.unwrap();
561        assert_eq!(signer.id, "test-aws-signer");
562        assert_eq!(signer.signer_type(), SignerType::AwsKms);
563
564        // Verify the config was properly converted
565        if let Some(aws_config) = signer.config.get_aws_kms() {
566            assert_eq!(aws_config.region, Some("us-east-1".to_string()));
567            assert_eq!(aws_config.key_id, "test-key-id");
568        } else {
569            panic!("Expected AWS KMS config");
570        }
571    }
572
573    #[test]
574    fn test_valid_vault_create_request() {
575        let request = SignerCreateRequest {
576            id: Some("test-vault-signer".to_string()),
577            signer_type: SignerTypeRequest::Vault,
578            config: SignerConfigRequest::Vault(VaultSignerRequestConfig {
579                address: "https://vault.example.com".to_string(),
580                namespace: None,
581                role_id: "test-role-id".to_string(),
582                secret_id: "test-secret-id".to_string(),
583                key_name: "test-key".to_string(),
584                mount_point: None,
585            }),
586        };
587
588        let result = Signer::try_from(request);
589        assert!(result.is_ok());
590
591        let signer = result.unwrap();
592        assert_eq!(signer.id, "test-vault-signer");
593        assert_eq!(signer.signer_type(), SignerType::Vault);
594    }
595
596    #[test]
597    fn test_invalid_aws_kms_empty_key_id() {
598        let request = SignerCreateRequest {
599            id: Some("test-signer".to_string()),
600            signer_type: SignerTypeRequest::AwsKms,
601            config: SignerConfigRequest::AwsKms(AwsKmsSignerRequestConfig {
602                region: "us-east-1".to_string(),
603                key_id: "".to_string(), // Empty key ID should fail validation
604            }),
605        };
606
607        let result = Signer::try_from(request);
608        assert!(result.is_err());
609
610        if let Err(ApiError::BadRequest(msg)) = result {
611            assert!(msg.contains("Key ID cannot be empty"));
612        } else {
613            panic!("Expected BadRequest error for empty key ID");
614        }
615    }
616
617    #[test]
618    fn test_invalid_vault_empty_address() {
619        let request = SignerCreateRequest {
620            id: Some("test-signer".to_string()),
621            signer_type: SignerTypeRequest::Vault,
622            config: SignerConfigRequest::Vault(VaultSignerRequestConfig {
623                address: "".to_string(), // Empty address should fail validation
624                namespace: None,
625                role_id: "test-role".to_string(),
626                secret_id: "test-secret".to_string(),
627                key_name: "test-key".to_string(),
628                mount_point: None,
629            }),
630        };
631
632        let result = Signer::try_from(request);
633        assert!(result.is_err());
634    }
635
636    #[test]
637    fn test_invalid_vault_invalid_url() {
638        let request = SignerCreateRequest {
639            id: Some("test-signer".to_string()),
640            signer_type: SignerTypeRequest::Vault,
641            config: SignerConfigRequest::Vault(VaultSignerRequestConfig {
642                address: "not-a-url".to_string(), // Invalid URL should fail validation
643                namespace: None,
644                role_id: "test-role".to_string(),
645                secret_id: "test-secret".to_string(),
646                key_name: "test-key".to_string(),
647                mount_point: None,
648            }),
649        };
650
651        let result = Signer::try_from(request);
652        assert!(result.is_err());
653
654        if let Err(ApiError::BadRequest(msg)) = result {
655            assert!(msg.contains("Address must be a valid URL"));
656        } else {
657            panic!("Expected BadRequest error for invalid URL");
658        }
659    }
660
661    #[test]
662    fn test_create_request_generates_uuid_when_no_id() {
663        let request = SignerCreateRequest {
664            id: None,
665            signer_type: SignerTypeRequest::Local,
666            config: SignerConfigRequest::Local(LocalSignerRequestConfig {
667                key: "1111111111111111111111111111111111111111111111111111111111111111".to_string(), // 32 bytes as hex
668            }),
669        };
670
671        let result = Signer::try_from(request);
672        assert!(result.is_ok());
673
674        let signer = result.unwrap();
675        assert!(!signer.id.is_empty());
676        assert_eq!(signer.signer_type(), SignerType::Local);
677
678        // Verify it's a valid UUID format
679        assert!(uuid::Uuid::parse_str(&signer.id).is_ok());
680    }
681
682    #[test]
683    fn test_invalid_id_format() {
684        let request = SignerCreateRequest {
685            id: Some("invalid@id".to_string()), // Invalid characters
686            signer_type: SignerTypeRequest::Local,
687            config: SignerConfigRequest::Local(LocalSignerRequestConfig {
688                key: "2222222222222222222222222222222222222222222222222222222222222222".to_string(), // 32 bytes as hex
689            }),
690        };
691
692        let result = Signer::try_from(request);
693        assert!(result.is_err());
694
695        if let Err(ApiError::BadRequest(msg)) = result {
696            assert!(msg.contains("ID must contain only letters, numbers, dashes and underscores"));
697        } else {
698            panic!("Expected BadRequest error with validation message");
699        }
700    }
701
702    #[test]
703    fn test_test_signer_creation() {
704        let request = SignerCreateRequest {
705            id: Some("test-signer".to_string()),
706            signer_type: SignerTypeRequest::Local,
707            config: SignerConfigRequest::Local(LocalSignerRequestConfig {
708                key: "3333333333333333333333333333333333333333333333333333333333333333".to_string(), // 32 bytes as hex
709            }),
710        };
711
712        let result = Signer::try_from(request);
713        assert!(result.is_ok());
714
715        let signer = result.unwrap();
716        assert_eq!(signer.id, "test-signer");
717        assert_eq!(signer.signer_type(), SignerType::Local);
718    }
719
720    #[test]
721    fn test_local_signer_creation() {
722        let request = SignerCreateRequest {
723            id: Some("local-signer".to_string()),
724            signer_type: SignerTypeRequest::Local,
725            config: SignerConfigRequest::Local(LocalSignerRequestConfig {
726                key: "4444444444444444444444444444444444444444444444444444444444444444".to_string(), // 32 bytes as hex
727            }),
728        };
729
730        let result = Signer::try_from(request);
731        assert!(result.is_ok());
732
733        let signer = result.unwrap();
734        assert_eq!(signer.id, "local-signer");
735        assert_eq!(signer.signer_type(), SignerType::Local);
736    }
737
738    #[test]
739    fn test_valid_turnkey_create_request() {
740        let request = SignerCreateRequest {
741            id: Some("test-turnkey-signer".to_string()),
742            signer_type: SignerTypeRequest::Turnkey,
743            config: SignerConfigRequest::Turnkey(TurnkeySignerRequestConfig {
744                api_public_key: "test-public-key".to_string(),
745                api_private_key: "test-private-key".to_string(),
746                organization_id: "test-org".to_string(),
747                private_key_id: "test-private-key-id".to_string(),
748                public_key: "test-public-key".to_string(),
749            }),
750        };
751
752        let result = Signer::try_from(request);
753        assert!(result.is_ok());
754
755        let signer = result.unwrap();
756        assert_eq!(signer.id, "test-turnkey-signer");
757        assert_eq!(signer.signer_type(), SignerType::Turnkey);
758
759        if let Some(turnkey_config) = signer.config.get_turnkey() {
760            assert_eq!(turnkey_config.api_public_key, "test-public-key");
761            assert_eq!(turnkey_config.organization_id, "test-org");
762        } else {
763            panic!("Expected Turnkey config");
764        }
765    }
766
767    #[test]
768    fn test_valid_vault_transit_create_request() {
769        let request = SignerCreateRequest {
770            id: Some("test-vault-transit-signer".to_string()),
771            signer_type: SignerTypeRequest::VaultTransit,
772            config: SignerConfigRequest::VaultTransit(VaultTransitSignerRequestConfig {
773                key_name: "test-key".to_string(),
774                address: "https://vault.example.com".to_string(),
775                namespace: None,
776                role_id: "test-role".to_string(),
777                secret_id: "test-secret".to_string(),
778                pubkey: "test-pubkey".to_string(),
779                mount_point: None,
780            }),
781        };
782
783        let result = Signer::try_from(request);
784        assert!(result.is_ok());
785
786        let signer = result.unwrap();
787        assert_eq!(signer.id, "test-vault-transit-signer");
788        assert_eq!(signer.signer_type(), SignerType::VaultTransit);
789    }
790
791    #[test]
792    fn test_valid_google_cloud_kms_create_request() {
793        let request = SignerCreateRequest {
794            id: Some("test-gcp-kms-signer".to_string()),
795            signer_type: SignerTypeRequest::GoogleCloudKms,
796            config: SignerConfigRequest::GoogleCloudKms(GoogleCloudKmsSignerRequestConfig {
797                service_account: GoogleCloudKmsSignerServiceAccountRequestConfig {
798                    private_key: "test-private-key".to_string(),
799                    private_key_id: "test-key-id".to_string(),
800                    project_id: "test-project".to_string(),
801                    client_email: "test@email.com".to_string(),
802                    client_id: "test-client-id".to_string(),
803                    auth_uri: "https://accounts.google.com/o/oauth2/auth".to_string(),
804                    token_uri: "https://oauth2.googleapis.com/token".to_string(),
805                    auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs".to_string(),
806                    client_x509_cert_url: "https://www.googleapis.com/robot/v1/metadata/x509/test%40test.iam.gserviceaccount.com".to_string(),
807                    universe_domain: "googleapis.com".to_string(),
808                },
809                key: GoogleCloudKmsSignerKeyRequestConfig {
810                    location: "global".to_string(),
811                    key_ring_id: "test-ring".to_string(),
812                    key_id: "test-key".to_string(),
813                    key_version: 1,
814                },
815            }),
816        };
817
818        let result = Signer::try_from(request);
819        assert!(result.is_ok());
820
821        let signer = result.unwrap();
822        assert_eq!(signer.id, "test-gcp-kms-signer");
823        assert_eq!(signer.signer_type(), SignerType::GoogleCloudKms);
824    }
825
826    #[test]
827    fn test_invalid_local_hex_key() {
828        let request = SignerCreateRequest {
829            id: Some("test-signer".to_string()),
830            signer_type: SignerTypeRequest::Local,
831            config: SignerConfigRequest::Local(LocalSignerRequestConfig {
832                key: "invalid-hex".to_string(), // Invalid hex
833            }),
834        };
835
836        let result = Signer::try_from(request);
837        assert!(result.is_err());
838        if let Err(ApiError::BadRequest(msg)) = result {
839            assert!(msg.contains("Invalid hex key format"));
840        }
841    }
842
843    #[test]
844    fn test_invalid_turnkey_empty_key() {
845        let request = SignerCreateRequest {
846            id: Some("test-signer".to_string()),
847            signer_type: SignerTypeRequest::Turnkey,
848            config: SignerConfigRequest::Turnkey(TurnkeySignerRequestConfig {
849                api_public_key: "".to_string(), // Empty
850                api_private_key: "test-private-key".to_string(),
851                organization_id: "test-org".to_string(),
852                private_key_id: "test-private-key-id".to_string(),
853                public_key: "test-public-key".to_string(),
854            }),
855        };
856
857        let result = Signer::try_from(request);
858        assert!(result.is_err());
859        if let Err(ApiError::BadRequest(msg)) = result {
860            assert!(msg.contains("API public key cannot be empty"));
861        }
862    }
863}