github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/puk_roll.go (about)

     1  // Copyright 2017 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  // PerUserKeyRoll creates a new per-user-key for the active user.
     5  // This can be the first per-user-key for the user.
     6  package engine
     7  
     8  import (
     9  	"fmt"
    10  
    11  	"github.com/keybase/client/go/libkb"
    12  	"github.com/keybase/client/go/protocol/keybase1"
    13  )
    14  
    15  // PerUserKeyRoll is an engine.
    16  type PerUserKeyRoll struct {
    17  	libkb.Contextified
    18  	args      *PerUserKeyRollArgs
    19  	DidNewKey bool
    20  }
    21  
    22  type PerUserKeyRollArgs struct {
    23  	Me *libkb.User // optional
    24  }
    25  
    26  // NewPerUserKeyRoll creates a PerUserKeyRoll engine.
    27  func NewPerUserKeyRoll(g *libkb.GlobalContext, args *PerUserKeyRollArgs) *PerUserKeyRoll {
    28  	return &PerUserKeyRoll{
    29  		args:         args,
    30  		Contextified: libkb.NewContextified(g),
    31  	}
    32  }
    33  
    34  // Name is the unique engine name.
    35  func (e *PerUserKeyRoll) Name() string {
    36  	return "PerUserKeyRoll"
    37  }
    38  
    39  // GetPrereqs returns the engine prereqs.
    40  func (e *PerUserKeyRoll) Prereqs() Prereqs {
    41  	return Prereqs{
    42  		Device: true,
    43  	}
    44  }
    45  
    46  // RequiredUIs returns the required UIs.
    47  func (e *PerUserKeyRoll) RequiredUIs() []libkb.UIKind {
    48  	return []libkb.UIKind{}
    49  }
    50  
    51  // SubConsumers returns the other UI consumers for this engine.
    52  func (e *PerUserKeyRoll) SubConsumers() []libkb.UIConsumer {
    53  	return []libkb.UIConsumer{}
    54  }
    55  
    56  // Run starts the engine.
    57  func (e *PerUserKeyRoll) Run(mctx libkb.MetaContext) (err error) {
    58  	defer mctx.Trace("PerUserKeyRoll", &err)()
    59  	return retryOnEphemeralRace(mctx, e.inner)
    60  }
    61  
    62  func (e *PerUserKeyRoll) inner(mctx libkb.MetaContext) error {
    63  	var err error
    64  
    65  	uid := mctx.G().GetMyUID()
    66  	if uid.IsNil() {
    67  		return libkb.NoUIDError{}
    68  	}
    69  
    70  	me := e.args.Me
    71  	if me == nil {
    72  		mctx.Debug("PerUserKeyRoll load self")
    73  
    74  		loadArg := libkb.NewLoadUserArgWithMetaContext(mctx).
    75  			WithUID(uid).
    76  			WithSelf(true).
    77  			WithPublicKeyOptional()
    78  		me, err = libkb.LoadUser(loadArg)
    79  		if err != nil {
    80  			return err
    81  		}
    82  	}
    83  	meUPAK := me.ExportToUserPlusAllKeys()
    84  
    85  	sigKey, err := mctx.ActiveDevice().SigningKey()
    86  	if err != nil {
    87  		return fmt.Errorf("signing key not found: (%v)", err)
    88  	}
    89  	encKey, err := mctx.ActiveDevice().EncryptionKey()
    90  	if err != nil {
    91  		return fmt.Errorf("encryption key not found: (%v)", err)
    92  	}
    93  
    94  	pukring, err := mctx.G().GetPerUserKeyring(mctx.Ctx())
    95  	if err != nil {
    96  		return err
    97  	}
    98  	err = pukring.Sync(mctx)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	// Generation of the new key
   104  	gen := pukring.CurrentGeneration() + keybase1.PerUserKeyGeneration(1)
   105  	mctx.Debug("PerUserKeyRoll creating gen: %v", gen)
   106  
   107  	pukSeed, err := libkb.GeneratePerUserKeySeed()
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	var pukPrev *libkb.PerUserKeyPrev
   113  	if gen > 1 {
   114  		pukPrevInner, err := pukring.PreparePrev(mctx, pukSeed, gen)
   115  		if err != nil {
   116  			return err
   117  		}
   118  		pukPrev = &pukPrevInner
   119  	}
   120  
   121  	pukReceivers, err := e.getPukReceivers(mctx, &meUPAK)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	if len(pukReceivers) == 0 {
   126  		return fmt.Errorf("no receivers")
   127  	}
   128  
   129  	// Create boxes of the new per-user-key
   130  	pukBoxes, err := pukring.PrepareBoxesForDevices(mctx,
   131  		pukSeed, gen, pukReceivers, encKey)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	mctx.Debug("PerUserKeyRoll make sigs")
   137  	sig, err := libkb.PerUserKeyProofReverseSigned(mctx, me, pukSeed, gen, sigKey)
   138  	if err != nil {
   139  		return err
   140  	}
   141  	// Seqno when the per-user-key will be signed in.
   142  	pukSeqno := sig.Seqno
   143  
   144  	payload := make(libkb.JSONPayload)
   145  	payload["sigs"] = []libkb.JSONPayload{sig.Payload}
   146  
   147  	mctx.Debug("PerUserKeyRoll pukBoxes:%v pukPrev:%v for generation %v",
   148  		len(pukBoxes), pukPrev != nil, gen)
   149  	libkb.AddPerUserKeyServerArg(payload, gen, pukBoxes, pukPrev)
   150  
   151  	ekLib := mctx.G().GetEKLib()
   152  	var myUserEKBox *keybase1.UserEkBoxed
   153  	var newUserEKMetadata *keybase1.UserEkMetadata
   154  	if ekLib != nil {
   155  		merkleRoot, err := mctx.G().GetMerkleClient().FetchRootFromServer(mctx, libkb.EphemeralKeyMerkleFreshness)
   156  		if err != nil {
   157  			return err
   158  		}
   159  		sig, boxes, newMetadata, myBox, err := ekLib.PrepareNewUserEK(mctx, *merkleRoot, pukSeed)
   160  		if err != nil {
   161  			return err
   162  		}
   163  		// If there are no active deviceEKs, we can't publish this key. This
   164  		// should mostly only come up in tests.
   165  		if len(boxes) > 0 {
   166  			myUserEKBox = myBox
   167  			newUserEKMetadata = &newMetadata
   168  			userEKSection := make(libkb.JSONPayload)
   169  			userEKSection["sig"] = sig
   170  			userEKSection["boxes"] = boxes
   171  			payload["user_ek"] = userEKSection
   172  		} else {
   173  			mctx.Debug("skipping userEK publishing, there are no valid deviceEKs")
   174  		}
   175  	}
   176  
   177  	mctx.Debug("PerUserKeyRoll post")
   178  	_, err = mctx.G().API.PostJSON(mctx, libkb.APIArg{
   179  		Endpoint:    "key/multi",
   180  		SessionType: libkb.APISessionTypeREQUIRED,
   181  		JSONPayload: payload,
   182  	})
   183  	if err != nil {
   184  		return err
   185  	}
   186  	if err = libkb.MerkleCheckPostedUserSig(mctx, uid, pukSeqno, sig.LinkID); err != nil {
   187  		return err
   188  	}
   189  	e.DidNewKey = true
   190  
   191  	// Add the per-user-key locally
   192  	err = pukring.AddKey(mctx, gen, pukSeqno, pukSeed)
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	// Add the new userEK box to local storage, if it was created above.
   198  	if myUserEKBox != nil {
   199  		err = mctx.G().GetUserEKBoxStorage().Put(mctx, newUserEKMetadata.Generation, *myUserEKBox)
   200  		if err != nil {
   201  			mctx.Error("error while saving userEK box: %s", err)
   202  		}
   203  	}
   204  
   205  	mctx.G().UserChanged(mctx.Ctx(), uid)
   206  	return nil
   207  }
   208  
   209  // Get the receivers of the new per-user-key boxes.
   210  // Includes all the user's device subkeys.
   211  func (e *PerUserKeyRoll) getPukReceivers(mctx libkb.MetaContext, meUPAK *keybase1.UserPlusAllKeys) (res []libkb.NaclDHKeyPair, err error) {
   212  	for _, dk := range meUPAK.Base.DeviceKeys {
   213  		if !dk.IsSibkey && !dk.IsRevoked {
   214  			receiver, err := libkb.ImportNaclDHKeyPairFromHex(dk.KID.String())
   215  			if err != nil {
   216  				return res, err
   217  			}
   218  			res = append(res, receiver)
   219  		}
   220  	}
   221  	return res, nil
   222  }