static public bool CopyFolder(string SourceFolder, string DestFolder, string DisplayDestFolder, bool bDeleteTarget, bool bLowercase) { if (DisplayDestFolder == null) { DisplayDestFolder = DestFolder; } DirectoryInfo SourceFolderInfo = new DirectoryInfo(SourceFolder); if (!SourceFolderInfo.Exists) { Program.Error("Source folder does not exist"); return(false); } Program.Log(" ... '" + SourceFolder + "' -> '" + DisplayDestFolder + "'"); DirectoryInfo DestFolderInfo = new DirectoryInfo(DestFolder); if (bDeleteTarget && DestFolderInfo.Exists) { DeleteDirectory(DestFolderInfo); } try { Directory.CreateDirectory(DestFolderInfo.FullName); } catch (Exception Ex) { Program.Error("Could not create folder: " + DestFolderInfo.FullName); Program.Error(" Exception: " + Ex.Message); Program.ReturnCode = (int)ErrorCodes.Error_CreateDirectory; } RecursiveFolderCopy(SourceFolderInfo, DestFolderInfo, bLowercase); return(true); }
public static MobileProvision ParseFile(byte[] RawData) { //@TODO: This file is just an ASN.1 stream, should find or make a raw ASN1 parser and use // that instead of this (theoretically fragile) code (particularly the length extraction) // Scan it for the start of the embedded blob of xml byte[] SearchPattern = Encoding.UTF8.GetBytes("<?xml"); for (int TextStart = 2; TextStart < RawData.Length - SearchPattern.Length; ++TextStart) { // See if this point is a match bool bMatch = true; for (int PatternIndex = 0; bMatch && (PatternIndex < SearchPattern.Length); ++PatternIndex) { bMatch = bMatch && (RawData[TextStart + PatternIndex] == SearchPattern[PatternIndex]); } if (bMatch) { // Back up two bytes and read a two byte little endian plist size int TextLength = (RawData[TextStart - 2] << 8) | (RawData[TextStart - 1]); // Convert the data to a string string PlistText = Encoding.UTF8.GetString(RawData, TextStart, TextLength); //@TODO: Distribution provisions seem to be a byte too long, and it may be a general problem with interpreting the length // For now, we just cut back to the end of the last tag. int CutPoint = PlistText.LastIndexOf('>'); PlistText = PlistText.Substring(0, CutPoint + 1); // Return the constructed 'mobile provision' return(new MobileProvision(PlistText)); } } // Unable to find the start of the plist data Program.Error("Failed to find embedded plist in .mobileprovision file"); return(null); }
public override Stream OpenRead(string RelativeFilename) { string FullPath = InternalRootPath + RelativeFilename; Stream ReadStream; byte[] PreviousData; if (PendingWrites.TryGetValue(FullPath, out PreviousData)) { ReadStream = new MemoryStream(PreviousData); } else { if (Zip.ContainsEntry(FullPath)) { ZipEntry Entry = Zip[FullPath]; if (Entry.UncompressedSize >= (2L * 1024 * 1024 * 1024)) { Entry.Extract(Path.GetTempPath()); ReadStream = File.OpenRead(Path.Combine(Path.GetTempPath(), Entry.FileName)); } else { ReadStream = new MemoryStream((int)Entry.UncompressedSize); Zip[FullPath].Extract(ReadStream); } } else { string ErrorMessage = String.Format("Expected \"{0}\" in IPA but it was not found", FullPath); Program.Error(ErrorMessage); throw new Exception(ErrorMessage); } } ReadStream.Position = 0; return(ReadStream); }
/// <summary> /// Export the certificate to a file /// </summary> static public void ExportCertificate() { string ProvisionWithPrefix; if (!FindMobileProvision("", out ProvisionWithPrefix)) { Program.Error("Missing provision"); return; } if (Config.Certificate == null) { Program.Error("Missing -Certificate=... argument"); return; } // export the signing certificate to a file MobileProvision Provision = MobileProvisionParser.ParseFile(ProvisionWithPrefix); var Certificate = CodeSignatureBuilder.FindCertificate(Provision); byte[] Data = Certificate.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, "A"); File.WriteAllBytes(Config.Certificate, Data); }
public static DeploymentInterface Get() { if (DeployTimeInstance == null) { if (DeploymentServerProcess == null) { DeploymentServerProcess = CreateDeploymentServerProcess(); } DeployTimeInstance = (DeploymentInterface)Activator.GetObject( typeof(DeploymentInterface), @"ipc://iPhonePackager/DeploymentServer_PID" + Process.GetCurrentProcess().Id.ToString()); } if (DeployTimeInstance == null) { Program.Error("Failed to connect to deployment server"); throw new Exception("Failed to connect to deployment server"); } DeployTimeInstance.SetReportingInterface(Reporter); return(DeployTimeInstance); }
/** * Safely delete a directory */ static public void DeleteDirectory( DirectoryInfo DirInfo ) { if( DirInfo.Exists ) { foreach( FileInfo Info in DirInfo.GetFiles() ) { try { Info.IsReadOnly = false; Info.Delete(); } catch( Exception Ex ) { Program.Error( "Could not delete file: " + Info.FullName ); Program.Error( " Exception: " + Ex.Message ); Program.ReturnCode = (int)ErrorCodes.Error_DeleteFile; } } foreach( DirectoryInfo SubDirInfo in DirInfo.GetDirectories() ) { DeleteDirectory( SubDirInfo ); } try { DirInfo.Delete(); } catch( Exception Ex ) { Program.Error( "Could not delete folder: " + DirInfo.FullName ); Program.Error( " Exception: " + Ex.Message ); Program.ReturnCode = (int)ErrorCodes.Error_DeleteDirectory; } } }
/** * Handle spawning of the RPCUtility with parameters */ public static bool RunRPCUtilty(string RPCCommand) { string CommandLine = ""; string WorkingFolder = ""; string DisplayCommandLine = ""; switch (RPCCommand.ToLowerInvariant()) { case "deletemacstagingfiles": Program.Log(" ... deleting staging files on the Mac"); DisplayCommandLine = "rm -rf Payload"; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = MacStagingRootDir; break; case "ensureprovisiondirexists": Program.Log(" ... creating provisioning profiles directory"); DisplayCommandLine = String.Format("mkdir -p ~/Library/MobileDevice/Provisioning\\ Profiles"); CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine; WorkingFolder = MacXcodeStagingDir; break; case "installprovision": // Note: The provision must have already been copied over to the Mac Program.Log(" ... installing .mobileprovision"); DisplayCommandLine = String.Format("cp -f {0} ~/Library/MobileDevice/Provisioning\\ Profiles", MacMobileProvisionFilename); CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine; WorkingFolder = MacXcodeStagingDir; break; case "removeprovision": Program.Log(" ... removing .mobileprovision"); DisplayCommandLine = String.Format("rm -f ~/Library/MobileDevice/Provisioning\\ Profiles/{0}", MacMobileProvisionFilename); CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine; WorkingFolder = MacXcodeStagingDir; break; case "setexec": // Note: The executable must have already been copied over Program.Log(" ... setting executable bit"); DisplayCommandLine = "chmod a+x \'" + RemoteExecutablePath + "\'"; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = MacStagingRootDir; break; case "prepackage": Program.Log(" ... running prepackage script remotely "); DisplayCommandLine = String.Format("sh prepackage.sh {0} IOS {1} {2}", Program.GameName, Program.GameConfiguration, Program.Architecture); CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine; WorkingFolder = MacXcodeStagingDir; break; case "makeapp": Program.Log(" ... making application (codesign, etc...)"); Program.Log(" Using signing identity '{0}'", Config.CodeSigningIdentity); DisplayCommandLine = CurrentBaseXCodeCommandLine; CommandLine = "\"" + MacXcodeStagingDir + "/..\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "/..\""; break; case "validation": Program.Log(" ... validating distribution package"); DisplayCommandLine = XcodeDeveloperDir + "Platforms/iPhoneOS.platform/Developer/usr/bin/Validation " + RemoteAppDirectory; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = MacStagingRootDir; break; case "deleteipa": Program.Log(" ... deleting IPA on Mac"); DisplayCommandLine = "rm -f " + Config.IPAFilenameOnMac; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = MacStagingRootDir; break; case "kill": Program.Log(" ... killing"); DisplayCommandLine = "killall " + Program.GameName; CommandLine = ". " + DisplayCommandLine; WorkingFolder = "."; break; case "strip": Program.Log(" ... stripping"); DisplayCommandLine = XcodeDeveloperDir + "Platforms/iPhoneOS.platform/Developer/usr/bin/strip '" + RemoteExecutablePath + "'"; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = MacStagingRootDir; break; case "resign": Program.Log("... resigning"); DisplayCommandLine = "bash -c '" + "chmod a+x ResignScript" + ";" + "./ResignScript" + "'"; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = MacStagingRootDir; break; case "zip": Program.Log(" ... zipping"); // NOTE: -y preserves symbolic links which is needed for iOS distro builds // -x excludes a file (excluding the dSYM keeps sizes smaller, and it shouldn't be in the IPA anyways) string dSYMName = "Payload/" + Program.GameName + Program.Architecture + ".app.dSYM"; DisplayCommandLine = String.Format("zip -q -r -y -{0} -T {1} Payload iTunesArtwork -x {2}/ -x {2}/* " + "-x {2}/Contents/ -x {2}/Contents/* -x {2}/Contents/Resources/ -x {2}/Contents/Resources/* " + " -x {2}/Contents/Resources/DWARF/ -x {2}/Contents/Resources/DWARF/*", (int)Config.RecompressionSetting, Config.IPAFilenameOnMac, dSYMName); CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = MacStagingRootDir; break; case "gendsym": Program.Log(" ... generating DSYM"); string ExePath = "Payload/" + Program.GameName + ".app/" + Program.GameName; string dSYMPath = Program.GameName + ".app.dSYM"; DisplayCommandLine = String.Format("dsymutil -o {0} {1}", dSYMPath, ExePath); CommandLine = "\"" + MacStagingRootDir + "\"" + DisplayCommandLine; WorkingFolder = MacStagingRootDir; break; default: Program.Error("Unrecognized RPC command"); return(false); } Program.Log(" ... working folder: " + WorkingFolder); Program.Log(" ... " + DisplayCommandLine); Program.Log(" ... full command: " + MacName + " " + CommandLine); bool bSuccess = false; if (Config.bUseRPCUtil) { Program.Log("Running RPC on " + MacName + " ... "); Process RPCUtil = new Process(); RPCUtil.StartInfo.FileName = @"..\RPCUtility.exe"; RPCUtil.StartInfo.UseShellExecute = false; RPCUtil.StartInfo.Arguments = MacName + " " + CommandLine; RPCUtil.StartInfo.RedirectStandardOutput = true; RPCUtil.StartInfo.RedirectStandardError = true; RPCUtil.OutputDataReceived += new DataReceivedEventHandler(OutputReceivedRemoteProcessCall); RPCUtil.ErrorDataReceived += new DataReceivedEventHandler(OutputReceivedRemoteProcessCall); RPCUtil.Start(); RPCUtil.BeginOutputReadLine(); RPCUtil.BeginErrorReadLine(); RPCUtil.WaitForExit(); bSuccess = (RPCUtil.ExitCode == 0); if (bSuccess == false) { Program.Error("RPCCommand {0} failed with return code {1}", RPCCommand, RPCUtil.ExitCode); switch (RPCCommand.ToLowerInvariant()) { case "installprovision": Program.Error("Ensure your access permissions for '~/Library/MobileDevice/Provisioning Profiles' are set correctly."); break; default: break; } } } else { Program.Log("Running SSH on " + MacName + " ... "); bSuccess = SSHCommandHelper.Command(MacName, DisplayCommandLine, WorkingFolder); } return(bSuccess); }
private void GenerateSigningRequestViaOpenSSL(string TargetCertRequestFileName, AsymmetricCipherKeyPair KeyPair) { // We expect openssl.exe to exist in the same directory as iPhonePackager string OpenSSLPath = Path.GetDirectoryName(Application.ExecutablePath) + @"\openssl.exe"; if (!File.Exists(OpenSSLPath)) { MessageBox.Show("A version of OpenSSL is required to generate certificate requests. Please place OpenSSL.exe in Binaries\\DotNET\\IOS", Config.AppDisplayName, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } string EffectiveBuildPath = (Program.GameName.Length > 0) ? Config.BuildDirectory : Path.GetFullPath("."); // Create a temporary file to write the key pair out to (in a format that OpenSSL understands) string KeyFileName = Path.GetTempFileName(); TextWriter KeyWriter = new StreamWriter(KeyFileName); PemWriter KeyWriterPEM = new PemWriter(KeyWriter); KeyWriterPEM.WriteObject(KeyPair); KeyWriter.Close(); // Create a temporary file containing the configuration settings to drive OpenSSL string ConfigFileName = Path.GetTempFileName(); TextWriter ConfigFile = new StreamWriter(ConfigFileName); ConfigFile.WriteLine("[ req ]"); ConfigFile.WriteLine("distinguished_name = req_distinguished_name"); ConfigFile.WriteLine("prompt = no"); ConfigFile.WriteLine("[ req_distinguished_name ]"); ConfigFile.WriteLine("emailAddress = {0}", EMailEditBox.Text); ConfigFile.WriteLine("commonName = {0}", CommonNameEditBox.Text); ConfigFile.WriteLine("countryName = {0}", System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName); ConfigFile.Close(); // Invoke OpenSSL to generate the certificate request Program.Log("Running OpenSSL to generate certificate request..."); string ResultsText; string Executable = OpenSSLPath; string Arguments = String.Format("req -new -nodes -out \"{0}\" -key \"{1}\" -config \"{2}\"", TargetCertRequestFileName, KeyFileName, ConfigFileName); Utilities.RunExecutableAndWait(Executable, Arguments, out ResultsText); Program.Log(ResultsText); if (!File.Exists(TargetCertRequestFileName)) { Program.Error("... Failed to generate certificate request"); } else { Program.Log("... Successfully generated certificate request '{0}'", TargetCertRequestFileName); } // Clean up the temporary files we created File.Delete(KeyFileName); File.Delete(ConfigFileName); }
/// <summary> /// Finds all valid installed provisions /// </summary> public static void FindProvisions(string CFBundleIdentifier) { if (!Directory.Exists(Config.ProvisionDirectory)) { Program.Error("Could not find provision directory '{0}'.", Config.ProvisionDirectory); Program.ReturnCode = (int)ErrorCodes.Error_ProvisionNotFound; return; } // cache the provision library string SelectedProvision = ""; string SelectedCert = ""; string SelectedFile = ""; int FoundName = -1; Dictionary <string, MobileProvision> ProvisionLibrary = new Dictionary <string, MobileProvision>(); foreach (string Provision in Directory.EnumerateFiles(Config.ProvisionDirectory, "*.mobileprovision")) { MobileProvision p = MobileProvisionParser.ParseFile(Provision); DateTime EffectiveDate = p.CreationDate; DateTime ExpirationDate = p.ExpirationDate; DateTime Now = DateTime.UtcNow; bool bCertTimeIsValid = (EffectiveDate < Now) && (ExpirationDate > Now); bool bValid = false; X509Certificate2 Cert = FindCertificate(p); if (Cert != null) { bValid = (Cert.NotBefore < Now) && (Cert.NotAfter > Now); } bool bPassesNameCheck = p.ApplicationIdentifier.Substring(p.ApplicationIdentifierPrefix.Length + 1) == CFBundleIdentifier; bool bPassesCompanyCheck = false; bool bPassesWildCardCheck = false; if (p.ApplicationIdentifier.Contains("*")) { string CompanyName = p.ApplicationIdentifier.Substring(p.ApplicationIdentifierPrefix.Length + 1); if (CompanyName != "*") { CompanyName = CompanyName.Substring(0, CompanyName.LastIndexOf(".")); bPassesCompanyCheck = CFBundleIdentifier.StartsWith(CompanyName); } else { bPassesWildCardCheck = true; } } bool bIsManaged = false; if (p.ProvisionName == "iOS Team Provisioning Profile: " + CFBundleIdentifier) { bIsManaged = true; } bool bDistribution = ((p.ProvisionedDeviceIDs.Count == 0) && !p.bDebug); string Validity = "VALID"; if (!bCertTimeIsValid) { Validity = "EXPIRED"; } else if (!bValid) { Validity = "NO_CERT"; } else if (!bPassesNameCheck && !bPassesWildCardCheck && !bPassesCompanyCheck) { Validity = "NO_MATCH"; } if (bIsManaged) { Validity = "MANAGED"; } if ((string.IsNullOrWhiteSpace(SelectedProvision) || FoundName < 2) && Validity == "VALID" && !bDistribution) { int Prev = FoundName; if (bPassesNameCheck) { FoundName = 2; } else if (bPassesCompanyCheck && FoundName < 1) { FoundName = 1; } else if (bPassesWildCardCheck && FoundName == -1) { FoundName = 0; } if (FoundName != Prev) { SelectedProvision = p.ProvisionName; SelectedFile = Path.GetFileName(Provision); SelectedCert = Cert.FriendlyName; } } Program.LogVerbose("PROVISION-File:{0},Name:{1},Validity:{2},StartDate:{3},EndDate:{4},Type:{5}", Path.GetFileName(Provision), p.ProvisionName, Validity, EffectiveDate.ToString(), ExpirationDate.ToString(), bDistribution ? "DISTRIBUTION" : "DEVELOPMENT"); } Program.LogVerbose("MATCHED-Provision:{0},File:{1},Cert:{2}", SelectedProvision, SelectedFile, SelectedCert); }
public void Error(string Line) { Program.Error(Line); }
public static bool ExecuteDeployCommand(string Command, string GamePath, string RPCCommand) { switch (Command.ToLowerInvariant()) { case "backup": { string ApplicationIdentifier = RPCCommand; if (ApplicationIdentifier == null) { ApplicationIdentifier = Utilities.GetStringFromPList("CFBundleIdentifier"); } if (Config.FilesForBackup.Count > 0) { if (!DeploymentHelper.Get().BackupFiles(ApplicationIdentifier, Config.FilesForBackup.ToArray())) { Program.Error("Failed to transfer manifest file from device to PC"); Program.ReturnCode = (int)ErrorCodes.Error_DeviceBackupFailed; } } else if (!DeploymentHelper.Get().BackupDocumentsDirectory(ApplicationIdentifier, Config.GetRootBackedUpDocumentsDirectory())) { Program.Error("Failed to transfer documents directory from device to PC"); Program.ReturnCode = (int)ErrorCodes.Error_DeviceBackupFailed; } } break; case "uninstall": { string ApplicationIdentifier = RPCCommand; if (ApplicationIdentifier == null) { ApplicationIdentifier = Utilities.GetStringFromPList("CFBundleIdentifier"); } if (!DeploymentHelper.Get().UninstallIPAOnDevice(ApplicationIdentifier)) { Program.Error("Failed to uninstall IPA on device"); Program.ReturnCode = (int)ErrorCodes.Error_AppUninstallFailed; } } break; case "deploy": case "install": { string IPAPath = GamePath; string AdditionalCommandline = Program.AdditionalCommandline; if (!String.IsNullOrEmpty(AdditionalCommandline) && !Config.bIterate) { // Read the mobile provision to check for issues FileOperations.ReadOnlyZipFileSystem Zip = new FileOperations.ReadOnlyZipFileSystem(IPAPath); try { // Compare the commandline embedded to prevent us from any unnecessary writing. byte[] CommandlineBytes = Zip.ReadAllBytes("ue4commandline.txt"); string ExistingCommandline = Encoding.UTF8.GetString(CommandlineBytes, 0, CommandlineBytes.Length); if (ExistingCommandline != AdditionalCommandline) { // Ensure we have a temp dir to stage our temporary ipa if (!Directory.Exists(Config.PCStagingRootDir)) { Directory.CreateDirectory(Config.PCStagingRootDir); } string TmpFilePath = Path.Combine(Path.GetDirectoryName(Config.PCStagingRootDir), Path.GetFileNameWithoutExtension(IPAPath) + ".tmp.ipa"); if (File.Exists(TmpFilePath)) { File.Delete(TmpFilePath); } File.Copy(IPAPath, TmpFilePath); // Get the project name: string ProjectFile = ExistingCommandline.Split(' ').FirstOrDefault(); // Write out the new commandline. FileOperations.ZipFileSystem WritableZip = new FileOperations.ZipFileSystem(TmpFilePath); byte[] NewCommandline = Encoding.UTF8.GetBytes(ProjectFile + " " + AdditionalCommandline); WritableZip.WriteAllBytes("ue4commandline.txt", NewCommandline); // We need to residn the application after the commandline file has changed. CodeSignatureBuilder CodeSigner = new CodeSignatureBuilder(); CodeSigner.FileSystem = WritableZip; CodeSigner.PrepareForSigning(); CodeSigner.PerformSigning(); WritableZip.Close(); // Set the deploying ipa path to our new ipa IPAPath = TmpFilePath; } } catch (System.Exception ex) { Program.Warning(String.Format("Failed to override the commandline.txt file: ({0})", ex.Message)); } Zip.Close(); } if (Config.bIterate) { string ApplicationIdentifier = RPCCommand; if (String.IsNullOrEmpty(ApplicationIdentifier)) { ApplicationIdentifier = Utilities.GetStringFromPList("CFBundleIdentifier"); } DeploymentHelper.Get().DeviceId = Config.DeviceId; if (!DeploymentHelper.Get().InstallFilesOnDevice(ApplicationIdentifier, Config.DeltaManifest)) { Program.Error("Failed to install Files on device"); Program.ReturnCode = (int)ErrorCodes.Error_FilesInstallFailed; } } else if (File.Exists(IPAPath)) { DeploymentHelper.Get().DeviceId = Config.DeviceId; if (!DeploymentHelper.Get().InstallIPAOnDevice(IPAPath)) { Program.Error("Failed to install IPA on device"); Program.ReturnCode = (int)ErrorCodes.Error_AppInstallFailed; } } else { Program.Error(String.Format("Failed to find IPA file: '{0}'", IPAPath)); Program.ReturnCode = (int)ErrorCodes.Error_AppNotFound; } } break; default: return(false); } return(true); }
public static void TryInstallingCertificate_PromptForKey(string CertificateFilename, bool ShowPrompt = true) { try { if (!String.IsNullOrEmpty(CertificateFilename) || ShowOpenFileDialog(CertificatesFilter, "Choose a code signing certificate to import", "", "", ref ChoosingFilesToInstallDirectory, out CertificateFilename)) { if (Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix) { // run certtool y to get the currently installed certificates CertToolData = ""; Process CertTool = new Process(); CertTool.StartInfo.FileName = "/usr/bin/security"; CertTool.StartInfo.UseShellExecute = false; CertTool.StartInfo.Arguments = "import \"" + CertificateFilename + "\" -k login.keychain"; CertTool.StartInfo.RedirectStandardOutput = true; CertTool.OutputDataReceived += new DataReceivedEventHandler(OutputReceivedCertToolProcessCall); CertTool.Start(); CertTool.BeginOutputReadLine(); CertTool.WaitForExit(); if (CertTool.ExitCode != 0) { // todo: provide some feedback that it failed } Console.Write(CertToolData); } else { // Load the certificate string CertificatePassword = ""; X509Certificate2 Cert = null; try { Cert = new X509Certificate2(CertificateFilename, CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); } catch (System.Security.Cryptography.CryptographicException ex) { // Try once with a password if (PasswordDialog.RequestPassword(out CertificatePassword)) { Cert = new X509Certificate2(CertificateFilename, CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); } else { // User cancelled dialog, rethrow throw ex; } } // If the certificate doesn't have a private key pair, ask the user to provide one if (!Cert.HasPrivateKey) { string ErrorMsg = "Certificate does not include a private key and cannot be used to code sign"; // Prompt for a key pair if (MessageBox(new IntPtr(0), "Next, please choose the key pair that you made when generating the certificate request.", Config.AppDisplayName, 0x00000000 | 0x00000040 | 0x00001000 | 0x00010000) == 1) { string KeyFilename; if (ShowOpenFileDialog(KeysFilter, "Choose the key pair that belongs with the signing certificate", "", "", ref ChoosingFilesToInstallDirectory, out KeyFilename)) { Cert = CryptoAdapter.CombineKeyAndCert(CertificateFilename, KeyFilename); if (Cert.HasPrivateKey) { ErrorMsg = null; } } } if (ErrorMsg != null) { throw new Exception(ErrorMsg); } } // Add the certificate to the store X509Store Store = new X509Store(); Store.Open(OpenFlags.ReadWrite); Store.Add(Cert); Store.Close(); } } } catch (Exception ex) { string ErrorMsg = String.Format("Failed to load or install certificate due to an error: '{0}'", ex.Message); Program.Error(ErrorMsg); System.Threading.Thread.Sleep(500); MessageBox(new IntPtr(0), ErrorMsg, Config.AppDisplayName, 0x00000000 | 0x00000010 | 0x00001000 | 0x00010000); } }
/// <summary> /// Copy the files always needed (even in a stub IPA) /// </summary> static public void CopyFilesNeededForMakeApp() { // Copy Info.plist over (modifiying it as needed) string SourcePListFilename = Utilities.GetPrecompileSourcePListFilename(); Utilities.PListHelper Info = Utilities.PListHelper.CreateFromFile(SourcePListFilename); // Edit the plist CookTime.UpdateVersion(Info); // Write out the <GameName>-Info.plist file to the xcode staging directory string TargetPListFilename = Path.Combine(Config.PCXcodeStagingDir, Program.GameName + "-Info.plist"); Directory.CreateDirectory(Path.GetDirectoryName(TargetPListFilename)); string OutString = Info.SaveToString(); OutString = OutString.Replace("${EXECUTABLE_NAME}", Program.GameName); OutString = OutString.Replace("${BUNDLE_IDENTIFIER}", Program.GameName.Replace("_", "")); // this is a temp way to inject the iphone 6 images without needing to upgrade everyone's plist // eventually we want to generate this based on what the user has set in the project settings string[] IPhoneConfigs = { "Default-IPhone6", "Landscape", "{375, 667}", "Default-IPhone6", "Portrait", "{375, 667}", "Default-IPhone6Plus-Landscape", "Landscape", "{414, 736}", "Default-IPhone6Plus-Portrait", "Portrait", "{414, 736}", "Default", "Landscape", "{320, 480}", "Default", "Portrait", "{320, 480}", "Default-568h", "Landscape", "{320, 568}", "Default-568h", "Portrait", "{320, 568}", "Default-IPhoneX-Landscape", "Landscape", "{375, 812}", "Default-IPhoneX-Portrait", "Portrait", "{375, 812}", }; StringBuilder NewLaunchImagesString = new StringBuilder("<key>UILaunchImages~iphone</key>\n\t\t<array>\n"); for (int ConfigIndex = 0; ConfigIndex < IPhoneConfigs.Length; ConfigIndex += 3) { NewLaunchImagesString.Append("\t\t\t<dict>\n"); NewLaunchImagesString.Append("\t\t\t\t<key>UILaunchImageMinimumOSVersion</key>\n"); NewLaunchImagesString.Append("\t\t\t\t<string>8.0</string>\n"); NewLaunchImagesString.Append("\t\t\t\t<key>UILaunchImageName</key>\n"); NewLaunchImagesString.AppendFormat("\t\t\t\t<string>{0}</string>\n", IPhoneConfigs[ConfigIndex + 0]); NewLaunchImagesString.Append("\t\t\t\t<key>UILaunchImageOrientation</key>\n"); NewLaunchImagesString.AppendFormat("\t\t\t\t<string>{0}</string>\n", IPhoneConfigs[ConfigIndex + 1]); NewLaunchImagesString.Append("\t\t\t\t<key>UILaunchImageSize</key>\n"); NewLaunchImagesString.AppendFormat("\t\t\t\t<string>{0}</string>\n", IPhoneConfigs[ConfigIndex + 2]); NewLaunchImagesString.Append("\t\t\t</dict>\n"); } // close it out NewLaunchImagesString.Append("\t\t\t</array>\n\t\t<key>UILaunchImages~ipad</key>"); OutString = OutString.Replace("<key>UILaunchImages~ipad</key>", NewLaunchImagesString.ToString()); byte[] RawInfoPList = Encoding.UTF8.GetBytes(OutString); File.WriteAllBytes(TargetPListFilename, RawInfoPList); Program.Log("Updating .plist: {0} --> {1}", SourcePListFilename, TargetPListFilename); string FinalMobileProvisionFilename = null; CurrentBaseXCodeCommandLine = GetBaseXcodeCommandline(); if (!Config.bAutomaticSigning) { // Copy the mobile provision file over string CFBundleIdentifier = null; Info.GetString("CFBundleIdentifier", out CFBundleIdentifier); string ProvisionWithPrefix; if (!FindMobileProvision(CFBundleIdentifier, out ProvisionWithPrefix)) { Program.Error("Unable to find mobileprovision"); return; } FinalMobileProvisionFilename = Path.Combine(Config.PCXcodeStagingDir, MacMobileProvisionFilename); FileOperations.CopyRequiredFile(ProvisionWithPrefix, FinalMobileProvisionFilename); // make sure this .mobileprovision file is newer than any other .mobileprovision file on the Mac (this file gets multiple games named the same file, // so the time stamp checking can fail when moving between games, a la the buildmachines!) File.SetLastWriteTime(FinalMobileProvisionFilename, DateTime.UtcNow); // copy the signing certificate over // export the signing certificate to a file MobileProvision Provision = MobileProvisionParser.ParseFile(ProvisionWithPrefix); var Certificate = CodeSignatureBuilder.FindCertificate(Provision); byte[] Data = Certificate.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, "A"); File.WriteAllBytes(Path.Combine(Config.PCXcodeStagingDir, MacSigningIdentityFilename), Data); Config.CodeSigningIdentity = Certificate.FriendlyName; // since the pipeline will use a temporary keychain that will contain only this certificate, this should be the only identity that will work if (Provision != null) { Config.bForDistribution = !Provision.bDebug; } // regenerate command with new found identity CurrentBaseXCodeCommandLine = GetBaseXcodeCommandline(); // get the UUID string AllText = File.ReadAllText(FinalMobileProvisionFilename); string UUID = ""; int idx = AllText.IndexOf("<key>UUID</key>"); if (idx > 0) { idx = AllText.IndexOf("<string>", idx); if (idx > 0) { idx += "<string>".Length; UUID = AllText.Substring(idx, AllText.IndexOf("</string>", idx) - idx); } } CurrentBaseXCodeCommandLine += String.Format(" PROVISIONING_PROFILE=" + UUID); } else { CurrentBaseXCodeCommandLine += " DEVELOPMENT_TEAM=" + Config.TeamID + " CODE_SIGN_STYLE=\"Automatic\" -allowProvisioningUpdates"; } // look for an entitlements file (optional) string SourceEntitlements = FileOperations.FindPrefixedFile(Config.BuildDirectory, Program.GameName + ".entitlements"); // set where to make the entitlements file ( string TargetEntitlements = Path.Combine(Config.PCXcodeStagingDir, Program.GameName + ".entitlements"); if (File.Exists(SourceEntitlements)) { FileOperations.CopyRequiredFile(SourceEntitlements, TargetEntitlements); } else { // we need to have something so Xcode will compile, so we just set the get-task-allow, since we know the value, // which is based on distribution or not (true means debuggable) File.WriteAllText(TargetEntitlements, string.Format("<plist><dict><key>get-task-allow</key><{0}/></dict></plist>", Config.bForDistribution ? "false" : "true")); } string ProjectFile = Config.RootRelativePath + @"Engine\Intermediate\ProjectFiles\UE4.xcodeproj\project.pbxproj"; if (Program.GameName != "UE4Game") { ProjectFile = Path.GetDirectoryName(Config.IntermediateDirectory) + @"\ProjectFiles\" + Program.GameName + @".xcodeproj\project.pbxproj"; } FileOperations.CopyRequiredFile(ProjectFile, Path.Combine(Config.PCXcodeStagingDir, @"project.pbxproj.datecheck")); // needs Mac line endings so it can be executed string SrcPath = @"..\..\..\Build\" + Config.OSString + @"\XcodeSupportFiles\prepackage.sh"; string DestPath = Path.Combine(Config.PCXcodeStagingDir, @"prepackage.sh"); Program.Log(" ... '" + SrcPath + "' -> '" + DestPath + "'"); string SHContents = File.ReadAllText(SrcPath); SHContents = SHContents.Replace("\r\n", "\n"); File.WriteAllText(DestPath, SHContents); CookTime.CopySignedFiles(); }
/// <summary> /// Finds all valid installed provisions /// </summary> public static void FindProvisions(string CFBundleIdentifier) { if (!Directory.Exists(Config.ProvisionDirectory)) { Program.Error("Could not find provision directory '{0}'.", Config.ProvisionDirectory); Program.ReturnCode = (int)ErrorCodes.Error_ProvisionNotFound; return; } // cache the provision library string SelectedProvision = ""; string SelectedCert = ""; string SelectedFile = ""; int FoundName = -1; Dictionary <string, MobileProvision> ProvisionLibrary = new Dictionary <string, MobileProvision>(); foreach (string ProvisionFile in Directory.EnumerateFiles(Config.ProvisionDirectory, "*.mobileprovision")) { MobileProvision Provision = MobileProvisionParser.ParseFile(ProvisionFile); ProvisionLibrary.Add(ProvisionFile, Provision); } // first sort all profiles so we look at newer ones first. IEnumerable <string> ProfileKeys = ProvisionLibrary.Select(KV => KV.Key) .OrderByDescending(K => ProvisionLibrary[K].CreationDate) .ToArray(); // note - all of this is a near duplicate of code in MobileProvisionUtilities, which other functions // in this class call to do similar work! // @todo - unify all of this. foreach (string ProvisionFile in ProfileKeys) { MobileProvision p = ProvisionLibrary[ProvisionFile]; DateTime EffectiveDate = p.CreationDate; DateTime ExpirationDate = p.ExpirationDate; DateTime Now = DateTime.UtcNow; bool bCertTimeIsValid = (EffectiveDate < Now) && (ExpirationDate > Now); bool bValid = false; X509Certificate2 Cert = FindCertificate(p); if (Cert != null) { bValid = (Cert.NotBefore.ToUniversalTime() < Now) && (Cert.NotAfter.ToUniversalTime() > Now); } bool bPassesNameCheck = p.ApplicationIdentifier.Substring(p.ApplicationIdentifierPrefix.Length + 1) == CFBundleIdentifier; bool bPassesCompanyCheck = false; bool bPassesWildCardCheck = false; if (p.ApplicationIdentifier.Contains("*")) { string CompanyName = p.ApplicationIdentifier.Substring(p.ApplicationIdentifierPrefix.Length + 1); if (CompanyName != "*") { CompanyName = CompanyName.Substring(0, CompanyName.LastIndexOf(".")); bPassesCompanyCheck = CFBundleIdentifier.StartsWith(CompanyName); } else { bPassesWildCardCheck = true; } } bool bIsManaged = false; if (p.ProvisionName == "iOS Team Provisioning Profile: " + CFBundleIdentifier) { bIsManaged = true; } bool bDistribution = ((p.ProvisionedDeviceIDs.Count == 0) && !p.bDebug); string Validity = "VALID"; if (!bCertTimeIsValid) { Validity = "EXPIRED"; } else if (!bValid) { Validity = "NO_CERT"; } else if (!bPassesNameCheck && !bPassesWildCardCheck && !bPassesCompanyCheck) { Validity = "NO_MATCH"; } if (bIsManaged) { Validity = "MANAGED"; } if ((string.IsNullOrWhiteSpace(SelectedProvision) || FoundName < 2) && Validity == "VALID" && !bDistribution) { int Prev = FoundName; if (bPassesNameCheck) { FoundName = 2; } else if (bPassesCompanyCheck && FoundName < 1) { FoundName = 1; } else if (bPassesWildCardCheck && FoundName == -1) { FoundName = 0; } if (FoundName != Prev) { SelectedProvision = p.ProvisionName; SelectedFile = Path.GetFileName(ProvisionFile); SelectedCert = CryptoAdapter.GetFriendlyNameFromCert(Cert); } } Program.LogVerbose("PROVISION-File:{0},Name:{1},Validity:{2},StartDate:{3},EndDate:{4},Type:{5}", Path.GetFileName(ProvisionFile), p.ProvisionName, Validity, EffectiveDate.ToString(), ExpirationDate.ToString(), bDistribution ? "DISTRIBUTION" : "DEVELOPMENT"); } Program.LogVerbose("MATCHED-Provision:{0},File:{1},Cert:{2}", SelectedProvision, SelectedFile, SelectedCert); }
/** * Using the stub IPA previously compiled on the Mac, create a new IPA with assets */ static public void RepackageIPAFromStub() { if (string.IsNullOrEmpty(Config.RepackageStagingDirectory) || !Directory.Exists(Config.RepackageStagingDirectory)) { Program.Error("Directory specified with -stagedir could not be found!"); return; } DateTime StartTime = DateTime.Now; CodeSignatureBuilder CodeSigner = null; // Clean the staging directory Program.ExecuteCommand("Clean", null); // Create a copy of the IPA so as to not trash the original ZipFile Zip = SetupWorkIPA(); if (Zip == null) { return; } string ZipWorkingDir = String.Format("Payload/{0}{1}.app/", Program.GameName, Program.Architecture); FileOperations.ZipFileSystem FileSystem = new FileOperations.ZipFileSystem(Zip, ZipWorkingDir); // Check for a staged plist that needs to be merged into the main one { // Determine if there is a staged one we should try to use instead string PossiblePList = Path.Combine(Config.RepackageStagingDirectory, "Info.plist"); if (File.Exists(PossiblePList)) { if (Config.bPerformResignWhenRepackaging) { Program.Log("Found Info.plist ({0}) in stage, which will be merged in with stub plist contents", PossiblePList); // Merge the two plists, using the staged one as the authority when they conflict byte[] StagePListBytes = File.ReadAllBytes(PossiblePList); string StageInfoString = Encoding.UTF8.GetString(StagePListBytes); byte[] StubPListBytes = FileSystem.ReadAllBytes("Info.plist"); Utilities.PListHelper StubInfo = new Utilities.PListHelper(Encoding.UTF8.GetString(StubPListBytes)); StubInfo.MergePlistIn(StageInfoString); // Write it back to the cloned stub, where it will be used for all subsequent actions byte[] MergedPListBytes = Encoding.UTF8.GetBytes(StubInfo.SaveToString()); FileSystem.WriteAllBytes("Info.plist", MergedPListBytes); } else { Program.Warning("Found Info.plist ({0}) in stage that will be ignored; IPP cannot combine it with the stub plist since -sign was not specified", PossiblePList); } } } // Get the name of the executable file string CFBundleExecutable; { // Load the .plist from the stub byte[] RawInfoPList = FileSystem.ReadAllBytes("Info.plist"); Utilities.PListHelper Info = new Utilities.PListHelper(Encoding.UTF8.GetString(RawInfoPList)); // Get the name of the executable file if (!Info.GetString("CFBundleExecutable", out CFBundleExecutable)) { throw new InvalidDataException("Info.plist must contain the key CFBundleExecutable"); } } // Tell the file system about the executable file name so that we can set correct attributes on // the file when zipping it up FileSystem.ExecutableFileName = CFBundleExecutable; // Prepare for signing if requested if (Config.bPerformResignWhenRepackaging) { // Start the resign process (load the mobileprovision and info.plist, find the cert, etc...) CodeSigner = new CodeSignatureBuilder(); CodeSigner.FileSystem = FileSystem; CodeSigner.PrepareForSigning(); // Merge in any user overrides that exist UpdateVersion(CodeSigner.Info); } // Empty the current staging directory FileOperations.DeleteDirectory(new DirectoryInfo(Config.PCStagingRootDir)); // we will zip files in the pre-staged payload dir string ZipSourceDir = Config.RepackageStagingDirectory; // Save the zip Program.Log("Saving IPA ..."); FilesBeingModifiedToPrintOut.Clear(); Zip.SaveProgress += UpdateSaveProgress; Zip.CompressionLevel = (Ionic.Zlib.CompressionLevel)Config.RecompressionSetting; // Add all of the payload files, replacing existing files in the stub IPA if necessary (should only occur for icons) { string SourceDir = Path.GetFullPath(ZipSourceDir); string[] PayloadFiles = Directory.GetFiles(SourceDir, "*.*", SearchOption.AllDirectories); foreach (string Filename in PayloadFiles) { // Get the relative path to the file (this implementation only works because we know the files are all // deeper than the base dir, since they were generated from a search) string AbsoluteFilename = Path.GetFullPath(Filename); string RelativeFilename = AbsoluteFilename.Substring(SourceDir.Length + 1).Replace('\\', '/'); string ZipAbsolutePath = String.Format("Payload/{0}{1}.app/{1}", Program.GameName, Program.Architecture, RelativeFilename); byte[] FileContents = File.ReadAllBytes(AbsoluteFilename); if (FileContents.Length == 0) { // Zero-length files added by Ionic cause installation/upgrade to fail on device with error 0xE8000050 // We store a single byte in the files as a workaround for now FileContents = new byte[1]; FileContents[0] = 0; } FileSystem.WriteAllBytes(RelativeFilename, FileContents); if ((FileContents.Length >= 1024 * 1024) || (Config.bVerbose)) { FilesBeingModifiedToPrintOut.Add(ZipAbsolutePath); } } } // Re-sign the executable if there is a signing context if (CodeSigner != null) { if (Config.OverrideBundleName != null) { CodeSigner.Info.SetString("CFBundleDisplayName", Config.OverrideBundleName); string CFBundleIdentifier; if (CodeSigner.Info.GetString("CFBundleIdentifier", out CFBundleIdentifier)) { CodeSigner.Info.SetString("CFBundleIdentifier", CFBundleIdentifier + "_" + Config.OverrideBundleName); } } CodeSigner.PerformSigning(); } // Stick in the iTunesArtwork PNG if available string iTunesArtworkPath = Path.Combine(Config.BuildDirectory, "iTunesArtwork"); if (File.Exists(iTunesArtworkPath)) { Zip.UpdateFile(iTunesArtworkPath, ""); } // Save the Zip Program.Log("Compressing files into IPA (-compress={1}).{0}", Config.bVerbose ? "" : " Only large files will be listed next, but other files are also being packaged.", Config.RecompressionSetting); FileSystem.Close(); TimeSpan ZipLength = DateTime.Now - StartTime; FileInfo FinalZipInfo = new FileInfo(Zip.Name); Program.Log(String.Format("Finished repackaging into {2:0.00} MB IPA, written to '{0}' (took {1:0.00} s for all steps)", Zip.Name, ZipLength.TotalSeconds, FinalZipInfo.Length / (1024.0f * 1024.0f))); }
/** * Copy a single file (deleting the existing file at the destination if it exists) */ static public bool CopyFile(FileInfo SourceFileInfo, string DestFileName, bool bCopyMustSucceed, int MaxRetryCount) { int CopyTryCount = 0; bool bFileCopiedSuccessfully = false; // if the file doesn't exist, but isn't needed, then just early out if (!bCopyMustSucceed && !SourceFileInfo.Exists) { return false; } if (Config.bVerbose) { Program.Log(String.Format(" Copy: {0} -> {1}, last modified at {2}", SourceFileInfo.FullName, DestFileName, SourceFileInfo.LastWriteTime)); } do { // If this isn't the first time through, sleep a little before trying again if (CopyTryCount > 0) { Thread.Sleep(1000); } CopyTryCount++; try { // Delete the destination file if it exists FileInfo DestInfo = new FileInfo(DestFileName); if (DestInfo.Exists) { DestInfo.IsReadOnly = false; DestInfo.Delete(); } Directory.CreateDirectory(Path.GetDirectoryName(DestFileName)); // Copy over the new file SourceFileInfo.CopyTo(DestFileName); // Make sure the destination file is writable DestInfo = new FileInfo(DestFileName); DestInfo.IsReadOnly = false; DestInfo.LastWriteTime = SourceFileInfo.LastWriteTime; // Sanity check if (DestInfo.Length != SourceFileInfo.Length) { throw new Exception("Error: File copy failed (source and destination are different sizes)"); } // Success! bFileCopiedSuccessfully = true; } catch( Exception Ex ) { Program.Warning("Failed to copy file to '" + DestFileName + "'"); Program.Warning(" Exception: " + Ex.Message + " (going to retry copy)"); } } while (!bFileCopiedSuccessfully && (CopyTryCount < MaxRetryCount)); if (!bFileCopiedSuccessfully) { string Msg = "Failed to copy file to " + DestFileName + " and there are no more retries!"; if (bCopyMustSucceed) { Program.Error(Msg); Program.ReturnCode = (int)ErrorCodes.Error_CopyFile; } else { Program.Warning(Msg); } } return bFileCopiedSuccessfully; }
/** * Handle spawning of the RPCUtility with parameters */ public static bool RunRPCUtilty(string RPCCommand, bool bIsSilent = false) { string CommandLine = ""; string WorkingFolder = ""; string DisplayCommandLine = ""; string TempKeychain = "$HOME/Library/Keychains/UE4TempKeychain.keychain"; string Certificate = "XcodeSupportFiles/" + MacSigningIdentityFilename; string LoginKeychain = "$HOME/Library/Keychains/login.keychain"; ErrorCodes Error = ErrorCodes.Error_Unknown; switch (RPCCommand.ToLowerInvariant()) { case "deletemacstagingfiles": Program.Log(" ... deleting staging files on the Mac"); DisplayCommandLine = "rm -rf Payload"; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "ensureprovisiondirexists": Program.Log(" ... creating provisioning profiles directory"); DisplayCommandLine = String.Format("mkdir -p ~/Library/MobileDevice/Provisioning\\ Profiles"); CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "\""; break; case "installprovision": // Note: The provision must have already been copied over to the Mac Program.Log(" ... installing .mobileprovision"); DisplayCommandLine = String.Format("cp -f {0} ~/Library/MobileDevice/Provisioning\\ Profiles", MacMobileProvisionFilename); CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "\""; break; case "removeprovision": Program.Log(" ... removing .mobileprovision"); DisplayCommandLine = String.Format("rm -f ~/Library/MobileDevice/Provisioning\\ Profiles/{0}", MacMobileProvisionFilename); CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "\""; break; case "setexec": // Note: The executable must have already been copied over Program.Log(" ... setting executable bit"); DisplayCommandLine = "chmod a+x \'" + RemoteExecutablePath + "\'"; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "prepackage": Program.Log(" ... running prepackage script remotely "); DisplayCommandLine = String.Format("sh prepackage.sh {0} " + Config.OSString + " {1} {2}", Program.GameName, Program.GameConfiguration, Program.Architecture); CommandLine = "\"" + MacXcodeStagingDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "\""; break; case "makeapp": Program.Log(" ... making application (codesign, etc...)"); Program.Log(" Using signing identity '{0}'", Config.CodeSigningIdentity); DisplayCommandLine = "security -v unlock-keychain -p \"A\" \"" + TempKeychain + "\" && " + CurrentBaseXCodeCommandLine; CommandLine = "\"" + MacXcodeStagingDir + "/..\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "/..\""; Error = ErrorCodes.Error_RemoteCertificatesNotFound; break; case "createkeychain": Program.Log(" ... creating temporary key chain with signing certificate"); Program.Log(" Using signing identity '{0}'", Config.CodeSigningIdentity); DisplayCommandLine = "security create-keychain -p \"A\" \"" + TempKeychain + "\" && security list-keychains -s \"" + TempKeychain + "\" && security list-keychains && security set-keychain-settings -t 3600 -l \"" + TempKeychain + "\" && security -v unlock-keychain -p \"A\" \"" + TempKeychain + "\" && security import " + Certificate + " -k \"" + TempKeychain + "\" -P \"A\" -T /usr/bin/codesign -T /usr/bin/security -t agg && CERT_IDENTITY=$(security find-identity -v -p codesigning \"" + TempKeychain + "\" | head -1 | grep '\"' | sed -e 's/[^\"]*\"//' -e 's/\".*//') && security default-keychain -s \"" + TempKeychain + "\" && security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k \"A\" -D \"$CERT_IDENTITY\" -t private " + TempKeychain; CommandLine = "\"" + MacXcodeStagingDir + "/..\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "/..\""; break; case "deletekeychain": Program.Log(" ... remove temporary key chain"); Program.Log(" Using signing identity '{0}'", Config.CodeSigningIdentity); DisplayCommandLine = "security list-keychains -s \"" + LoginKeychain + "\" && security delete-keychain \"" + TempKeychain + "\""; CommandLine = "\"" + MacXcodeStagingDir + "/..\" " + DisplayCommandLine; WorkingFolder = "\"" + MacXcodeStagingDir + "/..\""; break; case "validation": Program.Log(" ... validating distribution package"); DisplayCommandLine = XcodeDeveloperDir + "Platforms/iPhoneOS.platform/Developer/usr/bin/Validation " + RemoteAppDirectory; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "deleteipa": Program.Log(" ... deleting IPA on Mac"); DisplayCommandLine = "rm -f " + Config.IPAFilenameOnMac; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "kill": Program.Log(" ... killing"); DisplayCommandLine = "killall " + Program.GameName; CommandLine = ". " + DisplayCommandLine; WorkingFolder = "."; break; case "strip": Program.Log(" ... stripping"); DisplayCommandLine = "/usr/bin/xcrun strip '" + RemoteExecutablePath + "'"; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "resign": Program.Log("... resigning"); DisplayCommandLine = "bash -c '" + "chmod a+x ResignScript" + ";" + "./ResignScript" + "'"; CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "zip": Program.Log(" ... zipping"); // NOTE: -y preserves symbolic links which is needed for iOS distro builds // -x excludes a file (excluding the dSYM keeps sizes smaller, and it shouldn't be in the IPA anyways) string dSYMName = "Payload/" + Program.GameName + Program.Architecture + ".app.dSYM"; DisplayCommandLine = String.Format("zip -q -r -y -{0} -T {1} Payload iTunesArtwork -x {2}/ -x {2}/* " + "-x {2}/Contents/ -x {2}/Contents/* -x {2}/Contents/Resources/ -x {2}/Contents/Resources/* " + " -x {2}/Contents/Resources/DWARF/ -x {2}/Contents/Resources/DWARF/*", (int)Config.RecompressionSetting, Config.IPAFilenameOnMac, dSYMName); CommandLine = "\"" + MacStagingRootDir + "\" " + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; case "gendsym": Program.Log(" ... generating DSYM"); string ExePath = "Payload/" + Program.GameName + ".app/" + Program.GameName; string dSYMPath = Program.GameName + ".app.dSYM"; DisplayCommandLine = String.Format("dsymutil -o {0} {1}", dSYMPath, ExePath); CommandLine = "\"" + MacStagingRootDir + "\"" + DisplayCommandLine; WorkingFolder = "\"" + MacStagingRootDir + "\""; break; default: Program.Error("Unrecognized RPC command"); return(false); } Program.Log(" ... working folder: " + WorkingFolder); Program.Log(" ... " + DisplayCommandLine); Program.Log(" ... full command: " + MacName + " " + CommandLine); bool bSuccess = false; if (Config.bUseRPCUtil) { Program.Log("Running RPC on " + MacName + " ... "); Process RPCUtil = new Process(); RPCUtil.StartInfo.FileName = @"..\RPCUtility.exe"; RPCUtil.StartInfo.UseShellExecute = false; RPCUtil.StartInfo.Arguments = MacName + " " + CommandLine; RPCUtil.StartInfo.RedirectStandardOutput = true; RPCUtil.StartInfo.RedirectStandardError = true; RPCUtil.OutputDataReceived += new DataReceivedEventHandler(OutputReceivedRemoteProcessCall); RPCUtil.ErrorDataReceived += new DataReceivedEventHandler(OutputReceivedRemoteProcessCall); RPCUtil.Start(); RPCUtil.BeginOutputReadLine(); RPCUtil.BeginErrorReadLine(); RPCUtil.WaitForExit(); bSuccess = (RPCUtil.ExitCode == 0); if (bSuccess == false && !bIsSilent) { Program.Error("RPCCommand {0} failed with return code {1}", RPCCommand, RPCUtil.ExitCode); switch (RPCCommand.ToLowerInvariant()) { case "installprovision": Program.Error("Ensure your access permissions for '~/Library/MobileDevice/Provisioning Profiles' are set correctly."); break; default: break; } } } else { Program.Log("Running SSH on " + MacName + " ... "); bSuccess = SSHCommandHelper.Command(MacName, DisplayCommandLine, WorkingFolder); if (bSuccess == false && !bIsSilent) { Program.Error("RPCCommand {0} failed with return code {1}", RPCCommand, Error); Program.ReturnCode = (int)Error; } } return(bSuccess); }
public static void TryInstallingCertificate_PromptForKey() { try { string CertificateFilename; if (ShowOpenFileDialog(CertificatesFilter, "Choose a code signing certificate to import", "", "", ref ChoosingFilesToInstallDirectory, out CertificateFilename)) { // Load the certificate string CertificatePassword = ""; X509Certificate2 Cert = null; try { Cert = new X509Certificate2(CertificateFilename, CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); } catch (System.Security.Cryptography.CryptographicException ex) { // Try once with a password if (PasswordDialog.RequestPassword(out CertificatePassword)) { Cert = new X509Certificate2(CertificateFilename, CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); } else { // User cancelled dialog, rethrow throw ex; } } // If the certificate doesn't have a private key pair, ask the user to provide one if (!Cert.HasPrivateKey) { string ErrorMsg = "Certificate does not include a private key and cannot be used to code sign"; // Prompt for a key pair if (MessageBox.Show("Next, please choose the key pair that you made when generating the certificate request.", Config.AppDisplayName, MessageBoxButtons.OK, MessageBoxIcon.Information) == DialogResult.OK) { string KeyFilename; if (ShowOpenFileDialog(KeysFilter, "Choose the key pair that belongs with the signing certificate", "", "", ref ChoosingFilesToInstallDirectory, out KeyFilename)) { Cert = CryptoAdapter.CombineKeyAndCert(CertificateFilename, KeyFilename); if (Cert.HasPrivateKey) { ErrorMsg = null; } } } if (ErrorMsg != null) { throw new Exception(ErrorMsg); } } // Add the certificate to the store X509Store Store = new X509Store(); Store.Open(OpenFlags.ReadWrite); Store.Add(Cert); Store.Close(); } } catch (Exception ex) { string ErrorMsg = String.Format("Failed to load or install certificate due to an error: '{0}'", ex.Message); Program.Error(ErrorMsg); MessageBox.Show(ErrorMsg, Config.AppDisplayName, MessageBoxButtons.OK, MessageBoxIcon.Error); } }