| // 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[:]) |
| } |
| } |