
This does not yet include support for the //go:embed directive added in this release. * Makefile.am (check-runtime): Don't create check-runtime-dir. (mostlyclean-local): Don't remove check-runtime-dir. (check-go-tool, check-vet): Copy in go.mod and modules.txt. (check-cgo-test, check-carchive-test): Add go.mod file. * Makefile.in: Regenerate. Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/280172
268 lines
8 KiB
Go
268 lines
8 KiB
Go
// 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[:])
|
|
}
|
|
}
|