Crypto library movement and changes to content helper interfaces
Signed-off-by: Derek McGowan <derek@mcgstyle.net>
This commit is contained in:
committed by
Brandon Lum
parent
bf8804c743
commit
dde436e65b
114
pkg/encryption/blockcipher/blockcipher.go
Normal file
114
pkg/encryption/blockcipher/blockcipher.go
Normal 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
|
||||
}
|
||||
194
pkg/encryption/blockcipher/blockcipher_siv.go
Normal file
194
pkg/encryption/blockcipher/blockcipher_siv.go
Normal 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
|
||||
}
|
||||
163
pkg/encryption/blockcipher/blockcipher_siv_test.go
Normal file
163
pkg/encryption/blockcipher/blockcipher_siv_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
115
pkg/encryption/blockcipher/blockcipher_test.go
Normal file
115
pkg/encryption/blockcipher/blockcipher_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
40
pkg/encryption/config/config.go
Normal file
40
pkg/encryption/config/config.go
Normal 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
|
||||
}
|
||||
244
pkg/encryption/encryption.go
Normal file
244
pkg/encryption/encryption.go
Normal 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
|
||||
}
|
||||
137
pkg/encryption/encryption_test.go
Normal file
137
pkg/encryption/encryption_test.go
Normal 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
453
pkg/encryption/gpg.go
Normal 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
100
pkg/encryption/gpgvault.go
Normal 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
|
||||
}
|
||||
132
pkg/encryption/keywrap/jwe/keywrapper_jwe.go
Normal file
132
pkg/encryption/keywrap/jwe/keywrapper_jwe.go
Normal 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
|
||||
}
|
||||
345
pkg/encryption/keywrap/jwe/keywrapper_jwe_test.go
Normal file
345
pkg/encryption/keywrap/jwe/keywrapper_jwe_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
40
pkg/encryption/keywrap/keywrap.go
Normal file
40
pkg/encryption/keywrap/keywrap.go
Normal 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)
|
||||
}
|
||||
270
pkg/encryption/keywrap/pgp/keywrapper_gpg.go
Normal file
270
pkg/encryption/keywrap/pgp/keywrapper_gpg.go
Normal 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
|
||||
}
|
||||
190
pkg/encryption/keywrap/pgp/keywrapper_gpg_test.go
Normal file
190
pkg/encryption/keywrap/pgp/keywrapper_gpg_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
87
pkg/encryption/keywrap/pgp/testingkeys_test.go
Normal file
87
pkg/encryption/keywrap/pgp/testingkeys_test.go
Normal 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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`))
|
||||
)
|
||||
132
pkg/encryption/keywrap/pkcs7/keywrapper_pkcs7.go
Normal file
132
pkg/encryption/keywrap/pkcs7/keywrapper_pkcs7.go
Normal 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
|
||||
}
|
||||
254
pkg/encryption/keywrap/pkcs7/keywrapper_pkcs7_test.go
Normal file
254
pkg/encryption/keywrap/pkcs7/keywrapper_pkcs7_test.go
Normal 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
40
pkg/encryption/reader.go
Normal 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
|
||||
}
|
||||
166
pkg/encryption/utils/testing.go
Normal file
166
pkg/encryption/utils/testing.go
Normal 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
|
||||
}
|
||||
221
pkg/encryption/utils/utils.go
Normal file
221
pkg/encryption/utils/utils.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user