mirror of
https://github.com/containers/skopeo.git
synced 2025-09-25 12:16:17 +00:00
Image encryption/decryption support in skopeo
Signed-off-by: Harshal Patil <harshal.patil@in.ibm.com> Signed-off-by: Brandon Lum <lumjjb@gmail.com>
This commit is contained in:
109
vendor/github.com/containers/ocicrypt/utils/delayedreader.go
generated
vendored
Normal file
109
vendor/github.com/containers/ocicrypt/utils/delayedreader.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
Copyright The ocicrypt 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 (
|
||||
"io"
|
||||
)
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// DelayedReader wraps a io.Reader and allows a client to use the Reader
|
||||
// interface. The DelayedReader holds back some buffer to the client
|
||||
// so that it can report any error that occurred on the Reader it wraps
|
||||
// early to the client while it may still have held some data back.
|
||||
type DelayedReader struct {
|
||||
reader io.Reader // Reader to Read() bytes from and delay them
|
||||
err error // error that occurred on the reader
|
||||
buffer []byte // delay buffer
|
||||
bufbytes int // number of bytes in the delay buffer to give to Read(); on '0' we return 'EOF' to caller
|
||||
bufoff int // offset in the delay buffer to give to Read()
|
||||
}
|
||||
|
||||
// NewDelayedReader wraps a io.Reader and allocates a delay buffer of bufsize bytes
|
||||
func NewDelayedReader(reader io.Reader, bufsize uint) io.Reader {
|
||||
return &DelayedReader{
|
||||
reader: reader,
|
||||
buffer: make([]byte, bufsize),
|
||||
}
|
||||
}
|
||||
|
||||
// Read implements the io.Reader interface
|
||||
func (dr *DelayedReader) Read(p []byte) (int, error) {
|
||||
if dr.err != nil && dr.err != io.EOF {
|
||||
return 0, dr.err
|
||||
}
|
||||
|
||||
// if we are completely drained, return io.EOF
|
||||
if dr.err == io.EOF && dr.bufbytes == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
// only at the beginning we fill our delay buffer in an extra step
|
||||
if dr.bufbytes < len(dr.buffer) && dr.err == nil {
|
||||
dr.bufbytes, dr.err = FillBuffer(dr.reader, dr.buffer)
|
||||
if dr.err != nil && dr.err != io.EOF {
|
||||
return 0, dr.err
|
||||
}
|
||||
}
|
||||
// dr.err != nil means we have EOF and can drain the delay buffer
|
||||
// otherwise we need to still read from the reader
|
||||
|
||||
var tmpbuf []byte
|
||||
tmpbufbytes := 0
|
||||
if dr.err == nil {
|
||||
tmpbuf = make([]byte, len(p))
|
||||
tmpbufbytes, dr.err = FillBuffer(dr.reader, tmpbuf)
|
||||
if dr.err != nil && dr.err != io.EOF {
|
||||
return 0, dr.err
|
||||
}
|
||||
}
|
||||
|
||||
// copy out of the delay buffer into 'p'
|
||||
tocopy1 := min(len(p), dr.bufbytes)
|
||||
c1 := copy(p[:tocopy1], dr.buffer[dr.bufoff:])
|
||||
dr.bufoff += c1
|
||||
dr.bufbytes -= c1
|
||||
|
||||
c2 := 0
|
||||
// can p still hold more data?
|
||||
if c1 < len(p) {
|
||||
// copy out of the tmpbuf into 'p'
|
||||
c2 = copy(p[tocopy1:], tmpbuf[:tmpbufbytes])
|
||||
}
|
||||
|
||||
// if tmpbuf holds data we need to hold onto, copy them
|
||||
// into the delay buffer
|
||||
if tmpbufbytes-c2 > 0 {
|
||||
// left-shift the delay buffer and append the tmpbuf's remaining data
|
||||
dr.buffer = dr.buffer[dr.bufoff : dr.bufoff+dr.bufbytes]
|
||||
dr.buffer = append(dr.buffer, tmpbuf[c2:tmpbufbytes]...)
|
||||
dr.bufoff = 0
|
||||
dr.bufbytes = len(dr.buffer)
|
||||
}
|
||||
|
||||
var err error
|
||||
if dr.bufbytes == 0 {
|
||||
err = io.EOF
|
||||
}
|
||||
return c1 + c2, err
|
||||
}
|
31
vendor/github.com/containers/ocicrypt/utils/ioutils.go
generated
vendored
Normal file
31
vendor/github.com/containers/ocicrypt/utils/ioutils.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright The ocicrypt 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 (
|
||||
"io"
|
||||
)
|
||||
|
||||
// FillBuffer fills the given buffer with as many bytes from the reader as possible. It returns
|
||||
// EOF if an EOF was encountered or any other error.
|
||||
func FillBuffer(reader io.Reader, buffer []byte) (int, error) {
|
||||
n, err := io.ReadFull(reader, buffer)
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
return n, io.EOF
|
||||
}
|
||||
return n, err
|
||||
}
|
166
vendor/github.com/containers/ocicrypt/utils/testing.go
generated
vendored
Normal file
166
vendor/github.com/containers/ocicrypt/utils/testing.go
generated
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
Copyright The ocicrypt 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
|
||||
}
|
220
vendor/github.com/containers/ocicrypt/utils/utils.go
generated
vendored
Normal file
220
vendor/github.com/containers/ocicrypt/utils/utils.go
generated
vendored
Normal file
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
Copyright The ocicrypt 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/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.Errorf("%s: Missing password for encrypted private key", prefix)
|
||||
}
|
||||
der, err = x509.DecryptPEMBlock(block, privKeyPassword)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("%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