1// Copyright 2020 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package codesign provides basic functionalities for
6// ad-hoc code signing of Mach-O files.
7//
8// This is not a general tool for code-signing. It is made
9// specifically for the Go toolchain. It uses the same
10// ad-hoc signing algorithm as the Darwin linker.
11package codesign
12
13import (
14	"debug/macho"
15	"encoding/binary"
16	"io"
17
18	"cmd/internal/notsha256"
19)
20
21// Code signature layout.
22//
23// The code signature is a block of bytes that contains
24// a SuperBlob, which contains one or more Blobs. For ad-hoc
25// signing, a single CodeDirectory Blob suffices.
26//
27// A SuperBlob starts with its header (the binary representation
28// of the SuperBlob struct), followed by a list of (in our case,
29// one) Blobs (offset and size). A CodeDirectory Blob starts
30// with its head (the binary representation of CodeDirectory struct),
31// followed by the identifier (as a C string) and the hashes, at
32// the corresponding offsets.
33//
34// The signature data must be included in the __LINKEDIT segment.
35// In the Mach-O file header, an LC_CODE_SIGNATURE load command
36// points to the data.
37
38const (
39	pageSizeBits = 12
40	pageSize     = 1 << pageSizeBits
41)
42
43const LC_CODE_SIGNATURE = 0x1d
44
45// Constants and struct layouts are from
46// https://opensource.apple.com/source/xnu/xnu-4903.270.47/osfmk/kern/cs_blobs.h
47
48const (
49	CSMAGIC_REQUIREMENT        = 0xfade0c00 // single Requirement blob
50	CSMAGIC_REQUIREMENTS       = 0xfade0c01 // Requirements vector (internal requirements)
51	CSMAGIC_CODEDIRECTORY      = 0xfade0c02 // CodeDirectory blob
52	CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0 // embedded form of signature data
53	CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1 // multi-arch collection of embedded signatures
54
55	CSSLOT_CODEDIRECTORY = 0 // slot index for CodeDirectory
56)
57
58const (
59	CS_HASHTYPE_SHA1             = 1
60	CS_HASHTYPE_SHA256           = 2
61	CS_HASHTYPE_SHA256_TRUNCATED = 3
62	CS_HASHTYPE_SHA384           = 4
63)
64
65const (
66	CS_EXECSEG_MAIN_BINARY     = 0x1   // executable segment denotes main binary
67	CS_EXECSEG_ALLOW_UNSIGNED  = 0x10  // allow unsigned pages (for debugging)
68	CS_EXECSEG_DEBUGGER        = 0x20  // main binary is debugger
69	CS_EXECSEG_JIT             = 0x40  // JIT enabled
70	CS_EXECSEG_SKIP_LV         = 0x80  // skip library validation
71	CS_EXECSEG_CAN_LOAD_CDHASH = 0x100 // can bless cdhash for execution
72	CS_EXECSEG_CAN_EXEC_CDHASH = 0x200 // can execute blessed cdhash
73)
74
75type Blob struct {
76	typ    uint32 // type of entry
77	offset uint32 // offset of entry
78	// data follows
79}
80
81func (b *Blob) put(out []byte) []byte {
82	out = put32be(out, b.typ)
83	out = put32be(out, b.offset)
84	return out
85}
86
87const blobSize = 2 * 4
88
89type SuperBlob struct {
90	magic  uint32 // magic number
91	length uint32 // total length of SuperBlob
92	count  uint32 // number of index entries following
93	// blobs []Blob
94}
95
96func (s *SuperBlob) put(out []byte) []byte {
97	out = put32be(out, s.magic)
98	out = put32be(out, s.length)
99	out = put32be(out, s.count)
100	return out
101}
102
103const superBlobSize = 3 * 4
104
105type CodeDirectory struct {
106	magic         uint32 // magic number (CSMAGIC_CODEDIRECTORY)
107	length        uint32 // total length of CodeDirectory blob
108	version       uint32 // compatibility version
109	flags         uint32 // setup and mode flags
110	hashOffset    uint32 // offset of hash slot element at index zero
111	identOffset   uint32 // offset of identifier string
112	nSpecialSlots uint32 // number of special hash slots
113	nCodeSlots    uint32 // number of ordinary (code) hash slots
114	codeLimit     uint32 // limit to main image signature range
115	hashSize      uint8  // size of each hash in bytes
116	hashType      uint8  // type of hash (cdHashType* constants)
117	_pad1         uint8  // unused (must be zero)
118	pageSize      uint8  // log2(page size in bytes); 0 => infinite
119	_pad2         uint32 // unused (must be zero)
120	scatterOffset uint32
121	teamOffset    uint32
122	_pad3         uint32
123	codeLimit64   uint64
124	execSegBase   uint64
125	execSegLimit  uint64
126	execSegFlags  uint64
127	// data follows
128}
129
130func (c *CodeDirectory) put(out []byte) []byte {
131	out = put32be(out, c.magic)
132	out = put32be(out, c.length)
133	out = put32be(out, c.version)
134	out = put32be(out, c.flags)
135	out = put32be(out, c.hashOffset)
136	out = put32be(out, c.identOffset)
137	out = put32be(out, c.nSpecialSlots)
138	out = put32be(out, c.nCodeSlots)
139	out = put32be(out, c.codeLimit)
140	out = put8(out, c.hashSize)
141	out = put8(out, c.hashType)
142	out = put8(out, c._pad1)
143	out = put8(out, c.pageSize)
144	out = put32be(out, c._pad2)
145	out = put32be(out, c.scatterOffset)
146	out = put32be(out, c.teamOffset)
147	out = put32be(out, c._pad3)
148	out = put64be(out, c.codeLimit64)
149	out = put64be(out, c.execSegBase)
150	out = put64be(out, c.execSegLimit)
151	out = put64be(out, c.execSegFlags)
152	return out
153}
154
155const codeDirectorySize = 13*4 + 4 + 4*8
156
157// CodeSigCmd is Mach-O LC_CODE_SIGNATURE load command.
158type CodeSigCmd struct {
159	Cmd      uint32 // LC_CODE_SIGNATURE
160	Cmdsize  uint32 // sizeof this command (16)
161	Dataoff  uint32 // file offset of data in __LINKEDIT segment
162	Datasize uint32 // file size of data in __LINKEDIT segment
163}
164
165func FindCodeSigCmd(f *macho.File) (CodeSigCmd, bool) {
166	get32 := f.ByteOrder.Uint32
167	for _, l := range f.Loads {
168		data := l.Raw()
169		cmd := get32(data)
170		if cmd == LC_CODE_SIGNATURE {
171			return CodeSigCmd{
172				cmd,
173				get32(data[4:]),
174				get32(data[8:]),
175				get32(data[12:]),
176			}, true
177		}
178	}
179	return CodeSigCmd{}, false
180}
181
182func put32be(b []byte, x uint32) []byte { binary.BigEndian.PutUint32(b, x); return b[4:] }
183func put64be(b []byte, x uint64) []byte { binary.BigEndian.PutUint64(b, x); return b[8:] }
184func put8(b []byte, x uint8) []byte     { b[0] = x; return b[1:] }
185func puts(b, s []byte) []byte           { n := copy(b, s); return b[n:] }
186
187// Size computes the size of the code signature.
188// id is the identifier used for signing (a field in CodeDirectory blob, which
189// has no significance in ad-hoc signing).
190func Size(codeSize int64, id string) int64 {
191	nhashes := (codeSize + pageSize - 1) / pageSize
192	idOff := int64(codeDirectorySize)
193	hashOff := idOff + int64(len(id)+1)
194	cdirSz := hashOff + nhashes*notsha256.Size
195	return int64(superBlobSize+blobSize) + cdirSz
196}
197
198// Sign generates an ad-hoc code signature and writes it to out.
199// out must have length at least Size(codeSize, id).
200// data is the file content without the signature, of size codeSize.
201// textOff and textSize is the file offset and size of the text segment.
202// isMain is true if this is a main executable.
203// id is the identifier used for signing (a field in CodeDirectory blob, which
204// has no significance in ad-hoc signing).
205func Sign(out []byte, data io.Reader, id string, codeSize, textOff, textSize int64, isMain bool) {
206	nhashes := (codeSize + pageSize - 1) / pageSize
207	idOff := int64(codeDirectorySize)
208	hashOff := idOff + int64(len(id)+1)
209	sz := Size(codeSize, id)
210
211	// emit blob headers
212	sb := SuperBlob{
213		magic:  CSMAGIC_EMBEDDED_SIGNATURE,
214		length: uint32(sz),
215		count:  1,
216	}
217	blob := Blob{
218		typ:    CSSLOT_CODEDIRECTORY,
219		offset: superBlobSize + blobSize,
220	}
221	cdir := CodeDirectory{
222		magic:        CSMAGIC_CODEDIRECTORY,
223		length:       uint32(sz) - (superBlobSize + blobSize),
224		version:      0x20400,
225		flags:        0x20002, // adhoc | linkerSigned
226		hashOffset:   uint32(hashOff),
227		identOffset:  uint32(idOff),
228		nCodeSlots:   uint32(nhashes),
229		codeLimit:    uint32(codeSize),
230		hashSize:     notsha256.Size,
231		hashType:     CS_HASHTYPE_SHA256,
232		pageSize:     uint8(pageSizeBits),
233		execSegBase:  uint64(textOff),
234		execSegLimit: uint64(textSize),
235	}
236	if isMain {
237		cdir.execSegFlags = CS_EXECSEG_MAIN_BINARY
238	}
239
240	outp := out
241	outp = sb.put(outp)
242	outp = blob.put(outp)
243	outp = cdir.put(outp)
244
245	// emit the identifier
246	outp = puts(outp, []byte(id+"\000"))
247
248	// emit hashes
249	// NOTE(rsc): These must be SHA256, but for cgo bootstrap reasons
250	// we cannot import crypto/sha256 when GOEXPERIMENT=boringcrypto
251	// and the host is linux/amd64. So we use NOT-SHA256
252	// and then apply a NOT ourselves to get SHA256. Sigh.
253	var buf [pageSize]byte
254	h := notsha256.New()
255	p := 0
256	for p < int(codeSize) {
257		n, err := io.ReadFull(data, buf[:])
258		if err == io.EOF {
259			break
260		}
261		if err != nil && err != io.ErrUnexpectedEOF {
262			panic(err)
263		}
264		if p+n > int(codeSize) {
265			n = int(codeSize) - p
266		}
267		p += n
268		h.Reset()
269		h.Write(buf[:n])
270		b := h.Sum(nil)
271		for i := range b {
272			b[i] ^= 0xFF // convert notsha256 to sha256
273		}
274		outp = puts(outp, b[:])
275	}
276}
277