Skip to content
This repository has been archived by the owner on Dec 4, 2019. It is now read-only.

Add encryption mode support #18

Merged
merged 3 commits into from
Mar 21, 2018
Merged
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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ socket activation is assumed.

### Required

* `-command string`: the command to retrieve the key encryption key
* `--command string`: the command to retrieve the key encryption key
Copy link
Collaborator

Choose a reason for hiding this comment

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

$ ./kms -help
Usage of ./kms:
  -command string
    	the command to retrieve the key encryption key
  -endpoint string
    	the listen address (ex. unix:///tmp/kms.sock)
  -timeout duration
    	maximum time to cache KEK locally (default 1h0m0s)

Can you clarify the difference between single and double dash in this case?

Copy link
Owner Author

Choose a reason for hiding this comment

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

There is none (both do the same exact thing). I just really prefer -- (and it matches how kubectl, oc, etc print their flags).

Copy link
Owner Author

Choose a reason for hiding this comment

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

I can see if there is a simple way to override how the flags print so they have double dash.

Copy link
Owner Author

Choose a reason for hiding this comment

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

#20 to track


### Optional

* `-endpoint string`: the listen address (ex. unix:///tmp/kms.sock)
* `--endpoint string`: the listen address (ex. `unix:///tmp/kms.sock`)

* `-timeout duration`: maximum time to cache KEK locally (default 1h0m0s)
* `--timeout duration`: maximum time to cache KEK locally (default 1h)

* `--mode string`: encryption mode to use, the options are \[aescbc\] (default "aescbc")

## Crypto Details

Expand Down
7 changes: 4 additions & 3 deletions pkg/cmd/kms/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package kms

import (
"github.com/enj/kms/api/v1beta1"
"github.com/enj/kms/pkg/encryption"
"github.com/enj/kms/pkg/encryption/prefix"
"github.com/enj/kms/pkg/kek"
"github.com/enj/kms/pkg/kms"

Expand All @@ -21,12 +21,13 @@ func Execute() error {
}
defer cmdKEK.Stop()

aesService, err := encryption.NewAESCBCService(cmdKEK)
encryptionService, err := opts.mode.Handler(cmdKEK)
if err != nil {
return err
}
encryptionService = prefix.NewPrefixEncryption(opts.mode, encryptionService)

kmService := kms.NewKeyManagementService(aesService)
kmService := kms.NewKeyManagementService(encryptionService)

var serverOptions []grpc.ServerOption // TODO see if we need any server options
grpcServer := grpc.NewServer(serverOptions...)
Expand Down
35 changes: 33 additions & 2 deletions pkg/cmd/kms/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import (
"fmt"
"net"
"net/url"
"time"
"os"
"time"

"github.com/enj/kms/pkg/encryption"
"github.com/enj/kms/pkg/encryption/aes"
)

const (
Expand All @@ -22,19 +25,32 @@ type arguments struct {
endpoint string
command string
timeout time.Duration
mode string
}

type options struct {
listener net.Listener
command string
timeout time.Duration
mode encryption.EncryptionMode
}

var args = &arguments{}
var (
args = &arguments{}

encryptionModes = []encryption.EncryptionMode{
{
Name: "aescbc",
Version: "v1",
Handler: aes.NewAESCBCService,
},
}
)

func init() {
flag.StringVar(&args.endpoint, "endpoint", "", `the listen address (ex. unix:///tmp/kms.sock)`)
flag.StringVar(&args.command, "command", "", "the command to retrieve the key encryption key")
flag.StringVar(&args.mode, "mode", encryptionModes[0].Name, fmt.Sprintf("encryption mode to use, the options are %s", encryptionModes))
flag.DurationVar(&args.timeout, "timeout", time.Hour, "maximum time to cache KEK locally")
flag.Parse()
}
Expand All @@ -53,10 +69,16 @@ func getOptions() (*options, error) {
return nil, fmt.Errorf("the minimum supported timeout is %s", minTimeout)
}

mode, err := getMode(args.mode)
if err != nil {
return nil, err
}

return &options{
listener: listener,
command: args.command,
timeout: args.timeout,
mode: mode,
}, nil
}

Expand Down Expand Up @@ -107,3 +129,12 @@ func parseEndpoint(endpoint string) (string, error) {

return u.Path, nil
}

func getMode(mode string) (encryption.EncryptionMode, error) {
for _, encryptionMode := range encryptionModes {
if encryptionMode.Name == mode {
return encryptionMode, nil
}
}
return encryption.EncryptionMode{}, fmt.Errorf("invalid mode %q, use one of %s", mode, encryptionModes)
}
107 changes: 107 additions & 0 deletions pkg/encryption/aes/cbc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package aes

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"io"

"github.com/enj/kms/pkg/encryption"
"github.com/enj/kms/pkg/kek"
)

func NewAESCBCService(kek kek.KeyEncryptionKeyService) (encryption.EncryptionService, error) {
c := &cbc{kek: kek}
if _, err := c.getBlock(); err != nil {
return nil, err
}
return c, nil
}

var (
errInvalidDataLength = errors.New("the stored data was shorter than the required size")
errInvalidBlockSize = errors.New("the stored data is not a multiple of the block size")
errInvalidPKCS7Data = errors.New("invalid PKCS7 data (empty or not padded)")
errInvalidPKCS7Padding = errors.New("invalid padding on input")
)

type cbc struct {
kek kek.KeyEncryptionKeyService
}

func (c *cbc) Decrypt(ciphertext []byte) ([]byte, error) {
block, err := c.getBlock()
if err != nil {
return nil, err
}

blockSize := aes.BlockSize
if len(ciphertext) < blockSize {
return nil, errInvalidDataLength
}
iv := ciphertext[:blockSize]
ciphertext = ciphertext[blockSize:]

if len(ciphertext)%blockSize != 0 {
return nil, errInvalidBlockSize
}

result := make([]byte, len(ciphertext))
copy(result, ciphertext)
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(result, result)

// remove and verify PKCS#7 padding for CBC
lastPadding := result[len(result)-1]
paddingSize := int(lastPadding)
size := len(result) - paddingSize
if paddingSize == 0 || paddingSize > len(result) {
return nil, errInvalidPKCS7Data
}
for i := 0; i < paddingSize; i++ {
if result[size+i] != lastPadding {
return nil, errInvalidPKCS7Padding
}
}

return result[:size], nil
}

func (c *cbc) Encrypt(plaintext []byte) ([]byte, error) {
block, err := c.getBlock()
if err != nil {
return nil, err
}

blockSize := aes.BlockSize
paddingSize := blockSize - (len(plaintext) % blockSize)
result := make([]byte, blockSize+len(plaintext)+paddingSize)
iv := result[:blockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
copy(result[blockSize:], plaintext)

// add PKCS#7 padding for CBC
copy(result[blockSize+len(plaintext):], bytes.Repeat([]byte{byte(paddingSize)}, paddingSize))

mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(result[blockSize:], result[blockSize:])
return result, nil
}

func (c *cbc) getBlock() (cipher.Block, error) {
key, err := c.kek.Get()
if err != nil {
return nil, err
}

block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

return block, nil
}
103 changes: 9 additions & 94 deletions pkg/encryption/encryption.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
package encryption

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"io"
"fmt"

"github.com/enj/kms/pkg/kek"
)
Expand All @@ -16,96 +11,16 @@ type EncryptionService interface {
Encrypt(plaintext []byte) (ciphertext []byte, err error)
}

func NewAESCBCService(kek kek.KeyEncryptionKeyService) (EncryptionService, error) {
c := &cbc{kek: kek}
if _, err := c.getBlock(); err != nil {
return nil, err
}
return c, nil
}

var (
errInvalidDataLength = errors.New("the stored data was shorter than the required size")
errInvalidBlockSize = errors.New("the stored data is not a multiple of the block size")
errInvalidPKCS7Data = errors.New("invalid PKCS7 data (empty or not padded)")
errInvalidPKCS7Padding = errors.New("invalid padding on input")
)

type cbc struct {
kek kek.KeyEncryptionKeyService
}

func (c *cbc) Decrypt(ciphertext []byte) ([]byte, error) {
block, err := c.getBlock()
if err != nil {
return nil, err
}

blockSize := aes.BlockSize
if len(ciphertext) < blockSize {
return nil, errInvalidDataLength
}
iv := ciphertext[:blockSize]
ciphertext = ciphertext[blockSize:]

if len(ciphertext)%blockSize != 0 {
return nil, errInvalidBlockSize
}
type EncryptionHandler func(kek.KeyEncryptionKeyService) (EncryptionService, error)

result := make([]byte, len(ciphertext))
copy(result, ciphertext)
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(result, result)
var _ fmt.Stringer = EncryptionMode{}

// remove and verify PKCS#7 padding for CBC
lastPadding := result[len(result)-1]
paddingSize := int(lastPadding)
size := len(result) - paddingSize
if paddingSize == 0 || paddingSize > len(result) {
return nil, errInvalidPKCS7Data
}
for i := 0; i < paddingSize; i++ {
if result[size+i] != lastPadding {
return nil, errInvalidPKCS7Padding
}
}

return result[:size], nil
}

func (c *cbc) Encrypt(plaintext []byte) ([]byte, error) {
block, err := c.getBlock()
if err != nil {
return nil, err
}

blockSize := aes.BlockSize
paddingSize := blockSize - (len(plaintext) % blockSize)
result := make([]byte, blockSize+len(plaintext)+paddingSize)
iv := result[:blockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
copy(result[blockSize:], plaintext)

// add PKCS#7 padding for CBC
copy(result[blockSize+len(plaintext):], bytes.Repeat([]byte{byte(paddingSize)}, paddingSize))

mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(result[blockSize:], result[blockSize:])
return result, nil
type EncryptionMode struct {
Name string
Version string
Handler EncryptionHandler
}

func (c *cbc) getBlock() (cipher.Block, error) {
key, err := c.kek.Get()
if err != nil {
return nil, err
}

block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

return block, nil
func (e EncryptionMode) String() string {
return e.Name
}
46 changes: 46 additions & 0 deletions pkg/encryption/prefix/prefix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package prefix

import (
"bytes"
"errors"
"strings"

"github.com/enj/kms/pkg/encryption"
)

const (
kmsName = "ck"
sep = ":"
)

func NewPrefixEncryption(mode encryption.EncryptionMode, delegate encryption.EncryptionService) encryption.EncryptionService {
modeStr := strings.Join([]string{kmsName, mode.Name, mode.Version}, sep)
return &prefixEncryption{
prefix: []byte(sep + modeStr + sep),
delegate: delegate,
}
}

var errInvalidPrefix = errors.New("invalid encryption mode prefix")

type prefixEncryption struct {
prefix []byte
delegate encryption.EncryptionService
}

func (p *prefixEncryption) Decrypt(ciphertext []byte) ([]byte, error) {
if !bytes.HasPrefix(ciphertext, p.prefix) {
return nil, errInvalidPrefix
}
return p.delegate.Decrypt(ciphertext[len(p.prefix):])
}

func (p *prefixEncryption) Encrypt(plaintext []byte) ([]byte, error) {
result, err := p.delegate.Encrypt(plaintext)
if err != nil {
return nil, err
}
prefixedData := make([]byte, len(p.prefix), len(p.prefix)+len(result))
copy(prefixedData, p.prefix)
return append(prefixedData, result...), nil
}