From 0cf8d4e7a6ff1f1297f573aa607bcf468fd612ec Mon Sep 17 00:00:00 2001 From: lu4p Date: Sat, 13 Jun 2020 21:50:10 +0200 Subject: [PATCH] add seed flag to control how builds are reproducible Fixes #26. --- README.md | 1 + crypto.go | 15 ++++++++++++-- main.go | 35 +++++++++++++++++++++++++++++-- main_test.go | 10 ++++++--- testdata/scripts/seed.txt | 43 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 testdata/scripts/seed.txt diff --git a/README.md b/README.md index cf13edc..5624801 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/crypto.go b/crypto.go index eacd724..7e0054b 100644 --- a/crypto.go +++ b/crypto.go @@ -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 } diff --git a/main.go b/main.go index 6f6096d..b49f435 100644 --- a/main.go +++ b/main.go @@ -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)) diff --git a/main_test.go b/main_test.go index ae520d7..97ae9bd 100644 --- a/main_test.go +++ b/main_test.go @@ -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") diff --git a/testdata/scripts/seed.txt b/testdata/scripts/seed.txt new file mode 100644 index 0000000..4b77399 --- /dev/null +++ b/testdata/scripts/seed.txt @@ -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 \ No newline at end of file