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