├── .gitignore ├── example └── secstring.go ├── .travis.yml ├── LICENSE ├── README.md ├── secstring_test.go ├── secstring_mocks_test.go └── secstring.go /.gitignore: -------------------------------------------------------------------------------- 1 | .*.sw[op] 2 | *.o 3 | *.a 4 | *.so 5 | _obj 6 | _test 7 | *.[568vq] 8 | [568vq].out 9 | *.cgo1.go 10 | *.cgo2.c 11 | _cgo_defun.c 12 | _cgo_gotypes.go 13 | _cgo_export.* 14 | _testmain.go 15 | *.exe 16 | *.test 17 | -------------------------------------------------------------------------------- /example/secstring.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/worr/secstring" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | str := "Man do I love safe strings!" 11 | ss, err := secstring.FromString(&str) 12 | if err != nil { 13 | log.Fatalf("Can't initialize string: %v", err) 14 | } 15 | defer ss.Destroy() 16 | 17 | fmt.Printf("Old string: %v\n", str) 18 | fmt.Printf("String: %v\n", string(ss.String)) 19 | } 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.4 5 | 6 | script: 7 | - go get golang.org/x/tools/cmd/cover 8 | - go get github.com/hailiang/goveralls 9 | - go get golang.org/x/tools/cmd/goimports 10 | - go get github.com/axw/gocov/gocov 11 | #- go get code.google.com/p/gomock/gomock 12 | #- go get github.com/qur/withmock 13 | #- PATH=$PATH:$HOME/gopath/bin withmock go test -covermode=count -coverprofile="$(pwd)/profile.cov" secstring_mocks_test.go secstring.go 14 | - PATH=$PATH:$HOME/gopath/bin go test -covermode=count -coverprofile="$(pwd)/profile.cov" secstring_test.go secstring.go 15 | - sed -i -e 's:command-line-arguments:github.com/worr/secstring:' profile.cov 16 | #- sed -i -e 's:command-line-arguments:github.com/worr/secstring:' profile2.cov 17 | #- $HOME/gopath/bin/gocov convert profile.cov profile2.cov > profile.json 18 | - $HOME/gopath/bin/gocov convert profile.cov > profile.json 19 | - $HOME/gopath/bin/goveralls -gocovdata=profile.json -service=travis-ci 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 William Orr 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Secure-ish strings in Go! 2 | 3 | secstring aims to provide a basic secure string implementation to go 4 | 5 | ## Badges! 6 | 7 | [![Build Status](https://travis-ci.org/worr/secstring.png?branch=master)](https://travis-ci.org/worr/secstring) 8 | [![Coverage Status](https://coveralls.io/repos/worr/secstring/badge.png)](https://coveralls.io/r/worr/secstring) 9 | [![GoDoc](https://godoc.org/gitlab.com/worr/secstring.git?status.png)](https://godoc.org/github.com/worr/secstring) 10 | 11 | ## Should I use this? 12 | 13 | Probably not. I've implemented this mostly as a PoC. I use it somewhat, but I don't recommend other people use it right now. 14 | 15 | ## What makes them secure? 16 | 17 | * strings are unlikely to be written to swap (except during hibernation) 18 | * strings are immutable - modifying them causes a non-recoverable `panic` 19 | 20 | ## This doesn't work on Windows/FreeBSD/etc. 21 | 22 | Yes. I use `syscall` heavily, and unfortunately, golang in many BSDs 23 | don't have the functions I'm using. I'm going to submit patches, so hopefully 24 | they get added soon. 25 | 26 | Windows support will never be added. I don't have a test box for it. 27 | 28 | ## Can I get an example? 29 | 30 | Damn straight. 31 | 32 | ```go 33 | import "github.com/worr/secstring" 34 | import "fmt" 35 | 36 | func main() { 37 | str := "testing" 38 | ss, _ := secstring.FromString(&str) 39 | defer ss.Destroy() 40 | 41 | fmt.Printf("String: %v", ss.String) 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /secstring_test.go: -------------------------------------------------------------------------------- 1 | package secstring 2 | 3 | import "testing" 4 | 5 | func TestNew(t *testing.T) { 6 | b := []byte("test") 7 | c := []byte("test") 8 | s, err := NewSecString(b) 9 | if s == nil { 10 | t.Error("New: Expected non-nil SecString. Got nil") 11 | } 12 | 13 | if err != nil { 14 | t.Errorf("New: Expected nil err. Got %v", err) 15 | } 16 | defer s.Destroy() 17 | 18 | for i := 0; i < len(b); i++ { 19 | if b[i] != 0 { 20 | t.Error("New: input should have been cleared") 21 | t.Errorf("offending byte %v at position %v", b[i], i) 22 | } 23 | } 24 | 25 | count := 0 26 | for i := 0; i < len(c); i++ { 27 | if c[i] == s.String[i] { 28 | count++ 29 | } 30 | } 31 | 32 | if s.Length != 4 { 33 | t.Errorf("New: Expected length 4. Got %v", s.Length) 34 | } 35 | } 36 | 37 | func TestDestroy(t *testing.T) { 38 | s, err := NewSecString([]byte("test")) 39 | err = s.Destroy() 40 | 41 | if err != nil { 42 | t.Errorf("Destroy: Expected nil err. Got %v", err) 43 | } 44 | 45 | if s.String != nil { 46 | t.Errorf("Destroy: Expected nil s.String. got %v", s.String) 47 | } 48 | } 49 | 50 | func TestFromString(t *testing.T) { 51 | b := "test" 52 | c := []byte("test") 53 | s, err := FromString(&b) 54 | if s == nil { 55 | t.Error("FromString: Expected non-nil SecString. Got nil") 56 | } 57 | 58 | if err != nil { 59 | t.Errorf("FromString: Expected nil err. Got %v", err) 60 | } 61 | defer s.Destroy() 62 | 63 | for i := 0; i < s.Length; i++ { 64 | if b[i] != uint8('x') { 65 | t.Error("FromString: input should have been cleared") 66 | t.Errorf("offending byte %v at position %v", b[i], i) 67 | } 68 | } 69 | 70 | if s.Length != 4 { 71 | t.Errorf("FromString: Expected length 4. Got %v", s.Length) 72 | } 73 | 74 | count := 0 75 | for i := 0; i < len(c); i++ { 76 | if c[i] == s.String[i] { 77 | count++ 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /secstring_mocks_test.go: -------------------------------------------------------------------------------- 1 | package secstring 2 | 3 | import ( 4 | "code.google.com/p/gomock/gomock" 5 | "errors" 6 | "golang.org/x/sys/unix" // mock 7 | "testing" 8 | ) 9 | 10 | func TestNewBadMmap(t *testing.T) { 11 | ctrl := gomock.NewController(t) 12 | defer ctrl.Finish() 13 | 14 | b := []byte("test") 15 | unix.MOCK().SetController(ctrl) 16 | unix.EXPECT().Mmap(0, int64(0), 16, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_ANON|unix.MAP_PRIVATE).Return(nil, errors.New("error")) 17 | if _, err := NewSecString(b); err == nil { 18 | t.Error("NewBadMmap: Expected non-nil error. Got nil") 19 | } 20 | } 21 | 22 | func TestNewBadMlock(t *testing.T) { 23 | ctrl := gomock.NewController(t) 24 | defer ctrl.Finish() 25 | 26 | b := []byte("test") 27 | unix.MOCK().SetController(ctrl) 28 | unix.EXPECT().Mmap(0, int64(0), 16, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_ANON|unix.MAP_PRIVATE).Return([]byte("test"), nil) 29 | unix.EXPECT().Mlock([]byte("test")).Return(errors.New("error")) 30 | unix.EXPECT().Munmap([]byte("test")).Return(nil) 31 | if _, err := NewSecString(b); err == nil { 32 | t.Error("NewBadMlock: Expected non-nil error. Got nil") 33 | } 34 | checkScrubbed("NewBadRand", b, t) 35 | } 36 | 37 | func checkScrubbed(test string, val []byte, t *testing.T) { 38 | for i := 0; i < len(val); i++ { 39 | if val[i] != 0 { 40 | t.Errorf("%v: b not scrubbed. b: %v", test, val) 41 | } 42 | } 43 | } 44 | 45 | func TestNewBadRand(t *testing.T) { 46 | ctrl := gomock.NewController(t) 47 | defer ctrl.Finish() 48 | 49 | b := []byte("test") 50 | unix.MOCK().SetController(ctrl) 51 | unix.EXPECT().Mmap(0, int64(0), 16, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_ANON|unix.MAP_PRIVATE).Return([]byte("test"), nil) 52 | unix.EXPECT().Mlock([]byte("test")).Return(nil) 53 | unix.EXPECT().Munmap(make([]byte, 4)).Return(nil) 54 | if _, err := NewSecString(b); err == nil { 55 | t.Error("NewBadRand: Expected non-nil error. Got nil") 56 | } 57 | checkScrubbed("NewBadRand", b, t) 58 | } 59 | 60 | func TestNewBadMprotect(t *testing.T) { 61 | ctrl := gomock.NewController(t) 62 | defer ctrl.Finish() 63 | 64 | b := []byte("test") 65 | k := make([]byte, 32) 66 | unix.MOCK().SetController(ctrl) 67 | 68 | unix.EXPECT().Mmap(0, int64(0), 16, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_ANON|unix.MAP_PRIVATE).Return([]byte("test"), nil) 69 | unix.EXPECT().Mlock(b).Return(nil) 70 | unix.EXPECT().Munmap(make([]byte, 4)).Return(nil) 71 | unix.EXPECT().Mprotect([]byte("test"), 3).Return(errors.New("error")) 72 | 73 | if _, err := NewSecString(b); err == nil { 74 | t.Error("NewBadMprotect: Expected non-nil error. Got nil.") 75 | } 76 | checkScrubbed("NewBadRand", b, t) 77 | } 78 | -------------------------------------------------------------------------------- /secstring.go: -------------------------------------------------------------------------------- 1 | package secstring 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | type SecString struct { 12 | String []byte // Protected string 13 | Length int // Length of the target string 14 | } 15 | 16 | func memset(s []byte, c byte) { 17 | for i := 0; i < len(s); i++ { 18 | s[i] = c 19 | } 20 | } 21 | 22 | // Takes a []byte and builds a SecString out of it, wiping str in the 23 | // process. 24 | // 25 | // A SecString should be destroyed when it's no longer needed to prevent memory leaks. 26 | // It is probably a good idea to defer SecString.Destroy() 27 | func NewSecString(str []byte) (*SecString, error) { 28 | ret := &SecString{Length: len(str)} 29 | var err error 30 | 31 | ret.String, err = unix.Mmap(-1, 0, ret.Length, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_ANON|unix.MAP_PRIVATE) 32 | if err != nil { 33 | memset(str, 0) 34 | return nil, err 35 | } 36 | 37 | if err := unix.Mlock(ret.String); err != nil { 38 | memset(str, 0) 39 | unix.Munmap(ret.String) 40 | return nil, err 41 | } 42 | 43 | for i := 0; i < ret.Length; i++ { 44 | ret.String[i] = str[i] 45 | str[i] = 0 46 | } 47 | 48 | if err := unix.Mprotect(ret.String, unix.PROT_READ); err != nil { 49 | memset(str, 0) 50 | memset(ret.String, 0) 51 | unix.Munmap(ret.String) 52 | return nil, err 53 | } 54 | 55 | return ret, nil 56 | } 57 | 58 | // Makes a new SecString from a string reference. Destroys str after creating 59 | // the secstring 60 | func FromString(str *string) (*SecString, error) { 61 | b := make([]byte, len(*str)) 62 | for i := 0; i < len(*str); i++ { 63 | b[i] = (*str)[i] 64 | } 65 | *str = strings.Repeat("x", len(*str)) 66 | return NewSecString(b) 67 | } 68 | 69 | func (s *SecString) Clone() (*SecString, error) { 70 | var ret *SecString 71 | 72 | str := make([]byte, len(s.String), cap(s.String)) 73 | if copied := copy(str, s.String); copied != len(s.String) { 74 | return nil, errors.New(fmt.Sprintf("Only %v copied", copied)) 75 | } 76 | 77 | var err error 78 | if ret, err = NewSecString(str); err != nil { 79 | return nil, err 80 | } 81 | 82 | return ret, nil 83 | } 84 | 85 | // Destroys the s. *MUST* be called to prevent memory leaks. Probably best to 86 | // be called in a defer 87 | func (s *SecString) Destroy() error { 88 | if err := unix.Mprotect(s.String, unix.PROT_READ|unix.PROT_WRITE); err != nil { 89 | return err 90 | } 91 | 92 | memset(s.String, 0) 93 | 94 | if err := unix.Munlock(s.String); err != nil { 95 | return err 96 | } 97 | 98 | if err := unix.Munmap(s.String); err != nil { 99 | return err 100 | } 101 | 102 | s.String = nil 103 | return nil 104 | } 105 | --------------------------------------------------------------------------------