diff options
Diffstat (limited to 'vendor/github.com/theupdateframework/go-tuf/encrypted/encrypted.go')
-rw-r--r-- | vendor/github.com/theupdateframework/go-tuf/encrypted/encrypted.go | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/vendor/github.com/theupdateframework/go-tuf/encrypted/encrypted.go b/vendor/github.com/theupdateframework/go-tuf/encrypted/encrypted.go new file mode 100644 index 000000000..4d174d61f --- /dev/null +++ b/vendor/github.com/theupdateframework/go-tuf/encrypted/encrypted.go @@ -0,0 +1,226 @@ +// Package encrypted provides a simple, secure system for encrypting data +// symmetrically with a passphrase. +// +// It uses scrypt derive a key from the passphrase and the NaCl secret box +// cipher for authenticated encryption. +package encrypted + +import ( + "crypto/rand" + "encoding/json" + "errors" + "fmt" + "io" + + "golang.org/x/crypto/nacl/secretbox" + "golang.org/x/crypto/scrypt" +) + +const saltSize = 32 + +const ( + boxKeySize = 32 + boxNonceSize = 24 +) + +const ( + // N parameter was chosen to be ~100ms of work using the default implementation + // on the 2.3GHz Core i7 Haswell processor in a late-2013 Apple Retina Macbook + // Pro (it takes ~113ms). + scryptN = 32768 + scryptR = 8 + scryptP = 1 +) + +const ( + nameScrypt = "scrypt" + nameSecretBox = "nacl/secretbox" +) + +type data struct { + KDF scryptKDF `json:"kdf"` + Cipher secretBoxCipher `json:"cipher"` + Ciphertext []byte `json:"ciphertext"` +} + +type scryptParams struct { + N int `json:"N"` + R int `json:"r"` + P int `json:"p"` +} + +func newScryptKDF() (scryptKDF, error) { + salt := make([]byte, saltSize) + if err := fillRandom(salt); err != nil { + return scryptKDF{}, err + } + return scryptKDF{ + Name: nameScrypt, + Params: scryptParams{ + N: scryptN, + R: scryptR, + P: scryptP, + }, + Salt: salt, + }, nil +} + +type scryptKDF struct { + Name string `json:"name"` + Params scryptParams `json:"params"` + Salt []byte `json:"salt"` +} + +func (s *scryptKDF) Key(passphrase []byte) ([]byte, error) { + return scrypt.Key(passphrase, s.Salt, s.Params.N, s.Params.R, s.Params.P, boxKeySize) +} + +// CheckParams checks that the encoded KDF parameters are what we expect them to +// be. If we do not do this, an attacker could cause a DoS by tampering with +// them. +func (s *scryptKDF) CheckParams() error { + if s.Params.N != scryptN || s.Params.R != scryptR || s.Params.P != scryptP { + return errors.New("encrypted: unexpected kdf parameters") + } + return nil +} + +func newSecretBoxCipher() (secretBoxCipher, error) { + nonce := make([]byte, boxNonceSize) + if err := fillRandom(nonce); err != nil { + return secretBoxCipher{}, err + } + return secretBoxCipher{ + Name: nameSecretBox, + Nonce: nonce, + }, nil +} + +type secretBoxCipher struct { + Name string `json:"name"` + Nonce []byte `json:"nonce"` + + encrypted bool +} + +func (s *secretBoxCipher) Encrypt(plaintext, key []byte) []byte { + var keyBytes [boxKeySize]byte + var nonceBytes [boxNonceSize]byte + + if len(key) != len(keyBytes) { + panic("incorrect key size") + } + if len(s.Nonce) != len(nonceBytes) { + panic("incorrect nonce size") + } + + copy(keyBytes[:], key) + copy(nonceBytes[:], s.Nonce) + + // ensure that we don't re-use nonces + if s.encrypted { + panic("Encrypt must only be called once for each cipher instance") + } + s.encrypted = true + + return secretbox.Seal(nil, plaintext, &nonceBytes, &keyBytes) +} + +func (s *secretBoxCipher) Decrypt(ciphertext, key []byte) ([]byte, error) { + var keyBytes [boxKeySize]byte + var nonceBytes [boxNonceSize]byte + + if len(key) != len(keyBytes) { + panic("incorrect key size") + } + if len(s.Nonce) != len(nonceBytes) { + // return an error instead of panicking since the nonce is user input + return nil, errors.New("encrypted: incorrect nonce size") + } + + copy(keyBytes[:], key) + copy(nonceBytes[:], s.Nonce) + + res, ok := secretbox.Open(nil, ciphertext, &nonceBytes, &keyBytes) + if !ok { + return nil, errors.New("encrypted: decryption failed") + } + return res, nil +} + +// Encrypt takes a passphrase and plaintext, and returns a JSON object +// containing ciphertext and the details necessary to decrypt it. +func Encrypt(plaintext, passphrase []byte) ([]byte, error) { + k, err := newScryptKDF() + if err != nil { + return nil, err + } + key, err := k.Key(passphrase) + if err != nil { + return nil, err + } + + c, err := newSecretBoxCipher() + if err != nil { + return nil, err + } + + data := &data{ + KDF: k, + Cipher: c, + } + data.Ciphertext = c.Encrypt(plaintext, key) + + return json.Marshal(data) +} + +// Marshal encrypts the JSON encoding of v using passphrase. +func Marshal(v interface{}, passphrase []byte) ([]byte, error) { + data, err := json.MarshalIndent(v, "", "\t") + if err != nil { + return nil, err + } + return Encrypt(data, passphrase) +} + +// Decrypt takes a JSON-encoded ciphertext object encrypted using Encrypt and +// tries to decrypt it using passphrase. If successful, it returns the +// plaintext. +func Decrypt(ciphertext, passphrase []byte) ([]byte, error) { + data := &data{} + if err := json.Unmarshal(ciphertext, data); err != nil { + return nil, err + } + + if data.KDF.Name != nameScrypt { + return nil, fmt.Errorf("encrypted: unknown kdf name %q", data.KDF.Name) + } + if data.Cipher.Name != nameSecretBox { + return nil, fmt.Errorf("encrypted: unknown cipher name %q", data.Cipher.Name) + } + if err := data.KDF.CheckParams(); err != nil { + return nil, err + } + + key, err := data.KDF.Key(passphrase) + if err != nil { + return nil, err + } + + return data.Cipher.Decrypt(data.Ciphertext, key) +} + +// Unmarshal decrypts the data using passphrase and unmarshals the resulting +// plaintext into the value pointed to by v. +func Unmarshal(data []byte, v interface{}, passphrase []byte) error { + decrypted, err := Decrypt(data, passphrase) + if err != nil { + return err + } + return json.Unmarshal(decrypted, v) +} + +func fillRandom(b []byte) error { + _, err := io.ReadFull(rand.Reader, b) + return err +} |