aboutsummaryrefslogtreecommitdiffstats
path: root/dec.go
diff options
context:
space:
mode:
Diffstat (limited to 'dec.go')
-rw-r--r--dec.go125
1 files changed, 125 insertions, 0 deletions
diff --git a/dec.go b/dec.go
new file mode 100644
index 0000000..657da35
--- /dev/null
+++ b/dec.go
@@ -0,0 +1,125 @@
+package sym
+
+import (
+ "encoding/binary"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+)
+
+func decrypt(w io.Writer, r io.Reader, password string) error {
+ fileFormat := make([]byte, 4)
+ if _, err := io.ReadFull(r, fileFormat); err != nil {
+ return err
+ }
+ if string(fileFormat) != magic {
+ return fmt.Errorf("bad file format")
+ }
+ header := new(fileMetadata)
+ if err := binary.Read(r, binary.BigEndian, header); err != nil {
+ return err
+ }
+ if err := header.validate(); err != nil {
+ return err
+ }
+ _, err := io.Copy(w, header.EncryptionMetadata.newDecryptingReader(r, password, &header.HashMetadata))
+ return err
+}
+
+type decryptFlags struct {
+ password string
+ force bool
+}
+
+func (f *decryptFlags) RegisterFlags(fs *flag.FlagSet) {
+ fs.StringVar(&f.password, "p", "", "use the specified password; if not provided, dec will prompt for a password")
+ fs.BoolVar(&f.force, "f", false, "overwrite output files even if they already exist")
+}
+
+type DecryptOptions struct {
+ decryptFlags
+
+ passwordIn func() (string, error)
+ stdin io.Reader
+ stdout io.Writer
+}
+
+var DefaultDecryptOptions = DecryptOptions{
+ passwordIn: termReadPassword,
+ stdin: os.Stdin,
+ stdout: os.Stdout,
+}
+
+func (o *DecryptOptions) decryptFile(fileName string, password string) (err error) {
+ var outFileName string
+ if name, ok := strings.CutSuffix(fileName, ".enc"); ok {
+ outFileName = name
+ } else if name, ok := strings.CutSuffix(fileName, ".enc.txt"); ok {
+ outFileName = name
+ } else {
+ outFileName = fileName + ".dec"
+ }
+ fIn, err := os.Open(fileName)
+ if err != nil {
+ return err
+ }
+ defer fIn.Close()
+ fileOpts := os.O_CREATE | os.O_WRONLY
+ if o.force {
+ fileOpts |= os.O_TRUNC
+ } else {
+ fileOpts |= os.O_EXCL
+ }
+ fOut, err := os.OpenFile(outFileName, fileOpts, 0644)
+ if err != nil {
+ if errors.Is(err, os.ErrExist) {
+ return fmt.Errorf("output file %q exists (use -f to overwrite)", outFileName)
+ }
+ return err
+ }
+ defer func() {
+ fOut.Close()
+ if err != nil {
+ os.Remove(fOut.Name())
+ }
+ }()
+ if err := decrypt(fOut, fIn, password); err != nil {
+ return fmt.Errorf("decrypt %q: %s", fileName, err)
+ }
+ return fOut.Close()
+}
+
+func (o *DecryptOptions) readPassword() (string, error) {
+ fmt.Fprint(os.Stderr, "Enter password: ")
+ pw, err := o.passwordIn()
+ fmt.Fprintln(os.Stderr)
+ return pw, err
+}
+
+func (o *DecryptOptions) Run(args ...string) error {
+ if len(args) == 0 && o.password == "" {
+ return fmt.Errorf("-p is required when reading from stdin")
+ }
+ var password string
+ if o.password != "" {
+ password = o.password
+ } else {
+ var err error
+ password, err = o.readPassword()
+ if err != nil {
+ return err
+ }
+ }
+ if len(args) == 0 {
+ return decrypt(o.stdout, o.stdin, password)
+ }
+ for _, fileName := range args {
+ if err := o.decryptFile(fileName, password); err != nil {
+ return err
+ }
+ }
+ return nil
+}