Skip to content

Commit

Permalink
sign: Add support for entitlements
Browse files Browse the repository at this point in the history
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 anchore#4

Signed-off-by: Christophe Fergeau <[email protected]>
  • Loading branch information
cfergeau committed Apr 18, 2024
1 parent 0158456 commit d05c1be
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 6 deletions.
1 change: 1 addition & 0 deletions cmd/quill/cli/commands/sign.go
Original file line number Diff line number Diff line change
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)
}
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
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
30 changes: 30 additions & 0 deletions quill/sign/entitlements.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package sign

import (
"fmt"
"hash"

"github.com/go-restruct/restruct"

"github.com/anchore/quill/quill/macho"
)

func generateEntitlements(h hash.Hash, entitlementsXML string) (*SpecialSlot, error) {
if entitlementsXML == "" {
return nil, nil
}
entitlementsBytes := []byte(entitlementsXML)
blob := macho.NewBlob(macho.MagicEmbeddedEntitlements, entitlementsBytes)
blobBytes, err := restruct.Pack(macho.SigningOrder, &blob)
if err != nil {
return nil, fmt.Errorf("unable to encode entitlements blob: %w", err)
}

// the requirements hash is against the entire blob, not just the payload
h.Write(blobBytes)
if err != nil {
return nil, err
}

return &SpecialSlot{macho.CsSlotEntitlements, &blob, h.Sum(nil)}, nil
}
12 changes: 9 additions & 3 deletions quill/sign/signing_super_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type SpecialSlot struct {
HashBytes []byte
}

func GenerateSigningSuperBlob(id string, m *macho.File, signingMaterial pki.SigningMaterial, paddingTarget int) (int, []byte, error) {
func GenerateSigningSuperBlob(id string, m *macho.File, signingMaterial pki.SigningMaterial, entitlementsData string, paddingTarget int) (int, []byte, error) {
var cdFlags macho.CdFlag
if signingMaterial.Signer != nil {
// TODO: add options to enable more strict rules (such as macho.Hard)
Expand All @@ -29,6 +29,14 @@ func GenerateSigningSuperBlob(id string, m *macho.File, signingMaterial pki.Sign

specialSlots := []SpecialSlot{}

entitlements, err := generateEntitlements(sha256.New(), entitlementsData)
if err != nil {
return 0, nil, fmt.Errorf("unable to create entitlements: %w", err)
}
if entitlements != nil {
specialSlots = append(specialSlots, *entitlements)
}

requirements, err := generateRequirements(id, sha256.New(), signingMaterial)
if err != nil {
return 0, nil, fmt.Errorf("unable to create requirements: %w", err)
Expand All @@ -37,8 +45,6 @@ func GenerateSigningSuperBlob(id string, m *macho.File, signingMaterial pki.Sign
specialSlots = append(specialSlots, *requirements)
}

// TODO: add entitlements, for the meantime, don't include it

cdBlob, err := generateCodeDirectory(id, sha256.New(), m, cdFlags, specialSlots)
if err != nil {
return 0, nil, fmt.Errorf("unable to create code directory: %w", err)
Expand Down

0 comments on commit d05c1be

Please sign in to comment.