github.com/keybase/client/go@v0.0.0-20240520164431-4f512a4c85a3/ephemeral/user_ek.go (about)

     1  package ephemeral
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  
     7  	"github.com/keybase/client/go/kbcrypto"
     8  	"github.com/keybase/client/go/libkb"
     9  	"github.com/keybase/client/go/protocol/keybase1"
    10  )
    11  
    12  type UserEKSeed keybase1.Bytes32
    13  
    14  func newUserEphemeralSeed() (seed UserEKSeed, err error) {
    15  	randomSeed, err := makeNewRandomSeed()
    16  	if err != nil {
    17  		return seed, err
    18  	}
    19  	return UserEKSeed(randomSeed), nil
    20  }
    21  
    22  func newUserEKSeedFromBytes(b []byte) (s UserEKSeed, err error) {
    23  	seed, err := newEKSeedFromBytes(b)
    24  	if err != nil {
    25  		return s, err
    26  	}
    27  	return UserEKSeed(seed), nil
    28  }
    29  
    30  func (s *UserEKSeed) DeriveDHKey() *libkb.NaclDHKeyPair {
    31  	return deriveDHKey(keybase1.Bytes32(*s), libkb.DeriveReasonUserEKEncryption)
    32  }
    33  
    34  // Upload a new userEK directly, when we're not adding it to a PUK or device
    35  // transaction.
    36  func postNewUserEK(mctx libkb.MetaContext, sig string, boxes []keybase1.UserEkBoxMetadata) (err error) {
    37  	defer mctx.Trace("postNewUserEK", &err)()
    38  
    39  	boxesJSON, err := json.Marshal(boxes)
    40  	if err != nil {
    41  		return err
    42  	}
    43  	apiArg := libkb.APIArg{
    44  		Endpoint:    "user/user_ek",
    45  		SessionType: libkb.APISessionTypeREQUIRED,
    46  		Args: libkb.HTTPArgs{
    47  			"sig":   libkb.S{Val: sig},
    48  			"boxes": libkb.S{Val: string(boxesJSON)},
    49  		},
    50  	}
    51  	_, err = mctx.G().GetAPI().Post(mctx, apiArg)
    52  	return err
    53  }
    54  
    55  // There are two cases where we need to generate a new userEK. One is where
    56  // we're rolling the userEK by itself, and we need to sign it with the current
    57  // PUK. The other is where we're rolling the PUK, and we need to sign a new
    58  // userEK with the new PUK to upload both together. This helper covers the
    59  // steps common to both cases.
    60  func prepareNewUserEK(mctx libkb.MetaContext, merkleRoot libkb.MerkleRoot,
    61  	pukSigning *libkb.NaclSigningKeyPair) (sig string, boxes []keybase1.UserEkBoxMetadata,
    62  	newMetadata keybase1.UserEkMetadata, myBox *keybase1.UserEkBoxed, err error) {
    63  	defer mctx.Trace("prepareNewUserEK", &err)()
    64  
    65  	seed, err := newUserEphemeralSeed()
    66  	if err != nil {
    67  		return "", nil, newMetadata, nil, err
    68  	}
    69  
    70  	prevStatement, latestGeneration, wrongKID, err := fetchUserEKStatement(mctx, mctx.G().Env.GetUID())
    71  	if !wrongKID && err != nil {
    72  		return "", nil, newMetadata, nil, err
    73  	}
    74  	var newGeneration keybase1.EkGeneration
    75  	if prevStatement == nil {
    76  		// Even if the userEK statement was signed by the wrong key (this can
    77  		// happen when legacy clients roll the PUK), fetchUserEKStatement will
    78  		// return the generation number from the last (unverifiable) statement.
    79  		// If there was never any statement, latestGeneration will be 0, so
    80  		// adding one is correct in all cases.
    81  		newGeneration = latestGeneration + 1
    82  	} else {
    83  		newGeneration = prevStatement.CurrentUserEkMetadata.Generation + 1
    84  	}
    85  
    86  	dhKeypair := seed.DeriveDHKey()
    87  
    88  	metadata := keybase1.UserEkMetadata{
    89  		Kid:        dhKeypair.GetKID(),
    90  		Generation: newGeneration,
    91  		HashMeta:   merkleRoot.HashMeta(),
    92  		// The ctime is derivable from the hash meta, by fetching the hashed
    93  		// root from the server, but including it saves readers a potential
    94  		// extra round trip.
    95  		Ctime: keybase1.TimeFromSeconds(merkleRoot.Ctime()),
    96  	}
    97  
    98  	statement := keybase1.UserEkStatement{
    99  		CurrentUserEkMetadata: metadata,
   100  	}
   101  	statementJSON, err := json.Marshal(statement)
   102  	if err != nil {
   103  		return "", nil, newMetadata, nil, err
   104  	}
   105  	sig, _, err = pukSigning.SignToString(statementJSON)
   106  	if err != nil {
   107  		return "", nil, newMetadata, nil, err
   108  	}
   109  
   110  	boxes, myUserEKBoxed, err := boxUserEKForDevices(mctx, merkleRoot, seed, metadata)
   111  	if err != nil {
   112  		return "", nil, newMetadata, nil, err
   113  	}
   114  
   115  	return sig, boxes, metadata, myUserEKBoxed, nil
   116  }
   117  
   118  // Create a new userEK and upload it. Add our box to the local box store.
   119  func publishNewUserEK(mctx libkb.MetaContext, merkleRoot libkb.MerkleRoot) (
   120  	metadata keybase1.UserEkMetadata, err error) {
   121  	defer mctx.Trace("publishNewUserEK", &err)()
   122  
   123  	// Sign the statement blob with the latest PUK.
   124  	pukKeyring, err := mctx.G().GetPerUserKeyring(mctx.Ctx())
   125  	if err != nil {
   126  		return metadata, err
   127  	}
   128  	if err := pukKeyring.Sync(mctx); err != nil {
   129  		return metadata, err
   130  	}
   131  	if !pukKeyring.HasAnyKeys() {
   132  		return metadata, fmt.Errorf("A PUK is needed to generate ephemeral keys. Aborting.")
   133  	}
   134  	pukSigning, err := pukKeyring.GetLatestSigningKey(mctx)
   135  	if err != nil {
   136  		return metadata, err
   137  	}
   138  
   139  	sig, boxes, newMetadata, myBox, err := prepareNewUserEK(mctx, merkleRoot, pukSigning)
   140  	if err != nil {
   141  		return metadata, err
   142  	}
   143  
   144  	if err = postNewUserEK(mctx, sig, boxes); err != nil {
   145  		return metadata, err
   146  	}
   147  
   148  	// Cache the new box after we see the post succeeded.
   149  	if myBox == nil {
   150  		mctx.Debug("No box made for own deviceEK")
   151  	} else {
   152  		storage := mctx.G().GetUserEKBoxStorage()
   153  		err = storage.Put(mctx, newMetadata.Generation, *myBox)
   154  	}
   155  	return newMetadata, err
   156  }
   157  
   158  func ForcePublishNewUserEKForTesting(mctx libkb.MetaContext, merkleRoot libkb.MerkleRoot) (metadata keybase1.UserEkMetadata, err error) {
   159  	defer mctx.Trace("ForcePublishNewUserEKForTesting", &err)()
   160  	return publishNewUserEK(mctx, merkleRoot)
   161  }
   162  
   163  func boxUserEKForDevices(mctx libkb.MetaContext, merkleRoot libkb.MerkleRoot,
   164  	seed UserEKSeed, userMetadata keybase1.UserEkMetadata) (boxes []keybase1.UserEkBoxMetadata,
   165  	myUserEKBoxed *keybase1.UserEkBoxed, err error) {
   166  	defer mctx.Trace("boxUserEKForDevices", &err)()
   167  
   168  	devicesMetadata, err := allActiveDeviceEKMetadata(mctx, merkleRoot)
   169  	if err != nil {
   170  		return nil, nil, err
   171  	}
   172  
   173  	myDeviceID := mctx.ActiveDevice().DeviceID()
   174  	for deviceID, deviceMetadata := range devicesMetadata {
   175  		recipientKey, err := libkb.ImportKeypairFromKID(deviceMetadata.Kid)
   176  		if err != nil {
   177  			return nil, nil, err
   178  		}
   179  		// Encrypting with a nil sender means we'll generate a random sender private key.
   180  		box, err := recipientKey.EncryptToString(seed[:], nil)
   181  		if err != nil {
   182  			return nil, nil, err
   183  		}
   184  		boxMetadata := keybase1.UserEkBoxMetadata{
   185  			RecipientDeviceID:   deviceID,
   186  			RecipientGeneration: deviceMetadata.Generation,
   187  			Box:                 box,
   188  		}
   189  		boxes = append(boxes, boxMetadata)
   190  
   191  		if deviceID == myDeviceID {
   192  			myUserEKBoxed = &keybase1.UserEkBoxed{
   193  				Box:                box,
   194  				DeviceEkGeneration: deviceMetadata.Generation,
   195  				Metadata:           userMetadata,
   196  			}
   197  		}
   198  	}
   199  	return boxes, myUserEKBoxed, nil
   200  }
   201  
   202  type userEKStatementResponse struct {
   203  	Sigs map[keybase1.UID]*string `json:"sigs"`
   204  }
   205  
   206  // Returns nil if the user has never published a userEK. If the user has
   207  // published a userEK, but has since rolled their PUK without publishing a new
   208  // one, this function will also return nil and log a warning. This is a
   209  // transitional thing, and eventually when all "reasonably up to date" clients
   210  // in the wild have EK support, we will make that case an error.
   211  func fetchUserEKStatements(mctx libkb.MetaContext, uids []keybase1.UID) (
   212  	statements map[keybase1.UID]*keybase1.UserEkStatement, err error) {
   213  	defer mctx.Trace(fmt.Sprintf("fetchUserEKStatements: numUids: %v", len(uids)), &err)()
   214  
   215  	apiArg := libkb.APIArg{
   216  		Endpoint:    "user/get_user_ek_batch",
   217  		SessionType: libkb.APISessionTypeREQUIRED,
   218  		Args: libkb.HTTPArgs{
   219  			"uids": libkb.S{Val: libkb.UidsToString(uids)},
   220  		},
   221  	}
   222  	res, err := mctx.G().GetAPI().Post(mctx, apiArg)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	userEKStatements := userEKStatementResponse{}
   228  	if err = res.Body.UnmarshalAgain(&userEKStatements); err != nil {
   229  		return nil, err
   230  	}
   231  
   232  	getArg := func(i int) *libkb.LoadUserArg {
   233  		if i >= len(uids) {
   234  			return nil
   235  		}
   236  		tmp := libkb.NewLoadUserArgWithMetaContext(mctx).WithUID(uids[i])
   237  		return &tmp
   238  	}
   239  
   240  	var upaks []*keybase1.UserPlusKeysV2AllIncarnations
   241  	statements = make(map[keybase1.UID]*keybase1.UserEkStatement)
   242  	processResult := func(i int, upak *keybase1.UserPlusKeysV2AllIncarnations) error {
   243  		mctx.Debug("processing member %d/%d %.2f%% complete", i, len(uids), (float64(i) / float64(len(uids)) * 100))
   244  		if upak == nil {
   245  			mctx.Debug("Unable to load user %v", uids[i])
   246  			return nil
   247  		}
   248  		upaks = append(upaks, upak)
   249  		return nil
   250  	}
   251  
   252  	if err = mctx.G().GetUPAKLoader().Batcher(mctx.Ctx(), getArg, processResult, 0); err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	for _, upak := range upaks {
   257  		uid := upak.GetUID()
   258  		sig, ok := userEKStatements.Sigs[uid]
   259  		if !ok || sig == nil {
   260  			mctx.Debug("missing memberEK statement for UID %v", uid)
   261  			continue
   262  		}
   263  
   264  		statement, _, wrongKID, err := verifySigWithLatestPUK(mctx, uid,
   265  			upak.Current.GetLatestPerUserKey(), *sig)
   266  		if wrongKID {
   267  			mctx.Debug("UID %v has a statement signed with the wrongKID, skipping", uid)
   268  			// Don't box for this member since they have no valid userEK
   269  			continue
   270  		} else if err != nil {
   271  			return nil, err
   272  		}
   273  		statements[uid] = statement
   274  	}
   275  
   276  	return statements, nil
   277  }
   278  
   279  // Returns nil if the user has never published a userEK. If the user has
   280  // published a userEK, but has since rolled their PUK without publishing a new
   281  // one, this function will return wrongKID. This allows clients to chose the
   282  // correct generation number but not include the statement when generating a
   283  // new userEK.
   284  func fetchUserEKStatement(mctx libkb.MetaContext, uid keybase1.UID) (
   285  	statement *keybase1.UserEkStatement, latestGeneration keybase1.EkGeneration, wrongKID bool, err error) {
   286  	defer mctx.Trace("fetchUserEKStatement", &err)()
   287  
   288  	apiArg := libkb.APIArg{
   289  		Endpoint:    "user/user_ek",
   290  		SessionType: libkb.APISessionTypeREQUIRED,
   291  		Args: libkb.HTTPArgs{
   292  			"uids": libkb.S{Val: libkb.UidsToString([]keybase1.UID{uid})},
   293  		},
   294  	}
   295  	res, err := mctx.G().GetAPI().Get(mctx, apiArg)
   296  	if err != nil {
   297  		return nil, latestGeneration, false, err
   298  	}
   299  
   300  	parsedResponse := userEKStatementResponse{}
   301  	err = res.Body.UnmarshalAgain(&parsedResponse)
   302  	if err != nil {
   303  		return nil, latestGeneration, false, err
   304  	}
   305  	// User has no statements
   306  	if len(parsedResponse.Sigs) == 0 {
   307  		return nil, latestGeneration, false, nil
   308  	}
   309  	if len(parsedResponse.Sigs) != 1 {
   310  		return nil, latestGeneration, false, fmt.Errorf("Invalid server response, multiple userEK statements returned")
   311  	}
   312  	sig, ok := parsedResponse.Sigs[uid]
   313  	if !ok {
   314  		return nil, latestGeneration, false, fmt.Errorf("Invalid server response, wrong uid returned")
   315  	}
   316  
   317  	upak, _, err := mctx.G().GetUPAKLoader().LoadV2(
   318  		libkb.NewLoadUserArgWithMetaContext(mctx).WithUID(uid))
   319  	if err != nil {
   320  		return nil, latestGeneration, false, err
   321  	}
   322  	latestPUK := upak.Current.GetLatestPerUserKey()
   323  	statement, latestGeneration, wrongKID, err = verifySigWithLatestPUK(mctx, uid, latestPUK, *sig)
   324  	// Check the wrongKID condition before checking the error, since an error
   325  	// is still returned in this case. TODO: Turn this warning into an error
   326  	// after EK support is sufficiently widespread.
   327  	if wrongKID {
   328  		mctx.Debug("It looks like you revoked a device without generating new ephemeral keys. Are you running an old version?")
   329  		return nil, latestGeneration, true, nil
   330  	} else if err != nil {
   331  		return nil, latestGeneration, false, err
   332  	}
   333  
   334  	return statement, latestGeneration, false, nil
   335  }
   336  
   337  func extractUserEKStatementFromSig(sig string) (signerKey *kbcrypto.NaclSigningKeyPublic, statement *keybase1.UserEkStatement, err error) {
   338  	signerKey, payload, _, err := kbcrypto.NaclVerifyAndExtract(sig)
   339  	if err != nil {
   340  		return signerKey, nil, err
   341  	}
   342  
   343  	parsedStatement := keybase1.UserEkStatement{}
   344  	if err = json.Unmarshal(payload, &parsedStatement); err != nil {
   345  		return signerKey, nil, err
   346  	}
   347  	return signerKey, &parsedStatement, nil
   348  }
   349  
   350  // Verify that the blob is validly signed, and that the signing key is the
   351  // given user's latest PUK, then parse its contents. If the blob is signed by
   352  // the wrong KID, that's still an error, but we'll also return this special
   353  // `wrongKID` flag. As a transitional measure while we wait for all clients in
   354  // the wild to have EK support, callers will treat that case as "there is no
   355  // key" and convert the error to a warning. We set `latestGeneration` so that
   356  // callers can use this value to generate a new key even if `wrongKID` is set.
   357  func verifySigWithLatestPUK(mctx libkb.MetaContext, uid keybase1.UID,
   358  	latestPUK *keybase1.PerUserKey, sig string) (
   359  	statement *keybase1.UserEkStatement, latestGeneration keybase1.EkGeneration, wrongKID bool, err error) {
   360  	defer mctx.Trace("verifySigWithLatestPUK", &err)()
   361  
   362  	// Parse the statement before we verify the signing key. Even if the
   363  	// signing key is bad (likely because of a legacy PUK roll that didn't
   364  	// include a userEK statement), we'll still return the generation number.
   365  	signerKey, parsedStatement, err := extractUserEKStatementFromSig(sig)
   366  	if err != nil {
   367  		return nil, latestGeneration, false, err
   368  	}
   369  	latestGeneration = parsedStatement.CurrentUserEkMetadata.Generation
   370  
   371  	// Verify the signing key corresponds to the latest PUK. We use the user's
   372  	// UPAK from cache, but if the KID doesn't match, we try a forced reload to
   373  	// see if the cache might've been stale. Only if the KID still doesn't
   374  	// match after the reload do we complain.
   375  	if latestPUK == nil || !latestPUK.SigKID.Equal(signerKey.GetKID()) {
   376  		// The latest PUK might be stale. Force a reload, then check this over again.
   377  		upak, _, err := mctx.G().GetUPAKLoader().LoadV2(
   378  			libkb.NewLoadUserArgWithMetaContext(mctx).WithUID(uid).WithForceReload())
   379  		if err != nil {
   380  			return nil, latestGeneration, false, err
   381  		}
   382  		latestPUK = upak.Current.GetLatestPerUserKey()
   383  		if latestPUK == nil || !latestPUK.SigKID.Equal(signerKey.GetKID()) {
   384  			// The latest PUK still doesn't match the signing key after a
   385  			// forced reload. Bail out, and set the `wrongKID` flag.
   386  			latestPUKSigningKIDString := "<nil>"
   387  			if latestPUK != nil {
   388  				latestPUKSigningKIDString = fmt.Sprint(latestPUK.SigKID)
   389  			}
   390  			err = fmt.Errorf("userEK returned for PUK signing KID %s, but latest is %s",
   391  				signerKey.GetKID(), latestPUKSigningKIDString)
   392  			return nil, latestGeneration, true, err
   393  		}
   394  	}
   395  
   396  	return parsedStatement, latestGeneration, false, nil
   397  }
   398  
   399  func filterStaleUserEKStatements(mctx libkb.MetaContext, statementMap map[keybase1.UID]*keybase1.UserEkStatement,
   400  	merkleRoot libkb.MerkleRoot) (activeMap map[keybase1.UID]keybase1.UserEkStatement, err error) {
   401  	defer mctx.Trace(fmt.Sprintf("filterStaleUserEKStatements: numStatements: %v", len(statementMap)), &err)()
   402  
   403  	activeMap = make(map[keybase1.UID]keybase1.UserEkStatement)
   404  	for uid, statement := range statementMap {
   405  		if statement == nil {
   406  			mctx.Debug("found stale userStatement for uid: %s", uid)
   407  			continue
   408  		}
   409  		metadata := statement.CurrentUserEkMetadata
   410  		if ctimeIsStale(metadata.Ctime.Time(), merkleRoot) {
   411  			mctx.Debug("found stale userStatement for KID: %s", metadata.Kid)
   412  			continue
   413  		}
   414  		activeMap[uid] = *statement
   415  	}
   416  
   417  	return activeMap, nil
   418  }
   419  
   420  func activeUserEKMetadata(mctx libkb.MetaContext, statementMap map[keybase1.UID]*keybase1.UserEkStatement,
   421  	merkleRoot libkb.MerkleRoot) (activeMetadata map[keybase1.UID]keybase1.UserEkMetadata, err error) {
   422  	activeMap, err := filterStaleUserEKStatements(mctx, statementMap, merkleRoot)
   423  	if err != nil {
   424  		return nil, err
   425  	}
   426  	activeMetadata = make(map[keybase1.UID]keybase1.UserEkMetadata)
   427  	for uid, statement := range activeMap {
   428  		activeMetadata[uid] = statement.CurrentUserEkMetadata
   429  	}
   430  	return activeMetadata, nil
   431  }