diff --git a/.golangci.yaml b/.golangci.yaml
index f2dd296..8727ae8 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -21,7 +21,6 @@ linters:
enable:
- asciicheck
- bodyclose
- - depguard
- dogsled
- dupl
- errcheck
@@ -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
\ No newline at end of file
+# - 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
diff --git a/Makefile b/Makefile
index b6f47dd..2662c3c 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/cmd/quill/cli/commands/describe.go b/cmd/quill/cli/commands/describe.go
index dce8b27..4546cd4 100644
--- a/cmd/quill/cli/commands/describe.go
+++ b/cmd/quill/cli/commands/describe.go
@@ -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
diff --git a/cmd/quill/cli/commands/embedded_certificates.go b/cmd/quill/cli/commands/embedded_certificates.go
index 356777b..2eba434 100644
--- a/cmd/quill/cli/commands/embedded_certificates.go
+++ b/cmd/quill/cli/commands/embedded_certificates.go
@@ -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
diff --git a/cmd/quill/cli/commands/extract_certificates.go b/cmd/quill/cli/commands/extract_certificates.go
index cea7906..fb5eeb3 100644
--- a/cmd/quill/cli/commands/extract_certificates.go
+++ b/cmd/quill/cli/commands/extract_certificates.go
@@ -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)
diff --git a/cmd/quill/cli/commands/notarize.go b/cmd/quill/cli/commands/notarize.go
index 55b73b3..ff90e78 100644
--- a/cmd/quill/cli/commands/notarize.go
+++ b/cmd/quill/cli/commands/notarize.go
@@ -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
diff --git a/cmd/quill/cli/commands/p12_attach_chain.go b/cmd/quill/cli/commands/p12_attach_chain.go
index 5dc35b5..c4570e8 100644
--- a/cmd/quill/cli/commands/p12_attach_chain.go
+++ b/cmd/quill/cli/commands/p12_attach_chain.go
@@ -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)
diff --git a/cmd/quill/cli/commands/p12_describe.go b/cmd/quill/cli/commands/p12_describe.go
index cd00b00..3797bfb 100644
--- a/cmd/quill/cli/commands/p12_describe.go
+++ b/cmd/quill/cli/commands/p12_describe.go
@@ -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)
diff --git a/cmd/quill/cli/commands/sign.go b/cmd/quill/cli/commands/sign.go
index 3f7f0b7..b143eb4 100644
--- a/cmd/quill/cli/commands/sign.go
+++ b/cmd/quill/cli/commands/sign.go
@@ -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)
@@ -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)
}
diff --git a/cmd/quill/cli/commands/sign_and_notarize.go b/cmd/quill/cli/commands/sign_and_notarize.go
index 822ab20..1ead33c 100644
--- a/cmd/quill/cli/commands/sign_and_notarize.go
+++ b/cmd/quill/cli/commands/sign_and_notarize.go
@@ -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)
diff --git a/cmd/quill/cli/commands/submission_list.go b/cmd/quill/cli/commands/submission_list.go
index 439d2e2..daa9ca4 100644
--- a/cmd/quill/cli/commands/submission_list.go
+++ b/cmd/quill/cli/commands/submission_list.go
@@ -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")
diff --git a/cmd/quill/cli/commands/submission_logs.go b/cmd/quill/cli/commands/submission_logs.go
index cbc06c5..51b2cc4 100644
--- a/cmd/quill/cli/commands/submission_logs.go
+++ b/cmd/quill/cli/commands/submission_logs.go
@@ -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)
diff --git a/cmd/quill/cli/commands/submission_status.go b/cmd/quill/cli/commands/submission_status.go
index 0b52549..0555beb 100644
--- a/cmd/quill/cli/commands/submission_status.go
+++ b/cmd/quill/cli/commands/submission_status.go
@@ -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)
diff --git a/cmd/quill/cli/options/signing.go b/cmd/quill/cli/options/signing.go
index db51271..49972b1 100644
--- a/cmd/quill/cli/options/signing.go
+++ b/cmd/quill/cli/options/signing.go
@@ -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 {
@@ -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) {
diff --git a/quill/extract/details.go b/quill/extract/details.go
index 364d2eb..ad50324 100644
--- a/quill/extract/details.go
+++ b/quill/extract/details.go
@@ -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
}
diff --git a/quill/extract/entitlements.go b/quill/extract/entitlements.go
index e462aeb..830b8a3 100644
--- a/quill/extract/entitlements.go
+++ b/quill/extract/entitlements.go
@@ -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
}
diff --git a/quill/extract/super_blob.go b/quill/extract/super_blob.go
index 077eb64..37d4eff 100644
--- a/quill/extract/super_blob.go
+++ b/quill/extract/super_blob.go
@@ -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"`
}
diff --git a/quill/pki/apple/internal/generate/main.go b/quill/pki/apple/internal/generate/main.go
index 6de0a7b..856bbed 100644
--- a/quill/pki/apple/internal/generate/main.go
+++ b/quill/pki/apple/internal/generate/main.go
@@ -191,7 +191,7 @@ func extractCertLinks(selection *goquery.Selection, u string) []Link {
baseURL := parsedURL.Scheme + "://" + parsedURL.Host
// Find all
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 element in the first in the row
link := row.Find("a")
diff --git a/quill/sign.go b/quill/sign.go
index 3d4cbb3..a034131 100644
--- a/quill/sign.go
+++ b/quill/sign.go
@@ -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) {
@@ -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 {
@@ -212,6 +218,16 @@ 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
@@ -219,7 +235,7 @@ func signSingleBinary(cfg SigningConfig) error {
// 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)
}
@@ -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)
}
diff --git a/quill/sign/code_directory.go b/quill/sign/code_directory.go
index f8359c9..93b3b1b 100644
--- a/quill/sign/code_directory.go
+++ b/quill/sign/code_directory.go
@@ -11,11 +11,12 @@ import (
"github.com/go-restruct/restruct"
+ "github.com/anchore/quill/internal/log"
"github.com/anchore/quill/quill/macho"
)
-func generateCodeDirectory(id string, hasher hash.Hash, m *macho.File, flags macho.CdFlag, requirementsHashBytes, entitlementsHashBytes []byte) (*macho.Blob, error) {
- cd, err := newCodeDirectoryFromMacho(id, hasher, m, flags, requirementsHashBytes, entitlementsHashBytes)
+func generateCodeDirectory(id string, hasher hash.Hash, m *macho.File, flags macho.CdFlag, specialSlots []SpecialSlot) (*macho.Blob, error) {
+ cd, err := newCodeDirectoryFromMacho(id, hasher, m, flags, specialSlots)
if err != nil {
return nil, err
}
@@ -38,7 +39,7 @@ func packCodeDirectory(cd *macho.CodeDirectory, order binary.ByteOrder) (*macho.
return &blob, nil
}
-func newCodeDirectoryFromMacho(id string, hasher hash.Hash, m *macho.File, flags macho.CdFlag, requirementsHashBytes, entitlementsHashBytes []byte) (*macho.CodeDirectory, error) {
+func newCodeDirectoryFromMacho(id string, hasher hash.Hash, m *macho.File, flags macho.CdFlag, specialSlots []SpecialSlot) (*macho.CodeDirectory, error) {
textSeg := m.Segment("__TEXT")
var codeSize uint32
@@ -58,14 +59,83 @@ func newCodeDirectoryFromMacho(id string, hasher hash.Hash, m *macho.File, flags
return nil, err
}
- return newCodeDirectory(id, hasher, textSeg.Offset, textSeg.Filesz, codeSize, hashes, flags, requirementsHashBytes, entitlementsHashBytes)
+ return newCodeDirectory(id, hasher, textSeg.Offset, textSeg.Filesz, codeSize, hashes, flags, specialSlots)
}
-func newCodeDirectory(id string, hasher hash.Hash, execOffset, execSize uint64, codeSize uint32, hashes [][]byte, flags macho.CdFlag, requirementsHashBytes, entitlementsHashBytes []byte) (*macho.CodeDirectory, error) {
+// SpecialSlotHashWriter writes the special slots in the right order and with the right content.
+// Special slots have a type defined in quill/macho/blob_index.go, their hashes must be written from higher
+// type to lower type.
+// All slot types between CsSlotInfoslot (1) and the higher valued type must be written to the file.
+// The hashes for the missing slots must be filled with 0s.
+//
+// newCodeDirectory() also needs to know how many slots are present (including
+// the 0-filled ones), and the total number of bytes which were written (to
+// maintain an offset). It can use maxSlotType and totalBytesWritten for this.
+type SpecialSlotHashWriter struct {
+ totalBytesWritten int // total number of bytes written by the Write method
+ // slot type with the higher int value. This corresponds to the number of slots which will be written
+ maxSlotType int
+ // used to detect inconsistencies in the provided hashes - they must all have the same size
+ hashSize int
+ // SpecialSlot map keyed by their type to easily detect which slot types are missing
+ slots map[int]SpecialSlot
+}
+
+// newSpecialSlotHashWriter creates a new SpecialSlotHashWriter for the slots defined in specialSlots.
+func newSpecialSlotHashWriter(specialSlots []SpecialSlot) (*SpecialSlotHashWriter, error) {
+ w := SpecialSlotHashWriter{}
+ w.slots = map[int]SpecialSlot{}
+
+ for _, slot := range specialSlots {
+ switch w.hashSize {
+ case 0:
+ w.hashSize = len(slot.HashBytes)
+ case len(slot.HashBytes):
+ // w.hashSize was set previously and has the right value, nothing to do
+ default:
+ return nil, fmt.Errorf("inconsistent hash size: %d != %d", w.hashSize, len(slot.HashBytes))
+ }
+
+ slotType := int(slot.Type)
+ if slotType > w.maxSlotType {
+ w.maxSlotType = slotType
+ }
+ w.slots[slotType] = slot
+ }
+
+ log.Debugf("SpecialSlotHashWriter: %d special slots", w.maxSlotType)
+
+ return &w, nil
+}
+
+// Write will write all the special slots hashes to w.buffer.
+func (w *SpecialSlotHashWriter) Write(buffer *bytes.Buffer) error {
+ nullHashBytes := bytes.Repeat([]byte{0}, w.hashSize)
+ w.totalBytesWritten = 0
+
+ for i := w.maxSlotType; i > 0; i-- {
+ log.Debugf("SpecialSlotHashWriter: writing slot %d", i)
+ hashBytes := nullHashBytes
+ slot, hasSlot := w.slots[i]
+ if hasSlot {
+ hashBytes = slot.HashBytes
+ } else {
+ log.Debugf("SpecialSlotHashWriter: slot %d is empty", i)
+ }
+ written, err := buffer.Write(hashBytes)
+ if err != nil {
+ return fmt.Errorf("unable to write plist hash to code directory: %w", err)
+ }
+ w.totalBytesWritten += written
+ }
+
+ return nil
+}
+
+func newCodeDirectory(id string, hasher hash.Hash, execOffset, execSize uint64, codeSize uint32, hashes [][]byte, flags macho.CdFlag, specialSlots []SpecialSlot) (*macho.CodeDirectory, error) {
cdSize := unsafe.Sizeof(macho.BlobHeader{}) + unsafe.Sizeof(macho.CodeDirectoryHeader{})
idOff := int32(cdSize)
// note: the hash offset starts at the first non-special hash (page hashes). Special hashes (e.g. requirements hash) are written before the page hashes.
- hashOff := idOff + int32(len(id)+1) + int32(len(requirementsHashBytes)) + int32(len(entitlementsHashBytes))
var ht macho.HashType
switch hasher.Size() {
@@ -80,17 +150,25 @@ func newCodeDirectory(id string, hasher hash.Hash, execOffset, execSize uint64,
buff := bytes.Buffer{}
// write the identifier
- if _, err := buff.Write([]byte(id + "\000")); err != nil {
+ hashOff := int(idOff)
+ var (
+ written int
+ err error
+ )
+ if written, err = buff.Write([]byte(id + "\000")); err != nil {
return nil, fmt.Errorf("unable to write ID to code directory: %w", err)
}
+ hashOff += written
// write hashes
- if _, err := buff.Write(requirementsHashBytes); err != nil {
- return nil, fmt.Errorf("unable to write requirements hash to code directory: %w", err)
+ specialSlotHashWriter, err := newSpecialSlotHashWriter(specialSlots)
+ if err != nil {
+ return nil, err
}
- if _, err := buff.Write(entitlementsHashBytes); err != nil {
- return nil, fmt.Errorf("unable to write plist hash to code directory: %w", err)
+ if err := specialSlotHashWriter.Write(&buff); err != nil {
+ return nil, err
}
+ hashOff += specialSlotHashWriter.totalBytesWritten
for idx, hBytes := range hashes {
_, err := buff.Write(hBytes)
@@ -105,7 +183,7 @@ func newCodeDirectory(id string, hasher hash.Hash, execOffset, execSize uint64,
Flags: flags,
HashOffset: uint32(hashOff),
IdentOffset: uint32(idOff),
- NSpecialSlots: uint32(2), // requirements + plist
+ NSpecialSlots: uint32(specialSlotHashWriter.maxSlotType),
NCodeSlots: uint32(len(hashes)),
CodeLimit: codeSize,
HashSize: uint8(hasher.Size()),
diff --git a/quill/sign/code_directory_test.go b/quill/sign/code_directory_test.go
index 9ba4a53..88ef548 100644
--- a/quill/sign/code_directory_test.go
+++ b/quill/sign/code_directory_test.go
@@ -87,7 +87,16 @@ func Test_newCodeDirectoryFromMacho(t *testing.T) {
pListBytes, err := hex.DecodeString(tt.pListHash)
require.NoError(t, err)
- actualCD, err := newCodeDirectoryFromMacho(tt.id, tt.hasher, m, tt.flags, reqBytes, pListBytes)
+ reqSlot := SpecialSlot{
+ Type: macho.CsSlotRequirements,
+ HashBytes: reqBytes,
+ }
+ plistSlot := SpecialSlot{
+ Type: macho.CsSlotInfoslot,
+ HashBytes: pListBytes,
+ }
+
+ actualCD, err := newCodeDirectoryFromMacho(tt.id, tt.hasher, m, tt.flags, []SpecialSlot{reqSlot, plistSlot})
require.NoError(t, err)
// make certain the headers match
@@ -184,7 +193,16 @@ func Test_generateCodeDirectory(t *testing.T) {
pListBytes, err := hex.DecodeString(tt.pListHash)
require.NoError(t, err)
- cdBlob, err := generateCodeDirectory(tt.id, tt.hasher, m, tt.flags, reqBytes, pListBytes)
+ reqSlot := SpecialSlot{
+ Type: macho.CsSlotRequirements,
+ HashBytes: reqBytes,
+ }
+ plistSlot := SpecialSlot{
+ Type: macho.CsSlotInfoslot,
+ HashBytes: pListBytes,
+ }
+
+ cdBlob, err := generateCodeDirectory(tt.id, tt.hasher, m, tt.flags, []SpecialSlot{reqSlot, plistSlot})
require.NoError(t, err)
cdBytes, err := cdBlob.Pack()
diff --git a/quill/sign/entitlements.go b/quill/sign/entitlements.go
new file mode 100644
index 0000000..4eb8d57
--- /dev/null
+++ b/quill/sign/entitlements.go
@@ -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
+}
diff --git a/quill/sign/requirements.go b/quill/sign/requirements.go
index 646790d..74d2c56 100644
--- a/quill/sign/requirements.go
+++ b/quill/sign/requirements.go
@@ -61,7 +61,7 @@ const (
anchorCertIndex = ^uint32(0) // index for anchor (last in chain), equiv to -1
)
-func generateRequirements(id string, h hash.Hash, signingMaterial pki.SigningMaterial) (*macho.Blob, []byte, error) {
+func generateRequirements(id string, h hash.Hash, signingMaterial pki.SigningMaterial) (*SpecialSlot, error) {
var reqBytes []byte
if signingMaterial.Signer == nil {
log.Trace("skipping adding designated requirement because no signer was found")
@@ -69,12 +69,12 @@ func generateRequirements(id string, h hash.Hash, signingMaterial pki.SigningMat
} else {
req, err := newRequirements(id, signingMaterial)
if err != nil {
- return nil, nil, err
+ return nil, err
}
reqBytes, err = restruct.Pack(macho.SigningOrder, req)
if err != nil {
- return nil, nil, fmt.Errorf("unable to encode requirement: %w", err)
+ return nil, fmt.Errorf("unable to encode requirement: %w", err)
}
}
@@ -82,16 +82,16 @@ func generateRequirements(id string, h hash.Hash, signingMaterial pki.SigningMat
blobBytes, err := restruct.Pack(macho.SigningOrder, &blob)
if err != nil {
- return nil, nil, fmt.Errorf("unable to encode requirements blob: %w", err)
+ return nil, fmt.Errorf("unable to encode requirements blob: %w", err)
}
// the requirements hash is against the entire blob, not just the payload
_, err = h.Write(blobBytes)
if err != nil {
- return nil, nil, err
+ return nil, err
}
- return &blob, h.Sum(nil), nil
+ return &SpecialSlot{macho.CsSlotRequirements, &blob, h.Sum(nil)}, nil
}
func newRequirements(id string, signingMaterial pki.SigningMaterial) (*macho.Requirements, error) {
diff --git a/quill/sign/signing_super_blob.go b/quill/sign/signing_super_blob.go
index 552f6de..e91e353 100644
--- a/quill/sign/signing_super_blob.go
+++ b/quill/sign/signing_super_blob.go
@@ -1,7 +1,6 @@
package sign
import (
- "bytes"
"crypto/sha256"
"fmt"
@@ -11,7 +10,13 @@ import (
"github.com/anchore/quill/quill/pki"
)
-func GenerateSigningSuperBlob(id string, m *macho.File, signingMaterial pki.SigningMaterial, paddingTarget int) (int, []byte, error) {
+type SpecialSlot struct {
+ Type macho.SlotType
+ Blob *macho.Blob
+ HashBytes []byte
+}
+
+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)
@@ -22,15 +27,25 @@ func GenerateSigningSuperBlob(id string, m *macho.File, signingMaterial pki.Sign
cdFlags = macho.Adhoc
}
- requirementsBlob, requirementsHashBytes, err := generateRequirements(id, sha256.New(), signingMaterial)
+ specialSlots := []SpecialSlot{}
+
+ entitlements, err := generateEntitlements(sha256.New(), entitlementsData)
if err != nil {
- return 0, nil, fmt.Errorf("unable to create requirements: %w", err)
+ return 0, nil, fmt.Errorf("unable to create entitlements: %w", err)
+ }
+ if entitlements != nil {
+ specialSlots = append(specialSlots, *entitlements)
}
- // TODO: add entitlements, for the meantime, don't include it
- entitlementsHashBytes := bytes.Repeat([]byte{0}, sha256.New().Size())
+ requirements, err := generateRequirements(id, sha256.New(), signingMaterial)
+ if err != nil {
+ return 0, nil, fmt.Errorf("unable to create requirements: %w", err)
+ }
+ if requirements != nil {
+ specialSlots = append(specialSlots, *requirements)
+ }
- cdBlob, err := generateCodeDirectory(id, sha256.New(), m, cdFlags, requirementsHashBytes, entitlementsHashBytes)
+ cdBlob, err := generateCodeDirectory(id, sha256.New(), m, cdFlags, specialSlots)
if err != nil {
return 0, nil, fmt.Errorf("unable to create code directory: %w", err)
}
@@ -41,9 +56,11 @@ func GenerateSigningSuperBlob(id string, m *macho.File, signingMaterial pki.Sign
}
sb := macho.NewSuperBlob(macho.MagicEmbeddedSignature)
-
sb.Add(macho.CsSlotCodedirectory, cdBlob)
- sb.Add(macho.CsSlotRequirements, requirementsBlob)
+ for _, slot := range specialSlots {
+ sb.Add(slot.Type, slot.Blob)
+ }
+
sb.Add(macho.CsSlotCmsSignature, cmsBlob)
sb.Finalize(paddingTarget)
|