aboutsummaryrefslogtreecommitdiffstats
path: root/dec_test.go
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-10-23 21:02:22 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-10-23 21:02:22 -0700
commit3c2c07a69daf75d377ba196c28524c0fc6a16db6 (patch)
tree83a1c9ea49bc8c0927f8de2ed9f3b933f29759f0 /dec_test.go
parentc241eeb8ad4baafa75691ffb140029ec54cb349e (diff)
downloadsym-3c2c07a69daf75d377ba196c28524c0fc6a16db6.tar.zst
Move sym files into the root directory
Diffstat (limited to 'dec_test.go')
-rw-r--r--dec_test.go326
1 files changed, 326 insertions, 0 deletions
diff --git a/dec_test.go b/dec_test.go
new file mode 100644
index 0000000..e1d6d9b
--- /dev/null
+++ b/dec_test.go
@@ -0,0 +1,326 @@
+package sym
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "flag"
+ "path/filepath"
+ "slices"
+ "strings"
+ "testing"
+)
+
+func TestDecryptFile_Force(t *testing.T) {
+ t.Parallel()
+
+ for _, tc := range []struct {
+ desc string
+ force bool
+ wantErr bool
+ }{{
+ desc: "OutputExists",
+ force: false,
+ wantErr: true,
+ }, {
+ desc: "Force",
+ force: true,
+ wantErr: false,
+ }} {
+ t.Run(tc.desc, func(t *testing.T) {
+ t.Parallel()
+
+ const password = "asdf"
+ fileName := filepath.Join(t.TempDir(), "file")
+ mustWriteFile(t, fileName, []byte("test file content"))
+ if err := testEncryptOptions.encryptFile(fileName, password); err != nil {
+ t.Fatalf("Failed to encrypt file: %s", err)
+ }
+ decOpts := DefaultDecryptOptions
+ decOpts.force = tc.force
+ err := decOpts.decryptFile(fileName+".enc", password)
+ if gotErr := err != nil; gotErr != tc.wantErr {
+ t.Errorf("decryptFile(force=%t) returned returned error %v when output file exists, want error? %t", tc.force, err, tc.wantErr)
+ }
+ })
+ }
+}
+
+func encodeHeader(t *testing.T, f *fileMetadata) []byte {
+ t.Helper()
+
+ if f.HashMetadata.PasswordHashType == pwHashInvalid {
+ f.HashMetadata.PasswordHashType = pwHashPBKDF2_HMAC_SHA256
+ }
+ if f.HashMetadata.Iterations == 0 {
+ f.HashMetadata.Iterations = 10
+ }
+ if f.HashMetadata.SaltSize == 0 {
+ f.HashMetadata.SaltSize = defaultSaltSize
+ }
+ if f.EncryptionMetadata.EncryptionType == encryptionAlgInvalid {
+ f.EncryptionMetadata.EncryptionType = encryptionAlgAES256_GCM
+ }
+ if f.EncryptionMetadata.SegmentSize == 0 {
+ f.EncryptionMetadata.SegmentSize = defaultSegmentSize
+ }
+ b, err := binary.Append([]byte(magic), binary.BigEndian, f)
+ if err != nil {
+ t.Fatalf("Bad file metadata: %s", err)
+ }
+ return b
+}
+
+func TestDecrypt_BadFileFormat(t *testing.T) {
+ t.Parallel()
+
+ for _, tc := range []struct {
+ desc string
+ fileContent []byte
+ }{{
+ desc: "Empty",
+ fileContent: nil,
+ }, {
+ desc: "Short",
+ fileContent: []byte{0x80},
+ }, {
+ desc: "BadFormat",
+ fileContent: []byte("bad file format"),
+ }, {
+ desc: "BadMagic",
+ fileContent: []byte("\x80asdf"),
+ }, {
+ desc: "BadHeader",
+ fileContent: []byte("\x80symasdf"),
+ }, {
+ desc: "BadVersion",
+ fileContent: encodeHeader(t, &fileMetadata{
+ Version: -1,
+ }),
+ }, {
+ desc: "BadEncryptionAlg",
+ fileContent: encodeHeader(t, &fileMetadata{
+ EncryptionMetadata: encryptionMetadata{
+ EncryptionType: -1,
+ },
+ }),
+ }, {
+ desc: "BadSegmentSize",
+ fileContent: encodeHeader(t, &fileMetadata{
+ EncryptionMetadata: encryptionMetadata{
+ SegmentSize: -1,
+ },
+ }),
+ }, {
+ desc: "BadSaltSize",
+ fileContent: encodeHeader(t, &fileMetadata{
+ HashMetadata: hashMetadata{
+ SaltSize: -1,
+ },
+ }),
+ }, {
+ desc: "BadPasswordHashType",
+ fileContent: encodeHeader(t, &fileMetadata{
+ HashMetadata: hashMetadata{
+ PasswordHashType: -1,
+ },
+ }),
+ }, {
+ desc: "BadIterations",
+ fileContent: encodeHeader(t, &fileMetadata{
+ HashMetadata: hashMetadata{
+ Iterations: -1,
+ },
+ }),
+ }, {
+ desc: "NoSalt",
+ fileContent: encodeHeader(t, &fileMetadata{}),
+ }, {
+ desc: "ShortSalt",
+ fileContent: slices.Concat(
+ encodeHeader(t, &fileMetadata{}),
+ []byte("asdf")),
+ }, {
+ desc: "BadContent",
+ fileContent: slices.Concat(
+ encodeHeader(t, &fileMetadata{}),
+ bytes.Repeat([]byte{0}, defaultSaltSize),
+ []byte("bad content")),
+ }} {
+ t.Run(tc.desc, func(t *testing.T) {
+ t.Parallel()
+
+ fileName := filepath.Join(t.TempDir(), "file")
+ mustWriteFile(t, fileName, tc.fileContent)
+ err := DefaultDecryptOptions.decryptFile(fileName, "asdf")
+ if err == nil {
+ t.Errorf("DecryptFile succeeded for incorrect file format, want error")
+ }
+ })
+ }
+}
+
+func TestDecryptFile_WeirdName(t *testing.T) {
+ t.Parallel()
+
+ const password = "asdf"
+ fileContent := []byte("file content")
+ fileName := filepath.Join(t.TempDir(), "file")
+ mustWriteFile(t, fileName, fileContent)
+ if err := testEncryptOptions.encryptFile(fileName, password); err != nil {
+ t.Fatalf("EncryptFile failed: %s", err)
+ }
+ mustRename(t, fileName+".enc", fileName+".encrypted")
+ if err := DefaultDecryptOptions.decryptFile(fileName+".encrypted", password); err != nil {
+ t.Fatalf("DecryptFile failed: %s", err)
+ }
+ gotContents := mustReadFile(t, fileName+".encrypted.dec")
+ if !bytes.Equal(gotContents, fileContent) {
+ t.Errorf("contents differ")
+ }
+}
+
+func TestDecryptFile_NotFound(t *testing.T) {
+ t.Parallel()
+
+ err := DefaultDecryptOptions.decryptFile("my-nonexistent-file.txt", "asdf")
+ if err == nil {
+ t.Fatal("decryptFile succeeded for nonexistent file, want error")
+ }
+}
+
+func TestDecryptFile_NoPermission(t *testing.T) {
+ t.Parallel()
+
+ fileName := filepath.Join(t.TempDir(), "file.enc")
+ mustWriteFile(t, fileName, []byte("test file content"))
+ mustWriteFile(t, strings.TrimSuffix(fileName, ".enc"), nil)
+ mustChmod(t, strings.TrimSuffix(fileName, ".enc"), 0400)
+ opts := DefaultDecryptOptions
+ opts.force = true
+ err := opts.decryptFile(fileName, "asdf")
+ if err == nil {
+ t.Fatal("decryptFile succeeded for unwritable file, want error")
+ }
+}
+
+func TestDecryptOptions_RegisterFlags(t *testing.T) {
+ t.Parallel()
+
+ var o DecryptOptions
+ fs := flag.NewFlagSet("test", flag.ContinueOnError)
+ o.RegisterFlags(fs)
+ const cmd = "-p asdf -f"
+ fs.Parse(strings.Split(cmd, " "))
+ want := decryptFlags{
+ password: "asdf",
+ force: true,
+ }
+ if o.decryptFlags != want {
+ t.Errorf("Command line %q parsed incorrect DecryptOptions, got %+v, want %+v", cmd, o, want)
+ }
+}
+
+func TestDecryptOptions_Run(t *testing.T) {
+ t.Parallel()
+
+ const password = "asdf"
+ fileContent := []byte("test file content")
+ fileName := filepath.Join(t.TempDir(), "file")
+ mustWriteFile(t, fileName, fileContent)
+ if err := testEncryptOptions.encryptFile(fileName, password); err != nil {
+ t.Errorf("EncryptFile failed: %s", err)
+ }
+ mustRemove(t, fileName)
+ opts := DefaultDecryptOptions
+ opts.password = password
+ err := opts.Run(fileName + ".enc")
+ if err != nil {
+ t.Errorf("dec failed: %s", err)
+ }
+ gotFileContents := mustReadFile(t, fileName)
+ if !bytes.Equal(gotFileContents, fileContent) {
+ t.Errorf("Run returned incorrect contents %q, want %q", gotFileContents, fileContent)
+ }
+}
+
+func TestDecryptOptions_Run_UsageError(t *testing.T) {
+ t.Parallel()
+
+ err := DefaultDecryptOptions.Run()
+ if err == nil {
+ t.Errorf("Run without -p when reading from stdin, want error")
+ }
+}
+
+func TestDecryptOptions_Run_NotFound(t *testing.T) {
+ t.Parallel()
+
+ opts := DefaultDecryptOptions
+ opts.password = "asdf"
+ err := opts.Run("my-nonexistent-file-name.txt")
+ if err == nil {
+ t.Errorf("Run succeeded with nonexistent file, want error")
+ }
+}
+
+func TestDecryptOptions_Run_Stdin(t *testing.T) {
+ t.Parallel()
+
+ const password = "asdf"
+ content := []byte("test contents")
+ encrypted := new(bytes.Buffer)
+ if err := testEncryptOptions.encrypt(encrypted, bytes.NewReader(content), password); err != nil {
+ t.Fatalf("Failed to encrypt: %s", err)
+ }
+ gotContentBuf := new(bytes.Buffer)
+ opts := DefaultDecryptOptions
+ opts.password = password
+ opts.stdin = bytes.NewReader(encrypted.Bytes())
+ opts.stdout = gotContentBuf
+ if err := opts.Run(); err != nil {
+ t.Fatalf("Run failed: %s", err)
+ }
+ gotContent := gotContentBuf.Bytes()
+ if !bytes.Equal(gotContent, content) {
+ t.Errorf("dec returned incorrect contents %q, want %q", gotContent, content)
+ }
+}
+
+func TestDecryptOptions_Run_ReadPassword(t *testing.T) {
+ t.Parallel()
+
+ for _, tc := range []struct {
+ desc string
+ err error
+ wantErr bool
+ }{{
+ desc: "Ok",
+ }, {
+ desc: "Err",
+ err: errors.New("test error"),
+ wantErr: true,
+ }} {
+ t.Run(tc.desc, func(t *testing.T) {
+ t.Parallel()
+
+ const password = "asdf"
+
+ fileName := filepath.Join(t.TempDir(), "file")
+ mustWriteFile(t, fileName, []byte("test file content"))
+ if err := testEncryptOptions.encryptFile(fileName, password); err != nil {
+ t.Errorf("EncryptFile failed: %s", err)
+ }
+ mustRemove(t, fileName)
+
+ opts := DefaultDecryptOptions
+ opts.passwordIn = func() (string, error) {
+ return password, tc.err
+ }
+ err := opts.Run(fileName + ".enc")
+ if gotErr := err != nil; gotErr != tc.wantErr {
+ t.Errorf("DecryptOptions.Run returned error %v reading password from stdin, want error? %t", err, tc.wantErr)
+ }
+ })
+ }
+}