From 9912618673680811e87a2a25d75899f7da1b2688 Mon Sep 17 00:00:00 2001 From: Adam Shannon Date: Wed, 12 Jul 2023 15:10:06 -0500 Subject: [PATCH 1/2] feat: add nothing cryptor --- cryptor.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cryptor.go b/cryptor.go index 623d9bfb..0fe7bb8d 100644 --- a/cryptor.go +++ b/cryptor.go @@ -21,3 +21,17 @@ type Cryptor interface { encrypt(data []byte) ([]byte, error) decrypt(data []byte) ([]byte, error) } + +func NoEncryption() Cryptor { + return ¬hingCryptor{} +} + +type nothingCryptor struct{} + +func (*nothingCryptor) encrypt(data []byte) ([]byte, error) { + return data, nil +} + +func (*nothingCryptor) decrypt(data []byte) ([]byte, error) { + return data, nil +} From fd122027d39be3916c6be67067643881077b064a Mon Sep 17 00:00:00 2001 From: Adam Shannon Date: Wed, 12 Jul 2023 15:10:26 -0500 Subject: [PATCH 2/2] feat: add config struct for creating *FS instances --- config.go | 102 ++++++++++++++++++++++++++++++++++++++++++++++++ config_test.go | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 config.go create mode 100644 config_test.go diff --git a/config.go b/config.go new file mode 100644 index 00000000..aeba3448 --- /dev/null +++ b/config.go @@ -0,0 +1,102 @@ +package cryptfs + +import ( + "fmt" + "os" +) + +type Config struct { + Compression CompressionConfig `json:"compression" yaml:"compression"` + Encryption EncryptionConfig `json:"encryption" yaml:"encryption"` + Encoding EncodingConfig `json:"encoding" yaml:"encoding"` +} + +type CompressionConfig struct { + Gzip *GzipConfig `json:"gzip" yaml:"gzip"` +} + +type GzipConfig struct { + Level int `json:"level" yaml:"level"` + Required bool `json:"required" yaml:"required"` +} + +type EncryptionConfig struct { + AES *AESConfig `json:"aes" yaml:"aes"` + GPG *GPGConfig `json:"gpg" yaml:"gpg"` +} + +type AESConfig struct { + Key string `json:"key" yaml:"yaml"` + KeyPath string `json:"keyPath" yaml:"keyPath"` +} + +type GPGConfig struct { + PublicPath string `json:"publicPath" yaml:"publicPath"` + PrivatePath string `json:"privatePath" yaml:"privatePath"` + PrivatePassword string `json:"privatePassword" yaml:"privatePassword"` +} + +type EncodingConfig struct { + Base64 bool `json:"base64" yaml:"base64"` +} + +func FromConfig(conf Config) (*FS, error) { + var err error + + // Encryption + cryptor := NoEncryption() + switch { + case conf.Encryption.AES != nil: + var key []byte + if len(conf.Encryption.AES.Key) > 0 { + key = []byte(conf.Encryption.AES.Key) + } else { + key, err = os.ReadFile(conf.Encryption.AES.KeyPath) + if err != nil { + return nil, fmt.Errorf("reading AES key from %s: %w", conf.Encryption.AES.KeyPath, err) + } + } + cryptor, err = NewAESCryptor(key) + + case conf.Encryption.GPG != nil: + if conf.Encryption.GPG.PublicPath != "" && conf.Encryption.GPG.PrivatePath == "" { + cryptor, err = NewGPGEncryptorFile(conf.Encryption.GPG.PublicPath) + } + + password := []byte(conf.Encryption.GPG.PrivatePassword) + if conf.Encryption.GPG.PublicPath == "" && conf.Encryption.GPG.PrivatePath != "" { + cryptor, err = NewGPGDecryptorFile(conf.Encryption.GPG.PrivatePath, password) + } + if conf.Encryption.GPG.PublicPath != "" && conf.Encryption.GPG.PrivatePath != "" { + cryptor, err = NewGPGCryptorFile(conf.Encryption.GPG.PublicPath, conf.Encryption.GPG.PrivatePath, password) + } + } + if err != nil { + return nil, fmt.Errorf("cryptor from config: %w", err) + } + + // Setup the FS + fsys, err := New(cryptor) + if err != nil { + return nil, fmt.Errorf("cryptfs from config: %w", err) + } + + // Compression + if conf.Compression.Gzip != nil { + compressor := Gzip() + if conf.Compression.Gzip.Level > 0 { + compressor = GzipLevel(conf.Compression.Gzip.Level) + } + if conf.Compression.Gzip.Required { + compressor = GzipRequired(conf.Compression.Gzip.Level) + } + fsys.SetCompression(compressor) + } + + // Encoding + if conf.Encoding.Base64 { + fsys.SetCoder(Base64()) + } + + return fsys, nil +} diff --git a/config_test.go b/config_test.go new file mode 100644 index 00000000..9db792ea --- /dev/null +++ b/config_test.go @@ -0,0 +1,104 @@ +// Licensed to The Moov Authors under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. The Moov Authors licenses this file to you 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 cryptfs + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFromConfig(t *testing.T) { + var conf Config + + t.Run("zero value", func(t *testing.T) { + fsys, err := FromConfig(conf) + require.NoError(t, err) + testCryptFS(t, fsys) + }) + + t.Run("gzip + base64", func(t *testing.T) { + conf.Compression.Gzip = &GzipConfig{} + conf.Encoding.Base64 = true + + fsys, err := FromConfig(conf) + require.NoError(t, err) + testCryptFS(t, fsys) + }) + + t.Run("AES", func(t *testing.T) { + conf.Encryption.AES = &AESConfig{ + Key: strings.Repeat("1", 16), + } + + fsys, err := FromConfig(conf) + require.NoError(t, err) + testCryptFS(t, fsys) + }) + + t.Run("AES - filepath error", func(t *testing.T) { + conf.Encryption.AES.Key = "" + conf.Encryption.AES.KeyPath = "/does/not/exist" + + fsys, err := FromConfig(conf) + require.Error(t, err) + require.Nil(t, fsys) + }) + + t.Run("GPG one-sided", func(t *testing.T) { + conf.Encryption.AES = nil + conf.Encryption.GPG = &GPGConfig{ + PublicPath: filepath.Join("internal", "gpgx", "testdata", "key.pub"), + } + + fsys, err := FromConfig(conf) + require.NoError(t, err) + + parent := t.TempDir() + path := filepath.Join(parent, "foo.txt") + err = fsys.WriteFile(path, []byte("hello, world"), 0600) + require.NoError(t, err) + + // Setup fsys with private keys (for decryption) + conf.Encryption.GPG.PublicPath = "" + conf.Encryption.GPG.PrivatePath = filepath.Join("internal", "gpgx", "testdata", "key.priv") + conf.Encryption.GPG.PrivatePassword = "password" + + fsys, err = FromConfig(conf) + require.NoError(t, err) + + bs, err := fsys.ReadFile(path) + require.NoError(t, err) + require.Equal(t, "hello, world", string(bs)) + }) + + t.Run("GPG both filepaths", func(t *testing.T) { + conf.Encryption.AES = nil + conf.Encryption.GPG = &GPGConfig{ + PublicPath: filepath.Join("internal", "gpgx", "testdata", "key.pub"), + PrivatePath: filepath.Join("internal", "gpgx", "testdata", "key.priv"), + PrivatePassword: "password", + } + + fsys, err := FromConfig(conf) + require.NoError(t, err) + testCryptFS(t, fsys) + }) +}