[Bf-blender-cvs] [d2888558f10] codesign: Codesign: Hook up notarial office

Sergey Sharybin noreply at git.blender.org
Thu Dec 12 16:29:15 CET 2019


Commit: d2888558f10e64d44c5e3d124c61643aaeef2b7c
Author: Sergey Sharybin
Date:   Thu Dec 12 16:26:49 2019 +0100
Branches: codesign
https://developer.blender.org/rBd2888558f10e64d44c5e3d124c61643aaeef2b7c

Codesign: Hook up notarial office

===================================================================

M	build_files/buildbot/codesign/base_code_signer.py
M	build_files/buildbot/codesign/config_server_template.py
M	build_files/buildbot/codesign/macos_code_signer.py

===================================================================

diff --git a/build_files/buildbot/codesign/base_code_signer.py b/build_files/buildbot/codesign/base_code_signer.py
index a72662963bf..0481bcf2564 100644
--- a/build_files/buildbot/codesign/base_code_signer.py
+++ b/build_files/buildbot/codesign/base_code_signer.py
@@ -409,8 +409,43 @@ class BaseCodeSigner(metaclass=abc.ABCMeta):
         If the platform is different then it will only be printed allowing
         to verify logic of the code signing process.
         """
-        if platform != self.platform:
+
+        if platform != self.platform or True:
             logger_server.info(
                 f'Will run command for {platform}: {command}')
             return
+
+        logger_server.info(f'Running command: {command}')
         subprocess.run(command)
+
+    # TODO(sergey): What is the type annotation for the command?
+    def check_output_or_mock(self, command,
+                             platform: util.Platform,
+                             allow_nonzero_exit_code=False) -> str:
+        """
+        Run given command if current platform matches given one
+
+        If the platform is different then it will only be printed allowing
+        to verify logic of the code signing process.
+
+        If allow_nonzero_exit_code is truth then the output will be returned
+        even if application quit with non-zero exit code.
+        Otherwise an subprocess.CalledProcessError exception will be raised
+        in such case.
+        """
+
+        if platform != self.platform:
+            logger_server.info(
+                f'Will run command for {platform}: {command}')
+            return
+
+        if allow_nonzero_exit_code:
+            process = subprocess.Popen(command,
+                                       stdout=subprocess.PIPE,
+                                       stderr=subprocess.STDOUT)
+            output = process.communicate()[0]
+            return output.decode()
+
+        logger_server.info(f'Running command: {command}')
+        return subprocess.check_output(
+            command, stderr=subprocess.STDOUT).decode()
diff --git a/build_files/buildbot/codesign/config_server_template.py b/build_files/buildbot/codesign/config_server_template.py
index 326e76d0283..1d6ddc54380 100644
--- a/build_files/buildbot/codesign/config_server_template.py
+++ b/build_files/buildbot/codesign/config_server_template.py
@@ -50,6 +50,15 @@ MACOS_ENTITLEMENTS_FILE = \
 # NOTE: This identity is just an example from release/darwin/README.txt.
 MACOS_CODESIGN_IDENTITY = 'AE825E26F12D08B692F360133210AF46F4CF7B97'
 
+# User name (Apple ID) which will be used to request notarization.
+MACOS_XCRUN_USERNAME = 'me at example.com'
+
+# One-time application password which will be used to request notarization.
+MACOS_XCRUN_PASSWORD = '@keychain:altool-password'
+
+# Timeout in seconds within which the notarial office is supposed to reply.
+MACOS_NOTARIZE_TIMEOUT_IN_SECONDS = 60 * 60
+
 ################################################################################
 # Windows-specific configuration.
 
diff --git a/build_files/buildbot/codesign/macos_code_signer.py b/build_files/buildbot/codesign/macos_code_signer.py
index 279b77e1ec6..79df50ae674 100644
--- a/build_files/buildbot/codesign/macos_code_signer.py
+++ b/build_files/buildbot/codesign/macos_code_signer.py
@@ -19,7 +19,9 @@
 # <pep8 compliant>
 
 import logging
+import re
 import subprocess
+import time
 
 from pathlib import Path
 from typing import List
@@ -87,9 +89,27 @@ def is_bundle_executable_file(file: AbsoluteAndRelativeFileName) -> bool:
     return True
 
 
+def xcrun_field_value_from_output(field: str, output: str) -> str:
+    """
+    Get value of a given field from xcrun output.
+
+    If field is not found empty string is returned.
+    """
+
+    field_prefix = field + ': '
+    for line in output.splitlines():
+        line = line.strip()
+        if line.startswith(field_prefix):
+            return line[len(field_prefix):]
+    return ''
+
+
 class MacOSCodeSigner(BaseCodeSigner):
     def check_file_is_to_be_signed(
             self, file: AbsoluteAndRelativeFileName) -> bool:
+        if file.relative_filepath.name.startswith('.'):
+            return False
+
         if is_bundle_executable_file(file):
             return True
 
@@ -100,6 +120,9 @@ class MacOSCodeSigner(BaseCodeSigner):
 
         return file.relative_filepath.suffix in EXTENSIONS_TO_BE_SIGNED
 
+    ############################################################################
+    # Codesign.
+
     def codesign_remove_signature(
             self, file: AbsoluteAndRelativeFileName) -> None:
         """
@@ -222,6 +245,161 @@ class MacOSCodeSigner(BaseCodeSigner):
 
         return True
 
+    ############################################################################
+    # Notarization.
+
+    def notarize_get_bundle_id(self, file: AbsoluteAndRelativeFileName) -> str:
+        """
+        Get bundle ID which will be used to notarize DMG
+        """
+        name = file.relative_filepath.name
+        app_name = name.split('-', 2)[0].lower()
+
+        # TODO(sergey): Consider using "alpha" for buildbot builds.
+        return f'org.blenderfoundation.{app_name}.release'
+
+    def notarize_request(self, file) -> str:
+        """
+        Request notarization of the given file.
+
+        Returns UUID of the notarization request. If error occurred None is
+        returned instead of UUID.
+        """
+
+        bundle_id = self.notarize_get_bundle_id(file)
+        logger_server.info('Bundle ID: %s', bundle_id)
+
+        logger_server.info('Submitting file to the notarial office.')
+        command = [
+            'xcrun', 'altool', '--notarize-app', '--verbose',
+            '-f', file.absolute_filepath,
+            '--primary-bundle-id', bundle_id,
+            '--username', self.config.MACOS_XCRUN_USERNAME,
+            '--password', self.config.MACOS_XCRUN_PASSWORD]
+
+        output = self.check_output_or_mock(
+            command, util.Platform.MACOS, allow_nonzero_exit_code=True)
+
+        for line in output.splitlines():
+            line = line.strip()
+            if line.startswith('RequestUUID = '):
+                request_uuid = line[14:]
+                return request_uuid
+
+            # Check whether the package has been already submitted.
+            if 'The software asset has already been uploaded.' in line:
+                request_uuid = re.sub(
+                    '.*The upload ID is ([A-Fa-f0-9\-]+).*', '\\1', line)
+                logger_server.warning(
+                    f'The package has been already submitted under UUID {request_uuid}')
+                return request_uuid
+
+        logger_server.error('xcrun command did not report RequestUUID')
+        return None
+
+    def notarize_wait_result(self, request_uuid: str) -> bool:
+        """
+        Wait for until notarial office have a reply
+        """
+
+        logger_server.info(
+            'Waiting for a result from the notarization office.')
+
+        command = ['xcrun', 'altool',
+                   '--notarization-info', request_uuid,
+                   '--username', self.config.MACOS_XCRUN_USERNAME,
+                   '--password', self.config.MACOS_XCRUN_PASSWORD]
+
+        time_start = time.monotonic()
+        timeout_in_seconds = self.config.MACOS_NOTARIZE_TIMEOUT_IN_SECONDS
+
+        while True:
+            output = self.check_output_or_mock(command, util.Platform.MACOS)
+            # Parse status and message
+            status = xcrun_field_value_from_output('Status', output)
+            status_message = xcrun_field_value_from_output(
+                'Status Message', output)
+
+            # Review status.
+            if status:
+                if status == 'success':
+                    logger_server.info(
+                        'Package successfully notarized: %s', status_message)
+                    return True
+                elif status == 'invalid':
+                    logger_server.error(
+                        'Package notarization has failed: %s', status_message)
+                    return False
+                else:
+                    logger_server.info(
+                        'Unknown notarization status %s (%s)', status, status_message)
+
+            logger_server.info('Keep waiting for notarization office.')
+            time.sleep(30)
+
+            time_slept_in_seconds = time.monotonic() - time_start
+            if time_slept_in_seconds > timeout_in_seconds:
+                logger_server.error(
+                    "Notarial office didn't reply in %f seconds.",
+                    timeout_in_seconds)
+
+    def notarize_staple(self, file: AbsoluteAndRelativeFileName) -> bool:
+        """
+        Staple notarial label on the file
+        """
+
+        logger_server.info(
+            'Waiting for a result from the notarization office.')
+
+        command = ['xcrun', 'stapler', 'staple', '-v', file.absolute_filepath]
+        self.check_output_or_mock(command, util.Platform.MACOS)
+
+        return True
+
+    def notarize_dmg(self, file: AbsoluteAndRelativeFileName) -> bool:
+        """
+        Run entire pipeline to get DMG notarized.
+        """
+        logger_server.info('Begin notarization routines on %s',
+                           file.relative_filepath)
+
+        # Submit file for notarization.
+        request_uuid = self.notarize_request(file)
+        if not request_uuid:
+            return False
+        logger_server.info('Received Request UUID: %s', request_uuid)
+
+        # Wait for the status from the notarization office.
+        if not self.notarize_wait_result(request_uuid):
+            return False
+
+        # Staple.
+        if not self.notarize_staple(file):
+            return False
+
+        return True
+
+    def notarize_all_dmg(
+            self, files: List[AbsoluteAndRelativeFileName]) -> bool:
+        """
+        Notarize all DMG images from the input.
+
+        Images are supposed to be codesigned already.
+        """
+        for file in files:
+            if not file.relative_filepath.name.endswith('.dmg'):
+                continue
+            if not self.check_file_is_to_be_sig

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list