зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset e8d20bbb8f68 (bug 1876902
) for causing rst lint failures in signing_macos_build.rst CLOSED TREE
This commit is contained in:
Родитель
dc95368b38
Коммит
08a71b5d1d
|
@ -41,14 +41,6 @@ development process and source code documentation.
|
|||
debugging/*
|
||||
|
||||
|
||||
.. toctree::
|
||||
:caption: Signing
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
signing/*
|
||||
|
||||
|
||||
.. toctree::
|
||||
:caption: Additional Information
|
||||
: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(
|
||||
"python/mach/mach/commands/commandinfo.py"
|
||||
),
|
||||
"macos-sign": MachCommandReference("tools/signing/macos/mach_commands.py"),
|
||||
"manifest": MachCommandReference("testing/mach_commands.py"),
|
||||
"marionette-test": MachCommandReference("testing/marionette/mach_commands.py"),
|
||||
"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
|
Загрузка…
Ссылка в новой задаче