1use crate::Result;
7use crate::error::CommonError;
8
9const VALID_PEM_HEADERS: &[&str] = &[
11 "-----BEGIN PRIVATE KEY-----",
12 "-----BEGIN EC PRIVATE KEY-----",
13 "-----BEGIN RSA PRIVATE KEY-----",
14 "-----BEGIN ENCRYPTED PRIVATE KEY-----",
15];
16
17const VALID_PEM_FOOTERS: &[&str] = &[
19 "-----END PRIVATE KEY-----",
20 "-----END EC PRIVATE KEY-----",
21 "-----END RSA PRIVATE KEY-----",
22 "-----END ENCRYPTED PRIVATE KEY-----",
23];
24
25pub fn validate_pem_format(private_key: &str) -> Result<()> {
45 if !private_key.starts_with("-----BEGIN")
47 || !(private_key.ends_with("-----\n") || private_key.ends_with("-----"))
48 {
49 return Err(CommonError::Auth(
50 "Invalid PEM format: must start with '-----BEGIN' and end with '-----'".into(),
51 ));
52 }
53
54 let has_valid_header = VALID_PEM_HEADERS
56 .iter()
57 .any(|header| private_key.contains(header));
58 if !has_valid_header {
59 return Err(CommonError::Auth(
60 "Invalid PEM format: must contain valid private key header".into(),
61 ));
62 }
63
64 let has_valid_footer = VALID_PEM_FOOTERS
66 .iter()
67 .any(|footer| private_key.contains(footer));
68 if !has_valid_footer {
69 return Err(CommonError::Auth(
70 "Invalid PEM format: must contain valid private key footer".into(),
71 ));
72 }
73
74 for (header, footer) in VALID_PEM_HEADERS.iter().zip(VALID_PEM_FOOTERS.iter()) {
76 if private_key.contains(header) && !private_key.contains(footer) {
77 return Err(CommonError::Auth(
78 format!(
79 "Invalid PEM format: header '{header}' requires matching footer '{footer}'"
80 )
81 .into(),
82 ));
83 }
84 }
85
86 Ok(())
87}
88
89pub fn extract_pem_key_type(private_key: &str) -> Option<&'static str> {
98 if private_key.contains("-----BEGIN EC PRIVATE KEY-----") {
99 Some("EC")
100 } else if private_key.contains("-----BEGIN RSA PRIVATE KEY-----") {
101 Some("RSA")
102 } else if private_key.contains("-----BEGIN PRIVATE KEY-----") {
103 Some("") } else if private_key.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----") {
105 Some("ENCRYPTED")
106 } else {
107 None
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_valid_pem_formats() {
117 let valid_pems = vec![
118 "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----",
119 "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEE...\n-----END EC PRIVATE KEY-----",
120 "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIB...\n-----END RSA PRIVATE KEY-----",
121 "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----\n",
122 ];
123
124 for pem in valid_pems {
125 assert!(
126 validate_pem_format(pem).is_ok(),
127 "Failed to validate: {pem}"
128 );
129 }
130 }
131
132 #[test]
133 fn test_invalid_pem_formats() {
134 let invalid_pems = vec![
135 "not a pem key",
136 "-----BEGIN PRIVATE KEY-----",
137 "-----END PRIVATE KEY-----",
138 "-----BEGIN CERTIFICATE-----\nMIIE...\n-----END CERTIFICATE-----",
139 "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----", ];
141
142 for pem in invalid_pems {
143 assert!(
144 validate_pem_format(pem).is_err(),
145 "Should have failed: {pem}"
146 );
147 }
148 }
149
150 #[test]
151 fn test_extract_key_type() {
152 assert_eq!(
153 extract_pem_key_type(
154 "-----BEGIN EC PRIVATE KEY-----\ntest\n-----END EC PRIVATE KEY-----"
155 ),
156 Some("EC")
157 );
158 assert_eq!(
159 extract_pem_key_type(
160 "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----"
161 ),
162 Some("RSA")
163 );
164 assert_eq!(
165 extract_pem_key_type("-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----"),
166 Some("")
167 );
168 assert_eq!(extract_pem_key_type("not a pem"), None);
169 }
170}