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 }