summaryrefslogtreecommitdiffstats
path: root/src/cmd/internal/codesign/codesign.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
commit43a123c1ae6613b3efeed291fa552ecd909d3acf (patch)
treefd92518b7024bc74031f78a1cf9e454b65e73665 /src/cmd/internal/codesign/codesign.go
parentInitial commit. (diff)
downloadgolang-1.20-upstream.tar.xz
golang-1.20-upstream.zip
Adding upstream version 1.20.14.upstream/1.20.14upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/internal/codesign/codesign.go')
-rw-r--r--src/cmd/internal/codesign/codesign.go276
1 files changed, 276 insertions, 0 deletions
diff --git a/src/cmd/internal/codesign/codesign.go b/src/cmd/internal/codesign/codesign.go
new file mode 100644
index 0000000..1116393
--- /dev/null
+++ b/src/cmd/internal/codesign/codesign.go
@@ -0,0 +1,276 @@
+// 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 (
+ "debug/macho"
+ "encoding/binary"
+ "io"
+
+ "cmd/internal/notsha256"
+)
+
+// 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*notsha256.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: notsha256.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
+ // NOTE(rsc): These must be SHA256, but for cgo bootstrap reasons
+ // we cannot import crypto/sha256 when GOEXPERIMENT=boringcrypto
+ // and the host is linux/amd64. So we use NOT-SHA256
+ // and then apply a NOT ourselves to get SHA256. Sigh.
+ var buf [pageSize]byte
+ h := notsha256.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)
+ for i := range b {
+ b[i] ^= 0xFF // convert notsha256 to sha256
+ }
+ outp = puts(outp, b[:])
+ }
+}