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 * Replace as many useful identifiers as possible with short base64 hashes
* Remove [module build information](https://golang.org/pkg/runtime/debug/#ReadBuildInfo) * Remove [module build information](https://golang.org/pkg/runtime/debug/#ReadBuildInfo)
* Strip filenames and unnecessary lines, to make position info less useful * 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: It also wraps calls to the linker in order to:

@ -3,7 +3,9 @@ package main
import ( import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "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), // 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. // genRandBytes return a random []byte with the length of size.
func genRandBytes(size int) []byte { func genRandBytes(size int) []byte {
buffer := make([]byte, size) 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 return buffer
} }

@ -5,8 +5,10 @@ package main
import ( import (
"bytes" "bytes"
"crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/binary"
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
@ -19,6 +21,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
mathrand "math/rand"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -33,25 +36,29 @@ var flagSet = flag.NewFlagSet("garble", flag.ContinueOnError)
var ( var (
flagGarbleLiterals bool flagGarbleLiterals bool
flagDebugDir string flagDebugDir string
flagSeed string
) )
func init() { func init() {
flagSet.Usage = usage flagSet.Usage = usage
flagSet.BoolVar(&flagGarbleLiterals, "literals", false, "Encrypt all literals with AES, currently only literal strings are supported") 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() { func usage() {
fmt.Fprintf(os.Stderr, ` fmt.Fprintf(os.Stderr, `
Usage of garble: 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]" The tool supports wrapping the following Go commands - run "garble cmd [args]"
instead of "go cmd [args]" to add obfuscation: instead of "go cmd [args]" to add obfuscation:
build build
test test
garble accepts the following flags:
`[1:]) `[1:])
flagSet.PrintDefaults() flagSet.PrintDefaults()
os.Exit(2) os.Exit(2)
@ -86,7 +93,10 @@ var (
envGarbleDir = os.Getenv("GARBLE_DIR") envGarbleDir = os.Getenv("GARBLE_DIR")
envGarbleLiterals = os.Getenv("GARBLE_LITERALS") == "true" envGarbleLiterals = os.Getenv("GARBLE_LITERALS") == "true"
envGarbleDebugDir = os.Getenv("GARBLE_DEBUGDIR") envGarbleDebugDir = os.Getenv("GARBLE_DEBUGDIR")
envGarbleSeed = os.Getenv("GARBLE_SEED")
envGoPrivate string // filled via 'go env' below to support 'go env -w' envGoPrivate string // filled via 'go env' below to support 'go env -w'
seed []byte
) )
type listedPackage struct { type listedPackage struct {
@ -185,6 +195,7 @@ func mainErr(args []string) error {
} }
os.Setenv("GARBLE_DIR", wd) os.Setenv("GARBLE_DIR", wd)
os.Setenv("GARBLE_LITERALS", fmt.Sprint(flagGarbleLiterals)) os.Setenv("GARBLE_LITERALS", fmt.Sprint(flagGarbleLiterals))
os.Setenv("GARBLE_SEED", flagSeed)
if flagDebugDir != "" { if flagDebugDir != "" {
if !filepath.IsAbs(flagDebugDir) { if !filepath.IsAbs(flagDebugDir) {
@ -298,6 +309,7 @@ var transformFuncs = map[string]func([]string) ([]string, error){
} }
func transformCompile(args []string) ([]string, error) { func transformCompile(args []string) ([]string, error) {
var err error
flags, paths := splitFlagsFromFiles(args, ".go") flags, paths := splitFlagsFromFiles(args, ".go")
if len(paths) == 0 { if len(paths) == 0 {
// Nothing to transform; probably just ["-V=full"]. // Nothing to transform; probably just ["-V=full"].
@ -337,6 +349,22 @@ func transformCompile(args []string) ([]string, error) {
files = append(files, file) 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 { if envGarbleLiterals {
files = obfuscateLiterals(files) files = obfuscateLiterals(files)
} }
@ -485,6 +513,8 @@ func readBuildIDs(flags []string) error {
if err != nil { if err != nil {
return err return err
} }
// log.Println("buildid:", fileID)
if len(buildInfo.imports) == 0 { if len(buildInfo.imports) == 0 {
buildInfo.firstImport = importPath buildInfo.firstImport = importPath
} }
@ -553,6 +583,7 @@ func hashWith(salt, value string) string {
d := sha256.New() d := sha256.New()
io.WriteString(d, salt) io.WriteString(d, salt)
d.Write(seed)
io.WriteString(d, value) io.WriteString(d, value)
sum := b64.EncodeToString(d.Sum(nil)) 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) { func bincmp(ts *testscript.TestScript, neg bool, args []string) {
if neg {
ts.Fatalf("unsupported: ! bincmp")
}
if len(args) != 2 { if len(args) != 2 {
ts.Fatalf("usage: bincmp file1 file2") ts.Fatalf("usage: bincmp file1 file2")
} }
data1 := ts.ReadFile(args[0]) data1 := ts.ReadFile(args[0])
data2 := ts.ReadFile(args[1]) 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 data1 != data2 {
if _, err := exec.LookPath("diffoscope"); err != nil { if _, err := exec.LookPath("diffoscope"); err != nil {
ts.Logf("diffoscope is not installing; skipping binary diff") 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