add seed flag to control how builds are reproducible

Fixes #26.
pull/36/head
lu4p 4 years ago committed by GitHub
parent 5604a2aa9e
commit 0cf8d4e7a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -27,6 +27,7 @@ order to:
* Replace as many useful identifiers as possible with short base64 hashes
* Remove [module build information](https://golang.org/pkg/runtime/debug/#ReadBuildInfo)
* Strip filenames and unnecessary lines, to make position info less useful
* Replace all string literals with AES encrypted literals
It also wraps calls to the linker in order to:

@ -3,7 +3,9 @@ package main
import (
"crypto/aes"
"crypto/cipher"
"math/rand"
"crypto/rand"
"fmt"
mathrand "math/rand"
)
// If math/rand.Seed() is not called, the generator behaves as if seeded by rand.Seed(1),
@ -22,7 +24,16 @@ func genNonce() []byte {
// genRandBytes return a random []byte with the length of size.
func genRandBytes(size int) []byte {
buffer := make([]byte, size)
rand.Read(buffer) // error is always nil so save to ignore
if envGarbleSeed == "random" {
_, err := rand.Read(buffer)
if err != nil {
panic(fmt.Sprintf("couldn't generate random key: %v", err))
}
} else {
mathrand.Read(buffer) // error is always nil so save to ignore
}
return buffer
}

@ -5,8 +5,10 @@ package main
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/json"
"flag"
"fmt"
@ -19,6 +21,7 @@ import (
"io"
"io/ioutil"
"log"
mathrand "math/rand"
"os"
"os/exec"
"path/filepath"
@ -33,25 +36,29 @@ var flagSet = flag.NewFlagSet("garble", flag.ContinueOnError)
var (
flagGarbleLiterals bool
flagDebugDir string
flagSeed string
)
func init() {
flagSet.Usage = usage
flagSet.BoolVar(&flagGarbleLiterals, "literals", false, "Encrypt all literals with AES, currently only literal strings are supported")
flagSet.StringVar(&flagDebugDir, "debugdir", "", "Write the garbled source to a given directory")
flagSet.StringVar(&flagDebugDir, "debugdir", "", "Write the garbled source to a given directory: '-debugdir=./debug'")
flagSet.StringVar(&flagSeed, "seed", "", "Provide a custom base64-encoded seed: '-seed=o9WDTZ4CN4w=' \nFor a random seed provide: '-seed=random'")
}
func usage() {
fmt.Fprintf(os.Stderr, `
Usage of garble:
garble build [build flags] [packages]
garble [flags] build [build flags] [packages]
The tool supports wrapping the following Go commands - run "garble cmd [args]"
instead of "go cmd [args]" to add obfuscation:
build
test
garble accepts the following flags:
`[1:])
flagSet.PrintDefaults()
os.Exit(2)
@ -86,7 +93,10 @@ var (
envGarbleDir = os.Getenv("GARBLE_DIR")
envGarbleLiterals = os.Getenv("GARBLE_LITERALS") == "true"
envGarbleDebugDir = os.Getenv("GARBLE_DEBUGDIR")
envGarbleSeed = os.Getenv("GARBLE_SEED")
envGoPrivate string // filled via 'go env' below to support 'go env -w'
seed []byte
)
type listedPackage struct {
@ -185,6 +195,7 @@ func mainErr(args []string) error {
}
os.Setenv("GARBLE_DIR", wd)
os.Setenv("GARBLE_LITERALS", fmt.Sprint(flagGarbleLiterals))
os.Setenv("GARBLE_SEED", flagSeed)
if flagDebugDir != "" {
if !filepath.IsAbs(flagDebugDir) {
@ -298,6 +309,7 @@ var transformFuncs = map[string]func([]string) ([]string, error){
}
func transformCompile(args []string) ([]string, error) {
var err error
flags, paths := splitFlagsFromFiles(args, ".go")
if len(paths) == 0 {
// Nothing to transform; probably just ["-V=full"].
@ -337,6 +349,22 @@ func transformCompile(args []string) ([]string, error) {
files = append(files, file)
}
if envGarbleSeed == "random" {
seed = make([]byte, 16) // random 128 bit seed
_, err = rand.Read(seed)
if err != nil {
return nil, fmt.Errorf("Error generating random seed: %v", err)
}
} else if envGarbleSeed != "" {
seed, err = base64.StdEncoding.DecodeString(envGarbleSeed)
if err != nil {
return nil, fmt.Errorf("Error decoding base64 encoded seed: %v", err)
}
mathrand.Seed(int64(binary.BigEndian.Uint64(seed)))
}
if envGarbleLiterals {
files = obfuscateLiterals(files)
}
@ -485,6 +513,8 @@ func readBuildIDs(flags []string) error {
if err != nil {
return err
}
// log.Println("buildid:", fileID)
if len(buildInfo.imports) == 0 {
buildInfo.firstImport = importPath
}
@ -553,6 +583,7 @@ func hashWith(salt, value string) string {
d := sha256.New()
io.WriteString(d, salt)
d.Write(seed)
io.WriteString(d, value)
sum := b64.EncodeToString(d.Sum(nil))

@ -96,14 +96,18 @@ func binsubstr(ts *testscript.TestScript, neg bool, args []string) {
}
func bincmp(ts *testscript.TestScript, neg bool, args []string) {
if neg {
ts.Fatalf("unsupported: ! bincmp")
}
if len(args) != 2 {
ts.Fatalf("usage: bincmp file1 file2")
}
data1 := ts.ReadFile(args[0])
data2 := ts.ReadFile(args[1])
if neg {
if data1 == data2 {
ts.Fatalf("%s and %s don't differ",
args[0], args[1])
}
return
}
if data1 != data2 {
if _, err := exec.LookPath("diffoscope"); err != nil {
ts.Logf("diffoscope is not installing; skipping binary diff")

@ -0,0 +1,43 @@
# Check the binary with a given base64 encoded seed
garble -literals -seed=OQg9kACEECQ= build main.go
exec ./main$exe
cmp stderr main.stdout
! binsubstr main$exe 'teststring' 'teststringVar'
[short] stop # checking that the build is reproducible and random is slow
# Also check that the binary is reproducible.
cp main$exe main_old$exe
rm main$exe
garble -literals -seed=OQg9kACEECQ= build main.go
bincmp main$exe main_old$exe
# Also check that a different seed leads to a different binary
cp main$exe main_old$exe
rm main$exe
garble -literals -seed=NruiDmVz6/s= build main.go
! bincmp main$exe main_old$exe
# Check the random binary
garble -literals -seed=random build main.go
exec ./main$exe
cmp stderr main.stdout
! binsubstr main$exe 'teststring' 'teststringVar'
# Also check that the random binary is not reproducible.
cp main$exe main_old$exe
rm main$exe
garble -literals -seed=random build main.go
! bincmp main$exe main_old$exe
-- main.go --
package main
var teststringVar = "teststring"
func main() {
println(teststringVar)
}
-- main.stdout --
teststring
Loading…
Cancel
Save