Skip to content

Commit 81cdd26

Browse files
jenspinneysykesm
authored andcommitted
Add encryption package
[#103024166] Signed-off-by: Matthew Sykes <[email protected]>
1 parent 86e9b5d commit 81cdd26

File tree

8 files changed

+540
-0
lines changed

8 files changed

+540
-0
lines changed

encryption/crypt.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package encryption
2+
3+
import (
4+
"crypto/cipher"
5+
"errors"
6+
"fmt"
7+
"io"
8+
)
9+
10+
type Encrypted struct {
11+
Nonce []byte
12+
KeyLabel string
13+
CipherText []byte
14+
}
15+
16+
type Encryptor interface {
17+
Encrypt(plaintext []byte) (Encrypted, error)
18+
}
19+
20+
type Decryptor interface {
21+
Decrypt(encrypted Encrypted) ([]byte, error)
22+
}
23+
24+
type Cryptor interface {
25+
Encryptor
26+
Decryptor
27+
}
28+
29+
type cryptor struct {
30+
keyManager KeyManager
31+
prng io.Reader
32+
}
33+
34+
func NewCryptor(keyManager KeyManager, prng io.Reader) Cryptor {
35+
return &cryptor{
36+
keyManager: keyManager,
37+
prng: prng,
38+
}
39+
}
40+
41+
func (c *cryptor) Encrypt(plaintext []byte) (Encrypted, error) {
42+
key := c.keyManager.EncryptionKey()
43+
44+
aead, err := cipher.NewGCM(key.Block())
45+
if err != nil {
46+
return Encrypted{}, fmt.Errorf("Unable to create GCM-wrapped cipher: %q", err)
47+
}
48+
49+
nonce := make([]byte, aead.NonceSize())
50+
n, err := c.prng.Read(nonce)
51+
if err != nil {
52+
return Encrypted{}, fmt.Errorf("Unable to generate random nonce: %q", err)
53+
}
54+
if n != len(nonce) {
55+
return Encrypted{}, errors.New("Unable to generate random nonce")
56+
}
57+
58+
ciphertext := aead.Seal(nil, nonce, plaintext, nil)
59+
return Encrypted{KeyLabel: key.Label(), Nonce: nonce, CipherText: ciphertext}, nil
60+
}
61+
62+
func (d *cryptor) Decrypt(encrypted Encrypted) ([]byte, error) {
63+
key := d.keyManager.DecryptionKey(encrypted.KeyLabel)
64+
if key == nil {
65+
return nil, fmt.Errorf("Key with label %q was not found", encrypted.KeyLabel)
66+
}
67+
68+
aead, err := cipher.NewGCM(key.Block())
69+
if err != nil {
70+
return nil, fmt.Errorf("Unable to create GCM-wrapped cipher: %q", err)
71+
}
72+
73+
return aead.Open(nil, encrypted.Nonce, encrypted.CipherText, nil)
74+
}

encryption/crypt_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package encryption_test
2+
3+
import (
4+
"bytes"
5+
"crypto/des"
6+
"crypto/rand"
7+
"io"
8+
9+
"github.com/cloudfoundry-incubator/bbs/encryption"
10+
"github.com/cloudfoundry-incubator/bbs/encryption/fakes"
11+
12+
. "github.com/onsi/ginkgo"
13+
. "github.com/onsi/gomega"
14+
)
15+
16+
var _ = Describe("Crypt", func() {
17+
var cryptor encryption.Cryptor
18+
var keyManager encryption.KeyManager
19+
var prng io.Reader
20+
21+
BeforeEach(func() {
22+
key, err := encryption.NewKey("label", "pass phrase")
23+
Expect(err).NotTo(HaveOccurred())
24+
keyManager, err = encryption.NewKeyManager(key, nil)
25+
Expect(err).NotTo(HaveOccurred())
26+
prng = rand.Reader
27+
})
28+
29+
JustBeforeEach(func() {
30+
cryptor = encryption.NewCryptor(keyManager, prng)
31+
})
32+
33+
It("successfully encrypts and decrypts with a key", func() {
34+
input := []byte("some plaintext data")
35+
36+
encrypted, err := cryptor.Encrypt(input)
37+
Expect(err).NotTo(HaveOccurred())
38+
Expect(encrypted.CipherText).NotTo(HaveLen(0))
39+
Expect(encrypted.CipherText).NotTo(Equal(input))
40+
41+
plaintext, err := cryptor.Decrypt(encrypted)
42+
Expect(err).NotTo(HaveOccurred())
43+
Expect(plaintext).NotTo(HaveLen(0))
44+
Expect(plaintext).To(Equal(input))
45+
})
46+
47+
Context("when the nonce is incorrect", func() {
48+
It("fails to decrypt", func() {
49+
input := []byte("some plaintext data")
50+
51+
encrypted, err := cryptor.Encrypt(input)
52+
Expect(err).NotTo(HaveOccurred())
53+
Expect(encrypted.CipherText).NotTo(HaveLen(0))
54+
Expect(encrypted.CipherText).NotTo(Equal(input))
55+
56+
encrypted.Nonce = []byte("123456789012")
57+
58+
_, err = cryptor.Decrypt(encrypted)
59+
Expect(err).To(HaveOccurred())
60+
Expect(err).To(MatchError("cipher: message authentication failed"))
61+
})
62+
})
63+
64+
Context("when the key is not found", func() {
65+
It("fails to decrypt", func() {
66+
encrypted := encryption.Encrypted{
67+
KeyLabel: "doesnt-exist",
68+
Nonce: []byte("123456789012"),
69+
}
70+
71+
_, err := cryptor.Decrypt(encrypted)
72+
Expect(err).To(HaveOccurred())
73+
Expect(err).To(MatchError(`Key with label "doesnt-exist" was not found`))
74+
})
75+
})
76+
77+
Context("when the ciphertext is modified", func() {
78+
It("fails to decrypt", func() {
79+
input := []byte("some plaintext data")
80+
81+
encrypted, err := cryptor.Encrypt(input)
82+
Expect(err).NotTo(HaveOccurred())
83+
Expect(encrypted.CipherText).NotTo(HaveLen(0))
84+
Expect(encrypted.CipherText).NotTo(Equal(input))
85+
86+
encrypted.CipherText[0] ^= 1
87+
88+
_, err = cryptor.Decrypt(encrypted)
89+
Expect(err).To(HaveOccurred())
90+
Expect(err).To(MatchError("cipher: message authentication failed"))
91+
})
92+
})
93+
94+
Context("when the random number generator fails", func() {
95+
BeforeEach(func() {
96+
prng = bytes.NewBuffer([]byte{})
97+
})
98+
99+
It("fails to encrypt", func() {
100+
input := []byte("some plaintext data")
101+
102+
_, err := cryptor.Encrypt(input)
103+
Expect(err).To(MatchError(`Unable to generate random nonce: "EOF"`))
104+
})
105+
})
106+
107+
Context("when the random number generator fails to generate enough data", func() {
108+
BeforeEach(func() {
109+
prng = bytes.NewBufferString("goo")
110+
})
111+
112+
It("fails to encrypt", func() {
113+
input := []byte("some plaintext data")
114+
115+
_, err := cryptor.Encrypt(input)
116+
Expect(err).To(MatchError("Unable to generate random nonce"))
117+
})
118+
})
119+
120+
Context("when the encryption key is invalid", func() {
121+
var key *fakes.FakeKey
122+
123+
BeforeEach(func() {
124+
desCipher, err := des.NewCipher([]byte("12345678"))
125+
Expect(err).NotTo(HaveOccurred())
126+
127+
key = &fakes.FakeKey{}
128+
key.BlockReturns(desCipher)
129+
keyManager, err = encryption.NewKeyManager(key, nil)
130+
Expect(err).NotTo(HaveOccurred())
131+
})
132+
133+
It("returns an error", func() {
134+
input := []byte("some plaintext data")
135+
136+
_, err := cryptor.Encrypt(input)
137+
Expect(err).To(MatchError(HavePrefix("Unable to create GCM-wrapped cipher:")))
138+
})
139+
})
140+
})

encryption/encryption_suite_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package encryption_test
2+
3+
import (
4+
. "github.com/onsi/ginkgo"
5+
. "github.com/onsi/gomega"
6+
7+
"testing"
8+
)
9+
10+
func TestEncryption(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Encryption Suite")
13+
}

encryption/fakes/fake_key.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// This file was generated by counterfeiter
2+
package fakes
3+
4+
import (
5+
"crypto/cipher"
6+
"sync"
7+
8+
"github.com/cloudfoundry-incubator/bbs/encryption"
9+
)
10+
11+
type FakeKey struct {
12+
LabelStub func() string
13+
labelMutex sync.RWMutex
14+
labelArgsForCall []struct{}
15+
labelReturns struct {
16+
result1 string
17+
}
18+
BlockStub func() cipher.Block
19+
blockMutex sync.RWMutex
20+
blockArgsForCall []struct{}
21+
blockReturns struct {
22+
result1 cipher.Block
23+
}
24+
}
25+
26+
func (fake *FakeKey) Label() string {
27+
fake.labelMutex.Lock()
28+
fake.labelArgsForCall = append(fake.labelArgsForCall, struct{}{})
29+
fake.labelMutex.Unlock()
30+
if fake.LabelStub != nil {
31+
return fake.LabelStub()
32+
} else {
33+
return fake.labelReturns.result1
34+
}
35+
}
36+
37+
func (fake *FakeKey) LabelCallCount() int {
38+
fake.labelMutex.RLock()
39+
defer fake.labelMutex.RUnlock()
40+
return len(fake.labelArgsForCall)
41+
}
42+
43+
func (fake *FakeKey) LabelReturns(result1 string) {
44+
fake.LabelStub = nil
45+
fake.labelReturns = struct {
46+
result1 string
47+
}{result1}
48+
}
49+
50+
func (fake *FakeKey) Block() cipher.Block {
51+
fake.blockMutex.Lock()
52+
fake.blockArgsForCall = append(fake.blockArgsForCall, struct{}{})
53+
fake.blockMutex.Unlock()
54+
if fake.BlockStub != nil {
55+
return fake.BlockStub()
56+
} else {
57+
return fake.blockReturns.result1
58+
}
59+
}
60+
61+
func (fake *FakeKey) BlockCallCount() int {
62+
fake.blockMutex.RLock()
63+
defer fake.blockMutex.RUnlock()
64+
return len(fake.blockArgsForCall)
65+
}
66+
67+
func (fake *FakeKey) BlockReturns(result1 cipher.Block) {
68+
fake.BlockStub = nil
69+
fake.blockReturns = struct {
70+
result1 cipher.Block
71+
}{result1}
72+
}
73+
74+
var _ encryption.Key = new(FakeKey)

encryption/key.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package encryption
2+
3+
import (
4+
"crypto/aes"
5+
"crypto/cipher"
6+
"crypto/sha256"
7+
"errors"
8+
)
9+
10+
//go:generate counterfeiter . Key
11+
type Key interface {
12+
Label() string
13+
Block() cipher.Block
14+
}
15+
16+
type key struct {
17+
block cipher.Block
18+
label string
19+
}
20+
21+
func NewKey(label, phrase string) (Key, error) {
22+
if label == "" {
23+
return nil, errors.New("A key label is required")
24+
}
25+
26+
hash := sha256.Sum256([]byte(phrase))
27+
block, err := aes.NewCipher(hash[:])
28+
if err != nil {
29+
return nil, err
30+
}
31+
32+
return &key{
33+
label: label,
34+
block: block,
35+
}, nil
36+
}
37+
38+
func (k *key) Label() string {
39+
return k.label
40+
}
41+
42+
func (k *key) Block() cipher.Block {
43+
return k.block
44+
}

encryption/key_manager.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package encryption
2+
3+
import "fmt"
4+
5+
type keyManager struct {
6+
encryptionKey Key
7+
decryptionKeys map[string]Key
8+
}
9+
10+
type KeyManager interface {
11+
EncryptionKey() Key
12+
DecryptionKey(label string) Key
13+
}
14+
15+
func NewKeyManager(encryptionKey Key, decryptionKeys []Key) (KeyManager, error) {
16+
decryptionKeyMap := map[string]Key{
17+
encryptionKey.Label(): encryptionKey,
18+
}
19+
20+
for _, key := range decryptionKeys {
21+
if existingKey, ok := decryptionKeyMap[key.Label()]; ok && key != existingKey {
22+
return nil, fmt.Errorf("Multiple keys with the same label: %q", key.Label())
23+
}
24+
decryptionKeyMap[key.Label()] = key
25+
}
26+
27+
return &keyManager{
28+
encryptionKey: encryptionKey,
29+
decryptionKeys: decryptionKeyMap,
30+
}, nil
31+
}
32+
33+
func (m *keyManager) EncryptionKey() Key {
34+
return m.encryptionKey
35+
}
36+
37+
func (m *keyManager) DecryptionKey(label string) Key {
38+
return m.decryptionKeys[label]
39+
}

0 commit comments

Comments
 (0)