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