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")
}
}

View File

@@ -0,0 +1,40 @@
/*
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 config
// EncryptConfig is the container image PGP encryption configuration holding
// the identifiers of those that will be able to decrypt the container and
// the PGP public keyring file data that contains their public keys.
type EncryptConfig struct {
// map holding 'gpg-recipients', 'gpg-pubkeyringfile', 'pubkeys', 'x509s'
Parameters map[string][][]byte
DecryptConfig DecryptConfig
}
// DecryptConfig wraps the Parameters map that holds the decryption key
type DecryptConfig struct {
// map holding 'privkeys', 'x509s', 'gpg-privatekeys'
Parameters map[string][][]byte
}
// CryptoConfig is a common wrapper for EncryptConfig and DecrypConfig that can
// be passed through functions that share much code for encryption and decryption
type CryptoConfig struct {
EncryptConfig *EncryptConfig
DecryptConfig *DecryptConfig
}

View File

@@ -0,0 +1,244 @@
/*
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 encryption
import (
"encoding/base64"
"encoding/json"
"io"
"strings"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/pkg/encryption/blockcipher"
"github.com/containerd/containerd/pkg/encryption/config"
"github.com/containerd/containerd/pkg/encryption/keywrap"
"github.com/containerd/containerd/pkg/encryption/keywrap/jwe"
"github.com/containerd/containerd/pkg/encryption/keywrap/pgp"
"github.com/containerd/containerd/pkg/encryption/keywrap/pkcs7"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
func init() {
keyWrappers = make(map[string]keywrap.KeyWrapper)
keyWrapperAnnotations = make(map[string]string)
RegisterKeyWrapper("pgp", pgp.NewKeyWrapper())
RegisterKeyWrapper("jwe", jwe.NewKeyWrapper())
RegisterKeyWrapper("pkcs7", pkcs7.NewKeyWrapper())
}
var keyWrappers map[string]keywrap.KeyWrapper
var keyWrapperAnnotations map[string]string
// RegisterKeyWrapper allows to register key wrappers by their encryption scheme
func RegisterKeyWrapper(scheme string, iface keywrap.KeyWrapper) {
keyWrappers[scheme] = iface
keyWrapperAnnotations[iface.GetAnnotationID()] = scheme
}
// GetKeyWrapper looks up the encryptor interface given an encryption scheme (gpg, jwe)
func GetKeyWrapper(scheme string) keywrap.KeyWrapper {
return keyWrappers[scheme]
}
// GetWrappedKeysMap returns a map of wrappedKeys as values in a
// map with the encryption scheme(s) as the key(s)
func GetWrappedKeysMap(desc ocispec.Descriptor) map[string]string {
wrappedKeysMap := make(map[string]string)
for annotationsID, scheme := range keyWrapperAnnotations {
if annotation, ok := desc.Annotations[annotationsID]; ok {
wrappedKeysMap[scheme] = annotation
}
}
return wrappedKeysMap
}
// EncryptLayer encrypts the layer by running one encryptor after the other
func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, desc ocispec.Descriptor) (io.Reader, map[string]string, error) {
var (
encLayerReader io.Reader
err error
optsData []byte
)
if ec == nil {
return nil, nil, errors.Wrapf(errdefs.ErrInvalidArgument, "EncryptConfig must not be nil")
}
for annotationsID := range keyWrapperAnnotations {
annotation := desc.Annotations[annotationsID]
if annotation != "" {
optsData, err = decryptLayerKeyOptsData(&ec.DecryptConfig, desc)
if err != nil {
return nil, nil, err
}
// already encrypted!
}
}
newAnnotations := make(map[string]string)
for annotationsID, scheme := range keyWrapperAnnotations {
b64Annotations := desc.Annotations[annotationsID]
if b64Annotations == "" && optsData == nil {
encLayerReader, optsData, err = commonEncryptLayer(encOrPlainLayerReader, desc.Digest, blockcipher.AESSIVCMAC512)
if err != nil {
return nil, nil, err
}
}
keywrapper := GetKeyWrapper(scheme)
b64Annotations, err = preWrapKeys(keywrapper, ec, b64Annotations, optsData)
if err != nil {
return nil, nil, err
}
if b64Annotations != "" {
newAnnotations[annotationsID] = b64Annotations
}
}
if len(newAnnotations) == 0 {
err = errors.Errorf("no encryptor found to handle encryption")
}
// if nothing was encrypted, we just return encLayer = nil
return encLayerReader, newAnnotations, err
}
// preWrapKeys calls WrapKeys and handles the base64 encoding and concatenation of the
// annotation data
func preWrapKeys(keywrapper keywrap.KeyWrapper, ec *config.EncryptConfig, b64Annotations string, optsData []byte) (string, error) {
newAnnotation, err := keywrapper.WrapKeys(ec, optsData)
if err != nil || len(newAnnotation) == 0 {
return b64Annotations, err
}
b64newAnnotation := base64.StdEncoding.EncodeToString(newAnnotation)
if b64Annotations == "" {
return b64newAnnotation, nil
}
return b64Annotations + "," + b64newAnnotation, nil
}
// DecryptLayer decrypts a layer trying one keywrap.KeyWrapper after the other to see whether it
// can apply the provided private key
// If unwrapOnly is set we will only try to decrypt the layer encryption key and return
func DecryptLayer(dc *config.DecryptConfig, encLayerReader io.Reader, desc ocispec.Descriptor, unwrapOnly bool) (io.Reader, digest.Digest, error) {
if dc == nil {
return nil, "", errors.Wrapf(errdefs.ErrInvalidArgument, "DecryptConfig must not be nil")
}
optsData, err := decryptLayerKeyOptsData(dc, desc)
if err != nil || unwrapOnly {
return nil, "", err
}
return commonDecryptLayer(encLayerReader, optsData)
}
func decryptLayerKeyOptsData(dc *config.DecryptConfig, desc ocispec.Descriptor) ([]byte, error) {
privKeyGiven := false
for annotationsID, scheme := range keyWrapperAnnotations {
b64Annotation := desc.Annotations[annotationsID]
if b64Annotation != "" {
keywrapper := GetKeyWrapper(scheme)
if len(keywrapper.GetPrivateKeys(dc.Parameters)) == 0 {
continue
}
privKeyGiven = true
optsData, err := preUnwrapKey(keywrapper, dc, b64Annotation)
if err != nil {
// try next keywrap.KeyWrapper
continue
}
if optsData == nil {
// try next keywrap.KeyWrapper
continue
}
return optsData, nil
}
}
if !privKeyGiven {
return nil, errors.New("missing private key needed for decryption")
}
return nil, errors.Errorf("no suitable key unwrapper found or none of the private keys could be used for decryption")
}
// preUnwrapKey decodes the comma separated base64 strings and calls the Unwrap function
// of the given keywrapper with it and returns the result in case the Unwrap functions
// does not return an error. If all attempts fail, an error is returned.
func preUnwrapKey(keywrapper keywrap.KeyWrapper, dc *config.DecryptConfig, b64Annotations string) ([]byte, error) {
if b64Annotations == "" {
return nil, nil
}
for _, b64Annotation := range strings.Split(b64Annotations, ",") {
annotation, err := base64.StdEncoding.DecodeString(b64Annotation)
if err != nil {
return nil, errors.New("could not base64 decode the annotation")
}
optsData, err := keywrapper.UnwrapKey(dc, annotation)
if err != nil {
continue
}
return optsData, nil
}
return nil, errors.New("no suitable key found for decrypting layer key")
}
// commonEncryptLayer is a function to encrypt the plain layer using a new random
// symmetric key and return the LayerBlockCipherHandler's JSON in string form for
// later use during decryption
func commonEncryptLayer(plainLayerReader io.Reader, d digest.Digest, typ blockcipher.LayerCipherType) (io.Reader, []byte, error) {
lbch, err := blockcipher.NewLayerBlockCipherHandler()
if err != nil {
return nil, nil, err
}
encLayerReader, opts, err := lbch.Encrypt(plainLayerReader, typ)
if err != nil {
return nil, nil, err
}
opts.Digest = d
optsData, err := json.Marshal(opts)
if err != nil {
return nil, nil, errors.Wrapf(err, "could not JSON marshal opts")
}
return encLayerReader, optsData, err
}
// commonDecryptLayer decrypts an encrypted layer previously encrypted with commonEncryptLayer
// by passing along the optsData
func commonDecryptLayer(encLayerReader io.Reader, optsData []byte) (io.Reader, digest.Digest, error) {
opts := blockcipher.LayerBlockCipherOptions{}
err := json.Unmarshal(optsData, &opts)
if err != nil {
return nil, "", errors.Wrapf(err, "could not JSON unmarshal optsData")
}
lbch, err := blockcipher.NewLayerBlockCipherHandler()
if err != nil {
return nil, "", err
}
plainLayerReader, opts, err := lbch.Decrypt(encLayerReader, opts)
if err != nil {
return nil, "", err
}
return plainLayerReader, opts.Digest, nil
}

View File

@@ -0,0 +1,137 @@
/*
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 encryption
import (
"bytes"
"reflect"
"testing"
"github.com/containerd/containerd/pkg/encryption/config"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
var (
privateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAnnExCL+jpTAgZzgEBwmR88ltusMBdsd4ZDO4M5NohTq0T6TP
vJN99ud8ZAY8fVpN63TKD2enWy6imkSE3Cdp+yFgOntsg8WgdPF+8VQaNpn/g8LC
rpWXpJuJIGzCSW5SlUt0OkvyeDvQFrKCqdI63H4cxXY5ly2HlTHJ1+YzEBSLONJY
xsJt6/7cL7mJ7CR/9NZouft2xeNQto9JSqWUNCwUGdZOS1pMVkka/2tFmq8psciF
YvCl6msX6haZ7IDq4/GfeteL8gp6t+hgyWmbJ5/h0hNEvAz4DVb5RnKYVLwWM/e9
oQTw9WgKCqUZKe0+DKmuKYMH2g77oTvDtP8NvQIDAQABAoIBAC8tUQZj2ZxEGkHh
wgE+bkECxzOHARaXClf7tmtVBxg0hJ/6WQizehxcjQNTgAtrKixj2A6CNKjH2A7L
PCw5aCsooviG66bI36AykDPXcP61GAnpogJN9JtE3K3U9Hzc5qYhk3gQSSBX3vwD
Jzjdqj0hJ/v72eYT3n0kGA+7MZUlsObpOouPAZMo72Bcvg2s20FLnKQCiGfH8zWv
wJAnO5BhinwTPhi+01Xj9LePk/2bs3hEzH1/bA3DVmmaWp5H8RuaGuvQ6eX4EXir
3xq9BjjYIK21dmD2R1S0jjez3/d2P7gENKGVItcakURWIn7IS0bYr8P2xIhnxQQV
OTYgWRkCgYEAyboK1GDr+5KtCoAQE1e1Ntbi+x3R2sIjX8lGzLumd5ybqSMGH8V9
6efRo7onuP5V66bDwxSeSFomOEF0rQZj3dpoEXkF95h8G65899okXMURsqjb6+wm
xyFKZAJojJXsR076ES3tas/TgPVD/ZfcBYTU8Ssvfsi3uzeUrbuVL58CgYEAyRHq
/1zsPDf3B7E8EsRA/YZJTfjzDlqXatTX5dnoUKBQH9nZut4qGWBwWPj2DKJwlGQr
m12RIbM5uGvUe3csddzClp0zInDhvD/K3XlUthUfrYX209xaeOD6d4+7wd56SNEo
AzhSobgmrITEAy8QA1u546ID+gFOQnzG17HelSMCgYEAsdmoep4I7//dOAi4I5WM
WxERhTxBLJFFBso59X7rwUD9rB0I5TIFVRfhKGyTYPI7ZkvdBD1FX5y7XZW3/GRJ
3+sTHXSJ4kU6Bl3MJ+jXbkMA23csjc/iUGX1ZD8LVgdIDYZ/ym2niCg63NNgYlBk
1yjJZOciNLFZ62GRX6qmWRkCgYAYS7j4mFLXR+/qlwjqP5qWx9YtvMopztqDByr7
VCRVMbncz2cWxGeT32pT5eldR3eRBrWaNWknCFAOL8FiFdlieIVuy5n1LGyqYY7y
yglpYw4L2qcjnHm2J4E8VzrZxzdBezx5fyHE9sp9iCFjPRmTPk8s6VPPrr61G/yu
7Yg2vwKBgAsJFi6zjqfUacMxB+Bb4Ehz7bqRAoeHZCH9MU2lGimjTUC322uQdu2H
ZkYkHwYVP/RngrN7bhYwPahoDThKy+fIGJAuMhPXg6HVTSkcQSJJ/VeIB/DE4AVj
8heezMN183u5gJvwaEj84fJvUEo/QdvG3NSjQptEGsXYSsE56wHu
-----END RSA PRIVATE KEY-----`)
publicKey = []byte(`-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnnExCL+jpTAgZzgEBwmR
88ltusMBdsd4ZDO4M5NohTq0T6TPvJN99ud8ZAY8fVpN63TKD2enWy6imkSE3Cdp
+yFgOntsg8WgdPF+8VQaNpn/g8LCrpWXpJuJIGzCSW5SlUt0OkvyeDvQFrKCqdI6
3H4cxXY5ly2HlTHJ1+YzEBSLONJYxsJt6/7cL7mJ7CR/9NZouft2xeNQto9JSqWU
NCwUGdZOS1pMVkka/2tFmq8psciFYvCl6msX6haZ7IDq4/GfeteL8gp6t+hgyWmb
J5/h0hNEvAz4DVb5RnKYVLwWM/e9oQTw9WgKCqUZKe0+DKmuKYMH2g77oTvDtP8N
vQIDAQAB
-----END PUBLIC KEY-----`)
)
var (
ec *config.EncryptConfig
dc *config.DecryptConfig
)
func init() {
// TODO: Create various EncryptConfigs for testing purposes
dcparameters := make(map[string][][]byte)
parameters := make(map[string][][]byte)
parameters["pubkeys"] = [][]byte{publicKey}
dcparameters["privkeys"] = [][]byte{privateKey}
dcparameters["privkeys-passwords"] = [][]byte{{}}
ec = &config.EncryptConfig{
Parameters: parameters,
DecryptConfig: config.DecryptConfig{
Parameters: dcparameters,
},
}
dc = &config.DecryptConfig{
Parameters: dcparameters,
}
}
func TestEncryptLayer(t *testing.T) {
data := []byte("This is some text!")
desc := ocispec.Descriptor{
Digest: digest.FromBytes(data),
Size: int64(len(data)),
}
dataReader := bytes.NewReader(data)
encLayerReader, annotations, err := EncryptLayer(ec, dataReader, desc)
if err != nil {
t.Fatal(err)
}
if len(annotations) == 0 {
t.Fatal("No keys created for annotations")
}
newDesc := ocispec.Descriptor{
Annotations: annotations,
}
encLayer := make([]byte, 1024)
encsize, err := encLayerReader.Read(encLayer)
if err != nil {
t.Fatal(err)
}
encLayerReaderAt := bytes.NewReader(encLayer[:encsize])
decLayerReader, _, err := DecryptLayer(dc, encLayerReaderAt, newDesc, false)
if err != nil {
t.Fatal(err)
}
decLayer := make([]byte, 1024)
decsize, err := decLayerReader.Read(decLayer)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(decLayer[:decsize], data) {
t.Fatalf("Expected %v, got %v", data, decLayer)
}
}

453
pkg/encryption/gpg.go Normal file
View File

@@ -0,0 +1,453 @@
/*
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 encryption
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"github.com/containerd/containerd/errdefs"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh/terminal"
)
// GPGVersion enum representing the GPG client version to use.
type GPGVersion int
const (
// GPGv2 signifies gpgv2+
GPGv2 GPGVersion = iota
// GPGv1 signifies gpgv1+
GPGv1
// GPGVersionUndetermined signifies gpg client version undetermined
GPGVersionUndetermined
)
// GPGClient defines an interface for wrapping the gpg command line tools
type GPGClient interface {
// ReadGPGPubRingFile gets the byte sequence of the gpg public keyring
ReadGPGPubRingFile() ([]byte, error)
// GetGPGPrivateKey gets the private key bytes of a keyid given a passphrase
GetGPGPrivateKey(keyid uint64, passphrase string) ([]byte, error)
// GetSecretKeyDetails gets the details of a secret key
GetSecretKeyDetails(keyid uint64) ([]byte, bool, error)
// GetKeyDetails gets the details of a public key
GetKeyDetails(keyid uint64) ([]byte, bool, error)
// ResolveRecipients resolves PGP key ids to user names
ResolveRecipients([]string) []string
}
// gpgClient contains generic gpg client information
type gpgClient struct {
gpgHomeDir string
}
// gpgv2Client is a gpg2 client
type gpgv2Client struct {
gpgClient
}
// gpgv1Client is a gpg client
type gpgv1Client struct {
gpgClient
}
// GuessGPGVersion guesses the version of gpg. Defaults to gpg2 if exists, if
// not defaults to regular gpg.
func GuessGPGVersion() GPGVersion {
if err := exec.Command("gpg2", "--version").Run(); err == nil {
return GPGv2
} else if err := exec.Command("gpg", "--version").Run(); err == nil {
return GPGv1
} else {
return GPGVersionUndetermined
}
}
// NewGPGClient creates a new GPGClient object representing the given version
// and using the given home directory
func NewGPGClient(gpgVersion, gpgHomeDir string) (GPGClient, error) {
v := new(GPGVersion)
switch gpgVersion {
case "v1":
*v = GPGv1
case "v2":
*v = GPGv2
default:
v = nil
}
return newGPGClient(v, gpgHomeDir)
}
func newGPGClient(version *GPGVersion, homedir string) (GPGClient, error) {
var gpgVersion GPGVersion
if version != nil {
gpgVersion = *version
} else {
gpgVersion = GuessGPGVersion()
}
switch gpgVersion {
case GPGv1:
return &gpgv1Client{
gpgClient: gpgClient{gpgHomeDir: homedir},
}, nil
case GPGv2:
return &gpgv2Client{
gpgClient: gpgClient{gpgHomeDir: homedir},
}, nil
case GPGVersionUndetermined:
return nil, fmt.Errorf("unable to determine GPG version")
default:
return nil, fmt.Errorf("unhandled case: NewGPGClient")
}
}
// GetGPGPrivateKey gets the bytes of a specified keyid, supplying a passphrase
func (gc *gpgv2Client) GetGPGPrivateKey(keyid uint64, passphrase string) ([]byte, error) {
var args []string
if gc.gpgHomeDir != "" {
args = append(args, []string{"--homedir", gc.gpgHomeDir}...)
}
args = append(args, []string{"--pinentry-mode", "loopback", "--batch", "--passphrase", passphrase, "--export-secret-key", fmt.Sprintf("0x%x", keyid)}...)
cmd := exec.Command("gpg2", args...)
return runGPGGetOutput(cmd)
}
// ReadGPGPubRingFile reads the GPG public key ring file
func (gc *gpgv2Client) ReadGPGPubRingFile() ([]byte, error) {
var args []string
if gc.gpgHomeDir != "" {
args = append(args, []string{"--homedir", gc.gpgHomeDir}...)
}
args = append(args, []string{"--batch", "--export"}...)
cmd := exec.Command("gpg2", args...)
return runGPGGetOutput(cmd)
}
func (gc *gpgv2Client) getKeyDetails(option string, keyid uint64) ([]byte, bool, error) {
var args []string
if gc.gpgHomeDir != "" {
args = append([]string{"--homedir", gc.gpgHomeDir})
}
args = append(args, option, fmt.Sprintf("0x%x", keyid))
cmd := exec.Command("gpg2", args...)
keydata, err := runGPGGetOutput(cmd)
return keydata, err == nil, err
}
// GetSecretKeyDetails retrives the secret key details of key with keyid.
// returns a byte array of the details and a bool if the key exists
func (gc *gpgv2Client) GetSecretKeyDetails(keyid uint64) ([]byte, bool, error) {
return gc.getKeyDetails("-K", keyid)
}
// GetKeyDetails retrives the public key details of key with keyid.
// returns a byte array of the details and a bool if the key exists
func (gc *gpgv2Client) GetKeyDetails(keyid uint64) ([]byte, bool, error) {
return gc.getKeyDetails("-k", keyid)
}
// ResolveRecipients converts PGP keyids to email addresses, if possible
func (gc *gpgv2Client) ResolveRecipients(recipients []string) []string {
return resolveRecipients(gc, recipients)
}
// GetGPGPrivateKey gets the bytes of a specified keyid, supplying a passphrase
func (gc *gpgv1Client) GetGPGPrivateKey(keyid uint64, _ string) ([]byte, error) {
var args []string
if gc.gpgHomeDir != "" {
args = append(args, []string{"--homedir", gc.gpgHomeDir}...)
}
args = append(args, []string{"--batch", "--export-secret-key", fmt.Sprintf("0x%x", keyid)}...)
cmd := exec.Command("gpg", args...)
return runGPGGetOutput(cmd)
}
// ReadGPGPubRingFile reads the GPG public key ring file
func (gc *gpgv1Client) ReadGPGPubRingFile() ([]byte, error) {
var args []string
if gc.gpgHomeDir != "" {
args = append(args, []string{"--homedir", gc.gpgHomeDir}...)
}
args = append(args, []string{"--batch", "--export"}...)
cmd := exec.Command("gpg", args...)
return runGPGGetOutput(cmd)
}
func (gc *gpgv1Client) getKeyDetails(option string, keyid uint64) ([]byte, bool, error) {
var args []string
if gc.gpgHomeDir != "" {
args = append([]string{"--homedir", gc.gpgHomeDir})
}
args = append(args, option, fmt.Sprintf("0x%x", keyid))
cmd := exec.Command("gpg", args...)
keydata, err := runGPGGetOutput(cmd)
return keydata, err == nil, err
}
// GetSecretKeyDetails retrives the secret key details of key with keyid.
// returns a byte array of the details and a bool if the key exists
func (gc *gpgv1Client) GetSecretKeyDetails(keyid uint64) ([]byte, bool, error) {
return gc.getKeyDetails("-K", keyid)
}
// GetKeyDetails retrives the public key details of key with keyid.
// returns a byte array of the details and a bool if the key exists
func (gc *gpgv1Client) GetKeyDetails(keyid uint64) ([]byte, bool, error) {
return gc.getKeyDetails("-k", keyid)
}
// ResolveRecipients converts PGP keyids to email addresses, if possible
func (gc *gpgv1Client) ResolveRecipients(recipients []string) []string {
return resolveRecipients(gc, recipients)
}
// runGPGGetOutput runs the GPG commandline and returns stdout as byte array
// and any stderr in the error
func runGPGGetOutput(cmd *exec.Cmd) ([]byte, error) {
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
stdoutstr, err2 := ioutil.ReadAll(stdout)
stderrstr, _ := ioutil.ReadAll(stderr)
if err := cmd.Wait(); err != nil {
return nil, fmt.Errorf("error from %s: %s", cmd.Path, string(stderrstr))
}
return stdoutstr, err2
}
// resolveRecipients walks the list of recipients and attempts to convert
// all keyIds to email addresses; if something goes wrong during the
// conversion of a recipient, the original string is returned for that
// recpient
func resolveRecipients(gc GPGClient, recipients []string) []string {
var result []string
for _, recipient := range recipients {
keyID, err := strconv.ParseUint(recipient, 0, 64)
if err != nil {
result = append(result, recipient)
} else {
details, found, _ := gc.GetKeyDetails(keyID)
if !found {
result = append(result, recipient)
} else {
email := extractEmailFromDetails(details)
if email == "" {
result = append(result, recipient)
} else {
result = append(result, email)
}
}
}
}
return result
}
var emailPattern = regexp.MustCompile(`uid\s+\[.*\]\s.*\s<(?P<email>.+)>`)
func extractEmailFromDetails(details []byte) string {
loc := emailPattern.FindSubmatchIndex(details)
if len(loc) == 0 {
return ""
}
return string(emailPattern.Expand(nil, []byte("$email"), details, loc))
}
// uint64ToStringArray converts an array of uint64's to an array of strings
// by applying a format string to each uint64
func uint64ToStringArray(format string, in []uint64) []string {
var ret []string
for _, v := range in {
ret = append(ret, fmt.Sprintf(format, v))
}
return ret
}
// GPGGetPrivateKey walks the list of layerInfos and tries to decrypt the
// wrapped symmetric keys. For this it determines whether a private key is
// in the GPGVault or on this system and prompts for the passwords for those
// that are available. If we do not find a private key on the system for
// getting to the symmetric key of a layer then an error is generated.
func GPGGetPrivateKey(descs []ocispec.Descriptor, gpgClient GPGClient, gpgVault GPGVault, mustFindKey bool, dcparameters map[string][][]byte) error {
// PrivateKeyData describes a private key
type PrivateKeyData struct {
KeyData []byte
KeyDataPassword []byte
}
var pkd PrivateKeyData
keyIDPasswordMap := make(map[uint64]PrivateKeyData)
for _, desc := range descs {
for scheme, b64pgpPackets := range GetWrappedKeysMap(desc) {
if scheme != "pgp" {
continue
}
keywrapper := GetKeyWrapper(scheme)
if keywrapper == nil {
return errors.Errorf("could not get KeyWrapper for %s\n", scheme)
}
keyIds, err := keywrapper.GetKeyIdsFromPacket(b64pgpPackets)
if err != nil {
return err
}
found := false
for _, keyid := range keyIds {
// do we have this key? -- first check the vault
if gpgVault != nil {
_, keydata := gpgVault.GetGPGPrivateKey(keyid)
if len(keydata) > 0 {
pkd = PrivateKeyData{
KeyData: keydata,
KeyDataPassword: nil, // password not supported in this case
}
keyIDPasswordMap[keyid] = pkd
found = true
break
}
} else if gpgClient != nil {
// check the local system's gpg installation
keyinfo, haveKey, _ := gpgClient.GetSecretKeyDetails(keyid)
// this may fail if the key is not here; we ignore the error
if !haveKey {
// key not on this system
continue
}
_, found = keyIDPasswordMap[keyid]
if !found {
fmt.Printf("Passphrase required for Key id 0x%x: \n%v", keyid, string(keyinfo))
fmt.Printf("Enter passphrase for key with Id 0x%x: ", keyid)
password, err := terminal.ReadPassword(int(os.Stdin.Fd()))
fmt.Printf("\n")
if err != nil {
return err
}
keydata, err := gpgClient.GetGPGPrivateKey(keyid, string(password))
if err != nil {
return err
}
pkd = PrivateKeyData{
KeyData: keydata,
KeyDataPassword: password,
}
keyIDPasswordMap[keyid] = pkd
found = true
}
break
} else {
return errors.Wrapf(errdefs.ErrInvalidArgument, "no GPGVault or GPGClient passed.")
}
}
if !found && len(b64pgpPackets) > 0 && mustFindKey {
ids := uint64ToStringArray("0x%x", keyIds)
return errors.Wrapf(errdefs.ErrNotFound, "missing key for decryption of layer %x of %s. Need one of the following keys: %s", desc.Digest, desc.Platform, strings.Join(ids, ", "))
}
}
}
var (
privKeys [][]byte
privKeysPwd [][]byte
)
for _, pkd := range keyIDPasswordMap {
privKeys = append(privKeys, pkd.KeyData)
privKeysPwd = append(privKeysPwd, pkd.KeyDataPassword)
}
dcparameters["gpg-privatekeys"] = privKeys
dcparameters["gpg-privatekeys-passwords"] = privKeysPwd
return nil
}
// GPGSetupPrivateKeys uses the gpg specific parameters in the dcparameters map
// to get the private keys needed for decryption the give layers
func GPGSetupPrivateKeys(dcparameters map[string][][]byte, descs []ocispec.Descriptor) error {
/* we have to find a GPG key until we also get other private keys passed */
var (
gpgVault GPGVault
gpgClient GPGClient
gpgVersion string
gpgHomeDir string
err error
)
gpgPrivateKeys := dcparameters["gpg-privatekeys"]
if len(gpgPrivateKeys) > 0 {
gpgVault = NewGPGVault()
gpgVault.AddSecretKeyRingDataArray(gpgPrivateKeys)
}
haveGPGClient := dcparameters["gpg-client"]
if len(haveGPGClient) > 0 {
item := dcparameters["gpg-client-version"]
if len(item) == 1 {
gpgVersion = string(item[0])
}
item = dcparameters["gpg-client-homedir"]
if len(item) == 1 {
gpgHomeDir = string(item[0])
}
gpgClient, err = NewGPGClient(gpgVersion, gpgHomeDir)
if err != nil {
return err
}
}
return GPGGetPrivateKey(descs, gpgClient, gpgVault, false, dcparameters)
}

100
pkg/encryption/gpgvault.go Normal file
View File

@@ -0,0 +1,100 @@
/*
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 encryption
import (
"bytes"
"io/ioutil"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// GPGVault defines an interface for wrapping multiple secret key rings
type GPGVault interface {
// AddSecretKeyRingData adds a secret keyring via its raw byte array
AddSecretKeyRingData(gpgSecretKeyRingData []byte) error
// AddSecretKeyRingDataArray adds secret keyring via its raw byte arrays
AddSecretKeyRingDataArray(gpgSecretKeyRingDataArray [][]byte) error
// AddSecretKeyRingFiles adds secret keyrings given their filenames
AddSecretKeyRingFiles(filenames []string) error
// GetGPGPrivateKey gets the private key bytes of a keyid given a passphrase
GetGPGPrivateKey(keyid uint64) ([]openpgp.Key, []byte)
}
// gpgVault wraps an array of gpgSecretKeyRing
type gpgVault struct {
entityLists []openpgp.EntityList
keyDataList [][]byte // the raw data original passed in
}
// NewGPGVault creates an empty GPGVault
func NewGPGVault() GPGVault {
return &gpgVault{}
}
// AddSecretKeyRingData adds a secret keyring's to the gpgVault; the raw byte
// array read from the file must be passed and will be parsed by this function
func (g *gpgVault) AddSecretKeyRingData(gpgSecretKeyRingData []byte) error {
// read the private keys
r := bytes.NewReader(gpgSecretKeyRingData)
entityList, err := openpgp.ReadKeyRing(r)
if err != nil {
return errors.Wrapf(err, "could not read keyring")
}
g.entityLists = append(g.entityLists, entityList)
g.keyDataList = append(g.keyDataList, gpgSecretKeyRingData)
return nil
}
// AddSecretKeyRingDataArray adds secret keyrings to the gpgVault; the raw byte
// arrays read from files must be passed
func (g *gpgVault) AddSecretKeyRingDataArray(gpgSecretKeyRingDataArray [][]byte) error {
for _, gpgSecretKeyRingData := range gpgSecretKeyRingDataArray {
if err := g.AddSecretKeyRingData(gpgSecretKeyRingData); err != nil {
return err
}
}
return nil
}
// AddSecretKeyRingFiles adds the secret key rings given their filenames
func (g *gpgVault) AddSecretKeyRingFiles(filenames []string) error {
for _, filename := range filenames {
gpgSecretKeyRingData, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
err = g.AddSecretKeyRingData(gpgSecretKeyRingData)
if err != nil {
return err
}
}
return nil
}
// GetGPGPrivateKey gets the bytes of a specified keyid, supplying a passphrase
func (g *gpgVault) GetGPGPrivateKey(keyid uint64) ([]openpgp.Key, []byte) {
for i, el := range g.entityLists {
decKeys := el.KeysByIdUsage(keyid, packet.KeyFlagEncryptCommunications)
if len(decKeys) > 0 {
return decKeys, g.keyDataList[i]
}
}
return nil, nil
}

View File

@@ -0,0 +1,132 @@
/*
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 jwe
import (
"crypto/ecdsa"
"github.com/containerd/containerd/pkg/encryption/config"
"github.com/containerd/containerd/pkg/encryption/keywrap"
"github.com/containerd/containerd/pkg/encryption/utils"
"github.com/pkg/errors"
jose "gopkg.in/square/go-jose.v2"
)
type jweKeyWrapper struct {
}
func (kw *jweKeyWrapper) GetAnnotationID() string {
return "org.opencontainers.image.enc.keys.jwe"
}
// NewKeyWrapper returns a new key wrapping interface using jwe
func NewKeyWrapper() keywrap.KeyWrapper {
return &jweKeyWrapper{}
}
// WrapKeys wraps the session key for recpients and encrypts the optsData, which
// describe the symmetric key used for encrypting the layer
func (kw *jweKeyWrapper) WrapKeys(ec *config.EncryptConfig, optsData []byte) ([]byte, error) {
var joseRecipients []jose.Recipient
err := addPubKeys(&joseRecipients, ec.Parameters["pubkeys"])
if err != nil {
return nil, err
}
// no recipients is not an error...
if len(joseRecipients) == 0 {
return nil, nil
}
encrypter, err := jose.NewMultiEncrypter(jose.A256GCM, joseRecipients, nil)
if err != nil {
return nil, errors.Wrapf(err, "jose.NewMultiEncrypter failed")
}
jwe, err := encrypter.Encrypt(optsData)
if err != nil {
return nil, errors.Wrapf(err, "JWE Encrypt failed")
}
return []byte(jwe.FullSerialize()), nil
}
func (kw *jweKeyWrapper) UnwrapKey(dc *config.DecryptConfig, jweString []byte) ([]byte, error) {
jwe, err := jose.ParseEncrypted(string(jweString))
if err != nil {
return nil, errors.New("jose.ParseEncrypted failed")
}
privKeys := kw.GetPrivateKeys(dc.Parameters)
if len(privKeys) == 0 {
return nil, errors.New("No private keys found for JWE decryption")
}
privKeysPasswords := kw.getPrivateKeysPasswords(dc.Parameters)
if len(privKeysPasswords) != len(privKeys) {
return nil, errors.New("Private key password array length must be same as that of private keys")
}
for idx, privKey := range privKeys {
key, err := utils.ParsePrivateKey(privKey, privKeysPasswords[idx], "JWE")
if err != nil {
return nil, err
}
_, _, plain, err := jwe.DecryptMulti(key)
if err == nil {
return plain, nil
}
}
return nil, errors.New("JWE: No suitable private key found for decryption")
}
func (kw *jweKeyWrapper) GetPrivateKeys(dcparameters map[string][][]byte) [][]byte {
return dcparameters["privkeys"]
}
func (kw *jweKeyWrapper) getPrivateKeysPasswords(dcparameters map[string][][]byte) [][]byte {
return dcparameters["privkeys-passwords"]
}
func (kw *jweKeyWrapper) GetKeyIdsFromPacket(b64jwes string) ([]uint64, error) {
return nil, nil
}
func (kw *jweKeyWrapper) GetRecipients(b64jwes string) ([]string, error) {
return []string{"[jwe]"}, nil
}
func addPubKeys(joseRecipients *[]jose.Recipient, pubKeys [][]byte) error {
if len(pubKeys) == 0 {
return nil
}
for _, pubKey := range pubKeys {
key, err := utils.ParsePublicKey(pubKey, "JWE")
if err != nil {
return err
}
alg := jose.RSA_OAEP
switch key.(type) {
case *ecdsa.PublicKey:
alg = jose.ECDH_ES_A256KW
}
*joseRecipients = append(*joseRecipients, jose.Recipient{
Algorithm: alg,
Key: key,
})
}
return nil
}

View File

@@ -0,0 +1,345 @@
/*
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 jwe
import (
"crypto/elliptic"
"testing"
"github.com/containerd/containerd/pkg/encryption/config"
"github.com/containerd/containerd/pkg/encryption/utils"
jose "gopkg.in/square/go-jose.v2"
)
var oneEmpty []byte
func createValidJweCcs() ([]*config.CryptoConfig, error) {
jwePubKeyPem, jwePrivKeyPem, err := utils.CreateRSATestKey(2048, oneEmpty, true)
if err != nil {
return nil, err
}
jwePubKey2Pem, jwePrivKey2Pem, err := utils.CreateRSATestKey(1024, oneEmpty, true)
if err != nil {
return nil, err
}
jwePrivKey3Password := []byte("password")
jwePubKey3Pem, jwePrivKey3PassPem, err := utils.CreateRSATestKey(2048, jwePrivKey3Password, true)
if err != nil {
return nil, err
}
jwePubKeyDer, jwePrivKeyDer, err := utils.CreateRSATestKey(2048, oneEmpty, false)
if err != nil {
return nil, err
}
jweEcPubKeyDer, jweEcPrivKeyDer, err := utils.CreateECDSATestKey(elliptic.P521())
if err != nil {
return nil, err
}
key, err := utils.CreateRSAKey(2048)
if err != nil {
return nil, err
}
jwePrivKeyJwk, err := jose.JSONWebKey{Key: key}.MarshalJSON()
if err != nil {
return nil, err
}
jwePubKeyJwk, err := jose.JSONWebKey{Key: &key.PublicKey}.MarshalJSON()
if err != nil {
return nil, err
}
validJweCcs := []*config.CryptoConfig{
// Key 1
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"pubkeys": {jwePubKeyPem},
},
DecryptConfig: config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jwePrivKeyPem},
"privkeys-passwords": {oneEmpty},
},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jwePrivKeyPem},
"privkeys-passwords": {oneEmpty},
},
},
},
// Key 2
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"pubkeys": {jwePubKey2Pem},
},
DecryptConfig: config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jwePrivKey2Pem},
"privkeys-passwords": {oneEmpty},
},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jwePrivKey2Pem},
"privkeys-passwords": {oneEmpty},
},
},
},
// Key 1 without enc private key
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"pubkeys": {jwePubKeyPem},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jwePrivKeyPem},
"privkeys-passwords": {oneEmpty},
},
},
},
// Key 2 without enc private key
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"pubkeys": {jwePubKey2Pem},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jwePrivKey2Pem},
"privkeys-passwords": {oneEmpty},
},
},
},
// Key 3 with enc private key
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"pubkeys": {jwePubKey3Pem},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jwePrivKey3PassPem},
"privkeys-passwords": {jwePrivKey3Password},
},
},
},
// Key (DER format)
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"pubkeys": {jwePubKeyDer},
},
DecryptConfig: config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jwePrivKeyDer},
"privkeys-passwords": {oneEmpty},
},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jwePrivKeyDer},
"privkeys-passwords": {oneEmpty},
},
},
},
// Key (JWK format)
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"pubkeys": {jwePubKeyJwk},
},
DecryptConfig: config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jwePrivKeyJwk},
"privkeys-passwords": {oneEmpty},
},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jwePrivKeyJwk},
"privkeys-passwords": {oneEmpty},
},
},
},
// EC Key (DER format)
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"pubkeys": {jweEcPubKeyDer},
},
DecryptConfig: config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jweEcPrivKeyDer},
"privkeys-passwords": {oneEmpty},
},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jweEcPrivKeyDer},
"privkeys-passwords": {oneEmpty},
},
},
},
}
return validJweCcs, nil
}
func createInvalidJweCcs() ([]*config.CryptoConfig, error) {
jwePubKeyPem, _, err := utils.CreateRSATestKey(2048, oneEmpty, true)
if err != nil {
return nil, err
}
jwePubKey2Pem, _, err := utils.CreateRSATestKey(2048, oneEmpty, true)
if err != nil {
return nil, err
}
invalidJweCcs := []*config.CryptoConfig{
// Client key 1 public with client 2 private decrypt
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"pubkeys": {jwePubKeyPem},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jwePubKey2Pem},
"privkeys-passwords": {oneEmpty},
},
},
},
// Client key 1 public with no private key
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"pubkeys": {jwePubKeyPem},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{},
},
},
// Invalid Client key 1 private key
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"pubkeys": {jwePubKeyPem},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {jwePubKeyPem},
"privkeys-passwords": {oneEmpty},
},
},
},
}
return invalidJweCcs, nil
}
func TestKeyWrapJweSuccess(t *testing.T) {
validJweCcs, err := createValidJweCcs()
if err != nil {
t.Fatal(err)
}
for _, cc := range validJweCcs {
kw := NewKeyWrapper()
data := []byte("This is some secret text")
wk, err := kw.WrapKeys(cc.EncryptConfig, data)
if err != nil {
t.Fatal(err)
}
ud, err := kw.UnwrapKey(cc.DecryptConfig, wk)
if err != nil {
t.Fatal(err)
}
if string(data) != string(ud) {
t.Fatal("Strings don't match")
}
}
}
func TestKeyWrapJweInvalid(t *testing.T) {
invalidJweCcs, err := createInvalidJweCcs()
if err != nil {
t.Fatal(err)
}
for _, cc := range invalidJweCcs {
kw := NewKeyWrapper()
data := []byte("This is some secret text")
wk, err := kw.WrapKeys(cc.EncryptConfig, data)
if err != nil {
return
}
ud, err := kw.UnwrapKey(cc.DecryptConfig, wk)
if err != nil {
return
}
if string(data) != string(ud) {
return
}
t.Fatal("Successfully wrap for invalid crypto config")
}
}

View File

@@ -0,0 +1,40 @@
/*
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 keywrap
import (
"github.com/containerd/containerd/pkg/encryption/config"
)
// KeyWrapper is the interface used for wrapping keys using
// a specific encryption technology (pgp, jwe)
type KeyWrapper interface {
WrapKeys(ec *config.EncryptConfig, optsData []byte) ([]byte, error)
UnwrapKey(dc *config.DecryptConfig, annotation []byte) ([]byte, error)
GetAnnotationID() string
// GetPrivateKeys (optional) gets the array of private keys. It is an optional implementation
// as in some key services, a private key may not be exportable (i.e. HSM)
GetPrivateKeys(dcparameters map[string][][]byte) [][]byte
// GetKeyIdsFromPacket (optional) gets a list of key IDs. This is optional as some encryption
// schemes may not have a notion of key IDs
GetKeyIdsFromPacket(packet string) ([]uint64, error)
// GetRecipients (optional) gets a list of recipients. It is optional due to the validity of
// recipients in a particular encryptiong scheme
GetRecipients(packet string) ([]string, error)
}

View File

@@ -0,0 +1,270 @@
/*
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 pgp
import (
"bytes"
"crypto"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/mail"
"strconv"
"strings"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/pkg/encryption/config"
"github.com/containerd/containerd/pkg/encryption/keywrap"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
type gpgKeyWrapper struct {
}
// NewKeyWrapper returns a new key wrapping interface for pgp
func NewKeyWrapper() keywrap.KeyWrapper {
return &gpgKeyWrapper{}
}
var (
// GPGDefaultEncryptConfig is the default configuration for layer encryption/decryption
GPGDefaultEncryptConfig = &packet.Config{
Rand: rand.Reader,
DefaultHash: crypto.SHA256,
DefaultCipher: packet.CipherAES256,
CompressionConfig: &packet.CompressionConfig{Level: 0}, // No compression
RSABits: 2048,
}
)
func (kw *gpgKeyWrapper) GetAnnotationID() string {
return "org.opencontainers.image.enc.keys.pgp"
}
// WrapKeys wraps the session key for recpients and encrypts the optsData, which
// describe the symmetric key used for encrypting the layer
func (kw *gpgKeyWrapper) WrapKeys(ec *config.EncryptConfig, optsData []byte) ([]byte, error) {
ciphertext := new(bytes.Buffer)
el, err := kw.createEntityList(ec)
if err != nil {
return nil, errors.Wrap(err, "unable to create entity list")
}
if len(el) == 0 {
// nothing to do -- not an error
return nil, nil
}
plaintextWriter, err := openpgp.Encrypt(ciphertext,
el, /*EntityList*/
nil, /* Sign*/
nil, /* FileHint */
GPGDefaultEncryptConfig)
if err != nil {
return nil, err
}
if _, err = plaintextWriter.Write(optsData); err != nil {
return nil, err
} else if err = plaintextWriter.Close(); err != nil {
return nil, err
}
return ciphertext.Bytes(), err
}
// UnwrapKey unwraps the symmetric key with which the layer is encrypted
// This symmetric key is encrypted in the PGP payload.
func (kw *gpgKeyWrapper) UnwrapKey(dc *config.DecryptConfig, pgpPacket []byte) ([]byte, error) {
pgpPrivateKeys, pgpPrivateKeysPwd, err := kw.getKeyParameters(dc.Parameters)
if err != nil {
return nil, err
}
for idx, pgpPrivateKey := range pgpPrivateKeys {
r := bytes.NewBuffer(pgpPrivateKey)
entityList, err := openpgp.ReadKeyRing(r)
if err != nil {
return nil, errors.Wrap(err, "unable to parse private keys")
}
var prompt openpgp.PromptFunction
if len(pgpPrivateKeysPwd) > idx {
responded := false
prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
if responded {
return nil, fmt.Errorf("don't seem to have the right password")
}
responded = true
for _, key := range keys {
if key.PrivateKey != nil {
key.PrivateKey.Decrypt(pgpPrivateKeysPwd[idx])
}
}
return pgpPrivateKeysPwd[idx], nil
}
}
r = bytes.NewBuffer(pgpPacket)
md, err := openpgp.ReadMessage(r, entityList, prompt, GPGDefaultEncryptConfig)
if err != nil {
continue
}
// we get the plain key options back
optsData, err := ioutil.ReadAll(md.UnverifiedBody)
if err != nil {
continue
}
return optsData, nil
}
return nil, errors.New("PGP: No suitable key found to unwrap key")
}
// GetKeyIdsFromWrappedKeys converts the base64 encoded PGPPacket to uint64 keyIds
func (kw *gpgKeyWrapper) GetKeyIdsFromPacket(b64pgpPackets string) ([]uint64, error) {
var keyids []uint64
for _, b64pgpPacket := range strings.Split(b64pgpPackets, ",") {
pgpPacket, err := base64.StdEncoding.DecodeString(b64pgpPacket)
if err != nil {
return nil, errors.Wrapf(err, "could not decode base64 encoded PGP packet")
}
newids, err := kw.getKeyIDs(pgpPacket)
if err != nil {
return nil, err
}
keyids = append(keyids, newids...)
}
return keyids, nil
}
// getKeyIDs parses a PGPPacket and gets the list of recipients' key IDs
func (kw *gpgKeyWrapper) getKeyIDs(pgpPacket []byte) ([]uint64, error) {
var keyids []uint64
kbuf := bytes.NewBuffer(pgpPacket)
packets := packet.NewReader(kbuf)
ParsePackets:
for {
p, err := packets.Next()
if err == io.EOF {
break ParsePackets
}
if err != nil {
return []uint64{}, errors.Wrapf(err, "packets.Next() failed")
}
switch p := p.(type) {
case *packet.EncryptedKey:
keyids = append(keyids, p.KeyId)
case *packet.SymmetricallyEncrypted:
break ParsePackets
}
}
return keyids, nil
}
// GetRecipients converts the wrappedKeys to an array of recipients
func (kw *gpgKeyWrapper) GetRecipients(b64pgpPackets string) ([]string, error) {
keyIds, err := kw.GetKeyIdsFromPacket(b64pgpPackets)
if err != nil {
return nil, err
}
var array []string
for _, keyid := range keyIds {
array = append(array, "0x"+strconv.FormatUint(keyid, 16))
}
return array, nil
}
func (kw *gpgKeyWrapper) GetPrivateKeys(dcparameters map[string][][]byte) [][]byte {
return dcparameters["gpg-privatekeys"]
}
func (kw *gpgKeyWrapper) getKeyParameters(dcparameters map[string][][]byte) ([][]byte, [][]byte, error) {
privKeys := kw.GetPrivateKeys(dcparameters)
if len(privKeys) == 0 {
return nil, nil, errors.New("GPG: Missing private key parameter")
}
return privKeys, dcparameters["gpg-privatekeys-passwords"], nil
}
// createEntityList creates the opengpg EntityList by reading the KeyRing
// first and then filtering out recipients' keys
func (kw *gpgKeyWrapper) createEntityList(ec *config.EncryptConfig) (openpgp.EntityList, error) {
pgpPubringFile := ec.Parameters["gpg-pubkeyringfile"]
if len(pgpPubringFile) == 0 {
return nil, nil
}
r := bytes.NewReader(pgpPubringFile[0])
entityList, err := openpgp.ReadKeyRing(r)
if err != nil {
return nil, err
}
gpgRecipients := ec.Parameters["gpg-recipients"]
if len(gpgRecipients) == 0 {
return nil, nil
}
rSet := make(map[string]int)
for _, r := range gpgRecipients {
rSet[string(r)] = 0
}
var filteredList openpgp.EntityList
for _, entity := range entityList {
for k := range entity.Identities {
addr, err := mail.ParseAddress(k)
if err != nil {
return nil, err
}
for _, r := range gpgRecipients {
recp := string(r)
if strings.Compare(addr.Name, recp) == 0 || strings.Compare(addr.Address, recp) == 0 {
filteredList = append(filteredList, entity)
rSet[recp] = rSet[recp] + 1
}
}
}
}
// make sure we found keys for all the Recipients...
var buffer bytes.Buffer
notFound := false
buffer.WriteString("PGP: No key found for the following recipients: ")
for k, v := range rSet {
if v == 0 {
if notFound {
buffer.WriteString(", ")
}
buffer.WriteString(k)
notFound = true
}
}
if notFound {
return nil, errors.Wrapf(errdefs.ErrNotFound, buffer.String())
}
return filteredList, nil
}

View File

@@ -0,0 +1,190 @@
/*
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 pgp
import (
"testing"
"github.com/containerd/containerd/pkg/encryption/config"
)
var validGpgCcs = []*config.CryptoConfig{
// Key 1
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"gpg-pubkeyringfile": {gpgPubKeyRing},
"gpg-recipients": {gpgRecipient1},
},
DecryptConfig: config.DecryptConfig{
Parameters: map[string][][]byte{
"gpg-privatekeys": {gpgPrivKey1},
},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"gpg-privatekeys": {gpgPrivKey1},
},
},
},
// Key 2
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"gpg-pubkeyringfile": {gpgPubKeyRing},
"gpg-recipients": {gpgRecipient2},
},
DecryptConfig: config.DecryptConfig{
Parameters: map[string][][]byte{
"gpg-privatekeys": {gpgPrivKey2},
},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"gpg-privatekeys": {gpgPrivKey2},
},
},
},
// Key 1 without enc private key
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"gpg-pubkeyringfile": {gpgPubKeyRing},
"gpg-recipients": {gpgRecipient1},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"gpg-privatekeys": {gpgPrivKey1},
},
},
},
// Key 2 without enc private key
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"gpg-pubkeyringfile": {gpgPubKeyRing},
"gpg-recipients": {gpgRecipient2},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"gpg-privatekeys": {gpgPrivKey2},
},
},
},
}
var invalidGpgCcs = []*config.CryptoConfig{
// Client key 1 public with client 2 private decrypt
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"gpg-pubkeyringfile": {gpgPubKeyRing},
"gpg-recipients": {gpgRecipient1},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"gpg-privatekeys": {gpgPrivKey2},
},
},
},
// Client key 1 public with no private key
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"gpg-pubkeyringfile": {gpgPubKeyRing},
"gpg-recipients": {gpgRecipient1},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{},
},
},
// Invalid Client key 1 private key
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"gpg-pubkeyringfile": {gpgPrivKey1},
"gpg-recipients": {gpgRecipient1},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"gpg-privatekeys": {gpgPrivKey1},
},
},
},
}
func TestKeyWrapGpgSuccess(t *testing.T) {
for _, cc := range validGpgCcs {
kw := NewKeyWrapper()
data := []byte("This is some secret text")
wk, err := kw.WrapKeys(cc.EncryptConfig, data)
if err != nil {
t.Fatal(err)
}
ud, err := kw.UnwrapKey(cc.DecryptConfig, wk)
if err != nil {
t.Fatal(err)
}
if string(data) != string(ud) {
t.Fatal("Strings don't match")
}
}
}
func TestKeyWrapGpgInvalid(t *testing.T) {
for _, cc := range invalidGpgCcs {
kw := NewKeyWrapper()
data := []byte("This is some secret text")
wk, err := kw.WrapKeys(cc.EncryptConfig, data)
if err != nil {
return
}
ud, err := kw.UnwrapKey(cc.DecryptConfig, wk)
if err != nil {
return
}
if string(data) != string(ud) {
return
}
t.Fatal("Successfully wrap for invalid crypto config")
}
}

View File

@@ -0,0 +1,87 @@
/*
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 pgp
import (
"encoding/base64"
"strings"
)
func b64Dec(str string) string {
str = strings.Replace(str, " ", "", -1)
s, err := base64.StdEncoding.DecodeString(str)
if err != nil {
panic(err)
}
return string(s)
}
var (
gpgPubKeyRing = []byte(b64Dec(`mQENBFulXFgBCADKrLe251CMrFS4Un4sPcFb9TVZxdSuMlf4lhFhMphqQkctMoyjeeGebN8P0R8E
8xeV4iJnIMPWqoWTabvDGkl9HorFrSVeZVj0OD9JoMAIg55KSbT1XUWzDgNiZ4p6PJkORx2uTdfZ
AwhdAAAu4HDzAGHF0YKV31iZbSdAcFMVAxCxc6zAVV7qL+3SLxT5UxB/lAbKX1c4Tn6y7wlKZOGm
WUWsBLQ1aQ/iloFIakUwwa+Yc03WUYEDEXnaQ9tDSyjI3fWcwTVRI29LOkFT7JiIK0FgYkebYex9
Cp+G8QuW6XK7A4ljrhQM5SVfw+XPbbPQG3kbA0YMP86oZ/VPHzq3ABEBAAG0G3Rlc3RrZXkxIDx0
ZXN0a2V5MUBrZXkub3JnPokBVAQTAQgAPhYhBNKhPj7F2BYBPVBwEO/H08vyNX7IBQJbpVxYAhsD
BQkDwmcABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEO/H08vyNX7ILWoH/135x+mCK9MV7YpI
WATHI3TjZ0e5VEzbMU4b4hH8R9TaFo2nbOO3APbfrOU8AnZSPSdgUMlcFJQhDLbP5rs01e+r2EG6
ksny3LNnXv1kfyn9aqC4gQVKVHXnZzd/Tn6H9h6AaZb3TrbgOY2ZBAZKXGPBzpHVKlRv93GiW8h8
VVlaHRJJK/NpLAA3QgcraGgBmp3u8FCGtvzJ5lXvUCbHrCjxHsGt/aj23xfo+wtlGnkg0kfvapQq
U1f69RoodoJTxP86WVeX5/Gm/NebZTgE538nXvJn+jta4Meh3//xf8g2yzhUEUaq0YUf96lYjf6j
Xb3uZhcu2eM37vM4sczE9Aa5AQ0EW6VcWAEIAK04qvvFX9gN8NDmUJaguSuQCwsEYG9H6HATZsJY
UvjwCbsL2HBQU08Yytm9maf0exYSKsoARapr53DGxnE0J4My1PcijE2daIwly0N1uF5IcXEHJqJ+
QPhfArFxd4HRP/R6xpcDfGuoJQ3G3Nl2KuLMVqD2+admltenwf+AjPYDqrsYBJkaLcY/IaHiSAgj
JPEm/T70J5ZxCbGqEPx93dTgdg4y4ybFiFWsHwFt8d2/gK7TlNEGILGAjzfy4zcEg9UKg7LYPacs
Pw6BbaUGOu4bqcKAZM0PP8+P+/9LVvFGE3V3XzKGDE5BxnzzaBpltnOC5t5MozQsy2XdKiQ4LzcA
EQEAAYkBPAQYAQgAJhYhBNKhPj7F2BYBPVBwEO/H08vyNX7IBQJbpVxYAhsMBQkDwmcAAAoJEO/H
08vyNX7IfDgIAK9qxhz/j0NUlbUx7KCqXOA1VSe9eBhWaVn6fenBzghLicr6H0FD1L8nWIuh1qAm
/Ad8aQEKEAQK5OoS/hBteECs1rlt9FHYa4gI8fCnAHzYleqntpKCeHDmYX4KxUuzbCaqOBgpgMog
yU6cx/VgqgAk/YHH4P1L+Gtbmv8sDAEo0l/6lRcZHZu98oQrAQnw5sYOvL8vCFFrAPh8Gvy8+M4P
/dS0tHkvLP1ah8uxj0SWiw5QIiovcAnwlM8aHNxPSkbhhKKZlh4lOWDWedobSwRK4HmSZcGVMflP
XvPlz32v+pJ64G9XUiyM/N9/8iQGWiu5Jb3ZMLB7JoQgoENKTY2ZAQ0EW6VcbgEIAL2RwpOU4Ffp
oxPDHWdjiz4+Qv/3JdEn23DXSeg2CfxgqnQ/SLH4tVkxIDsQgIRJ68Jh4l6L6xCGcb4gFQPJECy7
O6DA3q26bGlq0iEagHIvcM2QKkX1X9YaR0HsqFjQ42fxIP1ItpMJ8fvfyLB3/UhZdzpx3B0VzRt3
MRAfzvRsCm3bvPevmiRbjiJayf075NHWPxyj9S9aesQYc4Ql30IuFQ5nXWX1vmGWyjvM+iBmbZHr
uxQUommI76vi90yqB3ZoXmYOI5K+VEpE98F12qrOqQhEgsUuRm/s4H2eJaHazYRElvWydV+cLXJJ
GnJUUHm0rUz6ikrLcppf7M7QQ1UAEQEAAbQbdGVzdGtleTIgPHRlc3RrZXkyQGtleS5vcmc+iQFU
BBMBCAA+FiEEHAf5pLOwt580eOkqFWZAd+pqZ0kFAlulXG4CGwMFCQPCZwAFCwkIBwIGFQoJCAsC
BBYCAwECHgECF4AACgkQFWZAd+pqZ0mkGAf+NpJgHxKIFjnObqLLpPLiyN0WY1Bbu7RQpg1CqeMf
SNskZuHqz4AjgJXKUW3rtVY3RDx3vatEz0c05dJ0L5PPU8gcUOP2kA7mHlPvTne1R0ZBKXYXpHL1
wEBMKshoInVrL24JHd4w7bSiwokzhVRRW9yUn4kZiVnyJp+swZ5aKQE/QB1CMHEONz0KYe5aOjug
7Zi4b+wps2KSewSezU24i4bLEDZNrKWagsYFLGzJhZDDdbi1DzOSstYUvu8v9GBLEgi7lAK2Eud/
c2w3wS4Gc7LQNmtM4t686cH1xeCT7mQEpqdDT0HWf8YTl+8FtddohF4xtJi4aSxZlrvYtxlNdbkB
DQRbpVxuAQgAu5mM8iT/9495MySWxtTlOILxFAxluehWlJt4Btd/3vSn447bnkWsmGQBU6Fs6OZJ
O3F4SSwYghhUF/+Dtdn7Ua1r/Lt5ClXCLHe9m3akyutWJLocaEfn1/BifbhRNzJnqYIq4gMI4JB+
KPe+eLz8DhhLeRmPu4wPyJbicfICT5Pk+tvaUzxPrOybpd5fTkQc1X0JdiheUJDtnLeUOBLGzr3P
TlTd01+qIYKqUy2UsBi0bD/ajvUWuREfLnZYlyLXMLykz4JfBEABbn/L+ts9cvFqOGsNlP944ZXo
6vjSnJbq1rqnyI/uHOoY0eSF8mtVTs4ALZ9/8+vAsnKMCimJxQARAQABiQE8BBgBCAAmFiEEHAf5
pLOwt580eOkqFWZAd+pqZ0kFAlulXG4CGwwFCQPCZwAACgkQFWZAd+pqZ0lidQgAnnh6h3GontAZ
KJOtvI1Ybitv5LPGPmbGiOjQDJfGVGASDvD9fAmD4U8J5xFnR4/kLYO0OhS9HsSFv2eQTHC/8o2F
djH/1ft6IyvFn6DaLbitfMTkngJrwwo19MrKFiItM50EKbwYP6cRYAaiiqk6m3hhruhptzItc/v/
KiPYYGZulMTiN/b5uxIeKeIEzCaReJaVD+8n0BGI/VmNovgiBLg9XO8qjv4Xz7hiwTMKZAh6VC+y
wOPa32z1+jvKXtqJKIMBTooiHgTs38oWh5pcMZbDwWwJNqjQmeoeopjk1w7H/WD/Qdx6tTlBG0Zd
1ttP1WJTHrzjmCU0STxczxaigQ==`))
gpgRecipient1 = []byte("testkey1@key.org")
gpgRecipient2 = []byte("testkey2@key.org")
gpgPrivKey1 = []byte(b64Dec(`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`))
gpgPrivKey2 = []byte(b64Dec(`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`))
)

View File

@@ -0,0 +1,132 @@
/*
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 pkcs7
import (
"crypto"
"crypto/x509"
"github.com/containerd/containerd/pkg/encryption/config"
"github.com/containerd/containerd/pkg/encryption/keywrap"
"github.com/containerd/containerd/pkg/encryption/utils"
"github.com/fullsailor/pkcs7"
"github.com/pkg/errors"
)
type pkcs7KeyWrapper struct {
}
// NewKeyWrapper returns a new key wrapping interface using jwe
func NewKeyWrapper() keywrap.KeyWrapper {
return &pkcs7KeyWrapper{}
}
func (kw *pkcs7KeyWrapper) GetAnnotationID() string {
return "org.opencontainers.image.enc.keys.pkcs7"
}
// WrapKeys wraps the session key for recpients and encrypts the optsData, which
// describe the symmetric key used for encrypting the layer
func (kw *pkcs7KeyWrapper) WrapKeys(ec *config.EncryptConfig, optsData []byte) ([]byte, error) {
x509Certs, err := collectX509s(ec.Parameters["x509s"])
if err != nil {
return nil, err
}
// no recipients is not an error...
if len(x509Certs) == 0 {
return nil, nil
}
pkcs7.ContentEncryptionAlgorithm = pkcs7.EncryptionAlgorithmAES128GCM
return pkcs7.Encrypt(optsData, x509Certs)
}
func collectX509s(x509s [][]byte) ([]*x509.Certificate, error) {
if len(x509s) == 0 {
return nil, nil
}
var x509Certs []*x509.Certificate
for _, x509 := range x509s {
x509Cert, err := utils.ParseCertificate(x509, "PKCS7")
if err != nil {
return nil, err
}
x509Certs = append(x509Certs, x509Cert)
}
return x509Certs, nil
}
func (kw *pkcs7KeyWrapper) GetPrivateKeys(dcparameters map[string][][]byte) [][]byte {
return dcparameters["privkeys"]
}
func (kw *pkcs7KeyWrapper) getPrivateKeysPasswords(dcparameters map[string][][]byte) [][]byte {
return dcparameters["privkeys-passwords"]
}
// UnwrapKey unwraps the symmetric key with which the layer is encrypted
// This symmetric key is encrypted in the PKCS7 payload.
func (kw *pkcs7KeyWrapper) UnwrapKey(dc *config.DecryptConfig, pkcs7Packet []byte) ([]byte, error) {
privKeys := kw.GetPrivateKeys(dc.Parameters)
if len(privKeys) == 0 {
return nil, errors.New("no private keys found for PKCS7 decryption")
}
privKeysPasswords := kw.getPrivateKeysPasswords(dc.Parameters)
if len(privKeysPasswords) != len(privKeys) {
return nil, errors.New("private key password array length must be same as that of private keys")
}
x509Certs, err := collectX509s(dc.Parameters["x509s"])
if err != nil {
return nil, err
}
if len(x509Certs) == 0 {
return nil, errors.New("no x509 certificates found needed for PKCS7 decryption")
}
p7, err := pkcs7.Parse(pkcs7Packet)
if err != nil {
return nil, errors.Wrapf(err, "could not parse PKCS7 packet")
}
for idx, privKey := range privKeys {
key, err := utils.ParsePrivateKey(privKey, privKeysPasswords[idx], "PKCS7")
if err != nil {
return nil, err
}
for _, x509Cert := range x509Certs {
optsData, err := p7.Decrypt(x509Cert, crypto.PrivateKey(key))
if err != nil {
continue
}
return optsData, nil
}
}
return nil, errors.New("PKCS7: No suitable private key found for decryption")
}
// GetKeyIdsFromWrappedKeys converts the base64 encoded Packet to uint64 keyIds;
// We cannot do this with pkcs7
func (kw *pkcs7KeyWrapper) GetKeyIdsFromPacket(b64pkcs7Packets string) ([]uint64, error) {
return nil, nil
}
// GetRecipients converts the wrappedKeys to an array of recipients
// We cannot do this with pkcs7
func (kw *pkcs7KeyWrapper) GetRecipients(b64pkcs7Packets string) ([]string, error) {
return []string{"[pkcs7]"}, nil
}

View File

@@ -0,0 +1,254 @@
/*
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 pkcs7
import (
"crypto/x509"
"testing"
"github.com/containerd/containerd/pkg/encryption/config"
"github.com/containerd/containerd/pkg/encryption/utils"
)
var oneEmpty []byte
func createKeys() (*x509.Certificate, []byte, *x509.Certificate, []byte, error) {
caKey, caCert, err := utils.CreateTestCA()
if err != nil {
return nil, nil, nil, nil, err
}
pkcs7ClientPubKey, pkcs7ClientPrivKey, err := utils.CreateRSATestKey(2048, oneEmpty, true)
if err != nil {
return nil, nil, nil, nil, err
}
pkcs7ClientCert, err := utils.CertifyKey(pkcs7ClientPubKey, nil, caKey, caCert)
if err != nil {
return nil, nil, nil, nil, err
}
pkcs7ClientPubKey2, pkcs7ClientPrivKey2, err := utils.CreateRSATestKey(2048, oneEmpty, true)
if err != nil {
return nil, nil, nil, nil, err
}
pkcs7ClientCert2, err := utils.CertifyKey(pkcs7ClientPubKey2, nil, caKey, caCert)
if err != nil {
return nil, nil, nil, nil, err
}
return pkcs7ClientCert, pkcs7ClientPrivKey, pkcs7ClientCert2, pkcs7ClientPrivKey2, nil
}
func createValidPkcs7Ccs() ([]*config.CryptoConfig, error) {
pkcs7ClientCert, pkcs7ClientPrivKey, pkcs7ClientCert2, pkcs7ClientPrivKey2, err := createKeys()
if err != nil {
return nil, err
}
validPkcs7Ccs := []*config.CryptoConfig{
// Client key 1
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"x509s": {pkcs7ClientCert.Raw},
},
DecryptConfig: config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {pkcs7ClientPrivKey},
"privkeys-passwords": {oneEmpty},
"x509s": {pkcs7ClientCert.Raw},
},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {pkcs7ClientPrivKey},
"privkeys-passwords": {oneEmpty},
"x509s": {pkcs7ClientCert.Raw},
},
},
},
// Client key 2
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"x509s": {pkcs7ClientCert2.Raw},
},
DecryptConfig: config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {pkcs7ClientPrivKey2},
"privkeys-passwords": {oneEmpty},
"x509s": {pkcs7ClientCert2.Raw},
},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {pkcs7ClientPrivKey2},
"privkeys-passwords": {oneEmpty},
"x509s": {pkcs7ClientCert2.Raw},
},
},
},
// Client key 1 without enc private key
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"x509s": {pkcs7ClientCert.Raw},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {pkcs7ClientPrivKey},
"privkeys-passwords": {oneEmpty},
"x509s": {pkcs7ClientCert.Raw},
},
},
},
// Client key 2 without enc private key
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"x509s": {pkcs7ClientCert2.Raw},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {pkcs7ClientPrivKey2},
"privkeys-passwords": {oneEmpty},
"x509s": {pkcs7ClientCert2.Raw},
},
},
},
}
return validPkcs7Ccs, nil
}
func createInvalidPkcs7Ccs() ([]*config.CryptoConfig, error) {
pkcs7ClientCert, pkcs7ClientPrivKey, pkcs7ClientCert2, pkcs7ClientPrivKey2, err := createKeys()
if err != nil {
return nil, err
}
invalidPkcs7Ccs := []*config.CryptoConfig{
// Client key 1 public with client 2 private decrypt
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"x509s": {pkcs7ClientCert.Raw},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {pkcs7ClientPrivKey2},
"privkeys-passwords": {oneEmpty},
"x509s": {pkcs7ClientCert2.Raw},
},
},
},
// Client key 1 public with no private key
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"x509s": {pkcs7ClientCert.Raw},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{},
},
},
// Invalid Client key 1 private key
{
EncryptConfig: &config.EncryptConfig{
Parameters: map[string][][]byte{
"x509s": {pkcs7ClientPrivKey},
},
},
DecryptConfig: &config.DecryptConfig{
Parameters: map[string][][]byte{
"privkeys": {pkcs7ClientCert.Raw},
"privkeys-passwords": {oneEmpty},
"x509s": {pkcs7ClientCert.Raw},
},
},
},
}
return invalidPkcs7Ccs, nil
}
func TestKeyWrapPkcs7Success(t *testing.T) {
validPkcs7Ccs, err := createValidPkcs7Ccs()
if err != nil {
t.Fatal(err)
}
for _, cc := range validPkcs7Ccs {
kw := NewKeyWrapper()
data := []byte("This is some secret text")
wk, err := kw.WrapKeys(cc.EncryptConfig, data)
if err != nil {
t.Fatal(err)
}
ud, err := kw.UnwrapKey(cc.DecryptConfig, wk)
if err != nil {
t.Fatal(err)
}
if string(data) != string(ud) {
t.Fatal("Strings don't match")
}
}
}
func TestKeyWrapPkcs7Invalid(t *testing.T) {
invalidPkcs7Ccs, err := createInvalidPkcs7Ccs()
if err != nil {
t.Fatal(err)
}
for _, cc := range invalidPkcs7Ccs {
kw := NewKeyWrapper()
data := []byte("This is some secret text")
wk, err := kw.WrapKeys(cc.EncryptConfig, data)
if err != nil {
return
}
ud, err := kw.UnwrapKey(cc.DecryptConfig, wk)
if err != nil {
return
}
if string(data) != string(ud) {
return
}
t.Fatal("Successfully wrap for invalid crypto config")
}
}

40
pkg/encryption/reader.go Normal file
View File

@@ -0,0 +1,40 @@
/*
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 encryption
import (
"io"
)
type readerAtReader struct {
r io.ReaderAt
off int64
}
// ReaderFromReaderAt takes an io.ReaderAt and returns an io.Reader
func ReaderFromReaderAt(r io.ReaderAt) io.Reader {
return &readerAtReader{
r: r,
off: 0,
}
}
func (rar *readerAtReader) Read(p []byte) (n int, err error) {
n, err = rar.r.ReadAt(p, rar.off)
rar.off += int64(n)
return n, err
}

View File

@@ -0,0 +1,166 @@
/*
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 utils
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"time"
"github.com/pkg/errors"
)
// CreateRSAKey creates an RSA key
func CreateRSAKey(bits int) (*rsa.PrivateKey, error) {
key, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, errors.Wrap(err, "rsa.GenerateKey failed")
}
return key, nil
}
// CreateRSATestKey creates an RSA key of the given size and returns
// the public and private key in PEM or DER format
func CreateRSATestKey(bits int, password []byte, pemencode bool) ([]byte, []byte, error) {
key, err := CreateRSAKey(bits)
if err != nil {
return nil, nil, err
}
pubData, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
if err != nil {
return nil, nil, errors.Wrap(err, "x509.MarshalPKIXPublicKey failed")
}
privData := x509.MarshalPKCS1PrivateKey(key)
// no more encoding needed for DER
if !pemencode {
return pubData, privData, nil
}
publicKey := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: pubData,
})
var block *pem.Block
typ := "RSA PRIVATE KEY"
if len(password) > 0 {
block, err = x509.EncryptPEMBlock(rand.Reader, typ, privData, password, x509.PEMCipherAES256)
if err != nil {
return nil, nil, errors.Wrap(err, "x509.EncryptPEMBlock failed")
}
} else {
block = &pem.Block{
Type: typ,
Bytes: privData,
}
}
privateKey := pem.EncodeToMemory(block)
return publicKey, privateKey, nil
}
// CreateECDSATestKey creates and elliptic curve key for the given curve and returns
// the public and private key in DER format
func CreateECDSATestKey(curve elliptic.Curve) ([]byte, []byte, error) {
key, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, nil, errors.Wrapf(err, "ecdsa.GenerateKey failed")
}
pubData, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
if err != nil {
return nil, nil, errors.Wrapf(err, "x509.MarshalPKIXPublicKey failed")
}
privData, err := x509.MarshalECPrivateKey(key)
if err != nil {
return nil, nil, errors.Wrapf(err, "x509.MarshalECPrivateKey failed")
}
return pubData, privData, nil
}
// CreateTestCA creates a root CA for testing
func CreateTestCA() (*rsa.PrivateKey, *x509.Certificate, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, errors.Wrap(err, "rsa.GenerateKey failed")
}
ca := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: "test-ca",
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0),
IsCA: true,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
caCert, err := certifyKey(&key.PublicKey, ca, key, ca)
return key, caCert, err
}
// CertifyKey certifies a public key using the given CA's private key and cert;
// The certificate template for the public key is optional
func CertifyKey(pubbytes []byte, template *x509.Certificate, caKey *rsa.PrivateKey, caCert *x509.Certificate) (*x509.Certificate, error) {
pubKey, err := ParsePublicKey(pubbytes, "CertifyKey")
if err != nil {
return nil, err
}
return certifyKey(pubKey, template, caKey, caCert)
}
func certifyKey(pub interface{}, template *x509.Certificate, caKey *rsa.PrivateKey, caCert *x509.Certificate) (*x509.Certificate, error) {
if template == nil {
template = &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: "testkey",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour),
IsCA: false,
KeyUsage: x509.KeyUsageDigitalSignature,
BasicConstraintsValid: true,
}
}
certDER, err := x509.CreateCertificate(rand.Reader, template, caCert, pub, caKey)
if err != nil {
return nil, errors.Wrap(err, "x509.CreateCertificate failed")
}
cert, err := x509.ParseCertificate(certDER)
if err != nil {
return nil, errors.Wrap(err, "x509.ParseCertificate failed")
}
return cert, nil
}

View File

@@ -0,0 +1,221 @@
/*
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 utils
import (
"bytes"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"strings"
"github.com/containerd/containerd/errdefs"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
json "gopkg.in/square/go-jose.v2"
)
// parseJWKPrivateKey parses the input byte array as a JWK and makes sure it's a private key
func parseJWKPrivateKey(privKey []byte, prefix string) (interface{}, error) {
jwk := json.JSONWebKey{}
err := jwk.UnmarshalJSON(privKey)
if err != nil {
return nil, errors.Wrapf(err, "%s: Could not parse input as JWK", prefix)
}
if jwk.IsPublic() {
return nil, fmt.Errorf("%s: JWK is not a private key", prefix)
}
return &jwk, nil
}
// parseJWKPublicKey parses the input byte array as a JWK
func parseJWKPublicKey(privKey []byte, prefix string) (interface{}, error) {
jwk := json.JSONWebKey{}
err := jwk.UnmarshalJSON(privKey)
if err != nil {
return nil, errors.Wrapf(err, "%s: Could not parse input as JWK", prefix)
}
if !jwk.IsPublic() {
return nil, fmt.Errorf("%s: JWK is not a public key", prefix)
}
return &jwk, nil
}
// IsPasswordError checks whether an error is related to a missing or wrong
// password
func IsPasswordError(err error) bool {
if err == nil {
return false
}
msg := strings.ToLower(err.Error())
return strings.Contains(msg, "password") &&
(strings.Contains(msg, "missing") || strings.Contains(msg, "wrong"))
}
// ParsePrivateKey tries to parse a private key in DER format first and
// PEM format after, returning an error if the parsing failed
func ParsePrivateKey(privKey, privKeyPassword []byte, prefix string) (interface{}, error) {
key, err := x509.ParsePKCS8PrivateKey(privKey)
if err != nil {
key, err = x509.ParsePKCS1PrivateKey(privKey)
if err != nil {
key, err = x509.ParseECPrivateKey(privKey)
}
}
if err != nil {
block, _ := pem.Decode(privKey)
if block != nil {
var der []byte
if x509.IsEncryptedPEMBlock(block) {
if privKeyPassword == nil {
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%s: Missing password for encrypted private key", prefix)
}
der, err = x509.DecryptPEMBlock(block, privKeyPassword)
if err != nil {
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%s: Wrong password: could not decrypt private key", prefix)
}
} else {
der = block.Bytes
}
key, err = x509.ParsePKCS8PrivateKey(der)
if err != nil {
key, err = x509.ParsePKCS1PrivateKey(der)
if err != nil {
return nil, errors.Wrapf(err, "%s: Could not parse private key", prefix)
}
}
} else {
key, err = parseJWKPrivateKey(privKey, prefix)
}
}
return key, err
}
// IsPrivateKey returns true in case the given byte array represents a private key
// It returns an error if for example the password is wrong
func IsPrivateKey(data []byte, password []byte) (bool, error) {
_, err := ParsePrivateKey(data, password, "")
return err == nil, err
}
// ParsePublicKey tries to parse a public key in DER format first and
// PEM format after, returning an error if the parsing failed
func ParsePublicKey(pubKey []byte, prefix string) (interface{}, error) {
key, err := x509.ParsePKIXPublicKey(pubKey)
if err != nil {
block, _ := pem.Decode(pubKey)
if block != nil {
key, err = x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, errors.Wrapf(err, "%s: Could not parse public key", prefix)
}
} else {
key, err = parseJWKPublicKey(pubKey, prefix)
}
}
return key, err
}
// IsPublicKey returns true in case the given byte array represents a public key
func IsPublicKey(data []byte) bool {
_, err := ParsePublicKey(data, "")
return err == nil
}
// ParseCertificate tries to parse a public key in DER format first and
// PEM format after, returning an error if the parsing failed
func ParseCertificate(certBytes []byte, prefix string) (*x509.Certificate, error) {
x509Cert, err := x509.ParseCertificate(certBytes)
if err != nil {
block, _ := pem.Decode(certBytes)
if block == nil {
return nil, fmt.Errorf("%s: Could not PEM decode x509 certificate", prefix)
}
x509Cert, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, errors.Wrapf(err, "%s: Could not parse x509 certificate", prefix)
}
}
return x509Cert, err
}
// IsCertificate returns true in case the given byte array represents an x.509 certificate
func IsCertificate(data []byte) bool {
_, err := ParseCertificate(data, "")
return err == nil
}
// IsGPGPrivateKeyRing returns true in case the given byte array represents a GPG private key ring file
func IsGPGPrivateKeyRing(data []byte) bool {
r := bytes.NewBuffer(data)
_, err := openpgp.ReadKeyRing(r)
return err == nil
}
// SortDecryptionKeys parses a list of comma separated base64 entries and sorts the data into
// a map. Each entry in the list may be either a GPG private key ring, private key, or x.509
// certificate
func SortDecryptionKeys(b64ItemList string) (map[string][][]byte, error) {
dcparameters := make(map[string][][]byte)
for _, b64Item := range strings.Split(b64ItemList, ",") {
var password []byte
b64Data := strings.Split(b64Item, ":")
keyData, err := base64.StdEncoding.DecodeString(b64Data[0])
if err != nil {
return nil, errors.New("Could not base64 decode a passed decryption key")
}
if len(b64Data) == 2 {
password, err = base64.StdEncoding.DecodeString(b64Data[1])
if err != nil {
return nil, errors.New("Could not base64 decode a passed decryption key password")
}
}
var key string
isPrivKey, err := IsPrivateKey(keyData, password)
if IsPasswordError(err) {
return nil, err
}
if isPrivKey {
key = "privkeys"
if _, ok := dcparameters["privkeys-passwords"]; !ok {
dcparameters["privkeys-passwords"] = [][]byte{password}
} else {
dcparameters["privkeys-passwords"] = append(dcparameters["privkeys-passwords"], password)
}
} else if IsCertificate(keyData) {
key = "x509s"
} else if IsGPGPrivateKeyRing(keyData) {
key = "gpg-privatekeys"
}
if key != "" {
values := dcparameters[key]
if values == nil {
dcparameters[key] = [][]byte{keyData}
} else {
dcparameters[key] = append(dcparameters[key], keyData)
}
} else {
return nil, errors.New("Unknown decryption key type")
}
}
return dcparameters, nil
}