Crypto library movement and changes to content helper interfaces

Signed-off-by: Derek McGowan <derek@mcgstyle.net>
This commit is contained in:
Derek McGowan
2019-07-16 14:36:42 -07:00
committed by Brandon Lum
parent bf8804c743
commit dde436e65b
29 changed files with 713 additions and 710 deletions

View File

@@ -0,0 +1,114 @@
/*
Copyright The containerd 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 blockcipher
import (
"io"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
// LayerCipherType is the ciphertype as specified in the layer metadata
type LayerCipherType string
// TODO: Should be obtained from OCI spec once included
const (
AESSIVCMAC256 LayerCipherType = "AEAD_AES_SIV_CMAC_STREAM_256"
AESSIVCMAC512 LayerCipherType = "AEAD_AES_SIV_CMAC_STREAM_512"
CipherTypeOpt string = "type"
)
// LayerBlockCipherOptions includes the information required to encrypt/decrypt
// an image
type LayerBlockCipherOptions struct {
// SymmetricKey represents the symmetric key used for encryption/decryption
// This field should be populated by Encrypt/Decrypt calls
SymmetricKey []byte `json:"symkey"`
// Digest is the digest of the original data for verification.
// This is NOT populated by Encrypt/Decrypt calls
Digest digest.Digest `json:"digest"`
// CipherOptions contains the cipher metadata used for encryption/decryption
// This field should be populated by Encrypt/Decrypt calls
CipherOptions map[string][]byte `json:"cipheroptions"`
}
// LayerBlockCipher returns a provider for encrypt/decrypt functionality
// for handling the layer data for a specific algorithm
type LayerBlockCipher interface {
// GenerateKey creates a symmetric key
GenerateKey() []byte
// Encrypt takes in layer data and returns the ciphertext and relevant LayerBlockCipherOptions
Encrypt(layerDataReader io.Reader, opt LayerBlockCipherOptions) (io.Reader, LayerBlockCipherOptions, error)
// Decrypt takes in layer ciphertext data and returns the plaintext and relevant LayerBlockCipherOptions
Decrypt(layerDataReader io.Reader, opt LayerBlockCipherOptions) (io.Reader, LayerBlockCipherOptions, error)
}
// LayerBlockCipherHandler is the handler for encrypt/decrypt for layers
type LayerBlockCipherHandler struct {
cipherMap map[LayerCipherType]LayerBlockCipher
}
// Encrypt is the handler for the layer decryption routine
func (h *LayerBlockCipherHandler) Encrypt(plainDataReader io.Reader, typ LayerCipherType) (io.Reader, LayerBlockCipherOptions, error) {
if c, ok := h.cipherMap[typ]; ok {
opt := LayerBlockCipherOptions{
SymmetricKey: c.GenerateKey(),
}
encDataReader, newopt, err := c.Encrypt(plainDataReader, opt)
if err == nil {
newopt.CipherOptions[CipherTypeOpt] = []byte(typ)
}
return encDataReader, newopt, err
}
return nil, LayerBlockCipherOptions{}, errors.Errorf("unsupported cipher type: %s", typ)
}
// Decrypt is the handler for the layer decryption routine
func (h *LayerBlockCipherHandler) Decrypt(encDataReader io.Reader, opt LayerBlockCipherOptions) (io.Reader, LayerBlockCipherOptions, error) {
typ, ok := opt.CipherOptions[CipherTypeOpt]
if !ok {
return nil, LayerBlockCipherOptions{}, errors.New("no cipher type provided")
}
if c, ok := h.cipherMap[LayerCipherType(typ)]; ok {
return c.Decrypt(encDataReader, opt)
}
return nil, LayerBlockCipherOptions{}, errors.Errorf("unsupported cipher type: %s", typ)
}
// NewLayerBlockCipherHandler returns a new default handler
func NewLayerBlockCipherHandler() (*LayerBlockCipherHandler, error) {
h := LayerBlockCipherHandler{
cipherMap: map[LayerCipherType]LayerBlockCipher{},
}
var err error
h.cipherMap[AESSIVCMAC256], err = NewAESSIVLayerBlockCipher(256)
if err != nil {
return nil, errors.Wrap(err, "unable to set up Cipher AES-SIV-CMAC-256")
}
h.cipherMap[AESSIVCMAC512], err = NewAESSIVLayerBlockCipher(512)
if err != nil {
return nil, errors.Wrap(err, "unable to set up Cipher AES-SIV-CMAC-512")
}
return &h, nil
}

View File

@@ -0,0 +1,194 @@
/*
Copyright The containerd 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 blockcipher
import (
"crypto/rand"
"fmt"
"io"
miscreant "github.com/miscreant/miscreant-go"
"github.com/pkg/errors"
)
// AESSIVLayerBlockCipher implements the AES SIV block cipher
type AESSIVLayerBlockCipher struct {
keylen int // in bytes
reader io.Reader
encryptor *miscreant.StreamEncryptor
decryptor *miscreant.StreamDecryptor
err error // error that occurred during operation
eof bool // hit EOF in the input data
toread int // how many bytes to read in one chunk
inbuffer []byte // input buffer with data from reader
inoffset int64 // offset where to read from next
outbuffer []byte // output buffer to return to user
outoffset int // offset in output buffer
outsize int64 // output size
}
type aessivcryptor struct {
bc *AESSIVLayerBlockCipher
outputReader io.Reader
}
// NewAESSIVLayerBlockCipher returns a new AES SIV block cipher of 256 or 512 bits
func NewAESSIVLayerBlockCipher(bits int) (LayerBlockCipher, error) {
if bits != 256 && bits != 512 {
return nil, errors.New("AES SIV bit count not supported")
}
return &AESSIVLayerBlockCipher{keylen: bits / 8}, nil
}
func (r *aessivcryptor) Read(p []byte) (int, error) {
if r.bc.err != nil {
return 0, r.bc.err
}
for {
// return data if we have any
if r.bc.outbuffer != nil && r.bc.outoffset < len(r.bc.outbuffer) {
n := copy(p, r.bc.outbuffer[r.bc.outoffset:])
r.bc.outoffset += n
return n, nil
}
// no data and hit eof before?
if r.bc.eof {
return 0, io.EOF
}
// read new data; we expect to get r.bc.toread number of bytes
// for anything less we assume it's EOF
numbytes := 0
for numbytes < r.bc.toread {
var n int
n, r.bc.err = r.bc.reader.Read(r.bc.inbuffer[numbytes:r.bc.toread])
numbytes += n
if r.bc.err != nil {
if r.bc.err == io.EOF {
r.bc.eof = true
r.bc.err = nil
break
} else {
return 0, r.bc.err
}
}
if n == 0 {
break
}
}
if numbytes < r.bc.toread {
r.bc.eof = true
}
r.bc.inoffset += int64(numbytes)
// transform the data
if r.bc.encryptor != nil {
r.bc.outbuffer = r.bc.encryptor.Seal(nil, r.bc.inbuffer[:numbytes], []byte(""), r.bc.eof)
} else {
r.bc.outbuffer, r.bc.err = r.bc.decryptor.Open(nil, r.bc.inbuffer[:numbytes], []byte(""), r.bc.eof)
if r.bc.err != nil {
return 0, r.bc.err
}
}
// let reader start from beginning of buffer
r.bc.outoffset = 0
r.bc.outsize += int64(len(r.bc.outbuffer))
}
}
// init initializes an instance
func (bc *AESSIVLayerBlockCipher) init(encrypt bool, reader io.Reader, opt LayerBlockCipherOptions) (LayerBlockCipherOptions, error) {
var (
err error
se miscreant.StreamEncryptor
)
bc.reader = reader
key := opt.SymmetricKey
if len(key) != bc.keylen {
return LayerBlockCipherOptions{}, fmt.Errorf("invalid key length of %d bytes; need %d bytes", len(key), bc.keylen)
}
nonce := opt.CipherOptions["nonce"]
if len(nonce) == 0 {
nonce = make([]byte, se.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return LayerBlockCipherOptions{}, errors.Wrap(err, "unable to generate random nonce")
}
}
bc.inbuffer = make([]byte, 1024*1024)
bc.toread = len(bc.inbuffer)
bc.inoffset = 0
bc.outbuffer = nil
bc.outoffset = 0
bc.eof = false
bc.err = nil
bc.outsize = 0
if encrypt {
bc.encryptor, err = miscreant.NewStreamEncryptor("AES-SIV", key, nonce)
if err != nil {
return LayerBlockCipherOptions{}, errors.Wrap(err, "unable to create AES-SIV stream encryptor")
}
bc.toread -= bc.encryptor.Overhead()
bc.decryptor = nil
} else {
bc.decryptor, err = miscreant.NewStreamDecryptor("AES-SIV", key, nonce)
if err != nil {
return LayerBlockCipherOptions{}, errors.Wrap(err, "unable to create AES-SIV stream decryptor")
}
bc.encryptor = nil
}
lbco := LayerBlockCipherOptions{
SymmetricKey: key,
CipherOptions: map[string][]byte{
"nonce": nonce,
},
}
return lbco, nil
}
// GenerateKey creates a synmmetric key
func (bc *AESSIVLayerBlockCipher) GenerateKey() []byte {
return miscreant.GenerateKey(bc.keylen)
}
// Encrypt takes in layer data and returns the ciphertext and relevant LayerBlockCipherOptions
func (bc *AESSIVLayerBlockCipher) Encrypt(plainDataReader io.Reader, opt LayerBlockCipherOptions) (io.Reader, LayerBlockCipherOptions, error) {
lbco, err := bc.init(true, plainDataReader, opt)
if err != nil {
return nil, LayerBlockCipherOptions{}, err
}
return &aessivcryptor{bc, nil}, lbco, nil
}
// Decrypt takes in layer ciphertext data and returns the plaintext and relevant LayerBlockCipherOptions
func (bc *AESSIVLayerBlockCipher) Decrypt(encDataReader io.Reader, opt LayerBlockCipherOptions) (io.Reader, LayerBlockCipherOptions, error) {
lbco, err := bc.init(false, encDataReader, opt)
if err != nil {
return nil, LayerBlockCipherOptions{}, err
}
return &aessivcryptor{bc, nil}, lbco, nil
}

View File

@@ -0,0 +1,163 @@
/*
Copyright The containerd 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 blockcipher
import (
"bytes"
_ "crypto/sha256"
"io"
"testing"
)
func TestBlockCipherAesSivCreateValid(t *testing.T) {
_, err := NewAESSIVLayerBlockCipher(256)
if err != nil {
t.Fatal(err)
}
_, err = NewAESSIVLayerBlockCipher(512)
if err != nil {
t.Fatal(err)
}
}
func TestBlockCipherAesSivCreateInvalid(t *testing.T) {
_, err := NewAESSIVLayerBlockCipher(8)
if err == nil {
t.Fatal("Test should have failed due to invalid cipher size")
}
_, err = NewAESSIVLayerBlockCipher(255)
if err == nil {
t.Fatal("Test should have failed due to invalid cipher size")
}
}
func TestBlockCipherAesSivEncryption(t *testing.T) {
var (
symKey = []byte("01234567890123456789012345678912")
opt = LayerBlockCipherOptions{
SymmetricKey: symKey,
}
layerData = []byte("this is some data")
)
bc, err := NewAESSIVLayerBlockCipher(256)
if err != nil {
t.Fatal(err)
}
layerDataReader := bytes.NewReader(layerData)
ciphertextReader, lbco, err := bc.Encrypt(layerDataReader, opt)
if err != nil {
t.Fatal(err)
}
// Use a different instantiated object to indicate an invocation at a diff time
bc2, err := NewAESSIVLayerBlockCipher(256)
if err != nil {
t.Fatal(err)
}
ciphertext := make([]byte, 1024)
encsize, err := ciphertextReader.Read(ciphertext)
if err != nil {
t.Fatal(err)
}
ciphertextTestReader := bytes.NewReader(ciphertext[:encsize])
plaintextReader, _, err := bc2.Decrypt(ciphertextTestReader, lbco)
if err != nil {
t.Fatal(err)
}
plaintext := make([]byte, 1024)
size, err := plaintextReader.Read(plaintext)
if err != nil && err != io.EOF {
t.Fatal(err)
}
if string(plaintext[:size]) != string(layerData) {
t.Fatalf("expected %q, got %q", layerData, plaintext[:size])
}
}
func TestBlockCipherAesSivEncryptionInvalidKey(t *testing.T) {
var (
symKey = []byte("01234567890123456789012345678912")
opt = LayerBlockCipherOptions{
SymmetricKey: symKey,
}
layerData = []byte("this is some data")
)
bc, err := NewAESSIVLayerBlockCipher(256)
if err != nil {
t.Fatal(err)
}
layerDataReader := bytes.NewReader(layerData)
ciphertextReader, lbco, err := bc.Encrypt(layerDataReader, opt)
if err != nil {
t.Fatal(err)
}
// Use a different instantiated object to indicate an invokation at a diff time
bc2, err := NewAESSIVLayerBlockCipher(256)
if err != nil {
t.Fatal(err)
}
lbco.SymmetricKey = []byte("aaa34567890123456789012345678912")
ciphertext := make([]byte, 1024)
encsize, err := ciphertextReader.Read(ciphertext)
if err != nil {
t.Fatal(err)
}
ciphertextTestReader := bytes.NewReader(ciphertext[:encsize])
plaintextReader, _, err := bc2.Decrypt(ciphertextTestReader, lbco)
if err != nil {
t.Fatal(err)
}
plaintext := make([]byte, 1024)
_, err = plaintextReader.Read(plaintext)
if err == nil {
t.Fatal("Read() should have failed due to wrong key")
}
}
func TestBlockCipherAesSivEncryptionInvalidKeyLength(t *testing.T) {
var (
symKey = []byte("012345")
opt = LayerBlockCipherOptions{
SymmetricKey: symKey,
}
layerData = []byte("this is some data")
)
bc, err := NewAESSIVLayerBlockCipher(256)
if err != nil {
t.Fatal(err)
}
layerDataReader := bytes.NewReader(layerData)
_, _, err = bc.Encrypt(layerDataReader, opt)
if err == nil {
t.Fatal("Test should have failed due to invalid key length")
}
}

View File

@@ -0,0 +1,115 @@
/*
Copyright The containerd 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 blockcipher
import (
"bytes"
"io"
"testing"
)
func TestBlockCipherHandlerCreate(t *testing.T) {
_, err := NewLayerBlockCipherHandler()
if err != nil {
t.Fatal(err)
}
}
func TestBlockCipherEncryption(t *testing.T) {
var (
layerData = []byte("this is some data")
)
h, err := NewLayerBlockCipherHandler()
if err != nil {
t.Fatal(err)
}
layerDataReader := bytes.NewReader(layerData)
ciphertextReader, lbco, err := h.Encrypt(layerDataReader, AESSIVCMAC256)
if err != nil {
t.Fatal(err)
}
ciphertext := make([]byte, 1024)
encsize, err := ciphertextReader.Read(ciphertext)
if err != nil && err != io.EOF {
t.Fatal("Reading the ciphertext should not have failed")
}
ciphertextTestReader := bytes.NewReader(ciphertext[:encsize])
// Use a different instantiated object to indicate an invokation at a diff time
plaintextReader, _, err := h.Decrypt(ciphertextTestReader, lbco)
if err != nil {
t.Fatal(err)
}
plaintext := make([]byte, 1024)
decsize, err := plaintextReader.Read(plaintext)
if err != nil && err != io.EOF {
t.Fatal("Read the plaintext should not have failed")
}
if string(plaintext[:decsize]) != string(layerData) {
t.Fatal("Decrypted data is incorrect")
}
}
func TestBlockCipherEncryptionInvalidKey(t *testing.T) {
var (
layerData = []byte("this is some data")
)
h, err := NewLayerBlockCipherHandler()
if err != nil {
t.Fatal(err)
}
layerDataReader := bytes.NewReader(layerData)
ciphertextReader, lbco, err := h.Encrypt(layerDataReader, AESSIVCMAC512)
if err != nil {
t.Fatal(err)
}
// Use a different instantiated object to indicate an invokation at a diff time
bc2, err := NewAESSIVLayerBlockCipher(512)
if err != nil {
t.Fatal(err)
}
lbco.SymmetricKey = []byte("aaa3456789012345678901234567890123456789012345678901234567890123")
ciphertext := make([]byte, 1024)
encsize, err := ciphertextReader.Read(ciphertext)
if err != nil {
t.Fatal(err)
}
ciphertextTestReader := bytes.NewReader(ciphertext[:encsize])
plaintextReader, _, err := bc2.Decrypt(ciphertextTestReader, lbco)
if err != nil {
t.Fatal(err)
}
plaintext := make([]byte, 1024)
_, err = plaintextReader.Read(plaintext)
if err == nil {
t.Fatal("Read() should have failed due to wrong key")
}
}