forked from mirrors/gecko-dev
Backed out changeset e8d20bbb8f68 (bug 1876902) for causing rst lint failures in signing_macos_build.rst CLOSED TREE
This commit is contained in:
parent
dc95368b38
commit
08a71b5d1d
4 changed files with 0 additions and 838 deletions
|
|
@ -41,14 +41,6 @@ development process and source code documentation.
|
||||||
debugging/*
|
debugging/*
|
||||||
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:caption: Signing
|
|
||||||
:maxdepth: 1
|
|
||||||
:glob:
|
|
||||||
|
|
||||||
signing/*
|
|
||||||
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:caption: Additional Information
|
:caption: Additional Information
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
Signing Local macOS Builds
|
|
||||||
==========================
|
|
||||||
|
|
||||||
Background
|
|
||||||
----------
|
|
||||||
Firefox for macOS is mainly signed in one of two different ways: one for
|
|
||||||
production releases, and one for builds that run on try. Typically, developers
|
|
||||||
testing local builds don’t need to be concerned with signing unless they are
|
|
||||||
working on an area specifically affected by macOS entitlements such as passkeys,
|
|
||||||
loading third party libraries, or adding a new process type. However, it is
|
|
||||||
good practice to test builds that are as close as possible to the production
|
|
||||||
configuration or the try server configuration. Local builds are not signed
|
|
||||||
automatically and mach doesn’t include support for running tests on signed
|
|
||||||
builds. However, the mach command ``macos-sign`` can be used to sign local
|
|
||||||
packaged builds for manual testing. ``macos-sign`` supports signing builds to
|
|
||||||
match try or production builds.
|
|
||||||
|
|
||||||
Note: On Apple Silicon Macs, where all executables are required to be
|
|
||||||
signed, Firefox binaries will be “ad-hoc” self-signed automatically by the
|
|
||||||
linker during compilation, but this is a per-binary sign with no
|
|
||||||
Firefox-specific runtime settings or entitlements. This document ignores
|
|
||||||
automatic signing.
|
|
||||||
|
|
||||||
To sign your own local build so that it has the same set of entitlements as a
|
|
||||||
production release requires a signing certificate and provisioning profile
|
|
||||||
issued from Mozilla’s Apple Developer account. Entitlements are used to grant
|
|
||||||
Firefox certain permissions as well as impose security restrictions when it is
|
|
||||||
run on macOS. Some entitlements are considered restricted and can only be
|
|
||||||
enabled when signed by a certificate from an account that has been granted
|
|
||||||
permission to use the entitlement, such as the passkey macOS entitlement. As an
|
|
||||||
example of the production restrictions, production builds use an entitlement
|
|
||||||
that disallows debugger attachment. Disallowing debuggers is required for
|
|
||||||
distribution because it is required by the Notarization system. When signing
|
|
||||||
locally, developers can modify entitlements as needed.
|
|
||||||
|
|
||||||
Summary
|
|
||||||
-------
|
|
||||||
|
|
||||||
**Production build:** requires Mozilla's official Apple Developer ID
|
|
||||||
certificate, private key, and provisioning profile. Only Mozilla Release
|
|
||||||
Engineering has access to the official Developer ID certificate and private key.
|
|
||||||
Mozilla developers can request a limited-use Apple *Developer* certificate which
|
|
||||||
can be used to generated production-like builds for local testing. See
|
|
||||||
:ref:`like-prod` below.
|
|
||||||
|
|
||||||
**Developer build:** requires generating a self-signed certificate (to sign
|
|
||||||
like a try push is signed) or using ad-hoc signing (no setup required and only
|
|
||||||
usable locally), will have no passkey support (or any other restricted
|
|
||||||
entitlement), has fewer restrictions with respect to module loading, is
|
|
||||||
debuggable.
|
|
||||||
|
|
||||||
Signing Your Build Like try
|
|
||||||
---------------------------
|
|
||||||
To sign your own local build with entitlements that match what is used for try
|
|
||||||
push automated testing, generate a self-signed code signing certificate using
|
|
||||||
the macOS Keychain Access application. During the process, supply a unique name
|
|
||||||
not used by other Keychain entries making sure to not use any spaces. For
|
|
||||||
example, ``my-firefox-selfsign-cert-2024``. This string will be used as
|
|
||||||
the signing identity and passed to ``macos-sign`` with the ``-s`` option.
|
|
||||||
``./mach`` passes this to the codesign command which looks up the entry in the
|
|
||||||
keychain. When running the signing command, you'll be prompted to allow
|
|
||||||
``codesign`` to access the keychain entry. Select ``Always Allow`` when
|
|
||||||
prompted.
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ ./mach build package
|
|
||||||
$ open <path-to-dmg>
|
|
||||||
<drag Browser to the Desktop>
|
|
||||||
$ ./mach macos-sign -s my-firefox-selfsign-cert-2024 -a ~/Desktop/Nightly.app
|
|
||||||
|
|
||||||
The entitlements in the tree used for this configuration are labeled as
|
|
||||||
developer and we call this the developer build. Developer signed builds differ
|
|
||||||
from production signed in the following ways:
|
|
||||||
|
|
||||||
* They allow debugger attachment
|
|
||||||
* They allow loading of third party libraries in all processes
|
|
||||||
* They respect dyld environment variables
|
|
||||||
* They don’t include restricted entitlements such as the passkey entitlement
|
|
||||||
(and therefore passkeys can’t be used)
|
|
||||||
|
|
||||||
Ad-hoc Signing Your Build - Like try Signing, but Requires no Configuration and is For Local Use Only
|
|
||||||
-----------------------------------------------------------------------------------------------------
|
|
||||||
Omitting the ``-s`` option will use ad-hoc signing which requires no setup. The
|
|
||||||
build will be more limited than builds signed with a self-signed cert. Ad-hoc
|
|
||||||
signed builds are not verifiable or runnable on any other system. There is
|
|
||||||
little documentation available about the limitations.
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ ./mach build package
|
|
||||||
$ open <path-to-dmg>
|
|
||||||
<drag Browser to the Desktop>
|
|
||||||
$ ./mach macos-sign -a ~/Desktop/Nightly.app
|
|
||||||
|
|
||||||
.. _like-prod:
|
|
||||||
Signing Your Build Like Production
|
|
||||||
----------------------------------
|
|
||||||
To sign your local build like a production Firefox build, you’ll need an Apple
|
|
||||||
Developer signing certificate and provisioning profile issued from Mozilla's
|
|
||||||
Apple Developer account. Developers will be given a development-role login
|
|
||||||
allowing a signing certificate and provisioning profile to be generated. The
|
|
||||||
provisioning profile used with Development certificates limits the signed
|
|
||||||
application to Mozilla developer machines via a hardware ID. Employees can file
|
|
||||||
a bug `here <https://bugzilla.mozilla.org/enter_bug.cgi?product=App%20Stores&component=App%20Store%20Access>`__
|
|
||||||
to request an account. Once the developer's Apple account is setup as a member
|
|
||||||
of Mozilla's Apple account, Xcode can be used to download a Developer signing
|
|
||||||
certificate and provisioning profile for development use. Use Keychain Access to
|
|
||||||
get the codesigning identifier for your development cert which should be passed
|
|
||||||
as the ``-s`` codesigning identity to ``mach macos-sign``:
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ ./mach build package
|
|
||||||
$ open <path-to-dmg>
|
|
||||||
<drag Browser to the Desktop>
|
|
||||||
$ ./mach macos-sign -a ~/Desktop/Nightly.app -s <MOZILLA_DEVELOPER_CERT_ID>
|
|
||||||
|
|
||||||
Example: Re-Signing Official Nightly
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ ditto /Applications/Firefox\ Nightly.app ~/Desktop/FirefoxNightly.app
|
|
||||||
$ ./mach macos-sign -a ~/Desktop/FirefoxNightly.app
|
|
||||||
0:00.20 Using ad-hoc signing identity
|
|
||||||
0:00.20 Using nightly channel signing configuration
|
|
||||||
0:00.20 Using developer entitlements
|
|
||||||
0:00.20 Reading build config file /Users/me/r/mc/taskcluster/ci/config.yml
|
|
||||||
0:00.23 Stripping existing xattrs and signatures
|
|
||||||
0:01.91 Signing with codesign
|
|
||||||
0:02.72 Verification of signed app /Users/me/Desktop/FirefoxNightly.app OK
|
|
||||||
|
|
||||||
Example: Re-Signing Official Developer Edition With `rcodesign <https://crates.io/crates/apple-codesign>`__ Using a pkcs12 Certificate Key Pair
|
|
||||||
-----------------------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
More information about rcodesign can be found on the
|
|
||||||
`rust crate page <https://crates.io/crates/apple-codesign>`__ or
|
|
||||||
`github repo <https://github.com/indygreg/apple-platform-rs>`__. Certificates
|
|
||||||
can be exported from Keychain Access in .p12 format.
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
$ ditto /Applications/Firefox\ Developer\ Edition.app/ ~/Desktop/DevEdition.app
|
|
||||||
$ ./mach macos-sign -r -a ~/Desktop/DevEdition.app \
|
|
||||||
--rcodesign-p12-file ./myDevId.p12 \
|
|
||||||
--rcodesign-p12-password-file ./myDevId.p12.passwd
|
|
||||||
0:00.26 Using pkcs12 signing identity
|
|
||||||
0:00.26 Using devedition channel signing configuration
|
|
||||||
0:00.26 Using developer entitlements
|
|
||||||
0:00.26 Reading build config file /Users/me/r/mc/taskcluster/ci/config.yml
|
|
||||||
0:00.29 Stripping existing xattrs and signatures
|
|
||||||
0:02.09 Signing with rcodesign
|
|
||||||
0:11.16 Verification of signed app /Users/me/Desktop/DevEdition.app OK
|
|
||||||
|
|
@ -134,7 +134,6 @@ MACH_COMMANDS = {
|
||||||
"mach-debug-commands": MachCommandReference(
|
"mach-debug-commands": MachCommandReference(
|
||||||
"python/mach/mach/commands/commandinfo.py"
|
"python/mach/mach/commands/commandinfo.py"
|
||||||
),
|
),
|
||||||
"macos-sign": MachCommandReference("tools/signing/macos/mach_commands.py"),
|
|
||||||
"manifest": MachCommandReference("testing/mach_commands.py"),
|
"manifest": MachCommandReference("testing/mach_commands.py"),
|
||||||
"marionette-test": MachCommandReference("testing/marionette/mach_commands.py"),
|
"marionette-test": MachCommandReference("testing/marionette/mach_commands.py"),
|
||||||
"mochitest": MachCommandReference("testing/mochitest/mach_commands.py", ["test"]),
|
"mochitest": MachCommandReference("testing/mochitest/mach_commands.py", ["test"]),
|
||||||
|
|
|
||||||
|
|
@ -1,675 +0,0 @@
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
# file, # You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
import glob
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import plistlib
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
from mach.decorators import (
|
|
||||||
Command,
|
|
||||||
CommandArgument,
|
|
||||||
CommandArgumentGroup,
|
|
||||||
)
|
|
||||||
from mozbuild.base import MachCommandConditions as conditions
|
|
||||||
|
|
||||||
|
|
||||||
@Command(
|
|
||||||
"macos-sign",
|
|
||||||
category="misc",
|
|
||||||
description="Sign a built and packaged (./mach build package) Firefox "
|
|
||||||
"bundle on macOS. Limitations: 1) macos-sign doesn't support building "
|
|
||||||
"the .app built in the object dir in-place (for now) because it contains "
|
|
||||||
'symlinks. First use "./mach build package" to build a .dmg containing a '
|
|
||||||
"bundled .app. Then extract the .app from the dmg to a readable/writable "
|
|
||||||
"directory. The bundled app can be signed with macos-sign (using the -a "
|
|
||||||
"argument). 2) The signing configuration (which maps files in the .app "
|
|
||||||
"to entitlement files in the tree to be used when signing) is loaded from "
|
|
||||||
"the build configuration in the local repo. For example, when signing a "
|
|
||||||
"Release 120 build using mach from a revision of mozilla-central, "
|
|
||||||
"macos-sign will use the bundle ID to determine the signing should use the "
|
|
||||||
"Release channel entitlements, but the configuration used will be the "
|
|
||||||
"Release configuration as defined in the repo working directory, not the "
|
|
||||||
"configuration from the revision of the earlier 120 build.",
|
|
||||||
conditions=[conditions.is_firefox],
|
|
||||||
)
|
|
||||||
@CommandArgument(
|
|
||||||
"-v",
|
|
||||||
"--verbose",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
dest="verbose_arg",
|
|
||||||
help="Verbose output including the commands executed.",
|
|
||||||
)
|
|
||||||
# The app path could be a required positional argument, but let's reserve that
|
|
||||||
# for the future where the default will be to sign the locally built .app
|
|
||||||
# in-place in the object dir.
|
|
||||||
@CommandArgument(
|
|
||||||
"-a",
|
|
||||||
"--app-path",
|
|
||||||
required=True,
|
|
||||||
type=str,
|
|
||||||
dest="app_arg",
|
|
||||||
help="Path to the .app bundle to sign. This can not be the .app built "
|
|
||||||
"in the object dir because it contains symlinks and is unbundled. Use "
|
|
||||||
"an app generated with ./mach build package.",
|
|
||||||
)
|
|
||||||
@CommandArgument(
|
|
||||||
"-s",
|
|
||||||
"--signing-identity",
|
|
||||||
metavar="SIGNING_IDENTITY",
|
|
||||||
default=None,
|
|
||||||
type=str,
|
|
||||||
dest="signing_identity_arg",
|
|
||||||
help="The codesigning identity to be used when signing with the macOS "
|
|
||||||
"native codesign tool. By default ad-hoc self-signing will be used. ",
|
|
||||||
)
|
|
||||||
@CommandArgument(
|
|
||||||
"-e",
|
|
||||||
"--entitlements",
|
|
||||||
default="developer",
|
|
||||||
choices=["developer", "production", "production-without-restricted"],
|
|
||||||
type=str,
|
|
||||||
dest="entitlements_arg",
|
|
||||||
help="Whether to sign the build for development or production use. "
|
|
||||||
"By default, a developer signing is performed. This does not require "
|
|
||||||
"a certificate to be configured. Developer entitlements are limited "
|
|
||||||
"to be compatible with self-signing and to allow debugging. Production "
|
|
||||||
"entitlements require a valid Apple Developer ID certificate issued from "
|
|
||||||
"the organization's Apple Developer account (without one, signing will "
|
|
||||||
"succeed, but the build will not be usable) and a provisioning profile to "
|
|
||||||
"be added to the bundle or installed in macOS System Preferences. The "
|
|
||||||
"certificate may be a 'development' certificate issued by the account. The "
|
|
||||||
"Apple Developer account must have the necessary restricted entitlements "
|
|
||||||
"granted in order for the signed build to work correctly. Use "
|
|
||||||
"production-without-restricted if you have a Developer ID certificate "
|
|
||||||
"not associated with an account approved for the restricted entitlements.",
|
|
||||||
)
|
|
||||||
@CommandArgument(
|
|
||||||
"-c",
|
|
||||||
"--channel",
|
|
||||||
default="auto",
|
|
||||||
choices=["auto", "nightly", "devedition", "beta", "release"],
|
|
||||||
dest="channel_arg",
|
|
||||||
type=str,
|
|
||||||
help="Which channel build is being signed.",
|
|
||||||
)
|
|
||||||
@CommandArgumentGroup("rcodesign")
|
|
||||||
@CommandArgument(
|
|
||||||
"-r",
|
|
||||||
"--use_rcodesign",
|
|
||||||
group="rcodesign",
|
|
||||||
default=False,
|
|
||||||
dest="use_rcodesign_arg",
|
|
||||||
action="store_true",
|
|
||||||
help="Enables signing with rcodesign instead of codesign. With rcodesign, "
|
|
||||||
"only ad-hoc and pkcs12 signing is supported. To use a signing identity, "
|
|
||||||
"specify a pkcs12 file and password file. See rcodesign documentation for "
|
|
||||||
"more information.",
|
|
||||||
)
|
|
||||||
@CommandArgument(
|
|
||||||
"-f",
|
|
||||||
"--rcodesign-p12-file",
|
|
||||||
group="rcodesign",
|
|
||||||
metavar="RCODESIGN_P12_FILE_PATH",
|
|
||||||
default=None,
|
|
||||||
type=str,
|
|
||||||
dest="p12_file_arg",
|
|
||||||
help="The rcodesign pkcs12 file, passed to rcodesign without validation.",
|
|
||||||
)
|
|
||||||
@CommandArgument(
|
|
||||||
"-p",
|
|
||||||
"--rcodesign-p12-password-file",
|
|
||||||
group="rcodesign",
|
|
||||||
metavar="RCODESIGN_P12_PASSWORD_FILE_PATH",
|
|
||||||
default=None,
|
|
||||||
type=str,
|
|
||||||
dest="p12_password_file_arg",
|
|
||||||
help="The rcodesign pkcs12 password file, passed to rcodesign without "
|
|
||||||
"validation.",
|
|
||||||
)
|
|
||||||
def macos_sign(
|
|
||||||
command_context,
|
|
||||||
app_arg,
|
|
||||||
signing_identity_arg,
|
|
||||||
entitlements_arg,
|
|
||||||
channel_arg,
|
|
||||||
use_rcodesign_arg,
|
|
||||||
p12_file_arg,
|
|
||||||
p12_password_file_arg,
|
|
||||||
verbose_arg,
|
|
||||||
):
|
|
||||||
"""Signs a .app build with entitlements from the repo
|
|
||||||
|
|
||||||
Validates all the command line options, reads the signing config from
|
|
||||||
the repo to determine which entitlement files to use, signs the build
|
|
||||||
using either the native macOS codesign or rcodesign, and finally validates
|
|
||||||
the .app using codesign.
|
|
||||||
"""
|
|
||||||
command_context._set_log_level(verbose_arg)
|
|
||||||
|
|
||||||
# Check appdir and remove trailing slasshes
|
|
||||||
if not os.path.isdir(app_arg):
|
|
||||||
command_context.log(
|
|
||||||
logging.ERROR,
|
|
||||||
"macos-sign",
|
|
||||||
{"app": app_arg},
|
|
||||||
"ERROR: {app} is not a directory",
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
app = os.path.realpath(app_arg)
|
|
||||||
|
|
||||||
# With rcodesign, either both a p12 file and p12 password file should be
|
|
||||||
# provided or neither. If neither, the signing identity must be either '-'
|
|
||||||
# for ad-hoc or None.
|
|
||||||
rcodesign_p12_provided = False
|
|
||||||
if use_rcodesign_arg:
|
|
||||||
if p12_file_arg is None and p12_password_file_arg is not None:
|
|
||||||
command_context.log(
|
|
||||||
logging.ERROR,
|
|
||||||
"macos-sign",
|
|
||||||
{},
|
|
||||||
"ERROR: p12 password file with no p12 file, " "use both or neither",
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
if p12_file_arg is not None and p12_password_file_arg is None:
|
|
||||||
command_context.log(
|
|
||||||
logging.ERROR,
|
|
||||||
"macos-sign",
|
|
||||||
{},
|
|
||||||
"ERROR: p12 file with no p12 password file, " "use both or neither",
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
if p12_file_arg is not None and p12_password_file_arg is not None:
|
|
||||||
rcodesign_p12_provided = True
|
|
||||||
|
|
||||||
# Only rcodesign supports pkcs12 args
|
|
||||||
if not use_rcodesign_arg and (
|
|
||||||
p12_password_file_arg is not None or p12_file_arg is not None
|
|
||||||
):
|
|
||||||
command_context.log(
|
|
||||||
logging.ERROR,
|
|
||||||
"macos-sign",
|
|
||||||
{},
|
|
||||||
"ERROR: pkcs12 signing not supported with "
|
|
||||||
"native codesign, only rcodesign",
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Check the user didn't ask for a codesigning identity with rcodesign.
|
|
||||||
# Check the user didn't ask for ad-hoc signing AND rcodesign pkcs12 signing.
|
|
||||||
# Check the user didn't ask for ad-hoc signing with production entitlements.
|
|
||||||
# Self-signing and ad-hoc signing are both incompatible with production
|
|
||||||
# entitlements. (Library loading entitlements depend on the codesigning
|
|
||||||
# team ID which is not set on self-signed/ad-hoc signatures and requires an
|
|
||||||
# Apple-issued cert. Signing succeeds, but the bundle will not be
|
|
||||||
# launchable.)
|
|
||||||
if use_rcodesign_arg:
|
|
||||||
# With rcodesign, only accept "-s -" or no -s argument.
|
|
||||||
if not rcodesign_p12_provided:
|
|
||||||
if signing_identity_arg is not None and signing_identity_arg != "-s":
|
|
||||||
command_context.log(
|
|
||||||
logging.ERROR,
|
|
||||||
"macos-sign",
|
|
||||||
{},
|
|
||||||
"ERROR: rcodesign requires pkcs12 or " "ad-hoc signing",
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Did the user request a signing identity string and pkcs12 signing?
|
|
||||||
if rcodesign_p12_provided and signing_identity_arg is not None:
|
|
||||||
command_context.log(
|
|
||||||
logging.ERROR,
|
|
||||||
"macos-sign",
|
|
||||||
{},
|
|
||||||
"ERROR: both ad-hoc and pkcs12 signing " "requested",
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Is ad-hoc signing with production entitlements requested?
|
|
||||||
if (
|
|
||||||
(not rcodesign_p12_provided)
|
|
||||||
and (signing_identity_arg is None or signing_identity_arg == "-")
|
|
||||||
and (entitlements_arg != "developer")
|
|
||||||
):
|
|
||||||
command_context.log(
|
|
||||||
logging.ERROR,
|
|
||||||
"macos-sign",
|
|
||||||
{},
|
|
||||||
"ERROR: " "Production entitlements and self-signing are " "not compatible",
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# By default, use ad-hoc
|
|
||||||
signing_identity = None
|
|
||||||
signing_identity_label = None
|
|
||||||
if use_rcodesign_arg and rcodesign_p12_provided:
|
|
||||||
# signing_identity will not be used
|
|
||||||
signing_identity_label = "pkcs12"
|
|
||||||
elif signing_identity_arg is None or signing_identity_arg == "-":
|
|
||||||
signing_identity = "-"
|
|
||||||
signing_identity_label = "ad-hoc"
|
|
||||||
else:
|
|
||||||
signing_identity = signing_identity_arg
|
|
||||||
signing_identity_label = signing_identity_arg
|
|
||||||
|
|
||||||
command_context.log(
|
|
||||||
logging.INFO,
|
|
||||||
"macos-sign",
|
|
||||||
{"id": signing_identity_label},
|
|
||||||
"Using {id} signing identity",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Which channel are we signing? Set 'channel' based on 'channel_arg'.
|
|
||||||
channel = None
|
|
||||||
if channel_arg == "auto":
|
|
||||||
channel = auto_detect_channel(command_context, app)
|
|
||||||
else:
|
|
||||||
channel = channel_arg
|
|
||||||
command_context.log(
|
|
||||||
logging.INFO,
|
|
||||||
"macos-sign",
|
|
||||||
{"channel": channel},
|
|
||||||
"Using {channel} channel signing configuration",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Do we want production or developer entitlements? Set 'entitlements_key'
|
|
||||||
# based on 'entitlements_arg'. In the buildconfig, developer entitlements
|
|
||||||
# are labeled as "default".
|
|
||||||
entitlements_key = None
|
|
||||||
if entitlements_arg == "production":
|
|
||||||
entitlements_key = "production"
|
|
||||||
elif entitlements_arg == "developer":
|
|
||||||
entitlements_key = "default"
|
|
||||||
elif entitlements_arg == "production-without-restricted":
|
|
||||||
# We'll strip out restricted entitlements below.
|
|
||||||
entitlements_key = "production"
|
|
||||||
|
|
||||||
command_context.log(
|
|
||||||
logging.INFO,
|
|
||||||
"macos-sign",
|
|
||||||
{"ent": entitlements_arg},
|
|
||||||
"Using {ent} entitlements",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get a path to the config file which maps files in the .app
|
|
||||||
# bundle to their entitlement files in the tree (if any) to use
|
|
||||||
# when signing. There is a set of mappings for each combination
|
|
||||||
# of ({developer, production}, {nightly, devedition, release}).
|
|
||||||
# i.e., depending on the entitlements argument (production or dev)
|
|
||||||
# and the channel argument (nightly, devedition, or release) we'll
|
|
||||||
# use different entitlements.
|
|
||||||
sourcedir = command_context.topsrcdir
|
|
||||||
buildconfigpath = sourcedir + "/taskcluster/ci/config.yml"
|
|
||||||
|
|
||||||
command_context.log(
|
|
||||||
logging.INFO,
|
|
||||||
"macos-sign",
|
|
||||||
{"yaml": buildconfigpath},
|
|
||||||
"Reading build config file {yaml}",
|
|
||||||
)
|
|
||||||
|
|
||||||
with open(buildconfigpath, "r") as buildconfigfile:
|
|
||||||
parsedconfig = yaml.safe_load(buildconfigfile)
|
|
||||||
|
|
||||||
# Store all the mappings
|
|
||||||
signing_groups = parsedconfig["mac-signing"]["hardened-sign-config"][
|
|
||||||
"by-hardened-signing-type"
|
|
||||||
][entitlements_key]
|
|
||||||
|
|
||||||
command_context.log(
|
|
||||||
logging.INFO, "macos-sign", {}, "Stripping existing xattrs and signatures"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Remove extended attributes. Per Apple "Technical Note TN2206",
|
|
||||||
# code signing uses extended attributes to store signatures for
|
|
||||||
# non-Mach-O executables such as script files. We want to avoid
|
|
||||||
# any complications that might be caused by existing extended
|
|
||||||
# attributes.
|
|
||||||
xattr_cmd = ["xattr", "-cr", app]
|
|
||||||
run(command_context, xattr_cmd, capture_output=not verbose_arg)
|
|
||||||
|
|
||||||
# Remove existing signatures. The codesign command only replaces
|
|
||||||
# signatures if the --force option used. Remove all signatures so
|
|
||||||
# subsequent signing commands with different options will result
|
|
||||||
# in re-signing without requiring --force.
|
|
||||||
cs_reset_cmd = ["find", app, "-exec", "codesign", "--remove-signature", "{}", ";"]
|
|
||||||
run(command_context, cs_reset_cmd, capture_output=not verbose_arg)
|
|
||||||
|
|
||||||
if use_rcodesign_arg is True:
|
|
||||||
sign_with_rcodesign(
|
|
||||||
command_context,
|
|
||||||
verbose_arg,
|
|
||||||
signing_groups,
|
|
||||||
entitlements_arg,
|
|
||||||
channel,
|
|
||||||
app,
|
|
||||||
p12_file_arg,
|
|
||||||
p12_password_file_arg,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
sign_with_codesign(
|
|
||||||
command_context,
|
|
||||||
verbose_arg,
|
|
||||||
signing_groups,
|
|
||||||
signing_identity,
|
|
||||||
entitlements_arg,
|
|
||||||
channel,
|
|
||||||
app,
|
|
||||||
)
|
|
||||||
|
|
||||||
verify_result(command_context, app, verbose_arg)
|
|
||||||
|
|
||||||
|
|
||||||
def auto_detect_channel(ctx, app):
|
|
||||||
"""Detects the channel of the provided app (nightly, release, etc.)
|
|
||||||
|
|
||||||
Reads the CFBundleIdentifier from the provided apps Info.plist and
|
|
||||||
returns the appropriate channel string. Release and Beta builds use
|
|
||||||
org.mozilla.firefox for the CFBundleIdentifier. Nightly channel builds use
|
|
||||||
org.mozilla.nightly.
|
|
||||||
"""
|
|
||||||
# The bundle IDs for different channels. We use these strings to
|
|
||||||
# auto-detect the channel being signed. Different channels use
|
|
||||||
# different entitlement files.
|
|
||||||
NIGHTLY_BUNDLEID = "org.mozilla.nightly"
|
|
||||||
DEVEDITION_BUNDLEID = "org.mozilla.firefoxdeveloperedition"
|
|
||||||
# BETA uses the same bundle ID as Release
|
|
||||||
RELEASE_BUNDLEID = "org.mozilla.firefox"
|
|
||||||
|
|
||||||
info_plist = os.path.join(app, "Contents/Info.plist")
|
|
||||||
|
|
||||||
ctx.log(
|
|
||||||
logging.DEBUG, "macos-sign", {"plist": info_plist}, "Reading {plist} bundle ID"
|
|
||||||
)
|
|
||||||
|
|
||||||
process = subprocess.Popen(
|
|
||||||
["defaults", "read", info_plist, "CFBundleIdentifier"],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
)
|
|
||||||
out, err = process.communicate()
|
|
||||||
bundleid = out.decode("utf-8").strip()
|
|
||||||
|
|
||||||
ctx.log(
|
|
||||||
logging.DEBUG,
|
|
||||||
"macos-sign",
|
|
||||||
{"bundleid": bundleid},
|
|
||||||
"Found bundle ID {bundleid}",
|
|
||||||
)
|
|
||||||
|
|
||||||
if bundleid == NIGHTLY_BUNDLEID:
|
|
||||||
return "nightly"
|
|
||||||
elif bundleid == DEVEDITION_BUNDLEID:
|
|
||||||
return "devedition"
|
|
||||||
elif bundleid == RELEASE_BUNDLEID:
|
|
||||||
return "release"
|
|
||||||
else:
|
|
||||||
# Couldn't determine the channel from <info_plist>.
|
|
||||||
# Unrecognized bundle ID <bundleID>.
|
|
||||||
# Use the channel argument.
|
|
||||||
ctx.log(
|
|
||||||
logging.ERROR,
|
|
||||||
"macos-sign",
|
|
||||||
{"plist": info_plist},
|
|
||||||
"Couldn't read bundle ID from {plist}",
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def sign_with_codesign(
|
|
||||||
ctx, verbose_arg, signing_groups, signing_identity, entitlements_arg, channel, app
|
|
||||||
):
|
|
||||||
# Signing with codesign:
|
|
||||||
#
|
|
||||||
# For each signing_group in signing_groups, invoke codesign with the
|
|
||||||
# options and paths specified in the signging_group.
|
|
||||||
ctx.log(logging.INFO, "macos-sign", {}, "Signing with codesign")
|
|
||||||
|
|
||||||
for signing_group in signing_groups:
|
|
||||||
cs_cmd = ["codesign"]
|
|
||||||
cs_cmd.append("--sign")
|
|
||||||
cs_cmd.append(signing_identity)
|
|
||||||
|
|
||||||
if "deep" in signing_group and signing_group["deep"]:
|
|
||||||
cs_cmd.append("--deep")
|
|
||||||
if "force" in signing_group and signing_group["force"]:
|
|
||||||
cs_cmd.append("--force")
|
|
||||||
if "runtime" in signing_group and signing_group["runtime"]:
|
|
||||||
cs_cmd.append("--options")
|
|
||||||
cs_cmd.append("runtime")
|
|
||||||
|
|
||||||
entitlement_file = None
|
|
||||||
temp_files_to_cleanup = []
|
|
||||||
|
|
||||||
if "entitlements" in signing_group:
|
|
||||||
# This signing group has an entitlement file
|
|
||||||
cs_cmd.append("--entitlements")
|
|
||||||
|
|
||||||
# Given the type of build (dev, prod, or prod without restricted
|
|
||||||
# entitlements) and the channel we're going to sign, get the path
|
|
||||||
# to the entitlement file from the config.
|
|
||||||
if isinstance(signing_group["entitlements"], str):
|
|
||||||
# If the 'entitlements' key in the signing group maps to
|
|
||||||
# a string, it's a simple lookup.
|
|
||||||
entitlement_file = signing_group["entitlements"]
|
|
||||||
elif isinstance(signing_group["entitlements"], dict):
|
|
||||||
# If the 'entitlements' key in the signing group maps to
|
|
||||||
# a dict, the mapping from key to entitlement file is
|
|
||||||
# different for each channel:
|
|
||||||
if channel == "nightly":
|
|
||||||
entitlement_file = signing_group["entitlements"][
|
|
||||||
"by-build-platform"
|
|
||||||
]["default"]["by-project"]["mozilla-central"]
|
|
||||||
elif channel == "devedition":
|
|
||||||
entitlement_file = signing_group["entitlements"][
|
|
||||||
"by-build-platform"
|
|
||||||
][".*devedition.*"]
|
|
||||||
elif channel == "release" or channel == "beta":
|
|
||||||
entitlement_file = signing_group["entitlements"][
|
|
||||||
"by-build-platform"
|
|
||||||
]["default"]["by-project"]["default"]
|
|
||||||
else:
|
|
||||||
raise ("Unexpected channel")
|
|
||||||
|
|
||||||
# We now have an entitlement file for this signing group.
|
|
||||||
# If we are signing using production-without-restricted, strip out
|
|
||||||
# restricted entitlements and save the result in a temporary file.
|
|
||||||
if entitlements_arg == "production-without-restricted":
|
|
||||||
temp_ent_file = strip_restricted_entitlements(entitlement_file)
|
|
||||||
temp_files_to_cleanup.append(temp_ent_file)
|
|
||||||
cs_cmd.append(temp_ent_file)
|
|
||||||
else:
|
|
||||||
cs_cmd.append(entitlement_file)
|
|
||||||
|
|
||||||
for pathglob in signing_group["globs"]:
|
|
||||||
binary_paths = glob.glob(
|
|
||||||
os.path.join(app, pathglob.strip("/")), recursive=True
|
|
||||||
)
|
|
||||||
for binary_path in binary_paths:
|
|
||||||
cs_cmd.append(binary_path)
|
|
||||||
|
|
||||||
run(ctx, cs_cmd, capture_output=not verbose_arg, check=True)
|
|
||||||
|
|
||||||
for temp_file in temp_files_to_cleanup:
|
|
||||||
os.remove(temp_file)
|
|
||||||
|
|
||||||
|
|
||||||
def run(ctx, cmd, **kwargs):
|
|
||||||
cmd_as_str = " ".join(cmd)
|
|
||||||
ctx.log(logging.DEBUG, "macos-sign", {"cmd": cmd_as_str}, "[{cmd}]")
|
|
||||||
try:
|
|
||||||
subprocess.run(cmd, **kwargs)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
ctx.log(
|
|
||||||
logging.ERROR,
|
|
||||||
"macos-sign",
|
|
||||||
{"rc": e.returncode, "cmd": cmd_as_str, "prog": cmd[0]},
|
|
||||||
"{prog} subprocess failed with exit code {rc}. "
|
|
||||||
"See (-v) verbose output for command output. "
|
|
||||||
"Failing command: [{cmd}]",
|
|
||||||
)
|
|
||||||
sys.exit(e.returncode)
|
|
||||||
|
|
||||||
|
|
||||||
def verify_result(ctx, app, verbose_arg):
|
|
||||||
# Verbosely verify validity of signed app
|
|
||||||
cs_verify_cmd = ["codesign", "-vv", app]
|
|
||||||
try:
|
|
||||||
run(ctx, cs_verify_cmd, capture_output=not verbose_arg, check=True)
|
|
||||||
ctx.log(
|
|
||||||
logging.INFO,
|
|
||||||
"macos-sign",
|
|
||||||
{"app": app},
|
|
||||||
"Verification of signed app {app} OK",
|
|
||||||
)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
ctx.log(
|
|
||||||
logging.ERROR,
|
|
||||||
"macos-sign",
|
|
||||||
{"rc": e.returncode, "app": app},
|
|
||||||
"Verification of {app} failed with exit code {rc}",
|
|
||||||
)
|
|
||||||
sys.exit(e.returncode)
|
|
||||||
|
|
||||||
|
|
||||||
def sign_with_rcodesign(
|
|
||||||
ctx,
|
|
||||||
verbose_arg,
|
|
||||||
signing_groups,
|
|
||||||
entitlements_arg,
|
|
||||||
channel,
|
|
||||||
app,
|
|
||||||
p12_file_arg,
|
|
||||||
p12_password_file_arg,
|
|
||||||
):
|
|
||||||
# Signing with rcodesign:
|
|
||||||
#
|
|
||||||
# The rcodesign sign is a single rcodesign invocation with all necessary
|
|
||||||
# arguments included. rcodesign accepts signing options to be applied to
|
|
||||||
# an input path (the .app in this case). For inner bundle resources that
|
|
||||||
# have different codesigning settings, signing options are passed as
|
|
||||||
# scoped arguments in the form --option <relative-path>:<value>. For
|
|
||||||
# example, a different entitlement file is specified for the nested
|
|
||||||
# plugin-container.app with the following:
|
|
||||||
#
|
|
||||||
# --entitlements-xml-path \
|
|
||||||
# Contents/MacOS/plugin-container.app:/path/to/plugin-container.xml
|
|
||||||
#
|
|
||||||
# We iterate through the signing group and generate scoped arguments
|
|
||||||
# for each path to be signed. If the path is '/', it is the main signing
|
|
||||||
# input path and its options are specified as standard arguments.
|
|
||||||
ctx.log(logging.INFO, "macos-sign", {}, "Signing with rcodesign")
|
|
||||||
|
|
||||||
cs_cmd = ["rcodesign", "sign"]
|
|
||||||
if p12_file_arg is not None:
|
|
||||||
cs_cmd.append("--p12-file")
|
|
||||||
cs_cmd.append(p12_file_arg)
|
|
||||||
if p12_password_file_arg is not None:
|
|
||||||
cs_cmd.append("--p12-password-file")
|
|
||||||
cs_cmd.append(p12_password_file_arg)
|
|
||||||
|
|
||||||
temp_files_to_cleanup = []
|
|
||||||
|
|
||||||
for signing_group in signing_groups:
|
|
||||||
# Ignore the 'deep' and 'force' setting for rcodesign
|
|
||||||
group_runtime = "runtime" in signing_group and signing_group["runtime"]
|
|
||||||
|
|
||||||
entitlement_file = None
|
|
||||||
|
|
||||||
if "entitlements" in signing_group:
|
|
||||||
# Given the type of build (dev, prod, or prod without restricted
|
|
||||||
# entitlements) and the channel we're going to sign, get the path
|
|
||||||
# to the entitlement file from the config.
|
|
||||||
if isinstance(signing_group["entitlements"], str):
|
|
||||||
# If the 'entitlements' key in the signing group maps to
|
|
||||||
# a string, it's a simple lookup.
|
|
||||||
entitlement_file = signing_group["entitlements"]
|
|
||||||
elif isinstance(signing_group["entitlements"], dict):
|
|
||||||
# If the 'entitlements' key in the signing group maps to
|
|
||||||
# a dict, the mapping from key to entitlement file is
|
|
||||||
# different for each channel:
|
|
||||||
if channel == "nightly":
|
|
||||||
entitlement_file = signing_group["entitlements"][
|
|
||||||
"by-build-platform"
|
|
||||||
]["default"]["by-project"]["mozilla-central"]
|
|
||||||
elif channel == "devedition":
|
|
||||||
entitlement_file = signing_group["entitlements"][
|
|
||||||
"by-build-platform"
|
|
||||||
][".*devedition.*"]
|
|
||||||
elif channel == "release" or channel == "beta":
|
|
||||||
entitlement_file = signing_group["entitlements"][
|
|
||||||
"by-build-platform"
|
|
||||||
]["default"]["by-project"]["default"]
|
|
||||||
else:
|
|
||||||
raise ("Unexpected channel")
|
|
||||||
|
|
||||||
# We now have an entitlement file for this signing group.
|
|
||||||
# If we are signing using production-without-restricted, strip out
|
|
||||||
# restricted entitlements and save the result in a temporary file.
|
|
||||||
if entitlements_arg == "production-without-restricted":
|
|
||||||
entitlement_file = strip_restricted_entitlements(entitlement_file)
|
|
||||||
temp_files_to_cleanup.append(entitlement_file)
|
|
||||||
|
|
||||||
for pathglob in signing_group["globs"]:
|
|
||||||
binary_paths = glob.glob(
|
|
||||||
os.path.join(app, pathglob.strip("/")), recursive=True
|
|
||||||
)
|
|
||||||
for binary_path in binary_paths:
|
|
||||||
if pathglob == "/":
|
|
||||||
# This is the root of the app. Use these signing options
|
|
||||||
# without argument scoping.
|
|
||||||
if group_runtime:
|
|
||||||
cs_cmd.append("--code-signature-flags")
|
|
||||||
cs_cmd.append("runtime")
|
|
||||||
if entitlement_file is not None:
|
|
||||||
cs_cmd.append("--entitlements-xml-path")
|
|
||||||
cs_cmd.append(entitlement_file)
|
|
||||||
cs_cmd.append(binary_path)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# This is not the root of the app. Paths are convered to
|
|
||||||
# relative paths and signing options are specified as scoped
|
|
||||||
# arguments.
|
|
||||||
binary_path_relative = os.path.relpath(binary_path, app)
|
|
||||||
if group_runtime:
|
|
||||||
cs_cmd.append("--code-signature-flags")
|
|
||||||
scoped_arg = binary_path_relative + ":runtime"
|
|
||||||
cs_cmd.append(scoped_arg)
|
|
||||||
if entitlement_file is not None:
|
|
||||||
cs_cmd.append("--entitlements-xml-path")
|
|
||||||
scoped_arg = binary_path_relative + ":" + entitlement_file
|
|
||||||
cs_cmd.append(scoped_arg)
|
|
||||||
|
|
||||||
run(ctx, cs_cmd, capture_output=not verbose_arg, check=True)
|
|
||||||
|
|
||||||
for temp_file in temp_files_to_cleanup:
|
|
||||||
os.remove(temp_file)
|
|
||||||
|
|
||||||
|
|
||||||
def strip_restricted_entitlements(plist_file):
|
|
||||||
# Not a complete set. Update as needed. This is
|
|
||||||
# the set of restricted entitlements we use to date.
|
|
||||||
restricted_entitlements = [
|
|
||||||
"com.apple.developer.web-browser.public-key-credential",
|
|
||||||
"com.apple.application-identifier",
|
|
||||||
]
|
|
||||||
|
|
||||||
plist_file_obj = open(plist_file, "rb")
|
|
||||||
plist_data = plistlib.load(plist_file_obj, fmt=plistlib.FMT_XML)
|
|
||||||
for entitlement in restricted_entitlements:
|
|
||||||
if entitlement in plist_data:
|
|
||||||
del plist_data[entitlement]
|
|
||||||
|
|
||||||
_, temp_file_path = tempfile.mkstemp(prefix="mach-macos-sign.")
|
|
||||||
with open(temp_file_path, "wb") as temp_file_obj:
|
|
||||||
plistlib.dump(plist_data, temp_file_obj)
|
|
||||||
temp_file_obj.close()
|
|
||||||
|
|
||||||
return temp_file_path
|
|
||||||
Loading…
Reference in a new issue