diff --git a/Melanzana.CodeSign/Blobs/CmsWrapperBlob.cs b/Melanzana.CodeSign/Blobs/CmsWrapperBlob.cs
index c30793b..eda3acd 100644
--- a/Melanzana.CodeSign/Blobs/CmsWrapperBlob.cs
+++ b/Melanzana.CodeSign/Blobs/CmsWrapperBlob.cs
@@ -11,10 +11,67 @@ namespace Melanzana.CodeSign.Blobs
{
public class CmsWrapperBlob
{
+ ///
+ /// The key usage extension defines the purpose (e.g., encipherment,
+ /// signature, certificate signing) of the key contained in the
+ /// certificate.
+ ///
+ ///
+ public const string KeyUsage = "2.5.29.15";
+
+ ///
+ /// The extended key usage field indicates one or more purposes for which the certified
+ /// public key may be used, in addition to or in place of the basic
+ /// purposes indicated in the key usage extension field.
+ ///
+ ///
+ public const string ExtendedKeyUsage = "2.5.29.37";
+
+ ///
+ /// An extended key usage value indicating the certificate may be used for
+ /// signing of downloadable executable code.
+ ///
+ ///
+ public const string CodeSigning = "1.3.6.1.5.5.7.3.3";
+
+ ///
+ /// The OID of a certificate extension which indicates the certificate is inteded for iPhone Software Submission Signing.
+ ///
+ ///
+ public const string SoftwareSubmissionSigning = "1.2.840.113635.100.6.1.4";
+
+ ///
+ /// The OID of a certificate extension which indicates the certificate is intended for iPhone Software Development Signing.
+ ///
+ ///
+ public const string SoftwareDevelopmentSigning = "1.2.840.113635.100.6.1.2";
+
+ ///
+ /// The basic constraints extension identifies whether the subject of the
+ /// certificate is a CA and the maximum depth of valid certification
+ /// paths that include this certificate.
+ ///
+ ///
+ public const string BasicConstraints = "2.5.29.19";
+
+ ///
+ /// The certificate policies extension contains a sequence of one or more
+ /// policy information terms.
+ ///
+ ///
+ public const string CertificatePolicies = "2.5.29.32";
+
private readonly static string rootCertificatePath = "Melanzana.CodeSign.Certificates.RootCertificate.cer";
private readonly static string g1IntermediateCertificatePath = "Melanzana.CodeSign.Certificates.IntermediateG1Certificate.cer";
private readonly static string g3IntermediateCertificatePath = "Melanzana.CodeSign.Certificates.IntermediateG3Certificate.cer";
+ private readonly static X509Certificate2 RootCertificate = GetManifestCertificate(rootCertificatePath);
+ private readonly static X509Certificate2[] IntermediateCertificates = new X509Certificate2[]
+ {
+ GetManifestCertificate(g1IntermediateCertificatePath),
+ GetManifestCertificate(g3IntermediateCertificatePath),
+ };
+
private static X509Certificate2 GetManifestCertificate(string name)
{
var memoryStream = new MemoryStream();
@@ -54,24 +111,22 @@ public static byte[] Create(
var certificatesList = new X509Certificate2Collection();
// Try to build full chain
- var chain = new X509Chain();
- var chainPolicy = new X509ChainPolicy { TrustMode = X509ChainTrustMode.CustomRootTrust };
- chainPolicy.CustomTrustStore.Add(GetManifestCertificate(rootCertificatePath));
- chainPolicy.CustomTrustStore.Add(GetManifestCertificate(g1IntermediateCertificatePath));
- chainPolicy.CustomTrustStore.Add(GetManifestCertificate(g3IntermediateCertificatePath));
- chain.ChainPolicy = chainPolicy;
- if (chain.Build(developerCertificate))
+
+ if (GetCustomRootTrustChain(developerCertificate, out var customCertificates))
+ {
+ certificatesList.AddRange(customCertificates);
+ }
+ else if (GetDefaultChain(developerCertificate, out var defaultCertificates))
{
- certificatesList.AddRange(chain.ChainElements.Select(e => e.Certificate).ToArray());
+ certificatesList.AddRange(defaultCertificates);
+ }
+ else if(GetManualChain(developerCertificate, out var manualCertificates))
+ {
+ certificatesList.AddRange(manualCertificates);
}
else
{
- // Retry with default policy and system certificate store
- chain.ChainPolicy = new X509ChainPolicy();
- if (chain.Build(developerCertificate))
- {
- certificatesList.AddRange(chain.ChainElements.Select(e => e.Certificate).ToArray());
- }
+ throw new Exception("Could not build the certificate chain for the developer certificate.");
}
var cmsSigner = privateKey == null ?
@@ -117,5 +172,96 @@ public static byte[] Create(
return blobBuffer;
}
+
+ private static bool GetCustomRootTrustChain(X509Certificate2 developerCertificate, out X509Certificate2[] certificates)
+ {
+ var chain = new X509Chain();
+ var chainPolicy = new X509ChainPolicy { TrustMode = X509ChainTrustMode.CustomRootTrust };
+ chainPolicy.CustomTrustStore.Add(RootCertificate);
+ chainPolicy.CustomTrustStore.AddRange(IntermediateCertificates);
+ chain.ChainPolicy = chainPolicy;
+ if (chain.Build(developerCertificate))
+ {
+ certificates = chain.ChainElements.Select(e => e.Certificate).ToArray();
+ return true;
+ }
+ else
+ {
+ certificates = Array.Empty();
+ return false;
+ }
+ }
+
+ private static bool GetDefaultChain(X509Certificate2 developerCertificate, out X509Certificate2[] certificates)
+ {
+ var chain = new X509Chain();
+ if (chain.Build(developerCertificate))
+ {
+ certificates = chain.ChainElements.Select(e => e.Certificate).ToArray();
+ return true;
+ }
+ else
+ {
+ certificates = Array.Empty();
+ return false;
+ }
+ }
+
+ private static bool GetManualChain(X509Certificate2 developerCertificate, out X509Certificate2[] certificates)
+ {
+ certificates = Array.Empty();
+
+ // The certificate must be issued by any of the intermediate certificates
+ // TODO: we should also check on Authority Key Identifier
+ var intermediate = IntermediateCertificates.SingleOrDefault(c => developerCertificate.IssuerName.Name == c.SubjectName.Name);
+ if (intermediate == null)
+ {
+ return false;
+ }
+
+ // The certificate must be a signing certificate
+ var keyUsageExtension = (X509KeyUsageExtension?)developerCertificate.Extensions[KeyUsage];
+ if (keyUsageExtension == null || keyUsageExtension.KeyUsages != X509KeyUsageFlags.DigitalSignature)
+ {
+ return false;
+ }
+
+ // The certificate must be a code signing certificate
+ var extendedKeyUsage = (X509EnhancedKeyUsageExtension?)developerCertificate.Extensions[ExtendedKeyUsage];
+ if (extendedKeyUsage == null || extendedKeyUsage.EnhancedKeyUsages.Count != 1 || extendedKeyUsage.EnhancedKeyUsages[0].Value != CodeSigning)
+ {
+ return false;
+ }
+
+ // The certificate must be either a software submission or a software development certificate
+ bool softwareSubmissionSigning = developerCertificate.Extensions[SoftwareSubmissionSigning] != null;
+ bool softwareDevelopmentSigning = developerCertificate.Extensions[SoftwareDevelopmentSigning] != null;
+
+ if (!softwareSubmissionSigning && !softwareDevelopmentSigning)
+ {
+ return false;
+ }
+
+
+ // The certificate cannot be a self-signed certificate or a root CA
+ var basicConstraints = developerCertificate.Extensions[BasicConstraints] as X509BasicConstraintsExtension;
+ if (basicConstraints != null && basicConstraints.CertificateAuthority)
+ {
+ return false;
+ }
+
+ // The certificate should adhere to the Apple certificate policy (but .NET does not contain classes for validating
+ // certificate policies)
+ var policiesExtension = developerCertificate.Extensions[CertificatePolicies];
+
+ certificates = new X509Certificate2[]
+ {
+ RootCertificate,
+ intermediate,
+ developerCertificate,
+ };
+
+ return true;
+ }
}
}