libkeycard/
entry.rs

1use chrono::prelude::*;
2use chrono::{Duration, NaiveDate};
3use eznacl::*;
4use std::collections::HashMap;
5use std::fmt;
6
7use crate::base::*;
8use crate::verifiers::*;
9
10static ORG_PERMITTED_FIELDS: [&str; 14] = [
11    "Type",
12    "Index",
13    "Name",
14    "Domain",
15    "Contact-Admin",
16    "Contact-Abuse",
17    "Contact-Support",
18    "Language",
19    "Primary-Verification-Key",
20    "Secondary-Verification-Key",
21    "Encryption-Key",
22    "Time-To-Live",
23    "Expires",
24    "Timestamp",
25];
26
27static ORG_REQUIRED_FIELDS: [&str; 10] = [
28    "Type",
29    "Index",
30    "Name",
31    "Domain",
32    "Contact-Admin",
33    "Primary-Verification-Key",
34    "Encryption-Key",
35    "Time-To-Live",
36    "Expires",
37    "Timestamp",
38];
39
40static USER_PERMITTED_FIELDS: [&str; 13] = [
41    "Type",
42    "Index",
43    "Name",
44    "User-ID",
45    "Workspace-ID",
46    "Domain",
47    "Contact-Request-Verification-Key",
48    "Contact-Request-Encryption-Key",
49    "Encryption-Key",
50    "Verification-Key",
51    "Time-To-Live",
52    "Expires",
53    "Timestamp",
54];
55
56static USER_REQUIRED_FIELDS: [&str; 11] = [
57    "Type",
58    "Index",
59    "Workspace-ID",
60    "Domain",
61    "Contact-Request-Verification-Key",
62    "Contact-Request-Encryption-Key",
63    "Encryption-Key",
64    "Verification-Key",
65    "Time-To-Live",
66    "Expires",
67    "Timestamp",
68];
69
70/// Denotes an entry's type. None is only used for uninitialized Entry instances.
71#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
72#[cfg_attr(feature = "use_serde", derive(serde::Serialize, serde::Deserialize))]
73pub enum EntryType {
74    Organization,
75    User,
76    None,
77}
78
79impl fmt::Display for EntryType {
80    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
81        match self {
82            EntryType::None => write!(f, "None"),
83            EntryType::Organization => write!(f, "Organization"),
84            EntryType::User => write!(f, "User"),
85        }
86    }
87}
88
89/// Entry represents a single entry in a keycard and contains both fields and authentication
90/// strings, which can be a digital signature or a cryptographic hash.
91#[derive(Clone, Debug)]
92#[cfg_attr(feature = "use_serde", derive(serde::Serialize, serde::Deserialize))]
93pub struct Entry {
94    _type: EntryType,
95    fields: HashMap<String, String>,
96    sigs: HashMap<String, CryptoString>,
97}
98
99impl Entry {
100    /// Creates a new entry given the value held in the passed string. As if this writing only
101    /// "Organization", "User", or "" are valid, the last of which creating an Entry of type None.
102    pub fn new_from_str(entrytype: &str) -> Result<Entry, LKCError> {
103        match entrytype {
104            "Organization" => Entry::new(EntryType::Organization),
105            "User" => Entry::new(EntryType::User),
106            "" => Entry::new(EntryType::None),
107            _ => Err(LKCError::ErrBadValue),
108        }
109    }
110
111    /// Creates a new entry based on the type given
112    pub fn new(entrytype: EntryType) -> Result<Entry, LKCError> {
113        let mut out = Entry {
114            _type: entrytype,
115            fields: HashMap::<String, String>::new(),
116            sigs: HashMap::<String, CryptoString>::new(),
117        };
118
119        match entrytype {
120            EntryType::Organization => out.set_field("Type", "Organization")?,
121            EntryType::User => out.set_field("Type", "User")?,
122            EntryType::None => (),
123        }
124
125        // Set some default values to save the caller some time.
126        out.set_field("Timestamp", &get_timestamp())?;
127        out.set_field("Time-To-Live", "14")?;
128        match entrytype {
129            EntryType::User => out.set_expiration(90)?,
130            EntryType::Organization => out.set_expiration(365)?,
131            _ => (),
132        }
133
134        Ok(out)
135    }
136
137    /// Creates a new entry from the text data given it. The format of an entry is documented in
138    /// the Mensago
139    /// [Identity Services](https://gitlab.com/mensago/mensago-docs/-/blob/master/Identity%20Services.adoc)
140    /// design document.
141    pub fn from(s: &str) -> Result<Entry, LKCError> {
142        // 160 is a close approximation. It includes the names of all required fields and the
143        // minimum length for any variable-length fields, including keys. It's a good quick way of
144        // ruling out obviously bad data.
145        if s.len() < 160 {
146            return Err(LKCError::ErrBadValue);
147        }
148
149        let mut out = Entry::new(EntryType::None)?;
150        for line in s.split("\r\n") {
151            if line.len() == 0 {
152                continue;
153            }
154
155            let trimmed = line.trim();
156            if trimmed.len() == 0 {
157                continue;
158            }
159
160            let parts = trimmed.splitn(2, ":").collect::<Vec<&str>>();
161            if parts.len() != 2 {
162                return Err(LKCError::ErrBadFieldValue(String::from(trimmed)));
163            }
164
165            if parts[1].len() > 6144 {
166                return Err(LKCError::ErrOutOfRange);
167            }
168
169            let field_value = match parts.get(1) {
170                Some(v) => v.clone(),
171                None => return Err(LKCError::ErrBadFieldValue(String::from(parts[0]))),
172            };
173
174            match parts[0] {
175                "Custody-Signature"
176                | "Organization-Signature"
177                | "Previous-Hash"
178                | "Hash"
179                | "User-Signature" => match CryptoString::from(field_value) {
180                    Some(cs) => {
181                        out.add_authstr(parts[0], &cs)?;
182                        continue;
183                    }
184                    None => return Err(LKCError::ErrBadFieldValue(String::from(parts[0]))),
185                },
186                _ => (),
187            }
188
189            match out.set_field(parts[0], field_value) {
190                Ok(_) => { /* */ }
191                Err(e) => return Err(e),
192            }
193        }
194
195        Ok(out)
196    }
197
198    /// Returns true if the entry has a specific field
199    pub fn has_field(&self, field: &str) -> bool {
200        match self.fields.get(field) {
201            Some(_) => true,
202            None => false,
203        }
204    }
205
206    /// Gets the specified field for an entry. Naming for the field exactly matches the spec.
207    pub fn get_field(&self, field: &str) -> Result<String, LKCError> {
208        match self.fields.get(field) {
209            Some(v) => Ok(v.clone()),
210            None => Err(LKCError::ErrNotFound),
211        }
212    }
213
214    /// Sets an entry field. Naming for the field exactly matches the spec.
215    pub fn set_field(&mut self, field: &str, value: &str) -> Result<(), LKCError> {
216        verify_field(field, value)?;
217
218        if field == "Type" {
219            self._type = match value {
220                "Organization" => EntryType::Organization,
221                "User" => EntryType::User,
222                _ => EntryType::None,
223            };
224        }
225        let _ = self.fields.insert(String::from(field), String::from(value));
226        Ok(())
227    }
228
229    /// Sets multiple entry fields from a list of type-value mappings
230    pub fn set_fields(&mut self, fields: &Vec<(String, String)>) -> Result<(), LKCError> {
231        for (k, v) in fields.iter() {
232            self.set_field(&k, &v)?;
233        }
234        Ok(())
235    }
236
237    /// Deletes a field from the entry
238    pub fn delete_field(&mut self, field: &str) -> Result<(), LKCError> {
239        let _ = self.fields.remove(field);
240        Ok(())
241    }
242
243    /// Returns the owner for the entry, which will a string containing a workspace address for a
244    /// user entry and a domain for an organization entry. It will fail if the needed fields are
245    /// not populated (Doman, Domain + Workspace-ID).
246    pub fn get_owner(&self) -> Result<String, LKCError> {
247        match self._type {
248            EntryType::Organization => self.get_field("Domain"),
249            EntryType::User => {
250                let mut widstr = self.get_field("Workspace-ID")?;
251                let domain = self.get_field("Domain")?;
252                widstr.push('/');
253                widstr.push_str(&domain);
254
255                Ok(widstr)
256            }
257            _ => return Err(LKCError::ErrNoInit),
258        }
259    }
260
261    /// Checks the formatting of the regular fields in the entry and returns false if a field does
262    /// not comply. This method is usually called to ensure that the data in an entry is valid
263    /// before proceeding with the signing and hashing process.
264    pub fn is_data_compliant(&self) -> Result<(), LKCError> {
265        // Ensure that all required fields are present. Because each field is a ValidatedString, we
266        // already know that if the field is present, it's valid, too. :)
267        match self._type {
268            EntryType::Organization => {
269                for f in ORG_REQUIRED_FIELDS {
270                    match self.fields.get(f) {
271                        Some(_) => { /* do nothing */ }
272                        None => return Err(LKCError::ErrMissingField(String::from(f))),
273                    }
274                }
275            }
276            EntryType::User => {
277                for f in USER_REQUIRED_FIELDS {
278                    match self.fields.get(f) {
279                        Some(_) => { /* do nothing */ }
280                        None => return Err(LKCError::ErrMissingField(String::from(f))),
281                    }
282                }
283            }
284            _ => return Err(LKCError::ErrNoInit),
285        }
286
287        Ok(())
288    }
289
290    /// Returns false if the entry has any compliance issues, including missing or bad hashes
291    /// and/or signatures. This method performs all the checks made in `is_data_compliant()` and
292    /// more. Note that only the format of signatures and hashes are checked. The validity of a
293    /// hash or signature must be checked using [`verify()`](struct.Keycard.html#method.verify) or
294    /// [`verify_chain()`](struct.Keycard.html#method.verify_chain).
295    ///
296    /// For an entry to be compliant, an organization entry MUST have the following fields:
297    ///
298    /// - Type
299    /// - Index
300    /// - Name
301    /// - Domain
302    /// - Contact-Admin
303    /// - Primary-Verification-Key
304    /// - Encryption-Key
305    /// - Time-To-Live"
306    /// - Expires
307    /// - Timestamp
308    ///
309    ///
310    /// Organizational entries may also have any of the following optional fields:
311    ///
312    /// - Contact-Abuse
313    /// - Contact-Support
314    /// - Language
315    /// - Secondary-Verification-Key
316    ///
317    /// User entries MUST have the following fields:
318    ///
319    /// - Type
320    /// - Index
321    /// - Workspace-ID
322    /// - Domain
323    /// - Contact-Request-Verification-Key
324    /// - Contact-Request-Encryption-Key
325    /// - Verification-Key
326    /// - Encryption-Key
327    /// - Time-To-Live"
328    /// - Expires
329    /// - Timestamp
330    ///
331    /// User entries MAY also have a Name or User-ID field, although these are optional.
332    ///
333    /// Additionally, any entry MUST also have signatures and hashes applied in the order specified
334    /// in the description for [`get_full_text()`](struct.Entry.html#method.get_full_text).
335    pub fn is_compliant(&self) -> Result<(), LKCError> {
336        self.is_data_compliant()?;
337
338        match self._type {
339            EntryType::Organization => {
340                if self.get_field("Index")? == "1" {
341                    // An organization's first (and hopefully only) root keycard should *never*
342                    // have a Revoke field.
343                    if self.has_field("Revoke") {
344                        return Err(LKCError::ErrInvalidKeycard);
345                    }
346                } else {
347                    // The only time an org entry which has an Index greater than one should not
348                    // have a custody signature or a previous hash is if the previous entry was
349                    // revoked and, thus, the current one is the new root for the organization.
350                    if !self.has_field("Revoke") {
351                        _ = self.get_authstr("Custody-Signature")?;
352                        _ = self.get_authstr("Previous-Hash")?;
353                    }
354                }
355                _ = self.get_authstr("Hash")?;
356                _ = self.get_authstr("Organization-Signature")?;
357            }
358            EntryType::User => {
359                if self.get_field("Index")? == "1" {
360                    // A user's first root keycard should *never* have a Revoke field.
361                    if self.has_field("Revoke") {
362                        return Err(LKCError::ErrInvalidKeycard);
363                    }
364                } else {
365                    // A replacement root entry will have a Revoke field, in which case it will
366                    // not have a custody signature, but it will have an index greater than 1.
367                    if !self.has_field("Revoke") {
368                        _ = self.get_authstr("Custody-Signature")?;
369                    }
370                }
371                _ = self.get_authstr("Organization-Signature")?;
372                _ = self.get_authstr("Previous-Hash")?;
373                _ = self.get_authstr("Hash")?;
374                _ = self.get_authstr("User-Signature")?;
375            }
376            EntryType::None => return Err(LKCError::ErrNoInit),
377        }
378
379        Ok(())
380    }
381
382    /// Sets the expiration date for the entry. The maximum number of days for entries is 1095
383    /// (~3 years). The recommended value are 365 for an organization entry and 90 for a user entry.
384    pub fn set_expiration(&mut self, numdays: u16) -> Result<(), LKCError> {
385        if numdays > 1095 || numdays < 1 {
386            return Err(LKCError::ErrBadValue);
387        }
388
389        let offdate = match get_offset_date(Duration::days(numdays as i64)) {
390            Some(v) => v,
391            None => {
392                return Err(LKCError::ErrProgramException(String::from(
393                    "set_expiration: failure to create expiration date",
394                )))
395            }
396        };
397        self.set_field("Expires", &offdate)?;
398
399        Ok(())
400    }
401
402    /// Returns true if the entry has exceeded its expiration date
403    pub fn is_expired(&self) -> Result<bool, LKCError> {
404        // Yes, it would make more sense to simply have a stored value which was already parsed,
405        // but the necessary code to do the dynamic dispatch to handle this would probably increase
406        // complexity by an order of magnitude. We'll sacrifice a tiny bit of performance for
407        // simplicity.
408        let expdate = match self.fields.get("Expires") {
409            Some(v) => {
410                match NaiveDate::parse_from_str(v, "%Y%m%d") {
411                    Ok(d) => d,
412                    Err(e) => {
413                        // We should never be here
414                        return Err(LKCError::ErrProgramException(e.to_string()));
415                    }
416                }
417            }
418            None => return Err(LKCError::ErrNotFound),
419        };
420
421        let now = Utc::now().date().naive_utc();
422
423        Ok(now > expdate)
424    }
425
426    /// Returns the body text of the entry
427    pub fn get_text(&self) -> Result<String, LKCError> {
428        let mut lines = Vec::<String>::new();
429
430        // Ensure that all required fields are present. Because each field is validated at time
431        // of assignment, we know that each one is valid if present.
432        match self._type {
433            EntryType::Organization => {
434                for i in ORG_PERMITTED_FIELDS.iter() {
435                    match self.fields.get(*i) {
436                        Some(v) => {
437                            let parts = [*i, v];
438                            lines.push(parts.join(":"));
439                        }
440                        None => { /* */ }
441                    }
442                }
443            }
444            EntryType::User => {
445                for i in USER_PERMITTED_FIELDS.iter() {
446                    match self.fields.get(*i) {
447                        Some(v) => {
448                            let parts = [*i, v];
449                            lines.push(parts.join(":"));
450                        }
451                        None => { /* */ }
452                    }
453                }
454            }
455            _ => return Err(LKCError::ErrNoInit),
456        };
457
458        // Keycards are expected to end with a blank line
459        lines.push(String::from(""));
460
461        Ok(lines.join("\r\n"))
462    }
463
464    /// Returns the full text of the entry, including signatures, up to but not including the one
465    /// specified. Passing an empty string as the signature level will result in the entire entry
466    /// being returned.
467    ///
468    /// The order for organization entries:
469    ///
470    /// - Custody-Signature
471    /// - Previous-Hash
472    /// - Hash
473    /// - Organization-Signature
474    ///
475    /// The order for user entries:
476    ///
477    /// - Custody-Signature
478    /// - Organization-Signature
479    /// - Previous-Hash
480    /// - Hash
481    /// - User-Signature
482    pub fn get_full_text(&self, siglevel: &str) -> Result<String, LKCError> {
483        match self._type {
484            EntryType::Organization => self.org_get_full_text(siglevel),
485            EntryType::User => self.user_get_full_text(siglevel),
486            _ => return Err(LKCError::ErrNoInit),
487        }
488    }
489
490    /// Returns true if the supplied AuthStr is populated and valid
491    pub fn has_authstr(&self, astype: &str) -> bool {
492        match self.sigs.get(astype) {
493            Some(_) => true,
494            None => false,
495        }
496    }
497
498    /// Returns the specified authentication string
499    pub fn get_authstr(&self, astype: &str) -> Result<&CryptoString, LKCError> {
500        match self.sigs.get(astype) {
501            Some(v) => Ok(v),
502            None => Err(LKCError::ErrMissingField(String::from(astype))),
503        }
504    }
505
506    /// Sets the specified authentication string to the value passed. NOTE: no validation of the
507    /// authentication string is performed by this call. The primary use for this method is to set
508    /// the Previous-Hash for the entry
509    pub fn add_authstr(&mut self, astype: &str, astr: &CryptoString) -> Result<(), LKCError> {
510        verify_authstr(astype, astr)?;
511
512        let _ = self.sigs.insert(String::from(astype), astr.clone());
513        Ok(())
514    }
515
516    /// Creates the requested signature. Requirements for this call vary with the entry
517    /// implementation. ErrOutOfOrderSignature is returned if a signature is requested before
518    /// another required authentication string has been set. ErrBadValue is returned for a
519    /// signature type not used by the specific implementation.
520    pub fn sign(&mut self, astype: &str, signing_pair: &SigningPair) -> Result<(), LKCError> {
521        let totaldata = self.get_full_text(astype)?;
522        let signature = signing_pair.sign(totaldata.as_bytes())?;
523        self.add_authstr(&astype, &signature)
524    }
525
526    /// Verifies the requested signature. ErrBadValue is returned for a signature type not used by
527    /// the specific implementation. ErrVerificationFailure is returned if the signature fails to
528    /// verify
529    pub fn verify_signature<K: VerifySignature>(
530        &self,
531        astype: &str,
532        verify_key: &K,
533    ) -> Result<(), LKCError> {
534        let sig = self.get_authstr(astype)?;
535        let totaldata = match self._type {
536            EntryType::Organization => self.org_get_full_text(astype)?,
537            EntryType::User => self.user_get_full_text(astype)?,
538            EntryType::None => return Err(LKCError::ErrNoInit),
539        };
540
541        if verify_key.verify(totaldata.as_bytes(), &sig)? {
542            Ok(())
543        } else {
544            Err(LKCError::ErrVerificationFailure)
545        }
546    }
547
548    /// Calculates the hash for the entry text using the specified algorithm. For information on
549    /// signature order, please see [`get_full_text()`](struct.Keycard.html#method.get_full_text).
550    /// All signatures are required except for Custody-Signature and Previous-Hash, which are not
551    /// required for an organization's root keycard entry. ErrOutOfOrderSignature is returned if a
552    /// hash is requested before another required authentication string has been set.
553    pub fn hash(&mut self, algorithm: &str) -> Result<(), LKCError> {
554        let totaldata = self.get_full_text("Hash")?;
555        let hash = get_hash(algorithm, totaldata.as_bytes())?;
556        self.add_authstr("Hash", &hash)
557    }
558
559    /// Verifies the data of the entry with the hash currently assigned. Returns Ok on success and
560    /// ErrHashMismatch on failure.
561    pub fn verify_hash(&self) -> Result<(), LKCError> {
562        let currenthash = self.get_authstr("Hash")?;
563
564        let totaldata = self.get_full_text("Hash")?;
565        let hash = get_hash(currenthash.prefix(), totaldata.as_bytes())?;
566
567        if *currenthash == hash {
568            Ok(())
569        } else {
570            Err(LKCError::ErrHashMismatch)
571        }
572    }
573
574    /// Creates a new Entry object with new keys and a custody signature. It requires the contact
575    /// request signing keypair used for the entry so that the Custody-Signature field is
576    /// generated correctly. For handling of expiration date, see
577    /// [`set_expiration()`](struct.Keycard.html#method.set_expiration).
578    pub fn chain(
579        &self,
580        spair: &SigningPair,
581        expires: u16,
582    ) -> Result<(Entry, HashMap<&'static str, CryptoString>), LKCError> {
583        match self._type {
584            EntryType::Organization => self.org_chain(spair, expires),
585            EntryType::User => self.user_chain(spair, expires),
586            EntryType::None => Err(LKCError::ErrNoInit),
587        }
588    }
589
590    /// Verifies the chain of custody between the provided entry and the current one. If either
591    /// card is invalid, ErrInvalidKeycard is returned. If the index of entry is not the
592    /// immediate successor to the previous one, ErrBadValue is returned.
593    pub fn verify_chain(&self, previous: &Entry) -> Result<(), LKCError> {
594        previous.is_compliant()?;
595        self.is_compliant()?;
596
597        match self._type {
598            EntryType::Organization => self.org_verify_chain(previous),
599            EntryType::User => self.user_verify_chain(previous),
600            EntryType::None => Err(LKCError::ErrNoInit),
601        }
602    }
603
604    /// This method is called when the current entry must be revoked because one or more keys were
605    /// compromised. A new root entry is created with a `Revoke` field containing the hash of the
606    /// current one and an `Index` which is, like `chain()`, one greater than the current entry. For
607    /// handling of the expiration interval, see
608    /// [`set_expiration()`](struct.Keycard.html#method.set_expiration).
609    pub fn revoke(
610        &self,
611        expires: u16,
612    ) -> Result<(Entry, HashMap<&'static str, CryptoString>), LKCError> {
613        self.is_compliant()?;
614
615        match self._type {
616            EntryType::Organization => self.org_revoke(expires),
617            EntryType::User => self.user_revoke(expires),
618            EntryType::None => Err(LKCError::ErrNoInit),
619        }
620    }
621
622    /// Makes a careful duplicate of the entry. Note that this is NOT the same as the standard
623    /// library trait of the same name. This method handles the data duplication needs for chaining
624    /// together two entries, excluding certain fields and handling expiration carefully.
625    fn copy(&self) -> Self {
626        let entrytype = self
627            .get_field("Type")
628            .expect("Failed to get type in Entry::copy()");
629
630        let mut out = Entry::new_from_str(&entrytype).expect("Failed to create new ");
631
632        for (k, v) in self.fields.iter() {
633            match k.as_str() {
634                "Index"
635                | "PrimaryVerificationKey"
636                | "SecondaryVerificationKey"
637                | "EncryptionKey"
638                | "Expires"
639                | "Timestamp" => { /* Field is set correctly in new(). Do nothing. */ }
640                _ => {
641                    out.set_field(k, v)
642                        .expect("Failed to copy field in OrgEntry::copy()");
643                }
644            }
645        }
646
647        // The copy has an Index value of one greater than the original
648
649        let index = self
650            .get_field("Index")
651            .expect("Missing Index field in OrgEntry::copy()");
652        let new_index = increment_index_string(&index)
653            .expect("Failed to increment Index field in OrgEntry::copy()");
654        out.set_field("Index", &new_index)
655            .expect("Failed to set Index field to new value in OrgEntry::copy()");
656
657        out
658    }
659
660    // Private methods for type-specific functionality
661
662    fn org_get_full_text(&self, siglevel: &str) -> Result<String, LKCError> {
663        let mut strings = Vec::<String>::new();
664        strings.push(self.get_text()?);
665
666        let require_previous = self.get_field("Index")? != "1" && !self.has_field("Revoke");
667
668        match siglevel {
669            "User-Signature" => {
670                // User-Signature doesn't exist in an org entry
671                return Err(LKCError::ErrBadValue);
672            }
673            "Custody-Signature" => {
674                /* For the custody signature, we don't need to do anything extra */
675            }
676            "Previous-Hash" => {
677                match &self.sigs.get("Custody-Signature") {
678                    Some(v) => {
679                        strings.push(format!("Custody-Signature:{}\r\n", v.to_string()));
680                    }
681                    None => {
682                        if require_previous {
683                            return Err(LKCError::ErrMissingField(String::from(
684                                "Custody-Signature",
685                            )));
686                        }
687                    }
688                };
689            }
690            "Hash" => {
691                match &self.sigs.get("Custody-Signature") {
692                    Some(v) => {
693                        strings.push(format!("Custody-Signature:{}\r\n", v.to_string()));
694                    }
695                    None => {
696                        if require_previous {
697                            return Err(LKCError::ErrMissingField(String::from(
698                                "Custody-Signature",
699                            )));
700                        }
701                    }
702                };
703                match &self.sigs.get("Previous-Hash") {
704                    Some(v) => {
705                        strings.push(format!("Previous-Hash:{}\r\n", v.to_string()));
706                    }
707                    None => {
708                        if require_previous {
709                            return Err(LKCError::ErrMissingField(String::from("Previous-Hash")));
710                        }
711                    }
712                };
713            }
714            "Organization-Signature" => {
715                match &self.sigs.get("Custody-Signature") {
716                    Some(v) => {
717                        strings.push(format!("Custody-Signature:{}\r\n", v.to_string()));
718                    }
719                    None => {
720                        if require_previous {
721                            return Err(LKCError::ErrMissingField(String::from(
722                                "Custody-Signature",
723                            )));
724                        }
725                    }
726                };
727                match &self.sigs.get("Previous-Hash") {
728                    Some(v) => {
729                        strings.push(format!("Previous-Hash:{}\r\n", v.to_string()));
730                    }
731                    None => {
732                        if require_previous {
733                            return Err(LKCError::ErrMissingField(String::from("Previous-Hash")));
734                        }
735                    }
736                };
737                match &self.sigs.get("Hash") {
738                    Some(v) => {
739                        strings.push(format!("Hash:{}\r\n", v.to_string()));
740                    }
741                    None => {
742                        if require_previous {
743                            return Err(LKCError::ErrMissingField(String::from("Hash")));
744                        }
745                    }
746                };
747            }
748            "" => {
749                match &self.sigs.get("Custody-Signature") {
750                    Some(v) => {
751                        strings.push(format!("Custody-Signature:{}\r\n", v.to_string()));
752                    }
753                    None => {
754                        if require_previous {
755                            return Err(LKCError::ErrMissingField(String::from(
756                                "Custody-Signature",
757                            )));
758                        }
759                    }
760                };
761                match &self.sigs.get("Previous-Hash") {
762                    Some(v) => {
763                        strings.push(format!("Previous-Hash:{}\r\n", v.to_string()));
764                    }
765                    None => {
766                        if require_previous {
767                            return Err(LKCError::ErrMissingField(String::from("Previous-Hash")));
768                        }
769                    }
770                };
771                match &self.sigs.get("Hash") {
772                    Some(v) => {
773                        strings.push(format!("Hash:{}\r\n", v.to_string()));
774                    }
775                    None => {
776                        if require_previous {
777                            return Err(LKCError::ErrMissingField(String::from("Hash")));
778                        }
779                    }
780                };
781                match &self.sigs.get("Organization-Signature") {
782                    Some(v) => {
783                        strings.push(format!("Organization-Signature:{}\r\n", v.to_string()));
784                    }
785                    None => {
786                        return Err(LKCError::ErrMissingField(String::from(
787                            "Organization-Signature",
788                        )))
789                    }
790                };
791            }
792            _ => return Err(LKCError::ErrBadValue),
793        };
794
795        Ok(strings.join(""))
796    }
797
798    fn user_get_full_text(&self, siglevel: &str) -> Result<String, LKCError> {
799        let mut strings = Vec::<String>::new();
800        strings.push(self.get_text()?);
801
802        let require_previous = self.get_field("Index")? != "1" && !self.has_field("Revoke");
803
804        match siglevel {
805            "Custody-Signature" => {
806                /* For the custody signature, we don't need to do anything extra */
807            }
808            "Organization-Signature" => {
809                match &self.sigs.get("Custody-Signature") {
810                    Some(v) => {
811                        strings.push(format!("Custody-Signature:{}\r\n", v.to_string()));
812                    }
813                    None => {
814                        if require_previous {
815                            return Err(LKCError::ErrMissingField(String::from(
816                                "Custody-Signature",
817                            )));
818                        }
819                    }
820                };
821            }
822            "Previous-Hash" => {
823                match &self.sigs.get("Custody-Signature") {
824                    Some(v) => {
825                        strings.push(format!("Custody-Signature:{}\r\n", v.to_string()));
826                    }
827                    None => {
828                        if require_previous {
829                            return Err(LKCError::ErrMissingField(String::from(
830                                "Custody-Signature",
831                            )));
832                        }
833                    }
834                };
835                match &self.sigs.get("Organization-Signature") {
836                    Some(v) => {
837                        strings.push(format!("Organization-Signature:{}\r\n", v.to_string()));
838                    }
839                    None => {
840                        return Err(LKCError::ErrMissingField(String::from(
841                            "Organization-Signature",
842                        )))
843                    }
844                };
845            }
846            "Hash" => {
847                match &self.sigs.get("Custody-Signature") {
848                    Some(v) => {
849                        strings.push(format!("Custody-Signature:{}\r\n", v.to_string()));
850                    }
851                    None => {
852                        if require_previous {
853                            return Err(LKCError::ErrMissingField(String::from(
854                                "Custody-Signature",
855                            )));
856                        }
857                    }
858                };
859                match &self.sigs.get("Organization-Signature") {
860                    Some(v) => {
861                        strings.push(format!("Organization-Signature:{}\r\n", v.to_string()));
862                    }
863                    None => {
864                        return Err(LKCError::ErrMissingField(String::from(
865                            "Organization-Signature",
866                        )))
867                    }
868                };
869                match &self.sigs.get("Previous-Hash") {
870                    Some(v) => {
871                        strings.push(format!("Previous-Hash:{}\r\n", v.to_string()));
872                    }
873                    None => {
874                        if require_previous {
875                            return Err(LKCError::ErrMissingField(String::from("Previous-Hash")));
876                        }
877                    }
878                };
879            }
880            "User-Signature" => {
881                match &self.sigs.get("Custody-Signature") {
882                    Some(v) => {
883                        strings.push(format!("Custody-Signature:{}\r\n", v.to_string()));
884                    }
885                    None => {
886                        if require_previous {
887                            return Err(LKCError::ErrMissingField(String::from(
888                                "Custody-Signature",
889                            )));
890                        }
891                    }
892                };
893                match &self.sigs.get("Organization-Signature") {
894                    Some(v) => {
895                        strings.push(format!("Organization-Signature:{}\r\n", v.to_string()));
896                    }
897                    None => {
898                        return Err(LKCError::ErrMissingField(String::from(
899                            "Organization-Signature",
900                        )))
901                    }
902                };
903                match &self.sigs.get("Previous-Hash") {
904                    Some(v) => {
905                        strings.push(format!("Previous-Hash:{}\r\n", v.to_string()));
906                    }
907                    None => {
908                        if require_previous {
909                            return Err(LKCError::ErrMissingField(String::from("Previous-Hash")));
910                        }
911                    }
912                };
913                match &self.sigs.get("Hash") {
914                    Some(v) => {
915                        strings.push(format!("Hash:{}\r\n", v.to_string()));
916                    }
917                    None => return Err(LKCError::ErrMissingField(String::from("Hash"))),
918                };
919            }
920            "" => {
921                match &self.sigs.get("Custody-Signature") {
922                    Some(v) => {
923                        strings.push(format!("Custody-Signature:{}\r\n", v.to_string()));
924                    }
925                    None => {
926                        if require_previous {
927                            return Err(LKCError::ErrMissingField(String::from(
928                                "Custody-Signature",
929                            )));
930                        }
931                    }
932                };
933                match &self.sigs.get("Organization-Signature") {
934                    Some(v) => {
935                        strings.push(format!("Organization-Signature:{}\r\n", v.to_string()));
936                    }
937                    None => {
938                        return Err(LKCError::ErrMissingField(String::from(
939                            "Organization-Signature",
940                        )))
941                    }
942                };
943                match &self.sigs.get("Previous-Hash") {
944                    Some(v) => {
945                        strings.push(format!("Previous-Hash:{}\r\n", v.to_string()));
946                    }
947                    None => {
948                        if require_previous {
949                            return Err(LKCError::ErrMissingField(String::from("Previous-Hash")));
950                        }
951                    }
952                };
953                match &self.sigs.get("Hash") {
954                    Some(v) => {
955                        strings.push(format!("Hash:{}\r\n", v.to_string()));
956                    }
957                    None => return Err(LKCError::ErrMissingField(String::from("Hash"))),
958                };
959                match &self.sigs.get("User-Signature") {
960                    Some(v) => {
961                        strings.push(format!("User-Signature:{}\r\n", v.to_string()));
962                    }
963                    None => return Err(LKCError::ErrMissingField(String::from("User-Signature"))),
964                };
965            }
966            _ => return Err(LKCError::ErrBadValue),
967        };
968
969        Ok(strings.join(""))
970    }
971
972    fn org_chain(
973        &self,
974        spair: &SigningPair,
975        expires: u16,
976    ) -> Result<(Entry, HashMap<&'static str, CryptoString>), LKCError> {
977        let mut map = HashMap::<&str, CryptoString>::new();
978        let mut entry = self.copy();
979
980        // TODO: add preferred algorithm support once supported in eznacl
981        let newspair = SigningPair::generate("ED25519")?;
982        map.insert(
983            "primary.public",
984            CryptoString::from(&newspair.get_public_str())
985                .expect("Error getting inserting primary verification key in OrgEntry::chain()"),
986        );
987        map.insert(
988            "primary.private",
989            CryptoString::from(&newspair.get_private_str())
990                .expect("Error getting inserting primary signing key in OrgEntry::chain()"),
991        );
992        match entry.set_field("Primary-Verification-Key", &newspair.get_public_str()) {
993            Ok(_) => (),
994            Err(e) => {
995                return Err(LKCError::ErrProgramException(format!(
996                    "Error setting primary verification key in OrgEntry::chain(): {}",
997                    e.to_string()
998                )))
999            }
1000        }
1001
1002        // TODO: add preferred algorithm support once supported in eznacl
1003        let epair = EncryptionPair::generate("CURVE25519")?;
1004        map.insert(
1005            "encryption.public",
1006            CryptoString::from(&epair.get_public_str())
1007                .expect("Error getting inserting encryption key in OrgEntry::chain()"),
1008        );
1009        map.insert(
1010            "encryption.private",
1011            CryptoString::from(&epair.get_private_str())
1012                .expect("Error getting inserting decryption key in OrgEntry::chain()"),
1013        );
1014        match entry.set_field("Encryption-Key", &epair.get_public_str()) {
1015            Ok(_) => (),
1016            Err(e) => {
1017                return Err(LKCError::ErrProgramException(format!(
1018                    "Error setting encryption key in OrgEntry::chain(): {}",
1019                    e.to_string()
1020                )))
1021            }
1022        }
1023
1024        // Now that we have a new primary signing pair, move the old one over to secondary
1025        match entry.set_field(
1026            "Secondary-Verification-Key",
1027            &self.get_field("Primary-Verification-Key").expect(""),
1028        ) {
1029            Ok(_) => (),
1030            Err(e) => {
1031                return Err(LKCError::ErrProgramException(format!(
1032                    "Error moving primary key to secondary in OrgEntry::chain(): {}",
1033                    e.to_string()
1034                )))
1035            }
1036        }
1037
1038        let hash = self.get_authstr("Hash")?;
1039        entry.add_authstr("Previous-Hash", hash)?;
1040        entry.set_expiration(expires)?;
1041        entry.sign("Custody-Signature", &spair)?;
1042        entry.hash(hash.prefix())?;
1043        entry.sign("Organization-Signature", &newspair)?;
1044
1045        Ok((entry, map))
1046    }
1047
1048    fn user_chain(
1049        &self,
1050        spair: &SigningPair,
1051        expires: u16,
1052    ) -> Result<(Entry, HashMap<&'static str, CryptoString>), LKCError> {
1053        let mut map = HashMap::<&str, CryptoString>::new();
1054        let mut entry = self.copy();
1055
1056        // TODO: add preferred algorithm support once supported in eznacl
1057        let newcrspair = SigningPair::generate("ED25519")?;
1058        map.insert(
1059            "crsigning.public",
1060            CryptoString::from(&newcrspair.get_public_str())
1061                .expect("Error getting inserting CR verification key in UserEntry::chain()"),
1062        );
1063        map.insert(
1064            "crsigning.private",
1065            CryptoString::from(&newcrspair.get_private_str())
1066                .expect("Error getting inserting CR signing key in UserEntry::chain()"),
1067        );
1068        match entry.set_field(
1069            "Contact-Request-Verification-Key",
1070            &newcrspair.get_public_str(),
1071        ) {
1072            Ok(_) => (),
1073            Err(e) => {
1074                return Err(LKCError::ErrProgramException(format!(
1075                    "Error setting CR verification key in UserEntry::chain(): {}",
1076                    e.to_string()
1077                )))
1078            }
1079        }
1080
1081        // TODO: add preferred algorithm support once supported in eznacl
1082        let newcrepair = EncryptionPair::generate("CURVE25519")?;
1083        map.insert(
1084            "crencryption.public",
1085            CryptoString::from(&newcrepair.get_public_str())
1086                .expect("Error getting inserting CR encryption key in UserEntry::chain()"),
1087        );
1088        map.insert(
1089            "crencryption.private",
1090            CryptoString::from(&newcrepair.get_private_str())
1091                .expect("Error getting inserting CR decryption key in UserEntry::chain()"),
1092        );
1093        match entry.set_field(
1094            "Contact-Request-Encryption-Key",
1095            &newcrepair.get_public_str(),
1096        ) {
1097            Ok(_) => (),
1098            Err(e) => {
1099                return Err(LKCError::ErrProgramException(format!(
1100                    "Error setting CR encryption key in UserEntry::chain(): {}",
1101                    e.to_string()
1102                )))
1103            }
1104        }
1105
1106        // TODO: add preferred algorithm support once supported in eznacl
1107        let newspair = SigningPair::generate("ED25519")?;
1108        map.insert(
1109            "signing.public",
1110            CryptoString::from(&newspair.get_public_str())
1111                .expect("Error getting inserting verification key in UserEntry::chain()"),
1112        );
1113        map.insert(
1114            "signing.private",
1115            CryptoString::from(&newspair.get_private_str())
1116                .expect("Error getting inserting signing key in UserEntry::chain()"),
1117        );
1118        match entry.set_field("Verification-Key", &newspair.get_public_str()) {
1119            Ok(_) => (),
1120            Err(e) => {
1121                return Err(LKCError::ErrProgramException(format!(
1122                    "Error setting verification key in UserEntry::chain(): {}",
1123                    e.to_string()
1124                )))
1125            }
1126        }
1127
1128        // TODO: add preferred algorithm support once supported in eznacl
1129        let newepair = EncryptionPair::generate("CURVE25519")?;
1130        map.insert(
1131            "encryption.public",
1132            CryptoString::from(&newepair.get_public_str())
1133                .expect("Error getting inserting encryption key in UserEntry::chain()"),
1134        );
1135        map.insert(
1136            "encryption.private",
1137            CryptoString::from(&newepair.get_private_str())
1138                .expect("Error getting inserting decryption key in UserEntry::chain()"),
1139        );
1140        match entry.set_field("Encryption-Key", &newepair.get_public_str()) {
1141            Ok(_) => (),
1142            Err(e) => {
1143                return Err(LKCError::ErrProgramException(format!(
1144                    "Error setting encryption key in UserEntry::chain(): {}",
1145                    e.to_string()
1146                )))
1147            }
1148        }
1149
1150        let hash = self.get_authstr("Hash")?;
1151        entry.add_authstr("Previous-Hash", hash)?;
1152        entry.set_expiration(expires)?;
1153        entry.sign("Custody-Signature", &spair)?;
1154
1155        Ok((entry, map))
1156    }
1157
1158    fn org_verify_chain(&self, previous: &Entry) -> Result<(), LKCError> {
1159        if previous._type != EntryType::Organization {
1160            return Err(LKCError::ErrTypeMismatch);
1161        }
1162
1163        // Make sure that the previous entry is the immediate predecessor of the current one
1164        let previndex = match previous.get_field("Index") {
1165            Ok(v) => v,
1166            Err(_) => return Err(LKCError::ErrInvalidKeycard),
1167        };
1168        let currentindex = match self.get_field("Index") {
1169            Ok(v) => v,
1170            Err(_) => return Err(LKCError::ErrInvalidKeycard),
1171        };
1172        match increment_index_string(&previndex) {
1173            Ok(v) => {
1174                if v != currentindex {
1175                    return Err(LKCError::ErrBadValue);
1176                }
1177            }
1178            Err(_) => return Err(LKCError::ErrBadFieldValue(String::from("Index"))),
1179        }
1180
1181        let verkeystr = previous.get_field("Primary-Verification-Key")?;
1182        let verkey = match VerificationKey::from_string(&verkeystr) {
1183            Some(v) => v,
1184            None => return Err(LKCError::ErrInvalidKey),
1185        };
1186
1187        self.verify_signature("Custody-Signature", &verkey)
1188    }
1189
1190    fn user_verify_chain(&self, previous: &Entry) -> Result<(), LKCError> {
1191        // The previous entry for a root user entry must be an organization entry -- the org
1192        // keycard's branch point
1193        match previous._type {
1194            EntryType::User => {
1195                if self.get_field("Index")? == "1" {
1196                    return Err(LKCError::ErrTypeMismatch);
1197                }
1198            }
1199            EntryType::Organization => {
1200                if self.get_field("Index")? != "1" && !self.has_field("Revoke") {
1201                    return Err(LKCError::ErrTypeMismatch);
1202                }
1203            }
1204            _ => return Err(LKCError::ErrTypeMismatch),
1205        }
1206
1207        // Make sure that the two are hash linked and that the Index field is sequential unless
1208        // we're dealing with a branch point
1209        if previous.get_authstr("Hash")? != self.get_authstr("Previous-Hash")? {
1210            return Err(LKCError::ErrHashMismatch);
1211        }
1212
1213        if previous.get_field("Type")? == self.get_field("Type")? {
1214            let previndex = match previous.get_field("Index") {
1215                Ok(v) => v,
1216                Err(_) => return Err(LKCError::ErrInvalidKeycard),
1217            };
1218            let currentindex = match self.get_field("Index") {
1219                Ok(v) => v,
1220                Err(_) => return Err(LKCError::ErrInvalidKeycard),
1221            };
1222            match increment_index_string(&previndex) {
1223                Ok(v) => {
1224                    if v != currentindex {
1225                        return Err(LKCError::ErrBadValue);
1226                    }
1227                }
1228                Err(_) => return Err(LKCError::ErrBadFieldValue(String::from("Index"))),
1229            }
1230        }
1231
1232        match previous._type {
1233            EntryType::User => {
1234                let verkeystr = previous.get_field("Contact-Request-Verification-Key")?;
1235                let verkey = match VerificationKey::from_string(&verkeystr) {
1236                    Some(v) => v,
1237                    None => return Err(LKCError::ErrInvalidKey),
1238                };
1239                self.verify_signature("Custody-Signature", &verkey)
1240            }
1241            EntryType::Organization => {
1242                let verkeystr = previous.get_field("Primary-Verification-Key")?;
1243                let verkey = match VerificationKey::from_string(&verkeystr) {
1244                    Some(v) => v,
1245                    None => return Err(LKCError::ErrInvalidKey),
1246                };
1247                self.verify_signature("Organization-Signature", &verkey)
1248            }
1249            _ => unreachable!(),
1250        }
1251    }
1252
1253    fn org_revoke(
1254        &self,
1255        expires: u16,
1256    ) -> Result<(Entry, HashMap<&'static str, CryptoString>), LKCError> {
1257        let mut map = HashMap::<&str, CryptoString>::new();
1258        let mut entry = self.copy();
1259
1260        // TODO: add preferred algorithm support once supported in eznacl
1261        let newspair = SigningPair::generate("ED25519")?;
1262        map.insert(
1263            "primary.public",
1264            CryptoString::from(&newspair.get_public_str())
1265                .expect("Error getting inserting primary verification key in org_revoke()"),
1266        );
1267        map.insert(
1268            "primary.private",
1269            CryptoString::from(&newspair.get_private_str())
1270                .expect("Error getting inserting primary signing key in org_revoke()"),
1271        );
1272        match entry.set_field("Primary-Verification-Key", &newspair.get_public_str()) {
1273            Ok(_) => (),
1274            Err(e) => {
1275                return Err(LKCError::ErrProgramException(format!(
1276                    "Error setting primary verification key in org_revoke(): {}",
1277                    e.to_string()
1278                )))
1279            }
1280        }
1281
1282        // TODO: add preferred algorithm support once supported in eznacl
1283        let epair = EncryptionPair::generate("CURVE25519")?;
1284        map.insert(
1285            "encryption.public",
1286            CryptoString::from(&epair.get_public_str())
1287                .expect("Error getting inserting encryption key in org_revoke()"),
1288        );
1289        map.insert(
1290            "encryption.private",
1291            CryptoString::from(&epair.get_private_str())
1292                .expect("Error getting inserting decryption key in org_revoke()"),
1293        );
1294        match entry.set_field("Encryption-Key", &epair.get_public_str()) {
1295            Ok(_) => (),
1296            Err(e) => {
1297                return Err(LKCError::ErrProgramException(format!(
1298                    "Error setting encryption key in org_revoke(): {}",
1299                    e.to_string()
1300                )))
1301            }
1302        }
1303
1304        // This new entry has no custody signature--it's a new root entry.
1305
1306        let hash = self.get_authstr("Hash")?;
1307        entry.set_field("Revoke", hash.as_str())?;
1308        entry.set_expiration(expires)?;
1309        entry.hash(hash.prefix())?;
1310        entry.sign("Organization-Signature", &newspair)?;
1311
1312        Ok((entry, map))
1313    }
1314
1315    fn user_revoke(
1316        &self,
1317        expires: u16,
1318    ) -> Result<(Entry, HashMap<&'static str, CryptoString>), LKCError> {
1319        let mut map = HashMap::<&str, CryptoString>::new();
1320        let mut entry = self.copy();
1321
1322        // TODO: add preferred algorithm support once supported in eznacl
1323        let newcrspair = SigningPair::generate("ED25519")?;
1324        map.insert(
1325            "crsigning.public",
1326            CryptoString::from(&newcrspair.get_public_str())
1327                .expect("Error getting inserting CR verification key in user_revoke()"),
1328        );
1329        map.insert(
1330            "crsigning.private",
1331            CryptoString::from(&newcrspair.get_private_str())
1332                .expect("Error getting inserting CR signing key in user_revoke()"),
1333        );
1334        match entry.set_field(
1335            "Contact-Request-Verification-Key",
1336            &newcrspair.get_public_str(),
1337        ) {
1338            Ok(_) => (),
1339            Err(e) => {
1340                return Err(LKCError::ErrProgramException(format!(
1341                    "Error setting CR verification key in user_revoke(): {}",
1342                    e.to_string()
1343                )))
1344            }
1345        }
1346
1347        // TODO: add preferred algorithm support once supported in eznacl
1348        let newcrepair = EncryptionPair::generate("CURVE25519")?;
1349        map.insert(
1350            "crencryption.public",
1351            CryptoString::from(&newcrepair.get_public_str())
1352                .expect("Error getting inserting CR encryption key in user_revoke()"),
1353        );
1354        map.insert(
1355            "crencryption.private",
1356            CryptoString::from(&newcrepair.get_private_str())
1357                .expect("Error getting inserting CR decryption key in user_revoke()"),
1358        );
1359        match entry.set_field(
1360            "Contact-Request-Encryption-Key",
1361            &newcrepair.get_public_str(),
1362        ) {
1363            Ok(_) => (),
1364            Err(e) => {
1365                return Err(LKCError::ErrProgramException(format!(
1366                    "Error setting CR encryption key in user_revoke(): {}",
1367                    e.to_string()
1368                )))
1369            }
1370        }
1371
1372        // TODO: add preferred algorithm support once supported in eznacl
1373        let newspair = SigningPair::generate("ED25519")?;
1374        map.insert(
1375            "signing.public",
1376            CryptoString::from(&newspair.get_public_str())
1377                .expect("Error getting inserting verification key in user_revoke()"),
1378        );
1379        map.insert(
1380            "signing.private",
1381            CryptoString::from(&newspair.get_private_str())
1382                .expect("Error getting inserting signing key in user_revoke()"),
1383        );
1384        match entry.set_field("Verification-Key", &newspair.get_public_str()) {
1385            Ok(_) => (),
1386            Err(e) => {
1387                return Err(LKCError::ErrProgramException(format!(
1388                    "Error setting verification key in user_revoke(): {}",
1389                    e.to_string()
1390                )))
1391            }
1392        }
1393
1394        // TODO: add preferred algorithm support once supported in eznacl
1395        let newepair = EncryptionPair::generate("CURVE25519")?;
1396        map.insert(
1397            "encryption.public",
1398            CryptoString::from(&newepair.get_public_str())
1399                .expect("Error getting inserting encryption key in user_revoke()"),
1400        );
1401        map.insert(
1402            "encryption.private",
1403            CryptoString::from(&newepair.get_private_str())
1404                .expect("Error getting inserting decryption key in user_revoke()"),
1405        );
1406        match entry.set_field("Encryption-Key", &newepair.get_public_str()) {
1407            Ok(_) => (),
1408            Err(e) => {
1409                return Err(LKCError::ErrProgramException(format!(
1410                    "Error setting encryption key in user_revoke(): {}",
1411                    e.to_string()
1412                )))
1413            }
1414        }
1415
1416        entry.set_field("Revoke", self.get_authstr("Hash")?.as_str())?;
1417        entry.set_expiration(expires)?;
1418
1419        Ok((entry, map))
1420    }
1421}
1422
1423// Takes a string containing an index and increments the value inside it, e.g. "21" -> "22"
1424fn increment_index_string(s: &str) -> Result<String, LKCError> {
1425    let mut val: u32 = match s.parse::<u32>() {
1426        Ok(v) => v,
1427        Err(_) => return Err(LKCError::ErrBadValue),
1428    };
1429
1430    val += 1;
1431    Ok(val.to_string())
1432}
1433
1434fn get_offset_date(d: Duration) -> Option<String> {
1435    let offset_date = Utc::now()
1436        .date()
1437        .naive_utc()
1438        .checked_add_signed(d)
1439        .expect("Unable to create date 365 days from now");
1440
1441    Some(offset_date.format("%Y%m%d").to_string())
1442}