package encryption import ( cryptorand "crypto/rand" "encoding/base64" "fmt" "io" "strings" "github.com/docker/swarmkit/api" "github.com/gogo/protobuf/proto" "github.com/pkg/errors" ) // This package defines the interfaces and encryption package const humanReadablePrefix = "SWMKEY-1-" // ErrCannotDecrypt is the type of error returned when some data cannot be decryptd as plaintext type ErrCannotDecrypt struct { msg string } func (e ErrCannotDecrypt) Error() string { return e.msg } // A Decrypter can decrypt an encrypted record type Decrypter interface { Decrypt(api.MaybeEncryptedRecord) ([]byte, error) } // A Encrypter can encrypt some bytes into an encrypted record type Encrypter interface { Encrypt(data []byte) (*api.MaybeEncryptedRecord, error) } type noopCrypter struct{} func (n noopCrypter) Decrypt(e api.MaybeEncryptedRecord) ([]byte, error) { if e.Algorithm != n.Algorithm() { return nil, fmt.Errorf("record is encrypted") } return e.Data, nil } func (n noopCrypter) Encrypt(data []byte) (*api.MaybeEncryptedRecord, error) { return &api.MaybeEncryptedRecord{ Algorithm: n.Algorithm(), Data: data, }, nil } func (n noopCrypter) Algorithm() api.MaybeEncryptedRecord_Algorithm { return api.MaybeEncryptedRecord_NotEncrypted } // NoopCrypter is just a pass-through crypter - it does not actually encrypt or // decrypt any data var NoopCrypter = noopCrypter{} // Decrypt turns a slice of bytes serialized as an MaybeEncryptedRecord into a slice of plaintext bytes func Decrypt(encryptd []byte, decrypter Decrypter) ([]byte, error) { if decrypter == nil { return nil, ErrCannotDecrypt{msg: "no decrypter specified"} } r := api.MaybeEncryptedRecord{} if err := proto.Unmarshal(encryptd, &r); err != nil { // nope, this wasn't marshalled as a MaybeEncryptedRecord return nil, ErrCannotDecrypt{msg: "unable to unmarshal as MaybeEncryptedRecord"} } plaintext, err := decrypter.Decrypt(r) if err != nil { return nil, ErrCannotDecrypt{msg: err.Error()} } return plaintext, nil } // Encrypt turns a slice of bytes into a serialized MaybeEncryptedRecord slice of bytes func Encrypt(plaintext []byte, encrypter Encrypter) ([]byte, error) { if encrypter == nil { return nil, fmt.Errorf("no encrypter specified") } encryptedRecord, err := encrypter.Encrypt(plaintext) if err != nil { return nil, errors.Wrap(err, "unable to encrypt data") } data, err := proto.Marshal(encryptedRecord) if err != nil { return nil, errors.Wrap(err, "unable to marshal as MaybeEncryptedRecord") } return data, nil } // Defaults returns a default encrypter and decrypter func Defaults(key []byte) (Encrypter, Decrypter) { n := NewNACLSecretbox(key) return n, n } // GenerateSecretKey generates a secret key that can be used for encrypting data // using this package func GenerateSecretKey() []byte { secretData := make([]byte, naclSecretboxKeySize) if _, err := io.ReadFull(cryptorand.Reader, secretData); err != nil { // panic if we can't read random data panic(errors.Wrap(err, "failed to read random bytes")) } return secretData } // HumanReadableKey displays a secret key in a human readable way func HumanReadableKey(key []byte) string { // base64-encode the key return humanReadablePrefix + base64.RawStdEncoding.EncodeToString(key) } // ParseHumanReadableKey returns a key as bytes from recognized serializations of // said keys func ParseHumanReadableKey(key string) ([]byte, error) { if !strings.HasPrefix(key, humanReadablePrefix) { return nil, fmt.Errorf("invalid key string") } keyBytes, err := base64.RawStdEncoding.DecodeString(strings.TrimPrefix(key, humanReadablePrefix)) if err != nil { return nil, fmt.Errorf("invalid key string") } return keyBytes, nil }