Skip to main content

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}