Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add chef-vault implementation #minor #101

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions crypto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package chef

// https://github.com/golang/go/issues/23514

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"io"
)

// NewCrypter en/decrypts data parsing using aes
func newCrypter(key []byte) (cipher.AEAD, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

return cipher.NewGCM(block)
}

func iv() ([]byte, error) {
nonce := make([]byte, 12)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
return nonce[:], nil
}

func generateSecret() *[]byte {
secret := make([]byte, 32)
rand.Read(secret)
return &secret
}

// EncryptValue data
func EncryptValue(key, iv, plaintext []byte) (tag []byte, ciphertext []byte, encryptError error) {
cipher, err := newCrypter(key)
if err != nil {
return nil, nil, err
}

// https://sourcegraph.com/github.com/golang/go@52cc9e3762171bd45368b3280554bf12a63f23b2/-/blob/src/crypto/aes/aes_gcm.go#L120-131
out := cipher.Seal(make([]byte, 0), iv, plaintext, nil)

return out[len(out)-cipher.Overhead():], out[:len(out)-cipher.Overhead()], nil
}

// DecryptValue data
func DecryptValue(key, iv, tag, ciphertext []byte) ([]byte, error) {
// https://sourcegraph.com/github.com/golang/go@52cc9e3762171bd45368b3280554bf12a63f23b2/-/blob/src/crypto/aes/aes_gcm.go#L155
cipher, err := newCrypter(key)
if err != nil {
return nil, err
}
macData := append(ciphertext, tag...)

return cipher.Open(nil, iv, macData, nil)
}

// EncodeSharedSecret encrypts secret with the the specified and key returns a base64 encoded string
func EncodeSharedSecret(key *rsa.PrivateKey, secret []byte) (string, error) {
encryptedSecret, err := rsa.EncryptPKCS1v15(rand.Reader, &key.PublicKey, secret)
if err != nil {
return "", err
}

return base64.StdEncoding.EncodeToString(encryptedSecret), nil
}

// DecodeSharedSecret returns the plaintext shared secret encrypted by the key
func DecodeSharedSecret(key *rsa.PrivateKey, encodedSecret string) ([]byte, error) {
// Decode encrypted shared secret
encryptedSecret, err := base64.StdEncoding.DecodeString(encodedSecret)
if err != nil {
return nil, err
}

// Decrypt shared secret
return rsa.DecryptPKCS1v15(rand.Reader, key, encryptedSecret)
}
81 changes: 81 additions & 0 deletions crypto_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package chef

import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"testing"
)

var aesDecryptionTests = []struct {
cipher string
data string
key string
iv string
tag string
expected string
}{
{
"aes-256-gcm",
"tg3UG+t43RfVf2hRaPEqU5Vwllu8Uw==\n",
"3f5eccf62026458b8038a06daecff4a69640ce3c5e4585aace7243d5a7f01b62",
"MQ2DVUphtxAwdTfo",
"nsH3unmSGw9hcOG3KpYXpQ==\n",
`{"json_wrapper":"bar"}`,
},
}

func TestDecrypt(t *testing.T) {
for _, dt := range aesDecryptionTests {
// auth_tag = Base64.decode64(tag)
authTag, _ := base64.StdEncoding.DecodeString(dt.tag)
// data = Base64.decode64(data)
data, _ := base64.StdEncoding.DecodeString(dt.data)
// iv = Base64.decode64(iv)
iv, _ := base64.StdEncoding.DecodeString(dt.iv)

// key = [key].pack('H*')
key, _ := hex.DecodeString(dt.key)
// key = OpenSSL::Digest::SHA256.digest(key)
cryptKey := sha256.Sum256([]byte(key))

plaintext, err := DecryptValue(cryptKey[:], iv, authTag, data)
if err != nil {
t.Fatal(err)
}

if string(plaintext) != dt.expected {
t.Fatalf("Expected: %s, Actual: %s\n", dt.expected, plaintext)
}
}
}

func TestEncrypt(t *testing.T) {
for _, dt := range aesDecryptionTests {
// auth_tag = Base64.decode64(tag)
authTag, _ := base64.StdEncoding.DecodeString(dt.tag)
// data = Base64.decode64(data)
data, _ := base64.StdEncoding.DecodeString(dt.data)
// iv = Base64.decode64(iv)
iv, _ := base64.StdEncoding.DecodeString(dt.iv)

// key = [key].pack('H*')
key, _ := hex.DecodeString(dt.key)
// key = OpenSSL::Digest::SHA256.digest(key)
cryptKey := sha256.Sum256([]byte(key))

tag, ciphertext, err := EncryptValue(cryptKey[:], iv, []byte(dt.expected))
if err != nil {
t.Fatal(err)
}

if !bytes.Equal(ciphertext, data) {
t.Fatalf("ciphertext: [expected: %q, actual: %q]\n", hex.EncodeToString(data), hex.EncodeToString(ciphertext))
}

if !bytes.Equal(tag, authTag) {
t.Fatalf("tag: [expected: %q, actual: %q]\n", hex.EncodeToString(authTag), hex.EncodeToString(tag))
}
}
}
2 changes: 2 additions & 0 deletions http.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type Client struct {
Status *StatusService
Universe *UniverseService
Users *UserService
Vaults *VaultService
}

// Config contains the configuration options for a chef client. This structure is used primarily in the NewClient() constructor in order to setup a proper client object
Expand Down Expand Up @@ -176,6 +177,7 @@ func NewClient(cfg *Config) (*Client, error) {
c.Status = &StatusService{client: c}
c.Universe = &UniverseService{client: c}
c.Users = &UserService{client: c}
c.Vaults = &VaultService{client: c}
return c, nil
}

Expand Down
7 changes: 4 additions & 3 deletions http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
"encoding/pem"
"errors"
"fmt"
. "github.com/ctdk/goiardi/chefcrypto"
. "github.com/smartystreets/goconvey/convey"
"io"
"math/big"
"net/http"
Expand All @@ -18,6 +16,9 @@ import (
"strings"
"testing"
"time"

. "github.com/ctdk/goiardi/chefcrypto"
. "github.com/smartystreets/goconvey/convey"
)

var (
Expand Down Expand Up @@ -562,7 +563,7 @@ func TestDo_badjson(t *testing.T) {
defer teardown()

mux.HandleFunc("/hashrocket", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, " pigthrusters => 100%% ")
fmt.Fprint(w, " pigthrusters => 100% ")
})

stupidData := struct{}{}
Expand Down
4 changes: 2 additions & 2 deletions node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func TestNodesService_Methods(t *testing.T) {
// test Put
putRes, err := client.Nodes.Put(node)
if err != nil {
t.Errorf("Nodes.Put returned error %+v", err)
t.Errorf("Nodes.Put returned error: %v", err)
}

if !reflect.DeepEqual(putRes, node) {
Expand All @@ -117,6 +117,6 @@ func TestNodesService_Methods(t *testing.T) {
// test Delete
err = client.Nodes.Delete(node.Name)
if err != nil {
t.Errorf("Nodes.Delete returned error %+v", err)
t.Errorf("Nodes.Delete returned error: %v", err)
}
}
8 changes: 4 additions & 4 deletions principal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ func TestPrincipalsGet(t *testing.T) {

p, err := client.Principals.Get("client_node")
if err != nil {
t.Errorf("GET principal error %+v making request: ", err)
t.Errorf("GET principal error making request: %v", err)
return
}

if p.Name != "client_node" {
t.Errorf("Unexpected principal name: %+v", p.Name)
t.Errorf("Unexpected principal name: %v", p.Name)
}
if p.Type != "client" {
t.Errorf("Unexpected principal type: %+v", p.Type)
t.Errorf("Unexpected principal type: %v", p.Type)
}
if p.PublicKey != "-----BEGIN PUBLIC KEY-----No, not really-----END PUBLIC KEY-----" {
t.Errorf("Unexpected principal public key: %+v", p.PublicKey)
t.Errorf("Unexpected principal public key: %v", p.PublicKey)
}
}
7 changes: 4 additions & 3 deletions sandbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import (
"crypto/md5"
"crypto/rand"
"fmt"
. "github.com/smartystreets/goconvey/convey"
"net/http"
_ "reflect"
"testing"

. "github.com/smartystreets/goconvey/convey"
)

// generate random data for sandbox
Expand Down Expand Up @@ -51,7 +52,7 @@ func TestSandboxesPost(t *testing.T) {
// post the new sums/files to the sandbox
_, err := client.Sandboxes.Post(sums)
if err != nil {
t.Errorf("Sandbox Post error making request: %+v", err)
t.Errorf("Sandbox Post error making request: %v", err)
}
}

Expand All @@ -76,7 +77,7 @@ func TestSandboxesPut(t *testing.T) {

sandbox, err := client.Sandboxes.Put("f1c560ccb472448e9cfb31ff98134247")
if err != nil {
t.Errorf("Sandbox Put error making request: %+v", err)
t.Errorf("Sandbox Put error making request: %v", err)
}

expected := Sandbox{
Expand Down
5 changes: 2 additions & 3 deletions search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ func TestSearch_Get(t *testing.T) {
"users": "http://localhost:4000/search/users",
}
if !reflect.DeepEqual(indexes, wantedIdx) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self. Figure out if the deleted error message is needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

t.Errorf("Search.Get returned %+v, want %+v", indexes, wantedIdx)
}
}

Expand Down Expand Up @@ -78,12 +77,12 @@ func TestSearch_ExecDo(t *testing.T) {
// for now we aren't testing the result..
_, err = query.Do(client)
if err != nil {
t.Errorf("Search.Exec failed err: %+v", err)
t.Errorf("Search.Exec failed: %v", err)
}

_, err = client.Search.Exec("nodes", "name:latte")
if err != nil {
t.Errorf("Search.Exec failed err: %+v", err)
t.Errorf("Search.Exec failed: %v", err)
}

}
Loading