Skip to content

feat(gptk): verify Apple's code signature on an imported payload - #265

Open
frankea wants to merge 2 commits into
mainfrom
feat/gptk-apple-signature
Open

frankea wants to merge 2 commits into
mainfrom
feat/gptk-apple-signature

Conversation

@frankea

@frankea frankea commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Closes the last open finding from the 2026-08-29 security review of the GPTK importer: a structurally valid but unsigned payload was accepted and deployed into every bottle's Wine tree.

What changes

  • GPTKImporter.validatePayload now verifies external/libd3dshared.dylib and external/D3DMetal.framework against an anchor apple code requirement via SecStaticCodeCheckValidity. Apple signs both with its software-signing chain (checked on a real 4.0b2 payload: Authority=Software Signing / Apple Code Signing CA / Apple Root CA). The PE forwarders cannot be code-signed and keep their builtin-marker check.
  • New GPTKImportError.notAppleSigned(path), surfaced in the GPTK settings section like the other import errors, with the failing file named. New localized key gptk.error.notAppleSigned (kit-only, extraction manual, en plus en-GB).
  • The check runs last, after completeness, forwarder variant and version, so a wrong folder or a missing file still gets its specific message.
  • The verifier is an injectable parameter with the real check as default. Fixture payloads are not code objects, so the success-path tests inject an accepting verifier; two tests exercise the real check (a fixture is rejected, /bin/ls passes, a plain file and a missing path fail).

Verification

  • swift test --package-path WhiskyKit: 1318 XCTest plus 254 swift-testing, 0 failures (5 new tests).
  • App target builds (the catalog compiles).
  • swiftformat 0.58.7 lint clean, swiftlint lint --strict clean.
  • codesign --verify -R="anchor apple" confirmed on the imported payload's dylib and framework on this machine, and the plain-file rejection path.

Existing stores are not re-verified; the gate is at import, which is the point where a user picks a source.

The importer checked a payload's shape and the builtin marker on the PE
forwarders, but nothing about the Mach-O half: libd3dshared.dylib and the
D3DMetal framework, which are the code that every process in a bottle
loads once the payload is deployed. A folder assembled by hand or a
download altered in transit passed as long as the layout matched.

Apple signs both with its own software-signing chain, so validation now
runs them through SecStaticCodeCheckValidity against an "anchor apple"
requirement, the same test as codesign --verify -R="anchor apple". A
failure names the file. The check runs after the completeness, variant
and version checks so the common mistakes still get their specific
message first.

The verifier is injectable on validatePayload so the fixture payloads,
which are not code objects, keep validating in tests; one test runs the
real check against a fixture and expects the rejection, another against
/bin/ls and a plain file.
@codecov

codecov Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...it/Sources/WhiskyKit/WhiskyWine/GPTKImporter.swift 90.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant