aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--awwan.go55
-rw-r--r--awwan_test.go68
-rw-r--r--testdata/decrypt-with-passphrase/.awwan.env.vaultbin0 -> 384 bytes
l---------testdata/decrypt-with-passphrase/.awwan.key1
-rw-r--r--testdata/decrypt-with-passphrase/.ssh/empty0
l---------testdata/decrypt-wrong-privatekey/.awwan.env.vault1
-rw-r--r--testdata/decrypt-wrong-privatekey/.awwan.key39
-rw-r--r--testdata/decrypt-wrong-privatekey/.ssh/empty0
9 files changed, 165 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 96957aa..9c0deff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@
/cover.out
/testdata/encrypt-with-passphrase/.awwan.env.vault
/testdata/encrypt-without-passphrase/.awwan.env.vault
+/testdata/decrypt-with-passphrase/.awwan.env
diff --git a/awwan.go b/awwan.go
index 5479778..cd28c83 100644
--- a/awwan.go
+++ b/awwan.go
@@ -14,6 +14,7 @@ import (
"log"
"os"
"path/filepath"
+ "strings"
"git.sr.ht/~shulhan/awwan/internal"
libcrypto "github.com/shuLhan/share/lib/crypto"
@@ -42,6 +43,9 @@ const (
defTmpDir = "/tmp"
)
+// defEncryptExt default file extension for encrypted file.
+const defEncryptExt = `.vault`
+
// defFilePrivateKey define the default private key file name.
const defFilePrivateKey = `.awwan.key`
@@ -104,6 +108,57 @@ func New(baseDir string) (aww *Awwan, err error) {
return aww, nil
}
+// Decrypt the file using private key from file "{{.BaseDir}}/.awwan.key".
+// The encrypted file must have extension ".vault", otherwise it will return
+// an error.
+// The decrypted file output will be written in the same directory without
+// the ".vault" extension in filePlain.
+func (aww *Awwan) Decrypt(fileVault string) (filePlain string, err error) {
+ var (
+ logp = `Decrypt`
+ ext = filepath.Ext(fileVault)
+ )
+
+ if ext != defEncryptExt {
+ return ``, fmt.Errorf(`%s: invalid extension, expecting %s, got %s`, logp, defEncryptExt, ext)
+ }
+
+ if aww.privateKey == nil {
+ err = aww.loadPrivateKey()
+ if err != nil {
+ return ``, fmt.Errorf(`%s: %w`, logp, err)
+ }
+ }
+
+ var ciphertext []byte
+
+ ciphertext, err = os.ReadFile(fileVault)
+ if err != nil {
+ return ``, fmt.Errorf(`%s: %w`, logp, err)
+ }
+
+ var (
+ hash = sha256.New()
+ label = []byte(`awwan`)
+
+ plaintext []byte
+ )
+
+ plaintext, err = rsa.DecryptOAEP(hash, rand.Reader, aww.privateKey, ciphertext, label)
+ if err != nil {
+ return ``, fmt.Errorf(`%s: %w`, logp, err)
+ }
+
+ filePlain = strings.TrimSuffix(fileVault, defEncryptExt)
+
+ err = os.WriteFile(filePlain, plaintext, 0600)
+ if err != nil {
+ return ``, fmt.Errorf(`%s: %w`, logp, err)
+ }
+
+ return filePlain, nil
+}
+
// Encrypt the file using private key from file "{{.BaseDir}}/.awwan.key".
// The encrypted file output will be on the same file path with ".vault"
// extension.
diff --git a/awwan_test.go b/awwan_test.go
index a216fbf..e1f8b4d 100644
--- a/awwan_test.go
+++ b/awwan_test.go
@@ -9,6 +9,74 @@ import (
"github.com/shuLhan/share/lib/test/mock"
)
+func TestAwwanDecrypt(t *testing.T) {
+ type testCase struct {
+ baseDir string
+ fileVault string
+ passphrase string
+ expError string
+ }
+
+ var cases = []testCase{{
+ baseDir: filepath.Join(`testdata`, `decrypt-with-passphrase`),
+ fileVault: `.awwan.env`,
+ expError: `Decrypt: invalid extension, expecting .vault, got .env`,
+ }, {
+ baseDir: filepath.Join(`testdata`, `decrypt-with-passphrase`),
+ fileVault: `.awwan.env.vault`,
+ passphrase: "invalidpassphrase\r",
+ expError: `Decrypt: LoadPrivateKeyInteractive: x509: decryption password incorrect`,
+ }, {
+ baseDir: filepath.Join(`testdata`, `decrypt-with-passphrase`),
+ fileVault: `.awwan.env.vault`,
+ passphrase: "s3cret\r",
+ }, {
+ baseDir: filepath.Join(`testdata`, `decrypt-wrong-privatekey`),
+ fileVault: `.awwan.env.vault`,
+ passphrase: "news3cret\r",
+ expError: `Decrypt: crypto/rsa: decryption error`,
+ }}
+
+ var (
+ mockrw = mock.ReadWriter{}
+
+ c testCase
+ aww *Awwan
+ err error
+ filePlain string
+ fileVault string
+ )
+
+ for _, c = range cases {
+ fileVault = filepath.Join(c.baseDir, c.fileVault)
+
+ aww, err = New(c.baseDir)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(c.passphrase) != 0 {
+ // Write the passphrase to standard input to be read
+ // interactively.
+ mockrw.BufRead.WriteString(c.passphrase)
+ aww.termrw = &mockrw
+ } else {
+ aww.termrw = nil
+ }
+
+ filePlain, err = aww.Decrypt(fileVault)
+ if err != nil {
+ test.Assert(t, `Decrypt`, c.expError, err.Error())
+ continue
+ }
+
+ _, err = os.Stat(filePlain)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+}
+
func TestAwwanEncrypt(t *testing.T) {
type testCase struct {
baseDir string
diff --git a/testdata/decrypt-with-passphrase/.awwan.env.vault b/testdata/decrypt-with-passphrase/.awwan.env.vault
new file mode 100644
index 0000000..41960cd
--- /dev/null
+++ b/testdata/decrypt-with-passphrase/.awwan.env.vault
Binary files differ
diff --git a/testdata/decrypt-with-passphrase/.awwan.key b/testdata/decrypt-with-passphrase/.awwan.key
new file mode 120000
index 0000000..aa99eff
--- /dev/null
+++ b/testdata/decrypt-with-passphrase/.awwan.key
@@ -0,0 +1 @@
+../encrypt-with-passphrase/.awwan.key \ No newline at end of file
diff --git a/testdata/decrypt-with-passphrase/.ssh/empty b/testdata/decrypt-with-passphrase/.ssh/empty
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/testdata/decrypt-with-passphrase/.ssh/empty
diff --git a/testdata/decrypt-wrong-privatekey/.awwan.env.vault b/testdata/decrypt-wrong-privatekey/.awwan.env.vault
new file mode 120000
index 0000000..1d98464
--- /dev/null
+++ b/testdata/decrypt-wrong-privatekey/.awwan.env.vault
@@ -0,0 +1 @@
+../encrypt-with-passphrase/.awwan.env.vault \ No newline at end of file
diff --git a/testdata/decrypt-wrong-privatekey/.awwan.key b/testdata/decrypt-wrong-privatekey/.awwan.key
new file mode 100644
index 0000000..bf2f333
--- /dev/null
+++ b/testdata/decrypt-wrong-privatekey/.awwan.key
@@ -0,0 +1,39 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+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
+pVmomw==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/testdata/decrypt-wrong-privatekey/.ssh/empty b/testdata/decrypt-wrong-privatekey/.ssh/empty
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/testdata/decrypt-wrong-privatekey/.ssh/empty