commit f3997de0ff6b8eeed1deba9d7d8177fc6b42a392
parent a3163291d014bea9da99d86b83db224d9825d122
Author: Sean Enck <sean@ttypty.com>
Date: Sun, 18 Sep 2022 12:55:18 -0400
Revert multi-algorithm approach, only support one-at-a-time and do upgrades based on internal versions in headers
This reverts commit bbf1500d9af9d506e2ce3eb85a06147cef28f195.
This reverts commit 29f33c40c0730ea3e44becb7538ae15a21bdf97c.
This reverts commit ee21302f4b0f982ed584de87f62ca72ca8d43355.
This reverts commit 837ee077d0bf4a661352e7d02269426d66a10772.
This reverts commit e1dfc07c5f6e8b866c0cbbaf28960250bfea72a8.
This reverts commit 7d58e2af71dc769e9dc4857f087bfcc7ecc4f44f.
This reverts commit ba58b4335a615c345b538a1d306c6098bf2f0d3a.
This reverts commit 91e2581a09e8ed19c2611006689095ad5211ac59.
Diffstat:
9 files changed, 56 insertions(+), 332 deletions(-)
diff --git a/internal/encrypt/aesgcm.go b/internal/encrypt/aesgcm.go
@@ -1,82 +0,0 @@
-package encrypt
-
-import (
- "crypto/aes"
- "crypto/cipher"
- "crypto/rand"
- "io"
-)
-
-const (
- aesGCMAlgorithmSaltLength = 32
-)
-
-type (
- aesGCMAlgorithm struct {
- }
-)
-
-func (a aesGCMAlgorithm) version() algorithmVersions {
- return aesGCMAlgorithmVersion
-}
-
-func (a aesGCMAlgorithm) name() string {
- return "aesgcm"
-}
-
-func newCipher(key []byte, salt []byte) (cipher.Block, error) {
- useKey, err := pad(salt, key)
- if err != nil {
- return nil, err
- }
- return aes.NewCipher(useKey[:])
-}
-
-func (a aesGCMAlgorithm) encrypt(key, data []byte) ([]byte, error) {
- var salt [aesGCMAlgorithmSaltLength]byte
- if _, err := io.ReadFull(rand.Reader, salt[:]); err != nil {
- return nil, err
- }
- c, err := newCipher(key, salt[:])
- if err != nil {
- return nil, err
- }
-
- gcm, err := cipher.NewGCM(c)
- if err != nil {
- return nil, err
- }
-
- nonce := make([]byte, gcm.NonceSize())
- if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
- return nil, err
- }
- b := gcm.Seal(nonce, nonce, data, nil)
- var d []byte
- d = append(d, salt[:]...)
- d = append(d, b...)
- return d, nil
-}
-
-func (a aesGCMAlgorithm) dataSize() int {
- return aesGCMAlgorithmSaltLength
-}
-
-func (a aesGCMAlgorithm) decrypt(key, encrypted []byte) ([]byte, error) {
- var salt [aesGCMAlgorithmSaltLength]byte
- copy(salt[:], encrypted[0:aesGCMAlgorithmSaltLength])
- c, err := newCipher(key, salt[:])
- if err != nil {
- return nil, err
- }
- data := encrypted[aesGCMAlgorithmSaltLength:]
- gcm, err := cipher.NewGCM(c)
- if err != nil {
- return nil, err
- }
-
- nonceSize := gcm.NonceSize()
- nonce := data[:nonceSize]
- datum := data[nonceSize:]
- return gcm.Open(nil, nonce, datum, nil)
-}
diff --git a/internal/encrypt/core.go b/internal/encrypt/core.go
@@ -11,91 +11,37 @@ import (
"time"
"github.com/enckse/lockbox/internal/inputs"
+ "golang.org/x/crypto/nacl/secretbox"
"golang.org/x/crypto/pbkdf2"
)
const (
- keyLength = 32
- algorithmBaseVersion = 0
- padLength = 256
-)
-
-const (
- noopBoxAlgorithVersion algorithmVersions = iota
- secretBoxAlgorithmVersion
- aesGCMAlgorithmVersion
+ keyLength = 32
+ nonceLength = 24
+ padLength = 256
+ saltLength = 16
)
var (
- defaultAlgorithm = secretBoxAlgorithm{}
- algorithms = []algorithm{defaultAlgorithm, aesGCMAlgorithm{}}
+ cryptoVers = []byte{0, 1}
+ cryptoVersLength = len(cryptoVers)
)
type (
- algorithmVersions uint8
// Lockbox represents a method to encrypt/decrypt locked files.
Lockbox struct {
secret [keyLength]byte
file string
- algo string
}
// LockboxOptions represent options to create a lockbox from.
LockboxOptions struct {
- Key string
- KeyMode string
- File string
- Algorithm string
- }
- algorithm interface {
- encrypt(k, d []byte) ([]byte, error)
- decrypt(k, d []byte) ([]byte, error)
- version() algorithmVersions
- name() string
- dataSize() int
+ Key string
+ KeyMode string
+ File string
}
)
-func init() {
- random.Seed(time.Now().UnixNano())
-}
-
-func newAlgorithmFromVersion(vers algorithmVersions) algorithm {
- for _, a := range algorithms {
- if a.version() == vers {
- return a
- }
- }
- return nil
-}
-
-func newAlgorithm(mode string) algorithm {
- useMode := mode
- if mode == "" {
- useMode = inputs.EnvOrDefault(inputs.EncryptModeEnv, defaultAlgorithm.name())
- }
- for _, a := range algorithms {
- if useMode == a.name() {
- return a
- }
- }
- return nil
-}
-
-func algoVersion(v uint8) []byte {
- return []byte{algorithmBaseVersion, v}
-}
-
-func pad(salt, key []byte) ([keyLength]byte, error) {
- d := pbkdf2.Key(key, salt, 4096, keyLength, sha512.New)
- if len(d) != keyLength {
- return [keyLength]byte{}, errors.New("invalid key result from pad")
- }
- var obj [keyLength]byte
- copy(obj[:], d[:keyLength])
- return obj, nil
-}
-
// FromFile decrypts a file-system based encrypted file.
func FromFile(file string) ([]byte, error) {
l, err := NewLockbox(LockboxOptions{File: file})
@@ -116,21 +62,40 @@ func ToFile(file string, data []byte) error {
// NewLockbox creates a new usable lockbox instance.
func NewLockbox(options LockboxOptions) (Lockbox, error) {
- return newLockbox(options.Key, options.KeyMode, options.File, options.Algorithm)
+ return newLockbox(options.Key, options.KeyMode, options.File)
}
-func newLockbox(key, keyMode, file, algo string) (Lockbox, error) {
+func newLockbox(key, keyMode, file string) (Lockbox, error) {
b, err := inputs.GetKey(key, keyMode)
if err != nil {
return Lockbox{}, err
}
var secretKey [keyLength]byte
copy(secretKey[:], b)
- return Lockbox{secret: secretKey, file: file, algo: algo}, nil
+ return Lockbox{secret: secretKey, file: file}, nil
+}
+
+func pad(salt, key []byte) ([keyLength]byte, error) {
+ d := pbkdf2.Key(key, salt, 4096, keyLength, sha512.New)
+ if len(d) != keyLength {
+ return [keyLength]byte{}, errors.New("invalid key result from pad")
+ }
+ var obj [keyLength]byte
+ copy(obj[:], d[:keyLength])
+ return obj, nil
+}
+
+func init() {
+ random.Seed(time.Now().UnixNano())
}
// Encrypt will encrypt contents to file.
func (l Lockbox) Encrypt(datum []byte) error {
+ var nonce [nonceLength]byte
+ padTo := random.Intn(padLength)
+ if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
+ return err
+ }
data := datum
if data == nil {
b, err := inputs.RawStdin()
@@ -139,58 +104,49 @@ func (l Lockbox) Encrypt(datum []byte) error {
}
data = b
}
- if len(data) == 0 {
- return errors.New("no data given")
- }
- padTo := random.Intn(padLength)
var padding [padLength]byte
if _, err := io.ReadFull(rand.Reader, padding[:]); err != nil {
return err
}
- box := newAlgorithm(l.algo)
- if box == nil {
- return errors.New("unknown algorithm detected")
+ var salt [saltLength]byte
+ if _, err := io.ReadFull(rand.Reader, salt[:]); err != nil {
+ return err
}
var write []byte
write = append(write, byte(padTo))
write = append(write, padding[0:padTo]...)
write = append(write, data...)
- b, err := box.encrypt(l.secret[:], write)
+ key, err := pad(salt[:], l.secret[:])
if err != nil {
return err
}
+ encrypted := secretbox.Seal(nonce[:], write, &nonce, &key)
var persist []byte
- algo := algoVersion(uint8(box.version()))
- persist = append(persist, algo...)
- persist = append(persist, b...)
+ persist = append(persist, cryptoVers...)
+ persist = append(persist, salt[:]...)
+ persist = append(persist, encrypted...)
return os.WriteFile(l.file, persist, 0600)
}
// Decrypt will decrypt an object from file.
func (l Lockbox) Decrypt() ([]byte, error) {
+ var nonce [nonceLength]byte
+ var salt [saltLength]byte
encrypted, err := os.ReadFile(l.file)
if err != nil {
return nil, err
}
- version := len(algoVersion(0))
- if len(encrypted) <= version {
- return nil, errors.New("invalid decryption data")
- }
- data := encrypted[version:]
- if encrypted[0] != algorithmBaseVersion {
- return nil, errors.New("unknown input data header")
- }
- box := newAlgorithmFromVersion(algorithmVersions(encrypted[1]))
- if box == nil {
- return nil, errors.New("unable to detect algorithm")
- }
- if len(data) <= box.dataSize() {
- return nil, errors.New("data is invalid for decryption")
- }
- decrypted, err := box.decrypt(l.secret[:], data)
+ copy(salt[:], encrypted[cryptoVersLength:saltLength+cryptoVersLength])
+ copy(nonce[:], encrypted[cryptoVersLength+saltLength:cryptoVersLength+saltLength+nonceLength])
+ key, err := pad(salt[:], l.secret[:])
if err != nil {
return nil, err
}
+ decrypted, ok := secretbox.Open(nil, encrypted[cryptoVersLength+saltLength+nonceLength:], &nonce, &key)
+ if !ok {
+ return nil, errors.New("decrypt not ok")
+ }
+
padding := int(decrypted[0])
return decrypted[1+padding:], nil
}
diff --git a/internal/encrypt/core_test.go b/internal/encrypt/core_test.go
@@ -91,75 +91,3 @@ func TestEncryptDecryptPlainText(t *testing.T) {
t.Error("data mismatch")
}
}
-
-func TestEncryptDecryptSecretBox(t *testing.T) {
- e, err := encrypt.NewLockbox(encrypt.LockboxOptions{Key: "plain", KeyMode: inputs.PlainKeyMode, File: setupData(t), Algorithm: "secretbox"})
- if err != nil {
- t.Errorf("failed to create lockbox: %v", err)
- }
- data := []byte("datum")
- if err := e.Encrypt(data); err != nil {
- t.Errorf("failed to encrypt: %v", err)
- }
- d, err := e.Decrypt()
- if err != nil {
- t.Errorf("failed to decrypt: %v", err)
- }
- if string(d) != string(data) {
- t.Error("data mismatch")
- }
-}
-
-func TestEncryptDecryptAESBox(t *testing.T) {
- e, err := encrypt.NewLockbox(encrypt.LockboxOptions{Key: "plain", KeyMode: inputs.PlainKeyMode, File: setupData(t), Algorithm: "aesgcm"})
- if err != nil {
- t.Errorf("failed to create lockbox: %v", err)
- }
- data := []byte("datum")
- if err := e.Encrypt(data); err != nil {
- t.Errorf("failed to encrypt: %v", err)
- }
- d, err := e.Decrypt()
- if err != nil {
- t.Errorf("failed to decrypt: %v", err)
- }
- if string(d) != string(data) {
- t.Error("data mismatch")
- }
-}
-
-func TestEncryptErrors(t *testing.T) {
- e, _ := encrypt.NewLockbox(encrypt.LockboxOptions{Key: "plain", KeyMode: inputs.PlainKeyMode, File: setupData(t)})
- if err := e.Encrypt([]byte{}); err.Error() != "no data given" {
- t.Errorf("no data expected: %v", err)
- }
- e, _ = encrypt.NewLockbox(encrypt.LockboxOptions{Key: "plain", KeyMode: inputs.PlainKeyMode, File: setupData(t), Algorithm: "bad"})
- if err := e.Encrypt([]byte{0, 10, 10, 10, 10, 10, 10, 10, 1}); err.Error() != "unknown algorithm detected" {
- t.Errorf("unknown algorithm expected: %v", err)
- }
-}
-
-func TestDecryptErrors(t *testing.T) {
- d := setupData(t)
- e, _ := encrypt.NewLockbox(encrypt.LockboxOptions{Key: "plain", KeyMode: inputs.PlainKeyMode, File: d})
- os.WriteFile(d, []byte{}, 0600)
- if _, err := e.Decrypt(); err.Error() != "invalid decryption data" {
- t.Errorf("failed to decrypt, invalid: %v", err)
- }
- os.WriteFile(d, []byte{1, 80, 1}, 0600)
- if _, err := e.Decrypt(); err.Error() != "unknown input data header" {
- t.Errorf("failed to decrypt, bad base: %v", err)
- }
- os.WriteFile(d, []byte{0, 80, 1}, 0600)
- if _, err := e.Decrypt(); err.Error() != "unable to detect algorithm" {
- t.Errorf("failed to decrypt, bad algorithm: %v", err)
- }
- os.WriteFile(d, []byte{0, 0, 1}, 0600)
- if _, err := e.Decrypt(); err.Error() != "unable to detect algorithm" {
- t.Errorf("failed to decrypt, bad algorithm: %v", err)
- }
- os.WriteFile(d, []byte{0, 1, 1}, 0600)
- if _, err := e.Decrypt(); err.Error() != "data is invalid for decryption" {
- t.Errorf("failed to decrypt, bad data: %v", err)
- }
-}
diff --git a/internal/encrypt/secretbox.go b/internal/encrypt/secretbox.go
@@ -1,65 +0,0 @@
-package encrypt
-
-import (
- "crypto/rand"
- "errors"
- "io"
-
- "golang.org/x/crypto/nacl/secretbox"
-)
-
-type (
- secretBoxAlgorithm struct {
- }
-)
-
-const (
- secretBoxAlgorithmNonceLength = 24
- secretBoxAlgorithmSaltLength = 16
-)
-
-func (s secretBoxAlgorithm) dataSize() int {
- return secretBoxAlgorithmSaltLength + secretBoxAlgorithmNonceLength
-}
-
-func (s secretBoxAlgorithm) name() string {
- return "secretbox"
-}
-
-func (s secretBoxAlgorithm) version() algorithmVersions {
- return secretBoxAlgorithmVersion
-}
-
-func (s secretBoxAlgorithm) encrypt(encryptKey, data []byte) ([]byte, error) {
- var nonce [secretBoxAlgorithmNonceLength]byte
- var salt [secretBoxAlgorithmSaltLength]byte
- if _, err := io.ReadFull(rand.Reader, salt[:]); err != nil {
- return nil, err
- }
- key, err := pad(salt[:], encryptKey[:])
- if err != nil {
- return nil, err
- }
- encrypted := secretbox.Seal(nonce[:], data, &nonce, &key)
- var persist []byte
- persist = append(persist, salt[:]...)
- persist = append(persist, encrypted...)
- return persist, nil
-}
-
-func (s secretBoxAlgorithm) decrypt(encryptKey, encrypted []byte) ([]byte, error) {
- var nonce [secretBoxAlgorithmNonceLength]byte
- var salt [secretBoxAlgorithmSaltLength]byte
- copy(salt[:], encrypted[0:secretBoxAlgorithmSaltLength])
- copy(nonce[:], encrypted[secretBoxAlgorithmSaltLength:secretBoxAlgorithmSaltLength+secretBoxAlgorithmNonceLength])
- key, err := pad(salt[:], encryptKey[:])
- if err != nil {
- return nil, err
- }
- decrypted, ok := secretbox.Open(nil, encrypted[secretBoxAlgorithmSaltLength+secretBoxAlgorithmNonceLength:], &nonce, &key)
- if !ok {
- return nil, errors.New("decrypt not ok")
- }
-
- return decrypted, nil
-}
diff --git a/internal/inputs/env.go b/internal/inputs/env.go
@@ -34,8 +34,6 @@ const (
ClipMaxEnv = prefixKey + "CLIPMAX"
// ColorBetweenEnv is a comma-delimited list of times to color totp outputs (e.g. 0:5,30:35 which is the default).
ColorBetweenEnv = prefixKey + "TOTPBETWEEN"
- // EncryptModeEnv indicates the underlying algorith to use for encryption.
- EncryptModeEnv = prefixKey + "ALGORITHM"
// PlainKeyMode is plaintext based key resolution.
PlainKeyMode = "plaintext"
// CommandKeyMode will run an external command to get the key (from stdout).
diff --git a/internal/subcommands/readwrite.go b/internal/subcommands/readwrite.go
@@ -16,12 +16,11 @@ func ReadWrite(args []string) error {
key := flags.String("key", "", "security key")
file := flags.String("file", "", "file to process")
keyMode := flags.String("keymode", "", "key lookup mode")
- algo := flags.String("algorithm", "", "algorithm to use")
if err := flags.Parse(args); err != nil {
return err
}
- l, err := encrypt.NewLockbox(encrypt.LockboxOptions{Key: *key, KeyMode: *keyMode, File: *file, Algorithm: *algo})
+ l, err := encrypt.NewLockbox(encrypt.LockboxOptions{Key: *key, KeyMode: *keyMode, File: *file})
if err != nil {
return err
}
diff --git a/internal/subcommands/rekey.go b/internal/subcommands/rekey.go
@@ -17,8 +17,6 @@ func Rekey(args []string) error {
outKey := flags.String("outkey", "", "output encryption key to update values with")
inMode := flags.String("inmode", "", "input encryption key mode")
outMode := flags.String("outmode", "", "output encryption key mode")
- inAlgo := flags.String("inalgorithm", "", "input encryption algorithm")
- outAlgo := flags.String("outalgorithm", "", "output encryption algorithm")
if err := flags.Parse(args); err != nil {
return err
}
@@ -26,8 +24,8 @@ func Rekey(args []string) error {
if err != nil {
return err
}
- inOpts := encrypt.LockboxOptions{Key: *inKey, KeyMode: *inMode, Algorithm: *inAlgo}
- outOpts := encrypt.LockboxOptions{Key: *outKey, KeyMode: *outMode, Algorithm: *outAlgo}
+ inOpts := encrypt.LockboxOptions{Key: *inKey, KeyMode: *inMode}
+ outOpts := encrypt.LockboxOptions{Key: *outKey, KeyMode: *outMode}
for _, file := range found {
fmt.Printf("rekeying: %s\n", file)
inOpts.File = file
diff --git a/tests/Makefile b/tests/Makefile
@@ -1,9 +1,6 @@
BUILD :=
DATA := bin
-ALGOS := secretbox aesgcm
-all: $(ALGOS)
-
-$(ALGOS):
- ./run.sh $(BUILD) $(DATA) $@
+all:
+ ./run.sh $(BUILD) $(DATA)
diff -u $(DATA)/actual.log expected.log
diff --git a/tests/run.sh b/tests/run.sh
@@ -9,7 +9,6 @@ export LOCKBOX_TOTP="totp"
export LOCKBOX_INTERACTIVE="no"
export LOCKBOX_HOOKDIR="$TESTS/hooks"
export LOCKBOX_GIT="no"
-export LOCKBOX_ALGORITHM="$3"
rm -rf $TESTS
mkdir -p $LOCKBOX_STORE
@@ -60,8 +59,4 @@ _run() {
_hook > $HOOK
chmod 755 $HOOK
-LOG=$TESTS/actual.log
-_run 2>&1 | sed "s#$LOCKBOX_STORE##g" > $LOG
-if [[ "$3" != "secretbox" ]]; then
- sed -i 's/cipher: message authentication failed/decrypt not ok/g' $LOG
-fi
+_run 2>&1 | sed "s#$LOCKBOX_STORE##g" > $TESTS/actual.log