smart_id_rust_client/models/user_identity.rs
1use crate::error::Result;
2use crate::error::SmartIdClientError;
3use base64::prelude::BASE64_STANDARD;
4use base64::Engine;
5use oid_registry::asn1_rs::FromDer;
6use oid_registry::OidRegistry;
7use serde::{Deserialize, Serialize};
8use x509_parser::certificate::X509Certificate;
9
10/// User Identity
11///
12/// This struct represents the identity of a user, including their given name, surname, and identity code.
13/// This is used to validate the identity of a user against the identity provided in the response certificates.
14///
15/// # Properties
16///
17/// * `given_name` - The given name of the user.
18/// * `surname` - The surname of the user.
19/// * `identity_code` - The identity code of the user.
20///
21/// # Example
22///
23/// ```rust
24/// use smart_id_rust_client::models::user_identity::UserIdentity;
25///
26/// // Create a user identity from stored information
27/// let user_identity = UserIdentity {
28/// given_name: "Joey".to_string(),
29/// surname: "de l'Arago".to_string(),
30/// identity_code: "PNOBE-{ETSI_NUMBER}".to_string(),
31/// };
32///
33/// // Create a user identity from a certificate
34/// let certificate = "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";
35/// let user_identity = UserIdentity::from_certificate(certificate.to_string()).unwrap();
36/// ```
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct UserIdentity {
39 pub given_name: String,
40 pub surname: String,
41 pub identity_code: String,
42}
43
44impl UserIdentity {
45 /// Creates a `UserIdentity` from a base64 encoded certificate.
46 ///
47 /// # Arguments
48 ///
49 /// * `certificate` - A base64 encoded string representing the user's certificate.
50 ///
51 /// # Errors
52 ///
53 /// Returns an error if the certificate cannot be decoded from base64 or if the certificate cannot be parsed.
54 ///
55 /// # Example
56 ///
57 /// ```rust
58 /// use smart_id_rust_client::models::user_identity::UserIdentity;
59 /// use smart_id_rust_client::error::Result;
60 ///
61 /// let certificate = "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";
62 /// let user_identity = UserIdentity::from_certificate(certificate.to_string()).unwrap();
63 /// ```
64 pub fn from_certificate(certificate: String) -> Result<Self> {
65 let decoded_cert = BASE64_STANDARD.decode(&certificate).map_err(|e| {
66 SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
67 "Could not decode base64 certificate: {:?}",
68 e
69 ))
70 })?;
71
72 let (_, parsed_cert) = X509Certificate::from_der(decoded_cert.as_slice()).map_err(|e| {
73 SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
74 "Failed to parse certificate: {:?}",
75 e
76 ))
77 })?;
78
79 let given_name = UserIdentity::get_attribute_value(&parsed_cert, "givenName")?;
80 let surname = UserIdentity::get_attribute_value(&parsed_cert, "surname")?;
81 let identity_code = UserIdentity::get_attribute_value(&parsed_cert, "serialNumber")?;
82
83 Ok(UserIdentity {
84 given_name,
85 surname,
86 identity_code,
87 })
88 }
89
90 pub(crate) fn identity_matches_certificate(&self, certificate: String) -> Result<()> {
91 let certificate_identity = UserIdentity::from_certificate(certificate)?;
92
93 if self.given_name.to_uppercase() != certificate_identity.given_name.to_uppercase() {
94 return Err(
95 SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
96 "Given name provided in identity does not match certificate: {:?} != {:?}",
97 self.given_name.to_uppercase(),
98 certificate_identity.given_name.to_uppercase()
99 )),
100 );
101 }
102
103 if self.surname.to_uppercase() != certificate_identity.surname.to_uppercase() {
104 return Err(
105 SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
106 "Surname provided in identity does not match certificate: {:?} != {:?}",
107 self.surname.to_uppercase(),
108 certificate_identity.surname.to_uppercase()
109 )),
110 );
111 }
112
113 if self.identity_code.to_uppercase() != certificate_identity.identity_code.to_uppercase() {
114 return Err(
115 SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
116 "Identity code provided in identity does not match certificate: {:?} != {:?}",
117 self.identity_code.to_uppercase(),
118 certificate_identity.identity_code.to_uppercase()
119 )),
120 );
121 }
122
123 Ok(())
124 }
125
126 fn get_attribute_value(certificate: &X509Certificate, oid_simple_name: &str) -> Result<String> {
127 let registry = OidRegistry::default().with_x509();
128 let oid = registry
129 .iter_by_sn(oid_simple_name)
130 .next()
131 .map(|(oid, _)| oid)
132 .ok_or(
133 SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
134 "OID not found in registry: {:?}",
135 oid_simple_name
136 )),
137 )?;
138
139 let attribute = certificate
140 .subject()
141 .iter_attributes()
142 .find(|a| a.attr_type() == oid)
143 .ok_or(
144 SmartIdClientError::FailedToValidateSessionResponseCertificate(format!(
145 "OID not found in certificate: {:?}",
146 oid
147 )),
148 )?;
149
150 attribute
151 .attr_value()
152 .as_string()
153 .or_else(|_e| {
154 attribute
155 .attr_value()
156 .as_printablestring()
157 .map(|g| g.string())
158 })
159 .map_err(|e| {
160 SmartIdClientError::FailedToValidateSessionResponseCertificate(
161 format!("Certificate does not match provided user identity, attribute missing from cert: {:?}", e)
162 )
163 })
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 const CERT: &str = "";
172
173 #[test]
174 #[ignore]
175 fn test_check_identity_against_certificate() {
176 let user_identity = UserIdentity {
177 given_name: "Joey".to_string(),
178 surname: "de l'Arago".to_string(),
179 identity_code: "PNOBE-{ETSI_NUMBER}".to_string(),
180 };
181
182 let result = user_identity.identity_matches_certificate(CERT.to_string());
183
184 println!("Result: {:?}", result);
185 assert!(result.is_ok());
186 }
187
188 #[test]
189 #[ignore]
190 fn test_check_identity_case_insensitive() {
191 let user_identity = UserIdentity {
192 given_name: "joey".to_string(),
193 surname: "DE L'ARAGO".to_string(),
194 identity_code: "pnobe-{ETSI_NUMBER}".to_string(),
195 };
196
197 let result = user_identity.identity_matches_certificate(CERT.to_string());
198
199 assert!(result.is_ok());
200 }
201
202 #[test]
203 #[ignore]
204 fn test_check_identity_incorrect_given_name() {
205 let user_identity = UserIdentity {
206 given_name: "Incorrect".to_string(),
207 surname: "de l'Arago".to_string(),
208 identity_code: "PNOBE-{ETSI_NUMBER}".to_string(),
209 };
210
211 let result = user_identity.identity_matches_certificate(CERT.to_string());
212
213 assert!(result.is_err());
214 }
215
216 #[test]
217 #[ignore]
218 fn test_check_identity_incorrect_surname() {
219 let user_identity = UserIdentity {
220 given_name: "Joey".to_string(),
221 surname: "Incorrect".to_string(),
222 identity_code: "PNOBE-{ETSI_NUMBER}".to_string(),
223 };
224
225 let result = user_identity.identity_matches_certificate(CERT.to_string());
226
227 assert!(result.is_err());
228 }
229
230 #[test]
231 #[ignore]
232 fn test_check_identity_incorrect_identity_code() {
233 let user_identity = UserIdentity {
234 given_name: "Joey".to_string(),
235 surname: "de l'Arago".to_string(),
236 identity_code: "Incorrect".to_string(),
237 };
238
239 let result = user_identity.identity_matches_certificate(CERT.to_string());
240
241 assert!(result.is_err());
242 }
243
244}