|  | // Copyright 2020 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | // Package codesign provides basic functionalities for | 
|  | // ad-hoc code signing of Mach-O files. | 
|  | // | 
|  | // This is not a general tool for code-signing. It is made | 
|  | // specifically for the Go toolchain. It uses the same | 
|  | // ad-hoc signing algorithm as the Darwin linker. | 
|  | package codesign | 
|  |  | 
|  | import ( | 
|  | "crypto/sha256" | 
|  | "debug/macho" | 
|  | "encoding/binary" | 
|  | "io" | 
|  | ) | 
|  |  | 
|  | // Code signature layout. | 
|  | // | 
|  | // The code signature is a block of bytes that contains | 
|  | // a SuperBlob, which contains one or more Blobs. For ad-hoc | 
|  | // signing, a single CodeDirectory Blob suffices. | 
|  | // | 
|  | // A SuperBlob starts with its header (the binary representation | 
|  | // of the SuperBlob struct), followed by a list of (in our case, | 
|  | // one) Blobs (offset and size). A CodeDirectory Blob starts | 
|  | // with its head (the binary representation of CodeDirectory struct), | 
|  | // followed by the identifier (as a C string) and the hashes, at | 
|  | // the corresponding offsets. | 
|  | // | 
|  | // The signature data must be included in the __LINKEDIT segment. | 
|  | // In the Mach-O file header, an LC_CODE_SIGNATURE load command | 
|  | // points to the data. | 
|  |  | 
|  | const ( | 
|  | pageSizeBits = 12 | 
|  | pageSize     = 1 << pageSizeBits | 
|  | ) | 
|  |  | 
|  | const LC_CODE_SIGNATURE = 0x1d | 
|  |  | 
|  | // Constants and struct layouts are from | 
|  | // https://opensource.apple.com/source/xnu/xnu-4903.270.47/osfmk/kern/cs_blobs.h | 
|  |  | 
|  | const ( | 
|  | CSMAGIC_REQUIREMENT        = 0xfade0c00 // single Requirement blob | 
|  | CSMAGIC_REQUIREMENTS       = 0xfade0c01 // Requirements vector (internal requirements) | 
|  | CSMAGIC_CODEDIRECTORY      = 0xfade0c02 // CodeDirectory blob | 
|  | CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0 // embedded form of signature data | 
|  | CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1 // multi-arch collection of embedded signatures | 
|  |  | 
|  | CSSLOT_CODEDIRECTORY = 0 // slot index for CodeDirectory | 
|  | ) | 
|  |  | 
|  | const ( | 
|  | CS_HASHTYPE_SHA1             = 1 | 
|  | CS_HASHTYPE_SHA256           = 2 | 
|  | CS_HASHTYPE_SHA256_TRUNCATED = 3 | 
|  | CS_HASHTYPE_SHA384           = 4 | 
|  | ) | 
|  |  | 
|  | const ( | 
|  | CS_EXECSEG_MAIN_BINARY     = 0x1   // executable segment denotes main binary | 
|  | CS_EXECSEG_ALLOW_UNSIGNED  = 0x10  // allow unsigned pages (for debugging) | 
|  | CS_EXECSEG_DEBUGGER        = 0x20  // main binary is debugger | 
|  | CS_EXECSEG_JIT             = 0x40  // JIT enabled | 
|  | CS_EXECSEG_SKIP_LV         = 0x80  // skip library validation | 
|  | CS_EXECSEG_CAN_LOAD_CDHASH = 0x100 // can bless cdhash for execution | 
|  | CS_EXECSEG_CAN_EXEC_CDHASH = 0x200 // can execute blessed cdhash | 
|  | ) | 
|  |  | 
|  | type Blob struct { | 
|  | typ    uint32 // type of entry | 
|  | offset uint32 // offset of entry | 
|  | // data follows | 
|  | } | 
|  |  | 
|  | func (b *Blob) put(out []byte) []byte { | 
|  | out = put32be(out, b.typ) | 
|  | out = put32be(out, b.offset) | 
|  | return out | 
|  | } | 
|  |  | 
|  | const blobSize = 2 * 4 | 
|  |  | 
|  | type SuperBlob struct { | 
|  | magic  uint32 // magic number | 
|  | length uint32 // total length of SuperBlob | 
|  | count  uint32 // number of index entries following | 
|  | // blobs []Blob | 
|  | } | 
|  |  | 
|  | func (s *SuperBlob) put(out []byte) []byte { | 
|  | out = put32be(out, s.magic) | 
|  | out = put32be(out, s.length) | 
|  | out = put32be(out, s.count) | 
|  | return out | 
|  | } | 
|  |  | 
|  | const superBlobSize = 3 * 4 | 
|  |  | 
|  | type CodeDirectory struct { | 
|  | magic         uint32 // magic number (CSMAGIC_CODEDIRECTORY) | 
|  | length        uint32 // total length of CodeDirectory blob | 
|  | version       uint32 // compatibility version | 
|  | flags         uint32 // setup and mode flags | 
|  | hashOffset    uint32 // offset of hash slot element at index zero | 
|  | identOffset   uint32 // offset of identifier string | 
|  | nSpecialSlots uint32 // number of special hash slots | 
|  | nCodeSlots    uint32 // number of ordinary (code) hash slots | 
|  | codeLimit     uint32 // limit to main image signature range | 
|  | hashSize      uint8  // size of each hash in bytes | 
|  | hashType      uint8  // type of hash (cdHashType* constants) | 
|  | _pad1         uint8  // unused (must be zero) | 
|  | pageSize      uint8  // log2(page size in bytes); 0 => infinite | 
|  | _pad2         uint32 // unused (must be zero) | 
|  | scatterOffset uint32 | 
|  | teamOffset    uint32 | 
|  | _pad3         uint32 | 
|  | codeLimit64   uint64 | 
|  | execSegBase   uint64 | 
|  | execSegLimit  uint64 | 
|  | execSegFlags  uint64 | 
|  | // data follows | 
|  | } | 
|  |  | 
|  | func (c *CodeDirectory) put(out []byte) []byte { | 
|  | out = put32be(out, c.magic) | 
|  | out = put32be(out, c.length) | 
|  | out = put32be(out, c.version) | 
|  | out = put32be(out, c.flags) | 
|  | out = put32be(out, c.hashOffset) | 
|  | out = put32be(out, c.identOffset) | 
|  | out = put32be(out, c.nSpecialSlots) | 
|  | out = put32be(out, c.nCodeSlots) | 
|  | out = put32be(out, c.codeLimit) | 
|  | out = put8(out, c.hashSize) | 
|  | out = put8(out, c.hashType) | 
|  | out = put8(out, c._pad1) | 
|  | out = put8(out, c.pageSize) | 
|  | out = put32be(out, c._pad2) | 
|  | out = put32be(out, c.scatterOffset) | 
|  | out = put32be(out, c.teamOffset) | 
|  | out = put32be(out, c._pad3) | 
|  | out = put64be(out, c.codeLimit64) | 
|  | out = put64be(out, c.execSegBase) | 
|  | out = put64be(out, c.execSegLimit) | 
|  | out = put64be(out, c.execSegFlags) | 
|  | return out | 
|  | } | 
|  |  | 
|  | const codeDirectorySize = 13*4 + 4 + 4*8 | 
|  |  | 
|  | // CodeSigCmd is Mach-O LC_CODE_SIGNATURE load command. | 
|  | type CodeSigCmd struct { | 
|  | Cmd      uint32 // LC_CODE_SIGNATURE | 
|  | Cmdsize  uint32 // sizeof this command (16) | 
|  | Dataoff  uint32 // file offset of data in __LINKEDIT segment | 
|  | Datasize uint32 // file size of data in __LINKEDIT segment | 
|  | } | 
|  |  | 
|  | func FindCodeSigCmd(f *macho.File) (CodeSigCmd, bool) { | 
|  | get32 := f.ByteOrder.Uint32 | 
|  | for _, l := range f.Loads { | 
|  | data := l.Raw() | 
|  | cmd := get32(data) | 
|  | if cmd == LC_CODE_SIGNATURE { | 
|  | return CodeSigCmd{ | 
|  | cmd, | 
|  | get32(data[4:]), | 
|  | get32(data[8:]), | 
|  | get32(data[12:]), | 
|  | }, true | 
|  | } | 
|  | } | 
|  | return CodeSigCmd{}, false | 
|  | } | 
|  |  | 
|  | func put32be(b []byte, x uint32) []byte { binary.BigEndian.PutUint32(b, x); return b[4:] } | 
|  | func put64be(b []byte, x uint64) []byte { binary.BigEndian.PutUint64(b, x); return b[8:] } | 
|  | func put8(b []byte, x uint8) []byte     { b[0] = x; return b[1:] } | 
|  | func puts(b, s []byte) []byte           { n := copy(b, s); return b[n:] } | 
|  |  | 
|  | // Size computes the size of the code signature. | 
|  | // id is the identifier used for signing (a field in CodeDirectory blob, which | 
|  | // has no significance in ad-hoc signing). | 
|  | func Size(codeSize int64, id string) int64 { | 
|  | nhashes := (codeSize + pageSize - 1) / pageSize | 
|  | idOff := int64(codeDirectorySize) | 
|  | hashOff := idOff + int64(len(id)+1) | 
|  | cdirSz := hashOff + nhashes*sha256.Size | 
|  | return int64(superBlobSize+blobSize) + cdirSz | 
|  | } | 
|  |  | 
|  | // Sign generates an ad-hoc code signature and writes it to out. | 
|  | // out must have length at least Size(codeSize, id). | 
|  | // data is the file content without the signature, of size codeSize. | 
|  | // textOff and textSize is the file offset and size of the text segment. | 
|  | // isMain is true if this is a main executable. | 
|  | // id is the identifier used for signing (a field in CodeDirectory blob, which | 
|  | // has no significance in ad-hoc signing). | 
|  | func Sign(out []byte, data io.Reader, id string, codeSize, textOff, textSize int64, isMain bool) { | 
|  | nhashes := (codeSize + pageSize - 1) / pageSize | 
|  | idOff := int64(codeDirectorySize) | 
|  | hashOff := idOff + int64(len(id)+1) | 
|  | sz := Size(codeSize, id) | 
|  |  | 
|  | // emit blob headers | 
|  | sb := SuperBlob{ | 
|  | magic:  CSMAGIC_EMBEDDED_SIGNATURE, | 
|  | length: uint32(sz), | 
|  | count:  1, | 
|  | } | 
|  | blob := Blob{ | 
|  | typ:    CSSLOT_CODEDIRECTORY, | 
|  | offset: superBlobSize + blobSize, | 
|  | } | 
|  | cdir := CodeDirectory{ | 
|  | magic:        CSMAGIC_CODEDIRECTORY, | 
|  | length:       uint32(sz) - (superBlobSize + blobSize), | 
|  | version:      0x20400, | 
|  | flags:        0x20002, // adhoc | linkerSigned | 
|  | hashOffset:   uint32(hashOff), | 
|  | identOffset:  uint32(idOff), | 
|  | nCodeSlots:   uint32(nhashes), | 
|  | codeLimit:    uint32(codeSize), | 
|  | hashSize:     sha256.Size, | 
|  | hashType:     CS_HASHTYPE_SHA256, | 
|  | pageSize:     uint8(pageSizeBits), | 
|  | execSegBase:  uint64(textOff), | 
|  | execSegLimit: uint64(textSize), | 
|  | } | 
|  | if isMain { | 
|  | cdir.execSegFlags = CS_EXECSEG_MAIN_BINARY | 
|  | } | 
|  |  | 
|  | outp := out | 
|  | outp = sb.put(outp) | 
|  | outp = blob.put(outp) | 
|  | outp = cdir.put(outp) | 
|  |  | 
|  | // emit the identifier | 
|  | outp = puts(outp, []byte(id+"\000")) | 
|  |  | 
|  | // emit hashes | 
|  | var buf [pageSize]byte | 
|  | h := sha256.New() | 
|  | p := 0 | 
|  | for p < int(codeSize) { | 
|  | n, err := io.ReadFull(data, buf[:]) | 
|  | if err == io.EOF { | 
|  | break | 
|  | } | 
|  | if err != nil && err != io.ErrUnexpectedEOF { | 
|  | panic(err) | 
|  | } | 
|  | if p+n > int(codeSize) { | 
|  | n = int(codeSize) - p | 
|  | } | 
|  | p += n | 
|  | h.Reset() | 
|  | h.Write(buf[:n]) | 
|  | b := h.Sum(nil) | 
|  | outp = puts(outp, b[:]) | 
|  | } | 
|  | } |