go-macos-codesign
A pure Go library and Cobra/Viper CLI for Apple code signatures. The implementation
currently signs, inspects, verifies, and removes ad-hoc and RSA/ECDSA
certificate-backed Mach-O signatures, including a bounded macOS app-bundle
profile with XML/binary metadata, resource sealing, nested Mach-O helpers/dylibs,
apps, plug-ins, XPC services/extensions and unversioned or single-version frameworks.
It also signs, inspects and verifies UDIF DMGs using
go-apfs-v2 directly. Certificate verification uses explicit
leaf-certificate pins or caller-supplied CA roots. It is not yet a complete replacement for Apple
codesign. Full certificate policy, multiple framework versions, broader symlink/xattr policy,
additional disk-image forms, and other requirements remain open in the
compatibility inventory. Versioned releases document
that supported subset; the full-parity audit remains incomplete.
The CLI runs on Linux, macOS, and Windows without Apple frameworks, subprocess
helpers, CGO, an Apple SDK, or Clang. Clang and Apple tools are used only for
development research and independent macOS acceptance testing.
The current implementation includes portable certificate chains and Team IDs,
authenticated PKCS#12 import, and online RFC 3161 timestamps. The
progress report records delivered milestones, the tested
commit and actual CI evidence. App bundles now bind Info.plist
and deterministic CodeResources, including child designated requirements and
--deep signing/verification. UDIF signing reuses the APFS project's footer
model; wider format and policy coverage remains open. Supported bundle resources
include relative symlinks, sealed by their target text.
Build
Use Go 1.27.1 or newer and GoReleaser 2.18.1. From a checkout:
goreleaser check
goreleaser build --snapshot --clean --parallelism 2
make build runs the same command. It builds macoscodesign for Linux, Darwin,
and Windows on amd64 and arm64 with CGO_ENABLED=0. Outputs are under dist/.
For archives, SPDX SBOMs and SHA-256 checksums, install Syft and run:
goreleaser release --snapshot --clean --parallelism 2 --skip=sign
make snapshot runs that command. Snapshot mode creates local artifacts without
publishing. The GoReleaser configuration is shared by local builds
and CI. Windows packages are ZIP files; the other targets use tar.gz.
Tagged releases add keyless Cosign signatures to the checksum file. Release
Please owns version tags and release notes; release setup
documents the App/PAT credentials and artifact verification.
Build from a checkout: the module uses a local dependency replacement described
in NOTICE, so go install ...@version is not supported for the CLI.
CLI examples
With the built macoscodesign binary on your path:
macoscodesign -s - -i org.example.hello --timestamp=none ./hello
macoscodesign -vv ./hello
macoscodesign -dvvvv ./hello
macoscodesign -fs - -i org.example.hello --entitlements entitlements.plist ./hello
macoscodesign -v '-R=identifier "org.example.hello"' ./hello
macoscodesign --remove-signature ./hello
# A supported Contents-based app bundle:
macoscodesign -s - --timestamp=none ./Example.app
macoscodesign --verify ./Example.app
macoscodesign -dvvvv ./Example.app
# A supported app containing helpers, apps, plug-ins, XPC services or frameworks:
macoscodesign -s - --deep --timestamp=none ./Example.app
macoscodesign --verify --deep ./Example.app
# A single-segment UDIF disk image:
macoscodesign -s - -i org.example.image --timestamp=none ./Example.dmg
macoscodesign --verify ./Example.dmg
# Portable certificate identity and trust inputs (unencrypted PEM):
macoscodesign -s certificate.pem --key private-key.pem --timestamp=none ./hello
macoscodesign --verify --trust certificate.pem ./hello
# PKCS#12 identity with an Apple timestamp and explicit verification trust:
macoscodesign -fs identity.p12 --password-file password.txt --timestamp ./hello
macoscodesign --verify --trust-root code-root.pem --timestamp-root apple ./hello
Signing accepts the ad-hoc identity -, a PEM file, or a PKCS#12 file. A combined certificate
and private-key PEM file can be passed directly to -s; separate files use
--key. These are portable extensions, not native keychain-name lookup.
--trust-root enables portable CA chain verification. PKCS#12 identities use
-s identity.p12 --password-file password.txt. --trust pins the complete leaf
certificate; it does not accept CA anchors or
claim Apple trust-policy equivalence. See certificate signing
for supported formats and limits. Timestamped files additionally require
--timestamp-root tsa-root.pem or --timestamp-root apple for bundled Apple
roots. RFC 3161 support includes online signing with
--timestamp (Apple TSA) or --timestamp=http://URL, portable verification and
library callbacks. The CLI supports grouped short
options and Apple's overloaded -v. -h means native process hosting and reports
unsupported; use --help for the portable help extension. Unsupported features
return errors and are not counted as implemented.
--json provides a structured inspection/verification report. --config FILE,
or MACOSCODESIGN_CONFIG, loads an explicitly selected Viper configuration.
Currently only the portable json presentation option is configurable; native
signature defaults do not depend on ambient configuration.
Library
err := codesign.Sign(ctx, path, codesign.SignOptions{
Identifier: "org.example.hello",
})
if err != nil {
return err
}
report, err := codesign.Verify(ctx, path, codesign.VerifyOptions{})
Import github.com/deploymenttheory/go-macos-codesign/pkg/codesign.
SignBytes, InspectBytes, VerifyBytes, and RemoveSignatureBytes support
in-memory use. Input bytes are not modified by signing or signature removal.
File writes preserve the existing inode and are not atomic; sign a copy when
rollback is required. File operations currently have a 1 GiB input/output limit.
The path APIs also accept supported app bundles; byte APIs accept Mach-O and UDIF.
See bundle layouts and limits.
Verification
make verify
make lint
Verification runs unit tests and the compiled CLI as a subprocess, merges their
statement coverage, and requires more than 95% in every production package.
It writes coverage, raw acceptance transcripts, fixture hashes, and source
provenance under artifacts/. The merged recursive-app phase measures
96.20–96.44% library coverage, 98.10–99.53% CLI coverage and 100% entry-point coverage
across the three CI operating systems; see the commit-specific results.
The current implementation has been checked against
Apple codesign on macOS 27.0, build 26A428. See
the validation procedure for the exact evidence boundary.
CI runs tests on Linux, macOS 27, and Windows; builds all six targets with
GoReleaser; checks race behavior and fuzzes parsers; and sends files signed on
Linux/Windows to macOS for Apple verification. The merged recursive-app phase
passed all 174 artifacts, including 36 recursive app trees. Bundle layouts expand
the required matrix to 300, adding 126 tar archives that retain framework and
resource symlinks for native strict deep verification. See the
progress page for completed runs. Go code linting uses golangci-lint only.
Research and remaining work
This project retains the full-parity objective. Operations that depend on live
macOS process state, system keychains, or non-exportable hardware keys cannot be
reported equivalent without access to that state. They remain explicit blockers
under the requirement that the implementation have no macOS dependency.