...

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

Documentation: cmd/internal/codesign

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

View as plain text