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,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(`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
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")
}
}