Refactor salty-keygen as a library function (#8)

Co-authored-by: James Mills <prologic@shortcircuit.net.au>
Reviewed-on: #8
main
James Mills 6 months ago
parent 1af9e6828d
commit fb3d6fc9e8
  1. 27
      .drone.yml
  2. 2
      README.md
  3. 21
      cmd/salty-keygen/main.go
  4. 41
      cmd/salty/main.go
  5. 24
      crypto.go
  6. 27
      crypto_test.go
  7. 3
      go.mod
  8. 54
      keys.go
  9. 21
      keys_test.go

@ -0,0 +1,27 @@
---
kind: pipeline
name: default
steps:
- name: build-and-test
image: r.mills.io/prologic/golang-alpine:latest
commands:
- go test
- name: notify-irc
image: plugins/webhook
settings:
urls:
- https://msgbus.mills.io/ci.mills.io
when:
status:
- success
- failure
trigger:
branch:
- main
event:
- tag
- push
- pull_request

@ -1,5 +1,7 @@
# salty
[![Build Status](https://ci.mills.io/api/badges/prologic/salty/status.svg)](https://ci.mills.io/prologic/salty)
A command-line tool using the [saltpack](https://saltpack.org) messaging
format and the [keys.pub](https://keys.pub) Go library.

@ -1,13 +1,10 @@
package main
import (
"encoding/base64"
"fmt"
"os"
"path/filepath"
"time"
"github.com/keys-pub/keys"
log "github.com/sirupsen/logrus"
flag "github.com/spf13/pflag"
"golang.org/x/term"
@ -32,18 +29,6 @@ func init() {
flag.StringVarP(&output, "output", "o", "", "Write the result to the file")
}
func generate(out *os.File) {
k := keys.GenerateEdX25519Key()
if !term.IsTerminal(int(out.Fd())) {
fmt.Fprintf(os.Stderr, "Public key: %s\n", k.PublicKey().ID().String())
}
fmt.Fprintf(out, "# created: %s\n", time.Now().Format(time.RFC3339))
fmt.Fprintf(out, "# public key: %s\n", k.PublicKey().ID().String())
fmt.Fprintf(out, "%s\n", base64.StdEncoding.EncodeToString(k.Private()))
}
func main() {
flag.Parse()
@ -69,5 +54,9 @@ func main() {
if fi, err := out.Stat(); err == nil && fi.Mode().IsRegular() && fi.Mode().Perm()&0004 != 0 {
log.Warn("writing secret key to a world-readable file")
}
generate(out)
_, pub := salty.GenerateKeys(out)
if !term.IsTerminal(int(out.Fd())) {
fmt.Fprintf(os.Stderr, "Public key: %s\n", pub)
}
}

@ -1,16 +1,11 @@
package main
import (
"bufio"
"encoding/base64"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/keys-pub/keys"
"github.com/keys-pub/keys/saltpack"
log "github.com/sirupsen/logrus"
flag "github.com/spf13/pflag"
"go.mills.io/salty"
@ -41,29 +36,6 @@ func init() {
flag.StringVarP(&identity, "identity", "i", "", "Use the identity file at PATH. Can be repeated.")
}
func parseIdentity(f io.Reader) (*keys.EdX25519Key, error) {
const privateKeySizeLimit = 1 << 8 // 256 bytes
scanner := bufio.NewScanner(io.LimitReader(f, privateKeySizeLimit))
var n int
for scanner.Scan() {
n++
line := scanner.Text()
if strings.HasPrefix(line, "#") || line == "" {
continue
}
bs, err := base64.StdEncoding.DecodeString(line)
if err != nil {
return nil, fmt.Errorf("error at line %d: %v", n, err)
}
return keys.NewEdX25519KeyFromPrivateKey(keys.Bytes64(bs)), nil
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("failed to read identity file: %v", err)
}
return nil, fmt.Errorf("no key found")
}
func main() {
flag.Parse()
@ -87,16 +59,11 @@ func main() {
}
defer id.Close()
key, err := parseIdentity(id)
key, err := salty.ParseIdentity(id)
if err != nil {
log.WithError(err).Fatalf("error reading private key: %q", identity)
}
ids := keys.NewIDSet()
for _, recipient := range recipients {
ids.Add(keys.ID(recipient))
}
in := os.Stdin
if flag.NArg() == 1 {
input := flag.Arg(0)
@ -126,7 +93,7 @@ func main() {
out = f
}
message, err := io.ReadAll(in)
input, err := io.ReadAll(in)
if err != nil {
log.WithError(err).Fatalf("error reading input")
}
@ -134,13 +101,13 @@ func main() {
var result []byte
if encrypt {
encrypted, err := saltpack.Signcrypt(message, true, key, ids.IDs()...)
encrypted, err := salty.Encrypt(key, input, recipients)
if err != nil {
log.WithError(err).Fatalf("error encrypting input")
}
result = encrypted[:]
} else if decrypt {
out, sender, err := saltpack.SigncryptOpen(message, true, saltpack.NewKeyring(key))
out, sender, err := salty.Decrypt(key, input)
if err != nil {
log.WithError(err).Fatalf("error decrypting input")
}

@ -0,0 +1,24 @@
package salty
import (
"github.com/keys-pub/keys"
"github.com/keys-pub/keys/saltpack"
)
// Encrypt encrypts the `input` using the Private Key `key` to the Public Keys of the
// `recipients`. Armour serializing is used by default. The armour bytes are returned on
// success or nil bytes and an error on failure.
func Encrypt(key *keys.EdX25519Key, input []byte, recipients []string) ([]byte, error) {
ids := keys.NewIDSet()
for _, recipient := range recipients {
ids.Add(keys.ID(recipient))
}
return saltpack.Signcrypt(input, true, key, ids.IDs()...)
}
// Decrypt decrypts the `input` using the Private Key `key` and returns the unencrypted
// bytes and the sender's public key on success, or nill bytes and a nil sender on failure.
func Decrypt(key *keys.EdX25519Key, input []byte) ([]byte, *keys.EdX25519PublicKey, error) {
return saltpack.SigncryptOpen(input, true, saltpack.NewKeyring(key))
}

@ -0,0 +1,27 @@
package salty
import (
"io"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCrypto(t *testing.T) {
assert := assert.New(t)
key, pub := GenerateKeys(io.Discard)
assert.NotEmpty(key)
assert.NotEmpty(pub)
assert.Equal(key.PublicKey().String(), pub)
input := []byte("Hello World!")
encrypted, err := Encrypt(key, input, []string{pub})
assert.NoError(err)
decrypted, sender, err := Decrypt(key, encrypted)
assert.NoError(err)
assert.Equal(pub, sender.String())
assert.Equal(decrypted, input)
}

@ -6,6 +6,7 @@ require (
github.com/keys-pub/keys v0.1.22
github.com/sirupsen/logrus v1.8.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
)
@ -19,6 +20,7 @@ require (
github.com/keybase/go-codec v0.0.0-20180928230036-164397562123 // indirect
github.com/keybase/saltpack v0.0.0-20200430135328-e19b1910c0c5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
github.com/vmihailenco/tagparser v0.1.2 // indirect
@ -27,4 +29,5 @@ require (
golang.org/x/sys v0.0.0-20210324051608-47abb6519492 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.26.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

@ -0,0 +1,54 @@
package salty
import (
"bufio"
"encoding/base64"
"fmt"
"io"
"strings"
"time"
"github.com/keys-pub/keys"
)
const privateKeySizeLimit = 1 << 8 // 256 bytes
// GenerateKeys creates a new pair of Ed25519 keys and writes the Private Key
// to the `out io.Writer` and returns the Private and Public Keys.
// The Private Key written to `out` is Base64 encoded.
func GenerateKeys(out io.Writer) (*keys.EdX25519Key, string) {
k := keys.GenerateEdX25519Key()
fmt.Fprintf(out, "# created: %s\n", time.Now().Format(time.RFC3339))
fmt.Fprintf(out, "# public key: %s\n", k.PublicKey().ID().String())
fmt.Fprintf(out, "%s\n", base64.StdEncoding.EncodeToString(k.Private()))
return k, k.PublicKey().ID().String()
}
// ParseIdentity parses the Salty Identity file given by `r io.Reader` which has a
// line-oriented format where comments (lines beginning with a #) and the and blank
// lines are ignored and the private key is the first non-comment / non-blank line.
// The Private Key is a Base64 decoded.
// This returns the parsed Ed25519 key on success or nil key and error if it fails.
func ParseIdentity(r io.Reader) (*keys.EdX25519Key, error) {
scanner := bufio.NewScanner(io.LimitReader(r, privateKeySizeLimit))
var n int
for scanner.Scan() {
line := scanner.Text()
n++
if strings.HasPrefix(line, "#") || line == "" {
continue
}
bs, err := base64.StdEncoding.DecodeString(line)
if err != nil {
return nil, fmt.Errorf("error at line %d: %v", n, err)
}
return keys.NewEdX25519KeyFromPrivateKey(keys.Bytes64(bs)), nil
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("failed to read identity file: %v", err)
}
return nil, fmt.Errorf("no key found")
}

@ -0,0 +1,21 @@
package salty
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestKeys(t *testing.T) {
assert := assert.New(t)
buf := &bytes.Buffer{}
key, pub := GenerateKeys(buf)
assert.NotEmpty(pub)
parsedKey, err := ParseIdentity(buf)
assert.NoError(err)
assert.Equal(key, parsedKey)
assert.Equal(pub, key.PublicKey().String())
}
Loading…
Cancel
Save