diff --git a/.gitignore b/.gitignore index d034678..6b62deb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .DS_Store bin/ -bespin coverage.out +node_modules/ tags diff --git a/.python-version b/.python-version index d15b8b0..7c69a55 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.6.5 +3.7.0 diff --git a/Makefile b/Makefile index c5c66ae..5805a95 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ GO_TEST_DIRS := $(shell \ uniq) VERSION = `cat VERSION` -PACKAGE = github.com/amanelis/core-zero +PACKAGE = github.com/block27/core-zero # Aliases all: configuration build @@ -112,7 +112,7 @@ update: @govendor update -tree # TESTING ---------------------------------------------------------------------- -test: test_richgo +test: prepare_tests test_richgo test_coverage_func: @ENVIRONMENT=test go tool cover -func=coverage.out @@ -121,10 +121,10 @@ test_coverage_html: @ENVIRONMENT=test go tool cover -html=coverage.out test_golang: prepare_tests - @ENVIRONMENT=test go test -v ./... -cover -coverprofile=coverage.out #-bench=. + @ENVIRONMENT=test go test -v ./... -cover -coverprofile=coverage.out -bench=. test_gotest: prepare_tests - @ENVIRONMENT=test gotest -v ./... -cover -coverprofile=coverage.out #-bench=. + @ENVIRONMENT=test gotest -v ./... -cover -coverprofile=coverage.out -bench=. test_richgo: prepare_tests @ENVIRONMENT=test richgo test -v ./... -cover -coverprofile=coverage.out #-bench=. diff --git a/README.md b/README.md index b5ce411..601184a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -# Sigma HSM +# CORE ZERO HSM + This source repository contains the code used for creating, signing and encrypting keys and data through the "pseudo" HSM module authored by Alex Manelis. ## General + Below are the main points to be noted on the HSM that can be broken down into their own detailed sections. - **PSEUDO**, meaning that the keys are generated in a secure hardware environment, but encrypted with a custom Sigma KEY/IV device used to AES encrypt/decrypt pems and store them for safe backup. There is available an AES VHDL package that can be used to generate the keys on FPGA, but that has been a challenge to fetch keys and back them up. @@ -9,12 +11,28 @@ Below are the main points to be noted on the HSM that can be broken down into th - **RANDOMNESS**, when generating the ECDSA/RSA assymetric keys, the device needs the highest possible amount of entropy. It gains this through the use of two devices cabable of creating close to. 7.9999 bits of Entropy per byte. The first, `/dev/TrueRNG` is capaable of speeds around 3 mb/s. The second is the BitBlabber, `/dev/BB` capable of speeds around 20 mb/s. These have been mapped into the `crypto/rand` module `io.Reader` interface and used directly in Sigma to generate the keys. - **FPGA**, the motherboard here that will be used to run the application code is a DEC10-NANO from TerASIC. It is capable of complete VHDL programming and hardware managment by switches on board. Plan of importance on methods of implementation below: + - Implement complete golang code base capable of generating soft keys (meaning they can be accessed and passed around only inside of the HSM). These can be encrypted via AES and safely backed up. - - Second is to implement completely in hardware using VHDL. This is harder, but possible. +- Second is to implement completely in hardware using VHDL. This is harder, but possible. + +- After either are completed, build out Serial API so the device can be connected to HSM API via USB only. + + - - After either are completed, build out Serial API so the device can be connected to HSM API via USB only. +The main goal here is to build an HSM like device that can securly generate keys, but also give the ability to back up keys. It's important to note that what is currentlty available on the market is hard to use, minimally documented, bad libraries and very very expensive. I believe it can be done better and safer from a standpoint of hardware failure. - +## Practices + +Please be very aware of older and out dated versions of software, staying up to date with latest releases and abolished algorithms deemed as unacceptable is the first step to mitigating cyber risk. + +- Do NOT support any version of SSL or TLS 1.0. If you see SSL v2 or SSL v3, disable them immediately, as they will expose a range of vulnerabilities including Freak and Poodle. +- Do NOT support MD5, RC4, and DES in cipher suites. MD5 cannot be trusted as it hash signatures can cause collisions. RC4 has cipher weaknesses, and DES only has 56-bit keys, and which can be easily brute forced. +- Do NOT support 1,024-bit (or less) RSA keys. +- Do NOT support the export of 512-bit export suites, as 512-bit Diffie-Hellman keys have been long cracked. +- Only support AEAD ciphers. + - AEAD_CHACHA20_POLY1305 (chacha20-ietf-poly1305) + - AEAD_AES_256_GCM (aes-256-gcm) + - AEAD_AES_192_GCM (aes-192-gcm) + - AEAD_AES_128_GCM (aes-128-gcm) -The main goal here is to build an HSM like device that can securly generate keys, but also give the ability to back up keys. It's important to note that what is currentlty available on the market is hard to use, minimally documented, bad libraries and very very expensive. I believe it can be done better and safer from a standpoint of hardware failure. \ No newline at end of file diff --git a/backend/client.go b/backend/client.go index e85b283..03adfc4 100644 --- a/backend/client.go +++ b/backend/client.go @@ -1,24 +1,26 @@ package backend import ( + "crypto/subtle" "encoding/json" "fmt" "io/ioutil" "math/rand" "runtime" + // "strconv" "strings" "time" - "github.com/amanelis/core-zero/config" - "github.com/amanelis/core-zero/crypto" - h "github.com/amanelis/core-zero/helpers" - "github.com/amanelis/core-zero/services/bbolt" - "github.com/amanelis/core-zero/services/serial" + "github.com/block27/core-zero/config" + "github.com/block27/core-zero/crypto" + h "github.com/block27/core-zero/helpers" + "github.com/block27/core-zero/services/bbolt" + "github.com/block27/core-zero/services/serial" - "github.com/google/gousb" "github.com/awnumar/memguard" "github.com/briandowns/spinner" + "github.com/google/gousb" "github.com/sirupsen/logrus" ) @@ -36,9 +38,9 @@ type Backend struct { // device - capture data in from connected AES/Arduino usbmodem type device struct { - Product uint16 `json:"product"` - Vendor uint16 `json:"vendor"` - Serial string `json:"serial"` + Product uint16 `json:"product"` + Vendor uint16 `json:"vendor"` + Serial string `json:"serial"` Manufacturer string `json:"manufacturer"` } @@ -77,7 +79,7 @@ func NewBackend() (*Backend, error) { // work, if removed or altered, HSM code will not run. But key recovery is // still possible. func (b *Backend) HardwareAuthenticate() error { - spinners, err := newSpinner(3) + spinners, err := newSpinner(1) if err != nil { return err } @@ -90,6 +92,8 @@ func (b *Backend) HardwareAuthenticate() error { var extB1, extB2 string + // Grab the locaiton of the PIN 1 and 2. Depending on Architecture, this could + // differ, but not hugely. if runtime.GOOS == "darwin" { extB1 = fmt.Sprintf("%s/%s", "/Volumes/BASE1", config.ExtBase1Path) extB2 = fmt.Sprintf("%s/%s", "/Volumes/BASE2", config.ExtBase2Path) @@ -129,9 +133,11 @@ func (b *Backend) HardwareAuthenticate() error { for i := 0; i < len(spinners); i++ { spinners[i].Stop() } + return err } + // Hardware stored, on SBC two values that should equal to the values on the USB devices hmK, _ := h.ReadFile(config.HostMasterKeyPath) hmI, _ := h.ReadFile(config.HostMasterIvPath) @@ -185,42 +191,31 @@ func (b *Backend) HardwareAuthenticate() error { ) // Read ext1 - b1F, _ := h.ReadFile(extB1) - p1F, _ := h.ReadFile(config.HostPin1) + b1F, _ := h.ReadFileByte(extB1) + p1F, _ := h.ReadFileByte(config.HostPin1) - dec1, _ := c.Decrypt([]byte(b1F)) - if string(dec1) != p1F { + dec1, _ := c.Decrypt(b1F) + if subtle.ConstantTimeCompare(dec1, p1F) == 0 { return fmt.Errorf("%s", h.RFgB("pin1 does not match, invalid ext authentication")) } // Read ext2 - b2F, _ := h.ReadFile(extB2) - p2F, _ := h.ReadFile(config.HostPin2) + b2F, _ := h.ReadFileByte(extB2) + p2F, _ := h.ReadFileByte(config.HostPin2) - dec2, _ := c.Decrypt([]byte(b2F)) - if string(dec2) != p2F { + dec2, _ := c.Decrypt(b2F) + if subtle.ConstantTimeCompare(dec2, p2F) == 0 { return fmt.Errorf("%s", h.RFgB("pin2 does not match, invalid ext authentication")) } return nil } -func findMPD26(product, vendor uint16) func(desc *gousb.DeviceDesc) bool { - return func(desc *gousb.DeviceDesc) bool { - return desc.Product == gousb.ID(product) && desc.Vendor == gousb.ID(vendor) - } -} - // locateDevice ... temporary fix, but need to find the AES device to starts func (b *Backend) locateDevice() (string, error) { - data, err := ioutil.ReadDir("/dev") - if err != nil { - return "", err - } - // Run device identification process // Open our jsonFile - f, err := h.NewFile("/var/data/device") + f, err := h.NewFile("/var/data/device.teensy") if err != nil { return "", err } @@ -230,7 +225,7 @@ func (b *Backend) locateDevice() (string, error) { // Unmarshall data into struct var jerr := json.Unmarshal([]byte(string(f.GetBody())), &d) if jerr != nil { - return "", fmt.Errorf(h.RFgB("invalid device mapping file")) + return "", fmt.Errorf(h.RFgB("invalid device mapping file"), jerr) } ctx := gousb.NewContext() @@ -249,6 +244,11 @@ func (b *Backend) locateDevice() (string, error) { return "", fmt.Errorf(h.RFgB("device manufacturer and serial did not match")) } + data, err := ioutil.ReadDir("/dev") + if err != nil { + return "", err + } + for _, f := range data { // MacOSX if strings.Contains(f.Name(), "tty.usbmodem") { diff --git a/cmd/dsa.go b/cmd/ecdsa.go similarity index 97% rename from cmd/dsa.go rename to cmd/ecdsa.go index 7ffbecd..a9b5e7d 100644 --- a/cmd/dsa.go +++ b/cmd/ecdsa.go @@ -8,9 +8,9 @@ import ( "github.com/spf13/cobra" - h "github.com/amanelis/core-zero/helpers" - "github.com/amanelis/core-zero/services/dsa/ecdsa" - "github.com/amanelis/core-zero/services/dsa/signature" + h "github.com/block27/core-zero/helpers" + "github.com/block27/core-zero/services/dsa/ecdsa" + "github.com/block27/core-zero/services/dsa/signature" ) var ( @@ -45,7 +45,7 @@ var ( func init() { // Create flags ... dsaCreateCmd.Flags().StringVarP(&createName, "name", "n", "", "name required") - dsaCreateCmd.Flags().StringVarP(&createCurve, "curve", "c", "prime256v1", "default: prime256v1") + dsaCreateCmd.Flags().StringVarP(&createCurve, "curve", "c", "prime256v1", "") dsaCreateCmd.MarkFlagRequired("name") // Get flags ... @@ -287,9 +287,9 @@ var dsaExportPubCmd = &cobra.Command{ } pubKey, err := base64.StdEncoding.DecodeString(key.Struct().PublicKeyB64) - if err != nil { - panic(err) - } + if err != nil { + panic(err) + } fmt.Println(string(pubKey)) }, diff --git a/cmd/root.go b/cmd/root.go index e1d7696..0613110 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,8 +3,8 @@ package cmd import ( "fmt" - "github.com/amanelis/core-zero/backend" - h "github.com/amanelis/core-zero/helpers" + "github.com/block27/core-zero/backend" + h "github.com/block27/core-zero/helpers" "github.com/spf13/cobra" ) diff --git a/cmd/version.go b/cmd/version.go index 82d604e..c68c60f 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -3,7 +3,7 @@ package cmd import ( "fmt" - h "github.com/amanelis/core-zero/helpers" + h "github.com/block27/core-zero/helpers" "github.com/spf13/cobra" ) diff --git a/crypto/encrypt.go b/crypto/encrypt.go index b187982..ffe5186 100644 --- a/crypto/encrypt.go +++ b/crypto/encrypt.go @@ -7,7 +7,7 @@ import ( "errors" "github.com/awnumar/memguard" - "github.com/spacemonkeygo/openssl" + "github.com/block27/openssl" ) type AESCredentials struct { diff --git a/crypto/entropy.go b/crypto/entropy.go index c8cc134..552f5ab 100644 --- a/crypto/entropy.go +++ b/crypto/entropy.go @@ -2,6 +2,7 @@ package crypto import ( "bytes" + "errors" "fmt" "io/ioutil" "os" @@ -11,34 +12,59 @@ import ( "strings" "time" - "github.com/amanelis/core-zero/helpers" + "github.com/block27/core-zero/helpers" ) +// MinimumEntropy is the minimum amount of entropy that will be considered safe. +// Set this to what you consider to be a 'safe' minimum entropy amount (in bits) +var MinimumEntropy = 256 + +// Timeout sets the maximum amount of time to wait for entropy. +// Waiting for entropy will time out after this amount of time. Setting to zero will never time out. +var Timeout = time.Second * 10 + +// The only supported OS is linux at this time. +var supportedOS = "linux" + +// ErrTimeout is for when the system waits too long and gives up +var ErrTimeout = errors.New("timed out waiting for sufficient entropy") + +// ErrUnsupportedOS is for for an invalid OS that does not provide entropy estimates +var ErrUnsupportedOS = errors.New("unsupported OS. Only Linux is supported") + const ( + // EntropyAvail ... EntropyAvail = "/proc/sys/kernel/random/entropy_avail" + + // PoolSize ... PoolSize = "/proc/sys/kernel/random/poolsize" ) +// EntropyAPI ... type EntropyAPI interface { EntropyAvail() (int, error) PoolSize() (int, error) GenerateRandomBytes(size int) ([]byte, error) GenerateRandomFile(size int) (string, error) Ping() (string, error) + WaitForEntropy() error } type entropyAPI struct{} +// NewEntropy ... func NewEntropy() EntropyAPI { return &entropyAPI{} } +// PoolSize ... func (e *entropyAPI) PoolSize() (int, error) { return 0, nil } +// EntropyAvail ... func (e *entropyAPI) EntropyAvail() (int, error) { - if runtime.GOOS != "linux" || !helpers.FileExists(EntropyAvail) { + if runtime.GOOS != supportedOS || !helpers.FileExists(EntropyAvail) { return -1, fmt.Errorf("Invalid architecture for running hwrng, found system: %s", runtime.GOOS) } @@ -48,31 +74,30 @@ func (e *entropyAPI) EntropyAvail() (int, error) { cmd.Stdout = &stdout cmd.Stderr = &stderr - err := cmd.Run() - if err != nil { - return -1, fmt.Errorf("AvailableEntropy() exec failed with: %s\n", err) + if err := cmd.Run(); err != nil { + return -1, fmt.Errorf("function AvailableEntropy() exec failed with: %s", err) } - content := strings.TrimSuffix(string(stdout.Bytes()), "\n") - - value, err := strconv.Atoi(content) + value, err := strconv.Atoi(strings.TrimSuffix(string(stdout.Bytes()), "\n")) if err != nil { - return -1, fmt.Errorf("AvailableEntropy() strconv failed with: %s\n", err) + return -1, fmt.Errorf("function AvailableEntropy() strconv failed with: %s", err) } return value, nil } +// GenerateRandomBytes ... func (e *entropyAPI) GenerateRandomBytes(size int) ([]byte, error) { b := make([]byte, size) - // if _, err := Reader.Read(b); err != nil { - // return nil, err - // } + if _, err := Reader.Read(b); err != nil { + return nil, err + } return b, nil } +// GenerateRandomFile ... func (e *entropyAPI) GenerateRandomFile(size int) (string, error) { filename := fmt.Sprintf("/tmp/%s", strconv.Itoa(int(time.Now().Unix()))) @@ -80,15 +105,53 @@ func (e *entropyAPI) GenerateRandomFile(size int) (string, error) { return "", err } - randomBy, _ := e.GenerateRandomBytes(size) + randomBy, err := e.GenerateRandomBytes(size) + if err != nil { + return "", err + } if err := ioutil.WriteFile(filename, randomBy, 0644); err != nil { - panic(err) + return "", err } return filename, nil } +// Ping ... func (e *entropyAPI) Ping() (string, error) { return "pong", nil } + +// WaitForEntropy blocks until sufficient entropy is available +func (e *entropyAPI) WaitForEntropy() error { + if runtime.GOOS != supportedOS { + return ErrUnsupportedOS + } + + // set up the timeout + timeout := make(chan bool, 1) + if Timeout != 0 { + go func(timeoutDuration time.Duration) { + time.Sleep(timeoutDuration) + timeout <- true + }(Timeout) + } + + for { + entropy, err := e.EntropyAvail() + + switch { + case err != nil: + return err + case entropy > MinimumEntropy: + return nil + default: + select { + case <-timeout: + return ErrTimeout + default: + time.Sleep(50 * time.Millisecond) + } + } + } +} diff --git a/crypto/hmac.go b/crypto/hmac.go index 554f95c..ff1af89 100644 --- a/crypto/hmac.go +++ b/crypto/hmac.go @@ -9,13 +9,14 @@ import ( // NewHMACKey generates a random 256-bit secret key for HMAC use. // Because key generation is critical, it panics if the source of randomness fails. -func NewHMACKey() *[32]byte { +func NewHMACKey() (*[32]byte, error) { key := &[32]byte{} - _, err := io.ReadFull(rand.Reader, key[:]) - if err != nil { - panic(err) + + if _, err := io.ReadFull(rand.Reader, key[:]); err != nil { + return nil, err } - return key + + return key, nil } // GenerateHMAC produces a symmetric signature using a shared secret key. @@ -23,7 +24,6 @@ func GenerateHMAC(data []byte, key *[32]byte) []byte { h := hmac.New(sha512.New512_256, key[:]) h.Write(data) return h.Sum(nil) - } // CheckHMAC securely checks the supplied MAC against a message using the shared secret key. diff --git a/data/data_test.go b/data/data_test.go index c07b4dc..69c4ff1 100644 --- a/data/data_test.go +++ b/data/data_test.go @@ -7,7 +7,7 @@ import ( "fmt" "testing" - "github.com/amanelis/core-zero/helpers" + "github.com/block27/core-zero/helpers" ) func TestSHADummy(t *testing.T) { diff --git a/data/signatures/data b/data/signatures/data new file mode 100644 index 0000000..b6fc4c6 --- /dev/null +++ b/data/signatures/data @@ -0,0 +1 @@ +hello \ No newline at end of file diff --git a/data/signatures/ecdsa/prime256v1-sha1.der b/data/signatures/ecdsa/prime256v1-sha1.der new file mode 100644 index 0000000..3b696c4 Binary files /dev/null and b/data/signatures/ecdsa/prime256v1-sha1.der differ diff --git a/data/signatures/ecdsa/prime256v1-sha256.der b/data/signatures/ecdsa/prime256v1-sha256.der new file mode 100644 index 0000000..732c043 --- /dev/null +++ b/data/signatures/ecdsa/prime256v1-sha256.der @@ -0,0 +1 @@ +0D 42{CR;|jXL)c  5XI)|)ZfhriX~% \ No newline at end of file diff --git a/data/signatures/ecdsa/prime256v1-sha512.der b/data/signatures/ecdsa/prime256v1-sha512.der new file mode 100644 index 0000000..5cc2dd4 Binary files /dev/null and b/data/signatures/ecdsa/prime256v1-sha512.der differ diff --git a/data/signatures/ecdsa/secp384r1-sha1.der b/data/signatures/ecdsa/secp384r1-sha1.der new file mode 100644 index 0000000..c892dbd Binary files /dev/null and b/data/signatures/ecdsa/secp384r1-sha1.der differ diff --git a/data/signatures/ecdsa/secp384r1-sha256.der b/data/signatures/ecdsa/secp384r1-sha256.der new file mode 100644 index 0000000..f92f511 Binary files /dev/null and b/data/signatures/ecdsa/secp384r1-sha256.der differ diff --git a/data/signatures/ecdsa/secp384r1-sha512.der b/data/signatures/ecdsa/secp384r1-sha512.der new file mode 100644 index 0000000..f23080f Binary files /dev/null and b/data/signatures/ecdsa/secp384r1-sha512.der differ diff --git a/data/signatures/ecdsa/secp521r1-sha1.der b/data/signatures/ecdsa/secp521r1-sha1.der new file mode 100644 index 0000000..e35938b --- /dev/null +++ b/data/signatures/ecdsa/secp521r1-sha1.der @@ -0,0 +1 @@ +0A\|s~}U>7BȹOE[<[zVcp̿]B nB HQ,v+$孀pWDR|Z卂e.hL1R? \ No newline at end of file diff --git a/data/signatures/ecdsa/secp521r1-sha256.der b/data/signatures/ecdsa/secp521r1-sha256.der new file mode 100644 index 0000000..3c7d073 Binary files /dev/null and b/data/signatures/ecdsa/secp521r1-sha256.der differ diff --git a/data/signatures/ecdsa/secp521r1-sha512.der b/data/signatures/ecdsa/secp521r1-sha512.der new file mode 100644 index 0000000..d3ddcfb Binary files /dev/null and b/data/signatures/ecdsa/secp521r1-sha512.der differ diff --git a/go.mod b/go.mod index d529b1b..566cbb7 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/amanelis/core-zero +module github.com/block27/core-zero go 1.13 @@ -11,9 +11,11 @@ require ( github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect github.com/aristanetworks/goarista v0.0.0-20200131140622-c6473e3ed183 // indirect github.com/awnumar/memguard v0.21.0 + github.com/block27/openssl v1.0.2 github.com/briandowns/spinner v1.8.0 github.com/btcsuite/btcd v0.20.1-beta github.com/btcsuite/btcutil v1.0.1 + github.com/davecgh/go-spew v1.1.1 github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/edunuzzi/go-bip44 v0.0.0-20190109211530-eb6b7decf5cc @@ -45,7 +47,6 @@ require ( github.com/rogpeppe/godef v1.1.1 // indirect github.com/securego/gosec v0.0.0-20200106085552-9cb83e10afad // indirect github.com/sirupsen/logrus v1.4.2 - github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a github.com/spf13/afero v1.2.2 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cobra v0.0.5 @@ -61,7 +62,6 @@ require ( github.com/zmb3/gogetdoc v0.0.0-20190228002656-b37376c5da6a // indirect go.etcd.io/bbolt v1.3.3 golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 - golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 // indirect golang.org/x/tools v0.0.0-20200114012648-3b9e23528349 // indirect gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect gopkg.in/ini.v1 v1.51.1 // indirect diff --git a/go.sum b/go.sum index 21b1faa..478a1d6 100644 --- a/go.sum +++ b/go.sum @@ -65,6 +65,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver v3.6.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/block27/openssl v1.0.2 h1:b3Cv6J4ObtOsrXYRqVZr5dm8e+kCGBZUSzJFqESGXMs= +github.com/block27/openssl v1.0.2/go.mod h1:JQwNIMPBFtkiKyGbz53COeFy7A1epCNFhR0lY1A2xMs= github.com/bombsimon/wsl/v2 v2.0.0 h1:+Vjcn+/T5lSrO8Bjzhk4v14Un/2UyCA1E3V5j9nwTkQ= github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U= github.com/briandowns/spinner v1.8.0 h1:SeidJ8ASAayR4Wxl5Of54LHqgi8s6sBvAHg4kxKxia4= @@ -447,10 +449,6 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs= github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= -github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a h1:/eS3yfGjQKG+9kayBkj0ip1BGhq6zJ3eaVksphxAaek= -github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= -github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= -github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= @@ -617,6 +615,8 @@ golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 h1:gZpLHxUX5BdYLA08Lj4YCJNN/ golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= diff --git a/helpers/date_time.go b/helpers/date_time.go new file mode 100644 index 0000000..c5e853e --- /dev/null +++ b/helpers/date_time.go @@ -0,0 +1,15 @@ +package helpers + +import ( + "fmt" + "time" +) + +// CreatedAtNow ... +func CreatedAtNow() string { + t := time.Now() + + return fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d", + t.Year(), t.Month(), t.Day(), + t.Hour(), t.Minute(), t.Second()) +} diff --git a/helpers/date_time_test.go b/helpers/date_time_test.go new file mode 100644 index 0000000..345b806 --- /dev/null +++ b/helpers/date_time_test.go @@ -0,0 +1 @@ +package helpers diff --git a/helpers/file.go b/helpers/file.go index b368630..f7262d4 100644 --- a/helpers/file.go +++ b/helpers/file.go @@ -153,6 +153,16 @@ func ReadFile(filename string) (string, error) { return string(b), nil } +// ReadFileByte ... +func ReadFileByte(filename string) ([]byte, error) { + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + return b, nil +} + // ReadBinary ... func ReadBinary(filename string) ([]byte, error) { file, err := os.Open(filename) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..906c33b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,495 @@ +{ + "name": "core-zero", + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "husky": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/husky/-/husky-1.3.1.tgz", + "integrity": "sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.7", + "execa": "^1.0.0", + "find-up": "^3.0.0", + "get-stdin": "^6.0.0", + "is-ci": "^2.0.0", + "pkg-dir": "^3.0.0", + "please-upgrade-node": "^3.1.1", + "read-pkg": "^4.0.1", + "run-node": "^1.0.0", + "slash": "^2.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "read-pkg": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", + "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", + "dev": true, + "requires": { + "normalize-package-data": "^2.3.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0" + } + }, + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "run-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz", + "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f1f7ed5 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "devDependencies": { + "husky": "^1.3.1" + }, + "husky": { + "hooks": { + "pre-commit": "make test", + "pre-push": "make test" + } + }, + "name": "core-zero" +} diff --git a/pkg/api/main.go b/pkg/api/main.go index c971810..e0505b5 100644 --- a/pkg/api/main.go +++ b/pkg/api/main.go @@ -6,8 +6,8 @@ import ( "net/http" // jwt "github.com/dgrijalva/jwt-go" - "github.com/amanelis/core-zero/backend" - "github.com/amanelis/core-zero/services/dsa/ecdsa" + "github.com/block27/core-zero/backend" + "github.com/block27/core-zero/services/dsa/ecdsa" ) var ( @@ -34,10 +34,6 @@ func dsaList(w http.ResponseWriter, r *http.Request) { panic(err) } - if len(keys) == 0 { - w.Write(nil) - } - jData, err := json.Marshal(keys) if err != nil { panic(err) diff --git a/pkg/cli/main.go b/pkg/cli/main.go index b6bdcb2..f9cd0d9 100644 --- a/pkg/cli/main.go +++ b/pkg/cli/main.go @@ -1,9 +1,9 @@ package main import ( - "github.com/amanelis/core-zero/backend" - c "github.com/amanelis/core-zero/cmd" m "github.com/awnumar/memguard" + "github.com/block27/core-zero/backend" + c "github.com/block27/core-zero/cmd" ) func main() { diff --git a/services/aes/gcm/service.go b/services/aes/gcm/service.go index 98d5358..9197a35 100644 --- a/services/aes/gcm/service.go +++ b/services/aes/gcm/service.go @@ -10,21 +10,20 @@ import ( // NewEncryptionKey generates a random 256-bit key for Encrypt() and // Decrypt(). It panics if the source of randomness fails. -func NewEncryptionKey() *[32]byte { +func NewEncryptionKey() (*[32]byte, error) { key := [32]byte{} - _, err := io.ReadFull(rand.Reader, key[:]) - if err != nil { - panic(err) + if _, err := io.ReadFull(rand.Reader, key[:]); err != nil { + return nil, err } - return &key + return &key, nil } // Encrypt encrypts data using 256-bit AES-GCM. This both hides the content of // the data and provides a check that it hasn't been altered. Output takes the // form nonce|ciphertext|tag where '|' indicates concatenation. -func Encrypt(plaintext []byte, key *[32]byte) (ciphertext []byte, err error) { +func Encrypt(plaintext []byte, key *[32]byte) ([]byte, error) { block, err := aes.NewCipher(key[:]) if err != nil { return nil, err @@ -47,7 +46,7 @@ func Encrypt(plaintext []byte, key *[32]byte) (ciphertext []byte, err error) { // Decrypt decrypts data using 256-bit AES-GCM. This both hides the content of // the data and provides a check that it hasn't been altered. Expects input // form nonce|ciphertext|tag where '|' indicates concatenation. -func Decrypt(ciphertext []byte, key *[32]byte) (plaintext []byte, err error) { +func Decrypt(ciphertext []byte, key *[32]byte) ([]byte, error) { block, err := aes.NewCipher(key[:]) if err != nil { return nil, err diff --git a/services/dsa/dsa.go b/services/dsa/dsa.go index 83b324f..b06b430 100644 --- a/services/dsa/dsa.go +++ b/services/dsa/dsa.go @@ -4,8 +4,8 @@ import ( "crypto/elliptic" "fmt" - "github.com/amanelis/core-zero/helpers" - "github.com/spacemonkeygo/openssl" + "github.com/block27/core-zero/helpers" + "github.com/block27/openssl" guuid "github.com/google/uuid" ) @@ -16,6 +16,10 @@ const ( // StatusArchived is for keys that are "soft" deleted and no longer in use StatusArchived = "archive" + // StatusPending is for when a key has been created, but no encryption methods + // have been set to the key + StatusPending = "pending" + // Public string constant for type setting Public = "public" @@ -23,14 +27,13 @@ const ( Private = "private" ) -// EC ... -type EC struct { } - // GetCurve checks the string param matched and should return a valid ec curve func GetCurve(curve string) (elliptic.Curve, string, openssl.EllipticCurve, error) { switch curve { case "prime256v1": // prime256v1: X9.62/SECG curve over a 256 bit prime field return elliptic.P256(), "prime256v1", openssl.Prime256v1, nil + // case "secp224r1": + // return elliptic.P224(), "secp224r1", openssl.Secp224r1, nil case "secp384r1": // secp384r1: NIST/SECG curve over a 384 bit prime field return elliptic.P384(), "secp384r1", openssl.Secp384r1, nil case "secp521r1": // secp521r1: NIST/SECG curve over a 521 bit prime field diff --git a/services/dsa/dsa_test.go b/services/dsa/dsa_test.go index 1b22db5..bd4f583 100644 --- a/services/dsa/dsa_test.go +++ b/services/dsa/dsa_test.go @@ -7,6 +7,7 @@ import ( var Curves = []string{ "prime256v1", + // "secp224r1", "secp384r1", "secp521r1", } diff --git a/services/dsa/ec/helpers_test.go b/services/dsa/ec/helpers_test.go new file mode 100644 index 0000000..f762a40 --- /dev/null +++ b/services/dsa/ec/helpers_test.go @@ -0,0 +1,86 @@ +package ec + +import ( + "fmt" + "os" + "strings" + "testing" + + "github.com/block27/core-zero/config" + "github.com/block27/core-zero/helpers" + "github.com/block27/core-zero/services/dsa" + + "github.com/stretchr/testify/assert" +) + +// AssertStructCorrectness ... +func AssertStructCorrectness(t *testing.T, k KeyAPI, o string, c string) { + t.Helper() + + assert.NotNil(t, k.GetAttributes().GID) + assert.NotNil(t, k.GetAttributes().Name) + assert.NotNil(t, k.GetAttributes().Slug) + assert.NotNil(t, k.GetAttributes().FingerprintMD5) + assert.NotNil(t, k.GetAttributes().FingerprintSHA) + + if strings.Contains(k.GetAttributes().KeyType, dsa.Private) { + assert.NotNil(t, k.Struct().privateKeyPEM) + assert.Equal(t, dsa.ToString(c, o), k.GetAttributes().KeyType) + } + + assert.NotNil(t, k.Struct().publicKeyPEM) + + assert.Equal(t, "active", k.GetAttributes().Status) + +} + +// AssertStructNilness ... +func AssertStructNilness(t *testing.T, k KeyAPI) { + t.Helper() + + assert.Equal(t, k.GetAttributes().GID.String(), "00000000-0000-0000-0000-000000000000") + assert.Equal(t, k.GetAttributes().Name, "") + assert.Equal(t, k.GetAttributes().Slug, "") + assert.Equal(t, k.GetAttributes().Status, "") + assert.Equal(t, k.GetAttributes().KeyType, "") + assert.Equal(t, k.GetAttributes().FingerprintMD5, "") + assert.Equal(t, k.GetAttributes().FingerprintSHA, "") + + assert.Equal(t, k.Struct().privateKeyPEM, "") + assert.Equal(t, k.Struct().publicKeyPEM, "") +} + +func ClearSingleTestKey(t *testing.T, p string) { + t.Helper() + + err := os.RemoveAll(p) + if err != nil { + t.Fatal(err) + } + + t.Logf("successfully removed [%s]", p) +} + +// CheckFullKeyFileObjects checks that a full private, public pem and object +// files are created when a new key is created +func CheckFullKeyFileObjects(t *testing.T, c config.Reader, k KeyAPI, f string) { + t.Helper() + + // Check for filesystem keys are present + checkKeyFileObjects(t, f, + fmt.Sprintf("%s/ec/%s", c.GetString("paths.keys"), k.GetAttributes().FilePointer())) +} + +func checkKeyFileObjects(t *testing.T, f string, p string) { + paths := []string{ + fmt.Sprintf("%s/%s", p, "obj.bin"), + fmt.Sprintf("%s/%s", p, "key.pem"), + fmt.Sprintf("%s/%s", p, "pub.pem"), + } + + for _, p := range paths { + if !helpers.FileExists(p) { + t.Fatalf("%s failed to writeToFS() -> %s", f, p) + } + } +} diff --git a/services/dsa/ec/service.go b/services/dsa/ec/service.go index ddc73c3..acf9f0a 100644 --- a/services/dsa/ec/service.go +++ b/services/dsa/ec/service.go @@ -2,143 +2,306 @@ package ec import ( "fmt" - "sync" - "time" - - "github.com/amanelis/core-zero/config" - "github.com/amanelis/core-zero/helpers" - "github.com/amanelis/core-zero/services/dsa" - "github.com/amanelis/core-zero/services/dsa/ecdsa/encodings" + "io/ioutil" + "os" + "sort" + + "github.com/block27/core-zero/config" + "github.com/block27/core-zero/helpers" + "github.com/block27/core-zero/services/dsa" + "github.com/block27/core-zero/services/dsa/ecdsa/encodings" + "github.com/block27/core-zero/services/dsa/errors" + + "github.com/block27/openssl" + "github.com/jedib0t/go-pretty/table" + "github.com/jedib0t/go-pretty/text" +) - "github.com/spacemonkeygo/openssl" - guuid "github.com/google/uuid" +var ( + keyPath = "ec" ) // KeyAPI main api for defining Key behavior and functions type KeyAPI interface { - FilePointer() string - - getArtSignature() string - getPrivateKey() (openssl.PrivateKey, error) - getPublicKey() (openssl.PublicKey, error) + GetAttributes() *dsa.KeyAttributes + Struct() *key Sign([]byte) ([]byte, error) Verify([]byte, []byte) bool + + getPrivateKey() (openssl.PrivateKey, error) + getPublicKey() (openssl.PublicKey, error) } // key struct is the main type and placeholder for private keys on the system. These -// should be persisted to a flat file database storage. +// should be persisted to a flat file database storage type key struct { - sink sync.Mutex // mutex to allow clean concurrent access - GID guuid.UUID // guuid for crypto identification + attributes *dsa.KeyAttributes - // Base name passed from CLI, *not indexed - Name string + privateKeyPEM []byte + publicKeyPEM []byte +} - // Slug auto generated from Haiku *not indexed - Slug string +// NewEC returns a new EC type keypair created using our rand.Reader, and using +// the OpenSSL C bindings +func NewEC(c config.Reader, name string, curve string) (KeyAPI, error) { + _, cv, ol, err := dsa.GetCurve(curve) + if err != nil { + return nil, err + } - // Hold the base key status, {archive, active} - Status string + pri, err := openssl.GenerateECKey(ol) + if err != nil { + return nil, err + } - // Basically the elliptic curve size of the key - KeyType string + priPemBytes, err := pri.MarshalPKCS1PrivateKeyPEM() + if err != nil { + return nil, err + } - FingerprintMD5 string // Real fingerprint in MD5 (legacy) of the key - FingerprintSHA string // Real fingerprint in SHA256 of the key + pubPemBytes, err := pri.MarshalPKIXPublicKeyPEM() + if err != nil { + return nil, err + } - CreatedAt time.Time + // Create the key struct object + key := &key{ + attributes: &dsa.KeyAttributes{ + GID: dsa.GenerateUUID(), + Name: name, + Slug: helpers.NewHaikunator().Haikunate(), + KeyType: dsa.ToString(cv, dsa.Private), + KeyUse: dsa.ToString(cv, dsa.Private), + Status: dsa.StatusActive, + FingerprintMD5: encodings.BaseMD5(pubPemBytes), + FingerprintSHA: encodings.BaseSHA256(pubPemBytes), + CreatedAt: helpers.CreatedAtNow(), + }, + privateKeyPEM: priPemBytes, + publicKeyPEM: pubPemBytes, + } - PrivateKeyDER []byte - PrivateKeyPEM []byte + // Write the entire key object to FS + if err := key.writeToFS(c); err != nil { + return nil, err + } - PublicKeyDER []byte - PublicKeyPEM []byte + return key, nil } -// NewEC ... -func NewEC(c config.Reader, name string, curve string) (KeyAPI, error) { - // Validate the type of curve passed - _, cv, ol, err := dsa.GetCurve(curve) +// GetEC fetches a system key that lives on the file system. Return useful +// identification data aobut the key, likes its SHA256 and MD5 signatures +func GetEC(c config.Reader, fp string) (KeyAPI, error) { + dirPath := fmt.Sprintf("%s/%s/%s", c.GetString("paths.keys"), keyPath, fp) + if _, err := os.Stat(dirPath); os.IsNotExist(err) { + return (*key)(nil), errors.NewKeyPathError("invalid key path") + } + + objPath := fmt.Sprintf("%s/obj.bin", dirPath) + + if !helpers.FileExists(objPath) { + return (*key)(nil), fmt.Errorf("missing serialized object file") + } + + data, err := helpers.ReadFile(objPath) if err != nil { - return nil, err + return (*key)(nil), errors.NewKeyObjtError("invalid key obj") } - typ, terr := getType(cv, dsa.Private) - if terr != nil { - return nil, terr + obj, err := dsa.KAFromGOB64(data) + if err != nil { + return (*key)(nil), nil } - pri, err := openssl.GenerateECKey(ol) + k := &key{ + attributes: obj, + } + + keyPath := fmt.Sprintf("%s/key.pem", dirPath) + + // Load the privateKey + priKeyBytes, err := ioutil.ReadFile(keyPath) if err != nil { - return nil, err + return (*key)(nil), nil } - pubDer, err := pri.MarshalPKIXPublicKeyDER() + pri, err := openssl.LoadPrivateKeyFromPEM(priKeyBytes) if err != nil { - return nil, err + return (*key)(nil), nil + } + + priPemBytes, err := pri.MarshalPKCS1PrivateKeyPEM() + if err != nil { + return (*key)(nil), nil } - pubPem, err := pri.MarshalPKIXPublicKeyPEM() + pubPemBytes, err := pri.MarshalPKIXPublicKeyPEM() + if err != nil { + return (*key)(nil), nil + } + + k.privateKeyPEM = priPemBytes + k.publicKeyPEM = pubPemBytes + + return k, nil +} + +// ListEC returns a list of active keys stored on the local filesystem. Of +// which are all encrypted via AES from the hardware block +func ListEC(c config.Reader) ([]KeyAPI, error) { + files, err := ioutil.ReadDir(fmt.Sprintf("%s/ec", c.GetString("paths.keys"))) if err != nil { return nil, err } - priPem, err := pri.MarshalPKCS1PrivateKeyPEM() + sort.Slice(files, func(i, j int) bool { + return files[i].ModTime().Before(files[j].ModTime()) + }) + + var keys []KeyAPI + + for _, f := range files { + _key, _err := GetEC(c, f.Name()) + + if _err != nil { + continue + } + + keys = append(keys, _key) + } + + return keys, nil +} + +// ImportPublicEC imports an existing ECDSA key into a KeyAPI object for +// use in the Service API. Since you are importing a public Key, this will be +// an incomplete Key object. +func ImportPublicEC(c config.Reader, name string, curve string, public []byte) (KeyAPI, error) { + if name == "" { + return nil, fmt.Errorf("name cannot be empty") + } + + if curve == "" { + return nil, fmt.Errorf("curve cannot be empty") + } + + _, cv, _, err := dsa.GetCurve(curve) if err != nil { return nil, err } - priDer, err := pri.MarshalPKCS1PrivateKeyDER() + pub, err := openssl.LoadPublicKeyFromPEM(public) if err != nil { return nil, err } + pem, perr := pub.MarshalPKIXPublicKeyPEM() + if perr != nil { + return nil, perr + } - // Create the key struct object + // Resulting key will not be complete - create the key struct object anyways key := &key{ - GID: dsa.GenerateUUID(), - Name: name, - Slug: helpers.NewHaikunator().Haikunate(), - KeyType: typ, - Status: dsa.StatusActive, - FingerprintMD5: encodings.BaseMD5(pubPem), - FingerprintSHA: encodings.BaseSHA256(pubPem), - CreatedAt: time.Now(), - PrivateKeyDER: priDer, - PrivateKeyPEM: priPem, - PublicKeyDER: pubDer, - PublicKeyPEM: pubPem, + attributes: &dsa.KeyAttributes{ + GID: dsa.GenerateUUID(), + Name: name, + Slug: helpers.NewHaikunator().Haikunate(), + KeyType: dsa.ToString(cv, dsa.Public), + Status: dsa.StatusActive, + FingerprintMD5: encodings.BaseMD5(pem), + FingerprintSHA: encodings.BaseSHA256(pem), + CreatedAt: helpers.CreatedAtNow(), + }, + publicKeyPEM: pem, } // Write the entire key object to FS - // if err := key.writeToFS(c, priEnc, pubEnc); err != nil { - // return nil, err - // } + if err := key.writeToFS(c); err != nil { + return nil, err + } return key, nil } -func (k *key) writeToFS(c config.Reader, pri []byte, pub []byte) error { +// writeToFS writes object serialized data and both keys to disk. Encryption for +// keys should happen here +func (k *key) writeToFS(c config.Reader) error { + // Create the keys root directory based on it's FilePointer method + dirPath := fmt.Sprintf("%s/%s/%s", c.GetString("paths.keys"), keyPath, + k.attributes.FilePointer()) + if _, err := os.Stat(dirPath); os.IsNotExist(err) { + os.Mkdir(dirPath, os.ModePerm) + } + + // OBJ marshalling ----------------------------------------------------------- + objPath := fmt.Sprintf("%s/%s", dirPath, "obj.bin") + objFile, err := os.Create(objPath) + if err != nil { + return err + } + defer objFile.Close() + + // Marshall the objects + obj, err := dsa.KAToGOB64(k.attributes) + if err != nil { + return err + } + + if _, err := helpers.WriteBinary(objPath, []byte(obj)); err != nil { + return err + } + + if k.privateKeyPEM != nil { + priPath := fmt.Sprintf("%s/%s", dirPath, "key.pem") + + privatekeyFile, err := os.Create(priPath) + if err != nil { + return err + } + + privatekeyFile.Write(k.privateKeyPEM) + privatekeyFile.Close() + } + + if k.publicKeyPEM != nil { + pubPath := fmt.Sprintf("%s/%s", dirPath, "pub.pem") + + publickeyFile, err := os.Create(pubPath) + if err != nil { + return err + } + + publickeyFile.Write(k.publicKeyPEM) + publickeyFile.Close() + } + return nil } +// GetAttributes ... +func (k *key) GetAttributes() *dsa.KeyAttributes { + return k.attributes +} + // Struct ... func (k *key) Struct() *key { return k } -// FilePointer ... -func (k *key) FilePointer() string { - return k.GID.String() -} +// getPrivateKey ... +func (k *key) getPrivateKey() (openssl.PrivateKey, error) { + key, err := openssl.LoadPrivateKeyFromPEM(k.privateKeyPEM) + if err != nil { + return nil, err + } -func (k *key) getArtSignature() string { - return "" + return key, nil } -func (k *key) getPrivateKey() (openssl.PrivateKey, error) { - key, err := openssl.LoadPrivateKeyFromPEM(k.PrivateKeyPEM) +// getPublicKey ... +func (k *key) getPublicKey() (openssl.PublicKey, error) { + key, err := openssl.LoadPublicKeyFromPEM(k.publicKeyPEM) if err != nil { return nil, err } @@ -146,30 +309,93 @@ func (k *key) getPrivateKey() (openssl.PrivateKey, error) { return key, nil } -func (k *key) getPublicKey() (openssl.PublicKey, error) { - return nil, nil - // hex, err := hex.DecodeString(k.publicKey) - // if err != nil { - // return nil, err - // } - // - // key, err := openssl.LoadPublicKeyFromPEM(hex) - // if err != nil { - // return nil, err - // } - // - // return key, nil +// Sign uses the OpenSSL [SignPKCS1v15] method and returns a resulting signature +func (k *key) Sign(data []byte) ([]byte, error) { + pk, err := k.getPrivateKey() + if err != nil { + return nil, err + } + + return pk.SignPKCS1v15(openssl.SHA256_Method, data) } -func (k *key) Sign([]byte) ([]byte, error) { - return nil, nil +// Verify checks the passed signature and returns a bool depending on verification +func (k *key) Verify(data, sig []byte) bool { + pk, err := k.getPublicKey() + if err != nil { + return false + } + + if er := pk.VerifyPKCS1v15(openssl.SHA256_Method, data, sig); er != nil { + return false + } + + return true } -func (k *key) Verify([]byte, []byte) bool { - return false +// PrintKeysTW prints an elaborate way to display key information... not needed, +// but nice for demos and visually displays the key randomArt via a python script +func PrintKeysTW(keys []KeyAPI) { + stylePairs := [][]table.Style{ + {table.StyleColoredBright}, + } + + for ndx, f := range keys { + tw := table.NewWriter() + + tw.SetTitle(f.GetAttributes().FilePointer()) + tw.AppendRows([]table.Row{ + { + "Name", + f.GetAttributes().Name, + }, + { + "Slug", + f.GetAttributes().Slug, + }, + { + "Type", + helpers.RFgB(f.GetAttributes().KeyType), + }, + { + "Created", + f.GetAttributes().CreatedAt, + }, + { + "MD5", + f.GetAttributes().FingerprintMD5, + }, + { + "SHA256", + f.GetAttributes().FingerprintSHA, + }, + { + "SHA256 Visual", + f.GetAttributes().ArtSignature(), + }, + }) + + twOuter := table.NewWriter() + tw.SetStyle(table.StyleColoredDark) + tw.Style().Title.Align = text.AlignCenter + + for _, stylePair := range stylePairs { + row := make(table.Row, 1) + for idx := range stylePair { + row[idx] = tw.Render() + } + twOuter.AppendRow(row) + } + + twOuter.SetStyle(table.StyleDouble) + twOuter.SetTitle(fmt.Sprintf("Asymmetric Key (%d)", ndx)) + twOuter.Style().Options.SeparateRows = true + + fmt.Println(twOuter.Render()) + } } -// Helpers -func getType(curve string, pk string) (string, error) { - return fmt.Sprintf("ec.%sKey <==> %s", pk, curve), nil +// PrintKeyTW takes an array of keys and runs them through prettyPrint function +func PrintKeyTW(k *key) { + PrintKeysTW([]KeyAPI{k}) } diff --git a/services/dsa/ec/service_test.go b/services/dsa/ec/service_test.go index 74ba4cc..0b321d4 100644 --- a/services/dsa/ec/service_test.go +++ b/services/dsa/ec/service_test.go @@ -3,21 +3,25 @@ package ec import ( "fmt" "os" - // "reflect" + "reflect" "testing" - "github.com/amanelis/core-zero/config" - // "github.com/amanelis/core-zero/helpers" + "github.com/block27/core-zero/config" + "github.com/block27/core-zero/helpers" + "github.com/block27/core-zero/services/dsa" + "github.com/davecgh/go-spew/spew" ) var Config config.Reader var Curves = []string{ - "secp224r1", "prime256v1", + // "secp224r1", "secp384r1", "secp521r1", } +var Key *key + func init() { os.Setenv("ENVIRONMENT", "test") @@ -30,32 +34,542 @@ func init() { panic(fmt.Errorf("test [environment] is not in [test] mode")) } + k1, err := NewEC(c, "test-key-0", "prime256v1") + if err != nil { + panic(err) + } + + Key = k1.Struct() Config = c } +// +// +// +// +// +// TestNewEC tests simply that a NewEC can be created for each type of curve func TestNewEC(t *testing.T) { - // Invalid curve - _, q := NewEC(Config, "test-key-1", "prim56v1") - if q == nil { - t.Fatal("invalid curve") - } + t.Parallel() + + t.Run("invalid", func(t *testing.T) { + t.Parallel() + + _, q := NewEC(Config, "test-key-1", "prim56v1") + if q == nil { + t.Fatal("invalid curve") + } + }) + + t.Run("prime256v1", func(t *testing.T) { + t.Parallel() + + k, err := NewEC(Config, "test-key-1", "prime256v1") + if err != nil { + t.Fatalf(err.Error()) + } + + AssertStructCorrectness(t, k, dsa.Private, "prime256v1") + CheckFullKeyFileObjects(t, Config, k, "NewEC") + ClearSingleTestKey(t, fmt.Sprintf("%s/ec/%s", Config.GetString("paths.keys"), + k.GetAttributes().FilePointer())) + }) + + t.Run("secp384r1", func(t *testing.T) { + t.Parallel() + k, err := NewEC(Config, "test-key-1", "secp384r1") + if err != nil { + t.Fatalf(err.Error()) + } + + AssertStructCorrectness(t, k, dsa.Private, "secp384r1") + CheckFullKeyFileObjects(t, Config, k, "NewEC") + ClearSingleTestKey(t, fmt.Sprintf("%s/ec/%s", Config.GetString("paths.keys"), + k.GetAttributes().FilePointer())) + }) + + t.Run("secp521r1", func(t *testing.T) { + t.Parallel() + + k, err := NewEC(Config, "test-key-1", "secp521r1") + if err != nil { + t.Fatalf(err.Error()) + } + + AssertStructCorrectness(t, k, dsa.Private, "secp521r1") + CheckFullKeyFileObjects(t, Config, k, "NewEC") + ClearSingleTestKey(t, fmt.Sprintf("%s/ec/%s", Config.GetString("paths.keys"), + k.GetAttributes().FilePointer())) + }) +} + +// +// +// +// +// +// TestGetEC tests that a key type can be created and then extracted from raw obj +// files, imported into a struct and that it is in fact still valid +func TestGetEC(t *testing.T) { // Valid - k, err := NewEC(Config, "test-key-1", "prime256v1") + k, err := GetEC(Config, Key.GetAttributes().FilePointer()) if err != nil { + t.Fatalf(err.Error()) + } + + if !reflect.DeepEqual(k.GetAttributes(), Key.GetAttributes()) { + t.Fail() + } + + if !reflect.DeepEqual(k, Key) { t.Fail() } - if k == nil { + gPk1, err := k.getPrivateKey() + if err != nil { t.Fail() } + gPk2, err := Key.getPrivateKey() + if err != nil { + t.Fail() + } + + gPp1, err := k.getPublicKey() + if err != nil { + t.Fail() + } + + gPp2, err := Key.getPublicKey() + if err != nil { + t.Fail() + } + + // --------------------------------------------------------------------------- + if !reflect.DeepEqual(gPk1, gPk2) { + t.Fail() + } + + if !reflect.DeepEqual(gPp1, gPp2) { + t.Fail() + } + + AssertStructCorrectness(t, k, dsa.Private, "prime256v1") + CheckFullKeyFileObjects(t, Config, k, "NewEC") +} + +// +// +// +// +// +// TestGetPrivateKey checks that we can get a key, and that it's private key +// does in fact equals the key value it actually represents. +func TestGetPrivateKey(t *testing.T) { + // Test from New ------------------------------------------------------------- + k1, err := NewEC(Config, "test-key-1", "prime256v1") + if err != nil { + t.Fatalf(err.Error()) + } + + gPk1, err := k1.getPrivateKey() + if err != nil { + t.Fail() + } + + gPp1, err := gPk1.MarshalPKCS1PrivateKeyPEM() + if err != nil { + t.Fail() + } + + AssertStructCorrectness(t, k1, dsa.Private, "prime256v1") + CheckFullKeyFileObjects(t, Config, k1, "NewEC") + + // Test from Get ------------------------------------------------------------- + k2, err := GetEC(Config, k1.GetAttributes().FilePointer()) + if err != nil { + t.Fatalf(err.Error()) + } + + gPk2, err := k2.getPrivateKey() + if err != nil { + t.Fail() + } + + gPp2, err := gPk2.MarshalPKCS1PrivateKeyPEM() + if err != nil { + t.Fail() + } + + // --------------------------------------------------------------------------- + if !reflect.DeepEqual(gPk1, gPk2) { + t.Fail() + } + + if !reflect.DeepEqual(gPp1, gPp2) { + t.Fail() + } + + AssertStructCorrectness(t, k2, dsa.Private, "prime256v1") + CheckFullKeyFileObjects(t, Config, k2, "NewEC") + + ClearSingleTestKey(t, fmt.Sprintf("%s/ec/%s", Config.GetString("paths.keys"), + k1.GetAttributes().FilePointer())) +} + +// +// +// +// +// +// TestGetPublicKey checks that we can get a key, and that it's public key +// does in fact equals the key value it actually represents. +func TestGetPublicKey(t *testing.T) { + // Test from New ------------------------------------------------------------- + k1, err := NewEC(Config, "test-key-1", "prime256v1") + if err != nil { + t.Fatalf(err.Error()) + } + + gPk1, err := k1.getPublicKey() + if err != nil { + t.Fail() + } + + gPp1, err := gPk1.MarshalPKIXPublicKeyPEM() + if err != nil { + t.Fail() + } + + AssertStructCorrectness(t, k1, dsa.Private, "prime256v1") + CheckFullKeyFileObjects(t, Config, k1, "NewEC") + + // Test from Get ------------------------------------------------------------- + k2, err := GetEC(Config, k1.GetAttributes().FilePointer()) + if err != nil { + t.Fatalf(err.Error()) + } + + gPk2, err := k2.getPublicKey() + if err != nil { + t.Fail() + } + + gPp2, err := gPk2.MarshalPKIXPublicKeyPEM() + if err != nil { + t.Fail() + } + + // --------------------------------------------------------------------------- + if !reflect.DeepEqual(gPk1, gPk2) { + t.Fail() + } + + if !reflect.DeepEqual(gPp1, gPp2) { + t.Fail() + } + + AssertStructCorrectness(t, k2, dsa.Private, "prime256v1") + CheckFullKeyFileObjects(t, Config, k2, "NewEC") + + ClearSingleTestKey(t, fmt.Sprintf("%s/ec/%s", Config.GetString("paths.keys"), + k1.GetAttributes().FilePointer())) +} + +func BenchmarkSignP256(b *testing.B) { + b.ResetTimer() + hashed := []byte("testing") + + k, err := NewEC(Config, "bench-key-256", "prime256v1") + if err != nil { + b.Fail() + } + + _, e := k.getPrivateKey() + if e != nil { + b.Fail() + } + + b.ReportAllocs() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + k.Sign(hashed) + } + }) +} + +func BenchmarkSignSecp384r1(b *testing.B) { + b.ResetTimer() + hashed := []byte("testing") + + k, err := NewEC(Config, "bench-key-384", "secp384r1") + if err != nil { + b.Fail() + } + + _, e := k.getPrivateKey() + if e != nil { + b.Fail() + } + + b.ReportAllocs() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + k.Sign(hashed) + } + }) +} + +func BenchmarkSignSecp521r1(b *testing.B) { + b.ResetTimer() + hashed := []byte("testing") + + k, err := NewEC(Config, "bench-key-521", "secp521r1") + if err != nil { + b.Fail() + } + + _, e := k.getPrivateKey() + if e != nil { + b.Fail() + } + + b.ReportAllocs() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + k.Sign(hashed) + } + }) +} + +// +// +// +// +// +// TestSign redundant testing of a simple signature, next test will go into curve +// type and more depth +func TestSign(t *testing.T) { + if _, err := Key.Sign([]byte("the quick brown fox jumps over the lazy dog")); err != nil { + t.Fail() + } +} + +// +// +// +// +// +// TestSignEC ensures that we can generate signatures from each type of curve with +// no possibility of error +func TestSignEC(t *testing.T) { + t.Parallel() + + data := []byte("the quick brown fox jumps over the lazy dog") + + t.Run("sign:verify:prime256v1", func(t *testing.T) { + t.Parallel() + + key, err := NewEC(Config, "sign/verify:test1", "prime256v1") + if err != nil { + t.Fail() + } + + sig, err := key.Sign(data) + if err != nil { + t.Fail() + } + + t.Logf("Signature (prime256v1): %s\n", spew.Sdump(sig)) + + if !key.Verify(data, sig) { + t.Fail() + } + + AssertStructCorrectness(t, key, dsa.Private, "prime256v1") + CheckFullKeyFileObjects(t, Config, key, "NewEC") + + ClearSingleTestKey(t, fmt.Sprintf("%s/ec/%s", Config.GetString("paths.keys"), + key.GetAttributes().FilePointer())) + }) + + t.Run("sign:verify:secp384r1", func(t *testing.T) { + t.Parallel() + + key, err := NewEC(Config, "sign/verify:test2", "secp384r1") + if err != nil { + t.Fail() + } + + sig, err := key.Sign(data) + if err != nil { + t.Fail() + } + + t.Logf("Signature (secp384r1): %s\n", spew.Sdump(sig)) + + if !key.Verify(data, sig) { + t.Fail() + } + + AssertStructCorrectness(t, key, dsa.Private, "secp384r1") + CheckFullKeyFileObjects(t, Config, key, "NewEC") + + ClearSingleTestKey(t, fmt.Sprintf("%s/ec/%s", Config.GetString("paths.keys"), + key.GetAttributes().FilePointer())) + }) + + t.Run("sign:verify:secp521r1", func(t *testing.T) { + t.Parallel() + + key, err := NewEC(Config, "sign/verify:test3", "secp521r1") + if err != nil { + t.Fail() + } + + sig, err := key.Sign(data) + if err != nil { + t.Fail() + } + + t.Logf("Signature (secp521r1): %s\n", spew.Sdump(sig)) + + if !key.Verify(data, sig) { + t.Fail() + } + + AssertStructCorrectness(t, key, dsa.Private, "secp521r1") + CheckFullKeyFileObjects(t, Config, key, "NewEC") + + ClearSingleTestKey(t, fmt.Sprintf("%s/ec/%s", Config.GetString("paths.keys"), + key.GetAttributes().FilePointer())) + }) +} + +// +// +// +// +// +// TestImportPublicEC checks that each type of cure can be sucessfully imported +func TestImportPublicEC(t *testing.T) { + t.Parallel() + + t.Run("import:prime256v1", func(t *testing.T) { + t.Parallel() + + pub, err := helpers.NewFile("../../../data/keys/ecdsa/prime256v1-pubkey.pem") + if err != nil { + t.Fail() + } + + k, e := ImportPublicEC(Config, "prime256v1-name", "prime256v1", pub.GetBody()) + if e != nil { + t.Fatal(e) + } + + AssertStructCorrectness(t, k, dsa.Public, "prime256v1") + ClearSingleTestKey(t, fmt.Sprintf("%s/ec/%s", Config.GetString("paths.keys"), + k.GetAttributes().FilePointer())) + }) + + t.Run("import:secp384r1", func(t *testing.T) { + t.Parallel() + + pub, err := helpers.NewFile("../../../data/keys/ecdsa/secp384r1-pubkey.pem") + if err != nil { + t.Fail() + } + + k, e := ImportPublicEC(Config, "secp384r1-name", "secp384r1", pub.GetBody()) + if e != nil { + t.Fatal(e) + } + + AssertStructCorrectness(t, k, dsa.Public, "secp384r1") + ClearSingleTestKey(t, fmt.Sprintf("%s/ec/%s", Config.GetString("paths.keys"), + k.GetAttributes().FilePointer())) + }) + + t.Run("import:secp521r1", func(t *testing.T) { + t.Parallel() + + pub, err := helpers.NewFile("../../../data/keys/ecdsa/secp521r1-pubkey.pem") + if err != nil { + t.Fail() + } + + k, e := ImportPublicEC(Config, "secp521r1-name", "secp521r1", pub.GetBody()) + if e != nil { + t.Fatal(e) + } + + AssertStructCorrectness(t, k, dsa.Public, "secp521r1") + ClearSingleTestKey(t, fmt.Sprintf("%s/ec/%s", Config.GetString("paths.keys"), + k.GetAttributes().FilePointer())) + }) +} + +func TestVerifyExternalSignature(t *testing.T) { + t.Parallel() + + fccn := func(curve, pubKeyF, signFile string) bool { + pub, err := helpers.NewFile(pubKeyF) + if err != nil { + t.Fail() + } + + k, e := ImportPublicEC(Config, "ext", curve, pub.GetBody()) + if e != nil { + t.Fatal(e) + } + + sig, err := helpers.NewFile(signFile) + if err != nil { + t.Fail() + } + + AssertStructCorrectness(t, k, dsa.Public, "prime256v1") + ClearSingleTestKey(t, fmt.Sprintf("%s/ec/%s", Config.GetString("paths.keys"), + k.GetAttributes().FilePointer())) + + return k.Verify([]byte("hello"), sig.GetBody()) + } + + t.Run("prime256v1", func(t *testing.T) { + t.Parallel() + + pubKeyFile := "../../../data/keys/ecdsa/prime256v1-pubkey.pem" + signatureF := "../../../data/signatures/ecdsa/prime256v1-sha256.der" + + if !fccn("prime256v1", pubKeyFile, signatureF) { + t.Fail() + } + }) + + t.Run("secp384r1", func(t *testing.T) { + t.Parallel() + + pubKeyFile := "../../../data/keys/ecdsa/secp384r1-pubkey.pem" + signatureF := "../../../data/signatures/ecdsa/secp384r1-sha256.der" - fmt.Println(k) + if !fccn("secp384r1", pubKeyFile, signatureF) { + t.Fail() + } + }) - // if !reflect.DeepEqual(k, obj) { - // t.Fatalf("structs don't equal?") - // } + t.Run("secp521r1", func(t *testing.T) { + t.Parallel() + pubKeyFile := "../../../data/keys/ecdsa/secp521r1-pubkey.pem" + signatureF := "../../../data/signatures/ecdsa/secp521r1-sha256.der" + if !fccn("secp521r1", pubKeyFile, signatureF) { + t.Fail() + } + }) } diff --git a/services/dsa/ecdsa/encodings/encodings.go b/services/dsa/ecdsa/encodings/encodings.go index 1b9c52c..af9bb89 100644 --- a/services/dsa/ecdsa/encodings/encodings.go +++ b/services/dsa/ecdsa/encodings/encodings.go @@ -11,7 +11,7 @@ import ( "os" "strings" - "github.com/amanelis/core-zero/crypto" + "github.com/block27/core-zero/crypto" "golang.org/x/crypto/ssh" ) diff --git a/services/dsa/ecdsa/encodings/encodings_test.go b/services/dsa/ecdsa/encodings/encodings_test.go index 7ab6a21..de13eab 100644 --- a/services/dsa/ecdsa/encodings/encodings_test.go +++ b/services/dsa/ecdsa/encodings/encodings_test.go @@ -3,7 +3,7 @@ package encodings import ( "testing" - "github.com/amanelis/core-zero/helpers" + "github.com/block27/core-zero/helpers" ) // A keypair for NIST P-256 / secp256r1 diff --git a/services/dsa/ecdsa/helpers_test.go b/services/dsa/ecdsa/helpers_test.go index 8fb7419..712de49 100644 --- a/services/dsa/ecdsa/helpers_test.go +++ b/services/dsa/ecdsa/helpers_test.go @@ -5,8 +5,8 @@ import ( "os" "testing" - "github.com/amanelis/core-zero/config" - "github.com/amanelis/core-zero/helpers" + "github.com/block27/core-zero/config" + "github.com/block27/core-zero/helpers" "github.com/stretchr/testify/assert" ) diff --git a/services/dsa/ecdsa/service.go b/services/dsa/ecdsa/service.go index 1f1dc24..5010f34 100644 --- a/services/dsa/ecdsa/service.go +++ b/services/dsa/ecdsa/service.go @@ -18,14 +18,14 @@ import ( "sync" "time" - "github.com/amanelis/core-zero/config" - "github.com/amanelis/core-zero/crypto" - "github.com/amanelis/core-zero/helpers" - api "github.com/amanelis/core-zero/services/dsa" - enc "github.com/amanelis/core-zero/services/dsa/ecdsa/encodings" - mar "github.com/amanelis/core-zero/services/dsa/ecdsa/marshall" - eer "github.com/amanelis/core-zero/services/dsa/errors" - sig "github.com/amanelis/core-zero/services/dsa/signature" + "github.com/block27/core-zero/config" + "github.com/block27/core-zero/crypto" + "github.com/block27/core-zero/helpers" + api "github.com/block27/core-zero/services/dsa" + enc "github.com/block27/core-zero/services/dsa/ecdsa/encodings" + mar "github.com/block27/core-zero/services/dsa/ecdsa/marshall" + eer "github.com/block27/core-zero/services/dsa/errors" + sig "github.com/block27/core-zero/services/dsa/signature" guuid "github.com/google/uuid" "github.com/jedib0t/go-pretty/table" @@ -35,6 +35,7 @@ import ( // KeyAPI main api for defining Key behavior and functions type KeyAPI interface { FilePointer() string + KeyID() guuid.UUID Struct() *key getArtSignature() string @@ -141,7 +142,7 @@ func GetECDSA(c config.Reader, fp string) (KeyAPI, error) { data, err := helpers.ReadFile(fmt.Sprintf("%s/obj.bin", dirPath)) if err != nil { - return (*key)(nil), eer.NewKeyObjtError("invalid key objt") + return (*key)(nil), eer.NewKeyObjtError("invalid key obj") } obj, err := keyFromGOB64(data) @@ -313,6 +314,11 @@ func (k *key) writeToFS(c config.Reader, pri *ecdsa.PrivateKey, pub *ecdsa.Publi return nil } +// KeyID ... +func (k *key) KeyID() guuid.UUID { + return k.GID +} + // FilePointer returns a string that will represent the path the key can be // written to on the file system func (k *key) FilePointer() string { diff --git a/services/dsa/ecdsa/service_test.go b/services/dsa/ecdsa/service_test.go index e2f0205..94426f3 100644 --- a/services/dsa/ecdsa/service_test.go +++ b/services/dsa/ecdsa/service_test.go @@ -10,13 +10,14 @@ import ( "regexp" "testing" + "github.com/davecgh/go-spew/spew" "github.com/stretchr/testify/assert" - "github.com/amanelis/core-zero/config" - "github.com/amanelis/core-zero/helpers" - "github.com/amanelis/core-zero/test" + "github.com/block27/core-zero/config" + "github.com/block27/core-zero/helpers" + "github.com/block27/core-zero/test" - enc "github.com/amanelis/core-zero/services/dsa/ecdsa/encodings" + enc "github.com/block27/core-zero/services/dsa/ecdsa/encodings" ) var Config config.Reader @@ -46,6 +47,8 @@ func init() { panic(err) } + spew.Dump(k1) + Key = k1.Struct() Config = c } @@ -59,110 +62,121 @@ func TestNewECDSABlank(t *testing.T) { AssertStructNilness(t, k) } -func TestImportPublicECDSA256v1(t *testing.T) { - pub, err := helpers.NewFile("../../../data/keys/ecdsa/prime256v1-pubkey.pem") - if err != nil { - t.Fail() - } +func TestImportPublicECDSA(t *testing.T) { + t.Parallel() - k1, e := ImportPublicECDSA(Config, "prime256v1-name", "prime256v1", pub.GetBody()) - if e != nil { - t.Fail() - } + t.Run("import:prime256v1", func(t *testing.T) { + t.Parallel() - AssertStructCorrectness(t, k1, "PublicKey", "prime256v1") + pub, err := helpers.NewFile("../../../data/keys/ecdsa/prime256v1-pubkey.pem") + if err != nil { + t.Fail() + } - if k1.Struct().Name != "prime256v1-name" { - t.Fail() - } + // Check validity / only should be done in 1 test case + // ------------------------------------------------------------------------- + // Empty "name" + _, e := ImportPublicECDSA(Config, "", "secp521r1", pub.GetBody()) + if e == nil { + t.Fatal(e) + } - if k1.Struct().KeyType != "ecdsa.PublicKey <==> prime256v1" { - t.Fail() - } + // Empty "curve" + _, r := ImportPublicECDSA(Config, "some-name", "", pub.GetBody()) + if r == nil { + t.Fatal(r) + } - t.Logf("successfully imported [prime256v1-pubkey] [%s]", k1.FilePointer()) + // Invalid curve + _, p := ImportPublicECDSA(Config, "some-name", "junk1024r1", pub.GetBody()) + if p == nil { + t.Fatal(p) + } - ClearSingleTestKey(t, fmt.Sprintf("%s/ecdsa/%s", Config.GetString("paths.keys"), - k1.FilePointer())) -} + // Invalid pub + _, j := ImportPublicECDSA(Config, "some-name", "secp521r1", []byte("junk...")) + if j == nil { + t.Fatal(j) + } -func TestImportPublicECDSA384r1(t *testing.T) { - pub, err := helpers.NewFile("../../../data/keys/ecdsa/secp384r1-pubkey.pem") - if err != nil { - t.Fail() - } + k1, e := ImportPublicECDSA(Config, "prime256v1-name", "prime256v1", pub.GetBody()) + if e != nil { + t.Fail() + } - k1, e := ImportPublicECDSA(Config, "secp384r1-name", "secp384r1", pub.GetBody()) - if e != nil { - t.Fail() - } + AssertStructCorrectness(t, k1, "PublicKey", "prime256v1") - AssertStructCorrectness(t, k1, "PublicKey", "secp384r1") + if k1.Struct().Name != "prime256v1-name" { + t.Fail() + } - if k1.Struct().Name != "secp384r1-name" { - t.Fail() - } + if k1.Struct().KeyType != "ecdsa.PublicKey <==> prime256v1" { + t.Fail() + } - if k1.Struct().KeyType != "ecdsa.PublicKey <==> secp384r1" { - t.Fail() - } + t.Logf("successfully imported [prime256v1-pubkey] [%s]", k1.FilePointer()) - t.Logf("successfully imported [secp384r1-pubkey] [%s]", k1.FilePointer()) + ClearSingleTestKey(t, fmt.Sprintf("%s/ecdsa/%s", Config.GetString("paths.keys"), + k1.FilePointer())) + }) - ClearSingleTestKey(t, fmt.Sprintf("%s/ecdsa/%s", Config.GetString("paths.keys"), - k1.FilePointer())) -} + t.Run("import:secp384r1", func(t *testing.T) { + t.Parallel() -func TestImportPublicECDSA512r1(t *testing.T) { - pub, err := helpers.NewFile("../../../data/keys/ecdsa/secp521r1-pubkey.pem") - if err != nil { - t.Fail() - } + pub, err := helpers.NewFile("../../../data/keys/ecdsa/secp384r1-pubkey.pem") + if err != nil { + t.Fail() + } - // Empty "name" - _, e := ImportPublicECDSA(Config, "", "secp521r1", pub.GetBody()) - if e == nil { - t.Fatal(e) - } + k1, e := ImportPublicECDSA(Config, "secp384r1-name", "secp384r1", pub.GetBody()) + if e != nil { + t.Fail() + } - // Empty "curve" - _, r := ImportPublicECDSA(Config, "some-name", "", pub.GetBody()) - if r == nil { - t.Fatal(r) - } + AssertStructCorrectness(t, k1, "PublicKey", "secp384r1") - // Invalid curve - _, p := ImportPublicECDSA(Config, "some-name", "junk1024r1", pub.GetBody()) - if p == nil { - t.Fatal(p) - } + if k1.Struct().Name != "secp384r1-name" { + t.Fail() + } - // Invalid pub - _, j := ImportPublicECDSA(Config, "some-name", "secp521r1", []byte("junk...")) - if j == nil { - t.Fatal(j) - } + if k1.Struct().KeyType != "ecdsa.PublicKey <==> secp384r1" { + t.Fail() + } - // Valid key - k1, e := ImportPublicECDSA(Config, "secp521r1-name", "secp521r1", pub.GetBody()) - if e != nil { - t.Fatal(e) - } + t.Logf("successfully imported [secp384r1-pubkey] [%s]", k1.FilePointer()) + + ClearSingleTestKey(t, fmt.Sprintf("%s/ecdsa/%s", Config.GetString("paths.keys"), + k1.FilePointer())) + }) - AssertStructCorrectness(t, k1, "PublicKey", "secp521r1") + t.Run("import:secp521r1", func(t *testing.T) { + t.Parallel() - if k1.Struct().Name != "secp521r1-name" { - t.Fatalf("k1.Struct().Name did not equal expected valud: %s", k1.Struct().Name) - } + pub, err := helpers.NewFile("../../../data/keys/ecdsa/secp521r1-pubkey.pem") + if err != nil { + t.Fail() + } - if k1.Struct().KeyType != "ecdsa.PublicKey <==> secp521r1" { - t.Fatal("invalid key type") - } + k1, e := ImportPublicECDSA(Config, "secp521r1-name", "secp521r1", pub.GetBody()) + if e != nil { + t.Fatal(e) + } - t.Logf("successfully imported [secp521r1-pubkey] [%s]", k1.FilePointer()) + AssertStructCorrectness(t, k1, "PublicKey", "secp521r1") - ClearSingleTestKey(t, fmt.Sprintf("%s/ecdsa/%s", Config.GetString("paths.keys"), - k1.FilePointer())) + if k1.Struct().Name != "secp521r1-name" { + t.Fatalf("k1.Struct().Name did not equal expected valud: %s", k1.Struct().Name) + } + + if k1.Struct().KeyType != "ecdsa.PublicKey <==> secp521r1" { + t.Fatal("invalid key type") + } + + t.Logf("successfully imported [secp521r1-pubkey] [%s]", k1.FilePointer()) + + ClearSingleTestKey(t, fmt.Sprintf("%s/ecdsa/%s", Config.GetString("paths.keys"), + k1.FilePointer())) + }) } func TestNewECDSA(t *testing.T) { @@ -504,6 +518,12 @@ func TestFilePointer(t *testing.T) { } } +func TestKeyID(t *testing.T) { + if Key.FilePointer() != Key.KeyID().String() || Key.GID != Key.KeyID() { + t.Fail() + } +} + func TestSignandVerifyHuman(t *testing.T) { msg := "hello, world" hash := sha256.Sum256([]byte(msg)) diff --git a/services/dsa/key.go b/services/dsa/key.go new file mode 100644 index 0000000..b8bd104 --- /dev/null +++ b/services/dsa/key.go @@ -0,0 +1,142 @@ +package dsa + +import ( + "bytes" + "encoding/base64" + "encoding/gob" + "fmt" + "sync" + + "os/exec" + "os/user" + "runtime" + + "github.com/block27/core-zero/helpers" + + guuid "github.com/google/uuid" +) + +// KeyAttributes is the baseline key/values needed to identify a key for system +// storage. +type KeyAttributes struct { + sink sync.Mutex // mutex to allow clean concurrent access + GID guuid.UUID // guuid for crypto identification + + // Base name passed from CLI, *not indexed + Name string + + // Slug auto generated from Haiku *not indexed + Slug string + + // Hold the base key status, {archive, active} + Status string + + // Basically the elliptic curve size of the key + KeyType string + + // Should be "public" or "private" + KeyUse string + + FingerprintMD5 string // Real fingerprint in MD5 (legacy) of the key + FingerprintSHA string // Real fingerprint in SHA256 of the key + + CreatedAt string +} + +// NewKA ... +func NewKA() *KeyAttributes { + return &KeyAttributes{ + GID: GenerateUUID(), + Slug: helpers.NewHaikunator().Haikunate(), + Status: StatusPending, + CreatedAt: helpers.CreatedAtNow(), + } +} + +// ArtSignature generates the ssh-keygen style pubkey art +func (ka *KeyAttributes) ArtSignature() string { + usr, err := user.Current() + if err != nil { + return "--- path err ---" + } + + var pyPath string + + if runtime.GOOS == "darwin" { + pyPath = fmt.Sprintf("%s/.pyenv/shims/python", usr.HomeDir) + } else if runtime.GOOS == "linux" { + pyPath = "/usr/bin/python" + } + + cmd := exec.Command( + pyPath, + "tmp/drunken_bishop.py", + "--mode", + "sha256", + ka.FingerprintSHA, + ) + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + if err := cmd.Run(); err != nil { + return "--- run err ---" + } + + if outErr := string(stderr.Bytes()); outErr != "" { + return fmt.Sprintf("--- %s ---", outErr) + } + + return string(stdout.Bytes()) +} + +// FilePointer returns the objects FilePointer, important in locating the +// key files +func (ka *KeyAttributes) FilePointer() string { + return ka.GID.String() +} + +// KeyID ... +func (ka *KeyAttributes) KeyID() guuid.UUID { + return ka.GID +} + +// KAToGOB64 takes a pointer to an existing key and return it's entire body +// object base64 encoded for storage. +func KAToGOB64(ka *KeyAttributes) (string, error) { + b := bytes.Buffer{} + e := gob.NewEncoder(&b) + + if err := e.Encode(ka); err != nil { + return "", err + } + + return base64.StdEncoding.EncodeToString(b.Bytes()), nil +} + +// KAFromGOB64 takes a base64 encoded string and convert that to an object. We +// need a way to handle updates here. +func KAFromGOB64(str string) (*KeyAttributes, error) { + by, err := base64.StdEncoding.DecodeString(str) + if err != nil { + return (*KeyAttributes)(nil), err + } + + b := bytes.Buffer{} + b.Write(by) + d := gob.NewDecoder(&b) + + var ka *KeyAttributes + + if err = d.Decode(&ka); err != nil { + return (*KeyAttributes)(nil), err + } + + return ka, nil +} + +// ToString prints a helpful description of the key and it's type +func ToString(curve string, pk string) string { + return fmt.Sprintf("ec.%sKey <==> %s", pk, curve) +} diff --git a/services/dsa/key_test.go b/services/dsa/key_test.go new file mode 100644 index 0000000..8ac02b6 --- /dev/null +++ b/services/dsa/key_test.go @@ -0,0 +1,17 @@ +package dsa + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGToString(t *testing.T) { + if ToString("Prime256v1", "private") != "ec.privateKey <==> Prime256v1" { + t.Fail() + } +} + +func TestKeyID(t *testing.T) { + assert.Equal(t, "pending", NewKA().Status) +} diff --git a/services/dsa/signature/signature.go b/services/dsa/signature/signature.go index b1134e0..56a3627 100644 --- a/services/dsa/signature/signature.go +++ b/services/dsa/signature/signature.go @@ -5,7 +5,7 @@ import ( "encoding/hex" "math/big" - h "github.com/amanelis/core-zero/helpers" + h "github.com/block27/core-zero/helpers" ) // Signature - this struct is unique and must not be modified. ASN1 package diff --git a/tmp/encoded/main.go b/tmp/encoded/main.go index 08c7c01..3224598 100644 --- a/tmp/encoded/main.go +++ b/tmp/encoded/main.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - "github.com/amanelis/core-zero/helpers" + "github.com/block27/core-zero/helpers" ) func main() {