Skip to content

Commit d0b9934

Browse files
authored
Merge pull request #997 from Homebrew/pure-ruby-code-signing
Add pure-Ruby ad-hoc signing
2 parents ce5759a + 69b0d5d commit d0b9934

7 files changed

Lines changed: 983 additions & 12 deletions

File tree

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,25 @@ lc_vers = file[:LC_VERSION_MIN_MACOSX].first
4545
puts lc_vers.version_string # => "10.10.0"
4646
```
4747

48+
### Ad-hoc code signing
49+
50+
Changing a Mach-O load command invalidates any existing code signature. This is
51+
especially important when Homebrew pours bottles on Apple Silicon, where native
52+
code must remain signed after its paths are rewritten. `MachO.codesign!` creates
53+
the required ad-hoc signature in Ruby instead of invoking `/usr/bin/codesign`:
54+
55+
```ruby
56+
MachO.codesign!("/path/to/my/binary")
57+
```
58+
59+
4860
### What works?
4961

5062
* Reading data from x86/x86_64/arm64/PPC Mach-O files (other architectures are unsupported, but may work)
5163
* Changing the IDs of Mach-O and Fat dylibs
5264
* Changing install names in Mach-O and Fat files
5365
* Adding, deleting, and modifying rpaths.
66+
* Parsing embedded code signatures and applying ad-hoc signatures in pure Ruby.
5467

5568
### What needs to be done?
5669

@@ -73,6 +86,8 @@ overcommit --install
7386
* Constants were taken from Apple, Inc's
7487
[`loader.h` in `cctools/include/mach-o`](https://opensource.apple.com/source/cctools/cctools-973.0.1/include/mach-o/loader.h.auto.html).
7588
(Apple Public Source License 2.0).
89+
* Code-signing constants and structures follow Apple, Inc's
90+
[`cs_blobs.h` in XNU](https://github.com/apple-oss-distributions/xnu/blob/main/osfmk/kern/cs_blobs.h).
7691
* Binary files used for testing were taken from The LLVM Project. ([Apache License v2.0 with LLVM Exceptions](test/bin/llvm/LICENSE.txt)).
7792

7893
### License

lib/macho.rb

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
# frozen_string_literal: true
22

3-
require "open3"
4-
53
require_relative "macho/utils"
64
require_relative "macho/structure"
75
require_relative "macho/view"
86
require_relative "macho/headers"
7+
require_relative "macho/code_signing"
98
require_relative "macho/load_commands"
109
require_relative "macho/sections"
1110
require_relative "macho/macho_file"
@@ -42,20 +41,20 @@ def self.open(filename)
4241
file
4342
end
4443

45-
# Signs the dylib using an ad-hoc identity.
46-
# Necessary after making any changes to a dylib, since otherwise
47-
# changing a signed file invalidates its signature.
44+
# Signs a thin or fat Mach-O using an ad-hoc identity.
45+
# Necessary after changing signed Mach-O data because the signature covers
46+
# the header, load commands and all bytes preceding the signature.
4847
# @param filename [String] the file being opened
4948
# @return [void]
50-
# @raise [ModificationError] if the operation fails
49+
# @raise [CodeSigningError] if the operation fails
5150
def self.codesign!(filename)
52-
raise ArgumentError, "codesign binary is not available on Linux" if RUBY_PLATFORM !~ /darwin/
5351
raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
5452

55-
_, _, status = Open3.capture3("codesign", "--sign", "-", "--force",
56-
"--preserve-metadata=entitlements,requirements,flags,runtime",
57-
filename)
58-
59-
raise CodeSigningError, "#{filename}: signing failed!" unless status.success?
53+
file = MachO.open(filename)
54+
file.codesign!
55+
file.write!
56+
nil
57+
rescue MachOError => e
58+
raise CodeSigningError, "#{filename}: signing failed: #{e.message}"
6059
end
6160
end

0 commit comments

Comments
 (0)