1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	"errors"
8	"time"
9
10	"github.com/keybase/client/go/protocol/keybase1"
11)
12
13// DelegatorAggregator manages delegating multiple keys in one post to the
14// server When run produces a map which goes into the 'key/multi' 'sigs' list.
15type AggSigProducer func() (JSONPayload, keybase1.Seqno, LinkID, error)
16
17// Run posts an array of delegations to the server. Keeping this simple as we
18// don't need any state (yet) `extra` is optional and adds an extra sig,
19// produced by something other than a Delegator, after the others.
20func DelegatorAggregator(m MetaContext, ds []Delegator, extra AggSigProducer,
21	pukBoxes []keybase1.PerUserKeyBox, pukPrev *PerUserKeyPrev, userEKReboxArg *keybase1.UserEkReboxArg) (err error) {
22	if len(ds) == 0 {
23		return errors.New("Empty delegators to aggregator")
24	}
25
26	// Store all the args to build a single big json later
27	var args []JSONPayload
28	var uid keybase1.UID
29	var lastSeqno keybase1.Seqno
30	var lastLinkID LinkID
31
32	for i := range ds {
33		// Mutate the original and not a copy from range
34		var d = &ds[i]
35
36		d.Aggregated = true
37		if err := d.Run(m); err != nil {
38			return err
39		}
40
41		flatArgs := d.postArg.flattenHTTPArgs(d.postArg.getHTTPArgs())
42		args = append(args, convertStringMapToJSONPayload(flatArgs))
43		if uid.IsNil() && d.Me != nil {
44			uid = d.Me.GetUID()
45		}
46		lastSeqno = d.proof.Seqno
47		lastLinkID = d.linkID
48	}
49
50	if extra != nil {
51		x, seqno, linkID, err := extra()
52		if err != nil {
53			return err
54		}
55		args = append(args, x)
56		lastSeqno = seqno
57		lastLinkID = linkID
58	}
59
60	payload := make(JSONPayload)
61	payload["sigs"] = args
62
63	if len(pukBoxes) > 0 {
64		AddPerUserKeyServerArg(payload, pukBoxes[0].Generation, pukBoxes, pukPrev)
65	} else if pukPrev != nil {
66		return errors.New("cannot delegate per-user-key with prev but no boxes")
67	}
68	AddUserEKReBoxServerArg(payload, userEKReboxArg)
69
70	// Adopt most parameters from the first item
71	var apiArgBase = ds[0]
72	var apiArg = apiArgBase.postArg
73	apiArg.Args = nil
74	apiArg.uArgs = nil
75	apiArg.Endpoint = "key/multi"
76	apiArg.JSONPayload = payload
77	// It's bad to fail provisioning especially after signup.
78	apiArg.InitialTimeout = 5 * time.Minute
79	apiArg.RetryCount = 10
80
81	_, err = m.G().API.PostJSON(m, apiArg)
82	if err != nil {
83		return err
84	}
85	return MerkleCheckPostedUserSig(m, uid, lastSeqno, lastLinkID)
86}
87
88// Make the "per_user_key" section of an API arg.
89// Requires one or more `pukBoxes`
90// `pukPrev` is optional.
91// Modifies `serverArg`.
92func AddPerUserKeyServerArg(serverArg JSONPayload, generation keybase1.PerUserKeyGeneration,
93	pukBoxes []keybase1.PerUserKeyBox, pukPrev *PerUserKeyPrev) {
94	section := make(JSONPayload)
95	section["boxes"] = pukBoxes
96	section["generation"] = generation
97	if pukPrev != nil {
98		section["prev"] = *pukPrev
99	}
100	serverArg["per_user_key"] = section
101}
102
103// Make the "user_ek_rebox" and "device_eks" section of an API arg.  Modifies
104// `serverArg` unless arg is nil.
105func AddUserEKReBoxServerArg(serverArg JSONPayload, arg *keybase1.UserEkReboxArg) {
106	if arg == nil {
107		return
108	}
109	serverArg["device_eks"] = map[string]string{string(arg.DeviceID): arg.DeviceEkStatementSig}
110	serverArg["user_ek_rebox"] = arg.UserEkBoxMetadata
111}
112
113func convertStringMapToJSONPayload(in map[string]string) JSONPayload {
114	out := make(JSONPayload)
115	for k, v := range in {
116		out[k] = v
117	}
118	return out
119}
120