diff --git a/go.mod b/go.mod index 69117fe..0942990 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,8 @@ go 1.14 require ( github.com/google/uuid v1.2.0 + // ginkgo and gomega provide BDD testing capabilities + github.com/onsi/ginkgo v1.16.5-0.20210921182526-45ab7ed508d1 + github.com/onsi/gomega v1.16.0 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 ) diff --git a/go.sum b/go.sum index 7c70f78..708bbf6 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,106 @@ +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5-0.20210921182526-45ab7ed508d1 h1:VbNADOt2Ft9A9H2pGLY5EinbdqbsHC8+kBTAfavvIgE= +github.com/onsi/ginkgo v1.16.5-0.20210921182526-45ab7ed508d1/go.mod h1:FGGTNz05swxobKgpWKhnxbEiUUxN+CeHRdF9ViWWPDw= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/userlib.go b/userlib.go index 5b8c4e0..14381e2 100644 --- a/userlib.go +++ b/userlib.go @@ -1,14 +1,18 @@ package userlib import ( + "bytes" + "encoding/base64" + "encoding/json" "errors" "fmt" + "io" + "io/ioutil" "log" + "regexp" "strings" "time" - "io" - "crypto" "crypto/aes" "crypto/cipher" @@ -16,55 +20,40 @@ import ( "crypto/rand" "crypto/rsa" "crypto/sha512" + "crypto/x509" "github.com/google/uuid" "golang.org/x/crypto/argon2" ) +// More info about the UUID type: +// github.com/google/uuid type UUID = uuid.UUID -// RSA key size (in bits) -const rsaKeySizeBits = 2048 - // AES block size (in bytes) +// https://pkg.go.dev/crypto/aes const AESBlockSizeBytes = aes.BlockSize // AES key size (in bytes) const AESKeySizeBytes = 16 -// Output size (in bytes) of Hash and MAC +// Output size (in bytes) of Hash, HMAC, and HashKDF const HashSizeBytes = sha512.Size -// Debug print true/false -var DebugPrint = false - -// DebugMsg. Helper function: Does formatted printing to stderr if -// the DebugPrint global is set. All our testing ignores stderr, -// so feel free to use this for any sort of testing you want. +const rsaKeySizeBits = 2048 -func SetDebugStatus(status bool) { - DebugPrint = status -} +// UUID size (in bytes) +const UUIDSizeBytes = 16 -func DebugMsg(format string, args ...interface{}) { - if DebugPrint { - msg := fmt.Sprintf("%v ", time.Now().Format("15:04:05.00000")) - log.Printf(msg+strings.Trim(format, "\r\n ")+"\n", args...) - } -} - -// RandomBytes. Helper function: Returns a byte slice of the specified -// size filled with random data -func randomBytes(size int) (data []byte) { - data = make([]byte, size) - if _, err := io.ReadFull(rand.Reader, data); err != nil { - panic(err) - } - return -} +/* +******************************************** +** Global Definitions *** +******************************************** -// Can replace this function for development/testing -var RandomBytes = randomBytes +Here, we declare a number of global data +structures and types: Keystore/Datastore, +Public/Private Key structures, etc. +*/ type PublicKeyType struct { KeyType string @@ -83,6 +72,11 @@ var datastoreBandwidth = 0 var datastore map[UUID][]byte = make(map[UUID][]byte) var keystore map[string]PublicKeyType = make(map[string]PublicKeyType) +type DatastoreEntry struct { + UUID string + Value string +} + /* ******************************************** ** Datastore Functions ** @@ -130,6 +124,7 @@ var DatastoreDelete = datastoreDelete // Use this in testing to reset the datastore to empty func datastoreClear() { datastore = make(map[UUID][]byte) + symbols = make(map[string]string) } var DatastoreClear = datastoreClear @@ -153,8 +148,8 @@ var KeystoreClear = keystoreClear // Sets the value in the keystore func keystoreSet(key string, value PublicKeyType) error { _, present := keystore[key] - if present != false { - return errors.New("That entry in the Keystore has been taken.") + if present { + return errors.New("entry in keystore has been taken") } keystore[key] = value @@ -183,6 +178,72 @@ func KeystoreGetMap() map[string]PublicKeyType { return keystore } +/* +******************************************** +** JSON Marshal/Unmarshal *** +******************************************** + +userlib.Marshal and userlib.Unmarshal are two +wrapper functions around json.Marshal and +json.Unmarshal. We wrap around these methods +to provide symbolic debugger support. + +Reference: +https://pkg.go.dev/encoding/json +*/ + +func marshal(v interface{}) ([]byte, error) { + data, err := json.Marshal(v) + if err != nil { + return nil, err + } + if SymbolicDebug { + m1 := regexp.MustCompile(`{"KeyType":"PKE","PrivKey":{.*?}}}`) + replaced := m1.ReplaceAllStringFunc(string(data), resolveString) + + m2 := regexp.MustCompile(`{"KeyType":"DS","PrivKey":{.*?}}}`) + replaced = m2.ReplaceAllStringFunc(replaced, resolveString) + + m3 := regexp.MustCompile(`".*?"`) + replaced = m3.ReplaceAllStringFunc(replaced, resolveString) + + record(data, `%s`, replaced) + } + return data, nil +} + +func unmarshal(data []byte, v interface{}) error { + return json.Unmarshal(data, v) +} + +var Unmarshal = unmarshal +var Marshal = marshal + +/* +******************************************** +** Random Byte Generator *** +******************************************** + +This method may help with random byte generation. +*/ + +// RandomBytes. Helper function: Returns a byte slice of the specified +// size filled with random data +func randomBytes(size int) (data []byte) { + data = make([]byte, size) + _, err := rand.Read(data) + if err != nil { + panic(err) + } + + if SymbolicDebug { + record(data, `{"userlib.RandomBytes": %s}`, truncateBytes(data)) + } + return +} + +var RandomBytes = randomBytes + /* ******************************************** ** KDF ** @@ -193,7 +254,11 @@ func KeystoreGetMap() map[string]PublicKeyType { // Argon2: Automatically chooses a decent combination of iterations and memory // Use this to generate a key from a password func argon2Key(password []byte, salt []byte, keyLen uint32) []byte { - return argon2.IDKey(password, salt, 1, 64*1024, 4, keyLen) + result := argon2.IDKey(password, salt, 1, 64*1024, 4, keyLen) + if SymbolicVerbose { + record(result, `{"userlib.Argon2Key": {"password": %s, "salt": %s, "keyLen": %d}}`, resolve(password), resolve(salt), keyLen) + } + return result } var Argon2Key = argon2Key @@ -208,12 +273,62 @@ var Argon2Key = argon2Key // SHA512: Returns the checksum of data. func hash(data []byte) []byte { hashVal := sha512.Sum512(data) - return hashVal[:] // Converting from [64]byte array to []byte slice + // Converting from [64]byte array to []byte slice + result := hashVal[:] + if SymbolicVerbose { + record(result, `{"userlib.Hash": {"data": %s}}`, resolve(data)) + record(result[:16], `{"userlib.Hash[:16]": {"data": %s}}`, resolve(data)) + } + return result } // Hash returns a byte slice containing the SHA512 hash of the given byte slice. var Hash = hash +/* +******************************************** +** UUID ** +** UUIDNew(), UUIDFromBytes(...) ** +******************************************** + +These functions are wrappers around: +https://pkg.go.dev/github.com/google/uuid +*/ + +// UUIDNew creates a new random UUID. +func uuidNew() UUID { + bytes := make([]byte, UUIDSizeBytes) + if _, err := io.ReadFull(rand.Reader, bytes); err != nil { + panic(err) + } + result, _ := uuid.FromBytes(bytes) + if SymbolicVerbose { + record(bytes, `{"userlib.UUIDNew": %s}`, truncateBytes(bytes)) + } + return result +} + +var UUIDNew = uuidNew + +// UUIDFromBytes creates a new UUID from a byte slice. +// Returns an error if the slice has a length less than 16. +// The bytes are copied from the slice. +func uuidFromBytes(b []byte) (result UUID, err error) { + if len(b) < 16 { + return uuid.New(), errors.New("UUIDFromBytes expects an input greater than or equal to 16 characters") + } + result, err = uuid.FromBytes(b[:16]) + if err != nil { + return uuid.New(), err + } + if SymbolicDebug { + record(result[:], `{"userlib.UUIDFromBytes": %s}`, resolve(b)) + } + return result, err +} + +var UUIDFromBytes = uuidFromBytes + /* ******************************************** ** Public Key Encryption ** @@ -233,6 +348,27 @@ type PKEDecKey = PrivateKeyType type DSSignKey = PrivateKeyType type DSVerifyKey = PublicKeyType +func recordKeys(publicKey rsa.PublicKey, privateKey rsa.PrivateKey, + publicKeyStruct interface{}, privateKeyStruct interface{}, + publicKeyFormat string, privateKeyFormat string) { + + publicKeyBytes := x509.MarshalPKCS1PublicKey(&publicKey) + publicKeyId := truncateBytes(publicKeyBytes) + record(publicKeyBytes, publicKeyFormat, publicKeyId) + + privateKeyBytes := x509.MarshalPKCS1PrivateKey(&privateKey) + privateKeyId := truncateBytes(privateKeyBytes) + record(privateKeyBytes, privateKeyFormat, privateKeyId) + + // This is for the case where the key is used inside of a struct and we have + // to regex on the struct's marshalled type + pubKeyJSON, _ := json.Marshal(publicKeyStruct) + record(pubKeyJSON, publicKeyFormat, publicKeyId) + + privKeyJSON, _ := json.Marshal(privateKeyStruct) + record(privKeyJSON, privateKeyFormat, privateKeyId) +} + // Generates a key pair for public-key encryption via RSA func pkeKeyGen() (PKEEncKey, PKEDecKey, error) { RSAPrivKey, err := rsa.GenerateKey(rand.Reader, rsaKeySizeBits) @@ -246,6 +382,9 @@ func pkeKeyGen() (PKEEncKey, PKEDecKey, error) { PKEDecKeyRes.KeyType = "PKE" PKEDecKeyRes.PrivKey = *RSAPrivKey + if SymbolicDebug { + recordKeys(RSAPubKey, *RSAPrivKey, PKEEncKeyRes, PKEDecKeyRes, `{"PKEEncKey": %s}`, `{"PKEDecKey": %s}`) + } return PKEEncKeyRes, PKEDecKeyRes, err } @@ -256,12 +395,20 @@ func pkeEnc(ek PKEEncKey, plaintext []byte) ([]byte, error) { RSAPubKey := &ek.PubKey if ek.KeyType != "PKE" { - return nil, errors.New("Using a non-PKE key for PKE.") + return nil, errors.New("using a non-pke key for pke") } ciphertext, err := rsa.EncryptOAEP(sha512.New(), rand.Reader, RSAPubKey, plaintext, nil) - return ciphertext, err + if err != nil { + return nil, err + } + + if SymbolicDebug { + record(ciphertext, `{"userlib.PKEEnc": {"ek": %s, "plaintext": %s}}`, resolve(x509.MarshalPKCS1PublicKey(&ek.PubKey)), resolve(plaintext)) + } + + return ciphertext, nil } var PKEEnc = pkeEnc @@ -271,12 +418,19 @@ func pkeDec(dk PKEDecKey, ciphertext []byte) ([]byte, error) { RSAPrivKey := &dk.PrivKey if dk.KeyType != "PKE" { - return nil, errors.New("Using a non-PKE key for PKE.") + return nil, errors.New("using a non-pke for pke") } decryption, err := rsa.DecryptOAEP(sha512.New(), rand.Reader, RSAPrivKey, ciphertext, nil) + if err != nil { + return nil, err + } + + if SymbolicDebug { + record(decryption, `{"userlib.PKEDec": {"dk": %s, "ciphertext": %s}}`, resolve(x509.MarshalPKCS1PrivateKey(&dk.PrivKey)), resolve(ciphertext)) + } - return decryption, err + return decryption, nil } var PKEDec = pkeDec @@ -301,6 +455,10 @@ func dsKeyGen() (DSSignKey, DSVerifyKey, error) { DSVerifyKeyRes.KeyType = "DS" DSVerifyKeyRes.PubKey = RSAPubKey + if SymbolicDebug { + recordKeys(RSAPubKey, *RSAPrivKey, DSSignKeyRes, DSVerifyKeyRes, `{"DSVerifyKey": %s}`, `{"DSSignKey": %s}`) + } + return DSSignKeyRes, DSVerifyKeyRes, err } @@ -311,14 +469,21 @@ func dsSign(sk DSSignKey, msg []byte) ([]byte, error) { RSAPrivKey := &sk.PrivKey if sk.KeyType != "DS" { - return nil, errors.New("Using a non-DS key for DS.") + return nil, errors.New("using a non-ds key for ds") } hashed := sha512.Sum512(msg) sig, err := rsa.SignPKCS1v15(rand.Reader, RSAPrivKey, crypto.SHA512, hashed[:]) + if err != nil { + return nil, err + } - return sig, err + if SymbolicDebug { + record(sig, `{"userlib.DSSign": {"sk": %s, "msg": %s}}`, resolve(x509.MarshalPKCS1PrivateKey(&sk.PrivKey)), resolve(msg)) + } + + return sig, nil } var DSSign = dsSign @@ -328,14 +493,18 @@ func dsVerify(vk DSVerifyKey, msg []byte, sig []byte) error { RSAPubKey := &vk.PubKey if vk.KeyType != "DS" { - return errors.New("Using a non-DS key for DS.") + return errors.New("using a non-ds key for ds") } hashed := sha512.Sum512(msg) err := rsa.VerifyPKCS1v15(RSAPubKey, crypto.SHA512, hashed[:], sig) - return err + if err != nil { + return err + } else { + return nil + } } var DSVerify = dsVerify @@ -349,13 +518,19 @@ var DSVerify = dsVerify // Evaluate the HMAC using sha512 func hmacEval(key []byte, msg []byte) ([]byte, error) { - if len(key) != 16 && len(key) != 24 && len(key) != 32 { - panic(errors.New("The input as key for HMAC should be a 16-byte key.")) + if len(key) != 16 { // && len(key) != 24 && len(key) != 32 { + panic("input as key for hmac should be a 16-byte key") } mac := hmac.New(sha512.New, key) mac.Write(msg) res := mac.Sum(nil) + + if SymbolicDebug { + record(res, `{"userlib.HMAC": {"key": %s, "msg": %s}}`, resolve(key), resolve(msg)) + record(res[:16], `{"userlib.HMAC[:16]": {"key": %s, "msg": %s}}`, resolve(key), resolve(msg)) + } + return res, nil } @@ -379,13 +554,19 @@ var HMACEqual = hmacEqual // HashKDF (uses the same algorithm as hmacEval, wrapped to provide a useful // error) func hashKDF(key []byte, msg []byte) ([]byte, error) { - if len(key) != 16 && len(key) != 24 && len(key) != 32 { - panic(errors.New("The input as key for HashKDF should be a 16-byte key.")) + if len(key) != 16 { + return nil, errors.New("input as key for HashKDF should be a 16-byte key") } mac := hmac.New(sha512.New, key) mac.Write(msg) res := mac.Sum(nil) + + if SymbolicDebug { + record(res, `{"userlib.HashKDF": {"key": %s, "msg": %s}}`, resolve(key), resolve(msg)) + record(res[:16], `{"userlib.HashKDF[:16]": {"key": %s, "msg": %s}}`, resolve(key), resolve(msg)) + } + return res, nil } @@ -398,9 +579,8 @@ var HashKDF = hashKDF ******************************************** */ -// Encrypts a byte slice with AES-CBC +// Encrypts a byte slice with AES-CFB // Length of iv should be == AESBlockSizeBytes -// Length of plaintext should be divisible by AESBlockSize func symEnc(key []byte, iv []byte, plaintext []byte) []byte { if len(iv) != AESBlockSizeBytes { panic("IV length not equal to AESBlockSizeBytes") @@ -411,42 +591,180 @@ func symEnc(key []byte, iv []byte, plaintext []byte) []byte { panic(err) } - if len(plaintext)%AESBlockSizeBytes != 0 { - panic("plaintext is not a multiple of the block size") - } - + // The IV needs to be unique, but not secret. Therefore it's common to + // include it at the beginning of the ciphertext. ciphertext := make([]byte, AESBlockSizeBytes+len(plaintext)) - mode := cipher.NewCBCEncrypter(block, iv) - mode.CryptBlocks(ciphertext[AESBlockSizeBytes:], plaintext) + mode := cipher.NewCFBEncrypter(block, iv) + mode.XORKeyStream(ciphertext[AESBlockSizeBytes:], plaintext) copy(ciphertext[:AESBlockSizeBytes], iv) - // example taken here https://golang.org/pkg/crypto/cipher/#NewCBCEncrypter + + if SymbolicDebug { + record(ciphertext, `{"userlib.SymEnc": {"key": %s, "iv": %s, "plaintext": %s}}`, resolve(key), resolve(iv), resolve(plaintext)) + } return ciphertext } var SymEnc = symEnc -// Decrypts a ciphertext encrypted with AES-CTR +// Decrypts a ciphertext encrypted with AES-CFB func symDec(key []byte, ciphertext []byte) []byte { block, err := aes.NewCipher(key) if err != nil { panic(err) } + if len(ciphertext) < aes.BlockSize { + panic("ciphertext too short") + } + iv := ciphertext[:AESBlockSizeBytes] - plaintext := make([]byte, len(ciphertext)-AESBlockSizeBytes) + ciphertext = ciphertext[AESBlockSizeBytes:] - if len(plaintext)%AESBlockSizeBytes != 0 { - panic("ciphertext is not a multiple of the block size") - } + plaintext := make([]byte, len(ciphertext)) - mode := cipher.NewCBCDecrypter(block, iv) + mode := cipher.NewCFBDecrypter(block, iv) + mode.XORKeyStream(plaintext, ciphertext) - // usage adapted from this page https://golang.org/pkg/crypto/cipher/#NewCBCEncrypter - mode.CryptBlocks(plaintext, ciphertext[AESBlockSizeBytes:]) + if SymbolicDebug { + record(plaintext, `{"userlib.SymDec": {"key": %s, "ciphertext": %s}}`, resolve(key), resolve(ciphertext)) + } return plaintext } var SymDec = symDec + +/* +******************************************** +** BETA: Symbolic Debugger ** +******************************************** + +The Symbolic Debugger is a debugging tool that +we created to assist with debugging Datastore +entries. Read the comments below to see what you +can do with this. +*/ + +// This flag enables the Symbolic Debugger +// If the Symbolic Debugger is causing excessive slowdown or memory usage, +// you may want to set this to false. +var SymbolicDebug = true + +// This flag enables verbose logging through the Symbolic Debugger +// If you set this to true, you may want to pipe your output to a file +// e.g. go test -v > debug.txt +// to see the full, untrunctated debug logging +var SymbolicVerbose = true + +// This flag sets the maximum key length in the debugger +// You may want to increase this to a larger number (e.g. 100) if you're using +// identifiers that have meaning, e.g. "username-{}-filename-{}", and want to see +// the entire identifier in the symbolic representation. +var SymbolicMaxLength = 5 + +// Feel free to use userlib.DebugMsg(...) to print strings to the console. +func DebugMsg(format string, args ...interface{}) { + msg := fmt.Sprintf("%v ", time.Now().Format("15:04:05.00000")) + log.Printf(msg+strings.Trim(format, "\r\n ")+"\n", args...) +} + +// The Symbolic Debugger lets you export a snapshot of the Datastore in a symbolic representation. +// To do this, call DebugExportDatastore("datastore.json"), or any file name of your +// choice. +func DebugExportDatastore(outputFile string) { + entries := make([]string, 0) + for key, element := range datastore { + entries = append(entries, fmt.Sprintf(`{"Key": %s, "Value": %s}`, resolve(key[:]), resolve(element))) + } + output := fmt.Sprintf(`[%s]`, strings.Join(entries, ",")) + var prettyOutput bytes.Buffer + json.Indent(&prettyOutput, []byte(output), "", " ") + ioutil.WriteFile(outputFile, prettyOutput.Bytes(), 0644) +} + +// ----------- SYMBOLIC DEBUGGER: PRIVATE METHODS [BEGIN] --------------- + +// Takes in byte slice data (with no text interpretation) and cuts it short +func truncateBytes(data []byte) (truncated string) { + return truncateStr(fmt.Sprintf("%x", data)) +} + +// Takes in string data (with text interpretation) and cuts it short +func truncateStr(data string) (truncated string) { + if len(data) < SymbolicMaxLength+3 { + truncated = data + } else { + truncated = data[:SymbolicMaxLength] + "..." + } + data_bytes, err := json.Marshal(truncated) + if err != nil { + panic("symbolic debugger failed in truncateStr with error: " + err.Error()) + } + return fmt.Sprintf("%s", data_bytes) +} + +// Deterministically converts a byte slice to a string of length 128 that is +// suitable to use as the storage key in a map and marshal/unmarshal to/from +// JSON. +func MapKeyFromBytes(data []byte) (truncated string) { + return fmt.Sprintf("%x", sha512.Sum512(data)) +} + +// The Symbolic Debugger uses a symbols table to map data generated by userlib +// (e.g. hashes, encrypted strings, signatures, etc.) to their string representation. +var symbols map[string]string = make(map[string]string) + +// The `resolve` method attempts to look up a symbol corresponing to some particular data +// If it's not found, we treat the data as a byte array, and just slice it appropriatly +func resolve(data []byte) string { + symbolKey := MapKeyFromBytes(data) + if result, found := symbols[symbolKey]; found { + return result + } + // For some WACK reason, this is different than + // truncateBytes(data). So don't do that. + return truncateStr(string(data)) +} + +// A special resolution method to help with regex-based string resolution. +// When using regexp's ReplaceAllStringFunc method, we need to pass in a +// function that acts on a found match. That function's signature must be +// string => string, so we define a special resolveString method to allow +// for that. The handling of strings is also slightly different than normal +// byte slices, so we have some special decoding logic as well. +func resolveString(data string) string { + + doubleQuote := "\"" + + if strings.HasPrefix(data, doubleQuote) { + trimmed := strings.TrimPrefix(data, doubleQuote) + trimmed = strings.TrimSuffix(trimmed, doubleQuote) + + base64Decoded, err := base64.StdEncoding.DecodeString(trimmed) + symbolKey := MapKeyFromBytes([]byte(base64Decoded)) + symbol, found := symbols[symbolKey] + if found && err == nil { + return symbol + } + + symbolKey = MapKeyFromBytes([]byte(trimmed)) + symbol, found = symbols[symbolKey] + if found { + return symbol + } + } + + return data +} + +func record(key []byte, template string, values ...interface{}) { + s := fmt.Sprintf(template, values...) + symbols[MapKeyFromBytes(key)] = s + if SymbolicVerbose { + DebugMsg("%s => %s", truncateBytes(key), s) + } +} + +// ----------- SYMBOLIC DEBUGGER: PRIVATE METHODS [END] --------------- diff --git a/userlib_test.go b/userlib_test.go index 9c919f9..7668772 100644 --- a/userlib_test.go +++ b/userlib_test.go @@ -1,297 +1,314 @@ package userlib import ( - "bytes" "encoding/hex" + "reflect" "strings" "testing" - "github.com/google/uuid" + // A "dot" import is used here so that the functions in the ginko and gomega + // modules can be used without an identifier. For example, Describe() and + // Expect() instead of ginko.Describe() and gomega.Expect(). You can read more + // about dot imports here: + // https://stackoverflow.com/questions/6478962/what-does-the-dot-or-period-in-a-go-import-statement-do + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" ) -// Golang has a very powerful routine for building tests. - -// Run with "go test" to run the tests - -// And "go test -v" to run verbosely so you see all the logging and -// what tests pass/fail individually. - -// And "go test -cover" to check your code coverage in your tests - -// Default test strings -var key1 []byte = []byte("cs161teststring1") -var key2 []byte = []byte("cs161teststring2") -var key3 []byte = []byte("cs161teststring3") -var key4 []byte = []byte("cs161teststring4") -var key5 []byte = []byte("cs161teststring5") - -// Creates a UUID from the supplied bytes -// Use for testing only! -func UUIDFromBytes(t *testing.T, b []byte) (u UUID) { - u, err := uuid.FromBytes(b) - if err != nil { - t.Error("Got FromBytes error:", err) - } - - return -} - -func TestUUIDFromBytesDeterministic(t *testing.T) { - UUID1 := UUIDFromBytes(t, key1) - t.Log(UUID1) - - UUID2 := UUIDFromBytes(t, key1) - t.Log(UUID2) - - if UUID1 != UUID2 { - t.Error("UUID1 != UUID2") - t.Log("UUID1:", UUID1) - t.Log("UUID2:", UUID2) - } -} - -func TestDatastore(t *testing.T) { - UUID1 := UUIDFromBytes(t, key1) - UUID2 := UUIDFromBytes(t, key2) - UUID3 := UUIDFromBytes(t, key3) - - DatastoreSet(UUID1, []byte("foo")) - - _, valid := DatastoreGet(UUID3) - if valid { - t.Error("Datastore fetched UUID3 when it wasn't supposed to") - } - - data, valid := DatastoreGet(UUID1) - if !valid || string(data) != "foo" { - t.Error("Error with fetching 'foo' from UUID1") - } - - _, valid = DatastoreGet(UUID3) - if valid { - t.Error("Returned when nothing, oops") - } - - DatastoreSet(UUID2, []byte("bar")) - - data, valid = DatastoreGet(UUID1) - if !valid || string(data) != "foo" { - t.Error("Error with fetching 'foo' from UUID1") - } - - DatastoreDelete(UUID1) - - _, valid = DatastoreGet(UUID1) - if valid { - t.Error("DatastoreGet succeeded even after deleting UUID1") - } - - data, valid = DatastoreGet(UUID2) - if !valid || string(data) != "bar" { - t.Error("Error with fetching 'bar' from UUID2") - } - - DatastoreClear() - - _, valid = DatastoreGet(UUID2) - if valid { - t.Error("DatastoreGet succeeded even after DatastoreClear") - } - - t.Log("Datastore fetch", data) - t.Log("Datastore map", DatastoreGetMap()) - DatastoreClear() - t.Log("Datastore map", DatastoreGetMap()) - - // Test bandwidth tracker - DatastoreResetBandwidth() - fiveBytes := "ABCDE" - DatastoreSet(UUID1, []byte(fiveBytes)) - bandwidthUsed := DatastoreGetBandwidth() - if bandwidthUsed != 5 { - t.Error("Incorrect bandwidth calculation after storing 5 bytes.") - } - DatastoreGet(UUID1) - bandwidthUsed = DatastoreGetBandwidth() - if bandwidthUsed != 10 { - t.Error("Incorrect bandwidth calculation after storing and loading 5 bytes") - } -} - -func TestKeystore(t *testing.T) { - RSAPubKey, _, err1 := PKEKeyGen() - _, DSVerifyKey, err2 := DSKeyGen() - - if err1 != nil || err2 != nil { - t.Error("PKEKeyGen() failed") - } - - KeystoreSet("user1", RSAPubKey) - KeystoreSet("user2", DSVerifyKey) - - _, valid := KeystoreGet("user3") - if valid { - t.Error("Keystore fetched UUID3 when it wasn't supposed to") - } - - data, valid := KeystoreGet("user1") - if !valid { - t.Error("Key stored at UUID1 doesn't match") - } - - data, valid = KeystoreGet("user2") - if !valid { - t.Error("Key stored at UUID2 doesn't match") - } - - KeystoreClear() - - _, valid = KeystoreGet("user1") - if valid { - t.Error("KeystoreGet succeeded even after KeystoreClear") - } - - t.Log("Keystore fetch", data) - t.Log("Keystore map", KeystoreGetMap()) - KeystoreClear() - t.Log("Keystore map", KeystoreGetMap()) -} - -func TestRSA(t *testing.T) { - - // Test RSA Encrypt and Decrypt - RSAPubKey, RSAPrivKey, err := PKEKeyGen() - if err != nil { - t.Error("PKEKeyGen() failed", err) - } - - t.Log(RSAPubKey) - ciphertext, err := PKEEnc(RSAPubKey, []byte("Squeamish Ossifrage")) - if err != nil { - t.Error("PKEEnc() error", err) - } - - decryption, err := PKEDec(RSAPrivKey, ciphertext) - if err != nil || (string(decryption) != "Squeamish Ossifrage") { - t.Error("Decryption failed", err) - } - - // Test RSA Sign and Verify - DSSignKey, DSVerifyKey, err := DSKeyGen() - if err != nil { - t.Error("DSKeyGen() failed", err) - } - - sign, err := DSSign(DSSignKey, []byte("Squeamish Ossifrage")) - if err != nil { - t.Error("RSA sign failure") - } - - err = DSVerify(DSVerifyKey, []byte("Squeamish Ossifrage"), sign) - if err != nil { - t.Error("RSA verification failure") - } - - err = DSVerify(DSVerifyKey, []byte("foo"), sign) - if err == nil { - t.Error("RSA verification worked when it shouldn't") - } - - t.Log("Error return", err) -} - -func TestHMAC(t *testing.T) { - msga := []byte("foo") - msgb := []byte("bar") - - hmac1a, _ := HMACEval(key1, msga) - hmac1b, _ := HMACEval(key1, msgb) - if HMACEqual(hmac1a, hmac1b) { - t.Error("HMACs are equal for different data") - } - - hmac2a, _ := HMACEval(key2, msga) - if HMACEqual(hmac1a, hmac2a) { - t.Error("HMACs are equal for different key") - } - - hmac1a2, _ := HMACEval(key1, msga) - if !HMACEqual(hmac1a, hmac1a2) { - t.Error("HMACs are not equal when they should be") - } -} - -func TestArgon2(t *testing.T) { - val1 := Argon2Key([]byte("Password"), []byte("nosalt"), 32) - val2 := Argon2Key([]byte("Password"), []byte("nosalt"), 64) - val3 := Argon2Key([]byte("password"), []byte("nosalt"), 32) - - equal := bytes.Equal - - if equal(val1, val2) || equal(val1, val3) || equal(val2, val3) { - t.Error("Argon2 problem") - } - t.Log(hex.EncodeToString(val1)) - t.Log(hex.EncodeToString(val2)) - t.Log(hex.EncodeToString(val3)) -} - -func TestStreamCipher(t *testing.T) { - someBlockSizeMsg := strings.Repeat("A", AESBlockSizeBytes) - iv := RandomBytes(16) - t.Log("Random IV:", iv) - - t.Log("Also testing replacing SymDec with wrapper") - wrapped := false - - // Save the OLD version of SymDec - decryptInternal := SymDec - - SymDec = func(key []byte, ciphertext []byte) []byte { - wrapped = true - t.Log("Wrapped decryption called") - return decryptInternal(key, ciphertext) - } - - t.Log("Before SymEnc()") - ciphertext := SymEnc(key1, iv, []byte(someBlockSizeMsg)) - - t.Log("Before SymDec()") - decryption := SymDec(key1, ciphertext) - - t.Log("Decrypted message:", string(decryption)) - if string(decryption) != someBlockSizeMsg { - t.Error("Symmetric decryption failure") - } - if !wrapped { - t.Error("Failed to properly wrap decryption") - } -} - -func TestHash(t *testing.T) { - t.Log("Hashing test strings") - hash1 := Hash(key1) - hash2 := Hash(key2) - hash3 := Hash(key3) - hash4 := Hash(key4) - hash5 := Hash(key5) - - expected1, err1 := hex.DecodeString("4a56fb0ee081513a4ea0d22a33a6fba8edb95e1cb59dc1b773e77154c239c62e6377fcf26d80e3d7cf5357ebc1a4d0005fc54eb7b7110e1d5b82abc90ee4967b") - expected2, err2 := hex.DecodeString("2fa5d1e9acaedc743dc7e3a767e58dd8dfba9f61514c474f77f03b4a565808484bf9bdf5a26d40cdef47a93bc18ee88b192359173d8408b3f3c609e60308e998") - expected3, err3 := hex.DecodeString("0283967f6235d50886fb85d9892fa28642533cdf5aaf58ccfce5dadb2b0c0a6ca76f6ff5e0392d796925a34b57becb81904319e921b97718fb9faab597eea37b") - expected4, err4 := hex.DecodeString("8a168d2b7b4e28e68d4ed9c8828b60dcc3ad57431837b5d310fa767cb3e0c50a9a237e1ed38150c49e2f04f8a74263a1b830337c5aa93d8c7edda07761a4f851") - expected5, err5 := hex.DecodeString("03133ff43f9713e65c4a8906fbabd1d6331e47811e4d870c1d515e3471266e0383444aeb5187de640b7fe3e7505107abe91ec63c7df572f6279cb87d41c4ee61") - if err1 != nil || err2 != nil || err3 != nil || err4 != nil || err5 != nil { - t.Error("DecodeString failed") - } - t.Log("Checking against expected hash from https://sha512.online/") - if !bytes.Equal(hash1[:], expected1) || !bytes.Equal(hash2[:], expected2) || !bytes.Equal(hash3[:], expected3) || !bytes.Equal(hash4[:], expected4) || !bytes.Equal(hash5[:], expected5) { - t.Error("Hash does not match up") - } +func TestSetupAndExecution(t *testing.T) { + // We are using 2 libraries to help us write readable and maintainable tests: + // + // (1) Ginkgo, a Behavior Driven Development (BDD) testing framework that + // makes it easy to write expressive specs that describe the + // behavior of your code in an organized manner; and + // + // (2) Gomega, an assertion/matcher library that allows us to write individual + // assertion statements in tests that read more like natural + // language. For example "Expect(ACTUAL).To(Equal(EXPECTED))". + // + // In the Ginko framework, a test case signals failure by calling Ginkgo’s + // Fail(description string) function. However, we are using the Gomega library + // to execute our assertion statements. When a Gomega assertion fails, Gomega + // calls a GomegaFailHandler, which is a function that must be provided using + // gomega.RegisterFailHandler(). Here, we pass Ginko's Fail() function to + // Gomega so that Gomega can report failed assertions to the Ginko test + // framework, which can take the appropriate action when a test fails. + // + // This is the sole connection point between Ginkgo and Gomega. + RegisterFailHandler(Fail) + + RunSpecs(t, "Userlib Tests") } -// Deliberate fail example -// func TestFailure(t *testing.T){ -// t.Log("This test will fail") -// t.Error("Test of failure") -//} +// ================================================ +// Global variables +// ================================================ + +var key1 []byte +var key2 []byte +var key3 []byte +var key4 []byte +var key5 []byte + +// ================================================ +// The top level Describe() contains all tests in +// this test suite in nested Describe() blocks. +// ================================================ + +var _ = Describe("Client Tests", func() { + BeforeEach(func() { + key1 = []byte("cs161teststring1") + key2 = []byte("cs161teststring2") + key3 = []byte("cs161teststring3") + key4 = []byte("cs161teststring4") + key5 = []byte("cs161teststring5") + }) + + BeforeEach(func() { + SymbolicVerbose = false + }) + + Describe("UUIDFromBytes()", func() { + It("should be deterministic", func() { + one, _ := UUIDFromBytes(key1) + two, _ := UUIDFromBytes(key1) + Expect(one).To(Equal(two), + "UUIDs created from the same bytes were different") + }) + }) + + Describe("Datastore", func() { + BeforeEach(func() { + DatastoreClear() + DatastoreResetBandwidth() + }) + + It("should not return a value for a key that is not set", func() { + UUID1, _ := UUIDFromBytes(key1) + _, found := DatastoreGet(UUID1) + Expect(found).To(BeFalse(), + "Datastore returned a value for a key that was not set.") + }) + + It("should return the expected value for a key that is set", func() { + UUID1, _ := UUIDFromBytes(key1) + UUID2, _ := UUIDFromBytes(key2) + + foo := []byte("foo") + DatastoreSet(UUID1, foo) + + data, found := DatastoreGet(UUID1) + Expect(found).To(BeTrue(), "Could not find a value that was set.") + Expect(data).To(Equal(foo), "Did not retrieve the correct value for UUIID1.") + + bar := []byte("bar") + DatastoreSet(UUID2, bar) + + data, found = DatastoreGet(UUID2) + Expect(found).To(BeTrue(), "Could not find a value that was set.") + Expect(data).To(Equal(bar), "Did not retrieve the correct value for UUIID2.") + }) + + It("should correctly delete values", func() { + UUID1, _ := UUIDFromBytes(key1) + DatastoreSet(UUID1, []byte("foo")) + DatastoreDelete(UUID1) + _, found := DatastoreGet(UUID1) + Expect(found).To(BeFalse(), "Was able to load value from a delete key.") + }) + + It("should correctly track the bandwidth usage", func() { + DatastoreResetBandwidth() + fiveBytes := "ABCDE" + UUID1, _ := UUIDFromBytes(key1) + DatastoreSet(UUID1, []byte(fiveBytes)) + bandwidthUsed := DatastoreGetBandwidth() + Expect(bandwidthUsed).To(Equal(5), + "Incorrect bandwidth calculation after storing 5 bytes.") + + DatastoreGet(UUID1) + bandwidthUsed = DatastoreGetBandwidth() + Expect(bandwidthUsed).To(Equal(10), + "Incorrect bandwidth calculation after storing and loading 5 bytes") + }) + }) + + Describe("Keystore", func() { + const storageKeyA = "A" + const storageKeyB = "B" + var encPubKey PublicKeyType + var sigVerifyKey PublicKeyType + + BeforeEach(func() { + KeystoreClear() + encPubKey, _, _ = PKEKeyGen() + _, sigVerifyKey, _ = DSKeyGen() + }) + + It("should not return a value for a key that is not set", func() { + _, found := KeystoreGet("something") + Expect(found).To(BeFalse(), + "Keystore returned a value for a key that was not set.") + }) + + It("should return the expected value for a key that is set", func() { + KeystoreSet(storageKeyA, encPubKey) + KeystoreSet(storageKeyB, sigVerifyKey) + + key, found := KeystoreGet(storageKeyA) + Expect(found).To(BeTrue(), "Could not find a value that was set.") + Expect(key).To(Equal(encPubKey), + "Did not retrieve the correct value for storageKeyA.") + + key, found = KeystoreGet(storageKeyB) + Expect(found).To(BeTrue(), "Could not find a value that was set.") + Expect(key).To(Equal(sigVerifyKey), + "Did not retrieve the correct value for storageKeyB.") + }) + + It("should not allow overwriting keys since the Keystore is immutable", func() { + keystoreSet(storageKeyA, encPubKey) + err := keystoreSet(storageKeyA, sigVerifyKey) + Expect(err).ToNot(BeNil(), "Allowed overwriting an existing key.") + }) + + It("should reset state after calling clear", func() { + keystoreSet(storageKeyA, encPubKey) + key, found := KeystoreGet(storageKeyA) + Expect(found).To(BeTrue(), "Could not find a value that was set.") + Expect(key).To(Equal(encPubKey), + "Did not retrieve the correct value for storageKeyA.") + KeystoreClear() + _, found = KeystoreGet(storageKeyA) + Expect(found).To(BeFalse(), "Found the value even after clearing.") + }) + + Describe("KeystoreGetMap()", func() { + It("should return the underlying map", func() { + actualPtr := reflect.ValueOf(KeystoreGetMap()).Pointer() + expectedPtr := reflect.ValueOf(keystore).Pointer() + Expect(actualPtr).To(Equal(expectedPtr), + "The map returned was not the underlying keystore map.") + }) + }) + }) + + Describe("RSA encrypt and decrypt", func() { + // TODO: break this mega test up into discrete test cases + It("should work as expected", func() { + const someString = "Squeamish Ossifrage" + + RSAPubKey, RSAPrivKey, err := PKEKeyGen() + Expect(err).To(BeNil(), "PKEKeyGen() failed") + + ciphertext, err := PKEEnc(RSAPubKey, []byte(someString)) + Expect(err).To(BeNil(), "PKEEnc() failed") + + decryption, err := PKEDec(RSAPrivKey, ciphertext) + Expect(err).To(BeNil(), "PKEDec() failed") + Expect(string(decryption)).To(Equal(someString), + "PKEDec() failed") + + // Test RSA Sign and Verify + DSSignKey, DSVerifyKey, err := DSKeyGen() + Expect(err).To(BeNil(), "DSKeyGen() failed") + + sig, err := DSSign(DSSignKey, []byte(someString)) + Expect(err).To(BeNil(), "DSSignKey() failed") + + err = DSVerify(DSVerifyKey, []byte(someString), sig) + Expect(err).To(BeNil(), "DSVerifyKey() failed") + + err = DSVerify(DSVerifyKey, []byte("foo"), sig) + Expect(err).ToNot(BeNil(), + "DSVerifyKey() succeeded when it should have failed") + }) + }) + + Describe("HMAC", func() { + // TODO: break this mega test up into discrete test cases + It("should work as expected", func() { + msgA := []byte("foo") + msgB := []byte("bar") + + hmacForMsgAUsingKey1, _ := HMACEval(key1, msgA) + hmacForMsgB, _ := HMACEval(key1, msgB) + Expect(hmacForMsgAUsingKey1).ToNot(Equal(hmacForMsgB), + "HMACs are equal for different data") + + hmacForMsgAUsingKey2, _ := HMACEval(key2, msgA) + Expect(HMACEqual(hmacForMsgAUsingKey1, hmacForMsgAUsingKey2)).To(BeFalse(), + "HMACs are equal for different key") + + actual, _ := HMACEval(key1, msgA) + Expect(HMACEqual(actual, hmacForMsgAUsingKey1)).To(BeTrue(), + "HMACs are not equal when they should be") + }) + }) + + Describe("Argon2", func() { + It("should work as expected", func() { + val1 := Argon2Key([]byte("Password"), []byte("nosalt"), 32) + val2 := Argon2Key([]byte("Password"), []byte("nosalt"), 64) + val3 := Argon2Key([]byte("password"), []byte("nosalt"), 32) + + Expect(val1).ToNot(Equal(val2), "val1 equals val2 when it should not.") + Expect(val1).ToNot(Equal(val3), "val1 equals val3 when it should not.") + Expect(val2).ToNot(Equal(val3), "val2 equals val3 when it should not.") + }) + }) + + Describe("StreamCipher", func() { + It("should get back the plaintext when decrypting the ciphertext", func() { + expectedPlaintext := strings.Repeat("A", 123) + iv := RandomBytes(16) + ciphertext := SymEnc(key1, iv, []byte(expectedPlaintext)) + Expect(ciphertext).ToNot(Equal(expectedPlaintext), + "Symmetric encryption failure.") + + actualPlaintext := SymDec(key1, ciphertext) + Expect(string(actualPlaintext)).To(Equal(expectedPlaintext), + "Symmetric decryption failure.") + }) + }) + + Describe("Hash", func() { + It("should work as expected", func() { + hash := Hash(key1) + expected, err := hex.DecodeString("4a56fb0ee081513a4ea0d22a33a6fba8edb95e1cb59dc1b773e77154c239c62e6377fcf26d80e3d7cf5357ebc1a4d0005fc54eb7b7110e1d5b82abc90ee4967b") + Expect(hash).To(BeEquivalentTo(expected), "Hash of key1 is incorrect.") + + hash = Hash(key2) + expected, err = hex.DecodeString("2fa5d1e9acaedc743dc7e3a767e58dd8dfba9f61514c474f77f03b4a565808484bf9bdf5a26d40cdef47a93bc18ee88b192359173d8408b3f3c609e60308e998") + Expect(err).To(BeNil(), "Failed to hash key2.") + Expect(hash).To(Equal(expected), "Hash of key2 is incorrect.") + + hash = Hash(key3) + expected, err = hex.DecodeString("0283967f6235d50886fb85d9892fa28642533cdf5aaf58ccfce5dadb2b0c0a6ca76f6ff5e0392d796925a34b57becb81904319e921b97718fb9faab597eea37b") + Expect(err).To(BeNil(), "Failed to hash key2.") + Expect(hash).To(Equal(expected), "Hash of key2 is incorrect.") + + hash = Hash(key4) + expected, err = hex.DecodeString("8a168d2b7b4e28e68d4ed9c8828b60dcc3ad57431837b5d310fa767cb3e0c50a9a237e1ed38150c49e2f04f8a74263a1b830337c5aa93d8c7edda07761a4f851") + Expect(hash).To(Equal(expected), "Hash of key1 is incorrect.") + + hash = Hash(key5) + expected, err = hex.DecodeString("03133ff43f9713e65c4a8906fbabd1d6331e47811e4d870c1d515e3471266e0383444aeb5187de640b7fe3e7505107abe91ec63c7df572f6279cb87d41c4ee61") + Expect(err).To(BeNil(), "Failed to hash key5.") + Expect(hash).To(Equal(expected), "Hash of key5 is incorrect.") + }) + }) + + Describe("MapKeyFromBytes", func() { + It("should return a storage key of length 128", func() { + storageKey := MapKeyFromBytes([]byte("something")) + Expect(len(storageKey)).To(Equal(128), "storage key is not length 128.") + }) + }) + + Describe("Marshal and Unmarshal", func() { + XIt("should have tests here", func() { + + }) + }) +})