From 9e68b46346dc5ebc7135700ef9c2183e4e8a13f2 Mon Sep 17 00:00:00 2001 From: ston1th Date: Wed, 22 Mar 2023 22:52:19 +0100 Subject: [PATCH] added get loop and new key type xor --- pkg/api/client/client.go | 39 ++++++++++++++++++++++++++++++++++-- pkg/api/types/approver.go | 18 ++++++++++++++--- pkg/api/v1/client/client.go | 10 +++++++++ pkg/api/v1/schema/schema.go | 4 ++-- pkg/api/v1/server/handler.go | 14 ++++++++++--- pkg/cli/cli.go | 26 ++++++++++++++++-------- pkg/core/type.go | 26 ++++++++++++++++++++---- pkg/key/key.go | 9 +++++++-- 8 files changed, 122 insertions(+), 24 deletions(-) diff --git a/pkg/api/client/client.go b/pkg/api/client/client.go index 2f5168c..5f6ea13 100644 --- a/pkg/api/client/client.go +++ b/pkg/api/client/client.go @@ -15,6 +15,8 @@ import ( "os" "strings" "time" + + "git.giftfish.de/ston1th/keyctl/pkg/api/types" ) type Client struct { @@ -181,14 +183,47 @@ func (c *Client) Do(r *http.Request, v any) (resp *http.Response, err error) { } func (c *Client) FollowRedirect(r *http.Request, v any, delay time.Duration) (resp *http.Response, err error) { - rr := r + ctx := r.Context() + rr := r.Clone(ctx) for { resp, err = c.Do(rr, v) + if err == nil && resp.StatusCode == http.StatusOK { + return + } rdr, ok := err.(Redirect) if !ok { return } - rr, err = c.NewRequest(r.Context(), "GET", rdr.Location, nil) + rr, err = c.NewRequest(ctx, "GET", rdr.Location, nil) + if err != nil { + return + } + time.Sleep(delay) + } +} + +func (c *Client) FollowRedirectLoop(r *http.Request, v any, delay time.Duration) (resp *http.Response, err error) { + ctx := r.Context() + rr := r.Clone(ctx) + for { + resp, err = c.Do(rr, v) + rdr, ok := err.(Redirect) + if err != nil && !ok { + if errors.Is(err, types.ErrISE) || + errors.Is(err, types.ErrKeyNotFound) { + return + } + if errors.Is(err, types.ErrReqNotFound) || + errors.Is(err, types.ErrKeyRequestRejected) { + rr = r.Clone(ctx) + } + time.Sleep(delay) + continue + } + if err == nil && resp.StatusCode == http.StatusOK { + return + } + rr, err = c.NewRequest(ctx, "GET", rdr.Location, nil) if err != nil { return } diff --git a/pkg/api/types/approver.go b/pkg/api/types/approver.go index 76f98c2..9c00a8a 100644 --- a/pkg/api/types/approver.go +++ b/pkg/api/types/approver.go @@ -54,8 +54,14 @@ func (a *Approver) List() (m ApprovalMap) { } func (a *Approver) Status(id, aid, ip string) (s Status) { a.mu.RLock() - if app, ok := a.m[aid]; ok && app.ID == id && app.IP == ip { - s = app.Status + if app, ok := a.m[aid]; ok { + if app.ID == id && app.IP == ip { + s = app.Status + } else { + s = Rejected + } + } else { + s = NotFound } a.mu.RUnlock() if s == Approved || s == Rejected { @@ -66,13 +72,16 @@ func (a *Approver) Status(id, aid, ip string) (s Status) { return } -func (a *Approver) Update(aid string, s Status) { +func (a *Approver) Update(aid string, s Status) (err error) { a.mu.Lock() if app, ok := a.m[aid]; ok { app.Status = s a.m[aid] = app + } else { + err = ErrReqNotFound } a.mu.Unlock() + return } type Approval struct { @@ -88,6 +97,7 @@ const ( Pending Status = iota Approved Rejected + NotFound ) func (s Status) String() string { @@ -98,6 +108,8 @@ func (s Status) String() string { return "approved" case Rejected: return "rejected" + case NotFound: + return "notfound" } return "[invalid]" } diff --git a/pkg/api/v1/client/client.go b/pkg/api/v1/client/client.go index d566ea8..f96994f 100644 --- a/pkg/api/v1/client/client.go +++ b/pkg/api/v1/client/client.go @@ -83,6 +83,16 @@ func (c *Client) GetKey(ctx context.Context, id string) (key schema.Key, err err return } +func (c *Client) GetKeyLoop(ctx context.Context, id string) (key schema.Key, err error) { + path := fmt.Sprintf("%s/%s", keyPath, id) + req, err := c.c.NewRequest(ctx, "GET", path, nil) + if err != nil { + return + } + _, err = c.c.FollowRedirectLoop(req, &key, time.Second*2) + return +} + func (c *Client) CreateKey(ctx context.Context, newKey schema.NewKey) (key schema.Key, err error) { buf := new(bytes.Buffer) path := keyPath diff --git a/pkg/api/v1/schema/schema.go b/pkg/api/v1/schema/schema.go index 15360a8..9a0d0ca 100644 --- a/pkg/api/v1/schema/schema.go +++ b/pkg/api/v1/schema/schema.go @@ -90,7 +90,7 @@ 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 Size\t Encoding\t Type" + return "Key ID\t Name\t Size\t Encoding\t Type" } func (k Keys) Values() []string { vals := make([]string, len(k)) @@ -128,7 +128,7 @@ 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 IP" + return "Key ID\t Approval ID\t Name\t Status\t IP" } func (a Approvals) Values() []string { vals := make([]string, len(a)) diff --git a/pkg/api/v1/server/handler.go b/pkg/api/v1/server/handler.go index 95f84e5..953440d 100644 --- a/pkg/api/v1/server/handler.go +++ b/pkg/api/v1/server/handler.go @@ -64,7 +64,7 @@ func keyHandler(ctx *types.Context) { if err != nil { ctx.Log.Error(err, "error reading key", "id", id) if err == store.ErrKeyNotFound { - ctx.Err(types.ErrNotFound) + ctx.Err(types.ErrKeyNotFound) return } ctx.Err(types.ErrISE) @@ -77,7 +77,7 @@ func keyHandler(ctx *types.Context) { aid, err = ctx.Data.Approver.New(id, k.Name, ip) if err != nil { ctx.Log.Error(err, "error creating approval", "id", id, "name", k.Name) - ctx.Err(types.ErrISE) + ctx.Err(types.ErrTooManyApprovals) return } ctx.Redirect(schema.ApproveURL(id, aid), http.StatusFound) @@ -91,6 +91,9 @@ func keyHandler(ctx *types.Context) { case types.Rejected: ctx.Err(types.ErrKeyRequestRejected) return + case types.NotFound: + ctx.Err(types.ErrReqNotFound) + return } k, err := ctx.Data.DB.GetKeyWithSecret(id) if err != nil { @@ -142,6 +145,11 @@ func reqHandler(ctx *types.Context) { case "DELETE": status = types.Rejected } - ctx.Data.Approver.Update(id, status) + err := ctx.Data.Approver.Update(id, status) + if err != nil { + ctx.Log.Error(err, "error updating key request") + ctx.Err(types.ErrReqNotFound) + return + } ctx.OK() } diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index d2341b3..f520441 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -20,7 +20,7 @@ import ( var fs = flag.NewFlagSet("", flag.ExitOnError) func exit(err error) { - fmt.Println(err) + fmt.Fprintf(os.Stderr, "%s\n", err) os.Exit(1) } @@ -44,7 +44,8 @@ func Run(arg string) { exit(err) } fmt.Println("Key ID:", k.ID) - if k.Type == core.Shamir { + if k.Type == core.Shamir || k.Type == core.Xor { + fmt.Println("Key Type:", k.Type) fmt.Println("Key Share:", k.Key) } case "del": @@ -78,14 +79,22 @@ func Run(arg string) { exit(err) } case "get": - id, share, json, err := getFlags() + id, share, loop, json, err := getFlags() if err != nil { exit(err) } c := client() - key, err := c.GetKey(ctx, id) - if err != nil { - exit(err) + var key schema.Key + if loop { + key, err = c.GetKeyLoop(ctx, id) + if err != nil { + exit(err) + } + } else { + key, err = c.GetKey(ctx, id) + if err != nil { + exit(err) + } } out, err := key.Print(share) if err != nil { @@ -153,9 +162,10 @@ func idFlags(desc string) (id string, err error) { return } -func getFlags() (id, share string, json bool, err error) { +func getFlags() (id, share string, loop, json bool, err error) { fs.StringVar(&id, "id", "", "key id") fs.StringVar(&share, "share", "", "shamir key share") + fs.BoolVar(&loop, "loop", false, "ignore network errors and loop until key is received") fs.BoolVar(&json, "json", false, "json output") fs.Parse(os.Args[2:]) if id == "" { @@ -176,7 +186,7 @@ func outputFlags() (json bool) { 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.StringVar(&t, "type", "plain", "key type [plain|shamir|xor]") fs.IntVar(&size, "size", 16, "key size in bytes") fs.Parse(os.Args[2:]) if name == "" { diff --git a/pkg/core/type.go b/pkg/core/type.go index 441f397..098d703 100644 --- a/pkg/core/type.go +++ b/pkg/core/type.go @@ -13,12 +13,15 @@ type Type int const ( Plain Type = iota Shamir + Xor ) func (t Type) String() string { switch t { case Shamir: return "shamir" + case Xor: + return "xor" } return "plain" } @@ -26,14 +29,27 @@ func (t Type) String() string { 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}) + p1, p2 := split(src) + return shamir.Combine([][]byte{p1, p2}) + case Xor: + return xor(src), nil } return src, nil } +func split(src []byte) (p1, p2 []byte) { + plen := len(src) / 2 + return src[0:plen], src[plen:] +} + +func xor(src []byte) []byte { + p1, p2 := split(src) + for i := 0; i < len(p1); i++ { + p1[i] ^= p2[i] + } + return p1 +} + func (t Type) Encode(src []byte) ([]byte, error) { switch t { case Shamir: @@ -50,6 +66,8 @@ func TypeFromString(t string) Type { switch t { case "shamir": return Shamir + case "xor": + return Xor } return Plain } diff --git a/pkg/key/key.go b/pkg/key/key.go index 77b4b3d..39c3871 100644 --- a/pkg/key/key.go +++ b/pkg/key/key.go @@ -31,7 +31,12 @@ func Generate(name string, size int, enc core.Encoding, t core.Type) (kstore, k if err != nil { return } - key, err := generate(size) + var key []byte + if t == core.Xor { + key, err = generate(size * 2) + } else { + key, err = generate(size) + } if err != nil { return } @@ -47,7 +52,7 @@ func Generate(name string, size int, enc core.Encoding, t core.Type) (kstore, k Type: t, } k = kstore - if t == core.Shamir { + if t == core.Shamir || t == core.Xor { klen := len(key) / 2 k.Key = core.Hex.EncodeToString(key[klen:]) key = key[0:klen]