Skip to main content

isideload_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                        ExtendedKeyUsagePurpose::try_from(&oid).ok()
933                    } else {
934                        None
935                    }
936                } else {
937                    None
938                }
939            })
940            .collect::<Vec<_>>()
941    }
942
943    fn apple_code_signing_extensions(&self) -> Vec<CodeSigningCertificateExtension> {
944        let cert: &x509_certificate::rfc5280::Certificate = self.as_ref();
945
946        cert.iter_extensions()
947            .filter_map(|extension| {
948                CodeSigningCertificateExtension::try_from(&extension.id).ok()
949            })
950            .collect::<Vec<_>>()
951    }
952
953    fn apple_guess_profile(&self) -> Option<CertificateProfile> {
954        let ekus = self.apple_extended_key_usage_purposes();
955        let signing = self.apple_code_signing_extensions();
956
957        // Some EKUs uniquely identify the certificate profile. We don't yet handle
958        // all EKUs because we don't have profiles defined for them.
959        //
960        // Ideally this logic stays in sync with apple_certificate_profile().
961        if ekus.contains(&ExtendedKeyUsagePurpose::DeveloperIdInstaller) {
962            Some(CertificateProfile::DeveloperIdInstaller)
963        } else if ekus.contains(&ExtendedKeyUsagePurpose::ThirdPartyMacDeveloperInstaller) {
964            Some(CertificateProfile::MacInstallerDistribution)
965            // That's all the EKUs that have a 1:1 to CertificateProfile. Now look at
966            // code signing extensions.
967        } else if signing.contains(&CodeSigningCertificateExtension::DeveloperIdApplication) {
968            Some(CertificateProfile::DeveloperIdApplication)
969        } else if signing.contains(&CodeSigningCertificateExtension::IPhoneDeveloper)
970            && signing.contains(&CodeSigningCertificateExtension::MacDeveloper)
971        {
972            Some(CertificateProfile::AppleDevelopment)
973        } else if signing.contains(&CodeSigningCertificateExtension::AppleMacAppSigningDevelopment)
974            && signing
975                .contains(&CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission)
976        {
977            Some(CertificateProfile::AppleDistribution)
978        } else {
979            None
980        }
981    }
982
983    fn apple_issuing_chain(&self) -> Vec<KnownCertificate> {
984        self.resolve_signing_chain(KnownCertificate::all().iter().copied())
985            .into_iter()
986            .filter_map(|cert| KnownCertificate::try_from(cert).ok())
987            .collect::<Vec<_>>()
988    }
989
990    fn chains_to_apple_root_ca(&self) -> bool {
991        if self.is_apple_root_ca() {
992            true
993        } else {
994            self.resolve_signing_chain(KnownCertificate::all().iter().copied())
995                .into_iter()
996                .any(|cert| cert.is_apple_root_ca())
997        }
998    }
999
1000    fn apple_root_certificate_chain(&self) -> Option<Vec<CapturedX509Certificate>> {
1001        let mut chain = vec![self.clone()];
1002
1003        for cert in self.resolve_signing_chain(KnownCertificate::all().iter().copied()) {
1004            chain.push(cert.clone());
1005
1006            if cert.is_apple_root_ca() {
1007                break;
1008            }
1009        }
1010
1011        if chain.last().unwrap().is_apple_root_ca() {
1012            Some(chain)
1013        } else {
1014            None
1015        }
1016    }
1017
1018    fn apple_team_id(&self) -> Option<String> {
1019        self.subject_name()
1020            .find_first_attribute_string(Oid(
1021                x509_certificate::rfc4519::OID_ORGANIZATIONAL_UNIT_NAME
1022                    .as_ref()
1023                    .into(),
1024            ))
1025            .unwrap_or(None)
1026    }
1027
1028    fn is_test_apple_signed_certificate(&self) -> bool {
1029        if let Ok(digest) = self.sha256_fingerprint() {
1030            hex::encode(digest)
1031                == "5939ad5770d8b977b38d07533754371314744e87a8d606433f689e9bc6b980a0"
1032        } else {
1033            false
1034        }
1035    }
1036}
1037
1038/// Extensions to [X509CertificateBuilder] specializing in Apple certificate behavior.
1039///
1040/// Most callers should call [Self::apple_certificate_profile] to configure
1041/// a preset profile for the certificate being generated. After that - and it is
1042/// important it is after - call [Self::apple_subject] to define the subject
1043/// field. If you call this after registering code signing extensions, it
1044/// detects the appropriate format for the Common Name field.
1045pub trait AppleCertificateBuilder: Sized {
1046    /// This functions defines common attributes on the certificate subject.
1047    ///
1048    /// `team_id` is your Apple team id. It is a short alphanumeric string. You
1049    /// can find this at <https://developer.apple.com/account/#/membership/>.
1050    fn apple_subject(
1051        &mut self,
1052        team_id: &str,
1053        person_name: &str,
1054        country: &str,
1055    ) -> Result<(), AppleCodesignError>;
1056
1057    /// Add an email address to the certificate's subject name.
1058    fn apple_email_address(&mut self, address: &str) -> Result<(), AppleCodesignError>;
1059
1060    /// Add an [ExtendedKeyUsagePurpose] to this certificate.
1061    fn apple_extended_key_usage(
1062        &mut self,
1063        usage: ExtendedKeyUsagePurpose,
1064    ) -> Result<(), AppleCodesignError>;
1065
1066    /// Add a certificate extension as defined by a [CodeSigningCertificateExtension] instance.
1067    fn apple_code_signing_certificate_extension(
1068        &mut self,
1069        extension: CodeSigningCertificateExtension,
1070    ) -> Result<(), AppleCodesignError>;
1071
1072    /// Add a [CertificateProfile] to this builder.
1073    ///
1074    /// All certificate extensions relevant to this profile are added.
1075    ///
1076    /// This should be the first function you call after creating an instance
1077    /// because other functions rely on the state that it sets.
1078    fn apple_certificate_profile(
1079        &mut self,
1080        profile: CertificateProfile,
1081    ) -> Result<(), AppleCodesignError>;
1082
1083    /// Find code signing extensions that are currently registered.
1084    fn apple_code_signing_extensions(&self) -> Vec<CodeSigningCertificateExtension>;
1085}
1086
1087impl AppleCertificateBuilder for X509CertificateBuilder {
1088    fn apple_subject(
1089        &mut self,
1090        team_id: &str,
1091        person_name: &str,
1092        country: &str,
1093    ) -> Result<(), AppleCodesignError> {
1094        // TODO the subject schema here isn't totally accurate. While OU does always
1095        // appear to be the team id, the user id attribute can be something else.
1096        // For example, for Apple Development, there are a similarly formatted yet
1097        // different value. But the team id does still appear.
1098        self.subject()
1099            .append_utf8_string(Oid(OID_USER_ID.as_ref().into()), team_id)
1100            .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?;
1101
1102        // Common Name is derived from the profile in use.
1103
1104        let extensions = self.apple_code_signing_extensions();
1105
1106        let common_name =
1107            if extensions.contains(&CodeSigningCertificateExtension::DeveloperIdApplication) {
1108                format!("Developer ID Application: {person_name} ({team_id})")
1109            } else if extensions.contains(&CodeSigningCertificateExtension::DeveloperIdInstaller) {
1110                format!("Developer ID Installer: {person_name} ({team_id})")
1111            } else if extensions
1112                .contains(&CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission)
1113            {
1114                format!("Apple Distribution: {person_name} ({team_id})")
1115            } else if extensions
1116                .contains(&CodeSigningCertificateExtension::AppleMacAppSigningSubmission)
1117            {
1118                format!("3rd Party Mac Developer Installer: {person_name} ({team_id})")
1119            } else if extensions.contains(&CodeSigningCertificateExtension::MacDeveloper) {
1120                format!("Apple Development: {person_name} ({team_id})")
1121            } else {
1122                format!("{person_name} ({team_id})")
1123            };
1124
1125        self.subject()
1126            .append_common_name_utf8_string(&common_name)
1127            .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?;
1128
1129        self.subject()
1130            .append_organizational_unit_utf8_string(team_id)
1131            .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?;
1132
1133        self.subject()
1134            .append_organization_utf8_string(person_name)
1135            .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?;
1136
1137        self.subject()
1138            .append_printable_string(Oid(OID_COUNTRY_NAME.as_ref().into()), country)
1139            .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?;
1140
1141        Ok(())
1142    }
1143
1144    fn apple_email_address(&mut self, address: &str) -> Result<(), AppleCodesignError> {
1145        self.subject()
1146            .append_utf8_string(Oid(OID_EMAIL_ADDRESS.as_ref().into()), address)
1147            .map_err(|e| AppleCodesignError::CertificateBuildError(format!("{e:?}")))?;
1148
1149        Ok(())
1150    }
1151
1152    fn apple_extended_key_usage(
1153        &mut self,
1154        usage: ExtendedKeyUsagePurpose,
1155    ) -> Result<(), AppleCodesignError> {
1156        let payload =
1157            bcder::encode::sequence(Oid(Bytes::copy_from_slice(usage.as_oid().as_ref())).encode())
1158                .to_captured(bcder::Mode::Der);
1159
1160        self.add_extension_der_data(
1161            Oid(OID_EXTENDED_KEY_USAGE.as_ref().into()),
1162            true,
1163            payload.as_slice(),
1164        );
1165
1166        Ok(())
1167    }
1168
1169    fn apple_code_signing_certificate_extension(
1170        &mut self,
1171        extension: CodeSigningCertificateExtension,
1172    ) -> Result<(), AppleCodesignError> {
1173        let (critical, payload) = match extension {
1174            CodeSigningCertificateExtension::IPhoneDeveloper => {
1175                // SEQUENCE (3 elem)
1176                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.2
1177                //   BOOLEAN true
1178                //   OCTET STRING (2 byte) 0500
1179                //     NULL
1180                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1181            }
1182            CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission => {
1183                // SEQUENCE (3 elem)
1184                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.4
1185                //   BOOLEAN true
1186                //   OCTET STRING (2 byte) 0500
1187                //     NULL
1188                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1189            }
1190            CodeSigningCertificateExtension::AppleMacAppSigningDevelopment => {
1191                // SEQUENCE (3 elem)
1192                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.7
1193                //   BOOLEAN true
1194                //   OCTET STRING (2 byte) 0500
1195                //     NULL
1196                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1197            }
1198            CodeSigningCertificateExtension::AppleMacAppSigningSubmission => {
1199                // SEQUENCE (3 elem)
1200                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.8
1201                //   BOOLEAN true
1202                //   OCTET STRING (2 byte) 0500
1203                //   NULL
1204                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1205            }
1206            CodeSigningCertificateExtension::MacDeveloper => {
1207                // SEQUENCE (3 elem)
1208                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.12
1209                //   BOOLEAN true
1210                //   OCTET STRING (2 byte) 0500
1211                //     NULL
1212                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1213            }
1214            CodeSigningCertificateExtension::DeveloperIdApplication => {
1215                // SEQUENCE (3 elem)
1216                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.13
1217                //   BOOLEAN true
1218                //   OCTET STRING (2 byte) 0500
1219                //     NULL
1220                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1221            }
1222            CodeSigningCertificateExtension::DeveloperIdInstaller => {
1223                // SEQUENCE (3 elem)
1224                //   OBJECT IDENTIFIER 1.2.840.113635.100.6.1.14
1225                //   BOOLEAN true
1226                //   OCTET STRING (2 byte) 0500
1227                //   NULL
1228                (true, Bytes::copy_from_slice(&[0x05, 0x00]))
1229            }
1230
1231            // The rest of these probably have the same payload. But until we see
1232            // them, don't take chances.
1233            _ => {
1234                return Err(AppleCodesignError::CertificateBuildError(format!(
1235                    "don't know how to handle code signing extension {extension:?}"
1236                )));
1237            }
1238        };
1239
1240        self.add_extension_der_data(
1241            Oid(Bytes::copy_from_slice(extension.as_oid().as_ref())),
1242            critical,
1243            payload,
1244        );
1245
1246        Ok(())
1247    }
1248
1249    fn apple_certificate_profile(
1250        &mut self,
1251        profile: CertificateProfile,
1252    ) -> Result<(), AppleCodesignError> {
1253        // Try to keep this logic in sync with apple_guess_profile().
1254        match profile {
1255            CertificateProfile::DeveloperIdApplication => {
1256                self.constraint_not_ca();
1257                self.apple_extended_key_usage(ExtendedKeyUsagePurpose::CodeSigning)?;
1258                self.key_usage(KeyUsage::DigitalSignature);
1259
1260                // OID_EXTENSION_DEVELOPER_ID_DATE comes next. But we don't know what
1261                // that should be. It is a UTF8String instead of an ASN.1 time type
1262                // because who knows.
1263
1264                self.apple_code_signing_certificate_extension(
1265                    CodeSigningCertificateExtension::DeveloperIdApplication,
1266                )?;
1267            }
1268            CertificateProfile::DeveloperIdInstaller => {
1269                self.constraint_not_ca();
1270                self.apple_extended_key_usage(ExtendedKeyUsagePurpose::DeveloperIdInstaller)?;
1271                self.key_usage(KeyUsage::DigitalSignature);
1272
1273                // OID_EXTENSION_DEVELOPER_ID_DATE comes next.
1274
1275                self.apple_code_signing_certificate_extension(
1276                    CodeSigningCertificateExtension::DeveloperIdInstaller,
1277                )?;
1278            }
1279            CertificateProfile::AppleDevelopment => {
1280                self.constraint_not_ca();
1281                self.apple_extended_key_usage(ExtendedKeyUsagePurpose::CodeSigning)?;
1282                self.key_usage(KeyUsage::DigitalSignature);
1283                self.apple_code_signing_certificate_extension(
1284                    CodeSigningCertificateExtension::IPhoneDeveloper,
1285                )?;
1286                self.apple_code_signing_certificate_extension(
1287                    CodeSigningCertificateExtension::MacDeveloper,
1288                )?;
1289            }
1290            CertificateProfile::AppleDistribution => {
1291                self.constraint_not_ca();
1292                self.apple_extended_key_usage(ExtendedKeyUsagePurpose::CodeSigning)?;
1293                self.key_usage(KeyUsage::DigitalSignature);
1294
1295                // OID_EXTENSION_DEVELOPER_ID_DATE comes next.
1296
1297                self.apple_code_signing_certificate_extension(
1298                    CodeSigningCertificateExtension::AppleMacAppSigningDevelopment,
1299                )?;
1300                self.apple_code_signing_certificate_extension(
1301                    CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission,
1302                )?;
1303            }
1304            CertificateProfile::MacInstallerDistribution => {
1305                self.constraint_not_ca();
1306                self.apple_extended_key_usage(
1307                    ExtendedKeyUsagePurpose::ThirdPartyMacDeveloperInstaller,
1308                )?;
1309                self.key_usage(KeyUsage::DigitalSignature);
1310
1311                self.apple_code_signing_certificate_extension(
1312                    CodeSigningCertificateExtension::AppleMacAppSigningSubmission,
1313                )?;
1314            }
1315        }
1316
1317        Ok(())
1318    }
1319
1320    fn apple_code_signing_extensions(&self) -> Vec<CodeSigningCertificateExtension> {
1321        self.extensions()
1322            .iter()
1323            .filter_map(|ext| {
1324                CodeSigningCertificateExtension::try_from(&ext.id).ok()
1325            })
1326            .collect::<Vec<_>>()
1327    }
1328}
1329
1330/// Create a new self-signed X.509 certificate suitable for signing code.
1331///
1332/// The created certificate contains all the extensions needed to convey
1333/// that it is used for code signing and should resemble certificates.
1334///
1335/// However, because the certificate isn't signed by Apple or another
1336/// trusted certificate authority, binaries signed with the certificate
1337/// may not pass Apple's verification requirements and the OS may refuse
1338/// to proceed. Needless to say, only use certificates generated with this
1339/// function for testing purposes only.
1340pub fn create_self_signed_code_signing_certificate(
1341    algorithm: KeyAlgorithm,
1342    profile: CertificateProfile,
1343    team_id: &str,
1344    person_name: &str,
1345    country: &str,
1346    validity_duration: chrono::Duration,
1347) -> Result<(CapturedX509Certificate, InMemorySigningKeyPair), AppleCodesignError> {
1348    let mut builder = X509CertificateBuilder::default();
1349
1350    builder.apple_certificate_profile(profile)?;
1351    builder.apple_subject(team_id, person_name, country)?;
1352    builder.validity_duration(validity_duration);
1353
1354    // x509-certificate crate doesn't support RSA key generation. So do
1355    // that ourselves.
1356    if matches!(algorithm, KeyAlgorithm::Rsa) {
1357        let private_key = rsa::RsaPrivateKey::new(&mut rand::thread_rng(), 2048).map_err(|e| {
1358            AppleCodesignError::CertificateBuildError(format!("error generating RSA key: {}", e))
1359        })?;
1360        let key_pair = InMemorySigningKeyPair::from_pkcs8_der(
1361            private_key
1362                .to_pkcs8_der()
1363                .map_err(|e| {
1364                    AppleCodesignError::CertificateGeneric(format!(
1365                        "error converting RSA key to DER: {}",
1366                        e
1367                    ))
1368                })?
1369                .as_bytes(),
1370        )?;
1371
1372        let cert = builder.create_with_key_pair(&key_pair)?;
1373
1374        Ok((cert, key_pair))
1375    } else {
1376        Ok(builder.create_with_random_keypair(algorithm)?)
1377    }
1378}
1379
1380#[cfg(test)]
1381mod tests {
1382    use {
1383        super::*,
1384        cryptographic_message_syntax::{SignedData, SignedDataBuilder, SignerBuilder},
1385        x509_certificate::EcdsaCurve,
1386    };
1387
1388    #[test]
1389    fn generate_self_signed_certificate_ecdsa() {
1390        for curve in EcdsaCurve::all() {
1391            create_self_signed_code_signing_certificate(
1392                KeyAlgorithm::Ecdsa(*curve),
1393                CertificateProfile::DeveloperIdInstaller,
1394                "team1",
1395                "Joe Developer",
1396                "US",
1397                chrono::Duration::hours(1),
1398            )
1399            .unwrap();
1400        }
1401    }
1402
1403    #[test]
1404    fn generate_self_signed_certificate_ed25519() {
1405        create_self_signed_code_signing_certificate(
1406            KeyAlgorithm::Ed25519,
1407            CertificateProfile::DeveloperIdInstaller,
1408            "team2",
1409            "Joe Developer",
1410            "US",
1411            chrono::Duration::hours(1),
1412        )
1413        .unwrap();
1414    }
1415
1416    #[test]
1417    fn generate_all_profiles() {
1418        for profile in CertificateProfile::all() {
1419            create_self_signed_code_signing_certificate(
1420                KeyAlgorithm::Ed25519,
1421                *profile,
1422                "team",
1423                "Joe Developer",
1424                "Wakanda",
1425                chrono::Duration::hours(1),
1426            )
1427            .unwrap();
1428        }
1429    }
1430
1431    #[test]
1432    fn cms_self_signed_certificate_signing_ecdsa() {
1433        for curve in EcdsaCurve::all() {
1434            let (cert, signing_key) = create_self_signed_code_signing_certificate(
1435                KeyAlgorithm::Ecdsa(*curve),
1436                CertificateProfile::DeveloperIdInstaller,
1437                "team",
1438                "Joe Developer",
1439                "US",
1440                chrono::Duration::hours(1),
1441            )
1442            .unwrap();
1443
1444            let plaintext = "hello, world";
1445
1446            let cms = SignedDataBuilder::default()
1447                .certificate(cert.clone())
1448                .content_inline(plaintext.as_bytes().to_vec())
1449                .signer(SignerBuilder::new(&signing_key, cert.clone()))
1450                .build_der()
1451                .unwrap();
1452
1453            let signed_data = SignedData::parse_ber(&cms).unwrap();
1454
1455            for signer in signed_data.signers() {
1456                signer
1457                    .verify_signature_with_signed_data(&signed_data)
1458                    .unwrap();
1459            }
1460        }
1461    }
1462
1463    #[test]
1464    fn cms_self_signed_certificate_signing_ed25519() {
1465        let (cert, signing_key) = create_self_signed_code_signing_certificate(
1466            KeyAlgorithm::Ed25519,
1467            CertificateProfile::DeveloperIdInstaller,
1468            "team",
1469            "Joe Developer",
1470            "US",
1471            chrono::Duration::hours(1),
1472        )
1473        .unwrap();
1474
1475        let plaintext = "hello, world";
1476
1477        let cms = SignedDataBuilder::default()
1478            .certificate(cert.clone())
1479            .content_inline(plaintext.as_bytes().to_vec())
1480            .signer(SignerBuilder::new(&signing_key, cert))
1481            .build_der()
1482            .unwrap();
1483
1484        let signed_data = SignedData::parse_ber(&cms).unwrap();
1485
1486        for signer in signed_data.signers() {
1487            signer
1488                .verify_signature_with_signed_data(&signed_data)
1489                .unwrap();
1490        }
1491    }
1492
1493    #[test]
1494    fn third_mac_mac() {
1495        let der = include_bytes!("testdata/apple-signed-3rd-party-mac.cer");
1496        let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap();
1497
1498        assert_eq!(
1499            cert.apple_extended_key_usage_purposes(),
1500            vec![ExtendedKeyUsagePurpose::ThirdPartyMacDeveloperInstaller]
1501        );
1502        assert_eq!(
1503            cert.apple_code_signing_extensions(),
1504            vec![CodeSigningCertificateExtension::AppleMacAppSigningSubmission]
1505        );
1506        assert_eq!(
1507            cert.apple_guess_profile(),
1508            Some(CertificateProfile::MacInstallerDistribution)
1509        );
1510        assert_eq!(
1511            cert.apple_issuing_chain(),
1512            vec![
1513                KnownCertificate::WwdrG3,
1514                KnownCertificate::AppleRootCa,
1515                KnownCertificate::AppleComputerIncRoot
1516            ]
1517        );
1518        assert!(cert.chains_to_apple_root_ca());
1519        assert_eq!(
1520            cert.apple_root_certificate_chain(),
1521            Some(vec![
1522                cert.clone(),
1523                (*KnownCertificate::WwdrG3).clone(),
1524                (*KnownCertificate::AppleRootCa).clone()
1525            ])
1526        );
1527        assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into()));
1528
1529        let mut builder = X509CertificateBuilder::default();
1530        builder
1531            .apple_certificate_profile(CertificateProfile::MacInstallerDistribution)
1532            .unwrap();
1533
1534        let built = builder
1535            .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1))
1536            .unwrap()
1537            .0;
1538
1539        assert_eq!(
1540            built.apple_extended_key_usage_purposes(),
1541            cert.apple_extended_key_usage_purposes()
1542        );
1543        assert_eq!(
1544            built.apple_code_signing_extensions(),
1545            cert.apple_code_signing_extensions()
1546        );
1547        assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile());
1548        assert_eq!(built.apple_issuing_chain(), vec![]);
1549        assert!(!built.chains_to_apple_root_ca());
1550        assert!(built.apple_root_certificate_chain().is_none());
1551    }
1552
1553    #[test]
1554    fn apple_development() {
1555        let der = include_bytes!("testdata/apple-signed-apple-development.cer");
1556        let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap();
1557
1558        assert_eq!(
1559            cert.apple_extended_key_usage_purposes(),
1560            vec![ExtendedKeyUsagePurpose::CodeSigning]
1561        );
1562        assert_eq!(
1563            cert.apple_code_signing_extensions(),
1564            vec![
1565                CodeSigningCertificateExtension::IPhoneDeveloper,
1566                CodeSigningCertificateExtension::MacDeveloper
1567            ]
1568        );
1569        assert_eq!(
1570            cert.apple_guess_profile(),
1571            Some(CertificateProfile::AppleDevelopment)
1572        );
1573        assert_eq!(
1574            cert.apple_issuing_chain(),
1575            vec![
1576                KnownCertificate::WwdrG3,
1577                KnownCertificate::AppleRootCa,
1578                KnownCertificate::AppleComputerIncRoot
1579            ],
1580        );
1581        assert!(cert.chains_to_apple_root_ca());
1582        assert_eq!(
1583            cert.apple_root_certificate_chain(),
1584            Some(vec![
1585                cert.clone(),
1586                (*KnownCertificate::WwdrG3).clone(),
1587                (*KnownCertificate::AppleRootCa).clone()
1588            ])
1589        );
1590        assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into()));
1591
1592        let mut builder = X509CertificateBuilder::default();
1593        builder
1594            .apple_certificate_profile(CertificateProfile::AppleDevelopment)
1595            .unwrap();
1596
1597        let built = builder
1598            .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1))
1599            .unwrap()
1600            .0;
1601
1602        assert_eq!(
1603            built.apple_extended_key_usage_purposes(),
1604            cert.apple_extended_key_usage_purposes()
1605        );
1606        assert_eq!(
1607            built.apple_code_signing_extensions(),
1608            cert.apple_code_signing_extensions()
1609        );
1610        assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile());
1611        assert_eq!(built.apple_issuing_chain(), vec![]);
1612        assert!(!built.chains_to_apple_root_ca());
1613        assert!(built.apple_root_certificate_chain().is_none());
1614    }
1615
1616    #[test]
1617    fn apple_distribution() {
1618        let der = include_bytes!("testdata/apple-signed-apple-distribution.cer");
1619        let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap();
1620
1621        assert_eq!(
1622            cert.apple_extended_key_usage_purposes(),
1623            vec![ExtendedKeyUsagePurpose::CodeSigning]
1624        );
1625        assert_eq!(
1626            cert.apple_code_signing_extensions(),
1627            vec![
1628                CodeSigningCertificateExtension::AppleMacAppSigningDevelopment,
1629                CodeSigningCertificateExtension::AppleDeveloperCertificateSubmission
1630            ]
1631        );
1632        assert_eq!(
1633            cert.apple_guess_profile(),
1634            Some(CertificateProfile::AppleDistribution)
1635        );
1636        assert_eq!(
1637            cert.apple_issuing_chain(),
1638            vec![
1639                KnownCertificate::WwdrG3,
1640                KnownCertificate::AppleRootCa,
1641                KnownCertificate::AppleComputerIncRoot
1642            ],
1643        );
1644        assert!(cert.chains_to_apple_root_ca());
1645        assert_eq!(
1646            cert.apple_root_certificate_chain(),
1647            Some(vec![
1648                cert.clone(),
1649                (*KnownCertificate::WwdrG3).clone(),
1650                (*KnownCertificate::AppleRootCa).clone()
1651            ])
1652        );
1653        assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into()));
1654
1655        let mut builder = X509CertificateBuilder::default();
1656        builder
1657            .apple_certificate_profile(CertificateProfile::AppleDistribution)
1658            .unwrap();
1659
1660        let built = builder
1661            .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1))
1662            .unwrap()
1663            .0;
1664
1665        assert_eq!(
1666            built.apple_extended_key_usage_purposes(),
1667            cert.apple_extended_key_usage_purposes()
1668        );
1669        assert_eq!(
1670            built.apple_code_signing_extensions(),
1671            cert.apple_code_signing_extensions()
1672        );
1673        assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile());
1674        assert_eq!(built.apple_issuing_chain(), vec![]);
1675        assert!(!built.chains_to_apple_root_ca());
1676        assert!(built.apple_root_certificate_chain().is_none());
1677    }
1678
1679    #[test]
1680    fn apple_developer_id_application() {
1681        let der = include_bytes!("testdata/apple-signed-developer-id-application.cer");
1682        let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap();
1683
1684        assert_eq!(
1685            cert.apple_extended_key_usage_purposes(),
1686            vec![ExtendedKeyUsagePurpose::CodeSigning]
1687        );
1688        assert_eq!(
1689            cert.apple_code_signing_extensions(),
1690            vec![
1691                CodeSigningCertificateExtension::DeveloperIdDate,
1692                CodeSigningCertificateExtension::DeveloperIdApplication
1693            ]
1694        );
1695        assert_eq!(
1696            cert.apple_guess_profile(),
1697            Some(CertificateProfile::DeveloperIdApplication)
1698        );
1699        assert_eq!(
1700            cert.apple_issuing_chain(),
1701            vec![
1702                KnownCertificate::DeveloperIdG1,
1703                KnownCertificate::AppleRootCa,
1704                KnownCertificate::AppleComputerIncRoot
1705            ]
1706        );
1707        assert!(cert.chains_to_apple_root_ca());
1708        assert_eq!(
1709            cert.apple_root_certificate_chain(),
1710            Some(vec![
1711                cert.clone(),
1712                (*KnownCertificate::DeveloperIdG1).clone(),
1713                (*KnownCertificate::AppleRootCa).clone()
1714            ])
1715        );
1716        assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into()));
1717
1718        let mut builder = X509CertificateBuilder::default();
1719        builder
1720            .apple_certificate_profile(CertificateProfile::DeveloperIdApplication)
1721            .unwrap();
1722
1723        let built = builder
1724            .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1))
1725            .unwrap()
1726            .0;
1727
1728        assert_eq!(
1729            built.apple_extended_key_usage_purposes(),
1730            cert.apple_extended_key_usage_purposes()
1731        );
1732        assert_eq!(
1733            built.apple_code_signing_extensions(),
1734            // We don't write out the date extension.
1735            cert.apple_code_signing_extensions()
1736                .into_iter()
1737                .filter(|e| !matches!(e, CodeSigningCertificateExtension::DeveloperIdDate))
1738                .collect::<Vec<_>>()
1739        );
1740        assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile());
1741        assert_eq!(built.apple_issuing_chain(), vec![]);
1742        assert!(!built.chains_to_apple_root_ca());
1743        assert!(built.apple_root_certificate_chain().is_none());
1744    }
1745
1746    #[test]
1747    fn apple_developer_id_installer() {
1748        let der = include_bytes!("testdata/apple-signed-developer-id-installer.cer");
1749        let cert = CapturedX509Certificate::from_der(der.to_vec()).unwrap();
1750
1751        assert_eq!(
1752            cert.apple_extended_key_usage_purposes(),
1753            vec![ExtendedKeyUsagePurpose::DeveloperIdInstaller]
1754        );
1755        assert_eq!(
1756            cert.apple_code_signing_extensions(),
1757            vec![
1758                CodeSigningCertificateExtension::DeveloperIdDate,
1759                CodeSigningCertificateExtension::DeveloperIdInstaller
1760            ]
1761        );
1762        assert_eq!(
1763            cert.apple_guess_profile(),
1764            Some(CertificateProfile::DeveloperIdInstaller)
1765        );
1766        assert_eq!(
1767            cert.apple_issuing_chain(),
1768            vec![
1769                KnownCertificate::DeveloperIdG1,
1770                KnownCertificate::AppleRootCa,
1771                KnownCertificate::AppleComputerIncRoot
1772            ]
1773        );
1774        assert!(cert.chains_to_apple_root_ca());
1775        assert_eq!(
1776            cert.apple_root_certificate_chain(),
1777            Some(vec![
1778                cert.clone(),
1779                (*KnownCertificate::DeveloperIdG1).clone(),
1780                (*KnownCertificate::AppleRootCa).clone()
1781            ])
1782        );
1783        assert_eq!(cert.apple_team_id(), Some("MK22MZP987".into()));
1784
1785        let mut builder = X509CertificateBuilder::default();
1786        builder
1787            .apple_certificate_profile(CertificateProfile::DeveloperIdInstaller)
1788            .unwrap();
1789
1790        let built = builder
1791            .create_with_random_keypair(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1))
1792            .unwrap()
1793            .0;
1794
1795        assert_eq!(
1796            built.apple_extended_key_usage_purposes(),
1797            cert.apple_extended_key_usage_purposes()
1798        );
1799        assert_eq!(
1800            built.apple_code_signing_extensions(),
1801            // We don't write out the date extension.
1802            cert.apple_code_signing_extensions()
1803                .into_iter()
1804                .filter(|e| !matches!(e, CodeSigningCertificateExtension::DeveloperIdDate))
1805                .collect::<Vec<_>>()
1806        );
1807        assert_eq!(built.apple_guess_profile(), cert.apple_guess_profile());
1808        assert_eq!(built.apple_issuing_chain(), vec![]);
1809        assert!(!built.chains_to_apple_root_ca());
1810        assert!(built.apple_root_certificate_chain().is_none());
1811    }
1812}