github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/boot/model_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package boot_test 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/asserts/assertstest" 31 "github.com/snapcore/snapd/boot" 32 "github.com/snapcore/snapd/boot/boottest" 33 "github.com/snapcore/snapd/bootloader" 34 "github.com/snapcore/snapd/bootloader/bootloadertest" 35 "github.com/snapcore/snapd/dirs" 36 "github.com/snapcore/snapd/secboot" 37 "github.com/snapcore/snapd/seed" 38 "github.com/snapcore/snapd/snap" 39 "github.com/snapcore/snapd/testutil" 40 "github.com/snapcore/snapd/timings" 41 ) 42 43 type modelSuite struct { 44 baseBootenvSuite 45 46 oldUc20dev boot.Device 47 newUc20dev boot.Device 48 49 runKernelBf bootloader.BootFile 50 recoveryKernelBf bootloader.BootFile 51 52 keyID string 53 54 readSystemEssentialCalls int 55 } 56 57 var _ = Suite(&modelSuite{}) 58 59 var ( 60 brandPrivKey, _ = assertstest.GenerateKey(752) 61 ) 62 63 func makeEncodableModel(signingAccounts *assertstest.SigningAccounts, overrides map[string]interface{}) *asserts.Model { 64 headers := map[string]interface{}{ 65 "model": "my-model-uc20", 66 "display-name": "My Model", 67 "architecture": "amd64", 68 "base": "core20", 69 "grade": "dangerous", 70 "snaps": []interface{}{ 71 map[string]interface{}{ 72 "name": "pc-kernel", 73 "id": "pckernelidididididididididididid", 74 "type": "kernel", 75 }, 76 map[string]interface{}{ 77 "name": "pc", 78 "id": "pcididididididididididididididid", 79 "type": "gadget", 80 }, 81 }, 82 } 83 for k, v := range overrides { 84 headers[k] = v 85 } 86 return signingAccounts.Model("canonical", headers["model"].(string), headers) 87 } 88 89 func (s *modelSuite) SetUpTest(c *C) { 90 s.baseBootenvSuite.SetUpTest(c) 91 92 store := assertstest.NewStoreStack("canonical", nil) 93 brands := assertstest.NewSigningAccounts(store) 94 brands.Register("my-brand", brandPrivKey, nil) 95 s.keyID = brands.Signing("canonical").KeyID 96 97 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { return nil }) 98 s.AddCleanup(restore) 99 s.oldUc20dev = boottest.MockUC20Device("", makeEncodableModel(brands, nil)) 100 s.newUc20dev = boottest.MockUC20Device("", makeEncodableModel(brands, map[string]interface{}{ 101 "model": "my-new-model-uc20", 102 "grade": "secured", 103 })) 104 105 model := s.oldUc20dev.Model() 106 107 modeenv := &boot.Modeenv{ 108 Mode: "run", 109 // system 1234 corresponds to the new model 110 CurrentRecoverySystems: []string{"20200825", "1234"}, 111 GoodRecoverySystems: []string{"20200825", "1234"}, 112 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 113 "asset": []string{"asset-hash-1"}, 114 }, 115 CurrentTrustedBootAssets: boot.BootAssetsMap{ 116 "asset": []string{"asset-hash-1"}, 117 }, 118 CurrentKernels: []string{"pc-kernel_500.snap"}, 119 CurrentKernelCommandLines: boot.BootCommandLines{ 120 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 121 }, 122 123 Model: model.Model(), 124 BrandID: model.BrandID(), 125 Grade: string(model.Grade()), 126 ModelSignKeyID: model.SignKeyID(), 127 } 128 c.Assert(modeenv.WriteTo(""), IsNil) 129 130 mockAssetsCache(c, s.rootdir, "trusted", []string{ 131 "asset-asset-hash-1", 132 }) 133 134 mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets() 135 mtbl.TrustedAssetsList = []string{"asset-1"} 136 mtbl.StaticCommandLine = "static cmdline" 137 mtbl.BootChainList = []bootloader.BootFile{ 138 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 139 s.runKernelBf, 140 } 141 mtbl.RecoveryBootChainList = []bootloader.BootFile{ 142 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 143 s.recoveryKernelBf, 144 } 145 bootloader.Force(mtbl) 146 147 s.AddCleanup(func() { bootloader.Force(nil) }) 148 149 // run kernel 150 s.runKernelBf = bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_500.snap", 151 "kernel.efi", bootloader.RoleRunMode) 152 // seed (recovery) kernel 153 s.recoveryKernelBf = bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", 154 "kernel.efi", bootloader.RoleRecovery) 155 156 c.Assert(os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device"), 0755), IsNil) 157 158 s.readSystemEssentialCalls = 0 159 restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 160 s.readSystemEssentialCalls++ 161 kernelRev := 1 162 systemModel := s.oldUc20dev.Model() 163 if label == "1234" { 164 // recovery system for new model 165 kernelRev = 999 166 systemModel = s.newUc20dev.Model() 167 } 168 return systemModel, []*seed.Snap{mockKernelSeedSnap(c, snap.R(kernelRev)), mockGadgetSeedSnap(c, nil)}, nil 169 }) 170 s.AddCleanup(restore) 171 } 172 173 func (s *modelSuite) TestWriteModelToUbuntuBoot(c *C) { 174 err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model()) 175 c.Assert(err, IsNil) 176 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 177 "model: my-model-uc20\n") 178 179 // overwrite the file 180 err = boot.WriteModelToUbuntuBoot(s.newUc20dev.Model()) 181 c.Assert(err, IsNil) 182 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 183 "model: my-new-model-uc20\n") 184 185 err = os.RemoveAll(filepath.Join(boot.InitramfsUbuntuBootDir)) 186 c.Assert(err, IsNil) 187 // fails when trying to write 188 err = boot.WriteModelToUbuntuBoot(s.newUc20dev.Model()) 189 c.Assert(err, ErrorMatches, `open .*/run/mnt/ubuntu-boot/device/model\..*: no such file or directory`) 190 } 191 192 func (s *modelSuite) TestDeviceChangeHappy(c *C) { 193 // system is encrypted 194 s.stampSealedKeys(c, s.rootdir) 195 196 // set up the old model file 197 err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model()) 198 c.Assert(err, IsNil) 199 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 200 "model: my-model-uc20\n") 201 202 resealKeysCalls := 0 203 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 204 resealKeysCalls++ 205 m, err := boot.ReadModeenv("") 206 c.Assert(err, IsNil) 207 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 208 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 209 switch resealKeysCalls { 210 case 1: 211 // run key 212 c.Assert(params.KeyFiles, DeepEquals, []string{ 213 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 214 }) 215 case 2: // recovery keys 216 c.Assert(params.KeyFiles, DeepEquals, []string{ 217 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 218 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 219 }) 220 case 3: 221 // run key 222 c.Assert(params.KeyFiles, DeepEquals, []string{ 223 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 224 }) 225 case 4: // recovery keys 226 c.Assert(params.KeyFiles, DeepEquals, []string{ 227 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 228 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 229 }) 230 default: 231 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 232 } 233 234 switch resealKeysCalls { 235 case 1, 2: 236 // keys are first resealed for both models 237 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 238 c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 239 // boot/device/model is still the old file 240 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 241 "model: my-model-uc20\n") 242 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 243 "grade: dangerous\n") 244 case 3, 4: 245 // and finally just for the new model 246 c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 247 c.Assert(tryForSealing, Equals, "/,,") 248 // boot/device/model is the new model by this time 249 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 250 "model: my-new-model-uc20\n") 251 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 252 "grade: secured\n") 253 } 254 return nil 255 }) 256 defer restore() 257 258 err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev) 259 c.Assert(err, IsNil) 260 c.Assert(resealKeysCalls, Equals, 4) 261 262 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 263 "model: my-new-model-uc20\n") 264 265 m, err := boot.ReadModeenv("") 266 c.Assert(err, IsNil) 267 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 268 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 269 c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 270 // try model has been cleared 271 c.Assert(tryForSealing, Equals, "/,,") 272 } 273 274 func (s *modelSuite) TestDeviceChangeUnhappyFirstReseal(c *C) { 275 // system is encrypted 276 s.stampSealedKeys(c, s.rootdir) 277 278 // set up the old model file 279 err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model()) 280 c.Assert(err, IsNil) 281 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 282 "model: my-model-uc20\n") 283 284 resealKeysCalls := 0 285 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 286 resealKeysCalls++ 287 m, err := boot.ReadModeenv("") 288 c.Assert(err, IsNil) 289 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 290 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 291 switch resealKeysCalls { 292 case 1: 293 // run key 294 c.Assert(params.KeyFiles, DeepEquals, []string{ 295 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 296 }) 297 default: 298 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 299 } 300 301 switch resealKeysCalls { 302 case 1: 303 // keys are first resealed for both models 304 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 305 c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 306 // boot/device/model is still the old file 307 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 308 "model: my-model-uc20\n") 309 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 310 "grade: dangerous\n") 311 } 312 return fmt.Errorf("fail on first try") 313 }) 314 defer restore() 315 316 err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev) 317 c.Assert(err, ErrorMatches, "cannot reseal the encryption key: fail on first try") 318 c.Assert(resealKeysCalls, Equals, 1) 319 // still the old model file 320 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 321 "model: my-model-uc20\n") 322 323 m, err := boot.ReadModeenv("") 324 c.Assert(err, IsNil) 325 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 326 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 327 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 328 // try model has been cleared 329 c.Assert(tryForSealing, Equals, "/,,") 330 } 331 332 func (s *modelSuite) TestDeviceChangeUnhappyFirstSwapModelFile(c *C) { 333 // system is encrypted 334 s.stampSealedKeys(c, s.rootdir) 335 336 // set up the old model file 337 err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model()) 338 c.Assert(err, IsNil) 339 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 340 "model: my-model-uc20\n") 341 342 resealKeysCalls := 0 343 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 344 resealKeysCalls++ 345 m, err := boot.ReadModeenv("") 346 c.Assert(err, IsNil) 347 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 348 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 349 switch resealKeysCalls { 350 case 1: 351 // run key 352 c.Assert(params.KeyFiles, DeepEquals, []string{ 353 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 354 }) 355 case 2: 356 // recovery keys 357 c.Assert(params.KeyFiles, DeepEquals, []string{ 358 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 359 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 360 }) 361 default: 362 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 363 } 364 365 switch resealKeysCalls { 366 case 1, 2: 367 // keys are first resealed for both models 368 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 369 c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 370 // boot/device/model is still the old file 371 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 372 "model: my-model-uc20\n") 373 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 374 "grade: dangerous\n") 375 } 376 377 if resealKeysCalls == 2 { 378 // break writing of the model file 379 c.Assert(os.RemoveAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device")), IsNil) 380 } 381 return nil 382 }) 383 defer restore() 384 385 err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev) 386 c.Assert(err, ErrorMatches, `cannot write new model file: open .*/run/mnt/ubuntu-boot/device/model\..*: no such file or directory`) 387 c.Assert(resealKeysCalls, Equals, 2) 388 389 m, err := boot.ReadModeenv("") 390 c.Assert(err, IsNil) 391 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 392 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 393 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 394 // try model has been cleared 395 c.Assert(tryForSealing, Equals, "/,,") 396 } 397 398 func (s *modelSuite) TestDeviceChangeUnhappySecondReseal(c *C) { 399 // system is encrypted 400 s.stampSealedKeys(c, s.rootdir) 401 402 // set up the old model file 403 err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model()) 404 c.Assert(err, IsNil) 405 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 406 "model: my-model-uc20\n") 407 408 resealKeysCalls := 0 409 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 410 resealKeysCalls++ 411 m, err := boot.ReadModeenv("") 412 c.Assert(err, IsNil) 413 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 414 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 415 // which keys? 416 switch resealKeysCalls { 417 case 1, 3: 418 // run key 419 c.Assert(params.KeyFiles, DeepEquals, []string{ 420 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 421 }) 422 case 2: 423 // recovery keys 424 c.Assert(params.KeyFiles, DeepEquals, []string{ 425 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 426 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 427 }) 428 default: 429 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 430 } 431 // what's in params? 432 switch resealKeysCalls { 433 case 1: 434 c.Assert(params.ModelParams, HasLen, 2) 435 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 436 c.Assert(params.ModelParams[1].Model.Model(), Equals, "my-new-model-uc20") 437 case 2: 438 // recovery key resealed for current model only 439 c.Assert(params.ModelParams, HasLen, 1) 440 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 441 case 3, 4: 442 // try model has become current 443 c.Assert(params.ModelParams, HasLen, 1) 444 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-new-model-uc20") 445 } 446 // what's in modeenv? 447 switch resealKeysCalls { 448 case 1, 2: 449 // keys are first resealed for both models 450 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 451 c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 452 // boot/device/model is still the old file 453 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 454 "model: my-model-uc20\n") 455 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 456 "grade: dangerous\n") 457 case 3: 458 // and finally just for the new model 459 c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 460 c.Assert(tryForSealing, Equals, "/,,") 461 // boot/device/model is the new model by this time 462 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 463 "model: my-new-model-uc20\n") 464 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 465 "grade: secured\n") 466 } 467 468 if resealKeysCalls == 3 { 469 return fmt.Errorf("fail on second try") 470 } 471 472 return nil 473 }) 474 defer restore() 475 476 err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev) 477 c.Assert(err, ErrorMatches, `cannot reseal the encryption key: fail on second try`) 478 c.Assert(resealKeysCalls, Equals, 3) 479 // old model file was restored 480 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 481 "model: my-model-uc20\n") 482 483 m, err := boot.ReadModeenv("") 484 c.Assert(err, IsNil) 485 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 486 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 487 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 488 // try model has been cleared 489 c.Assert(tryForSealing, Equals, "/,,") 490 } 491 492 func (s *modelSuite) TestDeviceChangeRebootBeforeNewModel(c *C) { 493 // system is encrypted 494 s.stampSealedKeys(c, s.rootdir) 495 496 // set up the old model file 497 err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model()) 498 c.Assert(err, IsNil) 499 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 500 "model: my-model-uc20\n") 501 502 resealKeysCalls := 0 503 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 504 resealKeysCalls++ 505 c.Logf("reseal key call: %v", resealKeysCalls) 506 m, err := boot.ReadModeenv("") 507 c.Assert(err, IsNil) 508 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 509 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 510 // timeline & calls: 511 // 1 - pre reboot, run & recovery keys, try model set 512 // 2 - pre reboot, recovery keys, try model set, unexpected reboot is triggered 513 // (reboot) 514 // no call for run key, boot chains haven't changes since call 1 515 // 3 - recovery key, try model set 516 // 4, 5 - post reboot, run & recovery keys, after rewriting model file, try model cleared 517 518 // which keys? 519 switch resealKeysCalls { 520 case 1, 4: 521 // run key 522 c.Assert(params.KeyFiles, DeepEquals, []string{ 523 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 524 }) 525 case 2, 3, 5: 526 // recovery keys 527 c.Assert(params.KeyFiles, DeepEquals, []string{ 528 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 529 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 530 }) 531 default: 532 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 533 } 534 // what's in params? 535 switch resealKeysCalls { 536 case 1: 537 c.Assert(params.ModelParams, HasLen, 2) 538 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 539 c.Assert(params.ModelParams[1].Model.Model(), Equals, "my-new-model-uc20") 540 case 2: 541 // attempted reseal of recovery key before clearing try model 542 c.Assert(params.ModelParams, HasLen, 1) 543 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 544 case 3: 545 // recovery keys are resealed only for current system 546 c.Assert(params.ModelParams, HasLen, 1) 547 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 548 case 4, 5: 549 // try model has become current 550 c.Assert(params.ModelParams, HasLen, 1) 551 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-new-model-uc20") 552 } 553 // what's in modeenv? 554 switch resealKeysCalls { 555 case 1, 2, 3: 556 // keys are first resealed for both models, which are restored to the modeenv 557 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 558 c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 559 // boot/device/model is still the old file 560 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 561 "model: my-model-uc20\n") 562 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 563 "grade: dangerous\n") 564 case 4, 5: 565 // and finally just for the new model 566 c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 567 c.Assert(tryForSealing, Equals, "/,,") 568 // boot/device/model is the new model by this time 569 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 570 "model: my-new-model-uc20\n") 571 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 572 "grade: secured\n") 573 } 574 575 if resealKeysCalls == 2 { 576 panic(fmt.Sprintf("mock reboot after first complete reseal")) 577 } 578 579 return nil 580 }) 581 defer restore() 582 583 c.Assert(func() { boot.DeviceChange(s.oldUc20dev, s.newUc20dev) }, PanicMatches, 584 `mock reboot after first complete reseal`) 585 c.Assert(resealKeysCalls, Equals, 2) 586 // still old model in place 587 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 588 "model: my-model-uc20\n") 589 590 m, err := boot.ReadModeenv("") 591 c.Assert(err, IsNil) 592 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 593 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 594 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 595 // try model is already set 596 c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 597 598 // let's try again 599 err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev) 600 c.Assert(err, IsNil) 601 c.Assert(resealKeysCalls, Equals, 5) 602 // got new model now 603 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 604 "model: my-new-model-uc20\n") 605 606 m, err = boot.ReadModeenv("") 607 c.Assert(err, IsNil) 608 currForSealing = boot.ModelUniqueID(m.ModelForSealing()) 609 tryForSealing = boot.ModelUniqueID(m.TryModelForSealing()) 610 // new model is current 611 c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 612 // try model has been cleared 613 c.Assert(tryForSealing, Equals, "/,,") 614 615 } 616 617 func (s *modelSuite) TestDeviceChangeRebootAfterNewModelFileWrite(c *C) { 618 // system is encrypted 619 s.stampSealedKeys(c, s.rootdir) 620 621 // set up the old model file 622 err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model()) 623 c.Assert(err, IsNil) 624 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 625 "model: my-model-uc20\n") 626 627 resealKeysCalls := 0 628 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 629 resealKeysCalls++ 630 c.Logf("reseal key call: %v", resealKeysCalls) 631 m, err := boot.ReadModeenv("") 632 c.Assert(err, IsNil) 633 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 634 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 635 // timeline & calls: 636 // 1, 2 - pre reboot, run & recovery keys, try model set 637 // 3 - run key, after model file has been modified, try model cleared, unexpected 638 // reboot is triggered 639 // (reboot) 640 // no reseal - boot chains are identical to what was in calls 1 & 2 which were successful 641 // 4, 5 - post reboot, run & recovery keys, after rewriting model file, try model cleared 642 643 // which keys? 644 switch resealKeysCalls { 645 case 1, 3, 4: 646 // run key 647 c.Assert(params.KeyFiles, DeepEquals, []string{ 648 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 649 }) 650 case 2, 5: 651 // recovery keys 652 c.Assert(params.KeyFiles, DeepEquals, []string{ 653 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 654 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 655 }) 656 default: 657 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 658 } 659 // what's in params? 660 switch resealKeysCalls { 661 case 1: 662 c.Assert(params.ModelParams, HasLen, 2) 663 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 664 c.Assert(params.ModelParams[1].Model.Model(), Equals, "my-new-model-uc20") 665 case 2: 666 // recovery key resealed for current model only 667 c.Assert(params.ModelParams, HasLen, 1) 668 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 669 case 3: 670 // attempted reseal with of run key after clearing try model 671 c.Assert(params.ModelParams, HasLen, 1) 672 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-new-model-uc20") 673 case 4, 5: 674 // try model has become current 675 c.Assert(params.ModelParams, HasLen, 1) 676 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-new-model-uc20") 677 } 678 // what's in modeenv? 679 switch resealKeysCalls { 680 case 1, 2: 681 // keys are first resealed for both models 682 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 683 c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 684 // boot/device/model is still the old file 685 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 686 "model: my-model-uc20\n") 687 case 3, 4, 5: 688 // and finally just for the new model 689 c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 690 c.Assert(tryForSealing, Equals, "/,,") 691 // boot/device/model is the new model by this time 692 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 693 "model: my-new-model-uc20\n") 694 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 695 "grade: secured\n") 696 } 697 698 if resealKeysCalls == 3 { 699 panic(fmt.Sprintf("mock reboot before second complete reseal")) 700 } 701 702 return nil 703 }) 704 defer restore() 705 706 c.Assert(func() { boot.DeviceChange(s.oldUc20dev, s.newUc20dev) }, PanicMatches, 707 `mock reboot before second complete reseal`) 708 c.Assert(resealKeysCalls, Equals, 3) 709 // model file has already been replaced 710 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 711 "model: my-new-model-uc20\n") 712 713 m, err := boot.ReadModeenv("") 714 c.Assert(err, IsNil) 715 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 716 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 717 // as well as modeenv 718 c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 719 c.Assert(tryForSealing, Equals, "/,,") 720 721 // let's try again (post reboot) 722 err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev) 723 c.Assert(err, IsNil) 724 c.Assert(resealKeysCalls, Equals, 5) 725 // got new model now 726 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 727 "model: my-new-model-uc20\n") 728 729 m, err = boot.ReadModeenv("") 730 c.Assert(err, IsNil) 731 currForSealing = boot.ModelUniqueID(m.ModelForSealing()) 732 tryForSealing = boot.ModelUniqueID(m.TryModelForSealing()) 733 // new model is current 734 c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 735 // try model has been cleared 736 c.Assert(tryForSealing, Equals, "/,,") 737 738 } 739 740 func (s *modelSuite) TestDeviceChangeRebootPostSameModel(c *C) { 741 // system is encrypted 742 s.stampSealedKeys(c, s.rootdir) 743 744 // set up the old model file 745 err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model()) 746 c.Assert(err, IsNil) 747 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 748 "model: my-model-uc20\n") 749 750 resealKeysCalls := 0 751 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 752 resealKeysCalls++ 753 c.Logf("reseal key call: %v", resealKeysCalls) 754 m, err := boot.ReadModeenv("") 755 c.Assert(err, IsNil) 756 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 757 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 758 // timeline & calls: 759 // 1, 2 - pre reboot, run & recovery keys, try model set 760 // 3 - run key, after model file has been modified, try model cleared 761 // 4 - recovery key, model file has been modified, try model cleared, unexpected 762 // reboot is triggered 763 // (reboot) 764 // 5, 6 - run & recovery, try model set, new model also restored 765 // as 'old' model, params are grouped by model 766 // 7 - run only (recovery boot chains have not changed since) 767 768 // which keys? 769 switch resealKeysCalls { 770 case 1, 3, 5, 7: 771 // run key 772 c.Assert(params.KeyFiles, DeepEquals, []string{ 773 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 774 }) 775 case 2, 4, 6: 776 // recovery keys 777 c.Assert(params.KeyFiles, DeepEquals, []string{ 778 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 779 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 780 }) 781 default: 782 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 783 } 784 // what's in params? 785 switch resealKeysCalls { 786 case 1: 787 c.Assert(params.ModelParams, HasLen, 2) 788 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 789 c.Assert(params.ModelParams[1].Model.Model(), Equals, "my-new-model-uc20") 790 case 2: 791 // recovery key resealed for current model only 792 c.Assert(params.ModelParams, HasLen, 1) 793 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 794 case 3, 4: 795 // try model has become current 796 c.Assert(params.ModelParams, HasLen, 1) 797 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-new-model-uc20") 798 case 5, 6, 7: 799 // 800 c.Assert(params.ModelParams, HasLen, 1) 801 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-new-model-uc20") 802 } 803 // what's in modeenv? 804 switch resealKeysCalls { 805 case 1, 2: 806 // keys are first resealed for both models 807 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 808 c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 809 // boot/device/model is still the old file 810 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 811 "model: my-model-uc20\n") 812 case 3, 4, 7: 813 // and finally just for the new model 814 c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 815 c.Assert(tryForSealing, Equals, "/,,") 816 // boot/device/model is the new model by this time 817 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 818 "model: my-new-model-uc20\n") 819 case 5, 6: 820 // new model passed as old one 821 c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 822 c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 823 // boot/device/model is still the old file 824 c.Assert(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 825 "model: my-new-model-uc20\n") 826 } 827 828 if resealKeysCalls == 4 { 829 panic(fmt.Sprintf("mock reboot before second complete reseal")) 830 } 831 return nil 832 }) 833 defer restore() 834 835 // as if called by device manager in task handler 836 c.Assert(func() { boot.DeviceChange(s.oldUc20dev, s.newUc20dev) }, PanicMatches, 837 `mock reboot before second complete reseal`) 838 c.Assert(resealKeysCalls, Equals, 4) 839 // model file has already been replaced 840 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 841 "model: my-new-model-uc20\n") 842 843 // as if called by device manager, after the model has been changed, but 844 // the set-model task isn't marked as done 845 err = boot.DeviceChange(s.newUc20dev, s.newUc20dev) 846 c.Assert(err, IsNil) 847 c.Assert(resealKeysCalls, Equals, 7) 848 849 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 850 "model: my-new-model-uc20\n") 851 852 m, err := boot.ReadModeenv("") 853 c.Assert(err, IsNil) 854 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 855 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 856 c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 857 // try model has been cleared 858 c.Assert(tryForSealing, Equals, "/,,") 859 } 860 861 type unhappyMockedWriteModelToBootTestCase struct { 862 breakModeenvAfterFirstWrite bool 863 modelRestoreFail bool 864 expectedErr string 865 } 866 867 func (s *modelSuite) testDeviceChangeUnhappyMockedWriteModelToBoot(c *C, tc unhappyMockedWriteModelToBootTestCase) { 868 // system is encrypted 869 s.stampSealedKeys(c, s.rootdir) 870 871 // set up the old model file 872 err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model()) 873 c.Assert(err, IsNil) 874 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 875 "model: my-model-uc20\n") 876 877 modeenvDir := filepath.Dir(dirs.SnapModeenvFileUnder(dirs.GlobalRootDir)) 878 defer os.Chmod(modeenvDir, 0755) 879 880 writeModelToBootCalls := 0 881 resealKeysCalls := 0 882 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 883 resealKeysCalls++ 884 m, err := boot.ReadModeenv("") 885 c.Assert(err, IsNil) 886 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 887 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 888 switch resealKeysCalls { 889 case 1: 890 // run key 891 c.Assert(params.KeyFiles, DeepEquals, []string{ 892 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 893 }) 894 case 2: 895 // recovery keys 896 c.Assert(params.KeyFiles, DeepEquals, []string{ 897 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 898 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 899 }) 900 default: 901 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 902 } 903 904 switch resealKeysCalls { 905 case 1: 906 // keys are first resealed for both models 907 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 908 c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 909 // no model has been written to ubuntu-boot yet 910 c.Assert(writeModelToBootCalls, Equals, 0) 911 } 912 return nil 913 }) 914 defer restore() 915 916 restore = boot.MockWriteModelToUbuntuBoot(func(model *asserts.Model) error { 917 writeModelToBootCalls++ 918 c.Assert(model, NotNil) 919 switch writeModelToBootCalls { 920 case 1: 921 // a call to write the new model 922 c.Check(model.Model(), Equals, "my-new-model-uc20") 923 // only 2 calls to reseal until now 924 c.Check(resealKeysCalls, Equals, 2) 925 if tc.breakModeenvAfterFirstWrite { 926 c.Assert(os.Chmod(modeenvDir, 0000), IsNil) 927 return nil 928 } 929 case 2: 930 // a call to restore the old model 931 c.Check(model.Model(), Equals, "my-model-uc20") 932 if !tc.breakModeenvAfterFirstWrite { 933 c.Errorf("unexpected additional call to writeModelToBoot (call # %d)", writeModelToBootCalls) 934 } 935 if !tc.modelRestoreFail { 936 return nil 937 } 938 default: 939 c.Errorf("unexpected additional call to writeModelToBoot (call # %d)", writeModelToBootCalls) 940 } 941 return fmt.Errorf("mocked fail in write model to boot") 942 }) 943 defer restore() 944 945 err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev) 946 c.Assert(err, ErrorMatches, tc.expectedErr) 947 c.Assert(resealKeysCalls, Equals, 2) 948 if tc.breakModeenvAfterFirstWrite { 949 // write to boot failed on the second call 950 c.Assert(writeModelToBootCalls, Equals, 2) 951 } else { 952 c.Assert(writeModelToBootCalls, Equals, 1) 953 } 954 // still the old model file, all writes were intercepted 955 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 956 "model: my-model-uc20\n") 957 958 if !tc.breakModeenvAfterFirstWrite { 959 m, err := boot.ReadModeenv("") 960 c.Assert(err, IsNil) 961 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 962 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 963 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 964 // try model has been cleared 965 c.Assert(tryForSealing, Equals, "/,,") 966 } 967 } 968 969 func (s *modelSuite) TestDeviceChangeUnhappyMockedWriteModelToBootBeforeModelSwap(c *C) { 970 s.testDeviceChangeUnhappyMockedWriteModelToBoot(c, unhappyMockedWriteModelToBootTestCase{ 971 expectedErr: "cannot write new model file: mocked fail in write model to boot", 972 }) 973 } 974 975 func (s *modelSuite) TestDeviceChangeUnhappyMockedWriteModelToBootAfterModelSwapFailingRestore(c *C) { 976 // writing modeenv after placing new model file on disk fails, and so 977 // does restoring of the old model 978 if os.Getuid() == 0 { 979 // the test is manipulating file permissions, which doesn't 980 // affect root 981 c.Skip("test cannot be executed by root") 982 } 983 s.testDeviceChangeUnhappyMockedWriteModelToBoot(c, unhappyMockedWriteModelToBootTestCase{ 984 breakModeenvAfterFirstWrite: true, 985 modelRestoreFail: true, 986 987 expectedErr: `open .*/var/lib/snapd/modeenv\..*: permission denied \(restoring model failed: mocked fail in write model to boot\)`, 988 }) 989 } 990 991 func (s *modelSuite) TestDeviceChangeUnhappyMockedWriteModelToBootAfterModelSwapHappyRestore(c *C) { 992 // writing modeenv after placing new model file on disk fails, but 993 // restore is successful 994 if os.Getuid() == 0 { 995 // the test is manipulating file permissions, which doesn't 996 // affect root 997 c.Skip("test cannot be executed by root") 998 } 999 s.testDeviceChangeUnhappyMockedWriteModelToBoot(c, unhappyMockedWriteModelToBootTestCase{ 1000 breakModeenvAfterFirstWrite: true, 1001 modelRestoreFail: false, 1002 1003 expectedErr: `open .*/var/lib/snapd/modeenv\..*: permission denied$`, 1004 }) 1005 } 1006 1007 func (s *modelSuite) TestDeviceChangeUnhappyFailReseaWithSwappedModelMockedWriteToBoot(c *C) { 1008 // system is encrypted 1009 s.stampSealedKeys(c, s.rootdir) 1010 1011 // set up the old model file 1012 err := boot.WriteModelToUbuntuBoot(s.oldUc20dev.Model()) 1013 c.Assert(err, IsNil) 1014 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 1015 "model: my-model-uc20\n") 1016 1017 writeModelToBootCalls := 0 1018 resealKeysCalls := 0 1019 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 1020 resealKeysCalls++ 1021 if resealKeysCalls == 3 { 1022 // we are resealing the run key, the old model has been 1023 // replaced by the new one in modeenv 1024 c.Assert(params.KeyFiles, DeepEquals, []string{ 1025 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 1026 }) 1027 m, err := boot.ReadModeenv("") 1028 c.Assert(err, IsNil) 1029 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 1030 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 1031 // keys are first resealed for both models 1032 c.Assert(currForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 1033 c.Assert(tryForSealing, Equals, "/,,") 1034 // an new model has already been written 1035 c.Assert(writeModelToBootCalls, Equals, 1) 1036 return fmt.Errorf("mock reseal failure") 1037 } 1038 1039 return nil 1040 }) 1041 defer restore() 1042 1043 restore = boot.MockWriteModelToUbuntuBoot(func(model *asserts.Model) error { 1044 writeModelToBootCalls++ 1045 switch writeModelToBootCalls { 1046 case 1: 1047 c.Assert(model, NotNil) 1048 c.Check(model.Model(), Equals, "my-new-model-uc20") 1049 // only 2 calls to reseal until now 1050 c.Check(resealKeysCalls, Equals, 2) 1051 case 2: 1052 // handling of reseal with new model restores the old one on the disk 1053 c.Check(model.Model(), Equals, "my-model-uc20") 1054 m, err := boot.ReadModeenv("") 1055 c.Assert(err, IsNil) 1056 // and both models are present in the modeenv 1057 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 1058 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 1059 // keys are first resealed for both models 1060 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 1061 c.Assert(tryForSealing, Equals, "canonical/my-new-model-uc20,secured,"+s.keyID) 1062 1063 default: 1064 c.Errorf("unexpected additional call to writeModelToBoot (call # %d)", writeModelToBootCalls) 1065 } 1066 return nil 1067 }) 1068 defer restore() 1069 1070 err = boot.DeviceChange(s.oldUc20dev, s.newUc20dev) 1071 c.Assert(err, ErrorMatches, `cannot reseal the encryption key: mock reseal failure`) 1072 c.Assert(resealKeysCalls, Equals, 3) 1073 c.Assert(writeModelToBootCalls, Equals, 2) 1074 // still the old model file, all writes were intercepted 1075 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileContains, 1076 "model: my-model-uc20\n") 1077 1078 // finally the try model has been dropped from modeenv 1079 m, err := boot.ReadModeenv("") 1080 c.Assert(err, IsNil) 1081 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 1082 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 1083 c.Assert(currForSealing, Equals, "canonical/my-model-uc20,dangerous,"+s.keyID) 1084 // try model has been cleared 1085 c.Assert(tryForSealing, Equals, "/,,") 1086 } 1087 1088 func (s *modelSuite) TestDeviceChangeRebootRestoreModelKeyChangeMockedWriteModel(c *C) { 1089 // system is encrypted 1090 s.stampSealedKeys(c, s.rootdir) 1091 1092 oldKeyID := "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" 1093 newKeyID := "ZZZ_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" 1094 // model can be mocked freely as we will not encode it as we mocked a 1095 // function that writes out the model too 1096 s.oldUc20dev = boottest.MockUC20Device("", boottest.MakeMockUC20Model(map[string]interface{}{ 1097 "model": "my-model-uc20", 1098 "brand-id": "my-brand", 1099 "grade": "dangerous", 1100 "sign-key-sha3-384": oldKeyID, 1101 })) 1102 1103 s.newUc20dev = boottest.MockUC20Device("", boottest.MakeMockUC20Model(map[string]interface{}{ 1104 "model": "my-model-uc20", 1105 "brand-id": "my-brand", 1106 "grade": "dangerous", 1107 "sign-key-sha3-384": newKeyID, 1108 })) 1109 1110 resealKeysCalls := 0 1111 restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 1112 resealKeysCalls++ 1113 c.Logf("reseal key call: %v", resealKeysCalls) 1114 m, err := boot.ReadModeenv("") 1115 c.Assert(err, IsNil) 1116 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 1117 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 1118 // timeline & calls: 1119 // 1, 2 - pre reboot, run & recovery keys, try model set 1120 // 3 - run key, after model file has been modified, try model cleared 1121 // 4 - recovery key, model file has been modified, try model cleared, 1122 // unexpected reboot is triggered 1123 // (reboot) 1124 // 5 - run with old model & key (since we resealed run key in 1125 // call 3, and recovery has not changed), old model restored in modeenv 1126 // 6 - run with new model and key, old current has been dropped 1127 // 7 - recovery with new model only 1128 1129 // which keys? 1130 switch resealKeysCalls { 1131 case 1, 3, 5, 6: 1132 // run key 1133 c.Assert(params.KeyFiles, DeepEquals, []string{ 1134 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 1135 }) 1136 case 2, 4, 7: 1137 // recovery keys 1138 c.Assert(params.KeyFiles, DeepEquals, []string{ 1139 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 1140 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 1141 }) 1142 default: 1143 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 1144 } 1145 // what's in params? 1146 switch resealKeysCalls { 1147 case 1, 5: 1148 c.Assert(params.ModelParams, HasLen, 2) 1149 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 1150 c.Assert(params.ModelParams[0].Model.SignKeyID(), Equals, oldKeyID) 1151 c.Assert(params.ModelParams[1].Model.Model(), Equals, "my-model-uc20") 1152 c.Assert(params.ModelParams[1].Model.SignKeyID(), Equals, newKeyID) 1153 case 2: 1154 // recovery key resealed for current model only 1155 c.Assert(params.ModelParams, HasLen, 1) 1156 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 1157 c.Assert(params.ModelParams[0].Model.SignKeyID(), Equals, oldKeyID) 1158 case 3, 4, 6, 7: 1159 // try model has become current 1160 c.Assert(params.ModelParams, HasLen, 1) 1161 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 1162 c.Assert(params.ModelParams[0].Model.SignKeyID(), Equals, newKeyID) 1163 } 1164 // what's in modeenv? 1165 switch resealKeysCalls { 1166 case 1, 2, 5: 1167 // keys are first resealed for both models 1168 c.Assert(currForSealing, Equals, "my-brand/my-model-uc20,dangerous,"+oldKeyID) 1169 c.Assert(tryForSealing, Equals, "my-brand/my-model-uc20,dangerous,"+newKeyID) 1170 case 3, 4, 6, 7: 1171 // and finally just for the new model 1172 c.Assert(currForSealing, Equals, "my-brand/my-model-uc20,dangerous,"+newKeyID) 1173 c.Assert(tryForSealing, Equals, "/,,") 1174 } 1175 1176 if resealKeysCalls == 4 { 1177 panic(fmt.Sprintf("mock reboot before second complete reseal")) 1178 } 1179 return nil 1180 }) 1181 defer restore() 1182 1183 writeModelToBootCalls := 0 1184 restore = boot.MockWriteModelToUbuntuBoot(func(model *asserts.Model) error { 1185 writeModelToBootCalls++ 1186 c.Logf("write model to boot call: %v", writeModelToBootCalls) 1187 switch writeModelToBootCalls { 1188 case 1: 1189 c.Assert(model, NotNil) 1190 c.Check(model.Model(), Equals, "my-model-uc20") 1191 // only 2 calls to reseal until now 1192 c.Check(resealKeysCalls, Equals, 2) 1193 case 2: 1194 // handling of reseal with new model restores the old one on the disk 1195 c.Check(model.Model(), Equals, "my-model-uc20") 1196 m, err := boot.ReadModeenv("") 1197 c.Assert(err, IsNil) 1198 // and both models are present in the modeenv 1199 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 1200 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 1201 // keys are first resealed for both models 1202 c.Assert(currForSealing, Equals, "my-brand/my-model-uc20,dangerous,"+oldKeyID) 1203 c.Assert(tryForSealing, Equals, "my-brand/my-model-uc20,dangerous,"+newKeyID) 1204 1205 default: 1206 c.Errorf("unexpected additional call to writeModelToBoot (call # %d)", writeModelToBootCalls) 1207 } 1208 return nil 1209 }) 1210 defer restore() 1211 1212 // as if called by device manager in task handler 1213 c.Assert(func() { boot.DeviceChange(s.oldUc20dev, s.newUc20dev) }, PanicMatches, 1214 `mock reboot before second complete reseal`) 1215 c.Assert(resealKeysCalls, Equals, 4) 1216 c.Assert(writeModelToBootCalls, Equals, 1) 1217 1218 err := boot.DeviceChange(s.oldUc20dev, s.newUc20dev) 1219 c.Assert(err, IsNil) 1220 c.Assert(resealKeysCalls, Equals, 7) 1221 c.Assert(writeModelToBootCalls, Equals, 2) 1222 1223 m, err := boot.ReadModeenv("") 1224 c.Assert(err, IsNil) 1225 currForSealing := boot.ModelUniqueID(m.ModelForSealing()) 1226 tryForSealing := boot.ModelUniqueID(m.TryModelForSealing()) 1227 c.Assert(currForSealing, Equals, "my-brand/my-model-uc20,dangerous,"+newKeyID) 1228 // try model has been cleared 1229 c.Assert(tryForSealing, Equals, "/,,") 1230 }