Skip to content

Commit

Permalink
feat: Add entitlements support (#314)
Browse files Browse the repository at this point in the history
* extract: Add support for entitlements
This can be tested with https://github.com/crc-org/vfkit/releases/download/v0.5.1/vfkit
The "entitlements" and "entitlementsDER" fields are theoritically 2
distinct slots/.., but handling them together in `describe` should not
make a big differenc.

Signed-off-by: Christophe Fergeau <[email protected]>

* sign: Add SpecialSlot data structure

Special slots (requirements, entitlements, ...) are handled in
2 places: in GenerateSigningSuperBlob and in newCodeDirectory.

This handling mostly hardcodes that there's a macho.CsSlotRequirements
slot, and nothing else. For examples, to add handling for a new slot
type, newCodeDirectory needs changes in at least 3 non-obvious places
(`hashOff` computation, writing of the hashes, and NSpecialSlots).

This code abstracts special slots handling by:
- adding a new SpecialSlot struct to describe a special slot - the rest
  of the code no longer needs to know it's dealing with
  CsSlotRequirements or a CsSlotEntitlements (which I want to add
  support for)
- it adds a SpecialSlotHashWriter type for use in newCodeDirectory to
  count the number of special slots, compute the `hashOff` value
  accordingly, write the slots in the correct order, ...

This will be useful in the commits which add support for entitlements.

Signed-off-by: Christophe Fergeau <[email protected]>

* sign: Add support for entitlements

With the abstraction work done in the previous commit, adding support
for entitlements is now fairly straightforward, just need to build the
entitlements blob and hashes using user-provided XML data.

This fixes #4
Signed-off-by: Christophe Fergeau <[email protected]>

---------

Signed-off-by: Christophe Fergeau <[email protected]>
  • Loading branch information
cfergeau authored Apr 23, 2024
1 parent e32a886 commit 072cfb5
Show file tree
Hide file tree
Showing 24 changed files with 235 additions and 54 deletions.
4 changes: 2 additions & 2 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ linters:
enable:
- asciicheck
- bodyclose
- depguard
- dogsled
- dupl
- errcheck
Expand Down Expand Up @@ -69,4 +68,5 @@ linters:
# - varcheck # deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter. Replaced by unused.
# - deadcode # deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter. Replaced by unused.
# - structcheck # deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter. Replaced by unused.
# - rowserrcheck # we're not using sql.Rows at all in the codebase
# - rowserrcheck # we're not using sql.Rows at all in the codebase
# - depguard # this requires additional configuration https://github.com/golangci/golangci-lint/issues/3877#issuecomment-1573760321
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ GLOW_CMD = $(TEMP_DIR)/glow

# Tool versions #################################
QUILL_VERSION = latest
GOLANG_CI_VERSION = v1.52.2
GOLANG_CI_VERSION = v1.57.2
GOBOUNCER_VERSION = v0.4.0
GORELEASER_VERSION = v1.17.0
GOSIMPORTS_VERSION = v0.3.8
Expand Down
2 changes: 1 addition & 1 deletion cmd/quill/cli/commands/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func Describe(app clio.Application) *cobra.Command {
return nil
},
),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
defer bus.Exit()

var err error
Expand Down
2 changes: 1 addition & 1 deletion cmd/quill/cli/commands/embedded_certificates.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func EmbeddedCerts(app clio.Application) *cobra.Command {
Use: "embedded-certificates",
Short: "show the certificates embedded into quill (typically the Apple root and intermediate certs)",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
defer bus.Exit()

var err error
Expand Down
2 changes: 1 addition & 1 deletion cmd/quill/cli/commands/extract_certificates.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func ExtractCertificates(app clio.Application) *cobra.Command {
return nil
},
),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
defer bus.Exit()

certs, err := extractCertificates(opts.Path, opts.Leaf)
Expand Down
2 changes: 1 addition & 1 deletion cmd/quill/cli/commands/notarize.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func Notarize(app clio.Application) *cobra.Command {
return nil
},
),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
defer bus.Exit()

// TODO: verify path is a signed darwin binary
Expand Down
2 changes: 1 addition & 1 deletion cmd/quill/cli/commands/p12_attach_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func P12AttachChain(app clio.Application) *cobra.Command {
return nil
},
),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
defer bus.Exit()

newFilename, err := writeP12WithChain(opts.Path, opts.P12.Password, opts.Keychain.Path, true)
Expand Down
2 changes: 1 addition & 1 deletion cmd/quill/cli/commands/p12_describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func P12Describe(app clio.Application) *cobra.Command {
return nil
},
),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
defer bus.Exit()

description, err := describeP12(opts.Path, opts.Password)
Expand Down
3 changes: 2 additions & 1 deletion cmd/quill/cli/commands/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func Sign(app clio.Application) *cobra.Command {
return nil
},
),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
defer bus.Exit()

return sign(opts.Path, opts.Signing)
Expand Down Expand Up @@ -72,6 +72,7 @@ func sign(binPath string, opts options.Signing) error {

cfg.WithIdentity(opts.Identity)
cfg.WithTimestampServer(opts.TimestampServer)
cfg.WithEntitlements(opts.Entitlements)

return quill.Sign(cfg)
}
2 changes: 1 addition & 1 deletion cmd/quill/cli/commands/sign_and_notarize.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func SignAndNotarize(app clio.Application) *cobra.Command {
return nil
},
),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
defer bus.Exit()

err := sign(opts.Path, opts.Signing)
Expand Down
2 changes: 1 addition & 1 deletion cmd/quill/cli/commands/submission_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func SubmissionList(app clio.Application) *cobra.Command {
Use: "list",
Short: "list previous submissions to Apple's Notary service",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
defer bus.Exit()

log.Info("fetching previous submissions")
Expand Down
2 changes: 1 addition & 1 deletion cmd/quill/cli/commands/submission_logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func SubmissionLogs(app clio.Application) *cobra.Command {
return nil
},
),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, _ []string) error {
defer bus.Exit()

log.Infof("fetching submission logs for %q", opts.ID)
Expand Down
2 changes: 1 addition & 1 deletion cmd/quill/cli/commands/submission_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func SubmissionStatus(app clio.Application) *cobra.Command {
return nil
},
),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, _ []string) error {
defer bus.Exit()

log.Infof("checking submission status for %q", opts.ID)
Expand Down
9 changes: 8 additions & 1 deletion cmd/quill/cli/options/signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ type Signing struct {
FailWithoutFullChain bool `yaml:"fail-without-full-chain" json:"fail-without-full-chain" mapstructure:"fail-without-full-chain"`

// unbound options
Password string `yaml:"password" json:"password" mapstructure:"password"`
Password string `yaml:"password" json:"password" mapstructure:"password"`
Entitlements string `yaml:"entitlements" json:"entitlements" mapstructure:"entitlements"`
}

func DefaultSigning() Signing {
Expand Down Expand Up @@ -60,6 +61,12 @@ func (o *Signing) AddFlags(flags fangs.FlagSet) {
"ad-hoc", "",
"perform ad-hoc signing. No cryptographic signature is included and --p12 key and certificate input are not needed. Do NOT use this option for production builds.",
)

flags.StringVarP(
&o.Entitlements,
"entitlements", "",
"path to an XML file containing the entitlements for the binary being signed",
)
}

func (o *Signing) DescribeFields(d fangs.FieldDescriptionSet) {
Expand Down
5 changes: 3 additions & 2 deletions quill/extract/details.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ func (d Details) String(hideVerboseData bool) (r string) {
for idx, req := range d.SuperBlob.Requirements {
r += fmt.Sprintf("\nRequirements (block %d):\n", idx+1) + doIndent(req.String(), " ")
}
if d.SuperBlob.Entitlements != nil {
r += "\nEntitlements:\n" + doIndent(d.SuperBlob.Entitlements.String(), " ")
}
}

// TODO: add entitlements

return r
}

Expand Down
21 changes: 17 additions & 4 deletions quill/extract/entitlements.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
package extract

type EntitlementDetails struct {
Blob BlobDetails `json:"blob"`
Blob BlobDetails `json:"blob"`
Entitlements string `json:"entitlements,omitempty"`
EntitlementsDER []byte `json:"entitlements_der,omitempty"`
}

func getEntitlements(_ File) []EntitlementDetails {
// TODO
return nil
func getEntitlements(m File) *EntitlementDetails {
entitlements := m.blacktopFile.CodeSignature().Entitlements
entitlementsDER := m.blacktopFile.CodeSignature().EntitlementsDER
if entitlements == "" && entitlementsDER == nil {
return nil
}
return &EntitlementDetails{
Entitlements: entitlements,
EntitlementsDER: entitlementsDER,
}
}

func (e EntitlementDetails) String() string {
return e.Entitlements
}
2 changes: 1 addition & 1 deletion quill/extract/super_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ type SuperBlobDetails struct {
Size uint32 `json:"size"`
CodeDirectories []CodeDirectoryDetails `json:"codeDirectories"`
Requirements []RequirementDetails `json:"requirements"`
Entitlements []EntitlementDetails `json:"entitlements"`
Entitlements *EntitlementDetails `json:"entitlements"`
Signatures []SignatureDetails `json:"signatures"`
}

Expand Down
2 changes: 1 addition & 1 deletion quill/pki/apple/internal/generate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func extractCertLinks(selection *goquery.Selection, u string) []Link {
baseURL := parsedURL.Scheme + "://" + parsedURL.Host

// Find all <tr> elements in the selection
selection.Find("li").Each(func(i int, row *goquery.Selection) {
selection.Find("li").Each(func(_ int, row *goquery.Selection) {
// Find the <a> element in the first <td> in the row
link := row.Find("a")

Expand Down
20 changes: 18 additions & 2 deletions quill/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type SigningConfig struct {
SigningMaterial pki.SigningMaterial
Identity string
Path string
Entitlements string
}

func NewSigningConfigFromPEMs(binaryPath, certificate, privateKey, password string, failWithoutFullChain bool) (*SigningConfig, error) {
Expand Down Expand Up @@ -66,6 +67,11 @@ func (c *SigningConfig) WithTimestampServer(url string) *SigningConfig {
return c
}

func (c *SigningConfig) WithEntitlements(path string) *SigningConfig {
c.Entitlements = path
return c
}

func Sign(cfg SigningConfig) error {
f, err := os.Open(cfg.Path)
if err != nil {
Expand Down Expand Up @@ -212,14 +218,24 @@ func signSingleBinary(cfg SigningConfig) error {
log.Warnf("only ad-hoc signing, which means that anyone can alter the binary contents without you knowing (there is no cryptographic signature)")
}

entitlementsXML := ""
if cfg.Entitlements != "" {
log.Infof("Loading entitlements from %s", cfg.Entitlements)
data, err := os.ReadFile(cfg.Entitlements)
if err != nil {
return err
}
entitlementsXML = string(data)
}

// (patch) add empty LcCodeSignature loader (offset and size references are not set)
if err = m.AddEmptyCodeSigningCmd(); err != nil {
return err
}

// first pass: add the signed data with the dummy loader
log.Debugf("estimating signing material size")
superBlobSize, sbBytes, err := sign.GenerateSigningSuperBlob(cfg.Identity, m, cfg.SigningMaterial, 0)
superBlobSize, sbBytes, err := sign.GenerateSigningSuperBlob(cfg.Identity, m, cfg.SigningMaterial, entitlementsXML, 0)
if err != nil {
return fmt.Errorf("failed to add signing data on pass=1: %w", err)
}
Expand All @@ -232,7 +248,7 @@ func signSingleBinary(cfg SigningConfig) error {

// second pass: now that all of the sizing is right, let's do it again with the final contents (replacing the hashes and signature)
log.Debug("creating signature for binary")
_, sbBytes, err = sign.GenerateSigningSuperBlob(cfg.Identity, m, cfg.SigningMaterial, superBlobSize)
_, sbBytes, err = sign.GenerateSigningSuperBlob(cfg.Identity, m, cfg.SigningMaterial, entitlementsXML, superBlobSize)
if err != nil {
return fmt.Errorf("failed to add signing data on pass=2: %w", err)
}
Expand Down
Loading

0 comments on commit 072cfb5

Please sign in to comment.