Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 160 additions & 14 deletions Melanzana.CodeSign/Blobs/CmsWrapperBlob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,67 @@ namespace Melanzana.CodeSign.Blobs
{
public class CmsWrapperBlob
{
/// <summary>
/// The key usage extension defines the purpose (e.g., encipherment,
/// signature, certificate signing) of the key contained in the
/// certificate.
/// </summary>
/// <seealso href="https://tools.ietf.org/html/rfc2459#section-4.2.1.3"/>
public const string KeyUsage = "2.5.29.15";

/// <summary>
/// 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.
/// </summary>
/// <seealso href="https://tools.ietf.org/html/rfc2459#section-4.2.1.13"/>
public const string ExtendedKeyUsage = "2.5.29.37";

/// <summary>
/// An extended key usage value indicating the certificate may be used for
/// signing of downloadable executable code.
/// </summary>
/// <seealso href="https://tools.ietf.org/html/rfc3280.html#section-4.2.1.13"/>
public const string CodeSigning = "1.3.6.1.5.5.7.3.3";

/// <summary>
/// The OID of a certificate extension which indicates the certificate is inteded for iPhone Software Submission Signing.
/// </summary>
/// <seealso href="https://images.apple.com/certificateauthority/pdf/Apple_WWDR_CPS_v1.23.pdf"/>
public const string SoftwareSubmissionSigning = "1.2.840.113635.100.6.1.4";

/// <summary>
/// The OID of a certificate extension which indicates the certificate is intended for iPhone Software Development Signing.
/// </summary>
/// <seealso href="https://images.apple.com/certificateauthority/pdf/Apple_WWDR_CPS_v1.23.pdf"/>
public const string SoftwareDevelopmentSigning = "1.2.840.113635.100.6.1.2";

/// <summary>
/// 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.
/// </summary>
/// <seealso href="https://tools.ietf.org/html/rfc3280.html#section-4.2.1.10"/>
public const string BasicConstraints = "2.5.29.19";

/// <summary>
/// The certificate policies extension contains a sequence of one or more
/// policy information terms.
/// </summary>
/// <seealso href="https://tools.ietf.org/html/rfc5280#section-4.2.1.4"/>
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();
Expand Down Expand Up @@ -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 ?
Expand Down Expand Up @@ -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<X509Certificate2>();
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<X509Certificate2>();
return false;
}
}

private static bool GetManualChain(X509Certificate2 developerCertificate, out X509Certificate2[] certificates)
{
certificates = Array.Empty<X509Certificate2>();

// 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;
}
}
}