Skip to content

Commit

Permalink
Change vault to use GCM with counter (#178)
Browse files Browse the repository at this point in the history
* Starting converting encryption to GCM.

* Using base64 encodetostring

* Almost working..

* Encrypt - decrypt is now working.

* Encryption and password protection is working.

* Padding is no longer needed.

* Increase coverage and made sure the counter and decoding is working.

* Working on legacy decrypt

* Working on new password and testing the old vault decrypt.

* All works with the fall back.

* Added back the removal of GAIA check.

* Using keysize and extracted legacy code into its own file.
  • Loading branch information
Skarlso authored Mar 6, 2019
1 parent 2925ff9 commit 069a24b
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 62 deletions.
73 changes: 73 additions & 0 deletions security/legacy_vault.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package security

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"

"github.com/gaia-pipeline/gaia"
)

const (
secretCheckKey = "GAIA_CHECK_SECRET"
secretCheckValue = "!CHECK_ME!"
)

func (v *Vault) legacyDecrypt(data []byte) ([]byte, error) {
if len(data) < 1 {
gaia.Cfg.Logger.Info("the vault is empty")
return []byte{}, nil
}
paddedPassword := v.pad(v.cert)
ci := base64.URLEncoding.EncodeToString(paddedPassword)
block, err := aes.NewCipher([]byte(ci[:aes.BlockSize]))
if err != nil {
return []byte{}, err
}

decodedMsg, err := base64.URLEncoding.DecodeString(string(data))
if err != nil {
return []byte{}, err
}

if (len(decodedMsg) % aes.BlockSize) != 0 {
return []byte{}, errors.New("blocksize must be multiple of decoded message length")
}

iv := decodedMsg[:aes.BlockSize]
msg := decodedMsg[aes.BlockSize:]

cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(msg, msg)

unpadMsg, err := v.unpad(msg)
if err != nil {
return []byte{}, err
}

if !bytes.Contains(unpadMsg, []byte(secretCheckValue)) {
return []byte{}, errors.New("possible mistyped password")
}
return unpadMsg, nil
}

// Pad pads the src with 0x04 until block length.
func (v *Vault) pad(src []byte) []byte {
padding := aes.BlockSize - len(src)%aes.BlockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}

// Unpad removes the padding from pad.
func (v *Vault) unpad(src []byte) ([]byte, error) {
length := len(src)
unpadding := int(src[length-1])

if unpadding > length {
return nil, errors.New("possible mistyped password")
}

return src[:(length - unpadding)], nil
}
28 changes: 28 additions & 0 deletions security/testdata/ca.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDVCwxvazrjIbap
k4fP0gFThXS1xaWNucwp47w0xMCH0mOi5B1JGfHlRAJmfQOn4jqzEm+2nTzwQmR/
EN0wV5MPWg1cLTiSpDrFN9nmkJZiZiN5NROJpUPw4sozIkSBli055TSaNErf2KHF
1lylR4ZyMV313zS+hb5wPfzbSPrmey6f8eY6b1FoS/5caTGweoxk764qRm6UU25Z
cfGaTfpI3KsgN+Vot9qjSfdI7dDs9jRCf7BYCPhm0DfH9e3JBC/HyRQrbxghpjqQ
8Van3a8OIWeV/AV1BI2gTrzeCWIAhatNAfjRaEVIpTU+1KJc+bGlxUKldqviGICC
YKFnxLSTAgMBAAECggEAHS9gSrsz2/24Wk69ojiwudJkhKpI3buAPpTWKZxyi6jE
wYHiiSsmujOw6H1jzNHvHKz/5NJxkLBnuAiFZKP6n3XEssX3JA+fhXj7Pty73UsE
vQwKWybqwcsvzAV7wQzjsTS3GhDj2PqCXunY00OTJX2h05b6UMddqV60jw3WYVBq
qQ00MuZWV/rwF4Gmt28PCSnMxooLvNB6pJlH0q64prwiJoe/oEDd8yFDAIaOEDw9
5E6aPnPfCkj4pu3fraHdcGYCyLGsidS+EZ/hPxD1XL/Z0K/SU0o06jw/WV056Op2
ejcptcMXRXKdWy9+blUk1nniwBAE/dEbyssKulADAQKBgQD1KCxeq02Rr8i0cMQw
XaVYgCHzqDb6NmrE7AHAq/ouO3oGVH4a1r/ApVcp2/h8DZYWFuw7E2EoSCS9QrBk
2UB6h2zF471LE99JH9wPLhWD7LOsuAiunNzpgkysNbmBW7bJQvWiCoBja3gyJyMm
F587vEsKkFRQxh1/ElWfTK0MwQKBgQDed0MgkCSfNKB8NO7oUHGsyZPKH8b2tX+E
Yql+F19HXOrb7FlLgte0I7lB05CaT3wARZEarVsucK0bDt2rSB1E11SEJtVLYrZ2
/dSVk9aHAvDhAOtTj+KEVwDSgfj8spkt+0nCtIUUbsW1/vnO6OwxCF26G8jZNBnB
SJzX3cQSUwKBgCZcfezmY0Hrvr01dA2Zabkae7WT2d53S2e7Al8yyfgYCHUbHYx3
lBPCC4yaRhyrR5P3TEnGM4rJFy6iU9XEBQnnTQb+Ju2rk2Hu4VFixa0aCdd6CKnC
E/NaF0NPONLcFhMSLjuH5yUneOxoIWDhi2IeiaOCiB8HkTAEH2/I4L9BAoGBAL0I
ijm5QeUmSthAAmHVOUKhZrtxlRc90kUjsPI72fJBui91/cp0O+YOFPUiWNVGhQ+W
DV6lv70OcYl0cFeCx5wffOluNgAAuRsTRPh0zu2aSiRnK4+ty8S4STKWzoOrHw47
YMnZqttZ5RZousxej5R6j2n9AgXOh7P9h4jGID2RAoGAPCvBqiJco4udQsosuyt/
Z0dOjF6sSaiAnx7W2+0aKMRZOhtChlCq0SFvqTvKB7X9UvEVFSyfGEBOawiNXWDu
PHeDB1DWcqToDTKqS5V4dqS+3ZaMHjvX39YhnjLCS0c3nofVWW+Pay11cC9i8tV1
OILObT2tga2txO4JxUrs+NU=
-----END PRIVATE KEY-----
1 change: 1 addition & 0 deletions security/testdata/gaia_vault
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0xn40kNax9KkbKhUZZGYmIUZJaFDQ1ajEkyB1OgnRf6MSUhe2RK8xqlVj9mBcTMVin3_UPBlXMCrrDt5fbw0kw==
110 changes: 53 additions & 57 deletions security/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"

"github.com/gaia-pipeline/gaia"
)

const (
vaultName = ".gaia_vault"
secretCheckKey = "GAIA_CHECK_SECRET"
secretCheckValue = "!CHECK_ME!"
vaultName = ".gaia_vault"
keySize = 32
)

// VaultAPI defines a set of apis that a Vault must provide in order to be a Gaia Vault.
Expand Down Expand Up @@ -57,6 +57,8 @@ type Vault struct {
cert []byte
data map[string][]byte
sync.RWMutex
counter uint64
key []byte
}

// NewVault creates a vault which is a simple k/v storage medium with AES encryption.
Expand All @@ -82,8 +84,15 @@ func NewVault(ca CAAPI, storer VaultStorer) (*Vault, error) {
if err != nil {
return nil, err
}
if len(data) < keySize {
return nil, errors.New("key lenght should be longer than 32")
}
h := sha256.New()
h.Write(data)
sum := h.Sum(nil)
v.storer = storer
v.cert = data
v.key = sum[:keySize]
v.data = make(map[string][]byte, 0)
return v, nil
}
Expand Down Expand Up @@ -192,64 +201,70 @@ func (v *Vault) encrypt(data []byte) (string, error) {
// User has deleted all the secrets. the file will be empty.
return "", nil
}
secretCheck := fmt.Sprintf("\n%s=%s", secretCheckKey, secretCheckValue)
data = append(data, []byte(secretCheck)...)
paddedPassword := v.pad(v.cert)
ci := base64.URLEncoding.EncodeToString(paddedPassword)
block, err := aes.NewCipher([]byte(ci[:aes.BlockSize]))
key := v.key
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}

msg := v.pad(data)
ciphertext := make([]byte, aes.BlockSize+len(msg))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
v.counter++
nonce := make([]byte, 12)
binary.LittleEndian.PutUint64(nonce, v.counter)
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}

cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(msg))
finalMsg := base64.URLEncoding.EncodeToString(ciphertext)
ciphertext := aesgcm.Seal(nil, nonce, data, nil)
hexNonce := hex.EncodeToString(nonce)
hexChiperText := hex.EncodeToString(ciphertext)
content := fmt.Sprintf("%s||%s", hexNonce, hexChiperText)
finalMsg := hex.EncodeToString([]byte(content))
return finalMsg, nil
}

func (v *Vault) decrypt(data []byte) ([]byte, error) {
if len(data) < 1 {
func (v *Vault) decrypt(encodedData []byte) ([]byte, error) {
if len(encodedData) < 1 {
gaia.Cfg.Logger.Info("the vault is empty")
return []byte{}, nil
}
paddedPassword := v.pad(v.cert)
ci := base64.URLEncoding.EncodeToString(paddedPassword)
block, err := aes.NewCipher([]byte(ci[:aes.BlockSize]))
key := v.key
decodedMsg, err := hex.DecodeString(string(encodedData))
if err != nil {
if msg, err := v.legacyDecrypt(encodedData); err == nil {
return msg, nil
}
return []byte{}, err
}

decodedMsg, err := base64.URLEncoding.DecodeString(string(data))
split := strings.Split(string(decodedMsg), "||")
if len(split) < 2 {
message := fmt.Sprintln("invalid number of returned splits from data. was: ", len(split))
return []byte{}, errors.New(message)
}
nonce, err := hex.DecodeString(split[0])
if err != nil {
return []byte{}, err
}

if (len(decodedMsg) % aes.BlockSize) != 0 {
return []byte{}, errors.New("blocksize must be multiple of decoded message length")
data, err := hex.DecodeString(split[1])
if err != nil {
return []byte{}, err
}
v.counter = binary.LittleEndian.Uint64(nonce)
block, err := aes.NewCipher(key)
if err != nil {
return []byte{}, err
}

iv := decodedMsg[:aes.BlockSize]
msg := decodedMsg[aes.BlockSize:]

cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(msg, msg)

unpadMsg, err := v.unpad(msg)
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return []byte{}, err
}

if !bytes.Contains(unpadMsg, []byte(secretCheckValue)) {
return []byte{}, errors.New("possible mistyped password")
plaintext, err := aesgcm.Open(nil, nonce, []byte(data), nil)
if err != nil {
return []byte{}, err
}
return unpadMsg, nil
return plaintext, nil
}

// ParseToMap will update the Vault data map with values from
Expand Down Expand Up @@ -280,22 +295,3 @@ func (v *Vault) parseFromMap() []byte {

return bytes.Join(data, []byte("\n"))
}

// Pad pads the src with 0x04 until block length.
func (v *Vault) pad(src []byte) []byte {
padding := aes.BlockSize - len(src)%aes.BlockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}

// Unpad removes the padding from pad.
func (v *Vault) unpad(src []byte) ([]byte, error) {
length := len(src)
unpadding := int(src[length-1])

if unpadding > length {
return nil, errors.New("possible mistyped password")
}

return src[:(length - unpadding)], nil
}
Loading

0 comments on commit 069a24b

Please sign in to comment.