github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/ephemeral/device_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 DeviceEKSeed keybase1.Bytes32
    13  
    14  func newDeviceEphemeralSeed() (seed DeviceEKSeed, err error) {
    15  	randomSeed, err := makeNewRandomSeed()
    16  	if err != nil {
    17  		return seed, err
    18  	}
    19  	return DeviceEKSeed(randomSeed), nil
    20  }
    21  
    22  func (s *DeviceEKSeed) DeriveDHKey() *libkb.NaclDHKeyPair {
    23  	return deriveDHKey(keybase1.Bytes32(*s), libkb.DeriveReasonDeviceEKEncryption)
    24  }
    25  
    26  func postNewDeviceEK(mctx libkb.MetaContext, sig string) (err error) {
    27  	defer mctx.Trace("postNewDeviceEK", &err)()
    28  
    29  	apiArg := libkb.APIArg{
    30  		Endpoint:    "user/device_ek",
    31  		SessionType: libkb.APISessionTypeREQUIRED,
    32  		Args: libkb.HTTPArgs{
    33  			"sig":       libkb.S{Val: sig},
    34  			"device_id": libkb.S{Val: string(mctx.ActiveDevice().DeviceID())},
    35  		},
    36  	}
    37  	_, err = mctx.G().GetAPI().Post(mctx, apiArg)
    38  	return err
    39  }
    40  
    41  func serverMaxDeviceEK(mctx libkb.MetaContext, merkleRoot libkb.MerkleRoot) (maxGeneration keybase1.EkGeneration, err error) {
    42  	defer mctx.Trace("serverMaxDeviceEK", &err)()
    43  
    44  	deviceEKs, err := allDeviceEKMetadataMaybeStale(mctx, merkleRoot)
    45  	if err != nil {
    46  		return maxGeneration, err
    47  	}
    48  	deviceID := mctx.ActiveDevice().DeviceID()
    49  	metadata, ok := deviceEKs[deviceID]
    50  	if ok {
    51  		return metadata.Generation, nil
    52  	}
    53  	// We may not have an EK yet, let's try with this and fail if the server
    54  	// rejects.
    55  	mctx.Debug("No deviceEK found on the server")
    56  	return 0, nil
    57  }
    58  
    59  func publishNewDeviceEK(mctx libkb.MetaContext, merkleRoot libkb.MerkleRoot) (metadata keybase1.DeviceEkMetadata, err error) {
    60  	defer mctx.Trace("publishNewDeviceEK", &err)()
    61  
    62  	seed, err := newDeviceEphemeralSeed()
    63  	if err != nil {
    64  		return metadata, err
    65  	}
    66  
    67  	storage := mctx.G().GetDeviceEKStorage()
    68  	generation, err := storage.MaxGeneration(mctx, true)
    69  	if err != nil || generation < 0 {
    70  		// Let's try to get the max from the server
    71  		generation, err = serverMaxDeviceEK(mctx, merkleRoot)
    72  		if err != nil {
    73  			return metadata, err
    74  		}
    75  	}
    76  	// This is our first generation
    77  	if generation < 0 {
    78  		generation = 0
    79  	}
    80  	generation++
    81  
    82  	metadata, err = signAndPostDeviceEK(mctx, generation, seed, merkleRoot)
    83  	if err != nil {
    84  		mctx.Debug("Error posting deviceEK, retrying with server maxGeneration")
    85  		// Let's retry posting with the server given max
    86  		generation, err = serverMaxDeviceEK(mctx, merkleRoot)
    87  		if err != nil {
    88  			return metadata, err
    89  		}
    90  		generation++
    91  		metadata, err = signAndPostDeviceEK(mctx, generation, seed, merkleRoot)
    92  		if err != nil {
    93  			return metadata, err
    94  		}
    95  	}
    96  
    97  	return metadata, err
    98  }
    99  
   100  func signAndPostDeviceEK(mctx libkb.MetaContext, generation keybase1.EkGeneration,
   101  	seed DeviceEKSeed, merkleRoot libkb.MerkleRoot) (metadata keybase1.DeviceEkMetadata, err error) {
   102  	defer mctx.Trace("signAndPostDeviceEK", &err)()
   103  
   104  	storage := mctx.G().GetDeviceEKStorage()
   105  
   106  	// Sign the statement blob with the device's long term signing key.
   107  	signingKey, err := mctx.ActiveDevice().SigningKey()
   108  	if err != nil {
   109  		return metadata, err
   110  	}
   111  
   112  	dhKeypair := seed.DeriveDHKey()
   113  	statement, signedStatement, err := signDeviceEKStatement(generation, dhKeypair, signingKey, merkleRoot)
   114  
   115  	metadata = statement.CurrentDeviceEkMetadata
   116  	// Ensure we successfully write the secret to disk before posting to the
   117  	// server since the secret never leaves the device.
   118  	if err = storage.Put(mctx, generation, keybase1.DeviceEk{
   119  		Seed:     keybase1.Bytes32(seed),
   120  		Metadata: metadata,
   121  	}); err != nil {
   122  		return metadata, err
   123  	}
   124  
   125  	err = postNewDeviceEK(mctx, signedStatement)
   126  	if err != nil {
   127  		storage.ClearCache()
   128  		serr := NewDeviceEKStorage(mctx).Delete(mctx, generation, "unable to post deviceEK: %v", err)
   129  		if serr != nil {
   130  			mctx.Debug("DeviceEK deletion failed %v", err)
   131  		}
   132  	}
   133  
   134  	return metadata, err
   135  }
   136  
   137  func signDeviceEKStatement(generation keybase1.EkGeneration, dhKeypair *libkb.NaclDHKeyPair, signingKey libkb.GenericKey, merkleRoot libkb.MerkleRoot) (statement keybase1.DeviceEkStatement, signedStatement string, err error) {
   138  	metadata := keybase1.DeviceEkMetadata{
   139  		Kid:        dhKeypair.GetKID(),
   140  		Generation: generation,
   141  		HashMeta:   merkleRoot.HashMeta(),
   142  		// The ctime is derivable from the hash meta, by fetching the hashed
   143  		// root from the server, but including it saves readers a potential
   144  		// extra round trip.
   145  		Ctime: keybase1.TimeFromSeconds(merkleRoot.Ctime()),
   146  	}
   147  	statement = keybase1.DeviceEkStatement{
   148  		CurrentDeviceEkMetadata: metadata,
   149  	}
   150  
   151  	statementJSON, err := json.Marshal(statement)
   152  	if err != nil {
   153  		return statement, signedStatement, err
   154  	}
   155  
   156  	signedStatement, _, err = signingKey.SignToString(statementJSON)
   157  	return statement, signedStatement, err
   158  }
   159  
   160  type deviceEKStatementResponse struct {
   161  	Sigs []string `json:"sigs"`
   162  }
   163  
   164  func allDeviceEKMetadataMaybeStale(mctx libkb.MetaContext, merkleRoot libkb.MerkleRoot) (metadata map[keybase1.DeviceID]keybase1.DeviceEkMetadata, err error) {
   165  	defer mctx.Trace("allDeviceEKMetadataMaybeStale", &err)()
   166  
   167  	apiArg := libkb.APIArg{
   168  		Endpoint:    "user/device_eks",
   169  		SessionType: libkb.APISessionTypeREQUIRED,
   170  		Args:        libkb.HTTPArgs{},
   171  	}
   172  	res, err := mctx.G().GetAPI().Get(mctx, apiArg)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	parsedResponse := deviceEKStatementResponse{}
   178  	err = res.Body.UnmarshalAgain(&parsedResponse)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	// Make a map of the user's own active devices, by KID. We'll use this to
   184  	// match deviceEK sigs with the device that issued them below. (Checking
   185  	// the signing key is intentionally the only way to do this, so that we're
   186  	// forced to check authenticity.)
   187  	getDeviceKIDs := func(force bool) (map[keybase1.KID]keybase1.PublicKey, error) {
   188  		arg := libkb.NewLoadUserArgWithMetaContext(
   189  			mctx).WithUID(mctx.ActiveDevice().UID())
   190  		if force {
   191  			arg = arg.WithForceReload()
   192  		}
   193  		self, _, err := mctx.G().GetUPAKLoader().Load(arg)
   194  		if err != nil {
   195  			return nil, err
   196  		}
   197  		kidToDevice := map[keybase1.KID]keybase1.PublicKey{}
   198  		for _, device := range self.Base.DeviceKeys {
   199  			if device.IsRevoked {
   200  				continue
   201  			}
   202  			kidToDevice[device.KID] = device
   203  		}
   204  		return kidToDevice, nil
   205  	}
   206  
   207  	kidToDevice, err := getDeviceKIDs(false)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	if len(kidToDevice) != len(parsedResponse.Sigs) {
   213  		mctx.Debug("mismatch of active devices in UPAK to device EK sigs (%d (upak) != %d (ek sigs), attempting force reload.", len(kidToDevice), len(parsedResponse.Sigs))
   214  		// force a reload in case we are missing a device
   215  		kidToDevice, err = getDeviceKIDs(true)
   216  		if err != nil {
   217  			return nil, err
   218  		}
   219  		mctx.Debug("%d active devices found after force reload vs %d sigs.", len(kidToDevice), len(parsedResponse.Sigs))
   220  	}
   221  
   222  	// The client now needs to verify two things about these blobs its
   223  	// received: 1) Each is validly signed. 2) The signing key belongs to one
   224  	// of the current user's devices.
   225  	metadata = map[keybase1.DeviceID]keybase1.DeviceEkMetadata{}
   226  	for _, sig := range parsedResponse.Sigs {
   227  		// Verify the sig.
   228  		signerKey, payload, _, err := kbcrypto.NaclVerifyAndExtract(sig)
   229  		if err != nil {
   230  			return nil, err
   231  		}
   232  
   233  		// Find the device that matches the signing key. This checks
   234  		// authenticity.
   235  		matchingDevice, ok := kidToDevice[signerKey.GetKID()]
   236  		if !ok {
   237  			return nil, fmt.Errorf("deviceEK returned for unknown device KID %s", signerKey.GetKID())
   238  		}
   239  
   240  		// Decode the signed JSON.
   241  		var verifiedStatement keybase1.DeviceEkStatement
   242  		err = json.Unmarshal(payload, &verifiedStatement)
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  
   247  		metadata[matchingDevice.DeviceID] = verifiedStatement.CurrentDeviceEkMetadata
   248  	}
   249  
   250  	return metadata, nil
   251  }
   252  
   253  // allActiveDeviceEKMetadata fetches the latest deviceEK for each of your
   254  // devices, filtering out the ones that are stale.
   255  func allActiveDeviceEKMetadata(mctx libkb.MetaContext, merkleRoot libkb.MerkleRoot) (metadata map[keybase1.DeviceID]keybase1.DeviceEkMetadata, err error) {
   256  	defer mctx.Trace("allActiveDeviceEKMetadata", &err)()
   257  
   258  	maybeStale, err := allDeviceEKMetadataMaybeStale(mctx, merkleRoot)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  
   263  	active := map[keybase1.DeviceID]keybase1.DeviceEkMetadata{}
   264  	for deviceID, metadata := range maybeStale {
   265  		// Check whether the key is stale. This isn't considered an error,
   266  		// since the server doesn't do this check for us. We log these cases
   267  		// and skip them.
   268  		if ctimeIsStale(metadata.Ctime.Time(), merkleRoot) {
   269  			mctx.Debug("skipping stale deviceEK %s for device KID %s", metadata.Kid, deviceID)
   270  			continue
   271  		}
   272  		active[deviceID] = metadata
   273  	}
   274  
   275  	return active, nil
   276  }