1// Copyright 2020 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package engine
5
6import (
7	"errors"
8	"fmt"
9
10	"github.com/keybase/client/go/libkb"
11	keybase1 "github.com/keybase/client/go/protocol/keybase1"
12	jsonw "github.com/keybase/go-jsonw"
13)
14
15type WotVouchArg struct {
16	Vouchee    keybase1.UserVersion
17	Confidence keybase1.Confidence
18	VouchText  string
19}
20
21// WotVouch is an engine.
22type WotVouch struct {
23	arg *WotVouchArg
24	libkb.Contextified
25}
26
27// NewWotVouch creates a WotVouch engine.
28func NewWotVouch(g *libkb.GlobalContext, arg *WotVouchArg) *WotVouch {
29	return &WotVouch{
30		arg:          arg,
31		Contextified: libkb.NewContextified(g),
32	}
33}
34
35// Name is the unique engine name.
36func (e *WotVouch) Name() string {
37	return "WotVouch"
38}
39
40// GetPrereqs returns the engine prereqs.
41func (e *WotVouch) Prereqs() Prereqs {
42	return Prereqs{Device: true}
43}
44
45// RequiredUIs returns the required UIs.
46func (e *WotVouch) RequiredUIs() []libkb.UIKind {
47	return []libkb.UIKind{}
48}
49
50// SubConsumers returns the other UI consumers for this engine.
51func (e *WotVouch) SubConsumers() []libkb.UIConsumer {
52	return nil
53}
54
55func getSigIDToRevoke(mctx libkb.MetaContext, vouchee *libkb.User) (toRevoke *keybase1.SigID, err error) {
56	voucherUsername := mctx.ActiveDevice().Username(mctx).String()
57	vouches, err := libkb.FetchWotVouches(mctx, libkb.FetchWotVouchesArg{Voucher: voucherUsername, Vouchee: vouchee.GetName()})
58	if err != nil {
59		return nil, err
60	}
61	var unrevokedVouches []keybase1.WotVouch
62	for _, vouch := range vouches {
63		if vouch.Status != keybase1.WotStatusType_REVOKED {
64			unrevokedVouches = append(unrevokedVouches, vouch)
65		}
66	}
67	switch {
68	case len(unrevokedVouches) > 1:
69		return nil, fmt.Errorf("there should be at most one existing vouch to revoke, but there are %d", len(unrevokedVouches))
70	case len(unrevokedVouches) == 1:
71		return &unrevokedVouches[0].VouchProof, nil
72	default:
73		return nil, nil
74	}
75}
76
77// Run starts the engine.
78func (e *WotVouch) Run(mctx libkb.MetaContext) error {
79	ctx := mctx.Ctx()
80	g := mctx.G()
81	luArg := libkb.NewLoadUserArgWithMetaContext(mctx).WithUID(e.arg.Vouchee.Uid).WithStubMode(libkb.StubModeUnstubbed)
82	them, err := libkb.LoadUser(luArg)
83	if err != nil {
84		return err
85	}
86
87	if them.GetCurrentEldestSeqno() != e.arg.Vouchee.EldestSeqno {
88		mctx.Debug("eldest seqno mismatch: loaded %v != %v caller", them.GetCurrentEldestSeqno(), e.arg.Vouchee.EldestSeqno)
89		return errors.New("vouchee has reset, make sure you still know them")
90	}
91
92	if e.arg.Confidence.UsernameVerifiedVia == "" {
93		return errors.New("missing UsernameVerifiedVia")
94	}
95	if _, found := keybase1.UsernameVerificationTypeMap[string(e.arg.Confidence.UsernameVerifiedVia)]; !found {
96		return fmt.Errorf("unrecognized UsernameVerificationTypeMap value '%v'", e.arg.Confidence.UsernameVerifiedVia)
97	}
98
99	if e.arg.Confidence.UsernameVerifiedVia == keybase1.UsernameVerificationType_PROOFS {
100		if len(e.arg.Confidence.Proofs) == 0 {
101			return errors.New("vouching with proofs requires proofs list")
102		}
103	} else {
104		if len(e.arg.Confidence.Proofs) > 0 {
105			return errors.New("vouching with proof list requires proof type")
106		}
107	}
108
109	statement := jsonw.NewDictionary()
110	if err := statement.SetKey("user", them.ToWotStatement()); err != nil {
111		return err
112	}
113	confidenceJw, err := jsonw.WrapperFromObject(e.arg.Confidence)
114	if err != nil {
115		return err
116	}
117	if err := statement.SetKey("confidence", confidenceJw); err != nil {
118		return err
119	}
120	if err := statement.SetKey("vouch_text", jsonw.NewString(e.arg.VouchText)); err != nil {
121		return err
122	}
123	expansions, sum, err := libkb.EmbedExpansionObj(statement)
124	if err != nil {
125		return err
126	}
127
128	signingKey, err := mctx.G().ActiveDevice.SigningKey()
129	if err != nil {
130		return err
131	}
132
133	sigIDToRevoke, err := getSigIDToRevoke(mctx, them)
134	if err != nil {
135		return err
136	}
137	var lease *libkb.Lease
138	var merkleRoot *libkb.MerkleRoot
139	if sigIDToRevoke != nil {
140		lease, merkleRoot, err = libkb.RequestDowngradeLeaseBySigIDs(ctx, g, []keybase1.SigID{*sigIDToRevoke})
141		if err != nil {
142			return err
143		}
144		defer func() {
145			// not sure if this is necessary or not
146			err := libkb.CancelDowngradeLease(ctx, g, lease.LeaseID)
147			if err != nil {
148				g.Log.CWarningf(ctx, "Failed to cancel downgrade lease: %s", err.Error())
149			}
150		}()
151	}
152
153	sigVersion := libkb.KeybaseSignatureV2
154	var inner []byte
155	var sig string
156
157	// ForcePoll is required.
158	var proof *libkb.ProofMetadataRes
159	var linkID libkb.LinkID
160	err = mctx.G().GetFullSelfer().WithSelfForcePoll(ctx, func(me *libkb.User) (err error) {
161		if me.GetUID() == e.arg.Vouchee.Uid {
162			return libkb.InvalidArgumentError{Msg: "can't vouch for yourself"}
163		}
164		proof, err = me.WotVouchProof(mctx, signingKey, sigVersion, sum, merkleRoot, sigIDToRevoke)
165		if err != nil {
166			return err
167		}
168		inner, err = proof.J.Marshal()
169		if err != nil {
170			return err
171		}
172		sig, _, linkID, err = libkb.MakeSig(
173			mctx,
174			signingKey,
175			libkb.LinkTypeWotVouch,
176			inner,
177			libkb.SigHasRevokes(sigIDToRevoke != nil),
178			keybase1.SeqType_PUBLIC,
179			libkb.SigIgnoreIfUnsupported(true),
180			me,
181			sigVersion,
182		)
183
184		return err
185	})
186	if err != nil {
187		return err
188	}
189
190	item := libkb.SigMultiItem{
191		Sig:        sig,
192		SigningKID: signingKey.GetKID(),
193		Type:       string(libkb.LinkTypeWotVouch),
194		SeqType:    keybase1.SeqType_PUBLIC,
195		SigInner:   string(inner),
196		Version:    sigVersion,
197		Expansions: expansions,
198	}
199
200	payload := make(libkb.JSONPayload)
201	payload["sigs"] = []interface{}{item}
202	if lease != nil {
203		payload["downgrade_lease_id"] = lease.LeaseID
204	}
205	if _, err := e.G().API.PostJSON(mctx, libkb.APIArg{
206		Endpoint:    "sig/multi",
207		SessionType: libkb.APISessionTypeREQUIRED,
208		JSONPayload: payload,
209	}); err != nil {
210		return err
211	}
212
213	me, err := libkb.LoadMe(libkb.NewLoadUserArgWithMetaContext(mctx))
214	if err != nil {
215		return err
216	}
217	err = libkb.MerkleCheckPostedUserSig(mctx, me.GetUID(), proof.Seqno, linkID)
218	if err != nil {
219		return err
220	}
221
222	voucherUsername := mctx.ActiveDevice().Username(mctx).String()
223	mctx.G().NotifyRouter.HandleWebOfTrustChanged(voucherUsername)
224	return libkb.DismissWotNotifications(mctx, voucherUsername, them.GetName())
225}
226