apple_codesign/
certificate.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//! Functionality related to certificates.
6
7use {
8    crate::{apple_certificates::KnownCertificate, error::AppleCodesignError},
9    bcder::{
10        encode::{PrimitiveContent, Values},
11        ConstOid, Oid,
12    },
13    bytes::Bytes,
14    pkcs8::EncodePrivateKey,
15    std::{
16        fmt::{Display, Formatter},
17        str::FromStr,
18    },
19    x509_certificate::{
20        certificate::KeyUsage, rfc4519::OID_COUNTRY_NAME, CapturedX509Certificate,
21        InMemorySigningKeyPair, KeyAlgorithm, X509CertificateBuilder,
22    },
23};
24
25/// Extended Key Usage extension.
26///
27/// 2.5.29.37
28const OID_EXTENDED_KEY_USAGE: ConstOid = Oid(&[85, 29, 37]);
29
30/// Extended Key Usage purpose for code signing.
31///
32/// 1.3.6.1.5.5.7.3.3
33const OID_EKU_PURPOSE_CODE_SIGNING: ConstOid = Oid(&[43, 6, 1, 5, 5, 7, 3, 3]);
34
35/// Extended Key Usage for purpose of `Safari Developer`.
36///
37/// 1.2.840.113635.100.4.8
38const OID_EKU_PURPOSE_SAFARI_DEVELOPER: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 4, 8]);
39
40/// Extended Key Usage for purpose of `3rd Party Mac Developer Installer`.
41///
42/// 1.2.840.113635.100.4.9
43const OID_EKU_PURPOSE_3RD_PARTY_MAC_DEVELOPER_INSTALLER: ConstOid =
44    Oid(&[42, 134, 72, 134, 247, 99, 100, 4, 9]);
45
46/// Extended Key Usage for purpose of `Developer ID Installer`.
47///
48/// 1.2.840.113635.100.4.13
49const OID_EKU_PURPOSE_DEVELOPER_ID_INSTALLER: ConstOid =
50    Oid(&[42, 134, 72, 134, 247, 99, 100, 4, 13]);
51
52/// All OIDs known for extended key usage.
53const ALL_OID_EKUS: &[&ConstOid; 4] = &[
54    &OID_EKU_PURPOSE_CODE_SIGNING,
55    &OID_EKU_PURPOSE_SAFARI_DEVELOPER,
56    &OID_EKU_PURPOSE_3RD_PARTY_MAC_DEVELOPER_INSTALLER,
57    &OID_EKU_PURPOSE_DEVELOPER_ID_INSTALLER,
58];
59
60/// Extension for `Apple Signing`.
61///
62/// 1.2.840.113635.100.6.1.1
63const OID_EXTENSION_APPLE_SIGNING: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 1]);
64
65/// Extension for `iPhone Developer`.
66///
67/// 1.2.840.113635.100.6.1.2
68const OID_EXTENSION_IPHONE_DEVELOPER: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 2]);
69
70/// Extension for `Apple iPhone OS Application Signing`
71///
72/// 1.2.840.113635.100.6.1.3
73const OID_EXTENSION_IPHONE_OS_APPLICATION_SIGNING: ConstOid =
74    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 3]);
75
76/// Extension for `Apple Developer Certificate (Submission)`.
77///
78/// May also be referred to as `iPhone Distribution`.
79///
80/// 1.2.840.113635.100.6.1.4
81const OID_EXTENSION_APPLE_DEVELOPER_CERTIFICATE_SUBMISSION: ConstOid =
82    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 4]);
83
84/// Extension for `Safari Developer`.
85///
86/// 1.2.840.113635.100.6.1.5
87const OID_EXTENSION_SAFARI_DEVELOPER: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 5]);
88
89/// Extension for `Apple iPhone OS VPN Signing`
90///
91/// 1.2.840.113635.100.6.1.6
92const OID_EXTENSION_IPHONE_OS_VPN_SIGNING: ConstOid =
93    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 6]);
94
95/// Extension for `Apple Mac App Signing (Development)`.
96///
97/// May also appear as `3rd Party Mac Developer Application`.
98///
99/// 1.2.840.113635.100.6.1.7
100const OID_EXTENSION_APPLE_MAC_APP_SIGNING_DEVELOPMENT: ConstOid =
101    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 7]);
102
103/// Extension for `Apple Mac App Signing Submission`.
104///
105/// 1.2.840.113635.100.6.1.8
106const OID_EXTENSION_APPLE_MAC_APP_SIGNING_SUBMISSION: ConstOid =
107    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 8]);
108
109/// Extension for `Mac App Store Code Signing`.
110///
111/// 1.2.840.113635.100.6.1.9
112const OID_EXTENSION_APPLE_MAC_APP_STORE_CODE_SIGNING: ConstOid =
113    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 9]);
114
115/// Extension for `Mac App Store Installer Signing`.
116///
117/// 1.2.840.113635.100.6.1.10
118const OID_EXTENSION_APPLE_MAC_APP_STORE_INSTALLER_SIGNING: ConstOid =
119    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 10]);
120
121// 1.2.840.113635.100.6.1.11 is unknown.
122
123/// Extension for `Mac Developer`.
124///
125/// 1.2.840.113635.100.6.1.12
126const OID_EXTENSION_MAC_DEVELOPER: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 12]);
127
128/// Extension for `Developer ID Application`.
129///
130/// 1.2.840.113635.100.6.1.13
131const OID_EXTENSION_DEVELOPER_ID_APPLICATION: ConstOid =
132    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 13]);
133
134/// Extension for `Developer ID Installer`.
135///
136/// 1.2.840.113635.100.6.1.14
137const OID_EXTENSION_DEVELOPER_ID_INSTALLER: ConstOid =
138    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 14]);
139
140// 1.2.840.113635.100.6.1.15 looks to have something to do with core OS functionality,
141// as it appears in search results for hacking Apple OS booting.
142
143/// Extension for `Apple Pay Passbook Signing`
144///
145/// 1.2.840.113635.100.6.1.16
146const OID_EXTENSION_PASSBOOK_SIGNING: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 16]);
147
148/// Extension for `Web Site Push Notifications Signing`
149///
150/// 1.2.840.113635.100.6.1.17
151const OID_EXTENSION_WEBSITE_PUSH_NOTIFICATION_SIGNING: ConstOid =
152    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 17]);
153
154/// Extension for `Developer ID Kernel`.
155///
156/// 1.2.840.113635.100.6.1.18
157const OID_EXTENSION_DEVELOPER_ID_KERNEL: ConstOid =
158    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 18]);
159
160/// Extension for `Developer ID Date`.
161///
162/// This OID doesn't have a description in Apple tooling. But it
163/// holds a UtcDate (with hours, minutes, and seconds all set to 0) and seems to
164/// denote a date constraint to apply to validation. This is likely used
165/// to validating timestamping constrains for certificate validity.
166///
167/// 1.2.840.113635.100.6.1.33
168const OID_EXTENSION_DEVELOPER_ID_DATE: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 33]);
169
170/// Extension for `TestFlight`.
171///
172/// 1.2.840.113635.100.6.1.25.1
173const OID_EXTENSION_TEST_FLIGHT: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 1, 25, 1]);
174
175/// All OIDs associated with non Certificate Authority extensions.
176const ALL_OID_NON_CA_EXTENSIONS: &[&ConstOid; 18] = &[
177    &OID_EXTENSION_APPLE_SIGNING,
178    &OID_EXTENSION_IPHONE_DEVELOPER,
179    &OID_EXTENSION_IPHONE_OS_APPLICATION_SIGNING,
180    &OID_EXTENSION_APPLE_DEVELOPER_CERTIFICATE_SUBMISSION,
181    &OID_EXTENSION_SAFARI_DEVELOPER,
182    &OID_EXTENSION_IPHONE_OS_VPN_SIGNING,
183    &OID_EXTENSION_APPLE_MAC_APP_SIGNING_DEVELOPMENT,
184    &OID_EXTENSION_APPLE_MAC_APP_SIGNING_SUBMISSION,
185    &OID_EXTENSION_APPLE_MAC_APP_STORE_CODE_SIGNING,
186    &OID_EXTENSION_APPLE_MAC_APP_STORE_INSTALLER_SIGNING,
187    &OID_EXTENSION_MAC_DEVELOPER,
188    &OID_EXTENSION_DEVELOPER_ID_APPLICATION,
189    &OID_EXTENSION_DEVELOPER_ID_INSTALLER,
190    &OID_EXTENSION_PASSBOOK_SIGNING,
191    &OID_EXTENSION_WEBSITE_PUSH_NOTIFICATION_SIGNING,
192    &OID_EXTENSION_DEVELOPER_ID_KERNEL,
193    &OID_EXTENSION_DEVELOPER_ID_DATE,
194    &OID_EXTENSION_TEST_FLIGHT,
195];
196
197/// UserID.
198///
199/// 0.9.2342.19200300.100.1.1
200pub const OID_USER_ID: ConstOid = Oid(&[9, 146, 38, 137, 147, 242, 44, 100, 1, 1]);
201
202/// OID used for email address in RDN in Apple generated code signing certificates.
203const OID_EMAIL_ADDRESS: ConstOid = Oid(&[42, 134, 72, 134, 247, 13, 1, 9, 1]);
204
205/// Apple Worldwide Developer Relations.
206///
207/// 1.2.840.113635.100.6.2.1
208const OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS: ConstOid =
209    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 1]);
210
211/// Apple Application Integration.
212///
213/// 1.2.840.113635.100.6.2.3
214const OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION: ConstOid =
215    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 3]);
216
217/// Developer ID Certification Authority
218///
219/// 1.2.840.113635.100.6.2.6
220const OID_CA_EXTENSION_DEVELOPER_ID: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 6]);
221
222/// Apple Timestamp.
223///
224/// 1.2.840.113635.100.6.2.9
225const OID_CA_EXTENSION_APPLE_TIMESTAMP: ConstOid = Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 9]);
226
227/// Developer Authentication Certification Authority.
228///
229/// 1.2.840.113635.100.6.2.11
230const OID_CA_EXTENSION_DEVELOPER_AUTHENTICATION: ConstOid =
231    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 11]);
232
233/// Apple Application Integration CA - G3
234///
235/// 1.2.840.113635.100.6.2.14
236const OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G3: ConstOid =
237    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 14]);
238
239/// Apple Worldwide Developer Relations CA - G2
240///
241/// 1.2.840.113635.100.6.2.15
242const OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS_G2: ConstOid =
243    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 15]);
244
245/// Apple Software Update Certification.
246///
247/// 1.2.840.113635.100.6.2.19
248const OID_CA_EXTENSION_APPLE_SOFTWARE_UPDATE_CERTIFICATION: ConstOid =
249    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 19]);
250
251/// Apple Application Integration CA - G1.
252///
253/// This was introduced in `Apple Application Integration CA 7 - G1`
254/// The previous `Apple Application Integration CA 5 - G1` certificate
255/// had the legacy 1.2.840.113635.100.6.2.3 extension.
256///
257/// 1.2.840.113635.100.6.2.31
258const OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G1: ConstOid =
259    Oid(&[42, 134, 72, 134, 247, 99, 100, 6, 2, 31]);
260
261const ALL_OID_CA_EXTENSIONS: &[&ConstOid; 9] = &[
262    &OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS,
263    &OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION,
264    &OID_CA_EXTENSION_DEVELOPER_ID,
265    &OID_CA_EXTENSION_APPLE_TIMESTAMP,
266    &OID_CA_EXTENSION_DEVELOPER_AUTHENTICATION,
267    &OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G3,
268    &OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS_G2,
269    &OID_CA_EXTENSION_APPLE_SOFTWARE_UPDATE_CERTIFICATION,
270    &OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G1,
271];
272
273/// Describes the type of code signing that a certificate is authorized to perform.
274///
275/// Code signing certificates are issued with extended key usage (EKU) attributes
276/// denoting what that certificate will be used for. They basically say *I'm authorized
277/// to sign X*.
278///
279/// This type describes the different code signing key usages defined on Apple
280/// platforms.
281#[derive(Clone, Copy, Debug, Eq, PartialEq)]
282pub enum ExtendedKeyUsagePurpose {
283    /// Code signing.
284    CodeSigning,
285
286    /// Safari Developer.
287    SafariDeveloper,
288
289    /// 3rd Party Mac Developer Installer Packaging Signing.
290    ///
291    /// The certificate can be used to sign Mac installer packages.
292    ThirdPartyMacDeveloperInstaller,
293
294    /// Developer ID Installer.
295    DeveloperIdInstaller,
296}
297
298impl ExtendedKeyUsagePurpose {
299    /// Obtain all variants of this enumeration.
300    pub fn all() -> Vec<Self> {
301        vec![
302            Self::CodeSigning,
303            Self::SafariDeveloper,
304            Self::ThirdPartyMacDeveloperInstaller,
305            Self::DeveloperIdInstaller,
306        ]
307    }
308
309    pub fn all_oids() -> &'static [&'static ConstOid] {
310        ALL_OID_EKUS
311    }
312
313    pub fn as_oid(&self) -> ConstOid {
314        match self {
315            Self::CodeSigning => OID_EKU_PURPOSE_CODE_SIGNING,
316            Self::SafariDeveloper => OID_EKU_PURPOSE_SAFARI_DEVELOPER,
317            Self::ThirdPartyMacDeveloperInstaller => {
318                OID_EKU_PURPOSE_3RD_PARTY_MAC_DEVELOPER_INSTALLER
319            }
320            Self::DeveloperIdInstaller => OID_EKU_PURPOSE_DEVELOPER_ID_INSTALLER,
321        }
322    }
323}
324
325impl Display for ExtendedKeyUsagePurpose {
326    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
327        match self {
328            ExtendedKeyUsagePurpose::CodeSigning => f.write_str("Code Signing"),
329            ExtendedKeyUsagePurpose::SafariDeveloper => f.write_str("Safari Developer"),
330            ExtendedKeyUsagePurpose::ThirdPartyMacDeveloperInstaller => {
331                f.write_str("3rd Party Mac Developer Installer Packaging Signing")
332            }
333            ExtendedKeyUsagePurpose::DeveloperIdInstaller => f.write_str("Developer ID Installer"),
334        }
335    }
336}
337
338impl TryFrom<&Oid> for ExtendedKeyUsagePurpose {
339    type Error = AppleCodesignError;
340
341    fn try_from(oid: &Oid) -> Result<Self, Self::Error> {
342        // Surely there is a way to use `match`. But the `Oid` type is a bit wonky.
343        if oid.as_ref() == OID_EKU_PURPOSE_CODE_SIGNING.as_ref() {
344            Ok(Self::CodeSigning)
345        } else if oid.as_ref() == OID_EKU_PURPOSE_SAFARI_DEVELOPER.as_ref() {
346            Ok(Self::SafariDeveloper)
347        } else if oid.as_ref() == OID_EKU_PURPOSE_3RD_PARTY_MAC_DEVELOPER_INSTALLER.as_ref() {
348            Ok(Self::ThirdPartyMacDeveloperInstaller)
349        } else if oid.as_ref() == OID_EKU_PURPOSE_DEVELOPER_ID_INSTALLER.as_ref() {
350            Ok(Self::DeveloperIdInstaller)
351        } else {
352            Err(AppleCodesignError::OidIsntCertificateAuthority)
353        }
354    }
355}
356
357/// Describes one of the many X.509 certificate extensions found on Apple code signing certificates.
358#[derive(Clone, Copy, Debug, Eq, PartialEq)]
359pub enum CodeSigningCertificateExtension {
360    /// Apple Signing.
361    ///
362    /// (Appears to be deprecated).
363    AppleSigning,
364
365    /// iPhone Developer.
366    IPhoneDeveloper,
367
368    /// Apple iPhone OS Application Signing.
369    IPhoneOsApplicationSigning,
370
371    /// Apple Developer Certificate (Submission).
372    ///
373    /// May also be referred to as `iPhone Distribution`.
374    AppleDeveloperCertificateSubmission,
375
376    /// Safari Developer.
377    SafariDeveloper,
378
379    /// Apple iPhone OS VPN Signing.
380    IPhoneOsVpnSigning,
381
382    /// Apple Mac App Signing (Development).
383    ///
384    /// Also known as `3rd Party Mac Developer Application`.
385    AppleMacAppSigningDevelopment,
386
387    /// Apple Mac App Signing Submission.
388    AppleMacAppSigningSubmission,
389
390    /// Mac App Store Code Signing.
391    AppleMacAppStoreCodeSigning,
392
393    /// Mac App Store Installer Signing.
394    AppleMacAppStoreInstallerSigning,
395
396    /// Mac Developer.
397    MacDeveloper,
398
399    /// Developer ID Application.
400    DeveloperIdApplication,
401
402    /// Developer ID Date.
403    DeveloperIdDate,
404
405    /// Developer ID Installer.
406    DeveloperIdInstaller,
407
408    /// Apple Pay Passbook Signing.
409    ApplePayPassbookSigning,
410
411    /// Web Site Push Notifications Signing.
412    WebsitePushNotificationSigning,
413
414    /// Developer ID Kernel.
415    DeveloperIdKernel,
416
417    /// TestFlight.
418    TestFlight,
419}
420
421impl CodeSigningCertificateExtension {
422    /// Obtain all variants of this enumeration.
423    pub fn all() -> Vec<Self> {
424        vec![
425            Self::AppleSigning,
426            Self::IPhoneDeveloper,
427            Self::IPhoneOsApplicationSigning,
428            Self::AppleDeveloperCertificateSubmission,
429            Self::SafariDeveloper,
430            Self::IPhoneOsVpnSigning,
431            Self::AppleMacAppSigningDevelopment,
432            Self::AppleMacAppSigningSubmission,
433            Self::AppleMacAppStoreCodeSigning,
434            Self::AppleMacAppStoreInstallerSigning,
435            Self::MacDeveloper,
436            Self::DeveloperIdApplication,
437            Self::DeveloperIdDate,
438            Self::DeveloperIdInstaller,
439            Self::ApplePayPassbookSigning,
440            Self::WebsitePushNotificationSigning,
441            Self::DeveloperIdKernel,
442            Self::TestFlight,
443        ]
444    }
445
446    /// All OIDs known to be extensions in code signing certificates.
447    pub fn all_oids() -> &'static [&'static ConstOid] {
448        ALL_OID_NON_CA_EXTENSIONS
449    }
450
451    pub fn as_oid(&self) -> ConstOid {
452        match self {
453            Self::AppleSigning => OID_EXTENSION_APPLE_SIGNING,
454            Self::IPhoneDeveloper => OID_EXTENSION_IPHONE_DEVELOPER,
455            Self::IPhoneOsApplicationSigning => OID_EXTENSION_IPHONE_OS_APPLICATION_SIGNING,
456            Self::AppleDeveloperCertificateSubmission => {
457                OID_EXTENSION_APPLE_DEVELOPER_CERTIFICATE_SUBMISSION
458            }
459            Self::SafariDeveloper => OID_EXTENSION_SAFARI_DEVELOPER,
460            Self::IPhoneOsVpnSigning => OID_EXTENSION_IPHONE_OS_VPN_SIGNING,
461            Self::AppleMacAppSigningDevelopment => OID_EXTENSION_APPLE_MAC_APP_SIGNING_DEVELOPMENT,
462            Self::AppleMacAppSigningSubmission => OID_EXTENSION_APPLE_MAC_APP_SIGNING_SUBMISSION,
463            Self::AppleMacAppStoreCodeSigning => OID_EXTENSION_APPLE_MAC_APP_STORE_CODE_SIGNING,
464            Self::AppleMacAppStoreInstallerSigning => {
465                OID_EXTENSION_APPLE_MAC_APP_STORE_INSTALLER_SIGNING
466            }
467            Self::MacDeveloper => OID_EXTENSION_MAC_DEVELOPER,
468            Self::DeveloperIdApplication => OID_EXTENSION_DEVELOPER_ID_APPLICATION,
469            Self::DeveloperIdDate => OID_EXTENSION_DEVELOPER_ID_DATE,
470            Self::DeveloperIdInstaller => OID_EXTENSION_DEVELOPER_ID_INSTALLER,
471            Self::ApplePayPassbookSigning => OID_EXTENSION_PASSBOOK_SIGNING,
472            Self::WebsitePushNotificationSigning => OID_EXTENSION_WEBSITE_PUSH_NOTIFICATION_SIGNING,
473            Self::DeveloperIdKernel => OID_EXTENSION_DEVELOPER_ID_KERNEL,
474            Self::TestFlight => OID_EXTENSION_TEST_FLIGHT,
475        }
476    }
477}
478
479impl Display for CodeSigningCertificateExtension {
480    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
481        match self {
482            CodeSigningCertificateExtension::AppleSigning => f.write_str("Apple Signing"),
483            CodeSigningCertificateExtension::IPhoneDeveloper => f.write_str("iPhone Developer"),
484            CodeSigningCertificateExtension::IPhoneOsApplicationSigning => {
485                f.write_str("Apple iPhone OS Application Signing")
486            }
487            CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission => {
488                f.write_str("Apple Developer Certificate (Submission)")
489            }
490            CodeSigningCertificateExtension::SafariDeveloper => f.write_str("Safari Developer"),
491            CodeSigningCertificateExtension::IPhoneOsVpnSigning => {
492                f.write_str("Apple iPhone OS VPN Signing")
493            }
494            CodeSigningCertificateExtension::AppleMacAppSigningDevelopment => {
495                f.write_str("Apple Mac App Signing (Development)")
496            }
497            CodeSigningCertificateExtension::AppleMacAppSigningSubmission => {
498                f.write_str("Apple Mac App Signing Submission")
499            }
500            CodeSigningCertificateExtension::AppleMacAppStoreCodeSigning => {
501                f.write_str("Mac App Store Code Signing")
502            }
503            CodeSigningCertificateExtension::AppleMacAppStoreInstallerSigning => {
504                f.write_str("Mac App Store Installer Signing")
505            }
506            CodeSigningCertificateExtension::MacDeveloper => f.write_str("Mac Developer"),
507            CodeSigningCertificateExtension::DeveloperIdApplication => {
508                f.write_str("Developer ID Application")
509            }
510            CodeSigningCertificateExtension::DeveloperIdDate => f.write_str("Developer ID Date"),
511            CodeSigningCertificateExtension::DeveloperIdInstaller => {
512                f.write_str("Developer ID Installer")
513            }
514            CodeSigningCertificateExtension::ApplePayPassbookSigning => {
515                f.write_str("Apple Pay Passbook Signing")
516            }
517            CodeSigningCertificateExtension::WebsitePushNotificationSigning => {
518                f.write_str("Web Site Push Notifications Signing")
519            }
520            CodeSigningCertificateExtension::DeveloperIdKernel => {
521                f.write_str("Developer ID Kernel")
522            }
523            CodeSigningCertificateExtension::TestFlight => f.write_str("TestFlight"),
524        }
525    }
526}
527
528impl TryFrom<&Oid> for CodeSigningCertificateExtension {
529    type Error = AppleCodesignError;
530
531    fn try_from(oid: &Oid) -> Result<Self, Self::Error> {
532        // Surely there is a way to use `match`. But the `Oid` type is a bit wonky.
533        let o = oid.as_ref();
534
535        if o == OID_EXTENSION_APPLE_SIGNING.as_ref() {
536            Ok(Self::AppleSigning)
537        } else if o == OID_EXTENSION_IPHONE_DEVELOPER.as_ref() {
538            Ok(Self::IPhoneDeveloper)
539        } else if o == OID_EXTENSION_IPHONE_OS_APPLICATION_SIGNING.as_ref() {
540            Ok(Self::IPhoneOsApplicationSigning)
541        } else if o == OID_EXTENSION_APPLE_DEVELOPER_CERTIFICATE_SUBMISSION.as_ref() {
542            Ok(Self::AppleDeveloperCertificateSubmission)
543        } else if o == OID_EXTENSION_SAFARI_DEVELOPER.as_ref() {
544            Ok(Self::SafariDeveloper)
545        } else if o == OID_EXTENSION_IPHONE_OS_VPN_SIGNING.as_ref() {
546            Ok(Self::IPhoneOsVpnSigning)
547        } else if o == OID_EXTENSION_APPLE_MAC_APP_SIGNING_DEVELOPMENT.as_ref() {
548            Ok(Self::AppleMacAppSigningDevelopment)
549        } else if o == OID_EXTENSION_APPLE_MAC_APP_SIGNING_SUBMISSION.as_ref() {
550            Ok(Self::AppleMacAppSigningSubmission)
551        } else if o == OID_EXTENSION_APPLE_MAC_APP_STORE_CODE_SIGNING.as_ref() {
552            Ok(Self::AppleMacAppStoreCodeSigning)
553        } else if o == OID_EXTENSION_APPLE_MAC_APP_STORE_INSTALLER_SIGNING.as_ref() {
554            Ok(Self::AppleMacAppStoreInstallerSigning)
555        } else if o == OID_EXTENSION_MAC_DEVELOPER.as_ref() {
556            Ok(Self::MacDeveloper)
557        } else if o == OID_EXTENSION_DEVELOPER_ID_APPLICATION.as_ref() {
558            Ok(Self::DeveloperIdApplication)
559        } else if o == OID_EXTENSION_DEVELOPER_ID_INSTALLER.as_ref() {
560            Ok(Self::DeveloperIdInstaller)
561        } else if o == OID_EXTENSION_PASSBOOK_SIGNING.as_ref() {
562            Ok(Self::ApplePayPassbookSigning)
563        } else if o == OID_EXTENSION_WEBSITE_PUSH_NOTIFICATION_SIGNING.as_ref() {
564            Ok(Self::WebsitePushNotificationSigning)
565        } else if o == OID_EXTENSION_DEVELOPER_ID_KERNEL.as_ref() {
566            Ok(Self::DeveloperIdKernel)
567        } else if o == OID_EXTENSION_DEVELOPER_ID_DATE.as_ref() {
568            Ok(Self::DeveloperIdDate)
569        } else if o == OID_EXTENSION_TEST_FLIGHT.as_ref() {
570            Ok(Self::TestFlight)
571        } else {
572            Err(AppleCodesignError::OidIsntCodeSigningExtension)
573        }
574    }
575}
576
577/// Denotes specific certificate extensions on Apple certificate authority certificates.
578///
579/// Apple's CA certificates have extensions that appear to identify the role of
580/// that CA. This enumeration defines those.
581#[derive(Clone, Copy, Debug, Eq, PartialEq)]
582pub enum CertificateAuthorityExtension {
583    /// Apple Worldwide Developer Relations.
584    ///
585    /// An intermediate CA.
586    AppleWorldwideDeveloperRelations,
587
588    /// Apple Application Integration.
589    AppleApplicationIntegration,
590
591    /// Developer ID Certification Authority.
592    DeveloperId,
593
594    /// Apple Timestamp.
595    AppleTimestamp,
596
597    /// Developer Authentication Certification Authority.
598    DeveloperAuthentication,
599
600    /// Application Application Integration CA - G3.
601    AppleApplicationIntegrationG3,
602
603    /// Apple Worldwide Developer Relations CA - G2.
604    AppleWorldwideDeveloperRelationsG2,
605
606    /// Apple Software Update Certification.
607    AppleSoftwareUpdateCertification,
608
609    /// Apple Application Integration CA - G1.
610    AppleApplicationIntegrationG1,
611}
612
613impl CertificateAuthorityExtension {
614    /// Obtain all variants of this enumeration.
615    pub fn all() -> Vec<Self> {
616        vec![
617            Self::AppleWorldwideDeveloperRelations,
618            Self::AppleApplicationIntegration,
619            Self::DeveloperId,
620            Self::AppleTimestamp,
621            Self::DeveloperAuthentication,
622            Self::AppleApplicationIntegrationG3,
623            Self::AppleWorldwideDeveloperRelationsG2,
624            Self::AppleSoftwareUpdateCertification,
625            Self::AppleApplicationIntegrationG1,
626        ]
627    }
628
629    /// All the known OIDs constituting Apple CA extensions.
630    pub fn all_oids() -> &'static [&'static ConstOid] {
631        ALL_OID_CA_EXTENSIONS
632    }
633
634    pub fn as_oid(&self) -> ConstOid {
635        match self {
636            Self::AppleWorldwideDeveloperRelations => {
637                OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS
638            }
639            Self::AppleApplicationIntegration => OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION,
640            Self::DeveloperId => OID_CA_EXTENSION_DEVELOPER_ID,
641            Self::AppleTimestamp => OID_CA_EXTENSION_APPLE_TIMESTAMP,
642            Self::DeveloperAuthentication => OID_CA_EXTENSION_DEVELOPER_AUTHENTICATION,
643            Self::AppleApplicationIntegrationG3 => {
644                OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G3
645            }
646            Self::AppleWorldwideDeveloperRelationsG2 => {
647                OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS_G2
648            }
649            Self::AppleSoftwareUpdateCertification => {
650                OID_CA_EXTENSION_APPLE_SOFTWARE_UPDATE_CERTIFICATION
651            }
652            Self::AppleApplicationIntegrationG1 => {
653                OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G1
654            }
655        }
656    }
657}
658
659impl Display for CertificateAuthorityExtension {
660    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
661        match self {
662            CertificateAuthorityExtension::AppleWorldwideDeveloperRelations => {
663                f.write_str("Apple Worldwide Developer Relations")
664            }
665            CertificateAuthorityExtension::AppleApplicationIntegration => {
666                f.write_str("Apple Application Integration")
667            }
668            CertificateAuthorityExtension::DeveloperId => {
669                f.write_str("Developer ID Certification Authority")
670            }
671            CertificateAuthorityExtension::AppleTimestamp => f.write_str("Apple Timestamp"),
672            CertificateAuthorityExtension::DeveloperAuthentication => {
673                f.write_str("Developer Authentication Certification Authority")
674            }
675            CertificateAuthorityExtension::AppleApplicationIntegrationG3 => {
676                f.write_str("Apple Application Integration CA - G3")
677            }
678            CertificateAuthorityExtension::AppleWorldwideDeveloperRelationsG2 => {
679                f.write_str("Apple Worldwide Developer Relations CA - G2")
680            }
681            CertificateAuthorityExtension::AppleSoftwareUpdateCertification => {
682                f.write_str("Apple Software Update Certification")
683            }
684            CertificateAuthorityExtension::AppleApplicationIntegrationG1 => {
685                f.write_str("Apple Application Integration CA - G1")
686            }
687        }
688    }
689}
690
691impl TryFrom<&Oid> for CertificateAuthorityExtension {
692    type Error = AppleCodesignError;
693
694    fn try_from(oid: &Oid) -> Result<Self, Self::Error> {
695        // Surely there is a way to use `match`. But the `Oid` type is a bit wonky.
696        if oid.as_ref() == OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS.as_ref() {
697            Ok(Self::AppleWorldwideDeveloperRelations)
698        } else if oid.as_ref() == OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION.as_ref() {
699            Ok(Self::AppleApplicationIntegration)
700        } else if oid.as_ref() == OID_CA_EXTENSION_DEVELOPER_ID.as_ref() {
701            Ok(Self::DeveloperId)
702        } else if oid.as_ref() == OID_CA_EXTENSION_APPLE_TIMESTAMP.as_ref() {
703            Ok(Self::AppleTimestamp)
704        } else if oid.as_ref() == OID_CA_EXTENSION_DEVELOPER_AUTHENTICATION.as_ref() {
705            Ok(Self::DeveloperAuthentication)
706        } else if oid.as_ref() == OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G3.as_ref() {
707            Ok(Self::AppleApplicationIntegrationG3)
708        } else if oid.as_ref() == OID_CA_EXTENSION_APPLE_WORLDWIDE_DEVELOPER_RELATIONS_G2.as_ref() {
709            Ok(Self::AppleWorldwideDeveloperRelationsG2)
710        } else if oid.as_ref() == OID_CA_EXTENSION_APPLE_SOFTWARE_UPDATE_CERTIFICATION.as_ref() {
711            Ok(Self::AppleSoftwareUpdateCertification)
712        } else if oid.as_ref() == OID_CA_EXTENSION_APPLE_APPLICATION_INTEGRATION_G1.as_ref() {
713            Ok(Self::AppleApplicationIntegrationG1)
714        } else {
715            Err(AppleCodesignError::OidIsntCertificateAuthority)
716        }
717    }
718}
719
720/// Describes combinations of certificate extensions for Apple code signing certificates.
721///
722/// Code signing certificates contain various X.509 extensions denoting them for
723/// code signing.
724///
725/// This type represents various common extensions as used on Apple platforms.
726///
727/// Typically, you'll want to apply at most one of these extensions to a
728/// new certificate in order to mark it as compatible for code signing.
729///
730/// This type essentially encapsulates the logic for handling of different
731/// "profiles" attached to the different code signing certificates that Apple
732/// issues.
733#[derive(Clone, Copy, Debug, Eq, PartialEq)]
734pub enum CertificateProfile {
735    /// Mac Installer Distribution.
736    ///
737    /// In `Keychain Access.app`, this might render as `3rd Party Mac Developer Installer`.
738    ///
739    /// Certificates are marked for EKU with `3rd Party Developer Installer Package
740    /// Signing`.
741    ///
742    /// They also have the `Apple Mac App Signing (Submission)` extension.
743    ///
744    /// Typically issued by `Apple Worldwide Developer Relations Certificate
745    /// Authority`.
746    MacInstallerDistribution,
747
748    /// Apple Distribution.
749    ///
750    /// Certificates are marked for EKU with `Code Signing`. They also have
751    /// extensions `Apple Mac App Signing (Development)` and
752    /// `Apple Developer Certificate (Submission)`.
753    ///
754    /// Typically issued by `Apple Worldwide Developer Relations Certificate Authority`.
755    AppleDistribution,
756
757    /// Apple Development.
758    ///
759    /// Certificates are marked for EKU with `Code Signing`. They also have
760    /// extensions `Apple Developer Certificate (Development)` and
761    /// `Mac Developer`.
762    ///
763    /// Typically issued by `Apple Worldwide Developer Relations Certificate
764    /// Authority`.
765    AppleDevelopment,
766
767    /// Developer ID Application.
768    ///
769    /// Certificates are marked for EKU with `Code Signing`. They also have
770    /// extensions for `Developer ID Application` and `Developer ID Date`.
771    DeveloperIdApplication,
772
773    /// Developer ID Installer.
774    ///
775    /// Certificates are marked for EKU with `Developer ID Application`. They also
776    /// have extensions `Developer ID Installer` and `Developer ID Date`.
777    DeveloperIdInstaller,
778}
779
780impl CertificateProfile {
781    pub fn all() -> &'static [Self] {
782        &[
783            Self::MacInstallerDistribution,
784            Self::AppleDistribution,
785            Self::AppleDevelopment,
786            Self::DeveloperIdApplication,
787            Self::DeveloperIdInstaller,
788        ]
789    }
790
791    /// Obtain the string values that variants are recognized as.
792    pub fn str_names() -> [&'static str; 5] {
793        [
794            "mac-installer-distribution",
795            "apple-distribution",
796            "apple-development",
797            "developer-id-application",
798            "developer-id-installer",
799        ]
800    }
801}
802
803impl Display for CertificateProfile {
804    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
805        match self {
806            CertificateProfile::MacInstallerDistribution => {
807                f.write_str("mac-installer-distribution")
808            }
809            CertificateProfile::AppleDistribution => f.write_str("apple-distribution"),
810            CertificateProfile::AppleDevelopment => f.write_str("apple-development"),
811            CertificateProfile::DeveloperIdApplication => f.write_str("developer-id-application"),
812            CertificateProfile::DeveloperIdInstaller => f.write_str("developer-id-installer"),
813        }
814    }
815}
816
817impl FromStr for CertificateProfile {
818    type Err = AppleCodesignError;
819
820    fn from_str(s: &str) -> Result<Self, Self::Err> {
821        match s {
822            "apple-distribution" => Ok(Self::AppleDistribution),
823            "apple-development" => Ok(Self::AppleDevelopment),
824            "developer-id-application" => Ok(Self::DeveloperIdApplication),
825            "developer-id-installer" => Ok(Self::DeveloperIdInstaller),
826            "mac-installer-distribution" => Ok(Self::MacInstallerDistribution),
827            _ => Err(AppleCodesignError::UnknownCertificateProfile(s.to_string())),
828        }
829    }
830}
831
832/// Extends functionality of [CapturedX509Certificate] with Apple specific certificate knowledge.
833pub trait AppleCertificate: Sized {
834    /// Whether this is a known Apple root certificate authority.
835    ///
836    /// We define this criteria as a certificate in our built-in list of known
837    /// Apple certificates that has the same subject and issuer Names.
838    fn is_apple_root_ca(&self) -> bool;
839
840    /// Whether this is a known Apple intermediate certificate authority.
841    ///
842    /// This is similar to [Self::is_apple_root_ca] except it doesn't match against
843    /// known self-signed Apple certificates.
844    fn is_apple_intermediate_ca(&self) -> bool;
845
846    /// Find [CertificateAuthorityExtension] present on this certificate.
847    ///
848    /// If this is non-empty, the certificate says it is an Apple certificate
849    /// whose role is issuing other certificates using for signing things.
850    ///
851    /// This function does not perform trust validation that the underlying
852    /// certificate is a legitimate Apple issued certificate: just that it has
853    /// the desired property.
854    fn apple_ca_extensions(&self) -> Vec<CertificateAuthorityExtension>;
855
856    /// Obtain all of Apple's [ExtendedKeyUsagePurpose] in this certificate.
857    fn apple_extended_key_usage_purposes(&self) -> Vec<ExtendedKeyUsagePurpose>;
858
859    /// Obtain all of Apple's [CodeSigningCertificateExtension] in this certificate.
860    fn apple_code_signing_extensions(&self) -> Vec<CodeSigningCertificateExtension>;
861
862    /// Attempt to guess the [CertificateProfile] associated with this certificate.
863    ///
864    /// This keys off present certificate extensions to guess which profile it
865    /// belongs to. Incorrect guesses are possible, which is why *guess* is in the
866    /// function name.
867    ///
868    /// Returns `None` if we don't think a [CertificateProfile] is associated with
869    /// this extension.
870    fn apple_guess_profile(&self) -> Option<CertificateProfile>;
871
872    /// Attempt to resolve the certificate issuer chain back to [AppleCertificate].
873    ///
874    /// This is a glorified wrapper around [CapturedX509Certificate::resolve_signing_chain]
875    /// that filters matches against certificates in our known set of Apple
876    /// certificates and maps them back to our [KnownCertificate] Rust enumeration.
877    ///
878    /// False negatives (read: missing certificates) can be encountered if
879    /// we don't know about an Apple CA certificate.
880    fn apple_issuing_chain(&self) -> Vec<KnownCertificate>;
881
882    /// Whether this certificate chains back to a known Apple root certificate authority.
883    ///
884    /// This is true if the resolved certificate issuance chain (which is
885    /// confirmed via verifying the cryptographic signatures on certificates)
886    /// ands in a certificate that is known to be an Apple root CA.
887    fn chains_to_apple_root_ca(&self) -> bool;
888
889    /// Obtain the chain of issuing certificates, back to a known Apple root.
890    ///
891    /// The returned chain starts with this certificate and ends with a known
892    /// Apple root certificate authority. None is returned if this certificate
893    /// doesn't appear to chain to a known Apple root CA.
894    fn apple_root_certificate_chain(&self) -> Option<Vec<CapturedX509Certificate>>;
895
896    /// Attempt to resolve the *team id* of an Apple issued certificate.
897    ///
898    /// The *team id* is a value like `AB42XYZ789` that is attached to your
899    /// Apple Developer account. It seems to always be embedded in signing
900    /// certificates as the Organizational Unit field of the subject. So this
901    /// function is just a shortcut for retrieving that.
902    fn apple_team_id(&self) -> Option<String>;
903
904    /// Whether this is a certificate pretending to be signed by an Apple CA but isn't really.
905    fn is_test_apple_signed_certificate(&self) -> bool;
906}
907
908impl AppleCertificate for CapturedX509Certificate {
909    fn is_apple_root_ca(&self) -> bool {
910        KnownCertificate::all_roots().contains(&self)
911    }
912
913    fn is_apple_intermediate_ca(&self) -> bool {
914        KnownCertificate::all().contains(&self) && !KnownCertificate::all_roots().contains(&self)
915    }
916
917    fn apple_ca_extensions(&self) -> Vec<CertificateAuthorityExtension> {
918        let cert: &x509_certificate::rfc5280::Certificate = self.as_ref();
919
920        cert.iter_extensions()
921            .filter_map(|extension| CertificateAuthorityExtension::try_from(&extension.id).ok())
922            .collect::<Vec<_>>()
923    }
924
925    fn apple_extended_key_usage_purposes(&self) -> Vec<ExtendedKeyUsagePurpose> {
926        let cert: &x509_certificate::rfc5280::Certificate = self.as_ref();
927
928        cert.iter_extensions()
929            .filter_map(|extension| {
930                if extension.id.as_ref() == OID_EXTENDED_KEY_USAGE.as_ref() {
931                    if let Some(oid) = extension.try_decode_sequence_single_oid() {
932                        if let Ok(purpose) = ExtendedKeyUsagePurpose::try_from(&oid) {
933                            Some(purpose)
934                        } else {
935                            None
936                        }
937                    } else {
938                        None
939                    }
940                } else {
941                    None
942                }
943            })
944            .collect::<Vec<_>>()
945    }
946
947    fn apple_code_signing_extensions(&self) -> Vec<CodeSigningCertificateExtension> {
948        let cert: &x509_certificate::rfc5280::Certificate = self.as_ref();
949
950        cert.iter_extensions()
951            .filter_map(|extension| {
952                if let Ok(value) = CodeSigningCertificateExtension::try_from(&extension.id) {
953                    Some(value)
954                } else {
955                    None
956                }
957            })
958            .collect::<Vec<_>>()
959    }
960
961    fn apple_guess_profile(&self) -> Option<CertificateProfile> {
962        let ekus = self.apple_extended_key_usage_purposes();
963        let signing = self.apple_code_signing_extensions();
964
965        // Some EKUs uniquely identify the certificate profile. We don't yet handle
966        // all EKUs because we don't have profiles defined for them.
967        //
968        // Ideally this logic stays in sync with apple_certificate_profile().
969        if ekus.contains(&ExtendedKeyUsagePurpose::DeveloperIdInstaller) {
970            Some(CertificateProfile::DeveloperIdInstaller)
971        } else if ekus.contains(&ExtendedKeyUsagePurpose::ThirdPartyMacDeveloperInstaller) {
972            Some(CertificateProfile::MacInstallerDistribution)
973            // That's all the EKUs that have a 1:1 to CertificateProfile. Now look at
974            // code signing extensions.
975        } else if signing.contains(&CodeSigningCertificateExtension::DeveloperIdApplication) {
976            Some(CertificateProfile::DeveloperIdApplication)
977        } else if signing.contains(&CodeSigningCertificateExtension::IPhoneDeveloper)
978            && signing.contains(&CodeSigningCertificateExtension::MacDeveloper)
979        {
980            Some(CertificateProfile::AppleDevelopment)
981        } else if signing.contains(&CodeSigningCertificateExtension::AppleMacAppSigningDevelopment)
982            && signing
983                .contains(&CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission)
984        {
985            Some(CertificateProfile::AppleDistribution)
986        } else {
987            None
988        }
989    }
990
991    fn apple_issuing_chain(&self) -> Vec<KnownCertificate> {
992        self.resolve_signing_chain(KnownCertificate::all().iter().copied())
993            .into_iter()
994            .filter_map(|cert| KnownCertificate::try_from(cert).ok())
995            .collect::<Vec<_>>()
996    }
997
998    fn chains_to_apple_root_ca(&self) -> bool {
999        if self.is_apple_root_ca() {
1000            true
1001        } else {
1002            self.resolve_signing_chain(KnownCertificate::all().iter().copied())
1003                .into_iter()
1004                .any(|cert| cert.is_apple_root_ca())
1005        }
1006    }
1007
1008    fn apple_root_certificate_chain(&self) -> Option<Vec<CapturedX509Certificate>> {
1009        let mut chain = vec![self.clone()];
1010
1011        for cert in self.resolve_signing_chain(KnownCertificate::all().iter().copied()) {
1012            chain.push(cert.clone());
1013
1014            if cert.is_apple_root_ca() {
1015                break;
1016            }
1017        }
1018
1019        if chain.last().unwrap().is_apple_root_ca() {
1020            Some(chain)
1021        } else {
1022            None
1023        }
1024    }
1025
1026    fn apple_team_id(&self) -> Option<String> {
1027        self.subject_name()
1028            .find_first_attribute_string(Oid(
1029                x509_certificate::rfc4519::OID_ORGANIZATIONAL_UNIT_NAME
1030                    .as_ref()
1031                    .into(),
1032            ))
1033            .unwrap_or(None)
1034    }
1035
1036    fn is_test_apple_signed_certificate(&self) -> bool {
1037        if let Ok(digest) = self.sha256_fingerprint() {
1038            hex::encode(digest)
1039                == "5939ad5770d8b977b38d07533754371314744e87a8d606433f689e9bc6b980a0"
1040        } else {
1041            false
1042        }
1043    }
1044}
1045
1046/// Extensions to [X509CertificateBuilder] specializing in Apple certificate behavior.
1047///
1048/// Most callers should call [Self::apple_certificate_profile] to configure
1049/// a preset profile for the certificate being generated. After that - and it is
1050/// important it is after - call [Self::apple_subject] to define the subject
1051/// field. If you call this after registering code signing extensions, it
1052/// detects the appropriate format for the Common Name field.
1053pub trait AppleCertificateBuilder: Sized {
1054    /// This functions defines common attributes on the certificate subject.
1055    ///
1056    /// `team_id` is your Apple team id. It is a short alphanumeric string. You
1057    /// can find this at <https://developer.apple.com/account/#/membership/>.
1058    fn apple_subject(
1059        &mut self,
1060        team_id: &str,
1061        person_name: &str,
1062        country: &str,
1063    ) -> Result<(), AppleCodesignError>;
1064
1065    /// Add an email address to the certificate's subject name.
1066    fn apple_email_address(&mut self, address: &str) -> Result<(), AppleCodesignError>;
1067
1068    /// Add an [ExtendedKeyUsagePurpose] to this certificate.
1069    fn apple_extended_key_usage(
1070        &mut self,
1071        usage: ExtendedKeyUsagePurpose,
1072    ) -> Result<(), AppleCodesignError>;
1073
1074    /// Add a certificate extension as defined by a [CodeSigningCertificateExtension] instance.
1075    fn apple_code_signing_certificate_extension(
1076        &mut self,
1077        extension: CodeSigningCertificateExtension,
1078    ) -> Result<(), AppleCodesignError>;
1079
1080    /// Add a [CertificateProfile] to this builder.
1081    ///
1082    /// All certificate extensions relevant to this profile are added.
1083    ///
1084    /// This should be the first function you call after creating an instance
1085    /// because other functions rely on the state that it sets.
1086    fn apple_certificate_profile(
1087        &mut self,
1088        profile: CertificateProfile,
1089    ) -> Result<(), AppleCodesignError>;
1090
1091    /// Find code signing extensions that are currently registered.
1092    fn apple_code_signing_extensions(&self) -> Vec<CodeSigningCertificateExtension>;
1093}
1094
1095impl AppleCertificateBuilder for X509CertificateBuilder {
1096    fn apple_subject(
1097        &mut self,
1098        team_id: &str,
1099        person_name: &str,
1100        country: &str,
1101    ) -> Result<(), AppleCodesignError> {
1102        // TODO the subject schema here isn't totally accurate. While OU does always
1103        // appear to be the team id, the user id attribute can be something else.
1104        // For example, for Apple Development, there are a similarly formatted yet
1105        // different value. But the team id does still appear.
1106        self.subject()
1107            .append_utf8_string(Oid(OID_USER_ID.as_ref().into()), team_id)
1108            .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?;
1109
1110        // Common Name is derived from the profile in use.
1111
1112        let extensions = self.apple_code_signing_extensions();
1113
1114        let common_name =
1115            if extensions.contains(&CodeSigningCertificateExtension::DeveloperIdApplication) {
1116                format!("Developer ID Application: {person_name} ({team_id})")
1117            } else if extensions.contains(&CodeSigningCertificateExtension::DeveloperIdInstaller) {
1118                format!("Developer ID Installer: {person_name} ({team_id})")
1119            } else if extensions
1120                .contains(&CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission)
1121            {
1122                format!("Apple Distribution: {person_name} ({team_id})")
1123            } else if extensions
1124                .contains(&CodeSigningCertificateExtension::AppleMacAppSigningSubmission)
1125            {
1126                format!("3rd Party Mac Developer Installer: {person_name} ({team_id})")
1127            } else if extensions.contains(&CodeSigningCertificateExtension::MacDeveloper) {
1128                format!("Apple Development: {person_name} ({team_id})")
1129            } else {
1130                format!("{person_name} ({team_id})")
1131            };
1132
1133        self.subject()
1134            .append_common_name_utf8_string(&common_name)
1135            .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?;
1136
1137        self.subject()
1138            .append_organizational_unit_utf8_string(team_id)
1139            .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?;
1140
1141        self.subject()
1142            .append_organization_utf8_string(person_name)
1143            .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?;
1144
1145        self.subject()
1146            .append_printable_string(Oid(OID_COUNTRY_NAME.as_ref().into()), country)
1147            .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?;
1148
1149        Ok(())
1150    }
1151
1152    fn apple_email_address(&mut self, address: &str) -> Result<(), AppleCodesignError> {
1153        self.subject()
1154            .append_utf8_string(Oid(OID_EMAIL_ADDRESS.as_ref().into()), address)
1155            .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?;
1156
1157        Ok(())
1158    }
1159
1160    fn apple_extended_key_usage(
1161        &mut self,
1162        usage: ExtendedKeyUsagePurpose,
1163    ) -> Result<(), AppleCodesignError> {
1164        let payload =
1165            bcder::encode::sequence(Oid(Bytes::copy_from_slice(usage.as_oid().as_ref())).encode())
1166                .to_captured(bcder::Mode::Der);
1167
1168        self.add_extension_der_data(
1169            Oid(OID_EXTENDED_KEY_USAGE.as_ref().into()),
1170            true,
1171            payload.as_slice(),
1172        );
1173
1174        Ok(())
1175    }
1176
1177    fn apple_code_signing_certificate_extension(
1178        &mut self,
1179        extension: CodeSigningCertificateExtension,
1180    ) -> Result<(), AppleCodesignError> {
1181        let (critical, payload) = match extension {
1182            CodeSigningCertificateExtension::IPhoneDeveloper => {
1183                // SEQUENCE (3 elem)
1184                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.2
1185                //   BOOLEAN true
1186                //   OCTET STRING (2 byte) 0500
1187                //     NULL
1188                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1189            }
1190            CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission => {
1191                // SEQUENCE (3 elem)
1192                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.4
1193                //   BOOLEAN true
1194                //   OCTET STRING (2 byte) 0500
1195                //     NULL
1196                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1197            }
1198            CodeSigningCertificateExtension::AppleMacAppSigningDevelopment => {
1199                // SEQUENCE (3 elem)
1200                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.7
1201                //   BOOLEAN true
1202                //   OCTET STRING (2 byte) 0500
1203                //     NULL
1204                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1205            }
1206            CodeSigningCertificateExtension::AppleMacAppSigningSubmission => {
1207                // SEQUENCE (3 elem)
1208                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.8
1209                //   BOOLEAN true
1210                //   OCTET STRING (2 byte) 0500
1211                //   NULL
1212                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1213            }
1214            CodeSigningCertificateExtension::MacDeveloper => {
1215                // SEQUENCE (3 elem)
1216                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.12
1217                //   BOOLEAN true
1218                //   OCTET STRING (2 byte) 0500
1219                //     NULL
1220                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1221            }
1222            CodeSigningCertificateExtension::DeveloperIdApplication => {
1223                // SEQUENCE (3 elem)
1224                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.13
1225                //   BOOLEAN true
1226                //   OCTET STRING (2 byte) 0500
1227                //     NULL
1228                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1229            }
1230            CodeSigningCertificateExtension::DeveloperIdInstaller => {
1231                // SEQUENCE (3 elem)
1232                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.14
1233                //   BOOLEAN true
1234                //   OCTET STRING (2 byte) 0500
1235                //   NULL
1236                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1237            }
1238
1239            // The rest of these probably have the same payload. But until we see
1240            // them, don't take chances.
1241            _ => {
1242                return Err(AppleCodesignError::CertificateBuildError(format!(
1243                    "don't know how to handle code signing extension {extension:?}"
1244                )));
1245            }
1246        };
1247
1248        self.add_extension_der_data(
1249            Oid(Bytes::copy_from_slice(extension.as_oid().as_ref())),
1250            critical,
1251            payload,
1252        );
1253
1254        Ok(())
1255    }
1256
1257    fn apple_certificate_profile(
1258        &mut self,
1259        profile: CertificateProfile,
1260    ) -> Result<(), AppleCodesignError> {
1261        // Try to keep this logic in sync with apple_guess_profile().
1262        match profile {
1263            CertificateProfile::DeveloperIdApplication => {
1264                self.constraint_not_ca();
1265                self.apple_extended_key_usage(ExtendedKeyUsagePurpose::CodeSigning)?;
1266                self.key_usage(KeyUsage::DigitalSignature);
1267
1268                // OID_EXTENSION_DEVELOPER_ID_DATE comes next. But we don't know what
1269                // that should be. It is a UTF8String instead of an ASN.1 time type
1270                // because who knows.
1271
1272                self.apple_code_signing_certificate_extension(
1273                    CodeSigningCertificateExtension::DeveloperIdApplication,
1274                )?;
1275            }
1276            CertificateProfile::DeveloperIdInstaller => {
1277                self.constraint_not_ca();
1278                self.apple_extended_key_usage(ExtendedKeyUsagePurpose::DeveloperIdInstaller)?;
1279                self.key_usage(KeyUsage::DigitalSignature);
1280
1281                // OID_EXTENSION_DEVELOPER_ID_DATE comes next.
1282
1283                self.apple_code_signing_certificate_extension(
1284                    CodeSigningCertificateExtension::DeveloperIdInstaller,
1285                )?;
1286            }
1287            CertificateProfile::AppleDevelopment => {
1288                self.constraint_not_ca();
1289                self.apple_extended_key_usage(ExtendedKeyUsagePurpose::CodeSigning)?;
1290                self.key_usage(KeyUsage::DigitalSignature);
1291                self.apple_code_signing_certificate_extension(
1292                    CodeSigningCertificateExtension::IPhoneDeveloper,
1293                )?;
1294                self.apple_code_signing_certificate_extension(
1295                    CodeSigningCertificateExtension::MacDeveloper,
1296                )?;
1297            }
1298            CertificateProfile::AppleDistribution => {
1299                self.constraint_not_ca();
1300                self.apple_extended_key_usage(ExtendedKeyUsagePurpose::CodeSigning)?;
1301                self.key_usage(KeyUsage::DigitalSignature);
1302
1303                // OID_EXTENSION_DEVELOPER_ID_DATE comes next.
1304
1305                self.apple_code_signing_certificate_extension(
1306                    CodeSigningCertificateExtension::AppleMacAppSigningDevelopment,
1307                )?;
1308                self.apple_code_signing_certificate_extension(
1309                    CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission,
1310                )?;
1311            }
1312            CertificateProfile::MacInstallerDistribution => {
1313                self.constraint_not_ca();
1314                self.apple_extended_key_usage(
1315                    ExtendedKeyUsagePurpose::ThirdPartyMacDeveloperInstaller,
1316                )?;
1317                self.key_usage(KeyUsage::DigitalSignature);
1318
1319                self.apple_code_signing_certificate_extension(
1320                    CodeSigningCertificateExtension::AppleMacAppSigningSubmission,
1321                )?;
1322            }
1323        }
1324
1325        Ok(())
1326    }
1327
1328    fn apple_code_signing_extensions(&self) -> Vec<CodeSigningCertificateExtension> {
1329        self.extensions()
1330            .iter()
1331            .filter_map(|ext| {
1332                if let Ok(e) = CodeSigningCertificateExtension::try_from(&ext.id) {
1333                    Some(e)
1334                } else {
1335                    None
1336                }
1337            })
1338            .collect::<Vec<_>>()
1339    }
1340}
1341
1342/// Create a new self-signed X.509 certificate suitable for signing code.
1343///
1344/// The created certificate contains all the extensions needed to convey
1345/// that it is used for code signing and should resemble certificates.
1346///
1347/// However, because the certificate isn't signed by Apple or another
1348/// trusted certificate authority, binaries signed with the certificate
1349/// may not pass Apple's verification requirements and the OS may refuse
1350/// to proceed. Needless to say, only use certificates generated with this
1351/// function for testing purposes only.
1352pub fn create_self_signed_code_signing_certificate(
1353    algorithm: KeyAlgorithm,
1354    profile: CertificateProfile,
1355    team_id: &str,
1356    person_name: &str,
1357    country: &str,
1358    validity_duration: chrono::Duration,
1359) -> Result<(CapturedX509Certificate, InMemorySigningKeyPair), AppleCodesignError> {
1360    let mut builder = X509CertificateBuilder::default();
1361
1362    builder.apple_certificate_profile(profile)?;
1363    builder.apple_subject(team_id, person_name, country)?;
1364    builder.validity_duration(validity_duration);
1365
1366    // x509-certificate crate doesn't support RSA key generation. So do
1367    // that ourselves.
1368    if matches!(algorithm, KeyAlgorithm::Rsa) {
1369        let private_key = rsa::RsaPrivateKey::new(&mut rand::thread_rng(), 2048).map_err(|e| {
1370            AppleCodesignError::CertificateBuildError(format!("error generating RSA key: {}", e))
1371        })?;
1372        let key_pair = InMemorySigningKeyPair::from_pkcs8_der(
1373            private_key
1374                .to_pkcs8_der()
1375                .map_err(|e| {
1376                    AppleCodesignError::CertificateGeneric(format!(
1377                        "error converting RSA key to DER: {}",
1378                        e
1379                    ))
1380                })?
1381                .as_bytes(),
1382        )?;
1383
1384        let cert = builder.create_with_key_pair(&key_pair)?;
1385
1386        Ok((cert, key_pair))
1387    } else {
1388        Ok(builder.create_with_random_keypair(algorithm)?)
1389    }
1390}
1391
1392#[cfg(test)]
1393mod tests {
1394    use {
1395        super::*,
1396        cryptographic_message_syntax::{SignedData, SignedDataBuilder, SignerBuilder},
1397        x509_certificate::EcdsaCurve,
1398    };
1399
1400    #[test]
1401    fn generate_self_signed_certificate_ecdsa() {
1402        for curve in EcdsaCurve::all() {
1403            create_self_signed_code_signing_certificate(
1404                KeyAlgorithm::Ecdsa(*curve),
1405                CertificateProfile::DeveloperIdInstaller,
1406                "team1",
1407                "Joe Developer",
1408                "US",
1409                chrono::Duration::hours(1),
1410            )
1411            .unwrap();
1412        }
1413    }
1414
1415    #[test]
1416    fn generate_self_signed_certificate_ed25519() {
1417        create_self_signed_code_signing_certificate(
1418            KeyAlgorithm::Ed25519,
1419            CertificateProfile::DeveloperIdInstaller,
1420            "team2",
1421            "Joe Developer",
1422            "US",
1423            chrono::Duration::hours(1),
1424        )
1425        .unwrap();
1426    }
1427
1428    #[test]
1429    fn generate_all_profiles() {
1430        for profile in CertificateProfile::all() {
1431            create_self_signed_code_signing_certificate(
1432                KeyAlgorithm::Ed25519,
1433                *profile,
1434                "team",
1435                "Joe Developer",
1436                "Wakanda",
1437                chrono::Duration::hours(1),
1438            )
1439            .unwrap();
1440        }
1441    }
1442
1443    #[test]
1444    fn cms_self_signed_certificate_signing_ecdsa() {
1445        for curve in EcdsaCurve::all() {
1446            let (cert, signing_key) = create_self_signed_code_signing_certificate(
1447                KeyAlgorithm::Ecdsa(*curve),
1448                CertificateProfile::DeveloperIdInstaller,
1449                "team",
1450                "Joe Developer",
1451                "US",
1452                chrono::Duration::hours(1),
1453            )
1454            .unwrap();
1455
1456            let plaintext = "hello, world";
1457
1458            let cms = SignedDataBuilder::default()
1459                .certificate(cert.clone())
1460                .content_inline(plaintext.as_bytes().to_vec())
1461                .signer(SignerBuilder::new(&signing_key, cert.clone()))
1462                .build_der()
1463                .unwrap();
1464
1465            let signed_data = SignedData::parse_ber(&cms).unwrap();
1466
1467            for signer in signed_data.signers() {
1468                signer
1469                    .verify_signature_with_signed_data(&signed_data)
1470                    .unwrap();
1471            }
1472        }
1473    }
1474
1475    #[test]
1476    fn cms_self_signed_certificate_signing_ed25519() {
1477        let (cert, signing_key) = create_self_signed_code_signing_certificate(
1478            KeyAlgorithm::Ed25519,
1479            CertificateProfile::DeveloperIdInstaller,
1480            "team",
1481            "Joe Developer",
1482            "US",
1483            chrono::Duration::hours(1),
1484        )
1485        .unwrap();
1486
1487        let plaintext = "hello, world";
1488
1489        let cms = SignedDataBuilder::default()
1490            .certificate(cert.clone())
1491            .content_inline(plaintext.as_bytes().to_vec())
1492            .signer(SignerBuilder::new(&signing_key, cert))
1493            .build_der()
1494            .unwrap();
1495
1496        let signed_data = SignedData::parse_ber(&cms).unwrap();
1497
1498        for signer in signed_data.signers() {
1499            signer
1500                .verify_signature_with_signed_data(&signed_data)
1501                .unwrap();
1502        }
1503    }
1504
1505    #[test]
1506    fn third_mac_mac() {
1507        let der = include_bytes!("testdata/apple-signed-3rd-party-mac.cer");
1508        let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap();
1509
1510        assert_eq!(
1511            cert.apple_extended_key_usage_purposes(),
1512            vec![ExtendedKeyUsagePurpose::ThirdPartyMacDeveloperInstaller]
1513        );
1514        assert_eq!(
1515            cert.apple_code_signing_extensions(),
1516            vec![CodeSigningCertificateExtension::AppleMacAppSigningSubmission]
1517        );
1518        assert_eq!(
1519            cert.apple_guess_profile(),
1520            Some(CertificateProfile::MacInstallerDistribution)
1521        );
1522        assert_eq!(
1523            cert.apple_issuing_chain(),
1524            vec![
1525                KnownCertificate::WwdrG3,
1526                KnownCertificate::AppleRootCa,
1527                KnownCertificate::AppleComputerIncRoot
1528            ]
1529        );
1530        assert!(cert.chains_to_apple_root_ca());
1531        assert_eq!(
1532            cert.apple_root_certificate_chain(),
1533            Some(vec![
1534                cert.clone(),
1535                (*KnownCertificate::WwdrG3).clone(),
1536                (*KnownCertificate::AppleRootCa).clone()
1537            ])
1538        );
1539        assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into()));
1540
1541        let mut builder = X509CertificateBuilder::default();
1542        builder
1543            .apple_certificate_profile(CertificateProfile::MacInstallerDistribution)
1544            .unwrap();
1545
1546        let built = builder
1547            .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1))
1548            .unwrap()
1549            .0;
1550
1551        assert_eq!(
1552            built.apple_extended_key_usage_purposes(),
1553            cert.apple_extended_key_usage_purposes()
1554        );
1555        assert_eq!(
1556            built.apple_code_signing_extensions(),
1557            cert.apple_code_signing_extensions()
1558        );
1559        assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile());
1560        assert_eq!(built.apple_issuing_chain(), vec![]);
1561        assert!(!built.chains_to_apple_root_ca());
1562        assert!(built.apple_root_certificate_chain().is_none());
1563    }
1564
1565    #[test]
1566    fn apple_development() {
1567        let der = include_bytes!("testdata/apple-signed-apple-development.cer");
1568        let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap();
1569
1570        assert_eq!(
1571            cert.apple_extended_key_usage_purposes(),
1572            vec![ExtendedKeyUsagePurpose::CodeSigning]
1573        );
1574        assert_eq!(
1575            cert.apple_code_signing_extensions(),
1576            vec![
1577                CodeSigningCertificateExtension::IPhoneDeveloper,
1578                CodeSigningCertificateExtension::MacDeveloper
1579            ]
1580        );
1581        assert_eq!(
1582            cert.apple_guess_profile(),
1583            Some(CertificateProfile::AppleDevelopment)
1584        );
1585        assert_eq!(
1586            cert.apple_issuing_chain(),
1587            vec![
1588                KnownCertificate::WwdrG3,
1589                KnownCertificate::AppleRootCa,
1590                KnownCertificate::AppleComputerIncRoot
1591            ],
1592        );
1593        assert!(cert.chains_to_apple_root_ca());
1594        assert_eq!(
1595            cert.apple_root_certificate_chain(),
1596            Some(vec![
1597                cert.clone(),
1598                (*KnownCertificate::WwdrG3).clone(),
1599                (*KnownCertificate::AppleRootCa).clone()
1600            ])
1601        );
1602        assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into()));
1603
1604        let mut builder = X509CertificateBuilder::default();
1605        builder
1606            .apple_certificate_profile(CertificateProfile::AppleDevelopment)
1607            .unwrap();
1608
1609        let built = builder
1610            .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1))
1611            .unwrap()
1612            .0;
1613
1614        assert_eq!(
1615            built.apple_extended_key_usage_purposes(),
1616            cert.apple_extended_key_usage_purposes()
1617        );
1618        assert_eq!(
1619            built.apple_code_signing_extensions(),
1620            cert.apple_code_signing_extensions()
1621        );
1622        assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile());
1623        assert_eq!(built.apple_issuing_chain(), vec![]);
1624        assert!(!built.chains_to_apple_root_ca());
1625        assert!(built.apple_root_certificate_chain().is_none());
1626    }
1627
1628    #[test]
1629    fn apple_distribution() {
1630        let der = include_bytes!("testdata/apple-signed-apple-distribution.cer");
1631        let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap();
1632
1633        assert_eq!(
1634            cert.apple_extended_key_usage_purposes(),
1635            vec![ExtendedKeyUsagePurpose::CodeSigning]
1636        );
1637        assert_eq!(
1638            cert.apple_code_signing_extensions(),
1639            vec![
1640                CodeSigningCertificateExtension::AppleMacAppSigningDevelopment,
1641                CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission
1642            ]
1643        );
1644        assert_eq!(
1645            cert.apple_guess_profile(),
1646            Some(CertificateProfile::AppleDistribution)
1647        );
1648        assert_eq!(
1649            cert.apple_issuing_chain(),
1650            vec![
1651                KnownCertificate::WwdrG3,
1652                KnownCertificate::AppleRootCa,
1653                KnownCertificate::AppleComputerIncRoot
1654            ],
1655        );
1656        assert!(cert.chains_to_apple_root_ca());
1657        assert_eq!(
1658            cert.apple_root_certificate_chain(),
1659            Some(vec![
1660                cert.clone(),
1661                (*KnownCertificate::WwdrG3).clone(),
1662                (*KnownCertificate::AppleRootCa).clone()
1663            ])
1664        );
1665        assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into()));
1666
1667        let mut builder = X509CertificateBuilder::default();
1668        builder
1669            .apple_certificate_profile(CertificateProfile::AppleDistribution)
1670            .unwrap();
1671
1672        let built = builder
1673            .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1))
1674            .unwrap()
1675            .0;
1676
1677        assert_eq!(
1678            built.apple_extended_key_usage_purposes(),
1679            cert.apple_extended_key_usage_purposes()
1680        );
1681        assert_eq!(
1682            built.apple_code_signing_extensions(),
1683            cert.apple_code_signing_extensions()
1684        );
1685        assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile());
1686        assert_eq!(built.apple_issuing_chain(), vec![]);
1687        assert!(!built.chains_to_apple_root_ca());
1688        assert!(built.apple_root_certificate_chain().is_none());
1689    }
1690
1691    #[test]
1692    fn apple_developer_id_application() {
1693        let der = include_bytes!("testdata/apple-signed-developer-id-application.cer");
1694        let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap();
1695
1696        assert_eq!(
1697            cert.apple_extended_key_usage_purposes(),
1698            vec![ExtendedKeyUsagePurpose::CodeSigning]
1699        );
1700        assert_eq!(
1701            cert.apple_code_signing_extensions(),
1702            vec![
1703                CodeSigningCertificateExtension::DeveloperIdDate,
1704                CodeSigningCertificateExtension::DeveloperIdApplication
1705            ]
1706        );
1707        assert_eq!(
1708            cert.apple_guess_profile(),
1709            Some(CertificateProfile::DeveloperIdApplication)
1710        );
1711        assert_eq!(
1712            cert.apple_issuing_chain(),
1713            vec![
1714                KnownCertificate::DeveloperIdG1,
1715                KnownCertificate::AppleRootCa,
1716                KnownCertificate::AppleComputerIncRoot
1717            ]
1718        );
1719        assert!(cert.chains_to_apple_root_ca());
1720        assert_eq!(
1721            cert.apple_root_certificate_chain(),
1722            Some(vec![
1723                cert.clone(),
1724                (*KnownCertificate::DeveloperIdG1).clone(),
1725                (*KnownCertificate::AppleRootCa).clone()
1726            ])
1727        );
1728        assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into()));
1729
1730        let mut builder = X509CertificateBuilder::default();
1731        builder
1732            .apple_certificate_profile(CertificateProfile::DeveloperIdApplication)
1733            .unwrap();
1734
1735        let built = builder
1736            .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1))
1737            .unwrap()
1738            .0;
1739
1740        assert_eq!(
1741            built.apple_extended_key_usage_purposes(),
1742            cert.apple_extended_key_usage_purposes()
1743        );
1744        assert_eq!(
1745            built.apple_code_signing_extensions(),
1746            // We don't write out the date extension.
1747            cert.apple_code_signing_extensions()
1748                .into_iter()
1749                .filter(|e| !matches!(e, CodeSigningCertificateExtension::DeveloperIdDate))
1750                .collect::<Vec<_>>()
1751        );
1752        assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile());
1753        assert_eq!(built.apple_issuing_chain(), vec![]);
1754        assert!(!built.chains_to_apple_root_ca());
1755        assert!(built.apple_root_certificate_chain().is_none());
1756    }
1757
1758    #[test]
1759    fn apple_developer_id_installer() {
1760        let der = include_bytes!("testdata/apple-signed-developer-id-installer.cer");
1761        let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap();
1762
1763        assert_eq!(
1764            cert.apple_extended_key_usage_purposes(),
1765            vec![ExtendedKeyUsagePurpose::DeveloperIdInstaller]
1766        );
1767        assert_eq!(
1768            cert.apple_code_signing_extensions(),
1769            vec![
1770                CodeSigningCertificateExtension::DeveloperIdDate,
1771                CodeSigningCertificateExtension::DeveloperIdInstaller
1772            ]
1773        );
1774        assert_eq!(
1775            cert.apple_guess_profile(),
1776            Some(CertificateProfile::DeveloperIdInstaller)
1777        );
1778        assert_eq!(
1779            cert.apple_issuing_chain(),
1780            vec![
1781                KnownCertificate::DeveloperIdG1,
1782                KnownCertificate::AppleRootCa,
1783                KnownCertificate::AppleComputerIncRoot
1784            ]
1785        );
1786        assert!(cert.chains_to_apple_root_ca());
1787        assert_eq!(
1788            cert.apple_root_certificate_chain(),
1789            Some(vec![
1790                cert.clone(),
1791                (*KnownCertificate::DeveloperIdG1).clone(),
1792                (*KnownCertificate::AppleRootCa).clone()
1793            ])
1794        );
1795        assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into()));
1796
1797        let mut builder = X509CertificateBuilder::default();
1798        builder
1799            .apple_certificate_profile(CertificateProfile::DeveloperIdInstaller)
1800            .unwrap();
1801
1802        let built = builder
1803            .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1))
1804            .unwrap()
1805            .0;
1806
1807        assert_eq!(
1808            built.apple_extended_key_usage_purposes(),
1809            cert.apple_extended_key_usage_purposes()
1810        );
1811        assert_eq!(
1812            built.apple_code_signing_extensions(),
1813            // We don't write out the date extension.
1814            cert.apple_code_signing_extensions()
1815                .into_iter()
1816                .filter(|e| !matches!(e, CodeSigningCertificateExtension::DeveloperIdDate))
1817                .collect::<Vec<_>>()
1818        );
1819        assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile());
1820        assert_eq!(built.apple_issuing_chain(), vec![]);
1821        assert!(!built.chains_to_apple_root_ca());
1822        assert!(built.apple_root_certificate_chain().is_none());
1823    }
1824}