Skip to content

Commit

Permalink
v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
thepax committed Aug 28, 2022
0 parents commit 428132b
Show file tree
Hide file tree
Showing 26 changed files with 2,538 additions and 0 deletions.
73 changes: 73 additions & 0 deletions cipher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package ecc

import (
"crypto/aes"
"crypto/cipher"
"encoding/asn1"
"strings"
"github.com/pkg/errors"
"golang.org/x/crypto/chacha20poly1305"
)

type Cipher struct {
Name string
Description string
OID asn1.ObjectIdentifier
KeySize int
New func(key []byte) (cipher.AEAD, error)
KDF *KDF
}

// Suppported ciphers
var Ciphers = []*Cipher{
{
Name: "aes-128-gcm",
OID: asn1.ObjectIdentifier{2,16,840,1,101,3,4,1,6},
KeySize: 16,
New: newAESGCM,
KDF: KDFs["hkdf-sha256"],
},
{
Name: "aes-192-gcm",
OID: asn1.ObjectIdentifier{2,16,840,1,101,3,4,1,26},
KeySize: 24,
New: newAESGCM,
KDF: KDFs["hkdf-sha256"],
},
{
Name: "aes-256-gcm",
OID: asn1.ObjectIdentifier{2,16,840,1,101,3,4,1,46},
KeySize: 32,
New: newAESGCM,
KDF: KDFs["hkdf-sha384"],
},
{
Name: "chacha20-poly1305",
OID: asn1.ObjectIdentifier{1,2,840,113549,1,9,16,3,18},
KeySize: 32,
New: chacha20poly1305.New,
KDF: KDFs["hkdf-sha384"],
},
}

func LookupCipher(name string) *Cipher {
name = strings.ReplaceAll(strings.ReplaceAll(strings.ToLower(name), "-", ""), "_", "")
for _, c := range Ciphers {
if strings.ReplaceAll(c.Name, "-", "") == name {
return c
}
}
return nil
}

func newAESGCM(key []byte) (cipher.AEAD, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, errors.Wrapf(err, "init AES")
}
aead, err := cipher.NewGCM(block)
if err != nil {
return nil, errors.Wrapf(err, "init GCM")
}
return aead, nil
}
3 changes: 3 additions & 0 deletions cmd/ecc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist/
ecc
test.*
27 changes: 27 additions & 0 deletions cmd/ecc/.goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
before:
hooks:
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
96 changes: 96 additions & 0 deletions cmd/ecc/commands/decrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package commands

import (
"io"
"os"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/thepax/ecc"
"github.com/thepax/ecc/eccutil"
)

type DecryptOpts struct {
Key string
Verify string
In string
Out string
}

var decryptOpts DecryptOpts

func DecryptCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "decrypt",
Short: "Decrypt file or stream",
Args: cobra.ExactArgs(0),
PreRunE: func(cmd *cobra.Command, args []string) error {
if decryptOpts.Key == "" {
return errors.New("private key is not specified")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
decryptRun(&decryptOpts)
},
}
cmd.Flags().StringVar(&decryptOpts.Key, "key", eccutil.GetenvDecryptKey(), "decrypt/private key")
cmd.Flags().StringVar(&decryptOpts.Verify, "verify", eccutil.GetenvVerifyKey(), "public key")
cmd.Flags().StringVarP(&decryptOpts.In, "in", "i", "", "input file (default is stdin)")
cmd.Flags().StringVarP(&decryptOpts.Out, "out", "o", "", "output file (default is stdout)")
return cmd
}

func decryptRun(opts *DecryptOpts) {
var err error

decryptKey, err := eccutil.GetPrivateKey("decrypting", opts.Key)
if err != nil {
log.Fatal(err)
}
verifyKey, err := eccutil.GetPublicKey("verifying", opts.Verify)
if err != nil {
log.Fatal(err)
}

var in *os.File
if opts.In != "" {
in, err = os.Open(opts.In)
if err != nil {
log.Fatal("open input: ", err)
}
defer in.Close()
} else {
in = os.Stdin
}
var out *os.File
if opts.Out != "" {
out, err = os.OpenFile(opts.Out, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
log.Fatal("create output: ", err)
}
defer func() {
if err == nil {
err = out.Close()
if err != nil {
os.Remove(opts.Out)
log.Fatal("close output: ", err)
}
} else {
out.Close()
os.Remove(opts.Out)
}
}()
} else {
out = os.Stdout
}
dec := &ecc.Decrypt{
PrivateKey: decryptKey,
VerifyKey: verifyKey,
Input: in,
}
_, err = io.Copy(out, dec)
if err != nil {
log.Fatal("decrypt: ", err)
}
}
162 changes: 162 additions & 0 deletions cmd/ecc/commands/edit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package commands

import (
"crypto/rand"
"io"
"os"
"os/exec"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/thepax/ecc"
"github.com/thepax/ecc/eccutil"
)

type EditOpts struct {
Cipher string
Key string
Rand string
}

var editOpts EditOpts

func EditCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "edit [flags] filename.ecc",
Short: "Edit an encrypted file",
Args: cobra.ExactArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error {
if editOpts.Key == "" {
return errors.New("private key is not specified")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
editRun(&editOpts, args[0])
},
}
cmd.Flags().StringVar(&editOpts.Cipher, "cipher", "aes-256-gcm", "encryption cipher")
cmd.Flags().StringVar(&editOpts.Key, "key", eccutil.GetenvPrivateKey(), "private key")
cmd.Flags().StringVar(&editOpts.Rand, "rand", "", "file to use for random number input")
return cmd
}

func editRun(opts *EditOpts, filename string) {
err := editDoRun(opts, filename)
if err != nil {
log.Fatal(err)
}
}

func editDoRun(opts *EditOpts, filename string) error {
privateKey, err := eccutil.GetPrivateKey("decrypting", opts.Key)
if err != nil {
return err
}

randReader := rand.Reader
if opts.Rand != "" {
f, err := os.Open(opts.Rand)
if err != nil {
log.Fatal("open random number input: ", err)
}
randReader = f
defer f.Close()
}

var f *os.File
if _, err := os.Stat(filename); !os.IsNotExist(err) {
f, err = os.OpenFile(filename, os.O_RDWR, 0600)
if err != nil {
return err
}
defer f.Close()
}

tmpf, err := os.CreateTemp("", "ecc*.txt")
if err != nil {
return err
}
tempfile := tmpf.Name()
defer func() {
if fi, err := os.Stat(tempfile); err == nil && fi.Size() > 0 {
tmpf, err := os.OpenFile(tempfile, os.O_RDWR, 0600)
if err == nil {
io.CopyN(tmpf, randReader, fi.Size())
tmpf.Close()
}
}
os.Remove(tempfile)
}()

if f != nil {
dec := &ecc.Decrypt{
PrivateKey: privateKey,
VerifyKey: &privateKey.PublicKey,
Input: f,
}
_, err = io.Copy(tmpf, dec)
if err != nil {
tmpf.Close()
return errors.Wrap(err, "decrypt")
}
}
if err = tmpf.Close(); err != nil {
return errors.Wrap(err, "decrypt")
}

editor := os.Getenv("EDITOR")
if editor == "" {
editor = "vi"
}
cmd := exec.Command(editor, tempfile)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return errors.Wrap(err, "run editor")
}

if f == nil {
f, err = os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer f.Close()
}
if _, err = f.Seek(0, os.SEEK_SET); err != nil {
return err
}

tmpf, err = os.Open(tempfile)
if err != nil {
return errors.Wrap(err, "encrypt")
}
defer tmpf.Close()

enc := &ecc.Encrypt{
PublicKey: &privateKey.PublicKey,
SignKey: privateKey,
Cipher: opts.Cipher,
Output: f,
Random: randReader,
}
_, err = io.Copy(enc, tmpf)
if err != nil {
return errors.Wrap(err, "encrypt")
}
if err = enc.Close(); err != nil {
return errors.Wrap(err, "encrypt")
}
pos, err := f.Seek(0, os.SEEK_CUR)
if err != nil {
return errors.Wrap(err, "encrypt")
}
if err = f.Truncate(pos); err != nil {
return errors.Wrap(err, "encrypt")
}
if err = f.Sync(); err != nil {
return errors.Wrap(err, "encrypt")
}
return nil
}
Loading

0 comments on commit 428132b

Please sign in to comment.