Esempio n. 1
0
		CodeSignIdentity GetBestMatch (List<CodeSignIdentity> pairs, CodeSignIdentity identity)
		{
			var matches = new List<CodeSignIdentity> ();
			int bestMatchLength = 0;
			int matchLength;

			// find matching provisioning profiles with compatible appid, keeping only those with the longest matching (wildcard) ids
			Log.LogMessage (MessageImportance.Low, MSBStrings.M0134);
			foreach (var pair in pairs) {
				var appid = ConstructValidAppId (pair.Profile, identity.BundleId, out matchLength);
				if (appid != null) {
					if (matchLength >= bestMatchLength) {
						if (matchLength > bestMatchLength) {
							bestMatchLength = matchLength;
							foreach (var previousMatch in matches)
								Log.LogMessage (MessageImportance.Low, MSBStrings.M0135, previousMatch.AppId, appid);
							matches.Clear ();
						}

						var match = identity.Clone ();
						match.SigningKey = pair.SigningKey;
						match.Profile = pair.Profile;
						match.AppId = appid;

						matches.Add (match);
					} else {
						string currentMatches = "";
						foreach (var match in matches)
							currentMatches += $"{match}; ";
						Log.LogMessage (MessageImportance.Low, MSBStrings.M0136, appid, currentMatches);
					}
				}
			}

			if (matches.Count == 0) {
				Log.LogWarning (null, null, null, AppManifest, 0, 0, 0, 0, MSBStrings.W0137);
				return identity;
			}

			if (matches.Count > 1) {
				var spaces = new string (' ', 3);

				Log.LogMessage (MessageImportance.Normal, MSBStrings.M0138);

				matches.Sort (new SigningIdentityComparer ());

				for (int i = 0; i < matches.Count; i++) {
					Log.LogMessage (MessageImportance.Normal, "{0,3}. Provisioning Profile: \"{1}\" ({2})", i + 1, matches[i].Profile.Name, matches[i].Profile.Uuid);

					if (matches[i].SigningKey != null)
						Log.LogMessage (MessageImportance.Normal, "{0}  Signing Identity: \"{1}\"", spaces, SecKeychain.GetCertificateCommonName (matches[i].SigningKey));
				}
			}

			return matches[0];
		}
Esempio n. 2
0
        CodeSignIdentity GetBestMatch(List <CodeSignIdentity> pairs, CodeSignIdentity identity)
        {
            var matches         = new List <CodeSignIdentity> ();
            int bestMatchLength = 0;
            int matchLength;

            // find matching provisioning profiles with compatible appid, keeping only those with the longest matching (wildcard) ids
            Log.LogMessage(MessageImportance.Low, "Finding matching provisioning profiles with compatible AppID, keeping only those with the longest matching (wildcard) IDs.");

            foreach (var pair in pairs)
            {
                var appid = ConstructValidAppId(pair.Profile, identity.BundleId, out matchLength);

                if (appid != null)
                {
                    if (matchLength >= bestMatchLength)
                    {
                        if (matchLength > bestMatchLength)
                        {
                            bestMatchLength = matchLength;
                            foreach (var previousMatch in matches)
                            {
                                Log.LogMessage(MessageImportance.Low, "AppID: {0} was ruled out because we found a better match: {1}.", previousMatch.AppId, appid);
                            }
                            matches.Clear();
                        }

                        var match = identity.Clone();

                        match.SigningKey = pair.SigningKey;
                        match.Profile    = pair.Profile;
                        match.AppId      = appid;

                        matches.Add(match);
                    }
                    else
                    {
                        string currentMatches = "";

                        foreach (var match in matches)
                        {
                            currentMatches += $"{match}; ";
                        }

                        Log.LogMessage(MessageImportance.Low, "AppID: {0} was ruled out because we already found better matches: {1}.", appid, currentMatches);
                    }
                }
            }

            if (matches.Count == 0)
            {
                Log.LogWarning(null, null, null, AppManifest, 0, 0, 0, 0, "No installed provisioning profiles match the bundle identifier.");

                return(identity);
            }

            if (matches.Count > 1)
            {
                var spaces = new string (' ', 3);

                Log.LogMessage(MessageImportance.Normal, "Multiple provisioning profiles match the bundle identifier; using the first match.");

                matches.Sort(new SigningIdentityComparer());

                for (int i = 0; i < matches.Count; i++)
                {
                    Log.LogMessage(MessageImportance.Normal, "{0,3}. Provisioning Profile: \"{1}\" ({2})", i + 1, matches [i].Profile.Name, matches [i].Profile.Uuid);

                    if (matches [i].SigningKey != null)
                    {
                        Log.LogMessage(MessageImportance.Normal, "{0}  Signing Identity: \"{1}\"", spaces, GetCertificateCommonName(matches [i].SigningKey));
                    }
                }
            }

            return(matches [0]);
        }
Esempio n. 3
0
        public override bool Execute()
        {
            var type     = MobileProvisionDistributionType.Any;
            var identity = new CodeSignIdentity();
            MobileProvisionPlatform  platform;
            IList <MobileProvision>  profiles;
            IList <X509Certificate2> certs;
            PDictionary plist;

            switch (SdkPlatform)
            {
            case "AppleTVSimulator":
            case "AppleTVOS":
                platform = MobileProvisionPlatform.tvOS;
                break;

            case "iPhoneSimulator":
            case "WatchSimulator":
            case "iPhoneOS":
            case "WatchOS":
                platform = MobileProvisionPlatform.iOS;
                break;

            case "MacOSX":
                platform = MobileProvisionPlatform.MacOS;
                break;

            default:
                Log.LogError("Unknown SDK platform: {0}", SdkPlatform);
                return(false);
            }

            if (ProvisioningProfile == AutomaticAppStoreProvision)
            {
                type = MobileProvisionDistributionType.AppStore;
            }
            else if (ProvisioningProfile == AutomaticInHouseProvision)
            {
                type = MobileProvisionDistributionType.InHouse;
            }
            else if (ProvisioningProfile == AutomaticAdHocProvision)
            {
                type = MobileProvisionDistributionType.AdHoc;
            }

            try {
                plist = PDictionary.FromFile(AppManifest);
            } catch (Exception ex) {
                Log.LogError(null, null, null, AppManifest, 0, 0, 0, 0, "Error loading '{0}': {1}", AppManifest, ex.Message);
                return(false);
            }

            DetectedCodesignAllocate = Path.Combine(DeveloperRoot, "Toolchains", "XcodeDefault.xctoolchain", "usr", "bin", "codesign_allocate");
            DetectedBundleVersion    = plist.GetCFBundleVersion();
            DetectedDistributionType = type.ToString();

            identity.BundleId = plist.GetCFBundleIdentifier();
            if (string.IsNullOrEmpty(identity.BundleId))
            {
                Log.LogError(null, null, null, AppManifest, 0, 0, 0, 0, "{0} does not define CFBundleIdentifier", AppManifest);
                return(false);
            }

            if (Framework == PlatformFramework.MacOS && !RequireCodeSigning)
            {
                DetectedBundleId = identity.BundleId;
                DetectedAppId    = DetectedBundleId;

                ReportDetectedCodesignInfo();

                return(!Log.HasLoggedErrors);
            }

            if (!RequireProvisioningProfile && string.IsNullOrEmpty(ProvisioningProfile))
            {
                if (SdkIsSimulator && AppleSdkSettings.XcodeVersion.Major >= 8)
                {
                    // Note: Starting with Xcode 8.0, we need to codesign iOS Simulator builds in order for them to run.
                    // The "-" key is a special value allowed by the codesign utility that allows us to get away with
                    // not having an actual codesign key. As far as we know, this only works with Xcode >= 8.
                    DetectedCodeSigningKey = "-";
                }
                else
                {
                    // Try and get a valid codesigning certificate...
                    if (!TryGetSigningCertificates(out certs, SdkIsSimulator))
                    {
                        return(false);
                    }

                    if (certs.Count > 0)
                    {
                        if (certs.Count > 1)
                        {
                            if (!string.IsNullOrEmpty(SigningKey))
                            {
                                Log.LogMessage(MessageImportance.Normal, "Multiple signing identities match '{0}'; using the first match.", SigningKey);
                            }
                            else
                            {
                                Log.LogMessage(MessageImportance.Normal, "Multiple signing identities found; using the first identity.");
                            }

                            for (int i = 0; i < certs.Count; i++)
                            {
                                Log.LogMessage(MessageImportance.Normal, "{0,3}. Signing Identity: {1} ({2})", i + 1,
                                               SecKeychain.GetCertificateCommonName(certs[i]), certs[i].Thumbprint);
                            }
                        }

                        codesignCommonName     = SecKeychain.GetCertificateCommonName(certs[0]);
                        DetectedCodeSigningKey = certs[0].Thumbprint;
                    }
                    else
                    {
                        // Note: We don't have to codesign for iOS Simulator builds meant to run on Xcode iOS Simulators
                        // older than 8.0, so it's non-fatal if we don't find any...
                    }
                }

                DetectedBundleId = identity.BundleId;
                DetectedAppId    = DetectedBundleId;

                ReportDetectedCodesignInfo();

                return(!Log.HasLoggedErrors);
            }

            // Note: if we make it this far, we absolutely need a codesigning certificate
            if (!TryGetSigningCertificates(out certs, false))
            {
                return(false);
            }

            if (certs.Count > 0)
            {
                Log.LogMessage(MessageImportance.Low, "Available certificates:");
                foreach (var cert in certs)
                {
                    Log.LogMessage(MessageImportance.Low, "    {0}", Xamarin.MacDev.Keychain.GetCertificateCommonName(cert));
                }
            }

            if (!IsAutoCodeSignProfile(ProvisioningProfile))
            {
                identity.Profile = MobileProvisionIndex.GetMobileProvision(platform, ProvisioningProfile);

                if (identity.Profile == null)
                {
                    Log.LogError("The specified " + PlatformName + " provisioning profile '{0}' could not be found", ProvisioningProfile);
                    return(false);
                }

                var profile = identity.Profile;                 // capture ref for lambda

                if (certs.Count > 0)
                {
                    identity.SigningKey = certs.FirstOrDefault(c => profile.DeveloperCertificates.Any(p => p.Thumbprint == c.Thumbprint));
                    if (identity.SigningKey == null)
                    {
                        Log.LogError("No " + PlatformName + " signing identities match the specified provisioning profile '{0}'.", ProvisioningProfile);
                        return(false);
                    }
                }

                identity.AppId = ConstructValidAppId(identity.Profile, identity.BundleId);
                if (identity.AppId == null)
                {
                    Log.LogError(null, null, null, AppManifest, 0, 0, 0, 0, "Project bundle identifier '{0}' does not match specified provisioning profile '{1}'", identity.BundleId, ProvisioningProfile);
                    return(false);
                }

                if (identity.SigningKey != null)
                {
                    codesignCommonName     = SecKeychain.GetCertificateCommonName(identity.SigningKey);
                    DetectedCodeSigningKey = identity.SigningKey.Thumbprint;
                }

                provisioningProfileName = identity.Profile.Name;

                DetectedProvisioningProfile = identity.Profile.Uuid;
                DetectedDistributionType    = identity.Profile.DistributionType.ToString();
                DetectedBundleId            = identity.BundleId;
                DetectedAppId = identity.AppId;

                ReportDetectedCodesignInfo();

                return(!Log.HasLoggedErrors);
            }

            List <string> failures = new List <string> ();

            if (identity.BundleId != null)
            {
                if (certs.Count > 0)
                {
                    profiles = MobileProvisionIndex.GetMobileProvisions(platform, identity.BundleId, type, certs, unique: true, failures: failures);
                }
                else
                {
                    profiles = MobileProvisionIndex.GetMobileProvisions(platform, identity.BundleId, type, unique: true, failures: failures);
                }
            }
            else if (certs.Count > 0)
            {
                profiles = MobileProvisionIndex.GetMobileProvisions(platform, type, certs, unique: true, failures: failures);
            }
            else
            {
                profiles = MobileProvisionIndex.GetMobileProvisions(platform, type, unique: true, failures: failures);
            }

            if (profiles.Count == 0)
            {
                foreach (var f in failures)
                {
                    Log.LogMessage(MessageImportance.Low, "{0}", f);
                }
                Log.LogError($"Could not find any available provisioning profiles for {PlatformName}.");
                return(false);
            }
            else
            {
                Log.LogMessage(MessageImportance.Low, "Available profiles:");
                foreach (var p in profiles)
                {
                    Log.LogMessage(MessageImportance.Low, "    {0}", p.Name);
                }
            }

            List <CodeSignIdentity> pairs;

            if (certs.Count > 0)
            {
                pairs = (from p in profiles
                         from c in certs
                         where p.DeveloperCertificates.Any(d => d.Thumbprint == c.Thumbprint)
                         select new CodeSignIdentity {
                    SigningKey = c, Profile = p
                }).ToList();

                if (pairs.Count == 0)
                {
                    Log.LogError("No installed provisioning profiles match the installed " + PlatformName + " signing identities.");
                    return(false);
                }
            }
            else
            {
                pairs = (from p in profiles select new CodeSignIdentity {
                    Profile = p
                }).ToList();
            }

            var matches         = new List <CodeSignIdentity> ();
            int bestMatchLength = 0;
            int matchLength;

            // find matching provisioning profiles with compatible appid, keeping only those with the longest matching (wildcard) ids
            foreach (var pair in pairs)
            {
                var appid = ConstructValidAppId(pair.Profile, identity.BundleId, out matchLength);
                if (appid != null && matchLength >= bestMatchLength)
                {
                    if (matchLength > bestMatchLength)
                    {
                        bestMatchLength = matchLength;
                        matches.Clear();
                    }

                    var match = identity.Clone();
                    match.SigningKey = pair.SigningKey;
                    match.Profile    = pair.Profile;
                    match.AppId      = appid;

                    matches.Add(match);
                }
            }

            if (matches.Count == 0)
            {
                Log.LogWarning(null, null, null, AppManifest, 0, 0, 0, 0, "No installed provisioning profiles match the bundle identifier.");
            }
            else
            {
                if (matches.Count > 1)
                {
                    var spaces = new string (' ', 3);

                    Log.LogMessage(MessageImportance.Normal, "Multiple provisioning profiles match the bundle identifier; using the first match.");

                    matches.Sort(new SigningIdentityComparer());

                    for (int i = 0; i < matches.Count; i++)
                    {
                        Log.LogMessage(MessageImportance.Normal, "{0,3}. Provisioning Profile: \"{1}\" ({2})", i + 1, matches[i].Profile.Name, matches[i].Profile.Uuid);

                        if (matches[i].SigningKey != null)
                        {
                            Log.LogMessage(MessageImportance.Normal, "{0}  Signing Identity: \"{1}\"", spaces, SecKeychain.GetCertificateCommonName(matches[i].SigningKey));
                        }
                    }
                }

                identity = matches[0];
            }

            if (identity.Profile != null && identity.AppId != null)
            {
                codesignCommonName = identity.SigningKey != null?SecKeychain.GetCertificateCommonName(identity.SigningKey) : null;

                provisioningProfileName = identity.Profile.Name;

                DetectedCodeSigningKey      = identity.SigningKey?.Thumbprint;
                DetectedProvisioningProfile = identity.Profile.Uuid;
                DetectedBundleId            = identity.BundleId;
                DetectedAppId = identity.AppId;

                ReportDetectedCodesignInfo();
            }
            else
            {
                if (identity.SigningKey != null)
                {
                    Log.LogError("Bundle identifier '{0}' does not match any installed provisioning profile for selected signing identity '{0}'.", identity.BundleId, identity.SigningKey);
                }
                else
                {
                    Log.LogError("Bundle identifier '{0}' does not match any installed provisioning profile.", identity.BundleId);
                }
            }

            return(!Log.HasLoggedErrors);
        }
        public override bool Execute()
        {
            var type     = MobileProvisionDistributionType.Any;
            var identity = new CodeSignIdentity();
            MobileProvisionPlatform  platform;
            IList <MobileProvision>  profiles;
            IList <X509Certificate2> certs;
            PDictionary plist;

            Log.LogTaskName("DetectSigningIdentity");
            Log.LogTaskProperty("AppBundleName", AppBundleName);
            Log.LogTaskProperty("AppManifest", AppManifest);
            Log.LogTaskProperty("Keychain", Keychain);
            Log.LogTaskProperty("ProvisioningProfile", ProvisioningProfile);
            Log.LogTaskProperty("RequireCodesigning", RequireCodeSigning);
            Log.LogTaskProperty("RequireProvisioningProfile", RequireProvisioningProfile);
            Log.LogTaskProperty("SdkPlatform", SdkPlatform);
            Log.LogTaskProperty("SdkIsSimulator", SdkIsSimulator);
            Log.LogTaskProperty("SigningKey", SigningKey);

            switch (SdkPlatform)
            {
            case "AppleTVSimulator":
            case "AppleTVOS":
                platform = MobileProvisionPlatform.tvOS;
                break;

            case "iPhoneSimulator":
            case "WatchSimulator":
            case "iPhoneOS":
            case "WatchOS":
                platform = MobileProvisionPlatform.iOS;
                break;

            case "MacOSX":
                platform = MobileProvisionPlatform.MacOS;
                break;

            default:
                Log.LogError("Unknown SDK platform: {0}", SdkPlatform);
                return(false);
            }

            if (ProvisioningProfile == AutomaticAppStoreProvision)
            {
                type = MobileProvisionDistributionType.AppStore;
            }
            else if (ProvisioningProfile == AutomaticInHouseProvision)
            {
                type = MobileProvisionDistributionType.InHouse;
            }
            else if (ProvisioningProfile == AutomaticAdHocProvision)
            {
                type = MobileProvisionDistributionType.AdHoc;
            }

            try {
                plist = PDictionary.FromFile(AppManifest);
            } catch (Exception ex) {
                Log.LogError(null, null, null, AppManifest, 0, 0, 0, 0, "Error loading '{0}': {1}", AppManifest, ex.Message);
                return(false);
            }

            DetectedCodesignAllocate = Path.Combine(DeveloperRoot, "Toolchains", "XcodeDefault.xctoolchain", "usr", "bin", "codesign_allocate");
            DetectedBundleVersion    = plist.GetCFBundleVersion();
            DetectedDistributionType = type.ToString();

            identity.BundleId = plist.GetCFBundleIdentifier();
            if (string.IsNullOrEmpty(identity.BundleId))
            {
                identity.BundleId = null;
            }

            if (Framework == PlatformFramework.MacOS && !RequireCodeSigning)
            {
                DetectedBundleId = identity.BundleId ?? GetDefaultBundleId(AppBundleName, null);
                DetectedAppId    = DetectedBundleId;

                ReportDetectedCodesignInfo();

                return(!Log.HasLoggedErrors);
            }

            try {
                var keychain = !string.IsNullOrEmpty(Keychain) ? SecKeychain.Open(Keychain) : SecKeychain.Default;

                if (string.IsNullOrEmpty(SigningKey) || MatchesAny(SigningKey, DevelopmentPrefixes))
                {
                    // Note: we treat an empty signing key as "developer automatic".
                    if (!TryGetSigningCertificates(keychain, out certs, DevelopmentPrefixes))
                    {
                        return(false);
                    }
                }
                else if (MatchesAny(SigningKey, AppStoreDistributionPrefixes))
                {
                    if (!TryGetSigningCertificates(keychain, out certs, AppStoreDistributionPrefixes))
                    {
                        return(false);
                    }
                }
                else if (MatchesAny(SigningKey, DirectDistributionPrefixes))
                {
                    if (!TryGetSigningCertificates(keychain, out certs, DirectDistributionPrefixes))
                    {
                        return(false);
                    }
                }
                else
                {
                    // The user has specified an exact name to match...
                    if (!TryGetSigningCertificates(keychain, out certs, SigningKey))
                    {
                        return(false);
                    }
                }
            } catch (Exception ex) {
                Log.LogError("{0}", ex.Message);
                return(false);
            }

            if (!RequireProvisioningProfile && string.IsNullOrEmpty(ProvisioningProfile))
            {
                if (certs.Count > 1)
                {
                    if (!string.IsNullOrEmpty(SigningKey))
                    {
                        Log.LogMessage(MessageImportance.Normal, "Multiple signing identities match '{0}'; using the first match.", SigningKey);
                    }
                    else
                    {
                        Log.LogMessage(MessageImportance.Normal, "Multiple signing identities found; using the first identity.");
                    }

                    for (int i = 0; i < certs.Count; i++)
                    {
                        Log.LogMessage(MessageImportance.Normal, "{0,3}. Signing Identity: {1} ({2})", i + 1,
                                       SecKeychain.GetCertificateCommonName(certs[i]), certs[i].Thumbprint);
                    }
                }

                codesignCommonName     = SecKeychain.GetCertificateCommonName(certs[0]);
                DetectedCodeSigningKey = certs[0].Thumbprint;

                DetectedBundleId = identity.BundleId ?? GetDefaultBundleId(AppBundleName, null);
                DetectedAppId    = DetectedBundleId;

                ReportDetectedCodesignInfo();

                return(!Log.HasLoggedErrors);
            }

            if (!IsAutoCodeSignProfile(ProvisioningProfile))
            {
                identity.Profile = MobileProvisionIndex.GetMobileProvision(platform, ProvisioningProfile);

                if (identity.Profile == null)
                {
                    Log.LogError("The specified " + PlatformName + " provisioning profile '{0}' could not be found", ProvisioningProfile);
                    return(false);
                }

                var profile = identity.Profile;                 // capture ref for lambda

                if (certs.Count > 0)
                {
                    identity.SigningKey = certs.FirstOrDefault(c => profile.DeveloperCertificates.Any(p => p.Thumbprint == c.Thumbprint));
                    if (identity.SigningKey == null)
                    {
                        Log.LogError("No " + PlatformName + " signing identities match the specified provisioning profile '{0}'.", ProvisioningProfile);
                        return(false);
                    }
                }

                if (identity.BundleId == null)
                {
                    identity.BundleId = GetDefaultBundleId(AppBundleName, GetProfileBundleId(identity.Profile));
                    Log.LogWarning(null, null, null, AppManifest, 0, 0, 0, 0, "Project does not have bundle identifier specified. Using '{0}' to match provisioning profile.", identity.BundleId);
                }

                identity.AppId = ConstructValidAppId(identity.Profile, identity.BundleId);
                if (identity.AppId == null)
                {
                    Log.LogError(null, null, null, AppManifest, 0, 0, 0, 0, "Project bundle identifier '{0}' does not match specified provisioning profile '{1}'", identity.BundleId, ProvisioningProfile);
                    return(false);
                }

                if (identity.SigningKey != null)
                {
                    codesignCommonName     = SecKeychain.GetCertificateCommonName(identity.SigningKey);
                    DetectedCodeSigningKey = identity.SigningKey.Thumbprint;
                }

                provisioningProfileName = identity.Profile.Name;

                DetectedProvisioningProfile = identity.Profile.Uuid;
                DetectedDistributionType    = identity.Profile.DistributionType.ToString();
                DetectedBundleId            = identity.BundleId;
                DetectedAppId = identity.AppId;

                ReportDetectedCodesignInfo();

                return(!Log.HasLoggedErrors);
            }

            if (identity.BundleId != null)
            {
                if (certs.Count > 0)
                {
                    profiles = MobileProvisionIndex.GetMobileProvisions(platform, identity.BundleId, type, certs);
                }
                else
                {
                    profiles = MobileProvisionIndex.GetMobileProvisions(platform, identity.BundleId, type);
                }
            }
            else if (certs.Count > 0)
            {
                profiles = MobileProvisionIndex.GetMobileProvisions(platform, type, certs);
            }
            else
            {
                profiles = MobileProvisionIndex.GetMobileProvisions(platform, type);
            }

            List <CodeSignIdentity> pairs;

            if (certs.Count > 0)
            {
                pairs = (from p in profiles
                         from c in certs
                         where p.DeveloperCertificates.Any(d => d.Thumbprint == c.Thumbprint)
                         select new CodeSignIdentity {
                    SigningKey = c, Profile = p
                }).ToList();

                if (pairs.Count == 0)
                {
                    Log.LogError("No installed provisioning profiles match the installed " + PlatformName + " signing identities.");
                    return(false);
                }
            }
            else
            {
                pairs = (from p in profiles select new CodeSignIdentity {
                    Profile = p
                }).ToList();
            }

            if (identity.BundleId != null)
            {
                var matches         = new List <CodeSignIdentity> ();
                int bestMatchLength = 0;
                int matchLength;

                // find matching provisioning profiles with compatible appid, keeping only those with the longest matching (wildcard) ids
                foreach (var pair in pairs)
                {
                    var appid = ConstructValidAppId(pair.Profile, identity.BundleId, out matchLength);
                    if (appid != null && matchLength >= bestMatchLength)
                    {
                        if (matchLength > bestMatchLength)
                        {
                            bestMatchLength = matchLength;
                            matches.Clear();
                        }

                        var match = identity.Clone();
                        match.SigningKey = pair.SigningKey;
                        match.Profile    = pair.Profile;
                        match.AppId      = appid;

                        matches.Add(match);
                    }
                }

                if (matches.Count == 0)
                {
                    Log.LogWarning(null, null, null, AppManifest, 0, 0, 0, 0, "No installed provisioning profiles match the bundle identifier.");
                }
                else
                {
                    if (matches.Count > 1)
                    {
                        var spaces = new string (' ', 3);

                        Log.LogMessage(MessageImportance.Normal, "Multiple provisioning profiles match the bundle identifier; using the first match.");

                        matches.Sort(new SigningIdentityComparer());

                        for (int i = 0; i < matches.Count; i++)
                        {
                            Log.LogMessage(MessageImportance.Normal, "{0,3}. Provisioning Profile: \"{1}\" ({2})", i + 1, matches[i].Profile.Name, matches[i].Profile.Uuid);

                            if (matches[i].SigningKey != null)
                            {
                                Log.LogMessage(MessageImportance.Normal, "{0}  Signing Identity: \"{1}\"", spaces, SecKeychain.GetCertificateCommonName(matches[i].SigningKey));
                            }
                        }
                    }

                    identity = matches[0];
                }
            }
            else
            {
                // pick a provisioning profile to provide appid and better default bundle identifier, preferring wildcard bundle identifiers
                foreach (var pair in pairs)
                {
                    var  suggestion = GetProfileBundleId(pair.Profile);
                    bool wildcard   = (suggestion != null) && suggestion.EndsWith("*", StringComparison.Ordinal);

                    if (wildcard || identity.Profile == null)
                    {
                        identity.Profile    = pair.Profile;
                        identity.SigningKey = pair.SigningKey;
                        identity.BundleId   = GetDefaultBundleId(AppBundleName, suggestion);
                        identity.AppId      = ConstructValidAppId(pair.Profile, identity.BundleId);
                    }

                    if (wildcard)
                    {
                        break;
                    }
                }

                Log.LogWarning(null, null, null, AppManifest, 0, 0, 0, 0, "No bundle identifier specified. Using '{0}' to match an installed provisioning profile.", identity.BundleId);
            }

            if (identity.Profile != null && identity.AppId != null)
            {
                codesignCommonName = identity.SigningKey != null?SecKeychain.GetCertificateCommonName(identity.SigningKey) : null;

                provisioningProfileName = identity.Profile.Name;

                DetectedCodeSigningKey      = identity.SigningKey?.Thumbprint;
                DetectedProvisioningProfile = identity.Profile.Uuid;
                DetectedBundleId            = identity.BundleId;
                DetectedAppId = identity.AppId;

                ReportDetectedCodesignInfo();
            }
            else
            {
                if (identity.SigningKey != null)
                {
                    Log.LogError("Bundle identifier '{0}' does not match any installed provisioning profile for selected signing identity '{0}'.", identity.BundleId, identity.SigningKey);
                }
                else
                {
                    Log.LogError("Bundle identifier '{0}' does not match any installed provisioning profile.", identity.BundleId);
                }
            }

            return(!Log.HasLoggedErrors);
        }