added shamir keys

This commit is contained in:
ston1th 2023-03-20 21:18:44 +01:00
commit be0f532628
14 changed files with 238 additions and 43 deletions

View file

@ -51,7 +51,6 @@ func NewServer(log logr.Logger, listen, socket string) (*Server, error) {
s.listen = l
sock, err := unixListener(socket)
net.Listen("unix", socket)
if err != nil {
return nil, err
}

View file

@ -83,14 +83,14 @@ func (c *Client) GetKey(ctx context.Context, id string) (key schema.Key, err err
return
}
func (c *Client) CreateKey(ctx context.Context, key schema.NewKey) (err error) {
func (c *Client) CreateKey(ctx context.Context, newKey schema.NewKey) (key schema.Key, err error) {
buf := new(bytes.Buffer)
path := keyPath
err = key.Validate()
err = newKey.Validate()
if err != nil {
return
}
err = json.NewEncoder(buf).Encode(key)
err = json.NewEncoder(buf).Encode(newKey)
if err != nil {
return
}
@ -98,7 +98,7 @@ func (c *Client) CreateKey(ctx context.Context, key schema.NewKey) (err error) {
if err != nil {
return
}
_, err = c.c.Do(req, nil)
_, err = c.c.Do(req, &key)
return
}

View file

@ -3,7 +3,10 @@
package schema
import (
"encoding/hex"
"errors"
"fmt"
"time"
"git.giftfish.de/ston1th/keyctl/pkg/api/types"
"git.giftfish.de/ston1th/keyctl/pkg/core"
@ -22,6 +25,7 @@ type NewKey struct {
Name string `json:"name"`
Size int `json:"size"`
Encoding core.Encoding `json:"encoding"`
Type core.Type `json:"type"`
}
var (
@ -58,13 +62,52 @@ type Key struct {
Created int64 `json:"created"`
Size int `json:"size"`
Encoding core.Encoding `json:"encoding"`
Type core.Type `json:"type"`
Key string `json:"key,omitempty"`
}
func (k Key) Print(share string) (string, error) {
switch k.Type {
case core.Shamir:
part1, err := k.Encoding.Decode(k.Key)
if err != nil {
return "", err
}
part2, err := hex.DecodeString(share)
if err != nil {
return "", err
}
dec, err := k.Type.Decode(append(part1, part2...))
if err != nil {
return "", err
}
return k.Encoding.EncodeToString(dec), nil
}
return k.Key, nil
}
type Keys []Key
func (Keys) Less(a, b Key) bool { return a.Name < b.Name }
func (k Keys) Sort() { slices.SortStableFunc(k, k.Less) }
func (k Keys) Len() int { return len(k) }
func (k Keys) Fields() string {
return "ID\t Name\t Created\t Size\t Encoding\t Type"
}
func (k Keys) Values() []string {
vals := make([]string, len(k))
for i, v := range k {
vals[i] = fmt.Sprintf("%s\t %s\t %s\t %d\t %s\t %s",
v.ID,
v.Name,
timeFmt(v.Created),
v.Size,
v.Encoding,
v.Type,
)
}
return vals
}
func NewKeys(keys core.Keys) (k Keys) {
for _, key := range keys {
@ -87,6 +130,24 @@ type Approvals []Approval
func (Approvals) Less(a, b Approval) bool { return a.Name < b.Name }
func (a Approvals) Sort() { slices.SortStableFunc(a, a.Less) }
func (a Approvals) Len() int { return len(a) }
func (a Approvals) Fields() string {
return "ID\t Approval ID\t Name\t Status\t Created\t IP"
}
func (a Approvals) Values() []string {
vals := make([]string, len(a))
for i, v := range a {
vals[i] = fmt.Sprintf("%s\t %s\t %s\t %s\t %s\t %s",
v.ID,
v.AID,
v.Name,
v.Status,
timeFmt(v.Created),
v.IP,
)
}
return vals
}
func NewApprovals(m types.ApprovalMap) (a Approvals) {
for k, v := range m {
@ -102,3 +163,7 @@ func NewApprovals(m types.ApprovalMap) (a Approvals) {
a.Sort()
return
}
func timeFmt(t int64) string {
return time.Unix(t, 0).Format(time.RFC3339)
}

View file

@ -56,7 +56,7 @@ func newHandler(ctx *types.Context) {
ctx.Err(types.ErrInvalid)
return
}
key, err := ctx.Data.DB.CreateKey(newKey.Name, newKey.Size, newKey.Encoding)
key, err := ctx.Data.DB.CreateKey(newKey.Name, newKey.Size, newKey.Encoding, newKey.Type)
if err != nil {
ctx.Log.Error(err, "error writing key", "name", newKey.Name)
ctx.Err(types.ErrISE)

View file

@ -5,9 +5,11 @@ package cli
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"text/tabwriter"
apiclient "git.giftfish.de/ston1th/keyctl/pkg/api/client"
clientv1 "git.giftfish.de/ston1th/keyctl/pkg/api/v1/client"
@ -27,33 +29,64 @@ func Run(arg string) {
ctx := context.Background()
switch arg {
case "new":
name, encoding, size := newKeyFlags()
name, encoding, t, size, err := newKeyFlags()
if err != nil {
exit(err)
}
newKey := schema.NewKey{
Name: name,
Size: size,
Encoding: core.EncodingFromString(encoding),
Type: core.TypeFromString(t),
}
c := client()
err = c.CreateKey(ctx, newKey)
k, err := c.CreateKey(ctx, newKey)
if err != nil {
exit(err)
}
switch k.Type {
case core.Plain:
fmt.Println("Key ID:", k.ID)
case core.Shamir:
fmt.Println("Key ID:", k.ID)
fmt.Println("Key Share:", k.Key)
}
case "del":
id := idFlags("key id")
id, err := idFlags("key id")
if err != nil {
exit(err)
}
c := client()
err = c.DeleteKey(ctx, id)
case "approve":
id := idFlags("approval id")
id, err := idFlags("approval id")
if err != nil {
exit(err)
}
c := client()
err = c.AcceptApproval(ctx, id)
case "reject":
id := idFlags("approval id")
id, err := idFlags("approval id")
if err != nil {
exit(err)
}
c := client()
err = c.RejectApproval(ctx, id)
case "get":
id, json := getFlags()
id, share, json, err := getFlags()
if err != nil {
exit(err)
}
c := client()
key, err := c.GetKey(ctx, id)
if err != nil {
exit(err)
}
out, err := key.Print(share)
if err != nil {
exit(err)
}
key.Key = out
if json {
jsonOutput(key) // does not return
}
@ -68,15 +101,7 @@ func Run(arg string) {
if json {
jsonOutput(keys) // does not return
}
for _, k := range keys {
fmt.Printf("%s %s %d %s %d\n",
k.ID,
k.Name,
k.Size,
k.Encoding,
k.Created,
)
}
print(keys)
case "req":
json := outputFlags()
c := client()
@ -87,16 +112,7 @@ func Run(arg string) {
if json {
jsonOutput(approvals) // does not return
}
for _, a := range approvals {
fmt.Printf("%s %s %s %s %d %s\n",
a.ID,
a.AID,
a.Name,
a.Status,
a.Created,
a.IP,
)
}
print(approvals)
}
if err != nil {
exit(err)
@ -126,16 +142,23 @@ func jsonOutput(v any) {
os.Exit(0)
}
func idFlags(desc string) (id string) {
func idFlags(desc string) (id string, err error) {
fs.StringVar(&id, "id", "", desc)
fs.Parse(os.Args[2:])
if id == "" {
err = errors.New("missing flag: id")
}
return
}
func getFlags() (id string, json bool) {
func getFlags() (id, share string, json bool, err error) {
fs.StringVar(&id, "id", "", "key id")
fs.StringVar(&share, "share", "", "shamir key share")
fs.BoolVar(&json, "json", false, "json output")
fs.Parse(os.Args[2:])
if id == "" {
err = errors.New("missing flag: id")
}
return
}
@ -145,10 +168,26 @@ func outputFlags() (json bool) {
return
}
func newKeyFlags() (name, encoding string, size int) {
func newKeyFlags() (name, encoding, t string, size int, err error) {
fs.StringVar(&name, "name", "", "key name")
fs.StringVar(&encoding, "enc", "hex", "key encoding scheme [hex|base32|base64|base64url]")
fs.StringVar(&t, "type", "plain", "key type [plain|shamir]")
fs.IntVar(&size, "size", 16, "key size in bytes")
fs.Parse(os.Args[2:])
if name == "" {
err = errors.New("missing flag: name")
}
return
}
func print(tw core.TableWriter) {
if tw.Len() == 0 {
return
}
w := tabwriter.NewWriter(os.Stdout, 1, 0, 1, ' ', tabwriter.Debug)
fmt.Fprintln(w, tw.Fields())
for _, v := range tw.Values() {
fmt.Fprintln(w, v)
}
w.Flush()
}

View file

@ -36,6 +36,18 @@ func (e Encoding) EncodeToString(src []byte) string {
return hex.EncodeToString(src)
}
func (e Encoding) Decode(src string) ([]byte, error) {
switch e {
case Base32:
return b32raw.DecodeString(src)
case Base64:
return base64.RawStdEncoding.DecodeString(src)
case Base64URL:
return base64.RawURLEncoding.DecodeString(src)
}
return hex.DecodeString(src)
}
func EncodingFromString(e string) Encoding {
switch e {
case "base32":

View file

@ -20,6 +20,7 @@ type Key struct {
Created int64
Size int
Encoding Encoding
Type Type
Key string
}

9
pkg/core/tablewriter.go Normal file
View file

@ -0,0 +1,9 @@
// Copyright (C) 2023 Marius Schellenberger
package core
type TableWriter interface {
Len() int
Fields() string
Values() []string
}

55
pkg/core/type.go Normal file
View file

@ -0,0 +1,55 @@
// Copyright (C) 2023 Marius Schellenberger
package core
import (
"bytes"
"github.com/hashicorp/vault/shamir"
)
type Type int
func (t Type) String() string {
switch t {
case Shamir:
return "shamir"
}
return "plain"
}
func (t Type) Decode(src []byte) ([]byte, error) {
switch t {
case Shamir:
plen := len(src) / 2
part1 := src[0:plen]
part2 := src[plen:]
return shamir.Combine([][]byte{part1, part2})
}
return src, nil
}
func (t Type) Encode(src []byte) ([]byte, error) {
switch t {
case Shamir:
parts, err := shamir.Split(src, 2, 2)
if err != nil {
return nil, err
}
return bytes.Join(parts, nil), nil
}
return src, nil
}
func TypeFromString(t string) Type {
switch t {
case "shamir":
return Shamir
}
return Plain
}
const (
Plain Type = iota
Shamir
)

View file

@ -31,12 +31,12 @@ func (db *DB) GetKeyWithSecret(id string) (k core.Key, err error) {
return
}
func (db *DB) CreateKey(name string, size int, enc core.Encoding) (k core.Key, err error) {
k, err = key.Generate(name, size, enc)
func (db *DB) CreateKey(name string, size int, enc core.Encoding, t core.Type) (k core.Key, err error) {
kstore, k, err := key.Generate(name, size, enc, t)
if err != nil {
return
}
err = db.store.Set(keyPrefix+k.ID, &k)
err = db.store.Set(keyPrefix+k.ID, &kstore)
return
}

View file

@ -27,7 +27,7 @@ func GenerateID() (string, error) {
return hex.EncodeToString(id), nil
}
func Generate(name string, size int, enc core.Encoding) (k core.Key, err error) {
func Generate(name string, size int, enc core.Encoding, t core.Type) (kstore, k core.Key, err error) {
id, err := GenerateID()
if err != nil {
return
@ -36,13 +36,24 @@ func Generate(name string, size int, enc core.Encoding) (k core.Key, err error)
if err != nil {
return
}
k = core.Key{
key, err = t.Encode(key)
if err != nil {
return
}
kstore = core.Key{
Name: name,
ID: id,
Size: size,
Encoding: enc,
Key: enc.EncodeToString(key),
Type: t,
Created: time.Now().Unix(),
}
k = kstore
if t == core.Shamir {
klen := len(key) / 2
k.Key = core.Hex.EncodeToString(key[klen:])
key = key[0:klen]
}
kstore.Key = enc.EncodeToString(key)
return
}