Skip to content

Commit

Permalink
Merge pull request #44 from moov-io/config-struct
Browse files Browse the repository at this point in the history
feat: add config struct for creating *FS instances
  • Loading branch information
adamdecaf authored Jul 12, 2023
2 parents 71ebfec + fd12202 commit 5d7e0e3
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 0 deletions.
102 changes: 102 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -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
}
104 changes: 104 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
14 changes: 14 additions & 0 deletions cryptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,17 @@ type Cryptor interface {
encrypt(data []byte) ([]byte, error)
decrypt(data []byte) ([]byte, error)
}

func NoEncryption() Cryptor {
return &nothingCryptor{}
}

type nothingCryptor struct{}

func (*nothingCryptor) encrypt(data []byte) ([]byte, error) {
return data, nil
}

func (*nothingCryptor) decrypt(data []byte) ([]byte, error) {
return data, nil
}

0 comments on commit 5d7e0e3

Please sign in to comment.