gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/boot/seal_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 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 "errors" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "strconv" 29 30 . "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/asserts" 33 "github.com/snapcore/snapd/boot" 34 "github.com/snapcore/snapd/boot/boottest" 35 "github.com/snapcore/snapd/bootloader" 36 "github.com/snapcore/snapd/bootloader/bootloadertest" 37 "github.com/snapcore/snapd/dirs" 38 "github.com/snapcore/snapd/kernel/fde" 39 "github.com/snapcore/snapd/osutil" 40 "github.com/snapcore/snapd/secboot" 41 "github.com/snapcore/snapd/seed" 42 "github.com/snapcore/snapd/snap" 43 "github.com/snapcore/snapd/snap/snaptest" 44 "github.com/snapcore/snapd/testutil" 45 "github.com/snapcore/snapd/timings" 46 ) 47 48 type sealSuite struct { 49 testutil.BaseTest 50 } 51 52 var _ = Suite(&sealSuite{}) 53 54 func (s *sealSuite) SetUpTest(c *C) { 55 s.BaseTest.SetUpTest(c) 56 57 rootdir := c.MkDir() 58 dirs.SetRootDir(rootdir) 59 s.AddCleanup(func() { dirs.SetRootDir("/") }) 60 } 61 62 func mockKernelSeedSnap(c *C, rev snap.Revision) *seed.Snap { 63 return mockNamedKernelSeedSnap(c, rev, "pc-kernel") 64 } 65 66 func mockNamedKernelSeedSnap(c *C, rev snap.Revision, name string) *seed.Snap { 67 revAsString := rev.String() 68 if rev.Unset() { 69 revAsString = "unset" 70 } 71 return &seed.Snap{ 72 Path: fmt.Sprintf("/var/lib/snapd/seed/snaps/%v_%v.snap", name, revAsString), 73 SideInfo: &snap.SideInfo{ 74 RealName: name, 75 Revision: rev, 76 }, 77 EssentialType: snap.TypeKernel, 78 } 79 } 80 81 func mockGadgetSeedSnap(c *C, files [][]string) *seed.Snap { 82 gadgetSnapFile := snaptest.MakeTestSnapWithFiles(c, gadgetSnapYaml, files) 83 return &seed.Snap{ 84 Path: gadgetSnapFile, 85 SideInfo: &snap.SideInfo{ 86 RealName: "gadget", 87 Revision: snap.R(1), 88 }, 89 EssentialType: snap.TypeGadget, 90 } 91 } 92 93 func (s *sealSuite) TestSealKeyToModeenv(c *C) { 94 for _, tc := range []struct { 95 sealErr error 96 err string 97 }{ 98 {sealErr: nil, err: ""}, 99 {sealErr: errors.New("seal error"), err: "cannot seal the encryption keys: seal error"}, 100 } { 101 rootdir := c.MkDir() 102 dirs.SetRootDir(rootdir) 103 defer dirs.SetRootDir("") 104 105 err := createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed")) 106 c.Assert(err, IsNil) 107 108 err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot")) 109 c.Assert(err, IsNil) 110 111 model := boottest.MakeMockUC20Model() 112 113 modeenv := &boot.Modeenv{ 114 RecoverySystem: "20200825", 115 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 116 "grubx64.efi": []string{"grub-hash-1"}, 117 "bootx64.efi": []string{"shim-hash-1"}, 118 }, 119 120 CurrentTrustedBootAssets: boot.BootAssetsMap{ 121 "grubx64.efi": []string{"run-grub-hash-1"}, 122 }, 123 124 CurrentKernels: []string{"pc-kernel_500.snap"}, 125 126 CurrentKernelCommandLines: boot.BootCommandLines{ 127 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 128 }, 129 Model: model.Model(), 130 BrandID: model.BrandID(), 131 Grade: string(model.Grade()), 132 ModelSignKeyID: model.SignKeyID(), 133 } 134 135 // mock asset cache 136 mockAssetsCache(c, rootdir, "grub", []string{ 137 "bootx64.efi-shim-hash-1", 138 "grubx64.efi-grub-hash-1", 139 "grubx64.efi-run-grub-hash-1", 140 }) 141 142 // set encryption key 143 myKey := secboot.EncryptionKey{} 144 myKey2 := secboot.EncryptionKey{} 145 for i := range myKey { 146 myKey[i] = byte(i) 147 myKey2[i] = byte(128 + i) 148 } 149 150 // set a mock recovery kernel 151 readSystemEssentialCalls := 0 152 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 153 readSystemEssentialCalls++ 154 return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil 155 }) 156 defer restore() 157 158 // set mock key sealing 159 sealKeysCalls := 0 160 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { 161 sealKeysCalls++ 162 switch sealKeysCalls { 163 case 1: 164 // the run object seals only the ubuntu-data key 165 c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-policy-auth-key")) 166 c.Check(params.TPMLockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth")) 167 168 dataKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key") 169 c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}}) 170 case 2: 171 // the fallback object seals the ubuntu-data and the ubuntu-save keys 172 c.Check(params.TPMPolicyAuthKeyFile, Equals, "") 173 c.Check(params.TPMLockoutAuthFile, Equals, "") 174 175 dataKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key") 176 saveKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key") 177 c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}, {Key: myKey2, KeyName: "ubuntu-save", KeyFile: saveKeyFile}}) 178 default: 179 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 180 } 181 c.Assert(params.ModelParams, HasLen, 1) 182 for _, d := range []string{boot.InitramfsSeedEncryptionKeyDir, boot.InstallHostFDEDataDir} { 183 ex, isdir, _ := osutil.DirExists(d) 184 c.Check(ex && isdir, Equals, true, Commentf("location %q does not exist or is not a directory", d)) 185 } 186 187 shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash-1"), bootloader.RoleRecovery) 188 grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash-1"), bootloader.RoleRecovery) 189 runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash-1"), bootloader.RoleRunMode) 190 kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 191 runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode) 192 193 switch sealKeysCalls { 194 case 1: 195 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 196 secboot.NewLoadChain(shim, 197 secboot.NewLoadChain(grub, 198 secboot.NewLoadChain(kernel))), 199 secboot.NewLoadChain(shim, 200 secboot.NewLoadChain(grub, 201 secboot.NewLoadChain(runGrub, 202 secboot.NewLoadChain(runKernel)))), 203 }) 204 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 205 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 206 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 207 }) 208 case 2: 209 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 210 secboot.NewLoadChain(shim, 211 secboot.NewLoadChain(grub, 212 secboot.NewLoadChain(kernel))), 213 }) 214 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 215 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 216 }) 217 default: 218 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 219 } 220 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 221 222 return tc.sealErr 223 }) 224 defer restore() 225 226 err = boot.SealKeyToModeenv(myKey, myKey2, model, modeenv) 227 if tc.sealErr != nil { 228 c.Assert(sealKeysCalls, Equals, 1) 229 } else { 230 c.Assert(sealKeysCalls, Equals, 2) 231 } 232 if tc.err == "" { 233 c.Assert(err, IsNil) 234 } else { 235 c.Assert(err, ErrorMatches, tc.err) 236 continue 237 } 238 239 // verify the boot chains data file 240 pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "boot-chains")) 241 c.Assert(err, IsNil) 242 c.Check(cnt, Equals, 0) 243 c.Check(pbc, DeepEquals, boot.PredictableBootChains{ 244 boot.BootChain{ 245 BrandID: "my-brand", 246 Model: "my-model-uc20", 247 Grade: "dangerous", 248 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 249 AssetChain: []boot.BootAsset{ 250 { 251 Role: "recovery", 252 Name: "bootx64.efi", 253 Hashes: []string{"shim-hash-1"}, 254 }, 255 { 256 Role: "recovery", 257 Name: "grubx64.efi", 258 Hashes: []string{"grub-hash-1"}, 259 }, 260 }, 261 Kernel: "pc-kernel", 262 KernelRevision: "1", 263 KernelCmdlines: []string{ 264 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 265 }, 266 }, 267 boot.BootChain{ 268 BrandID: "my-brand", 269 Model: "my-model-uc20", 270 Grade: "dangerous", 271 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 272 AssetChain: []boot.BootAsset{ 273 { 274 Role: "recovery", 275 Name: "bootx64.efi", 276 Hashes: []string{"shim-hash-1"}, 277 }, 278 { 279 Role: "recovery", 280 Name: "grubx64.efi", 281 Hashes: []string{"grub-hash-1"}, 282 }, 283 { 284 Role: "run-mode", 285 Name: "grubx64.efi", 286 Hashes: []string{"run-grub-hash-1"}, 287 }, 288 }, 289 Kernel: "pc-kernel", 290 KernelRevision: "500", 291 KernelCmdlines: []string{ 292 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 293 }, 294 }, 295 }) 296 297 // verify the recovery boot chains 298 pbc, cnt, err = boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "recovery-boot-chains")) 299 c.Assert(err, IsNil) 300 c.Check(cnt, Equals, 0) 301 c.Check(pbc, DeepEquals, boot.PredictableBootChains{ 302 boot.BootChain{ 303 BrandID: "my-brand", 304 Model: "my-model-uc20", 305 Grade: "dangerous", 306 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 307 AssetChain: []boot.BootAsset{ 308 { 309 Role: "recovery", 310 Name: "bootx64.efi", 311 Hashes: []string{"shim-hash-1"}, 312 }, 313 { 314 Role: "recovery", 315 Name: "grubx64.efi", 316 Hashes: []string{"grub-hash-1"}, 317 }, 318 }, 319 Kernel: "pc-kernel", 320 KernelRevision: "1", 321 KernelCmdlines: []string{ 322 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 323 }, 324 }, 325 }) 326 327 // marker 328 marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys") 329 c.Check(marker, testutil.FileEquals, "tpm") 330 } 331 } 332 333 // TODO:UC20: also test fallback reseal 334 func (s *sealSuite) TestResealKeyToModeenvWithSystemFallback(c *C) { 335 var prevPbc boot.PredictableBootChains 336 var prevRecoveryPbc boot.PredictableBootChains 337 338 for idx, tc := range []struct { 339 sealedKeys bool 340 reuseRunPbc bool 341 reuseRecoveryPbc bool 342 resealErr error 343 err string 344 }{ 345 {sealedKeys: false, resealErr: nil, err: ""}, 346 {sealedKeys: true, resealErr: nil, err: ""}, 347 {sealedKeys: true, resealErr: errors.New("reseal error"), err: "cannot reseal the encryption key: reseal error"}, 348 {reuseRunPbc: true, reuseRecoveryPbc: true, sealedKeys: true, resealErr: nil, err: ""}, 349 // recovery boot chain is unchanged 350 {reuseRunPbc: false, reuseRecoveryPbc: true, sealedKeys: true, resealErr: nil, err: ""}, 351 // run boot chain is unchanged 352 {reuseRunPbc: true, reuseRecoveryPbc: false, sealedKeys: true, resealErr: nil, err: ""}, 353 } { 354 c.Logf("tc: %v", idx) 355 rootdir := c.MkDir() 356 dirs.SetRootDir(rootdir) 357 defer dirs.SetRootDir("") 358 359 if tc.sealedKeys { 360 c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil) 361 err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644) 362 c.Assert(err, IsNil) 363 364 } 365 366 err := createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed")) 367 c.Assert(err, IsNil) 368 369 err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot")) 370 c.Assert(err, IsNil) 371 372 model := boottest.MakeMockUC20Model() 373 374 modeenv := &boot.Modeenv{ 375 CurrentRecoverySystems: []string{"20200825"}, 376 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 377 "grubx64.efi": []string{"grub-hash-1"}, 378 "bootx64.efi": []string{"shim-hash-1", "shim-hash-2"}, 379 }, 380 381 CurrentTrustedBootAssets: boot.BootAssetsMap{ 382 "grubx64.efi": []string{"run-grub-hash-1", "run-grub-hash-2"}, 383 }, 384 385 CurrentKernels: []string{"pc-kernel_500.snap", "pc-kernel_600.snap"}, 386 387 CurrentKernelCommandLines: boot.BootCommandLines{ 388 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 389 }, 390 Model: model.Model(), 391 BrandID: model.BrandID(), 392 Grade: string(model.Grade()), 393 ModelSignKeyID: model.SignKeyID(), 394 } 395 396 if tc.reuseRunPbc { 397 err := boot.WriteBootChains(prevPbc, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 9) 398 c.Assert(err, IsNil) 399 } 400 if tc.reuseRecoveryPbc { 401 err = boot.WriteBootChains(prevRecoveryPbc, filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains"), 9) 402 c.Assert(err, IsNil) 403 } 404 405 // mock asset cache 406 mockAssetsCache(c, rootdir, "grub", []string{ 407 "bootx64.efi-shim-hash-1", 408 "bootx64.efi-shim-hash-2", 409 "grubx64.efi-grub-hash-1", 410 "grubx64.efi-run-grub-hash-1", 411 "grubx64.efi-run-grub-hash-2", 412 }) 413 414 // set a mock recovery kernel 415 readSystemEssentialCalls := 0 416 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 417 readSystemEssentialCalls++ 418 return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil 419 }) 420 defer restore() 421 422 // set mock key resealing 423 resealKeysCalls := 0 424 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 425 c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key")) 426 427 resealKeysCalls++ 428 c.Assert(params.ModelParams, HasLen, 1) 429 430 // shared parameters 431 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 432 433 // recovery parameters 434 shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash-1"), bootloader.RoleRecovery) 435 shim2 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash-2"), bootloader.RoleRecovery) 436 grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash-1"), bootloader.RoleRecovery) 437 kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 438 // run mode parameters 439 runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash-1"), bootloader.RoleRunMode) 440 runGrub2 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash-2"), bootloader.RoleRunMode) 441 runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode) 442 runKernel2 := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_600.snap"), "kernel.efi", bootloader.RoleRunMode) 443 444 checkRunParams := func() { 445 c.Check(params.KeyFiles, DeepEquals, []string{ 446 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 447 }) 448 c.Check(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 449 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 450 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 451 }) 452 // load chains 453 c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 6) 454 // recovery load chains 455 c.Assert(params.ModelParams[0].EFILoadChains[:2], DeepEquals, []*secboot.LoadChain{ 456 secboot.NewLoadChain(shim, 457 secboot.NewLoadChain(grub, 458 secboot.NewLoadChain(kernel))), 459 secboot.NewLoadChain(shim2, 460 secboot.NewLoadChain(grub, 461 secboot.NewLoadChain(kernel))), 462 }) 463 // run load chains 464 c.Assert(params.ModelParams[0].EFILoadChains[2:4], DeepEquals, []*secboot.LoadChain{ 465 secboot.NewLoadChain(shim, 466 secboot.NewLoadChain(grub, 467 secboot.NewLoadChain(runGrub, 468 secboot.NewLoadChain(runKernel)), 469 secboot.NewLoadChain(runGrub2, 470 secboot.NewLoadChain(runKernel)), 471 )), 472 secboot.NewLoadChain(shim2, 473 secboot.NewLoadChain(grub, 474 secboot.NewLoadChain(runGrub, 475 secboot.NewLoadChain(runKernel)), 476 secboot.NewLoadChain(runGrub2, 477 secboot.NewLoadChain(runKernel)), 478 )), 479 }) 480 481 c.Assert(params.ModelParams[0].EFILoadChains[4:], DeepEquals, []*secboot.LoadChain{ 482 secboot.NewLoadChain(shim, 483 secboot.NewLoadChain(grub, 484 secboot.NewLoadChain(runGrub, 485 secboot.NewLoadChain(runKernel2)), 486 secboot.NewLoadChain(runGrub2, 487 secboot.NewLoadChain(runKernel2)), 488 )), 489 secboot.NewLoadChain(shim2, 490 secboot.NewLoadChain(grub, 491 secboot.NewLoadChain(runGrub, 492 secboot.NewLoadChain(runKernel2)), 493 secboot.NewLoadChain(runGrub2, 494 secboot.NewLoadChain(runKernel2)), 495 )), 496 }) 497 } 498 499 checkRecoveryParams := func() { 500 c.Check(params.KeyFiles, DeepEquals, []string{ 501 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 502 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 503 }) 504 c.Check(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 505 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 506 }) 507 // load chains 508 c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 2) 509 // recovery load chains 510 c.Assert(params.ModelParams[0].EFILoadChains[:2], DeepEquals, []*secboot.LoadChain{ 511 secboot.NewLoadChain(shim, 512 secboot.NewLoadChain(grub, 513 secboot.NewLoadChain(kernel))), 514 secboot.NewLoadChain(shim2, 515 secboot.NewLoadChain(grub, 516 secboot.NewLoadChain(kernel))), 517 }) 518 } 519 520 switch resealKeysCalls { 521 case 1: 522 if !tc.reuseRunPbc { 523 checkRunParams() 524 } else if !tc.reuseRecoveryPbc { 525 checkRecoveryParams() 526 } else { 527 c.Errorf("unexpected call to secboot.ResealKeys (call # %d)", resealKeysCalls) 528 } 529 case 2: 530 if !tc.reuseRecoveryPbc { 531 checkRecoveryParams() 532 } else { 533 c.Errorf("unexpected call to secboot.ResealKeys (call # %d)", resealKeysCalls) 534 } 535 default: 536 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 537 } 538 539 return tc.resealErr 540 }) 541 defer restore() 542 543 // here we don't have unasserted kernels so just set 544 // expectReseal to false as it doesn't matter; 545 // the behavior with unasserted kernel is tested in 546 // boot_test.go specific tests 547 const expectReseal = false 548 err = boot.ResealKeyToModeenv(rootdir, modeenv, expectReseal) 549 if !tc.sealedKeys || (tc.reuseRunPbc && tc.reuseRecoveryPbc) { 550 // did nothing 551 c.Assert(err, IsNil) 552 c.Assert(resealKeysCalls, Equals, 0) 553 continue 554 } 555 if tc.err == "" { 556 c.Assert(err, IsNil) 557 } else { 558 c.Assert(err, ErrorMatches, tc.err) 559 } 560 if tc.resealErr != nil { 561 // mocked error is returned on first reseal 562 c.Assert(resealKeysCalls, Equals, 1) 563 } else if !tc.reuseRecoveryPbc && !tc.reuseRunPbc { 564 // none of the boot chains is reused, so 2 reseals are 565 // observed 566 c.Assert(resealKeysCalls, Equals, 2) 567 } else { 568 // one of the boot chains is reused, only one reseal 569 c.Assert(resealKeysCalls, Equals, 1) 570 } 571 if tc.err != "" { 572 continue 573 } 574 575 // verify the boot chains data file 576 pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains")) 577 c.Assert(err, IsNil) 578 if tc.reuseRunPbc { 579 c.Assert(cnt, Equals, 9) 580 } else { 581 c.Assert(cnt, Equals, 1) 582 } 583 c.Check(pbc, DeepEquals, boot.PredictableBootChains{ 584 boot.BootChain{ 585 BrandID: "my-brand", 586 Model: "my-model-uc20", 587 Grade: "dangerous", 588 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 589 AssetChain: []boot.BootAsset{ 590 { 591 Role: "recovery", 592 Name: "bootx64.efi", 593 Hashes: []string{"shim-hash-1", "shim-hash-2"}, 594 }, 595 { 596 Role: "recovery", 597 Name: "grubx64.efi", 598 Hashes: []string{"grub-hash-1"}, 599 }, 600 }, 601 Kernel: "pc-kernel", 602 KernelRevision: "1", 603 KernelCmdlines: []string{ 604 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 605 }, 606 }, 607 boot.BootChain{ 608 BrandID: "my-brand", 609 Model: "my-model-uc20", 610 Grade: "dangerous", 611 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 612 AssetChain: []boot.BootAsset{ 613 { 614 Role: "recovery", 615 Name: "bootx64.efi", 616 Hashes: []string{"shim-hash-1", "shim-hash-2"}, 617 }, 618 { 619 Role: "recovery", 620 Name: "grubx64.efi", 621 Hashes: []string{"grub-hash-1"}, 622 }, 623 { 624 Role: "run-mode", 625 Name: "grubx64.efi", 626 Hashes: []string{"run-grub-hash-1", "run-grub-hash-2"}, 627 }, 628 }, 629 Kernel: "pc-kernel", 630 KernelRevision: "500", 631 KernelCmdlines: []string{ 632 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 633 }, 634 }, 635 boot.BootChain{ 636 BrandID: "my-brand", 637 Model: "my-model-uc20", 638 Grade: "dangerous", 639 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 640 AssetChain: []boot.BootAsset{ 641 { 642 Role: "recovery", 643 Name: "bootx64.efi", 644 Hashes: []string{"shim-hash-1", "shim-hash-2"}, 645 }, 646 { 647 Role: "recovery", 648 Name: "grubx64.efi", 649 Hashes: []string{"grub-hash-1"}, 650 }, 651 { 652 Role: "run-mode", 653 Name: "grubx64.efi", 654 Hashes: []string{"run-grub-hash-1", "run-grub-hash-2"}, 655 }, 656 }, 657 Kernel: "pc-kernel", 658 KernelRevision: "600", 659 KernelCmdlines: []string{ 660 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 661 }, 662 }, 663 }) 664 prevPbc = pbc 665 recoveryPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains")) 666 c.Assert(err, IsNil) 667 if tc.reuseRecoveryPbc { 668 c.Check(cnt, Equals, 9) 669 } else { 670 c.Check(cnt, Equals, 1) 671 } 672 prevRecoveryPbc = recoveryPbc 673 } 674 } 675 676 func (s *sealSuite) TestResealKeyToModeenvRecoveryKeysForGoodSystemsOnly(c *C) { 677 rootdir := c.MkDir() 678 dirs.SetRootDir(rootdir) 679 defer dirs.SetRootDir("") 680 681 c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil) 682 err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644) 683 c.Assert(err, IsNil) 684 685 err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed")) 686 c.Assert(err, IsNil) 687 688 err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot")) 689 c.Assert(err, IsNil) 690 691 model := boottest.MakeMockUC20Model() 692 693 modeenv := &boot.Modeenv{ 694 // where 1234 is being tried 695 CurrentRecoverySystems: []string{"20200825", "1234"}, 696 // 20200825 has known to be good 697 GoodRecoverySystems: []string{"20200825"}, 698 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 699 "grubx64.efi": []string{"grub-hash"}, 700 "bootx64.efi": []string{"shim-hash"}, 701 }, 702 703 CurrentTrustedBootAssets: boot.BootAssetsMap{ 704 "grubx64.efi": []string{"run-grub-hash"}, 705 }, 706 707 CurrentKernels: []string{"pc-kernel_500.snap"}, 708 709 CurrentKernelCommandLines: boot.BootCommandLines{ 710 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 711 }, 712 Model: model.Model(), 713 BrandID: model.BrandID(), 714 Grade: string(model.Grade()), 715 ModelSignKeyID: model.SignKeyID(), 716 } 717 718 // mock asset cache 719 mockAssetsCache(c, rootdir, "grub", []string{ 720 "bootx64.efi-shim-hash", 721 "grubx64.efi-grub-hash", 722 "grubx64.efi-run-grub-hash", 723 }) 724 725 // set a mock recovery kernel 726 readSystemEssentialCalls := 0 727 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 728 readSystemEssentialCalls++ 729 kernelRev := 1 730 if label == "1234" { 731 kernelRev = 999 732 } 733 return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(kernelRev)), mockGadgetSeedSnap(c, nil)}, nil 734 }) 735 defer restore() 736 737 // set mock key resealing 738 resealKeysCalls := 0 739 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 740 c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key")) 741 742 resealKeysCalls++ 743 c.Assert(params.ModelParams, HasLen, 1) 744 745 // shared parameters 746 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 747 c.Logf("got:") 748 for _, ch := range params.ModelParams[0].EFILoadChains { 749 printChain(c, ch, "-") 750 } 751 switch resealKeysCalls { 752 case 1: // run key 753 c.Assert(params.KeyFiles, DeepEquals, []string{ 754 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 755 }) 756 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 757 "snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1", 758 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 759 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 760 }) 761 // load chains 762 c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 3) 763 case 2: // recovery keys 764 c.Assert(params.KeyFiles, DeepEquals, []string{ 765 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 766 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 767 }) 768 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 769 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 770 }) 771 // load chains 772 c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 1) 773 default: 774 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 775 } 776 777 // recovery parameters 778 shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash"), bootloader.RoleRecovery) 779 grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash"), bootloader.RoleRecovery) 780 kernelGoodRecovery := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 781 // kernel from a tried recovery system 782 kernelTriedRecovery := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_999.snap", "kernel.efi", bootloader.RoleRecovery) 783 // run mode parameters 784 runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash"), bootloader.RoleRunMode) 785 runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode) 786 787 switch resealKeysCalls { 788 case 1: // run load chain 789 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 790 secboot.NewLoadChain(shim, 791 secboot.NewLoadChain(grub, 792 secboot.NewLoadChain(kernelGoodRecovery), 793 )), 794 secboot.NewLoadChain(shim, 795 secboot.NewLoadChain(grub, 796 secboot.NewLoadChain(kernelTriedRecovery), 797 )), 798 secboot.NewLoadChain(shim, 799 secboot.NewLoadChain(grub, 800 secboot.NewLoadChain(runGrub, 801 secboot.NewLoadChain(runKernel)), 802 )), 803 }) 804 case 2: // recovery load chains 805 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 806 secboot.NewLoadChain(shim, 807 secboot.NewLoadChain(grub, 808 secboot.NewLoadChain(kernelGoodRecovery), 809 )), 810 }) 811 } 812 813 return nil 814 }) 815 defer restore() 816 817 // here we don't have unasserted kernels so just set 818 // expectReseal to false as it doesn't matter; 819 // the behavior with unasserted kernel is tested in 820 // boot_test.go specific tests 821 const expectReseal = false 822 err = boot.ResealKeyToModeenv(rootdir, modeenv, expectReseal) 823 c.Assert(err, IsNil) 824 c.Assert(resealKeysCalls, Equals, 2) 825 826 // verify the boot chains data file for run key 827 runPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains")) 828 c.Assert(err, IsNil) 829 c.Assert(cnt, Equals, 1) 830 c.Check(runPbc, DeepEquals, boot.PredictableBootChains{ 831 boot.BootChain{ 832 BrandID: "my-brand", 833 Model: "my-model-uc20", 834 Grade: "dangerous", 835 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 836 AssetChain: []boot.BootAsset{ 837 { 838 Role: "recovery", 839 Name: "bootx64.efi", 840 Hashes: []string{"shim-hash"}, 841 }, 842 { 843 Role: "recovery", 844 Name: "grubx64.efi", 845 Hashes: []string{"grub-hash"}, 846 }, 847 }, 848 Kernel: "pc-kernel", 849 KernelRevision: "1", 850 KernelCmdlines: []string{ 851 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 852 }, 853 }, 854 // includes the tried system 855 boot.BootChain{ 856 BrandID: "my-brand", 857 Model: "my-model-uc20", 858 Grade: "dangerous", 859 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 860 AssetChain: []boot.BootAsset{ 861 { 862 Role: "recovery", 863 Name: "bootx64.efi", 864 Hashes: []string{"shim-hash"}, 865 }, 866 { 867 Role: "recovery", 868 Name: "grubx64.efi", 869 Hashes: []string{"grub-hash"}, 870 }, 871 }, 872 Kernel: "pc-kernel", 873 KernelRevision: "999", 874 KernelCmdlines: []string{ 875 "snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1", 876 }, 877 }, 878 boot.BootChain{ 879 BrandID: "my-brand", 880 Model: "my-model-uc20", 881 Grade: "dangerous", 882 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 883 AssetChain: []boot.BootAsset{ 884 { 885 Role: "recovery", 886 Name: "bootx64.efi", 887 Hashes: []string{"shim-hash"}, 888 }, 889 { 890 Role: "recovery", 891 Name: "grubx64.efi", 892 Hashes: []string{"grub-hash"}, 893 }, 894 { 895 Role: "run-mode", 896 Name: "grubx64.efi", 897 Hashes: []string{"run-grub-hash"}, 898 }, 899 }, 900 Kernel: "pc-kernel", 901 KernelRevision: "500", 902 KernelCmdlines: []string{ 903 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 904 }, 905 }, 906 }) 907 // recovery boot chains 908 recoveryPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains")) 909 c.Assert(err, IsNil) 910 c.Assert(cnt, Equals, 1) 911 c.Check(recoveryPbc, DeepEquals, boot.PredictableBootChains{ 912 // only one entry for a recovery system that is known to be good 913 boot.BootChain{ 914 BrandID: "my-brand", 915 Model: "my-model-uc20", 916 Grade: "dangerous", 917 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 918 AssetChain: []boot.BootAsset{ 919 { 920 Role: "recovery", 921 Name: "bootx64.efi", 922 Hashes: []string{"shim-hash"}, 923 }, 924 { 925 Role: "recovery", 926 Name: "grubx64.efi", 927 Hashes: []string{"grub-hash"}, 928 }, 929 }, 930 Kernel: "pc-kernel", 931 KernelRevision: "1", 932 KernelCmdlines: []string{ 933 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 934 }, 935 }, 936 }) 937 } 938 939 func (s *sealSuite) TestResealKeyToModeenvFallbackCmdline(c *C) { 940 rootdir := c.MkDir() 941 dirs.SetRootDir(rootdir) 942 defer dirs.SetRootDir("") 943 944 model := boottest.MakeMockUC20Model() 945 946 c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil) 947 err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644) 948 c.Assert(err, IsNil) 949 950 modeenv := &boot.Modeenv{ 951 CurrentRecoverySystems: []string{"20200825"}, 952 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 953 "asset": []string{"asset-hash-1"}, 954 }, 955 956 CurrentTrustedBootAssets: boot.BootAssetsMap{ 957 "asset": []string{"asset-hash-1"}, 958 }, 959 960 CurrentKernels: []string{"pc-kernel_500.snap"}, 961 962 // as if it is unset yet 963 CurrentKernelCommandLines: nil, 964 965 Model: model.Model(), 966 BrandID: model.BrandID(), 967 Grade: string(model.Grade()), 968 ModelSignKeyID: model.SignKeyID(), 969 } 970 971 err = boot.WriteBootChains(nil, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 9) 972 c.Assert(err, IsNil) 973 // mock asset cache 974 mockAssetsCache(c, rootdir, "trusted", []string{ 975 "asset-asset-hash-1", 976 }) 977 978 // match one of current kernels 979 runKernelBf := bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_500.snap", "kernel.efi", bootloader.RoleRunMode) 980 // match the seed kernel 981 recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 982 983 bootdir := c.MkDir() 984 mtbl := bootloadertest.Mock("trusted", bootdir).WithTrustedAssets() 985 mtbl.TrustedAssetsList = []string{"asset-1"} 986 mtbl.StaticCommandLine = "static cmdline" 987 mtbl.BootChainList = []bootloader.BootFile{ 988 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 989 runKernelBf, 990 } 991 mtbl.RecoveryBootChainList = []bootloader.BootFile{ 992 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 993 recoveryKernelBf, 994 } 995 bootloader.Force(mtbl) 996 defer bootloader.Force(nil) 997 998 // set a mock recovery kernel 999 readSystemEssentialCalls := 0 1000 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 1001 readSystemEssentialCalls++ 1002 return model, []*seed.Snap{mockKernelSeedSnap(c, snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil 1003 }) 1004 defer restore() 1005 1006 // set mock key resealing 1007 resealKeysCalls := 0 1008 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 1009 resealKeysCalls++ 1010 c.Assert(params.ModelParams, HasLen, 1) 1011 c.Logf("reseal: %+v", params) 1012 switch resealKeysCalls { 1013 case 1: 1014 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 1015 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 1016 "snapd_recovery_mode=run static cmdline", 1017 }) 1018 case 2: 1019 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 1020 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 1021 }) 1022 default: 1023 c.Fatalf("unexpected number of reseal calls, %v", params) 1024 } 1025 return nil 1026 }) 1027 defer restore() 1028 1029 const expectReseal = false 1030 err = boot.ResealKeyToModeenv(rootdir, modeenv, expectReseal) 1031 c.Assert(err, IsNil) 1032 c.Assert(resealKeysCalls, Equals, 2) 1033 1034 // verify the boot chains data file 1035 pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains")) 1036 c.Assert(err, IsNil) 1037 c.Assert(cnt, Equals, 10) 1038 c.Check(pbc, DeepEquals, boot.PredictableBootChains{ 1039 boot.BootChain{ 1040 BrandID: "my-brand", 1041 Model: "my-model-uc20", 1042 Grade: "dangerous", 1043 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1044 AssetChain: []boot.BootAsset{ 1045 { 1046 Role: "recovery", 1047 Name: "asset", 1048 Hashes: []string{"asset-hash-1"}, 1049 }, 1050 }, 1051 Kernel: "pc-kernel", 1052 KernelRevision: "1", 1053 KernelCmdlines: []string{ 1054 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 1055 }, 1056 }, 1057 boot.BootChain{ 1058 BrandID: "my-brand", 1059 Model: "my-model-uc20", 1060 Grade: "dangerous", 1061 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1062 AssetChain: []boot.BootAsset{ 1063 { 1064 Role: "run-mode", 1065 Name: "asset", 1066 Hashes: []string{"asset-hash-1"}, 1067 }, 1068 }, 1069 Kernel: "pc-kernel", 1070 KernelRevision: "500", 1071 KernelCmdlines: []string{ 1072 "snapd_recovery_mode=run static cmdline", 1073 }, 1074 }, 1075 }) 1076 } 1077 1078 func (s *sealSuite) TestRecoveryBootChainsForSystems(c *C) { 1079 for _, tc := range []struct { 1080 desc string 1081 assetsMap boot.BootAssetsMap 1082 recoverySystems []string 1083 undefinedKernel bool 1084 gadgetFilesForSystem map[string][][]string 1085 expectedAssets []boot.BootAsset 1086 expectedKernelRevs []int 1087 expectedBootChainsCount int 1088 // in the order of boot chains 1089 expectedCmdlines [][]string 1090 err string 1091 }{ 1092 { 1093 desc: "transition sequences", 1094 recoverySystems: []string{"20200825"}, 1095 assetsMap: boot.BootAssetsMap{ 1096 "grubx64.efi": []string{"grub-hash-1", "grub-hash-2"}, 1097 "bootx64.efi": []string{"shim-hash-1"}, 1098 }, 1099 expectedAssets: []boot.BootAsset{ 1100 {Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}}, 1101 {Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}}, 1102 }, 1103 expectedKernelRevs: []int{1}, 1104 expectedCmdlines: [][]string{ 1105 {"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"}, 1106 }, 1107 }, 1108 { 1109 desc: "two systems", 1110 recoverySystems: []string{"20200825", "20200831"}, 1111 assetsMap: boot.BootAssetsMap{ 1112 "grubx64.efi": []string{"grub-hash-1", "grub-hash-2"}, 1113 "bootx64.efi": []string{"shim-hash-1"}, 1114 }, 1115 expectedAssets: []boot.BootAsset{ 1116 {Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}}, 1117 {Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}}, 1118 }, 1119 expectedKernelRevs: []int{1, 3}, 1120 expectedCmdlines: [][]string{ 1121 {"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"}, 1122 {"snapd_recovery_mode=recover snapd_recovery_system=20200831 console=ttyS0 console=tty1 panic=-1"}, 1123 }, 1124 }, 1125 { 1126 desc: "non transition sequence", 1127 recoverySystems: []string{"20200825"}, 1128 assetsMap: boot.BootAssetsMap{ 1129 "grubx64.efi": []string{"grub-hash-1"}, 1130 "bootx64.efi": []string{"shim-hash-1"}, 1131 }, 1132 expectedAssets: []boot.BootAsset{ 1133 {Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}}, 1134 {Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1"}}, 1135 }, 1136 expectedKernelRevs: []int{1}, 1137 expectedCmdlines: [][]string{ 1138 {"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"}, 1139 }, 1140 }, 1141 { 1142 desc: "two systems with command lines", 1143 recoverySystems: []string{"20200825", "20200831"}, 1144 assetsMap: boot.BootAssetsMap{ 1145 "grubx64.efi": []string{"grub-hash-1", "grub-hash-2"}, 1146 "bootx64.efi": []string{"shim-hash-1"}, 1147 }, 1148 expectedAssets: []boot.BootAsset{ 1149 {Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}}, 1150 {Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}}, 1151 }, 1152 gadgetFilesForSystem: map[string][][]string{ 1153 "20200825": { 1154 {"cmdline.extra", "extra for 20200825"}, 1155 }, 1156 "20200831": { 1157 // TODO: make it a cmdline.full 1158 {"cmdline.extra", "some-extra-for-20200831"}, 1159 }, 1160 }, 1161 expectedKernelRevs: []int{1, 3}, 1162 expectedCmdlines: [][]string{ 1163 {"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1 extra for 20200825"}, 1164 {"snapd_recovery_mode=recover snapd_recovery_system=20200831 console=ttyS0 console=tty1 panic=-1 some-extra-for-20200831"}, 1165 }, 1166 }, 1167 { 1168 desc: "three systems, one with different model", 1169 recoverySystems: []string{"20200825", "20200831", "off-model"}, 1170 assetsMap: boot.BootAssetsMap{ 1171 "grubx64.efi": []string{"grub-hash-1", "grub-hash-2"}, 1172 "bootx64.efi": []string{"shim-hash-1"}, 1173 }, 1174 expectedAssets: []boot.BootAsset{ 1175 {Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}}, 1176 {Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}}, 1177 }, 1178 expectedKernelRevs: []int{1, 3}, 1179 expectedCmdlines: [][]string{ 1180 {"snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1"}, 1181 {"snapd_recovery_mode=recover snapd_recovery_system=20200831 console=ttyS0 console=tty1 panic=-1"}, 1182 }, 1183 expectedBootChainsCount: 2, 1184 }, 1185 { 1186 desc: "invalid recovery system label", 1187 recoverySystems: []string{"0"}, 1188 err: `cannot read system "0" seed: invalid system seed`, 1189 }, 1190 } { 1191 c.Logf("tc: %q", tc.desc) 1192 rootdir := c.MkDir() 1193 dirs.SetRootDir(rootdir) 1194 defer dirs.SetRootDir("") 1195 1196 model := boottest.MakeMockUC20Model() 1197 1198 // set recovery kernel 1199 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 1200 systemModel := model 1201 kernelRev := 1 1202 switch label { 1203 case "20200825": 1204 // nothing special 1205 case "20200831": 1206 kernelRev = 3 1207 case "off-model": 1208 systemModel = boottest.MakeMockUC20Model(map[string]interface{}{ 1209 "model": "model-mismatch-uc20", 1210 }) 1211 default: 1212 return nil, nil, fmt.Errorf("invalid system seed") 1213 } 1214 return systemModel, []*seed.Snap{mockKernelSeedSnap(c, snap.R(kernelRev)), mockGadgetSeedSnap(c, tc.gadgetFilesForSystem[label])}, nil 1215 }) 1216 defer restore() 1217 1218 grubDir := filepath.Join(rootdir, "run/mnt/ubuntu-seed") 1219 err := createMockGrubCfg(grubDir) 1220 c.Assert(err, IsNil) 1221 1222 bl, err := bootloader.Find(grubDir, &bootloader.Options{Role: bootloader.RoleRecovery}) 1223 c.Assert(err, IsNil) 1224 tbl, ok := bl.(bootloader.TrustedAssetsBootloader) 1225 c.Assert(ok, Equals, true) 1226 1227 modeenv := &boot.Modeenv{ 1228 CurrentTrustedRecoveryBootAssets: tc.assetsMap, 1229 1230 BrandID: model.BrandID(), 1231 Model: model.Model(), 1232 ModelSignKeyID: model.SignKeyID(), 1233 Grade: string(model.Grade()), 1234 } 1235 1236 includeTryModel := false 1237 bc, err := boot.RecoveryBootChainsForSystems(tc.recoverySystems, tbl, modeenv, includeTryModel) 1238 if tc.err == "" { 1239 c.Assert(err, IsNil) 1240 if tc.expectedBootChainsCount == 0 { 1241 // usually there is a boot chain for each recovery system 1242 c.Assert(bc, HasLen, len(tc.recoverySystems)) 1243 } else { 1244 c.Assert(bc, HasLen, tc.expectedBootChainsCount) 1245 } 1246 c.Assert(tc.expectedCmdlines, HasLen, len(bc), Commentf("broken test, expected command lines must be of the same length as recovery systems and recovery boot chains")) 1247 for i, chain := range bc { 1248 c.Assert(chain.AssetChain, DeepEquals, tc.expectedAssets) 1249 c.Assert(chain.Kernel, Equals, "pc-kernel") 1250 expectedKernelRev := tc.expectedKernelRevs[i] 1251 c.Assert(chain.KernelRevision, Equals, fmt.Sprintf("%d", expectedKernelRev)) 1252 c.Assert(chain.KernelBootFile(), DeepEquals, bootloader.BootFile{ 1253 Snap: fmt.Sprintf("/var/lib/snapd/seed/snaps/pc-kernel_%d.snap", expectedKernelRev), 1254 Path: "kernel.efi", 1255 Role: bootloader.RoleRecovery, 1256 }) 1257 c.Assert(chain.KernelCmdlines, DeepEquals, tc.expectedCmdlines[i]) 1258 } 1259 } else { 1260 c.Assert(err, ErrorMatches, tc.err) 1261 } 1262 1263 } 1264 1265 } 1266 1267 func createMockGrubCfg(baseDir string) error { 1268 cfg := filepath.Join(baseDir, "EFI/ubuntu/grub.cfg") 1269 if err := os.MkdirAll(filepath.Dir(cfg), 0755); err != nil { 1270 return err 1271 } 1272 return ioutil.WriteFile(cfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644) 1273 } 1274 1275 func (s *sealSuite) TestSealKeyModelParams(c *C) { 1276 rootdir := c.MkDir() 1277 dirs.SetRootDir(rootdir) 1278 defer dirs.SetRootDir("") 1279 1280 model := boottest.MakeMockUC20Model() 1281 1282 roleToBlName := map[bootloader.Role]string{ 1283 bootloader.RoleRecovery: "grub", 1284 bootloader.RoleRunMode: "grub", 1285 } 1286 // mock asset cache 1287 mockAssetsCache(c, rootdir, "grub", []string{ 1288 "shim-shim-hash", 1289 "loader-loader-hash1", 1290 "loader-loader-hash2", 1291 }) 1292 1293 oldmodel := boottest.MakeMockUC20Model(map[string]interface{}{ 1294 "model": "old-model-uc20", 1295 "timestamp": "2019-10-01T08:00:00+00:00", 1296 }) 1297 1298 // old recovery 1299 oldrc := boot.BootChain{ 1300 BrandID: oldmodel.BrandID(), 1301 Model: oldmodel.Model(), 1302 Grade: oldmodel.Grade(), 1303 ModelSignKeyID: oldmodel.SignKeyID(), 1304 AssetChain: []boot.BootAsset{ 1305 {Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}}, 1306 {Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}}, 1307 }, 1308 KernelCmdlines: []string{"panic=1", "oldrc"}, 1309 } 1310 oldkbf := bootloader.BootFile{Snap: "pc-kernel_1.snap"} 1311 oldrc.SetKernelBootFile(oldkbf) 1312 1313 // recovery 1314 rc1 := boot.BootChain{ 1315 BrandID: model.BrandID(), 1316 Model: model.Model(), 1317 Grade: model.Grade(), 1318 ModelSignKeyID: model.SignKeyID(), 1319 AssetChain: []boot.BootAsset{ 1320 {Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}}, 1321 {Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}}, 1322 }, 1323 KernelCmdlines: []string{"panic=1", "rc1"}, 1324 } 1325 rc1kbf := bootloader.BootFile{Snap: "pc-kernel_10.snap"} 1326 rc1.SetKernelBootFile(rc1kbf) 1327 1328 // run system 1329 runc1 := boot.BootChain{ 1330 BrandID: model.BrandID(), 1331 Model: model.Model(), 1332 Grade: model.Grade(), 1333 ModelSignKeyID: model.SignKeyID(), 1334 AssetChain: []boot.BootAsset{ 1335 {Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}}, 1336 {Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}}, 1337 {Name: "loader", Role: bootloader.RoleRunMode, Hashes: []string{"loader-hash2"}}, 1338 }, 1339 KernelCmdlines: []string{"panic=1", "runc1"}, 1340 } 1341 runc1kbf := bootloader.BootFile{Snap: "pc-kernel_50.snap"} 1342 runc1.SetKernelBootFile(runc1kbf) 1343 1344 pbc := boot.ToPredictableBootChains([]boot.BootChain{rc1, runc1, oldrc}) 1345 1346 shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/shim-shim-hash"), bootloader.RoleRecovery) 1347 loader1 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/loader-loader-hash1"), bootloader.RoleRecovery) 1348 loader2 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/loader-loader-hash2"), bootloader.RoleRunMode) 1349 1350 params, err := boot.SealKeyModelParams(pbc, roleToBlName) 1351 c.Assert(err, IsNil) 1352 c.Check(params, HasLen, 2) 1353 c.Check(params[0].Model.Model(), Equals, model.Model()) 1354 // NB: merging of lists makes panic=1 appear once 1355 c.Check(params[0].KernelCmdlines, DeepEquals, []string{"panic=1", "rc1", "runc1"}) 1356 1357 c.Check(params[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 1358 secboot.NewLoadChain(shim, 1359 secboot.NewLoadChain(loader1, 1360 secboot.NewLoadChain(rc1kbf))), 1361 secboot.NewLoadChain(shim, 1362 secboot.NewLoadChain(loader1, 1363 secboot.NewLoadChain(loader2, 1364 secboot.NewLoadChain(runc1kbf)))), 1365 }) 1366 1367 c.Check(params[1].Model.Model(), Equals, oldmodel.Model()) 1368 c.Check(params[1].KernelCmdlines, DeepEquals, []string{"oldrc", "panic=1"}) 1369 c.Check(params[1].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 1370 secboot.NewLoadChain(shim, 1371 secboot.NewLoadChain(loader1, 1372 secboot.NewLoadChain(oldkbf))), 1373 }) 1374 } 1375 1376 func (s *sealSuite) TestIsResealNeeded(c *C) { 1377 if os.Geteuid() == 0 { 1378 c.Skip("the test cannot be run by the root user") 1379 } 1380 1381 chains := []boot.BootChain{ 1382 { 1383 BrandID: "mybrand", 1384 Model: "foo", 1385 Grade: "signed", 1386 ModelSignKeyID: "my-key-id", 1387 AssetChain: []boot.BootAsset{ 1388 // hashes will be sorted 1389 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"x", "y"}}, 1390 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}}, 1391 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"z", "x"}}, 1392 }, 1393 Kernel: "pc-kernel-other", 1394 KernelRevision: "2345", 1395 KernelCmdlines: []string{`snapd_recovery_mode=run foo`}, 1396 }, { 1397 BrandID: "mybrand", 1398 Model: "foo", 1399 Grade: "dangerous", 1400 ModelSignKeyID: "my-key-id", 1401 AssetChain: []boot.BootAsset{ 1402 // hashes will be sorted 1403 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"y", "x"}}, 1404 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}}, 1405 }, 1406 Kernel: "pc-kernel-recovery", 1407 KernelRevision: "1234", 1408 KernelCmdlines: []string{`snapd_recovery_mode=recover foo`}, 1409 }, 1410 } 1411 1412 pbc := boot.ToPredictableBootChains(chains) 1413 1414 rootdir := c.MkDir() 1415 err := boot.WriteBootChains(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 2) 1416 c.Assert(err, IsNil) 1417 1418 needed, _, err := boot.IsResealNeeded(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false) 1419 c.Assert(err, IsNil) 1420 c.Check(needed, Equals, false) 1421 1422 otherchain := []boot.BootChain{pbc[0]} 1423 needed, cnt, err := boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false) 1424 c.Assert(err, IsNil) 1425 // chains are different 1426 c.Check(needed, Equals, true) 1427 c.Check(cnt, Equals, 3) 1428 1429 // boot-chains does not exist, we cannot compare so advise to reseal 1430 otherRootdir := c.MkDir() 1431 needed, cnt, err = boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(otherRootdir), "boot-chains"), false) 1432 c.Assert(err, IsNil) 1433 c.Check(needed, Equals, true) 1434 c.Check(cnt, Equals, 1) 1435 1436 // exists but cannot be read 1437 c.Assert(os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0000), IsNil) 1438 defer os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0755) 1439 needed, _, err = boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false) 1440 c.Assert(err, ErrorMatches, "cannot open existing boot chains data file: open .*/boot-chains: permission denied") 1441 c.Check(needed, Equals, false) 1442 1443 // unrevisioned kernel chain 1444 unrevchain := []boot.BootChain{pbc[0], pbc[1]} 1445 unrevchain[1].KernelRevision = "" 1446 // write on disk 1447 bootChainsFile := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains") 1448 err = boot.WriteBootChains(unrevchain, bootChainsFile, 2) 1449 c.Assert(err, IsNil) 1450 1451 needed, cnt, err = boot.IsResealNeeded(pbc, bootChainsFile, false) 1452 c.Assert(err, IsNil) 1453 c.Check(needed, Equals, true) 1454 c.Check(cnt, Equals, 3) 1455 1456 // cases falling back to expectReseal 1457 needed, _, err = boot.IsResealNeeded(unrevchain, bootChainsFile, false) 1458 c.Assert(err, IsNil) 1459 c.Check(needed, Equals, false) 1460 1461 needed, cnt, err = boot.IsResealNeeded(unrevchain, bootChainsFile, true) 1462 c.Assert(err, IsNil) 1463 c.Check(needed, Equals, true) 1464 c.Check(cnt, Equals, 3) 1465 } 1466 1467 func (s *sealSuite) TestSealToModeenvWithFdeHookHappy(c *C) { 1468 rootdir := c.MkDir() 1469 dirs.SetRootDir(rootdir) 1470 defer dirs.SetRootDir("") 1471 1472 restore := boot.MockHasFDESetupHook(func() (bool, error) { 1473 return true, nil 1474 }) 1475 defer restore() 1476 1477 model := boottest.MakeMockUC20Model() 1478 1479 n := 0 1480 var runFDESetupHookReqs []*fde.SetupRequest 1481 restore = boot.MockRunFDESetupHook(func(req *fde.SetupRequest) ([]byte, error) { 1482 n++ 1483 runFDESetupHookReqs = append(runFDESetupHookReqs, req) 1484 1485 key := []byte(fmt.Sprintf("key-%v", strconv.Itoa(n))) 1486 return key, nil 1487 }) 1488 defer restore() 1489 keyToSave := make(map[string][]byte) 1490 restore = boot.MockSecbootSealKeysWithFDESetupHook(func(runHook fde.RunSetupHookFunc, skrs []secboot.SealKeyRequest, params *secboot.SealKeysWithFDESetupHookParams) error { 1491 c.Check(params.Model.Model(), Equals, model.Model()) 1492 c.Check(params.AuxKeyFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "aux-key")) 1493 for _, skr := range skrs { 1494 out, err := runHook(&fde.SetupRequest{ 1495 Key: skr.Key, 1496 KeyName: skr.KeyName, 1497 }) 1498 c.Assert(err, IsNil) 1499 keyToSave[skr.KeyFile] = out 1500 } 1501 return nil 1502 }) 1503 defer restore() 1504 1505 modeenv := &boot.Modeenv{ 1506 RecoverySystem: "20200825", 1507 Model: model.Model(), 1508 BrandID: model.BrandID(), 1509 Grade: string(model.Grade()), 1510 ModelSignKeyID: model.SignKeyID(), 1511 } 1512 key := secboot.EncryptionKey{1, 2, 3, 4} 1513 saveKey := secboot.EncryptionKey{5, 6, 7, 8} 1514 1515 err := boot.SealKeyToModeenv(key, saveKey, model, modeenv) 1516 c.Assert(err, IsNil) 1517 // check that runFDESetupHook was called the expected way 1518 c.Check(runFDESetupHookReqs, DeepEquals, []*fde.SetupRequest{ 1519 {Key: key, KeyName: "ubuntu-data"}, 1520 {Key: key, KeyName: "ubuntu-data"}, 1521 {Key: saveKey, KeyName: "ubuntu-save"}, 1522 }) 1523 // check that the sealed keys got written to the expected places 1524 for i, p := range []string{ 1525 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 1526 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 1527 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 1528 } { 1529 // Check for a valid platform handle, encrypted payload (base64) 1530 mockedSealedKey := []byte(fmt.Sprintf("key-%v", strconv.Itoa(i+1))) 1531 c.Check(keyToSave[p], DeepEquals, mockedSealedKey) 1532 } 1533 1534 marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys") 1535 c.Check(marker, testutil.FileEquals, "fde-setup-hook") 1536 } 1537 1538 func (s *sealSuite) TestSealToModeenvWithFdeHookSad(c *C) { 1539 rootdir := c.MkDir() 1540 dirs.SetRootDir(rootdir) 1541 defer dirs.SetRootDir("") 1542 1543 restore := boot.MockHasFDESetupHook(func() (bool, error) { 1544 return true, nil 1545 }) 1546 defer restore() 1547 1548 restore = boot.MockSecbootSealKeysWithFDESetupHook(func(fde.RunSetupHookFunc, []secboot.SealKeyRequest, *secboot.SealKeysWithFDESetupHookParams) error { 1549 return fmt.Errorf("hook failed") 1550 }) 1551 defer restore() 1552 1553 modeenv := &boot.Modeenv{ 1554 RecoverySystem: "20200825", 1555 } 1556 key := secboot.EncryptionKey{1, 2, 3, 4} 1557 saveKey := secboot.EncryptionKey{5, 6, 7, 8} 1558 1559 model := boottest.MakeMockUC20Model() 1560 err := boot.SealKeyToModeenv(key, saveKey, model, modeenv) 1561 c.Assert(err, ErrorMatches, "hook failed") 1562 marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys") 1563 c.Check(marker, testutil.FileAbsent) 1564 } 1565 1566 func (s *sealSuite) TestResealKeyToModeenvWithFdeHookCalled(c *C) { 1567 rootdir := c.MkDir() 1568 dirs.SetRootDir(rootdir) 1569 defer dirs.SetRootDir("") 1570 1571 resealKeyToModeenvUsingFDESetupHookCalled := 0 1572 restore := boot.MockResealKeyToModeenvUsingFDESetupHook(func(string, *boot.Modeenv, bool) error { 1573 resealKeyToModeenvUsingFDESetupHookCalled++ 1574 return nil 1575 }) 1576 defer restore() 1577 1578 // TODO: this simulates that the hook is not available yet 1579 // because of e.g. seeding. Longer term there will be 1580 // more, see TODO in resealKeyToModeenvUsingFDESetupHookImpl 1581 restore = boot.MockHasFDESetupHook(func() (bool, error) { 1582 return false, fmt.Errorf("hook not available yet because e.g. seeding") 1583 }) 1584 defer restore() 1585 1586 marker := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys") 1587 err := os.MkdirAll(filepath.Dir(marker), 0755) 1588 c.Assert(err, IsNil) 1589 err = ioutil.WriteFile(marker, []byte("fde-setup-hook"), 0644) 1590 c.Assert(err, IsNil) 1591 1592 model := boottest.MakeMockUC20Model() 1593 modeenv := &boot.Modeenv{ 1594 RecoverySystem: "20200825", 1595 Model: model.Model(), 1596 BrandID: model.BrandID(), 1597 Grade: string(model.Grade()), 1598 ModelSignKeyID: model.SignKeyID(), 1599 } 1600 expectReseal := false 1601 err = boot.ResealKeyToModeenv(rootdir, modeenv, expectReseal) 1602 c.Assert(err, IsNil) 1603 c.Check(resealKeyToModeenvUsingFDESetupHookCalled, Equals, 1) 1604 } 1605 1606 func (s *sealSuite) TestResealKeyToModeenvWithFdeHookVerySad(c *C) { 1607 rootdir := c.MkDir() 1608 dirs.SetRootDir(rootdir) 1609 defer dirs.SetRootDir("") 1610 1611 resealKeyToModeenvUsingFDESetupHookCalled := 0 1612 restore := boot.MockResealKeyToModeenvUsingFDESetupHook(func(string, *boot.Modeenv, bool) error { 1613 resealKeyToModeenvUsingFDESetupHookCalled++ 1614 return fmt.Errorf("fde setup hook failed") 1615 }) 1616 defer restore() 1617 1618 marker := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys") 1619 err := os.MkdirAll(filepath.Dir(marker), 0755) 1620 c.Assert(err, IsNil) 1621 err = ioutil.WriteFile(marker, []byte("fde-setup-hook"), 0644) 1622 c.Assert(err, IsNil) 1623 1624 model := boottest.MakeMockUC20Model() 1625 modeenv := &boot.Modeenv{ 1626 RecoverySystem: "20200825", 1627 Model: model.Model(), 1628 BrandID: model.BrandID(), 1629 Grade: string(model.Grade()), 1630 ModelSignKeyID: model.SignKeyID(), 1631 } 1632 expectReseal := false 1633 err = boot.ResealKeyToModeenv(rootdir, modeenv, expectReseal) 1634 c.Assert(err, ErrorMatches, "fde setup hook failed") 1635 c.Check(resealKeyToModeenvUsingFDESetupHookCalled, Equals, 1) 1636 } 1637 1638 func (s *sealSuite) TestResealKeyToModeenvWithTryModel(c *C) { 1639 rootdir := c.MkDir() 1640 dirs.SetRootDir(rootdir) 1641 defer dirs.SetRootDir("") 1642 1643 c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil) 1644 err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644) 1645 c.Assert(err, IsNil) 1646 1647 err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed")) 1648 c.Assert(err, IsNil) 1649 1650 err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot")) 1651 c.Assert(err, IsNil) 1652 1653 model := boottest.MakeMockUC20Model() 1654 // a try model which would normally only appear during remodel 1655 tryModel := boottest.MakeMockUC20Model(map[string]interface{}{ 1656 "model": "try-my-model-uc20", 1657 "grade": "secured", 1658 }) 1659 1660 modeenv := &boot.Modeenv{ 1661 // recovery system set up like during a remodel, right before a 1662 // set-device is called 1663 CurrentRecoverySystems: []string{"20200825", "1234", "off-model"}, 1664 GoodRecoverySystems: []string{"20200825", "1234"}, 1665 1666 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 1667 "grubx64.efi": []string{"grub-hash"}, 1668 "bootx64.efi": []string{"shim-hash"}, 1669 }, 1670 1671 CurrentTrustedBootAssets: boot.BootAssetsMap{ 1672 "grubx64.efi": []string{"run-grub-hash"}, 1673 }, 1674 1675 CurrentKernels: []string{"pc-kernel_500.snap"}, 1676 1677 CurrentKernelCommandLines: boot.BootCommandLines{ 1678 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 1679 }, 1680 // the current model 1681 Model: model.Model(), 1682 BrandID: model.BrandID(), 1683 Grade: string(model.Grade()), 1684 ModelSignKeyID: model.SignKeyID(), 1685 // the try model 1686 TryModel: tryModel.Model(), 1687 TryBrandID: tryModel.BrandID(), 1688 TryGrade: string(tryModel.Grade()), 1689 TryModelSignKeyID: tryModel.SignKeyID(), 1690 } 1691 1692 // mock asset cache 1693 mockAssetsCache(c, rootdir, "grub", []string{ 1694 "bootx64.efi-shim-hash", 1695 "grubx64.efi-grub-hash", 1696 "grubx64.efi-run-grub-hash", 1697 }) 1698 1699 // set a mock recovery kernel 1700 readSystemEssentialCalls := 0 1701 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 1702 readSystemEssentialCalls++ 1703 kernelRev := 1 1704 systemModel := model 1705 if label == "1234" { 1706 // recovery system for new model 1707 kernelRev = 999 1708 systemModel = tryModel 1709 } 1710 if label == "off-model" { 1711 // a model that matches neither current not try models 1712 systemModel = boottest.MakeMockUC20Model(map[string]interface{}{ 1713 "model": "different-model-uc20", 1714 "grade": "secured", 1715 }) 1716 } 1717 return systemModel, []*seed.Snap{mockKernelSeedSnap(c, snap.R(kernelRev)), mockGadgetSeedSnap(c, nil)}, nil 1718 }) 1719 defer restore() 1720 1721 // set mock key resealing 1722 resealKeysCalls := 0 1723 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 1724 c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key")) 1725 c.Logf("got:") 1726 for _, mp := range params.ModelParams { 1727 c.Logf("model: %v", mp.Model.Model()) 1728 for _, ch := range mp.EFILoadChains { 1729 printChain(c, ch, "-") 1730 } 1731 } 1732 1733 resealKeysCalls++ 1734 1735 switch resealKeysCalls { 1736 case 1: // run key 1737 c.Assert(params.KeyFiles, DeepEquals, []string{ 1738 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 1739 }) 1740 // 2 models, one current and one try model 1741 c.Assert(params.ModelParams, HasLen, 2) 1742 // shared parameters 1743 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 1744 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 1745 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 1746 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 1747 }) 1748 // 2 load chains (bootloader + run kernel, bootloader + recovery kernel) 1749 c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 2) 1750 1751 c.Assert(params.ModelParams[1].Model.Model(), Equals, "try-my-model-uc20") 1752 c.Assert(params.ModelParams[1].KernelCmdlines, DeepEquals, []string{ 1753 "snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1", 1754 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 1755 }) 1756 // 2 load chains (bootloader + run kernel, bootloader + recovery kernel) 1757 c.Assert(params.ModelParams[1].EFILoadChains, HasLen, 2) 1758 case 2: // recovery keys 1759 c.Assert(params.KeyFiles, DeepEquals, []string{ 1760 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 1761 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 1762 }) 1763 // only the current model 1764 c.Assert(params.ModelParams, HasLen, 1) 1765 // shared parameters 1766 c.Assert(params.ModelParams[0].Model.Model(), Equals, "my-model-uc20") 1767 for _, mp := range params.ModelParams { 1768 c.Assert(mp.KernelCmdlines, DeepEquals, []string{ 1769 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 1770 }) 1771 // load chains 1772 c.Assert(mp.EFILoadChains, HasLen, 1) 1773 } 1774 default: 1775 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 1776 } 1777 1778 // recovery parameters 1779 shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash"), bootloader.RoleRecovery) 1780 grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash"), bootloader.RoleRecovery) 1781 kernelOldRecovery := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 1782 // kernel from a tried recovery system 1783 kernelNewRecovery := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_999.snap", "kernel.efi", bootloader.RoleRecovery) 1784 // run mode parameters 1785 runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash"), bootloader.RoleRunMode) 1786 runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode) 1787 1788 // verify the load chains, which are identical for both models 1789 switch resealKeysCalls { 1790 case 1: // run load chain for 2 models, current and a try model 1791 c.Assert(params.ModelParams, HasLen, 2) 1792 // each load chain has either the run kernel (shared for 1793 // both), or the kernel of the respective recovery 1794 // system 1795 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 1796 secboot.NewLoadChain(shim, 1797 secboot.NewLoadChain(grub, 1798 secboot.NewLoadChain(kernelOldRecovery), 1799 )), 1800 secboot.NewLoadChain(shim, 1801 secboot.NewLoadChain(grub, 1802 secboot.NewLoadChain(runGrub, 1803 secboot.NewLoadChain(runKernel)), 1804 )), 1805 }) 1806 c.Assert(params.ModelParams[1].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 1807 secboot.NewLoadChain(shim, 1808 secboot.NewLoadChain(grub, 1809 secboot.NewLoadChain(kernelNewRecovery), 1810 )), 1811 secboot.NewLoadChain(shim, 1812 secboot.NewLoadChain(grub, 1813 secboot.NewLoadChain(runGrub, 1814 secboot.NewLoadChain(runKernel)), 1815 )), 1816 }) 1817 case 2: // recovery load chains, only for the current model 1818 c.Assert(params.ModelParams, HasLen, 1) 1819 // load chain with a kernel from a recovery system that 1820 // matches the current model only 1821 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 1822 secboot.NewLoadChain(shim, 1823 secboot.NewLoadChain(grub, 1824 secboot.NewLoadChain(kernelOldRecovery), 1825 )), 1826 }) 1827 } 1828 1829 return nil 1830 }) 1831 defer restore() 1832 1833 // here we don't have unasserted kernels so just set 1834 // expectReseal to false as it doesn't matter; 1835 // the behavior with unasserted kernel is tested in 1836 // boot_test.go specific tests 1837 const expectReseal = false 1838 err = boot.ResealKeyToModeenv(rootdir, modeenv, expectReseal) 1839 c.Assert(err, IsNil) 1840 c.Assert(resealKeysCalls, Equals, 2) 1841 1842 // verify the boot chains data file for run key 1843 1844 recoveryAssetChain := []boot.BootAsset{{ 1845 Role: "recovery", 1846 Name: "bootx64.efi", 1847 Hashes: []string{"shim-hash"}, 1848 }, { 1849 Role: "recovery", 1850 Name: "grubx64.efi", 1851 Hashes: []string{"grub-hash"}, 1852 }} 1853 runAssetChain := []boot.BootAsset{{ 1854 Role: "recovery", 1855 Name: "bootx64.efi", 1856 Hashes: []string{"shim-hash"}, 1857 }, { 1858 Role: "recovery", 1859 Name: "grubx64.efi", 1860 Hashes: []string{"grub-hash"}, 1861 }, { 1862 Role: "run-mode", 1863 Name: "grubx64.efi", 1864 Hashes: []string{"run-grub-hash"}, 1865 }} 1866 runPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains")) 1867 c.Assert(err, IsNil) 1868 c.Assert(cnt, Equals, 1) 1869 c.Check(runPbc, DeepEquals, boot.PredictableBootChains{ 1870 // the current model 1871 boot.BootChain{ 1872 BrandID: "my-brand", 1873 Model: "my-model-uc20", 1874 Grade: "dangerous", 1875 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1876 AssetChain: recoveryAssetChain, 1877 Kernel: "pc-kernel", 1878 KernelRevision: "1", 1879 KernelCmdlines: []string{ 1880 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 1881 }, 1882 }, 1883 boot.BootChain{ 1884 BrandID: "my-brand", 1885 Model: "my-model-uc20", 1886 Grade: "dangerous", 1887 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1888 AssetChain: runAssetChain, 1889 Kernel: "pc-kernel", 1890 KernelRevision: "500", 1891 KernelCmdlines: []string{ 1892 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 1893 }, 1894 }, 1895 // the try model 1896 boot.BootChain{ 1897 BrandID: "my-brand", 1898 Model: "try-my-model-uc20", 1899 Grade: "secured", 1900 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1901 AssetChain: recoveryAssetChain, 1902 Kernel: "pc-kernel", 1903 KernelRevision: "999", 1904 KernelCmdlines: []string{ 1905 "snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1", 1906 }, 1907 }, 1908 boot.BootChain{ 1909 BrandID: "my-brand", 1910 Model: "try-my-model-uc20", 1911 Grade: "secured", 1912 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1913 AssetChain: runAssetChain, 1914 Kernel: "pc-kernel", 1915 KernelRevision: "500", 1916 KernelCmdlines: []string{ 1917 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 1918 }, 1919 }, 1920 }) 1921 // recovery boot chains 1922 recoveryPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains")) 1923 c.Assert(err, IsNil) 1924 c.Assert(cnt, Equals, 1) 1925 c.Check(recoveryPbc, DeepEquals, boot.PredictableBootChains{ 1926 // recovery keys are sealed to current model only 1927 boot.BootChain{ 1928 BrandID: "my-brand", 1929 Model: "my-model-uc20", 1930 Grade: "dangerous", 1931 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1932 AssetChain: recoveryAssetChain, 1933 Kernel: "pc-kernel", 1934 KernelRevision: "1", 1935 KernelCmdlines: []string{ 1936 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 1937 }, 1938 }, 1939 }) 1940 }