diff --git a/CraftSettings.ini.template b/CraftSettings.ini.template index 67907c821..8b20be23b 100644 --- a/CraftSettings.ini.template +++ b/CraftSettings.ini.template @@ -1,240 +1,250 @@ ## You can use cmake like variables for values in the same section ${Variable} ## or for variables from a different section ${Section:Variable}. ## See Paths/${DOWNLOADDIR} [General] ## Here you set the ABI to be used. ## #platform-#abi-#compiler ## Valid combinations are: ## windows-msvc[2015, 2017]_[32, 64]-[cl, clang] ## windows-mingw_[32, 64]-[gcc, clang] ## linux-[32, 64]-[gcc, clang] ## macos-[32, 64]-clang ## freebsd-[32, 64]-clang ABI = windows-msvc2015_64-cl ## This option should be set to False if you use the msvc 201X Express Edition 64bit compiler ## in all other cases, simply keep this option commented out #Native=False ## This option specifies which MSVC toolset to use. Leave this commented out for MSVC to pick ## the newest available toolset. ## This is an expert option, do not touch it unless you know exactly what it means. #MSVCToolset=14.16 ## Set the minimum supported MacOs version, this is also limited by Qt ## 10.13 https://github.com/qt/qtbase/blob/5.14/mkspecs/common/macx.conf#L8 ## 10.12 https://github.com/qt/qtbase/blob/5.13/mkspecs/common/macx.conf#L8 ## 10.12 https://github.com/qt/qtbase/blob/5.12/mkspecs/common/macx.conf#L8 #MacDeploymentTarget = 10.13 ## This option can be used to enable a notification backend. ## As soon as the buildprocess of a project has finished a notification will be displayed. ## Possible Backends: ## Snore: https://commits.kde.org/snorenotify. Snore supports multiple backends. ## Just 'craft snorenotify' ## To configure snorenotify for craft call 'snoresettings --appName snoresend' ## SnoreToast: simple command line util for Windows notifications Notify = SnoreToast ## Speed up the merging of packages by using hard links UseHardlinks = True ## Use ANSI colors for the logs and enable tools to use ANSI colors AllowAnsiColor = 1 [Variables] ## Values here are usually set by craft and can be used for dynamic values ## To override the variables, uncomment them ## The working directory of Craft #CraftRoot = ## The directory of the Craft clone #CraftDir = [Paths] ## This is the location of your python installation. ## This value must be set. Python = C:\PROGRA~1\Python36 ## Some applications may need python 2.7 #Python27 = C:\python27 ## Craft ist able to fetch and install Msys itself, but if you prefer to use ## your own installation specify it here #Msys = C:\msys ## Here you change the download directory. ## If you want, so you can share the same download directory between ## mingw and msvc. ## The default value is craft/../download #DownloadDir = C:\kde\download ## This option defines the location for git checkouts. ## The default value is craft/../download/git #KDEGitDir = ${DOWNLOADDIR}\git ## This option defines the location for svn checkouts. ## The default value is craft/../download/svn #KDESVNDir = ${DOWNLOADDIR}\svn ## This option defines the location where the ccache files are stored. ## The default location is KDEROOT/build/ccache #CCACHE_DIR = C:\CCACHE\kf5 [Compile] ## the buildtype of this installation ## Possible Values: ## Release ## RelWithDebInfo ## Debug ## MinSizeRel BuildType = RelWithDebInfo ## Whether to use ninja (default: False) UseNinja = True ## Whether to use ccache (only avalible with mingw compiler) #UseCCache = True ## This option can be used to override the default make program ## change the value to the path of the executable you want to use instead. #MakeProgram = jom ## Number of build jobs ## The default is number of cores or determined by the build tool #Jobs = 2 [CMake] ## Fetch the translations for KDE projects when build from git KDE_L10N_AUTO_TRANSLATIONS = OFF [ShortPath] ## The directory where the junctions are created. #JunctionDir= [Blueprints] ## The location where the default blueprints are stored ## This is especially useful when using multiple Craft setups #BlueprintRoot = ${Variables:CraftRoot}/etc/blueprints/locations ## The locations of the recipes ## You can specify additional external locations in ; separated list #Locations = C:\blueprints # Customer settings Settings = ${Variables:CraftRoot}/etc/BlueprintSettings.ini [BlueprintVersions] ## Allow to automatically update certain recipes once a day. EnableDailyUpdates = True [Packager] ## The location where generated installers are placed #Destination = ${Variables:CraftRoot}/tmp ## The archive type for packages. ## Possible values are: zip, 7z ## Todo: rename #7ZipArchiveType = 7z # id assigned to you by the Windows Store #AppxPublisherId = CN=98B52D9A-DF7C-493E-BADC-37004A92EFC8 ## If set this will override the default package type. ## Possible values are: ### SevenZipPackager: An image of the files installed by the package ### MSIFragmentPackager ### NullsoftInstallerPackager: A nsis based installer including all dependencies ### AppxPackager: Windows UWP style package including all dependencies ### CreateArchivePackager: An image including all dependencies #PackageType = SevenZipPackager ## Package the Source files too. PackageSrc = False ## Whether to package debug symbols ### by default debug symbols are stripped/removed from the package ### If PackageDebugSymbols is set to True, a separate archive with the symbols is created. PackageDebugSymbols = True ## A url to a Craft cache repository ## Sets a custom repository for the binary cache #RepositoryUrl = https://files.kde.org/craft/master/ ## Enable to fetch packages from a Craft cache repository ## See --use-cache and --no-cache in the Craft help. UseCache = True [CraftDebug] ## If you want to have verbose output, uncomment the following option ## and set it to positive integer for verbose output and to 0 ## (or disable it) for normal output. Currently the highest verbosity level ## is 3 (equal to 'craft -v -v -v'). level -1 equals 'craft -q' ## Default is Verbose = 0 #Verbose = 1 # Log environment, prints the current state of the environment before an application is run LogEnvironment = True PrintPutEnv = False ## Prints time spend on various craft tasks MeasureTime = False ## Dump internal state of craftSettings to kdesettings.ini.dump #DumpSettings = True ## Print function Deprecation messages LogDeprecated = True [Environment] ## All values defined here will be populated to the environment #GIT_COMMITTER_EMAIL = foo@bar.com ## Set the ssh client for git and svn. #GIT_SSH = plink #SVN_SSH = plink [QtSDK] ## For advanced users only ## Whether to use prebuild Qt binaries. Enabled = False ## The path to the Qt sdk. Path = D:\Qt ## The version of Qt. Version = 5.11.1 ## The compiler version, if you are not sure what to use, have a look into the directory set in QtSDK/Path. ## The compiler must be of the same type as General/KDECOMPILER. ## If you are using mingw please make sure you have installed the mingw using the Qt installer. Compiler = mingw482_32 [ContinuousIntegration] ## Changes the verbosity of some sub processes. ## Installed versions must match. ## Don't write to stderr Enabled = False ## Delete the build folder after the install ClearBuildFolder = False [CodeSigning] ## Whether to use code signing Enabled = False ## Enable if your cetificate is password protected or on mac if your keychain need to be unlocked Protected = False ## Already sign the cache, this speeds up packaging on Windows as most deployed binaries won't need signing every time SignCache = ${CodeSigning:Enabled} #Certificate = C:\CraftRoot\mycert.pfx CommonName = K Desktop Environment e.V. Organization = K Desktop Environment e.V. Locality = Berlin Country = DE State = +## Mac code signing +################### +## Your mac deveoper id asigned with your certificates +MacDeveloperId = K Desktop Environment e.V. (5433B4KXM8) +## Specify the certificates directly instead of using a certificate from standard +## keychain +## The path to the Application certigicate +#MacCertificateApplication = +## The path to the Installer certigicate +#MacCertificateInstaller = [Version] ConfigVersion = 6 diff --git a/bin/Utils/CodeSign.py b/bin/Utils/CodeSign.py index 36ff47259..dc50d8ab5 100644 --- a/bin/Utils/CodeSign.py +++ b/bin/Utils/CodeSign.py @@ -1,150 +1,190 @@ # -*- coding: utf-8 -*- # Copyright Hannah von Reth # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. import os from pathlib import Path import subprocess +import tempfile from CraftCore import CraftCore from CraftOS.osutils import OsUtils, LockFile from CraftSetupHelper import SetupHelper -from Utils import CraftChoicePrompt +from Utils import CraftChoicePrompt, CraftCache import utils def signWindows(fileNames : [str]) -> bool: if not CraftCore.settings.getboolean("CodeSigning", "Enabled", False): return True if not CraftCore.compiler.isWindows: CraftCore.log.warning("Code signing is currently only supported on Windows") return True signTool = CraftCore.cache.findApplication("signtool", forceCache=True) if not signTool: env = SetupHelper.getMSVCEnv() signTool = CraftCore.cache.findApplication("signtool", env["PATH"], forceCache=True) if not signTool: CraftCore.log.warning("Code signing requires a VisualStudio installation") return False command = [signTool, "sign", "/tr", "http://timestamp.digicert.com", "/td", "SHA256", "/fd", "SHA256", "/a"] certFile = CraftCore.settings.get("CodeSigning", "Certificate", "") subjectName = CraftCore.settings.get("CodeSigning", "CommonName", "") certProtected = CraftCore.settings.getboolean("CodeSigning", "Protected", False) kwargs = dict() if certFile: command += ["/f", certFile] if subjectName: command += ["/n", subjectName] if certProtected: password = CraftChoicePrompt.promptForPassword(message='Enter the password for your package signing certificate', key="WINDOWS_CODE_SIGN_CERTIFICATE_PASSWORD") command += ["/p", password] kwargs["secret"] = [password] if True or CraftCore.debug.verbose() > 0: command += ["/v"] else: command += ["/q"] for args in utils.limitCommandLineLength(command, fileNames): if not utils.system(args, **kwargs): return False return True +class _MacSignScoe(LockFile, utils.ScopedEnv): + __REAL_HOME = None + def __init__(self): + LockFile.__init__(self, "keychainLock") + # ci setups tend to mess with the env and we need the users real home + if not _MacSignScoe.__REAL_HOME: + user = subprocess.getoutput("id -un") + _MacSignScoe.__REAL_HOME = Path("/Users") / user + utils.ScopedEnv.__init__(self, {"HOME": str(_MacSignScoe.__REAL_HOME)}) + self.loginKeychain = CraftCore.settings.get("CodeSigning", "MacKeychainPath", os.path.expanduser("~/Library/Keychains/login.keychain")) + self.certFileApplication = CraftCore.settings.get("CodeSigning", "MacCertificateApplication", "") + self.certFilesInstaller = CraftCore.settings.get("CodeSigning", "MacCertificateInstaller", "") + self.__loginKeychainTemp = None + if self._useCertFile: + self.__loginKeychainTemp = tempfile.TemporaryDirectory() + self.loginKeychain = Path(self.__loginKeychainTemp.name) / "craft.keychain" + + @property + def _useCertFile(self): + return self.certFileApplication or self.certFilesInstaller + + def __unlock(self): + password = "" + if CraftCore.settings.getboolean("CodeSigning", "Protected", False): + password = CraftChoicePrompt.promptForPassword(message="Enter the password for your signing keychain", key="MAC_KEYCHAIN_PASSWORD") + if self._useCertFile: + if not utils.system(["security", "create-keychain", "-p", password, self.loginKeychain], stdout=subprocess.DEVNULL, secret=[password]): + return False + def importCert(cert, pwKey): + pw = CraftChoicePrompt.promptForPassword(message=f"Enter the password for your package signing certificate: {Path(cert).name}", key=pwKey) + return utils.system(["security", "import", cert, "-k", self.loginKeychain, "-P", pw, "-T", "/usr/bin/codesign", "-T", "/usr/bin/productsign"], stdout=subprocess.DEVNULL, secret=[password]) + if self.certFileApplication: + if not importCert(self.certFileApplication, "MAC_CERTIFICATE_APPLICATION_PASSWORD"): + return False + if self.certFilesInstaller: + if not importCert(self.certFilesInstaller, "MAC_CERTIFICATE_INSTALLER_PASSWORD"): + return False + + if password: + if not utils.system(["security", "unlock-keychain", "-p", password, self.loginKeychain], stdout=subprocess.DEVNULL, secret=[password]): + CraftCore.log.error("Failed to unlock keychain.") + return False + + if not utils.system(["security", "set-key-partition-list", "-S", "apple-tool:,apple:,codesign:", "-s" ,"-k", password, self.loginKeychain], stdout=subprocess.DEVNULL, secret=[password]): + CraftCore.log.error("Failed to set key partition list.") + return False + return True + + def __enter__(self): + LockFile.__enter__(self) + utils.ScopedEnv.__enter__(self) + if not self.__unlock(): + raise Exception("Failed to setup keychain") + return None + return self + + def __exit__(self, exc_type, exc_value, trback): + if self._useCertFile: + utils.system(["security", "delete-keychain", self.loginKeychain]) + self.__loginKeychainTemp.cleanup() + utils.ScopedEnv.__exit__(self, exc_type, exc_value, trback) + LockFile.__exit__(self, exc_type, exc_value, trback) + def signMacApp(appPath : str): if not CraftCore.settings.getboolean("CodeSigning", "Enabled", False): return True # special case, two independent setups of craft might want to sign at the same time and only one keychain can be unlocked at a time - with LockFile("keychainLock"): + with _MacSignScoe() as scope: devID = CraftCore.settings.get("CodeSigning", "MacDeveloperId") - loginKeychain = CraftCore.settings.get("CodeSigning", "MacKeychainPath", os.path.expanduser("~/Library/Keychains/login.keychain")) - - if CraftCore.settings.getboolean("CodeSigning", "Protected", False): - if not unlockMacKeychain(loginKeychain): - return False # Recursively sign app - if not utils.system(["codesign", "--keychain", loginKeychain, "--sign", f"Developer ID Application: {devID}", "--force", "--preserve-metadata=entitlements", "--options", "runtime", "--verbose=99", "--deep", appPath]): + if not utils.system(["codesign", "--keychain", scope.loginKeychain, "--sign", f"Developer ID Application: {devID}", "--force", "--preserve-metadata=entitlements", "--options", "runtime", "--verbose=99", "--deep", appPath]): return False ## Verify signature if not utils.system(["codesign", "--display", "--verbose", appPath]): return False if not utils.system(["codesign", "--verify", "--verbose", "--strict", appPath]): return False # TODO: this step might require notarisation utils.system(["spctl", "-a", "-t", "exec", "-vv", appPath]) ## Validate that the key used for signing the binary matches the expected TeamIdentifier ## needed to pass the SocketApi through the sandbox #if not utils.system("codesign -dv %s 2>&1 | grep 'TeamIdentifier=%s'" % (self.appPath, teamIdentifierFromConfig)): #return False return True def signMacPackage(packagePath : str): if not CraftCore.settings.getboolean("CodeSigning", "Enabled", False): return True # special case, two independent setups of craft might want to sign at the same time and only one keychain can be unlocked at a time - with LockFile("keychainLock"): + with _MacSignScoe() as scope: packagePath = Path(packagePath) devID = CraftCore.settings.get("CodeSigning", "MacDeveloperId") - loginKeychain = CraftCore.settings.get("CodeSigning", "MacKeychainPath", os.path.expanduser("~/Library/Keychains/login.keychain")) - - if CraftCore.settings.getboolean("CodeSigning", "Protected", False): - if not unlockMacKeychain(loginKeychain): - return False if packagePath.name.endswith(".dmg"): # sign dmg - if not utils.system(["codesign", "--force", "--keychain", loginKeychain, "--sign", f"Developer ID Application: {devID}", packagePath]): + if not utils.system(["codesign", "--force", "--keychain", scope.loginKeychain, "--sign", f"Developer ID Application: {devID}", packagePath]): return False # TODO: this step would require notarisation # verify dmg signature utils.system(["spctl", "-a", "-t", "open", "--context", "context:primary-signature", packagePath]) else: # sign pkg packagePathTmp = f"{packagePath}.sign" - if not utils.system(["productsign", "--keychain", loginKeychain, "--sign", f"Developer ID Installer: {devID}", packagePath, packagePathTmp]): + if not utils.system(["productsign", "--keychain", scope.loginKeychain, "--sign", f"Developer ID Installer: {devID}", packagePath, packagePathTmp]): return False utils.moveFile(packagePathTmp, packagePath) - return True - - -def unlockMacKeychain(loginKeychain : str): - password = CraftChoicePrompt.promptForPassword(message='Enter the password for your package signing certificate', key="MAC_KEYCHAIN_PASSWORD") - - if not utils.system(["security", "unlock-keychain", "-p", password, loginKeychain], stdout=subprocess.DEVNULL, secret=[password]): - CraftCore.log.error("Failed to unlock keychain.") - return False - - if not utils.system(["security", "set-key-partition-list", "-S", "apple-tool:,apple:,codesign:", "-s" ,"-k", password, loginKeychain], stdout=subprocess.DEVNULL, secret=[password]): - CraftCore.log.error("Failed to set key partition list.") - return False - - return True \ No newline at end of file + return True \ No newline at end of file