Source file src/cmd/internal/codesign/codesign.go

     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.
    11  package codesign
    12  
    13  import (
    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  
    38  const (
    39  	pageSizeBits = 12
    40  	pageSize     = 1 << pageSizeBits
    41  )
    42  
    43  const 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  
    48  const (
    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  
    58  const (
    59  	CS_HASHTYPE_SHA1             = 1
    60  	CS_HASHTYPE_SHA256           = 2
    61  	CS_HASHTYPE_SHA256_TRUNCATED = 3
    62  	CS_HASHTYPE_SHA384           = 4
    63  )
    64  
    65  const (
    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  
    75  type Blob struct {
    76  	typ    uint32 // type of entry
    77  	offset uint32 // offset of entry
    78  	// data follows
    79  }
    80  
    81  func (b *Blob) put(out []byte) []byte {
    82  	out = put32be(out, b.typ)
    83  	out = put32be(out, b.offset)
    84  	return out
    85  }
    86  
    87  const blobSize = 2 * 4
    88  
    89  type 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  
    96  func (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  
   103  const superBlobSize = 3 * 4
   104  
   105  type 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  
   130  func (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  
   155  const codeDirectorySize = 13*4 + 4 + 4*8
   156  
   157  // CodeSigCmd is Mach-O LC_CODE_SIGNATURE load command.
   158  type 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  
   165  func 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  
   182  func put32be(b []byte, x uint32) []byte { binary.BigEndian.PutUint32(b, x); return b[4:] }
   183  func put64be(b []byte, x uint64) []byte { binary.BigEndian.PutUint64(b, x); return b[8:] }
   184  func put8(b []byte, x uint8) []byte     { b[0] = x; return b[1:] }
   185  func 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).
   190  func 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).
   205  func 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  

View as plain text