Add a secretbox implementation for encryption

Uses nacl/secretbox
parent 868cdeca
...@@ -371,6 +371,7 @@ staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory ...@@ -371,6 +371,7 @@ staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory
staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory
staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes
staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/identity staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/identity
staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/secretbox
staging/src/k8s.io/apiserver/pkg/util/flushwriter staging/src/k8s.io/apiserver/pkg/util/flushwriter
staging/src/k8s.io/apiserver/pkg/util/logs staging/src/k8s.io/apiserver/pkg/util/logs
staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook
......
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package secretbox transforms values for storage at rest using XSalsa20 and Poly1305.
package secretbox
import (
"crypto/rand"
"fmt"
"golang.org/x/crypto/nacl/secretbox"
"k8s.io/apiserver/pkg/storage/value"
)
// secretbox implements at rest encryption of the provided values given a 32 byte secret key.
// Uses a standard 24 byte nonce (placed at the the beginning of the cipher text) generated
// from crypto/rand. Does not perform authentication of the data at rest.
type secretboxTransformer struct {
key [32]byte
}
const nonceSize = 24
// NewSecretboxTransformer takes the given key and performs encryption and decryption on the given
// data.
func NewSecretboxTransformer(key [32]byte) value.Transformer {
return &secretboxTransformer{key: key}
}
func (t *secretboxTransformer) TransformFromStorage(data []byte, context value.Context) ([]byte, bool, error) {
if len(data) < (secretbox.Overhead + nonceSize) {
return nil, false, fmt.Errorf("the stored data was shorter than the required size")
}
var nonce [nonceSize]byte
copy(nonce[:], data[:nonceSize])
data = data[nonceSize:]
out := make([]byte, 0, len(data)-secretbox.Overhead)
result, ok := secretbox.Open(out, data, &nonce, &t.key)
if !ok {
return nil, false, fmt.Errorf("output array was not large enough for encryption")
}
return result, false, nil
}
func (t *secretboxTransformer) TransformToStorage(data []byte, context value.Context) ([]byte, error) {
var nonce [nonceSize]byte
n, err := rand.Read(nonce[:])
if err != nil {
return nil, err
}
if n != nonceSize {
return nil, fmt.Errorf("unable to read sufficient random bytes")
}
return secretbox.Seal(nonce[:], data, &nonce, &t.key), nil
}
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package secretbox
import (
"bytes"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"reflect"
"testing"
"k8s.io/apiserver/pkg/storage/value"
)
var (
key1 = [32]byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}
key2 = [32]byte{0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02}
)
func TestSecretboxKeyRotation(t *testing.T) {
testErr := fmt.Errorf("test error")
context := value.DefaultContext([]byte("authenticated_data"))
p := value.NewPrefixTransformers(testErr,
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewSecretboxTransformer(key1)},
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewSecretboxTransformer(key2)},
)
out, err := p.TransformToStorage([]byte("firstvalue"), context)
if err != nil {
t.Fatal(err)
}
if !bytes.HasPrefix(out, []byte("first:")) {
t.Fatalf("unexpected prefix: %q", out)
}
from, stale, err := p.TransformFromStorage(out, context)
if err != nil {
t.Fatal(err)
}
if stale || !bytes.Equal([]byte("firstvalue"), from) {
t.Fatalf("unexpected data: %t %q", stale, from)
}
// verify changing the context does not fails storage
// Secretbox is not currently an authenticating store
from, stale, err = p.TransformFromStorage(out, value.DefaultContext([]byte("incorrect_context")))
if err != nil {
t.Fatalf("secretbox is not authenticated")
}
// reverse the order, use the second key
p = value.NewPrefixTransformers(testErr,
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewSecretboxTransformer(key2)},
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewSecretboxTransformer(key1)},
)
from, stale, err = p.TransformFromStorage(out, context)
if err != nil {
t.Fatal(err)
}
if !stale || !bytes.Equal([]byte("firstvalue"), from) {
t.Fatalf("unexpected data: %t %q", stale, from)
}
}
func BenchmarkSecretboxRead_32_1024(b *testing.B) { benchmarkSecretboxRead(b, 32, 1024, false) }
func BenchmarkSecretboxRead_32_16384(b *testing.B) { benchmarkSecretboxRead(b, 32, 16384, false) }
func BenchmarkSecretboxRead_32_16384_Stale(b *testing.B) { benchmarkSecretboxRead(b, 32, 16384, true) }
func BenchmarkSecretboxWrite_32_1024(b *testing.B) { benchmarkSecretboxWrite(b, 32, 1024) }
func BenchmarkSecretboxWrite_32_16384(b *testing.B) { benchmarkSecretboxWrite(b, 32, 16384) }
func benchmarkSecretboxRead(b *testing.B, keyLength int, valueLength int, stale bool) {
p := value.NewPrefixTransformers(nil,
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewSecretboxTransformer(key1)},
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewSecretboxTransformer(key2)},
)
context := value.DefaultContext([]byte("authenticated_data"))
v := bytes.Repeat([]byte("0123456789abcdef"), valueLength/16)
out, err := p.TransformToStorage(v, context)
if err != nil {
b.Fatal(err)
}
// reverse the key order if stale
if stale {
p = value.NewPrefixTransformers(nil,
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewSecretboxTransformer(key1)},
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewSecretboxTransformer(key2)},
)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
from, stale, err := p.TransformFromStorage(out, context)
if err != nil {
b.Fatal(err)
}
if stale {
b.Fatalf("unexpected data: %t %q", stale, from)
}
}
b.StopTimer()
}
func benchmarkSecretboxWrite(b *testing.B, keyLength int, valueLength int) {
p := value.NewPrefixTransformers(nil,
value.PrefixTransformer{Prefix: []byte("first:"), Transformer: NewSecretboxTransformer(key1)},
value.PrefixTransformer{Prefix: []byte("second:"), Transformer: NewSecretboxTransformer(key2)},
)
context := value.DefaultContext([]byte("authenticated_data"))
v := bytes.Repeat([]byte("0123456789abcdef"), valueLength/16)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := p.TransformToStorage(v, context)
if err != nil {
b.Fatal(err)
}
}
b.StopTimer()
}
func TestRoundTrip(t *testing.T) {
lengths := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 128, 1024}
tests := []struct {
name string
context value.Context
t value.Transformer
}{
{name: "GCM 16 byte key", t: NewSecretboxTransformer(key1)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
context := tt.context
if context == nil {
context = value.DefaultContext("")
}
for _, l := range lengths {
data := make([]byte, l)
if _, err := io.ReadFull(rand.Reader, data); err != nil {
t.Fatalf("unable to read sufficient random bytes: %v", err)
}
original := append([]byte{}, data...)
ciphertext, err := tt.t.TransformToStorage(data, context)
if err != nil {
t.Errorf("TransformToStorage error = %v", err)
continue
}
result, stale, err := tt.t.TransformFromStorage(ciphertext, context)
if err != nil {
t.Errorf("TransformFromStorage error = %v", err)
continue
}
if stale {
t.Errorf("unexpected stale output")
continue
}
switch {
case l == 0:
if len(result) != 0 {
t.Errorf("Round trip failed len=%d\noriginal:\n%s\nresult:\n%s", l, hex.Dump(original), hex.Dump(result))
}
case !reflect.DeepEqual(original, result):
t.Errorf("Round trip failed len=%d\noriginal:\n%s\nresult:\n%s", l, hex.Dump(original), hex.Dump(result))
}
}
})
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment