kanidmd_lib/valueset/
certificate.rs

1use crate::be::dbvalue::DbValueCertificate;
2use crate::prelude::*;
3use crate::schema::SchemaAttribute;
4use crate::valueset::ScimResolveStatus;
5use crate::valueset::{DbValueSetV2, ValueSet, ValueSetResolveStatus, ValueSetScimPut};
6use crypto_glue::{
7    s256::Sha256Output,
8    traits::{DecodeDer, EncodeDer, EncodePem, LineEndingPem},
9    x509::{x509_digest_public_key_sha256, Certificate},
10};
11use kanidm_proto::scim_v1::client::ScimCertificate as ClientScimCertificate;
12use kanidm_proto::scim_v1::server::ScimCertificate;
13use kanidm_proto::scim_v1::JsonValue;
14use std::collections::BTreeMap;
15
16#[derive(Debug, Clone)]
17pub struct ValueSetCertificate {
18    map: BTreeMap<Sha256Output, Box<Certificate>>,
19}
20
21impl ValueSetCertificate {
22    pub fn new(certificate: Box<Certificate>) -> Result<Box<Self>, OperationError> {
23        let mut map = BTreeMap::new();
24
25        let pk_s256 = x509_digest_public_key_sha256(&certificate).ok_or_else(|| {
26            error!("Unable to digest public key");
27            OperationError::VS0002CertificatePublicKeyDigest
28        })?;
29        map.insert(pk_s256, certificate);
30
31        Ok(Box::new(ValueSetCertificate { map }))
32    }
33
34    pub fn from_dbvs2(data: Vec<DbValueCertificate>) -> Result<ValueSet, OperationError> {
35        Self::from_dbv_iter(data.into_iter())
36    }
37
38    fn from_dbv_iter(
39        certs: impl Iterator<Item = DbValueCertificate>,
40    ) -> Result<ValueSet, OperationError> {
41        let mut map = BTreeMap::new();
42
43        for db_cert in certs {
44            match db_cert {
45                DbValueCertificate::V1 { certificate_der } => {
46                    // Parse the DER
47                    let certificate = Certificate::from_der(&certificate_der)
48                        .map(Box::new)
49                        .map_err(|x509_err| {
50                            error!(?x509_err, "Unable to restore certificate from DER");
51                            OperationError::VS0003CertificateDerDecode
52                        })?;
53
54                    // sha256 the public key
55                    let pk_s256 = x509_digest_public_key_sha256(&certificate).ok_or_else(|| {
56                        error!("Unable to digest public key");
57                        OperationError::VS0004CertificatePublicKeyDigest
58                    })?;
59
60                    map.insert(pk_s256, certificate);
61                }
62            }
63        }
64
65        Ok(Box::new(ValueSetCertificate { map }))
66    }
67
68    fn to_vec_dbvs(&self) -> Vec<DbValueCertificate> {
69        self.map
70            .iter()
71            .filter_map(|(pk_s256, cert)| {
72                cert.to_der()
73                    .map_err(|der_err| {
74                        error!(
75                            ?pk_s256,
76                            ?der_err,
77                            "Failed to serialise certificate to der. This value will be dropped!"
78                        );
79                    })
80                    .ok()
81            })
82            .map(|certificate_der| DbValueCertificate::V1 { certificate_der })
83            .collect()
84    }
85
86    #[allow(clippy::should_implement_trait)]
87    pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
88    where
89        T: IntoIterator<Item = Box<Certificate>>,
90    {
91        let mut map = BTreeMap::new();
92
93        for certificate in iter {
94            let pk_s256 = x509_digest_public_key_sha256(&certificate)?;
95            map.insert(pk_s256, certificate);
96        }
97
98        Some(Box::new(ValueSetCertificate { map }))
99    }
100}
101
102impl ValueSetScimPut for ValueSetCertificate {
103    fn from_scim_json_put(value: JsonValue) -> Result<ValueSetResolveStatus, OperationError> {
104        let der_values: Vec<ClientScimCertificate> =
105            serde_json::from_value(value).map_err(|err| {
106                error!(?err, "SCIM Certificate syntax invalid");
107                OperationError::SC0012CertificateSyntaxInvalid
108            })?;
109
110        // For each one, check it's a real der certificate.
111        let mut map = BTreeMap::new();
112
113        for ClientScimCertificate { der } in der_values {
114            // Parse the DER
115            let certificate = Certificate::from_der(&der)
116                .map(Box::new)
117                .map_err(|x509_err| {
118                    error!(?x509_err, "Unable to restore certificate from DER");
119                    OperationError::SC0013CertificateInvalidDer
120                })?;
121
122            // sha256 the public key
123            let pk_s256 = x509_digest_public_key_sha256(&certificate).ok_or_else(|| {
124                error!("Unable to digest public key");
125                OperationError::SC0014CertificateInvalidDigest
126            })?;
127
128            map.insert(pk_s256, certificate);
129        }
130
131        Ok(ValueSetResolveStatus::Resolved(Box::new(
132            ValueSetCertificate { map },
133        )))
134    }
135}
136
137impl ValueSetT for ValueSetCertificate {
138    fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
139        match value {
140            Value::Certificate(certificate) => {
141                let pk_s256 = x509_digest_public_key_sha256(&certificate).ok_or_else(|| {
142                    error!("Unable to digest public key");
143                    OperationError::VS0005CertificatePublicKeyDigest
144                })?;
145
146                // bool -> true if the insert did not trigger a duplicate.
147                Ok(self.map.insert(pk_s256, certificate).is_none())
148            }
149            _ => {
150                debug_assert!(false);
151                Err(OperationError::InvalidValueState)
152            }
153        }
154    }
155
156    fn clear(&mut self) {
157        self.map.clear();
158    }
159
160    fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
161        match pv {
162            PartialValue::HexString(hs) => {
163                let mut buf = Sha256Output::default();
164                if hex::decode_to_slice(hs, &mut buf).is_ok() {
165                    self.map.remove(&buf).is_some()
166                } else {
167                    false
168                }
169            }
170            _ => false,
171        }
172    }
173
174    fn contains(&self, pv: &PartialValue) -> bool {
175        match pv {
176            PartialValue::HexString(hs) => {
177                let mut buf = Sha256Output::default();
178                if hex::decode_to_slice(hs, &mut buf).is_ok() {
179                    self.map.contains_key(&buf)
180                } else {
181                    false
182                }
183            }
184            _ => false,
185        }
186    }
187
188    fn substring(&self, _pv: &PartialValue) -> bool {
189        false
190    }
191
192    fn startswith(&self, _pv: &PartialValue) -> bool {
193        false
194    }
195
196    fn endswith(&self, _pv: &PartialValue) -> bool {
197        false
198    }
199
200    fn lessthan(&self, _pv: &PartialValue) -> bool {
201        false
202    }
203
204    fn len(&self) -> usize {
205        self.map.len()
206    }
207
208    fn generate_idx_eq_keys(&self) -> Vec<String> {
209        self.map.keys().map(hex::encode).collect()
210    }
211
212    fn syntax(&self) -> SyntaxType {
213        SyntaxType::Certificate
214    }
215
216    fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
217        true
218    }
219
220    fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
221        Box::new(self.map.iter().filter_map(|(pk_s256, cert)| {
222            cert.to_pem(LineEndingPem::LF)
223                .ok()
224                .map(|pem| format!("{}\n{}", hex::encode(pk_s256), pem))
225        }))
226    }
227
228    fn to_scim_value(&self) -> Option<ScimResolveStatus> {
229        let vals: Vec<ScimCertificate> = self
230            .map
231            .iter()
232            .filter_map(|(s256, cert)| {
233                cert.to_der()
234                    .map_err(|der_err| {
235                        error!(
236                            ?s256,
237                            ?der_err,
238                            "Failed to serialise certificate to der. This value will be dropped!"
239                        );
240                    })
241                    .ok()
242                    .map(|der| (s256, der))
243            })
244            .map(|(s256, cert_der)| ScimCertificate {
245                s256: s256.to_vec(),
246                der: cert_der,
247            })
248            .collect::<Vec<_>>();
249
250        if vals.is_empty() {
251            None
252        } else {
253            Some(ScimValueKanidm::from(vals).into())
254        }
255    }
256
257    fn to_db_valueset_v2(&self) -> DbValueSetV2 {
258        let data = self.to_vec_dbvs();
259        DbValueSetV2::Certificate(data)
260    }
261
262    fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
263        Box::new(
264            self.map
265                .keys()
266                .map(hex::encode)
267                .map(PartialValue::HexString),
268        )
269    }
270
271    fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
272        Box::new(self.map.values().cloned().map(Value::Certificate))
273    }
274
275    fn equal(&self, other: &ValueSet) -> bool {
276        if let Some(other) = other.as_certificate_set() {
277            &self.map == other
278        } else {
279            debug_assert!(false);
280            false
281        }
282    }
283
284    fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
285        if let Some(b) = other.as_certificate_set() {
286            mergemaps!(self.map, b)
287        } else {
288            debug_assert!(false);
289            Err(OperationError::InvalidValueState)
290        }
291    }
292
293    fn to_certificate_single(&self) -> Option<&Certificate> {
294        if self.map.len() == 1 {
295            self.map.values().take(1).map(|b| b.as_ref()).next()
296        } else {
297            None
298        }
299    }
300
301    fn as_certificate_set(&self) -> Option<&BTreeMap<Sha256Output, Box<Certificate>>> {
302        Some(&self.map)
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::ValueSetCertificate;
309    use crate::prelude::{ScimValueKanidm, ValueSet};
310    use crypto_glue::{traits::DecodePem, x509::Certificate};
311
312    // Generated with:
313    //
314    // openssl ecparam -out ec_key.pem -name secp256r1 -genkey
315    // openssl req -new -key ec_key.pem -x509 -nodes -days 365 -out cert.pem
316    const PEM_DATA: &str = r#"-----BEGIN CERTIFICATE-----
317MIIB3zCCAYWgAwIBAgIUdJ6IWvI+8M6nwK7ykUK7/iBq7yQwCgYIKoZIzj0EAwIw
318RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
319dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA4MjEwNjQ2MzBaFw0yNTA4MjEw
320NjQ2MzBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
321VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjO
322PQMBBwNCAAS2Szn4NPmgxawC1+MRC41jqobemNkXkRZ9AgozK0zRDFc6k1IHUZ++
323wN0USpXDQYDnJfATqvlpKPebnHxTytt6o1MwUTAdBgNVHQ4EFgQU1oR1x2CnoPap
324JMKPCVVzqWf2ANYwHwYDVR0jBBgwFoAU1oR1x2CnoPapJMKPCVVzqWf2ANYwDwYD
325VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBpy0o2CY97MIxeQ0HgG44Y
326raBy6edj7W0EIH+yQxkDEwIhAI0nVKaI6duHLAvtKW6CfEQFG6jKg7dyk37YYiRD
3272jS0
328-----END CERTIFICATE-----"#;
329
330    #[test]
331    fn test_scim_certificate() {
332        let cert = Certificate::from_pem(PEM_DATA).unwrap();
333
334        let vs: ValueSet = ValueSetCertificate::new(Box::new(cert)).unwrap();
335
336        let scim_value = vs.to_scim_value().unwrap().assume_resolved();
337
338        let cert = match scim_value {
339            ScimValueKanidm::ArrayCertificate(mut set) => set.pop().unwrap(),
340            _ => unreachable!(),
341        };
342
343        let expect_s256 =
344            hex::decode("8c98a09d0a50db92ccb6d05be846d9b9315015520d19a3e1739aeb8d84ebc28d")
345                .unwrap();
346
347        assert_eq!(cert.s256, expect_s256);
348
349        // Test that we can parse json values into a valueset.
350        crate::valueset::scim_json_put_reflexive::<ValueSetCertificate>(&vs, &[])
351    }
352}