-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from saylorsolutions/feature/xorgen
Command xorgen
- Loading branch information
Showing
9 changed files
with
459 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// Code generated by xorgen, DO NOT EDIT. | ||
package {{.Package}} | ||
|
||
import ( | ||
"bytes" | ||
{{- if .Compressed }} | ||
"compress/gzip" | ||
"github.com/saylorsolutions/gocryptx/pkg/xor" | ||
"io" | ||
{{- else }} | ||
"github.com/saylorsolutions/gocryptx/pkg/xor" | ||
{{- end }} | ||
) | ||
|
||
var ( | ||
key{{.FileMethodName}} = {{ .KeyString }} | ||
data{{.FileMethodName}} = {{ .DataString }} | ||
offset{{.FileMethodName}} = {{ .Offset }} | ||
) | ||
|
||
func {{if .Exposed}}U{{else}}u{{end}}nscreen{{.FileMethodName}}() ([]byte, error) { | ||
r, err := xor.NewReader(bytes.NewReader(data{{.FileMethodName}}), key{{.FileMethodName}}, offset{{.FileMethodName}}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
{{- if .Compressed }} | ||
uncompress, err := gzip.NewReader(r) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var out bytes.Buffer | ||
_, err = io.Copy(&out, uncompress) | ||
if err != nil { | ||
return nil, err | ||
} | ||
uncompress.Close() | ||
return out.Bytes(), nil | ||
{{- else }} | ||
out := make([]byte, len(data)) | ||
_, err = r.Read(out) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return out, nil | ||
{{- end }} | ||
} | ||
|
||
func {{if .Exposed}}S{{else}}s{{end}}tream{{.FileMethodName}}() (io.Reader, error) { | ||
{{- if .Compressed }} | ||
r, err := xor.NewReader(bytes.NewReader(data{{.FileMethodName}}), key{{.FileMethodName}}, offset{{.FileMethodName}}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return gzip.NewReader(r) | ||
{{- else }} | ||
return xor.NewReader(bytes.NewReader(data), key, offset) | ||
{{- end }} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
package tmpl | ||
|
||
import ( | ||
"bytes" | ||
"compress/gzip" | ||
_ "embed" | ||
"fmt" | ||
"github.com/saylorsolutions/gocryptx/pkg/xor" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
"regexp" | ||
"text/template" | ||
"unicode" | ||
) | ||
|
||
const ( | ||
idealMinKeyLen = 20 | ||
) | ||
|
||
var ( | ||
//go:embed screen_embed.go.tmpl | ||
tmplText string | ||
tmplTemplate = template.Must(template.New("template").Parse(tmplText)) | ||
) | ||
|
||
type Params struct { | ||
Package string | ||
Exposed bool | ||
Compressed bool | ||
FileMethodName string | ||
KeyString string | ||
DataString string | ||
Offset int | ||
|
||
keyData []byte | ||
fileData []byte | ||
targetFileName string | ||
} | ||
|
||
// ParamOpt operates on Params in a standard and predictable way, and is used in GenerateFile. | ||
// If any ParamOpt returns an error, then file generation ceases and the error is returned. | ||
type ParamOpt = func(params *Params) error | ||
|
||
// CompressData indicates that data should be compressed. | ||
func CompressData(val ...bool) ParamOpt { | ||
return func(params *Params) error { | ||
if len(val) > 0 { | ||
params.Compressed = val[0] | ||
return nil | ||
} | ||
params.Compressed = true | ||
return nil | ||
} | ||
} | ||
|
||
// ExposeFunctions indicates that generated functions should be exposed. | ||
func ExposeFunctions(val ...bool) ParamOpt { | ||
return func(params *Params) error { | ||
if len(val) > 0 { | ||
params.Exposed = val[0] | ||
return nil | ||
} | ||
params.Exposed = true | ||
return nil | ||
} | ||
} | ||
|
||
// UseKeyOffset sets a key to be used instead of generating one randomly. | ||
func UseKeyOffset(key []byte, offset int) ParamOpt { | ||
return func(params *Params) error { | ||
params.keyData = key | ||
params.Offset = offset | ||
return nil | ||
} | ||
} | ||
|
||
// RandomKey generates a random key and offset based on the payload size. | ||
func RandomKey() ParamOpt { | ||
return randomKey | ||
} | ||
|
||
// GenerateFile will generate a file embedding the input file with XOR screening. | ||
// Various generation options may be passed as zero or more ParamOpt. | ||
func GenerateFile(input string, opts ...ParamOpt) error { | ||
params := new(Params) | ||
if err := populateContextData(params); err != nil { | ||
return err | ||
} | ||
if err := populateFileData(params, input); err != nil { | ||
return err | ||
} | ||
|
||
for _, opt := range opts { | ||
if err := opt(params); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
if len(params.keyData) == 0 { | ||
if err := randomKey(params); err != nil { | ||
return err | ||
} | ||
} | ||
if err := screenData(params); err != nil { | ||
return err | ||
} | ||
|
||
out, err := os.Create(params.targetFileName + ".go") | ||
if err != nil { | ||
return err | ||
} | ||
defer func() { | ||
_ = out.Close() | ||
}() | ||
|
||
if err := tmplTemplate.Execute(out, params); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func populateContextData(params *Params) error { | ||
cwd, err := os.Getwd() | ||
if err != nil { | ||
return err | ||
} | ||
params.Package = filepath.Base(cwd) | ||
return nil | ||
} | ||
|
||
var ( | ||
fileCleansePattern = regexp.MustCompile(`[^a-zA-Z0-9_]`) | ||
) | ||
|
||
func populateFileData(params *Params, file string) error { | ||
f, err := os.Open(file) | ||
if err != nil { | ||
return err | ||
} | ||
defer func() { | ||
_ = f.Close() | ||
}() | ||
|
||
data, err := io.ReadAll(f) | ||
if err != nil { | ||
return err | ||
} | ||
params.fileData = data | ||
_, fname := filepath.Split(file) | ||
params.FileMethodName = fileCleansePattern.ReplaceAllString(unicap(fname), "_") | ||
params.targetFileName = fileCleansePattern.ReplaceAllString(fname, "_") | ||
return nil | ||
} | ||
|
||
func randomKey(params *Params) error { | ||
var ( | ||
key []byte | ||
offset int | ||
err error | ||
length = len(params.fileData) | ||
) | ||
switch { | ||
case length > 3*idealMinKeyLen: | ||
key, offset, err = xor.GenKeyAndOffset(length / 3) | ||
case length > 2*idealMinKeyLen: | ||
key, offset, err = xor.GenKeyAndOffset(length / 2) | ||
default: | ||
key, offset, err = xor.GenKeyAndOffset(length) | ||
} | ||
if err != nil { | ||
return err | ||
} | ||
params.keyData = key | ||
params.Offset = offset | ||
return nil | ||
} | ||
|
||
func screenData(params *Params) error { | ||
var buf bytes.Buffer | ||
|
||
if params.Compressed { | ||
var buf bytes.Buffer | ||
w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) | ||
if err != nil { | ||
return err | ||
} | ||
_, err = w.Write(params.fileData) | ||
if err != nil { | ||
return err | ||
} | ||
if err := w.Close(); err != nil { | ||
return err | ||
} | ||
params.fileData = buf.Bytes() | ||
} | ||
|
||
w, err := xor.NewWriter(&buf, params.keyData, params.Offset) | ||
if err != nil { | ||
return err | ||
} | ||
_, err = w.Write(params.fileData) | ||
if err != nil { | ||
return err | ||
} | ||
params.KeyString = fmt.Sprintf("%#v", params.keyData) | ||
params.DataString = fmt.Sprintf("%#v", buf.Bytes()) | ||
return nil | ||
} | ||
|
||
func unicap(s string) string { | ||
runes := []rune(s) | ||
switch len(runes) { | ||
case 0: | ||
return "" | ||
case 1: | ||
return string(unicode.ToUpper(runes[0])) | ||
default: | ||
return string(append([]rune{unicode.ToUpper(runes[0])}, runes[1:]...)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
A test message that should be screened |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
//go:generate xorgen -Ec test.txt | ||
package tmpl | ||
|
||
import ( | ||
"github.com/stretchr/testify/assert" | ||
"io" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestUnscreenTest_txt(t *testing.T) { | ||
data, err := UnscreenTest_txt() | ||
assert.NoError(t, err) | ||
assert.Equal(t, "A test message that should be screened", string(data)) | ||
} | ||
|
||
func TestStreamTest_txt(t *testing.T) { | ||
r, err := StreamTest_txt() | ||
assert.NoError(t, err) | ||
var buf strings.Builder | ||
_, err = io.Copy(&buf, r) | ||
assert.NoError(t, err) | ||
assert.Equal(t, "A test message that should be screened", buf.String()) | ||
} |
Oops, something went wrong.