Skip to content

Commit d9fd649

Browse files
committed
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 anchore#4 Signed-off-by: Christophe Fergeau <[email protected]>
1 parent 1da1754 commit d9fd649

File tree

5 files changed

+65
-6
lines changed

5 files changed

+65
-6
lines changed

cmd/quill/cli/commands/sign.go

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ func sign(binPath string, opts options.Signing) error {
7272

7373
cfg.WithIdentity(opts.Identity)
7474
cfg.WithTimestampServer(opts.TimestampServer)
75+
cfg.WithEntitlements(opts.Entitlements)
7576

7677
return quill.Sign(cfg)
7778
}

cmd/quill/cli/options/signing.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ type Signing struct {
2020
FailWithoutFullChain bool `yaml:"fail-without-full-chain" json:"fail-without-full-chain" mapstructure:"fail-without-full-chain"`
2121

2222
// unbound options
23-
Password string `yaml:"password" json:"password" mapstructure:"password"`
23+
Password string `yaml:"password" json:"password" mapstructure:"password"`
24+
Entitlements string `yaml:"entitlements" json:"entitlements" mapstructure:"entitlements"`
2425
}
2526

2627
func DefaultSigning() Signing {
@@ -60,6 +61,12 @@ func (o *Signing) AddFlags(flags fangs.FlagSet) {
6061
"ad-hoc", "",
6162
"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.",
6263
)
64+
65+
flags.StringVarP(
66+
&o.Entitlements,
67+
"entitlements", "",
68+
"path to an XML file containing the entitlements for the binary being signed",
69+
)
6370
}
6471

6572
func (o *Signing) DescribeFields(d fangs.FieldDescriptionSet) {

quill/sign.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type SigningConfig struct {
2121
SigningMaterial pki.SigningMaterial
2222
Identity string
2323
Path string
24+
Entitlements string
2425
}
2526

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

70+
func (c *SigningConfig) WithEntitlements(path string) *SigningConfig {
71+
c.Entitlements = path
72+
return c
73+
}
74+
6975
func Sign(cfg SigningConfig) error {
7076
f, err := os.Open(cfg.Path)
7177
if err != nil {
@@ -212,14 +218,24 @@ func signSingleBinary(cfg SigningConfig) error {
212218
log.Warnf("only ad-hoc signing, which means that anyone can alter the binary contents without you knowing (there is no cryptographic signature)")
213219
}
214220

221+
entitlementsXML := ""
222+
if cfg.Entitlements != "" {
223+
log.Infof("Loading entitlements from %s", cfg.Entitlements)
224+
data, err := os.ReadFile(cfg.Entitlements)
225+
if err != nil {
226+
return err
227+
}
228+
entitlementsXML = string(data)
229+
}
230+
215231
// (patch) add empty LcCodeSignature loader (offset and size references are not set)
216232
if err = m.AddEmptyCodeSigningCmd(); err != nil {
217233
return err
218234
}
219235

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

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

quill/sign/entitlements.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package sign
2+
3+
import (
4+
"fmt"
5+
"hash"
6+
7+
"github.com/anchore/quill/quill/macho"
8+
"github.com/go-restruct/restruct"
9+
)
10+
11+
func generateEntitlements(h hash.Hash, entitlementsXML string) (*SpecialSlot, error) {
12+
if entitlementsXML == "" {
13+
return nil, nil
14+
}
15+
entitlementsBytes := []byte(entitlementsXML)
16+
blob := macho.NewBlob(macho.MagicEmbeddedEntitlements, entitlementsBytes)
17+
blobBytes, err := restruct.Pack(macho.SigningOrder, &blob)
18+
if err != nil {
19+
return nil, fmt.Errorf("unable to encode entitlements blob: %w", err)
20+
}
21+
22+
// the requirements hash is against the entire blob, not just the payload
23+
h.Write(blobBytes)
24+
if err != nil {
25+
return nil, err
26+
}
27+
28+
return &SpecialSlot{macho.CsSlotEntitlements, &blob, h.Sum(nil)}, nil
29+
}

quill/sign/signing_super_blob.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type SpecialSlot struct {
1616
HashBytes []byte
1717
}
1818

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

3030
specialSlots := []SpecialSlot{}
3131

32+
entitlements, err := generateEntitlements(sha256.New(), entitlementsData)
33+
if err != nil {
34+
return 0, nil, fmt.Errorf("unable to create entitlements: %w", err)
35+
}
36+
if entitlements != nil {
37+
specialSlots = append(specialSlots, *entitlements)
38+
}
39+
3240
requirements, err := generateRequirements(id, sha256.New(), signingMaterial)
3341
if err != nil {
3442
return 0, nil, fmt.Errorf("unable to create requirements: %w", err)
@@ -37,8 +45,6 @@ func GenerateSigningSuperBlob(id string, m *macho.File, signingMaterial pki.Sign
3745
specialSlots = append(specialSlots, *requirements)
3846
}
3947

40-
// TODO: add entitlements, for the meantime, don't include it
41-
4248
cdBlob, err := generateCodeDirectory(id, sha256.New(), m, cdFlags, specialSlots)
4349
if err != nil {
4450
return 0, nil, fmt.Errorf("unable to create code directory: %w", err)

0 commit comments

Comments
 (0)