apple_codesign/
plist_der.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5/*! Plist DER encoding. */
6
7use {
8    crate::error::AppleCodesignError,
9    num_traits::cast::ToPrimitive,
10    plist::Value,
11    rasn::{
12        ber::de::DecodeError,
13        ber::enc::EncodeError,
14        de::Error as DeError,
15        enc::Error as EncError,
16        types::{fields::{Field, Fields}, Class, Constraints, Constructed, Integer, Tag},
17        AsnType, Codec, Decode, Decoder, Encode, Encoder,
18    },
19    std::collections::BTreeMap,
20};
21
22#[derive(AsnType, Debug, Decode)]
23struct DictionaryEntry {
24    #[rasn(tag(universal, 12))]
25    key: String,
26    value: WrappedValue,
27}
28
29/// Represents a plist dictionary in the rasn domain.
30#[derive(Debug)]
31struct Dictionary(plist::Dictionary);
32
33impl AsnType for Dictionary {
34    const TAG: Tag = Tag {
35        class: Class::Context,
36        value: 16,
37    };
38}
39
40impl Constructed for Dictionary {
41    // This is incorrect, but a required field is needed to be specified to
42    // communicate to rasn it needs to know that there is fields to decode.
43    const FIELDS: Fields = Fields::from_static(&[Field::new_optional_type::<()>("key")]);
44}
45
46impl Encode for Dictionary {
47    fn encode_with_tag_and_constraints<E: Encoder>(
48        &self,
49        encoder: &mut E,
50        tag: Tag,
51        _constraints: Constraints,
52    ) -> Result<(), E::Error> {
53        // Sort it alphabetically.
54        let map = self.0.iter().collect::<BTreeMap<_, _>>();
55
56        encoder.encode_sequence::<Self, _>(tag, |encoder| {
57            for (k, v) in map {
58                let wrapped = WrappedValue::try_from(v.clone())?;
59
60                encoder.encode_sequence::<Self, _>(Tag::SEQUENCE, |encoder| {
61                    encoder.encode_utf8_string(Tag::UTF8_STRING, Constraints::NONE, k)?;
62                    wrapped.encode(encoder)?;
63                    Ok(())
64                })?;
65            }
66
67            Ok(())
68        })?;
69
70        Ok(())
71    }
72}
73
74impl Decode for Dictionary {
75    fn decode_with_tag_and_constraints<D: Decoder>(
76        decoder: &mut D,
77        tag: Tag,
78        _constraints: Constraints,
79    ) -> Result<Self, D::Error> {
80        decoder.decode_sequence::<Self, _, _>(tag, Some(|| Self(plist::Dictionary::new())), |decoder| {
81            let mut dict = plist::Dictionary::new();
82
83            loop {
84                let entry = decoder.decode_optional::<DictionaryEntry>()?;
85
86                if let Some(entry) = entry {
87                    let value = plist::Value::try_from(entry.value)?;
88
89                    dict.insert(entry.key, value);
90                } else {
91                    break;
92                }
93            }
94
95            Ok(Self(dict))
96        })
97    }
98}
99
100/// Represents a [Value] in the rasn domain.
101#[derive(AsnType, Debug, Decode, Encode)]
102#[rasn(choice)]
103enum WrappedValue {
104    Array(Vec<WrappedValue>),
105    Dictionary(Dictionary),
106    #[rasn(tag(universal, 1))]
107    Boolean(bool),
108    #[rasn(tag(universal, 2))]
109    Integer(Integer),
110    #[rasn(tag(universal, 12))]
111    String(String),
112}
113
114impl TryFrom<Value> for WrappedValue {
115    type Error = EncodeError;
116
117    fn try_from(value: Value) -> Result<Self, Self::Error> {
118        match value {
119            Value::Array(v) => Ok(Self::Array(
120                v.into_iter()
121                    .map(Self::try_from)
122                    .collect::<Result<Vec<_>, _>>()?,
123            )),
124            Value::Dictionary(v) => Ok(Self::Dictionary(Dictionary(v))),
125            Value::Boolean(v) => Ok(Self::Boolean(v)),
126            Value::Integer(v) => {
127                let integer = Integer::from(v.as_signed().ok_or(EncodeError::custom(
128                    "could not obtain integer representation from plist integer",
129                    Codec::Der,
130                ))?);
131
132                Ok(Self::Integer(integer))
133            }
134            Value::String(v) => Ok(Self::String(v)),
135            Value::Data(_) => Err(EncodeError::custom(
136                "encoding of data values not supported",
137                Codec::Der,
138            )),
139            Value::Date(_) => Err(EncodeError::custom(
140                "encoding of date values not supported",
141                Codec::Der,
142            )),
143            Value::Real(_) => Err(EncodeError::custom(
144                "encoding of real values not supported",
145                Codec::Der,
146            )),
147            Value::Uid(_) => Err(EncodeError::custom(
148                "encoding of uid values not supported",
149                Codec::Der,
150            )),
151            _ => Err(EncodeError::custom(
152                "encoding of unknown value type not supported",
153                Codec::Der,
154            )),
155        }
156    }
157}
158
159impl TryFrom<WrappedValue> for Value {
160    type Error = DecodeError;
161
162    fn try_from(value: WrappedValue) -> Result<Self, Self::Error> {
163        match value {
164            WrappedValue::Array(v) => Ok(Self::Array(
165                v.into_iter()
166                    .map(Self::try_from)
167                    .collect::<Result<Vec<_>, _>>()?,
168            )),
169            WrappedValue::Dictionary(v) => Ok(Self::Dictionary(v.0)),
170            WrappedValue::Boolean(v) => Ok(Self::Boolean(v)),
171            WrappedValue::Integer(v) => {
172                let v = v.to_i64().ok_or(DecodeError::custom(
173                    "could not convert BigInt to i64",
174                    Codec::Der,
175                ))?;
176
177                Ok(Self::Integer(plist::Integer::from(v)))
178            }
179            WrappedValue::String(v) => Ok(Self::String(v)),
180        }
181    }
182}
183
184/// Represents a top-level plist in the rasn domain.
185struct WrappedPlist(WrappedValue);
186
187impl AsnType for WrappedPlist {
188    const TAG: Tag = Tag {
189        class: Class::Application,
190        value: 16,
191    };
192}
193
194impl Constructed for WrappedPlist {
195    const FIELDS: Fields = Fields::from_static(&[
196        Field::new_required_type::<Integer>("key"),
197        Field::new_required_type::<WrappedValue>("value"),
198    ]);
199}
200
201impl TryFrom<Value> for WrappedPlist {
202    type Error = EncodeError;
203
204    fn try_from(value: Value) -> Result<Self, Self::Error> {
205        Ok(Self(value.try_into()?))
206    }
207}
208
209impl TryFrom<WrappedPlist> for Value {
210    type Error = DecodeError;
211
212    fn try_from(value: WrappedPlist) -> Result<Self, Self::Error> {
213        if let WrappedValue::Dictionary(d) = value.0 {
214            Ok(Self::Dictionary(d.0))
215        } else {
216            Err(DecodeError::custom(
217                "wrapped value not a dictionary",
218                Codec::Der,
219            ))
220        }
221    }
222}
223
224impl Encode for WrappedPlist {
225    fn encode_with_tag_and_constraints<E: Encoder>(
226        &self,
227        encoder: &mut E,
228        tag: Tag,
229        _constraints: Constraints,
230    ) -> Result<(), E::Error> {
231        encoder.encode_sequence::<Self, _>(tag, |encoder| {
232            encoder.encode_integer(Tag::INTEGER, Constraints::NONE, &Integer::from(1))?;
233            self.0.encode(encoder)
234        })?;
235
236        Ok(())
237    }
238}
239
240impl Decode for WrappedPlist {
241    fn decode_with_tag_and_constraints<D: Decoder>(
242        decoder: &mut D,
243        tag: Tag,
244        _constraints: Constraints,
245    ) -> Result<Self, D::Error> {
246        decoder.decode_sequence::<Self, _, _>(tag, None::<fn() -> Self>, |decoder| {
247            let _: Integer = decoder.decode_integer(Tag::INTEGER, Constraints::NONE)?;
248            let value = WrappedValue::decode(decoder)?;
249
250            Ok(Self(value))
251        })
252    }
253}
254
255/// Encode a top-level plist [Value] to DER.
256pub fn der_encode_plist(value: &Value) -> Result<Vec<u8>, AppleCodesignError> {
257    rasn::der::encode_scope(|encoder| {
258        let wrapped = WrappedPlist::try_from(value.clone())?;
259        wrapped.encode(encoder)
260    })
261    .map_err(|e| AppleCodesignError::PlistDer(format!("{e}")))
262}
263
264/// Decode DER to a plist [Value].
265pub fn der_decode_plist(data: impl AsRef<[u8]>) -> Result<Value, AppleCodesignError> {
266    rasn::der::decode::<WrappedPlist>(data.as_ref())
267        .and_then(Value::try_from)
268        .map_err(|e| AppleCodesignError::PlistDer(format!("{e}")))
269}
270
271#[cfg(test)]
272mod test {
273    use {
274        super::*,
275        crate::{
276            embedded_signature::{Blob, CodeSigningSlot},
277            macho::MachFile,
278        },
279        anyhow::{anyhow, Result},
280        plist::{Date, Uid},
281        std::{
282            process::Command,
283            time::{Duration, SystemTime},
284        },
285    };
286
287    const DER_EMPTY_DICT: &[u8] = &[112, 5, 2, 1, 1, 176, 0];
288    const DER_BOOL_FALSE: &[u8] = &[
289        112, 15, 2, 1, 1, 176, 10, 48, 8, 12, 3, 107, 101, 121, 1, 1, 0,
290    ];
291    const DER_BOOL_TRUE: &[u8] = &[
292        112, 15, 2, 1, 1, 176, 10, 48, 8, 12, 3, 107, 101, 121, 1, 1, 255,
293    ];
294    const DER_INTEGER_0: &[u8] = &[
295        112, 15, 2, 1, 1, 176, 10, 48, 8, 12, 3, 107, 101, 121, 2, 1, 0,
296    ];
297    const DER_INTEGER_NEG1: &[u8] = &[
298        112, 15, 2, 1, 1, 176, 10, 48, 8, 12, 3, 107, 101, 121, 2, 1, 255,
299    ];
300    const DER_INTEGER_1: &[u8] = &[
301        112, 15, 2, 1, 1, 176, 10, 48, 8, 12, 3, 107, 101, 121, 2, 1, 1,
302    ];
303    const DER_INTEGER_42: &[u8] = &[
304        112, 15, 2, 1, 1, 176, 10, 48, 8, 12, 3, 107, 101, 121, 2, 1, 42,
305    ];
306    const DER_STRING_EMPTY: &[u8] = &[112, 14, 2, 1, 1, 176, 9, 48, 7, 12, 3, 107, 101, 121, 12, 0];
307    const DER_STRING_VALUE: &[u8] = &[
308        112, 19, 2, 1, 1, 176, 14, 48, 12, 12, 3, 107, 101, 121, 12, 5, 118, 97, 108, 117, 101,
309    ];
310    const DER_ARRAY_EMPTY: &[u8] = &[112, 14, 2, 1, 1, 176, 9, 48, 7, 12, 3, 107, 101, 121, 48, 0];
311    const DER_ARRAY_FALSE: &[u8] = &[
312        112, 17, 2, 1, 1, 176, 12, 48, 10, 12, 3, 107, 101, 121, 48, 3, 1, 1, 0,
313    ];
314    const DER_ARRAY_TRUE_FOO: &[u8] = &[
315        112, 22, 2, 1, 1, 176, 17, 48, 15, 12, 3, 107, 101, 121, 48, 8, 1, 1, 255, 12, 3, 102, 111,
316        111,
317    ];
318    const DER_DICT_EMPTY: &[u8] = &[
319        112, 14, 2, 1, 1, 176, 9, 48, 7, 12, 3, 107, 101, 121, 176, 0,
320    ];
321    const DER_DICT_BOOL: &[u8] = &[
322        112, 26, 2, 1, 1, 176, 21, 48, 19, 12, 3, 107, 101, 121, 176, 12, 48, 10, 12, 5, 105, 110,
323        110, 101, 114, 1, 1, 0,
324    ];
325    const DER_MULTIPLE_KEYS: &[u8] = &[
326        112, 37, 2, 1, 1, 176, 32, 48, 8, 12, 3, 107, 101, 121, 1, 1, 0, 48, 9, 12, 4, 107, 101,
327        121, 50, 1, 1, 255, 48, 9, 12, 4, 107, 101, 121, 51, 2, 1, 42,
328    ];
329
330    /// Signs a binary with custom entitlements XML and retrieves the entitlements DER.
331    ///
332    /// This uses Apple's `codesign` executable to sign the current binary then uses
333    /// our library for extracting the entitlements DER that it generated.
334    #[allow(unused)]
335    fn sign_and_get_entitlements_der(value: &Value) -> Result<Vec<u8>> {
336        let this_exe = std::env::current_exe()?;
337
338        let temp_dir = tempfile::tempdir()?;
339
340        let in_path = temp_dir.path().join("original");
341        let entitlements_path = temp_dir.path().join("entitlements.xml");
342        std::fs::copy(this_exe, &in_path)?;
343        {
344            let mut fh = std::fs::File::create(&entitlements_path)?;
345            value.to_writer_xml(&mut fh)?;
346        }
347
348        let args = vec![
349            "--verbose".to_string(),
350            "--force".to_string(),
351            // ad-hoc signing since we don't care about a CMS signature.
352            "-s".to_string(),
353            "-".to_string(),
354            "--generate-entitlement-der".to_string(),
355            "--entitlements".to_string(),
356            format!("{}", entitlements_path.display()),
357            format!("{}", in_path.display()),
358        ];
359
360        let status = Command::new("codesign").args(args).output()?;
361        if !status.status.success() {
362            return Err(anyhow!("codesign invocation failure"));
363        }
364
365        // Now extract the data from the Apple produced code signature.
366
367        let signed_exe = std::fs::read(&in_path)?;
368        let mach = MachFile::parse(&signed_exe)?;
369        let macho = mach.nth_macho(0)?;
370
371        let signature = macho
372            .code_signature()?
373            .expect("unable to find code signature");
374
375        let slot = signature
376            .find_slot(CodeSigningSlot::EntitlementsDer)
377            .expect("unable to find der entitlements blob");
378
379        match slot.clone().into_parsed_blob()?.blob {
380            crate::embedded_signature::BlobData::EntitlementsDer(der) => {
381                Ok(der.serialize_payload()?)
382            }
383            _ => Err(anyhow!(
384                "failed to obtain entitlements DER (this should never happen)"
385            )),
386        }
387    }
388
389    // This test is failing in CI. Older versions of macOS / codesign likely have
390    // a different DER encoding mechanism.
391    // #[test]
392    #[cfg(target_os = "macos")]
393    #[allow(unused)]
394    fn apple_der_entitlements_encoding() -> Result<()> {
395        // `codesign` prints "unknown exception" if we attempt to serialize a plist where
396        // the root element isn't a dict.
397        let mut d = plist::Dictionary::new();
398
399        assert_eq!(
400            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
401            DER_EMPTY_DICT
402        );
403
404        d.insert("key".into(), Value::Boolean(false));
405        assert_eq!(
406            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
407            DER_BOOL_FALSE
408        );
409
410        d.insert("key".into(), Value::Boolean(true));
411        assert_eq!(
412            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
413            DER_BOOL_TRUE
414        );
415
416        d.insert("key".into(), Value::Integer(0u32.into()));
417        assert_eq!(
418            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
419            DER_INTEGER_0
420        );
421
422        d.insert("key".into(), Value::Integer((-1i32).into()));
423        assert_eq!(
424            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
425            DER_INTEGER_NEG1
426        );
427
428        d.insert("key".into(), Value::Integer(1u32.into()));
429        assert_eq!(
430            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
431            DER_INTEGER_1
432        );
433
434        d.insert("key".into(), Value::Integer(42u32.into()));
435        assert_eq!(
436            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
437            DER_INTEGER_42
438        );
439
440        // Floats fail to encode to DER.
441        d.insert("key".into(), Value::Real(0.0f32.into()));
442        assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err());
443
444        d.insert("key".into(), Value::Real((-1.0f32).into()));
445        assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err());
446
447        d.insert("key".into(), Value::Real(1.0f32.into()));
448        assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err());
449
450        d.insert("key".into(), Value::String("".into()));
451        assert_eq!(
452            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
453            DER_STRING_EMPTY
454        );
455
456        d.insert("key".into(), Value::String("value".into()));
457        assert_eq!(
458            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
459            DER_STRING_VALUE
460        );
461
462        // Uids fail to encode with `UidNotSupportedInXmlPlist` message.
463        d.insert("key".into(), Value::Uid(Uid::new(0)));
464        assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err());
465
466        d.insert("key".into(), Value::Uid(Uid::new(1)));
467        assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err());
468
469        d.insert("key".into(), Value::Uid(Uid::new(42)));
470        assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err());
471
472        // Date doesn't appear to work due to
473        // `Failed to parse entitlements: AMFIUnserializeXML: syntax error near line 6`. Perhaps
474        // a bug in the plist crate?
475        d.insert(
476            "key".into(),
477            Value::Date(Date::from(SystemTime::UNIX_EPOCH)),
478        );
479        assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err());
480        d.insert(
481            "key".into(),
482            Value::Date(Date::from(
483                SystemTime::UNIX_EPOCH + Duration::from_secs(86400 * 365 * 30),
484            )),
485        );
486        assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err());
487
488        // Data fails to encode to DER with `unknown exception`.
489        d.insert("key".into(), Value::Data(vec![]));
490        assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err());
491        d.insert("key".into(), Value::Data(b"foo".to_vec()));
492        assert!(sign_and_get_entitlements_der(&Value::Dictionary(d.clone())).is_err());
493
494        d.insert("key".into(), Value::Array(vec![]));
495        assert_eq!(
496            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
497            DER_ARRAY_EMPTY
498        );
499
500        d.insert("key".into(), Value::Array(vec![Value::Boolean(false)]));
501        assert_eq!(
502            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
503            DER_ARRAY_FALSE
504        );
505
506        d.insert(
507            "key".into(),
508            Value::Array(vec![Value::Boolean(true), Value::String("foo".into())]),
509        );
510        assert_eq!(
511            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
512            DER_ARRAY_TRUE_FOO
513        );
514
515        let mut inner = plist::Dictionary::new();
516        d.insert("key".into(), Value::Dictionary(inner.clone()));
517        assert_eq!(
518            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
519            DER_DICT_EMPTY
520        );
521
522        inner.insert("inner".into(), Value::Boolean(false));
523        d.insert("key".into(), Value::Dictionary(inner.clone()));
524        assert_eq!(
525            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
526            DER_DICT_BOOL
527        );
528
529        d.insert("key".into(), Value::Boolean(false));
530        d.insert("key2".into(), Value::Boolean(true));
531        d.insert("key3".into(), Value::Integer(42i32.into()));
532        assert_eq!(
533            sign_and_get_entitlements_der(&Value::Dictionary(d.clone()))?,
534            DER_MULTIPLE_KEYS
535        );
536
537        Ok(())
538    }
539
540    #[test]
541    fn der_encoding() -> Result<()> {
542        let mut d = plist::Dictionary::new();
543
544        assert_eq!(
545            der_encode_plist(&Value::Dictionary(d.clone()))?,
546            DER_EMPTY_DICT
547        );
548        assert_eq!(
549            der_decode_plist(DER_EMPTY_DICT)?,
550            Value::Dictionary(d.clone())
551        );
552
553        d.insert("key".into(), Value::Boolean(false));
554        assert_eq!(
555            der_encode_plist(&Value::Dictionary(d.clone()))?,
556            DER_BOOL_FALSE
557        );
558        assert_eq!(
559            der_decode_plist(DER_BOOL_FALSE)?,
560            Value::Dictionary(d.clone())
561        );
562
563        d.insert("key".into(), Value::Boolean(true));
564        assert_eq!(
565            der_encode_plist(&Value::Dictionary(d.clone()))?,
566            DER_BOOL_TRUE
567        );
568        assert_eq!(
569            der_decode_plist(DER_BOOL_TRUE)?,
570            Value::Dictionary(d.clone())
571        );
572
573        d.insert("key".into(), Value::Integer(0u32.into()));
574        assert_eq!(
575            der_encode_plist(&Value::Dictionary(d.clone()))?,
576            DER_INTEGER_0
577        );
578        assert_eq!(
579            der_decode_plist(DER_INTEGER_0)?,
580            Value::Dictionary(d.clone())
581        );
582
583        d.insert("key".into(), Value::Integer((-1i32).into()));
584        assert_eq!(
585            der_encode_plist(&Value::Dictionary(d.clone()))?,
586            DER_INTEGER_NEG1
587        );
588        assert_eq!(
589            der_decode_plist(DER_INTEGER_NEG1)?,
590            Value::Dictionary(d.clone())
591        );
592
593        d.insert("key".into(), Value::Integer(1u32.into()));
594        assert_eq!(
595            der_encode_plist(&Value::Dictionary(d.clone()))?,
596            DER_INTEGER_1
597        );
598        assert_eq!(
599            der_decode_plist(DER_INTEGER_1)?,
600            Value::Dictionary(d.clone())
601        );
602
603        d.insert("key".into(), Value::Integer(42u32.into()));
604        assert_eq!(
605            der_encode_plist(&Value::Dictionary(d.clone()))?,
606            DER_INTEGER_42
607        );
608        assert_eq!(
609            der_decode_plist(DER_INTEGER_42)?,
610            Value::Dictionary(d.clone())
611        );
612
613        d.insert("key".into(), Value::Real(0.0f32.into()));
614        assert!(matches!(
615            der_encode_plist(&Value::Dictionary(d.clone())),
616            Err(AppleCodesignError::PlistDer(_))
617        ));
618
619        d.insert("key".into(), Value::Real((-1.0f32).into()));
620        assert!(matches!(
621            der_encode_plist(&Value::Dictionary(d.clone())),
622            Err(AppleCodesignError::PlistDer(_))
623        ));
624
625        d.insert("key".into(), Value::Real(1.0f32.into()));
626        assert!(matches!(
627            der_encode_plist(&Value::Dictionary(d.clone())),
628            Err(AppleCodesignError::PlistDer(_))
629        ));
630
631        d.insert("key".into(), Value::String("".into()));
632        assert_eq!(
633            der_encode_plist(&Value::Dictionary(d.clone()))?,
634            DER_STRING_EMPTY
635        );
636        assert_eq!(
637            der_decode_plist(DER_STRING_EMPTY)?,
638            Value::Dictionary(d.clone())
639        );
640
641        d.insert("key".into(), Value::String("value".into()));
642        assert_eq!(
643            der_encode_plist(&Value::Dictionary(d.clone()))?,
644            DER_STRING_VALUE
645        );
646        assert_eq!(
647            der_decode_plist(DER_STRING_VALUE)?,
648            Value::Dictionary(d.clone())
649        );
650
651        d.insert("key".into(), Value::Uid(Uid::new(0)));
652        assert!(matches!(
653            der_encode_plist(&Value::Dictionary(d.clone())),
654            Err(AppleCodesignError::PlistDer(_))
655        ));
656
657        d.insert("key".into(), Value::Uid(Uid::new(1)));
658        assert!(matches!(
659            der_encode_plist(&Value::Dictionary(d.clone())),
660            Err(AppleCodesignError::PlistDer(_))
661        ));
662
663        d.insert("key".into(), Value::Uid(Uid::new(42)));
664        assert!(matches!(
665            der_encode_plist(&Value::Dictionary(d.clone())),
666            Err(AppleCodesignError::PlistDer(_))
667        ));
668
669        d.insert(
670            "key".into(),
671            Value::Date(Date::from(SystemTime::UNIX_EPOCH)),
672        );
673        assert!(matches!(
674            der_encode_plist(&Value::Dictionary(d.clone())),
675            Err(AppleCodesignError::PlistDer(_))
676        ));
677        d.insert(
678            "key".into(),
679            Value::Date(Date::from(
680                SystemTime::UNIX_EPOCH + Duration::from_secs(86400 * 365 * 30),
681            )),
682        );
683        assert!(matches!(
684            der_encode_plist(&Value::Dictionary(d.clone())),
685            Err(AppleCodesignError::PlistDer(_))
686        ));
687
688        // Data fails to encode to DER with `unknown exception`.
689        d.insert("key".into(), Value::Data(vec![]));
690        assert!(matches!(
691            der_encode_plist(&Value::Dictionary(d.clone())),
692            Err(AppleCodesignError::PlistDer(_))
693        ));
694        d.insert("key".into(), Value::Data(b"foo".to_vec()));
695        assert!(matches!(
696            der_encode_plist(&Value::Dictionary(d.clone())),
697            Err(AppleCodesignError::PlistDer(_))
698        ));
699
700        d.insert("key".into(), Value::Array(vec![]));
701        assert_eq!(
702            der_encode_plist(&Value::Dictionary(d.clone()))?,
703            DER_ARRAY_EMPTY
704        );
705        assert_eq!(
706            der_decode_plist(DER_ARRAY_EMPTY)?,
707            Value::Dictionary(d.clone())
708        );
709
710        d.insert("key".into(), Value::Array(vec![Value::Boolean(false)]));
711        assert_eq!(
712            der_encode_plist(&Value::Dictionary(d.clone()))?,
713            DER_ARRAY_FALSE
714        );
715        assert_eq!(
716            der_decode_plist(DER_ARRAY_FALSE)?,
717            Value::Dictionary(d.clone())
718        );
719
720        d.insert(
721            "key".into(),
722            Value::Array(vec![Value::Boolean(true), Value::String("foo".into())]),
723        );
724        assert_eq!(
725            der_encode_plist(&Value::Dictionary(d.clone()))?,
726            DER_ARRAY_TRUE_FOO
727        );
728        assert_eq!(
729            der_decode_plist(DER_ARRAY_TRUE_FOO)?,
730            Value::Dictionary(d.clone())
731        );
732
733        let mut inner = plist::Dictionary::new();
734        d.insert("key".into(), Value::Dictionary(inner.clone()));
735        assert_eq!(
736            der_encode_plist(&Value::Dictionary(d.clone()))?,
737            DER_DICT_EMPTY
738        );
739        assert_eq!(
740            der_decode_plist(DER_DICT_EMPTY)?,
741            Value::Dictionary(d.clone())
742        );
743
744        inner.insert("inner".into(), Value::Boolean(false));
745        d.insert("key".into(), Value::Dictionary(inner.clone()));
746        assert_eq!(
747            der_encode_plist(&Value::Dictionary(d.clone()))?,
748            DER_DICT_BOOL
749        );
750        assert_eq!(
751            der_decode_plist(DER_DICT_BOOL)?,
752            Value::Dictionary(d.clone())
753        );
754
755        d.insert("key".into(), Value::Boolean(false));
756        d.insert("key2".into(), Value::Boolean(true));
757        d.insert("key3".into(), Value::Integer(42i32.into()));
758        assert_eq!(
759            der_encode_plist(&Value::Dictionary(d.clone()))?,
760            DER_MULTIPLE_KEYS
761        );
762        assert_eq!(
763            der_decode_plist(DER_MULTIPLE_KEYS)?,
764            Value::Dictionary(d.clone())
765        );
766
767        Ok(())
768    }
769}