github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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/osutil" 39 "github.com/snapcore/snapd/secboot" 40 "github.com/snapcore/snapd/seed" 41 "github.com/snapcore/snapd/snap" 42 "github.com/snapcore/snapd/testutil" 43 "github.com/snapcore/snapd/timings" 44 ) 45 46 type sealSuite struct { 47 testutil.BaseTest 48 } 49 50 var _ = Suite(&sealSuite{}) 51 52 func (s *sealSuite) SetUpTest(c *C) { 53 s.BaseTest.SetUpTest(c) 54 55 rootdir := c.MkDir() 56 dirs.SetRootDir(rootdir) 57 s.AddCleanup(func() { dirs.SetRootDir("/") }) 58 } 59 60 func (s *sealSuite) TestSealKeyToModeenv(c *C) { 61 for _, tc := range []struct { 62 sealErr error 63 err string 64 }{ 65 {sealErr: nil, err: ""}, 66 {sealErr: errors.New("seal error"), err: "cannot seal the encryption keys: seal error"}, 67 } { 68 rootdir := c.MkDir() 69 dirs.SetRootDir(rootdir) 70 defer dirs.SetRootDir("") 71 72 err := createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed")) 73 c.Assert(err, IsNil) 74 75 err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot")) 76 c.Assert(err, IsNil) 77 78 modeenv := &boot.Modeenv{ 79 RecoverySystem: "20200825", 80 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 81 "grubx64.efi": []string{"grub-hash-1"}, 82 "bootx64.efi": []string{"shim-hash-1"}, 83 }, 84 85 CurrentTrustedBootAssets: boot.BootAssetsMap{ 86 "grubx64.efi": []string{"run-grub-hash-1"}, 87 }, 88 89 CurrentKernels: []string{"pc-kernel_500.snap"}, 90 91 CurrentKernelCommandLines: boot.BootCommandLines{ 92 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 93 }, 94 } 95 96 // mock asset cache 97 mockAssetsCache(c, rootdir, "grub", []string{ 98 "bootx64.efi-shim-hash-1", 99 "grubx64.efi-grub-hash-1", 100 "grubx64.efi-run-grub-hash-1", 101 }) 102 103 // set encryption key 104 myKey := secboot.EncryptionKey{} 105 myKey2 := secboot.EncryptionKey{} 106 for i := range myKey { 107 myKey[i] = byte(i) 108 myKey2[i] = byte(128 + i) 109 } 110 111 model := boottest.MakeMockUC20Model() 112 113 // set a mock recovery kernel 114 readSystemEssentialCalls := 0 115 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 116 readSystemEssentialCalls++ 117 kernelSnap := &seed.Snap{ 118 Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap", 119 SideInfo: &snap.SideInfo{ 120 RealName: "pc-kernel", 121 Revision: snap.Revision{N: 1}, 122 }, 123 } 124 return model, []*seed.Snap{kernelSnap}, nil 125 }) 126 defer restore() 127 128 // set mock key sealing 129 sealKeysCalls := 0 130 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { 131 sealKeysCalls++ 132 switch sealKeysCalls { 133 case 1: 134 // the run object seals only the ubuntu-data key 135 c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-policy-auth-key")) 136 c.Check(params.TPMLockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth")) 137 138 dataKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key") 139 c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}}) 140 case 2: 141 // the fallback object seals the ubuntu-data and the ubuntu-save keys 142 c.Check(params.TPMPolicyAuthKeyFile, Equals, "") 143 c.Check(params.TPMLockoutAuthFile, Equals, "") 144 145 dataKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key") 146 saveKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key") 147 c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}, {Key: myKey2, KeyName: "ubuntu-save", KeyFile: saveKeyFile}}) 148 default: 149 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 150 } 151 c.Assert(params.ModelParams, HasLen, 1) 152 for _, d := range []string{boot.InitramfsSeedEncryptionKeyDir, boot.InstallHostFDEDataDir} { 153 ex, isdir, _ := osutil.DirExists(d) 154 c.Check(ex && isdir, Equals, true, Commentf("location %q does not exist or is not a directory", d)) 155 } 156 157 shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash-1"), bootloader.RoleRecovery) 158 grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash-1"), bootloader.RoleRecovery) 159 runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash-1"), bootloader.RoleRunMode) 160 kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 161 runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode) 162 163 switch sealKeysCalls { 164 case 1: 165 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 166 secboot.NewLoadChain(shim, 167 secboot.NewLoadChain(grub, 168 secboot.NewLoadChain(kernel))), 169 secboot.NewLoadChain(shim, 170 secboot.NewLoadChain(grub, 171 secboot.NewLoadChain(runGrub, 172 secboot.NewLoadChain(runKernel)))), 173 }) 174 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 175 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 176 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 177 }) 178 case 2: 179 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 180 secboot.NewLoadChain(shim, 181 secboot.NewLoadChain(grub, 182 secboot.NewLoadChain(kernel))), 183 }) 184 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 185 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 186 }) 187 default: 188 c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) 189 } 190 c.Assert(params.ModelParams[0].Model.DisplayName(), Equals, "My Model") 191 192 return tc.sealErr 193 }) 194 defer restore() 195 196 err = boot.SealKeyToModeenv(myKey, myKey2, model, modeenv) 197 if tc.sealErr != nil { 198 c.Assert(sealKeysCalls, Equals, 1) 199 } else { 200 c.Assert(sealKeysCalls, Equals, 2) 201 } 202 if tc.err == "" { 203 c.Assert(err, IsNil) 204 } else { 205 c.Assert(err, ErrorMatches, tc.err) 206 continue 207 } 208 209 // verify the boot chains data file 210 pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "boot-chains")) 211 c.Assert(err, IsNil) 212 c.Check(cnt, Equals, 0) 213 c.Check(pbc, DeepEquals, boot.PredictableBootChains{ 214 boot.BootChain{ 215 BrandID: "my-brand", 216 Model: "my-model-uc20", 217 Grade: "dangerous", 218 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 219 AssetChain: []boot.BootAsset{ 220 { 221 Role: "recovery", 222 Name: "bootx64.efi", 223 Hashes: []string{"shim-hash-1"}, 224 }, 225 { 226 Role: "recovery", 227 Name: "grubx64.efi", 228 Hashes: []string{"grub-hash-1"}, 229 }, 230 }, 231 Kernel: "pc-kernel", 232 KernelRevision: "1", 233 KernelCmdlines: []string{ 234 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 235 }, 236 }, 237 boot.BootChain{ 238 BrandID: "my-brand", 239 Model: "my-model-uc20", 240 Grade: "dangerous", 241 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 242 AssetChain: []boot.BootAsset{ 243 { 244 Role: "recovery", 245 Name: "bootx64.efi", 246 Hashes: []string{"shim-hash-1"}, 247 }, 248 { 249 Role: "recovery", 250 Name: "grubx64.efi", 251 Hashes: []string{"grub-hash-1"}, 252 }, 253 { 254 Role: "run-mode", 255 Name: "grubx64.efi", 256 Hashes: []string{"run-grub-hash-1"}, 257 }, 258 }, 259 Kernel: "pc-kernel", 260 KernelRevision: "500", 261 KernelCmdlines: []string{ 262 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 263 }, 264 }, 265 }) 266 267 // verify the recovery boot chains 268 pbc, cnt, err = boot.ReadBootChains(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "recovery-boot-chains")) 269 c.Assert(err, IsNil) 270 c.Check(cnt, Equals, 0) 271 c.Check(pbc, DeepEquals, boot.PredictableBootChains{ 272 boot.BootChain{ 273 BrandID: "my-brand", 274 Model: "my-model-uc20", 275 Grade: "dangerous", 276 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 277 AssetChain: []boot.BootAsset{ 278 { 279 Role: "recovery", 280 Name: "bootx64.efi", 281 Hashes: []string{"shim-hash-1"}, 282 }, 283 { 284 Role: "recovery", 285 Name: "grubx64.efi", 286 Hashes: []string{"grub-hash-1"}, 287 }, 288 }, 289 Kernel: "pc-kernel", 290 KernelRevision: "1", 291 KernelCmdlines: []string{ 292 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 293 }, 294 }, 295 }) 296 297 // marker 298 marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys") 299 c.Check(marker, testutil.FileEquals, "tpm") 300 } 301 } 302 303 // TODO:UC20: also test fallback reseal 304 func (s *sealSuite) TestResealKeyToModeenvWithSystemFallback(c *C) { 305 var prevPbc boot.PredictableBootChains 306 307 for _, tc := range []struct { 308 sealedKeys bool 309 prevPbc bool 310 resealErr error 311 err string 312 }{ 313 {sealedKeys: false, resealErr: nil, err: ""}, 314 {sealedKeys: true, resealErr: nil, err: ""}, 315 {sealedKeys: true, resealErr: errors.New("reseal error"), err: "cannot reseal the encryption key: reseal error"}, 316 {prevPbc: true, sealedKeys: true, resealErr: nil, err: ""}, 317 } { 318 rootdir := c.MkDir() 319 dirs.SetRootDir(rootdir) 320 defer dirs.SetRootDir("") 321 322 if tc.sealedKeys { 323 c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil) 324 err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644) 325 c.Assert(err, IsNil) 326 327 } 328 329 err := createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed")) 330 c.Assert(err, IsNil) 331 332 err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot")) 333 c.Assert(err, IsNil) 334 335 modeenv := &boot.Modeenv{ 336 CurrentRecoverySystems: []string{"20200825"}, 337 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 338 "grubx64.efi": []string{"grub-hash-1"}, 339 "bootx64.efi": []string{"shim-hash-1", "shim-hash-2"}, 340 }, 341 342 CurrentTrustedBootAssets: boot.BootAssetsMap{ 343 "grubx64.efi": []string{"run-grub-hash-1", "run-grub-hash-2"}, 344 }, 345 346 CurrentKernels: []string{"pc-kernel_500.snap", "pc-kernel_600.snap"}, 347 348 CurrentKernelCommandLines: boot.BootCommandLines{ 349 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 350 }, 351 } 352 353 if tc.prevPbc { 354 err := boot.WriteBootChains(prevPbc, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 9) 355 c.Assert(err, IsNil) 356 } 357 358 // mock asset cache 359 mockAssetsCache(c, rootdir, "grub", []string{ 360 "bootx64.efi-shim-hash-1", 361 "bootx64.efi-shim-hash-2", 362 "grubx64.efi-grub-hash-1", 363 "grubx64.efi-run-grub-hash-1", 364 "grubx64.efi-run-grub-hash-2", 365 }) 366 367 model := boottest.MakeMockUC20Model() 368 369 // set a mock recovery kernel 370 readSystemEssentialCalls := 0 371 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 372 readSystemEssentialCalls++ 373 kernelSnap := &seed.Snap{ 374 Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap", 375 SideInfo: &snap.SideInfo{ 376 RealName: "pc-kernel", 377 Revision: snap.Revision{N: 1}, 378 }, 379 } 380 return model, []*seed.Snap{kernelSnap}, nil 381 }) 382 defer restore() 383 384 // set mock key resealing 385 resealKeysCalls := 0 386 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 387 c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key")) 388 389 resealKeysCalls++ 390 c.Assert(params.ModelParams, HasLen, 1) 391 392 // shared parameters 393 c.Assert(params.ModelParams[0].Model.DisplayName(), Equals, "My Model") 394 switch resealKeysCalls { 395 case 1: 396 c.Assert(params.KeyFiles, DeepEquals, []string{ 397 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 398 }) 399 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 400 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 401 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 402 }) 403 // load chains 404 c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 6) 405 case 2: 406 c.Assert(params.KeyFiles, DeepEquals, []string{ 407 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 408 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 409 }) 410 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 411 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 412 }) 413 // load chains 414 c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 2) 415 default: 416 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 417 } 418 419 // recovery parameters 420 shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash-1"), bootloader.RoleRecovery) 421 shim2 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash-2"), bootloader.RoleRecovery) 422 grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash-1"), bootloader.RoleRecovery) 423 kernel := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 424 425 c.Assert(params.ModelParams[0].EFILoadChains[:2], DeepEquals, []*secboot.LoadChain{ 426 secboot.NewLoadChain(shim, 427 secboot.NewLoadChain(grub, 428 secboot.NewLoadChain(kernel))), 429 secboot.NewLoadChain(shim2, 430 secboot.NewLoadChain(grub, 431 secboot.NewLoadChain(kernel))), 432 }) 433 434 // run mode parameters 435 runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash-1"), bootloader.RoleRunMode) 436 runGrub2 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash-2"), bootloader.RoleRunMode) 437 runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode) 438 runKernel2 := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_600.snap"), "kernel.efi", bootloader.RoleRunMode) 439 440 switch resealKeysCalls { 441 case 1: 442 c.Assert(params.ModelParams[0].EFILoadChains[2:4], DeepEquals, []*secboot.LoadChain{ 443 secboot.NewLoadChain(shim, 444 secboot.NewLoadChain(grub, 445 secboot.NewLoadChain(runGrub, 446 secboot.NewLoadChain(runKernel)), 447 secboot.NewLoadChain(runGrub2, 448 secboot.NewLoadChain(runKernel)), 449 )), 450 secboot.NewLoadChain(shim2, 451 secboot.NewLoadChain(grub, 452 secboot.NewLoadChain(runGrub, 453 secboot.NewLoadChain(runKernel)), 454 secboot.NewLoadChain(runGrub2, 455 secboot.NewLoadChain(runKernel)), 456 )), 457 }) 458 459 c.Assert(params.ModelParams[0].EFILoadChains[4:], DeepEquals, []*secboot.LoadChain{ 460 secboot.NewLoadChain(shim, 461 secboot.NewLoadChain(grub, 462 secboot.NewLoadChain(runGrub, 463 secboot.NewLoadChain(runKernel2)), 464 secboot.NewLoadChain(runGrub2, 465 secboot.NewLoadChain(runKernel2)), 466 )), 467 secboot.NewLoadChain(shim2, 468 secboot.NewLoadChain(grub, 469 secboot.NewLoadChain(runGrub, 470 secboot.NewLoadChain(runKernel2)), 471 secboot.NewLoadChain(runGrub2, 472 secboot.NewLoadChain(runKernel2)), 473 )), 474 }) 475 } 476 477 return tc.resealErr 478 }) 479 defer restore() 480 481 // here we don't have unasserted kernels so just set 482 // expectReseal to false as it doesn't matter; 483 // the behavior with unasserted kernel is tested in 484 // boot_test.go specific tests 485 const expectReseal = false 486 err = boot.ResealKeyToModeenv(rootdir, model, modeenv, expectReseal) 487 if !tc.sealedKeys || tc.prevPbc { 488 // did nothing 489 c.Assert(err, IsNil) 490 c.Assert(resealKeysCalls, Equals, 0) 491 continue 492 } 493 if tc.resealErr != nil { 494 c.Assert(resealKeysCalls, Equals, 1) 495 } else { 496 c.Assert(resealKeysCalls, Equals, 2) 497 } 498 if tc.err == "" { 499 c.Assert(err, IsNil) 500 } else { 501 c.Assert(err, ErrorMatches, tc.err) 502 continue 503 } 504 505 // verify the boot chains data file 506 pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains")) 507 c.Assert(err, IsNil) 508 if tc.prevPbc { 509 c.Assert(cnt, Equals, 10) 510 } else { 511 c.Assert(cnt, Equals, 1) 512 } 513 c.Check(pbc, DeepEquals, boot.PredictableBootChains{ 514 boot.BootChain{ 515 BrandID: "my-brand", 516 Model: "my-model-uc20", 517 Grade: "dangerous", 518 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 519 AssetChain: []boot.BootAsset{ 520 { 521 Role: "recovery", 522 Name: "bootx64.efi", 523 Hashes: []string{"shim-hash-1", "shim-hash-2"}, 524 }, 525 { 526 Role: "recovery", 527 Name: "grubx64.efi", 528 Hashes: []string{"grub-hash-1"}, 529 }, 530 }, 531 Kernel: "pc-kernel", 532 KernelRevision: "1", 533 KernelCmdlines: []string{ 534 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 535 }, 536 }, 537 boot.BootChain{ 538 BrandID: "my-brand", 539 Model: "my-model-uc20", 540 Grade: "dangerous", 541 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 542 AssetChain: []boot.BootAsset{ 543 { 544 Role: "recovery", 545 Name: "bootx64.efi", 546 Hashes: []string{"shim-hash-1", "shim-hash-2"}, 547 }, 548 { 549 Role: "recovery", 550 Name: "grubx64.efi", 551 Hashes: []string{"grub-hash-1"}, 552 }, 553 { 554 Role: "run-mode", 555 Name: "grubx64.efi", 556 Hashes: []string{"run-grub-hash-1", "run-grub-hash-2"}, 557 }, 558 }, 559 Kernel: "pc-kernel", 560 KernelRevision: "500", 561 KernelCmdlines: []string{ 562 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 563 }, 564 }, 565 boot.BootChain{ 566 BrandID: "my-brand", 567 Model: "my-model-uc20", 568 Grade: "dangerous", 569 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 570 AssetChain: []boot.BootAsset{ 571 { 572 Role: "recovery", 573 Name: "bootx64.efi", 574 Hashes: []string{"shim-hash-1", "shim-hash-2"}, 575 }, 576 { 577 Role: "recovery", 578 Name: "grubx64.efi", 579 Hashes: []string{"grub-hash-1"}, 580 }, 581 { 582 Role: "run-mode", 583 Name: "grubx64.efi", 584 Hashes: []string{"run-grub-hash-1", "run-grub-hash-2"}, 585 }, 586 }, 587 Kernel: "pc-kernel", 588 KernelRevision: "600", 589 KernelCmdlines: []string{ 590 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 591 }, 592 }, 593 }) 594 prevPbc = pbc 595 } 596 } 597 598 func (s *sealSuite) TestResealKeyToModeenvRecoveryKeysForGoodSystemsOnly(c *C) { 599 rootdir := c.MkDir() 600 dirs.SetRootDir(rootdir) 601 defer dirs.SetRootDir("") 602 603 c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil) 604 err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644) 605 c.Assert(err, IsNil) 606 607 err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-seed")) 608 c.Assert(err, IsNil) 609 610 err = createMockGrubCfg(filepath.Join(rootdir, "run/mnt/ubuntu-boot")) 611 c.Assert(err, IsNil) 612 613 modeenv := &boot.Modeenv{ 614 // where 1234 is being tried 615 CurrentRecoverySystems: []string{"20200825", "1234"}, 616 // 20200825 has known to be good 617 GoodRecoverySystems: []string{"20200825"}, 618 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 619 "grubx64.efi": []string{"grub-hash"}, 620 "bootx64.efi": []string{"shim-hash"}, 621 }, 622 623 CurrentTrustedBootAssets: boot.BootAssetsMap{ 624 "grubx64.efi": []string{"run-grub-hash"}, 625 }, 626 627 CurrentKernels: []string{"pc-kernel_500.snap"}, 628 629 CurrentKernelCommandLines: boot.BootCommandLines{ 630 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 631 }, 632 } 633 634 // mock asset cache 635 mockAssetsCache(c, rootdir, "grub", []string{ 636 "bootx64.efi-shim-hash", 637 "grubx64.efi-grub-hash", 638 "grubx64.efi-run-grub-hash", 639 }) 640 641 model := boottest.MakeMockUC20Model() 642 643 // set a mock recovery kernel 644 readSystemEssentialCalls := 0 645 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 646 readSystemEssentialCalls++ 647 kernelSnap := &seed.Snap{ 648 Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap", 649 SideInfo: &snap.SideInfo{ 650 RealName: "pc-kernel", 651 Revision: snap.Revision{N: 1}, 652 }, 653 } 654 if label == "1234" { 655 // special kernel snap for this system 656 kernelSnap = &seed.Snap{ 657 Path: "/var/lib/snapd/seed/snaps/pc-kernel_999.snap", 658 SideInfo: &snap.SideInfo{ 659 RealName: "pc-kernel", 660 Revision: snap.Revision{N: 999}, 661 }, 662 } 663 } 664 return model, []*seed.Snap{kernelSnap}, nil 665 }) 666 defer restore() 667 668 // set mock key resealing 669 resealKeysCalls := 0 670 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 671 c.Check(params.TPMPolicyAuthKeyFile, Equals, filepath.Join(dirs.SnapSaveDir, "device/fde", "tpm-policy-auth-key")) 672 673 resealKeysCalls++ 674 c.Assert(params.ModelParams, HasLen, 1) 675 676 // shared parameters 677 c.Assert(params.ModelParams[0].Model.DisplayName(), Equals, "My Model") 678 c.Logf("got:") 679 for _, ch := range params.ModelParams[0].EFILoadChains { 680 printChain(c, ch, "-") 681 } 682 switch resealKeysCalls { 683 case 1: // run key 684 c.Assert(params.KeyFiles, DeepEquals, []string{ 685 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 686 }) 687 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 688 "snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1", 689 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 690 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 691 }) 692 // load chains 693 c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 3) 694 case 2: // recovery keys 695 c.Assert(params.KeyFiles, DeepEquals, []string{ 696 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 697 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 698 }) 699 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 700 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 701 }) 702 // load chains 703 c.Assert(params.ModelParams[0].EFILoadChains, HasLen, 1) 704 default: 705 c.Errorf("unexpected additional call to secboot.ResealKeys (call # %d)", resealKeysCalls) 706 } 707 708 // recovery parameters 709 shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/bootx64.efi-shim-hash"), bootloader.RoleRecovery) 710 grub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-grub-hash"), bootloader.RoleRecovery) 711 kernelGoodRecovery := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 712 // kernel from a tried recovery system 713 kernelTriedRecovery := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_999.snap", "kernel.efi", bootloader.RoleRecovery) 714 // run mode parameters 715 runGrub := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/grubx64.efi-run-grub-hash"), bootloader.RoleRunMode) 716 runKernel := bootloader.NewBootFile(filepath.Join(rootdir, "var/lib/snapd/snaps/pc-kernel_500.snap"), "kernel.efi", bootloader.RoleRunMode) 717 718 switch resealKeysCalls { 719 case 1: // run load chain 720 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 721 secboot.NewLoadChain(shim, 722 secboot.NewLoadChain(grub, 723 secboot.NewLoadChain(kernelGoodRecovery), 724 )), 725 secboot.NewLoadChain(shim, 726 secboot.NewLoadChain(grub, 727 secboot.NewLoadChain(kernelTriedRecovery), 728 )), 729 secboot.NewLoadChain(shim, 730 secboot.NewLoadChain(grub, 731 secboot.NewLoadChain(runGrub, 732 secboot.NewLoadChain(runKernel)), 733 )), 734 }) 735 case 2: // recovery load chains 736 c.Assert(params.ModelParams[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 737 secboot.NewLoadChain(shim, 738 secboot.NewLoadChain(grub, 739 secboot.NewLoadChain(kernelGoodRecovery), 740 )), 741 }) 742 } 743 744 return nil 745 }) 746 defer restore() 747 748 // here we don't have unasserted kernels so just set 749 // expectReseal to false as it doesn't matter; 750 // the behavior with unasserted kernel is tested in 751 // boot_test.go specific tests 752 const expectReseal = false 753 err = boot.ResealKeyToModeenv(rootdir, model, modeenv, expectReseal) 754 c.Assert(resealKeysCalls, Equals, 2) 755 c.Assert(err, IsNil) 756 757 // verify the boot chains data file for run key 758 runPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains")) 759 c.Assert(err, IsNil) 760 c.Assert(cnt, Equals, 1) 761 c.Check(runPbc, DeepEquals, boot.PredictableBootChains{ 762 boot.BootChain{ 763 BrandID: "my-brand", 764 Model: "my-model-uc20", 765 Grade: "dangerous", 766 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 767 AssetChain: []boot.BootAsset{ 768 { 769 Role: "recovery", 770 Name: "bootx64.efi", 771 Hashes: []string{"shim-hash"}, 772 }, 773 { 774 Role: "recovery", 775 Name: "grubx64.efi", 776 Hashes: []string{"grub-hash"}, 777 }, 778 }, 779 Kernel: "pc-kernel", 780 KernelRevision: "1", 781 KernelCmdlines: []string{ 782 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 783 }, 784 }, 785 // includes the tried system 786 boot.BootChain{ 787 BrandID: "my-brand", 788 Model: "my-model-uc20", 789 Grade: "dangerous", 790 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 791 AssetChain: []boot.BootAsset{ 792 { 793 Role: "recovery", 794 Name: "bootx64.efi", 795 Hashes: []string{"shim-hash"}, 796 }, 797 { 798 Role: "recovery", 799 Name: "grubx64.efi", 800 Hashes: []string{"grub-hash"}, 801 }, 802 }, 803 Kernel: "pc-kernel", 804 KernelRevision: "999", 805 KernelCmdlines: []string{ 806 "snapd_recovery_mode=recover snapd_recovery_system=1234 console=ttyS0 console=tty1 panic=-1", 807 }, 808 }, 809 boot.BootChain{ 810 BrandID: "my-brand", 811 Model: "my-model-uc20", 812 Grade: "dangerous", 813 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 814 AssetChain: []boot.BootAsset{ 815 { 816 Role: "recovery", 817 Name: "bootx64.efi", 818 Hashes: []string{"shim-hash"}, 819 }, 820 { 821 Role: "recovery", 822 Name: "grubx64.efi", 823 Hashes: []string{"grub-hash"}, 824 }, 825 { 826 Role: "run-mode", 827 Name: "grubx64.efi", 828 Hashes: []string{"run-grub-hash"}, 829 }, 830 }, 831 Kernel: "pc-kernel", 832 KernelRevision: "500", 833 KernelCmdlines: []string{ 834 "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", 835 }, 836 }, 837 }) 838 // recovery boot chains 839 recoveryPbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "recovery-boot-chains")) 840 c.Assert(err, IsNil) 841 c.Assert(cnt, Equals, 1) 842 c.Check(recoveryPbc, DeepEquals, boot.PredictableBootChains{ 843 // only one entry for a recovery system that is known to be good 844 boot.BootChain{ 845 BrandID: "my-brand", 846 Model: "my-model-uc20", 847 Grade: "dangerous", 848 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 849 AssetChain: []boot.BootAsset{ 850 { 851 Role: "recovery", 852 Name: "bootx64.efi", 853 Hashes: []string{"shim-hash"}, 854 }, 855 { 856 Role: "recovery", 857 Name: "grubx64.efi", 858 Hashes: []string{"grub-hash"}, 859 }, 860 }, 861 Kernel: "pc-kernel", 862 KernelRevision: "1", 863 KernelCmdlines: []string{ 864 "snapd_recovery_mode=recover snapd_recovery_system=20200825 console=ttyS0 console=tty1 panic=-1", 865 }, 866 }, 867 }) 868 } 869 870 func (s *sealSuite) TestResealKeyToModeenvFallbackCmdline(c *C) { 871 rootdir := c.MkDir() 872 dirs.SetRootDir(rootdir) 873 defer dirs.SetRootDir("") 874 875 model := boottest.MakeMockUC20Model() 876 877 c.Assert(os.MkdirAll(dirs.SnapFDEDir, 0755), IsNil) 878 err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "sealed-keys"), nil, 0644) 879 c.Assert(err, IsNil) 880 881 modeenv := &boot.Modeenv{ 882 CurrentRecoverySystems: []string{"20200825"}, 883 CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{ 884 "asset": []string{"asset-hash-1"}, 885 }, 886 887 CurrentTrustedBootAssets: boot.BootAssetsMap{ 888 "asset": []string{"asset-hash-1"}, 889 }, 890 891 CurrentKernels: []string{"pc-kernel_500.snap"}, 892 893 // as if it is unset yet 894 CurrentKernelCommandLines: nil, 895 } 896 897 err = boot.WriteBootChains(nil, filepath.Join(dirs.SnapFDEDir, "boot-chains"), 9) 898 c.Assert(err, IsNil) 899 // mock asset cache 900 mockAssetsCache(c, rootdir, "trusted", []string{ 901 "asset-asset-hash-1", 902 }) 903 904 // match one of current kernels 905 runKernelBf := bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_500.snap", "kernel.efi", bootloader.RoleRunMode) 906 // match the seed kernel 907 recoveryKernelBf := bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap", "kernel.efi", bootloader.RoleRecovery) 908 909 bootdir := c.MkDir() 910 mtbl := bootloadertest.Mock("trusted", bootdir).WithTrustedAssets() 911 mtbl.TrustedAssetsList = []string{"asset-1"} 912 mtbl.StaticCommandLine = "static cmdline" 913 mtbl.BootChainList = []bootloader.BootFile{ 914 bootloader.NewBootFile("", "asset", bootloader.RoleRunMode), 915 runKernelBf, 916 } 917 mtbl.RecoveryBootChainList = []bootloader.BootFile{ 918 bootloader.NewBootFile("", "asset", bootloader.RoleRecovery), 919 recoveryKernelBf, 920 } 921 bootloader.Force(mtbl) 922 defer bootloader.Force(nil) 923 924 // set a mock recovery kernel 925 readSystemEssentialCalls := 0 926 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 927 readSystemEssentialCalls++ 928 kernelSnap := &seed.Snap{ 929 Path: "/var/lib/snapd/seed/snaps/pc-kernel_1.snap", 930 SideInfo: &snap.SideInfo{ 931 RealName: "pc-kernel", 932 Revision: snap.Revision{N: 1}, 933 }, 934 } 935 return model, []*seed.Snap{kernelSnap}, nil 936 }) 937 defer restore() 938 939 // set mock key resealing 940 resealKeysCalls := 0 941 restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { 942 resealKeysCalls++ 943 c.Assert(params.ModelParams, HasLen, 1) 944 c.Logf("reseal: %+v", params) 945 switch resealKeysCalls { 946 case 1: 947 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 948 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 949 "snapd_recovery_mode=run static cmdline", 950 }) 951 case 2: 952 c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{ 953 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 954 }) 955 default: 956 c.Fatalf("unexpected number of reseal calls, %v", params) 957 } 958 return nil 959 }) 960 defer restore() 961 962 const expectReseal = false 963 err = boot.ResealKeyToModeenv(rootdir, model, modeenv, expectReseal) 964 c.Assert(err, IsNil) 965 c.Assert(resealKeysCalls, Equals, 2) 966 967 // verify the boot chains data file 968 pbc, cnt, err := boot.ReadBootChains(filepath.Join(dirs.SnapFDEDir, "boot-chains")) 969 c.Assert(err, IsNil) 970 c.Assert(cnt, Equals, 10) 971 c.Check(pbc, DeepEquals, boot.PredictableBootChains{ 972 boot.BootChain{ 973 BrandID: "my-brand", 974 Model: "my-model-uc20", 975 Grade: "dangerous", 976 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 977 AssetChain: []boot.BootAsset{ 978 { 979 Role: "recovery", 980 Name: "asset", 981 Hashes: []string{"asset-hash-1"}, 982 }, 983 }, 984 Kernel: "pc-kernel", 985 KernelRevision: "1", 986 KernelCmdlines: []string{ 987 "snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline", 988 }, 989 }, 990 boot.BootChain{ 991 BrandID: "my-brand", 992 Model: "my-model-uc20", 993 Grade: "dangerous", 994 ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 995 AssetChain: []boot.BootAsset{ 996 { 997 Role: "run-mode", 998 Name: "asset", 999 Hashes: []string{"asset-hash-1"}, 1000 }, 1001 }, 1002 Kernel: "pc-kernel", 1003 KernelRevision: "500", 1004 KernelCmdlines: []string{ 1005 "snapd_recovery_mode=run static cmdline", 1006 }, 1007 }, 1008 }) 1009 } 1010 1011 func (s *sealSuite) TestRecoveryBootChainsForSystems(c *C) { 1012 for _, tc := range []struct { 1013 assetsMap boot.BootAssetsMap 1014 recoverySystems []string 1015 undefinedKernel bool 1016 expectedAssets []boot.BootAsset 1017 expectedKernelRevs []int 1018 err string 1019 }{ 1020 { 1021 // transition sequences 1022 recoverySystems: []string{"20200825"}, 1023 assetsMap: boot.BootAssetsMap{ 1024 "grubx64.efi": []string{"grub-hash-1", "grub-hash-2"}, 1025 "bootx64.efi": []string{"shim-hash-1"}, 1026 }, 1027 expectedAssets: []boot.BootAsset{ 1028 {Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}}, 1029 {Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}}, 1030 }, 1031 expectedKernelRevs: []int{1}, 1032 }, 1033 { 1034 // two systems 1035 recoverySystems: []string{"20200825", "20200831"}, 1036 assetsMap: boot.BootAssetsMap{ 1037 "grubx64.efi": []string{"grub-hash-1", "grub-hash-2"}, 1038 "bootx64.efi": []string{"shim-hash-1"}, 1039 }, 1040 expectedAssets: []boot.BootAsset{ 1041 {Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}}, 1042 {Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1", "grub-hash-2"}}, 1043 }, 1044 expectedKernelRevs: []int{1, 3}, 1045 }, 1046 { 1047 // non-transition sequence 1048 recoverySystems: []string{"20200825"}, 1049 assetsMap: boot.BootAssetsMap{ 1050 "grubx64.efi": []string{"grub-hash-1"}, 1051 "bootx64.efi": []string{"shim-hash-1"}, 1052 }, 1053 expectedAssets: []boot.BootAsset{ 1054 {Role: bootloader.RoleRecovery, Name: "bootx64.efi", Hashes: []string{"shim-hash-1"}}, 1055 {Role: bootloader.RoleRecovery, Name: "grubx64.efi", Hashes: []string{"grub-hash-1"}}, 1056 }, 1057 expectedKernelRevs: []int{1}, 1058 }, 1059 { 1060 // invalid recovery system label 1061 recoverySystems: []string{"0"}, 1062 err: `cannot read system "0" seed: invalid system seed`, 1063 }, 1064 } { 1065 rootdir := c.MkDir() 1066 dirs.SetRootDir(rootdir) 1067 defer dirs.SetRootDir("") 1068 1069 // set recovery kernel 1070 restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) { 1071 if label != "20200825" && label != "20200831" { 1072 return nil, nil, fmt.Errorf("invalid system seed") 1073 } 1074 kernelRev := 1 1075 if label == "20200831" { 1076 kernelRev = 3 1077 } 1078 kernelSnap := &seed.Snap{ 1079 Path: fmt.Sprintf("/var/lib/snapd/seed/snaps/pc-kernel_%d.snap", kernelRev), 1080 SideInfo: &snap.SideInfo{ 1081 RealName: "pc-kernel", 1082 Revision: snap.R(kernelRev), 1083 }, 1084 } 1085 return nil, []*seed.Snap{kernelSnap}, nil 1086 }) 1087 defer restore() 1088 1089 grubDir := filepath.Join(rootdir, "run/mnt/ubuntu-seed") 1090 err := createMockGrubCfg(grubDir) 1091 c.Assert(err, IsNil) 1092 1093 bl, err := bootloader.Find(grubDir, &bootloader.Options{Role: bootloader.RoleRecovery}) 1094 c.Assert(err, IsNil) 1095 tbl, ok := bl.(bootloader.TrustedAssetsBootloader) 1096 c.Assert(ok, Equals, true) 1097 1098 model := boottest.MakeMockUC20Model() 1099 1100 modeenv := &boot.Modeenv{ 1101 CurrentTrustedRecoveryBootAssets: tc.assetsMap, 1102 } 1103 1104 bc, err := boot.RecoveryBootChainsForSystems(tc.recoverySystems, tbl, model, modeenv) 1105 if tc.err == "" { 1106 c.Assert(err, IsNil) 1107 c.Assert(bc, HasLen, len(tc.recoverySystems)) 1108 for i, chain := range bc { 1109 c.Assert(chain.AssetChain, DeepEquals, tc.expectedAssets) 1110 c.Check(chain.Kernel, Equals, "pc-kernel") 1111 expectedKernelRev := tc.expectedKernelRevs[i] 1112 c.Check(chain.KernelRevision, Equals, fmt.Sprintf("%d", expectedKernelRev)) 1113 c.Check(chain.KernelBootFile(), DeepEquals, bootloader.BootFile{Snap: fmt.Sprintf("/var/lib/snapd/seed/snaps/pc-kernel_%d.snap", expectedKernelRev), Path: "kernel.efi", Role: bootloader.RoleRecovery}) 1114 } 1115 } else { 1116 c.Assert(err, ErrorMatches, tc.err) 1117 } 1118 1119 } 1120 1121 } 1122 1123 func createMockGrubCfg(baseDir string) error { 1124 cfg := filepath.Join(baseDir, "EFI/ubuntu/grub.cfg") 1125 if err := os.MkdirAll(filepath.Dir(cfg), 0755); err != nil { 1126 return err 1127 } 1128 return ioutil.WriteFile(cfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644) 1129 } 1130 1131 func (s *sealSuite) TestSealKeyModelParams(c *C) { 1132 rootdir := c.MkDir() 1133 dirs.SetRootDir(rootdir) 1134 defer dirs.SetRootDir("") 1135 1136 model := boottest.MakeMockUC20Model() 1137 1138 roleToBlName := map[bootloader.Role]string{ 1139 bootloader.RoleRecovery: "grub", 1140 bootloader.RoleRunMode: "grub", 1141 } 1142 // mock asset cache 1143 mockAssetsCache(c, rootdir, "grub", []string{ 1144 "shim-shim-hash", 1145 "loader-loader-hash1", 1146 "loader-loader-hash2", 1147 }) 1148 1149 oldmodel := boottest.MakeMockUC20Model(map[string]interface{}{ 1150 "model": "old-model-uc20", 1151 "timestamp": "2019-10-01T08:00:00+00:00", 1152 }) 1153 1154 // old recovery 1155 oldrc := boot.BootChain{ 1156 BrandID: oldmodel.BrandID(), 1157 Model: oldmodel.Model(), 1158 AssetChain: []boot.BootAsset{ 1159 {Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}}, 1160 {Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}}, 1161 }, 1162 KernelCmdlines: []string{"panic=1", "oldrc"}, 1163 } 1164 oldrc.SetModelAssertion(oldmodel) 1165 oldkbf := bootloader.BootFile{Snap: "pc-kernel_1.snap"} 1166 oldrc.SetKernelBootFile(oldkbf) 1167 1168 // recovery 1169 rc1 := boot.BootChain{ 1170 BrandID: model.BrandID(), 1171 Model: model.Model(), 1172 AssetChain: []boot.BootAsset{ 1173 {Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}}, 1174 {Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}}, 1175 }, 1176 KernelCmdlines: []string{"panic=1", "rc1"}, 1177 } 1178 rc1.SetModelAssertion(model) 1179 rc1kbf := bootloader.BootFile{Snap: "pc-kernel_10.snap"} 1180 rc1.SetKernelBootFile(rc1kbf) 1181 1182 // run system 1183 runc1 := boot.BootChain{ 1184 BrandID: model.BrandID(), 1185 Model: model.Model(), 1186 AssetChain: []boot.BootAsset{ 1187 {Name: "shim", Role: bootloader.RoleRecovery, Hashes: []string{"shim-hash"}}, 1188 {Name: "loader", Role: bootloader.RoleRecovery, Hashes: []string{"loader-hash1"}}, 1189 {Name: "loader", Role: bootloader.RoleRunMode, Hashes: []string{"loader-hash2"}}, 1190 }, 1191 KernelCmdlines: []string{"panic=1", "runc1"}, 1192 } 1193 runc1.SetModelAssertion(model) 1194 runc1kbf := bootloader.BootFile{Snap: "pc-kernel_50.snap"} 1195 runc1.SetKernelBootFile(runc1kbf) 1196 1197 pbc := boot.ToPredictableBootChains([]boot.BootChain{rc1, runc1, oldrc}) 1198 1199 shim := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/shim-shim-hash"), bootloader.RoleRecovery) 1200 loader1 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/loader-loader-hash1"), bootloader.RoleRecovery) 1201 loader2 := bootloader.NewBootFile("", filepath.Join(rootdir, "var/lib/snapd/boot-assets/grub/loader-loader-hash2"), bootloader.RoleRunMode) 1202 1203 params, err := boot.SealKeyModelParams(pbc, roleToBlName) 1204 c.Assert(err, IsNil) 1205 c.Check(params, HasLen, 2) 1206 c.Check(params[0].Model, Equals, model) 1207 // NB: merging of lists makes panic=1 appear once 1208 c.Check(params[0].KernelCmdlines, DeepEquals, []string{"panic=1", "rc1", "runc1"}) 1209 1210 c.Check(params[0].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 1211 secboot.NewLoadChain(shim, 1212 secboot.NewLoadChain(loader1, 1213 secboot.NewLoadChain(rc1kbf))), 1214 secboot.NewLoadChain(shim, 1215 secboot.NewLoadChain(loader1, 1216 secboot.NewLoadChain(loader2, 1217 secboot.NewLoadChain(runc1kbf)))), 1218 }) 1219 1220 c.Check(params[1].Model, Equals, oldmodel) 1221 c.Check(params[1].KernelCmdlines, DeepEquals, []string{"oldrc", "panic=1"}) 1222 c.Check(params[1].EFILoadChains, DeepEquals, []*secboot.LoadChain{ 1223 secboot.NewLoadChain(shim, 1224 secboot.NewLoadChain(loader1, 1225 secboot.NewLoadChain(oldkbf))), 1226 }) 1227 } 1228 1229 func (s *sealSuite) TestIsResealNeeded(c *C) { 1230 if os.Geteuid() == 0 { 1231 c.Skip("the test cannot be run by the root user") 1232 } 1233 1234 chains := []boot.BootChain{ 1235 { 1236 BrandID: "mybrand", 1237 Model: "foo", 1238 Grade: "signed", 1239 ModelSignKeyID: "my-key-id", 1240 AssetChain: []boot.BootAsset{ 1241 // hashes will be sorted 1242 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"x", "y"}}, 1243 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}}, 1244 {Role: bootloader.RoleRunMode, Name: "loader", Hashes: []string{"z", "x"}}, 1245 }, 1246 Kernel: "pc-kernel-other", 1247 KernelRevision: "2345", 1248 KernelCmdlines: []string{`snapd_recovery_mode=run foo`}, 1249 }, { 1250 BrandID: "mybrand", 1251 Model: "foo", 1252 Grade: "dangerous", 1253 ModelSignKeyID: "my-key-id", 1254 AssetChain: []boot.BootAsset{ 1255 // hashes will be sorted 1256 {Role: bootloader.RoleRecovery, Name: "shim", Hashes: []string{"y", "x"}}, 1257 {Role: bootloader.RoleRecovery, Name: "loader", Hashes: []string{"c", "d"}}, 1258 }, 1259 Kernel: "pc-kernel-recovery", 1260 KernelRevision: "1234", 1261 KernelCmdlines: []string{`snapd_recovery_mode=recover foo`}, 1262 }, 1263 } 1264 1265 pbc := boot.ToPredictableBootChains(chains) 1266 1267 rootdir := c.MkDir() 1268 err := boot.WriteBootChains(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 2) 1269 c.Assert(err, IsNil) 1270 1271 needed, _, err := boot.IsResealNeeded(pbc, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false) 1272 c.Assert(err, IsNil) 1273 c.Check(needed, Equals, false) 1274 1275 otherchain := []boot.BootChain{pbc[0]} 1276 needed, cnt, err := boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false) 1277 c.Assert(err, IsNil) 1278 // chains are different 1279 c.Check(needed, Equals, true) 1280 c.Check(cnt, Equals, 3) 1281 1282 // boot-chains does not exist, we cannot compare so advise to reseal 1283 otherRootdir := c.MkDir() 1284 needed, cnt, err = boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(otherRootdir), "boot-chains"), false) 1285 c.Assert(err, IsNil) 1286 c.Check(needed, Equals, true) 1287 c.Check(cnt, Equals, 1) 1288 1289 // exists but cannot be read 1290 c.Assert(os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0000), IsNil) 1291 defer os.Chmod(filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), 0755) 1292 needed, _, err = boot.IsResealNeeded(otherchain, filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains"), false) 1293 c.Assert(err, ErrorMatches, "cannot open existing boot chains data file: open .*/boot-chains: permission denied") 1294 c.Check(needed, Equals, false) 1295 1296 // unrevisioned kernel chain 1297 unrevchain := []boot.BootChain{pbc[0], pbc[1]} 1298 unrevchain[1].KernelRevision = "" 1299 // write on disk 1300 bootChainsFile := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "boot-chains") 1301 err = boot.WriteBootChains(unrevchain, bootChainsFile, 2) 1302 c.Assert(err, IsNil) 1303 1304 needed, cnt, err = boot.IsResealNeeded(pbc, bootChainsFile, false) 1305 c.Assert(err, IsNil) 1306 c.Check(needed, Equals, true) 1307 c.Check(cnt, Equals, 3) 1308 1309 // cases falling back to expectReseal 1310 needed, _, err = boot.IsResealNeeded(unrevchain, bootChainsFile, false) 1311 c.Assert(err, IsNil) 1312 c.Check(needed, Equals, false) 1313 1314 needed, cnt, err = boot.IsResealNeeded(unrevchain, bootChainsFile, true) 1315 c.Assert(err, IsNil) 1316 c.Check(needed, Equals, true) 1317 c.Check(cnt, Equals, 3) 1318 } 1319 1320 func (s *sealSuite) TestSealToModeenvWithFdeHookHappy(c *C) { 1321 rootdir := c.MkDir() 1322 dirs.SetRootDir(rootdir) 1323 defer dirs.SetRootDir("") 1324 1325 restore := boot.MockHasFDESetupHook(func() (bool, error) { 1326 return true, nil 1327 }) 1328 defer restore() 1329 1330 n := 0 1331 var runFDESetupHookParams []*boot.FDESetupHookParams 1332 restore = boot.MockRunFDESetupHook(func(op string, params *boot.FDESetupHookParams) ([]byte, error) { 1333 n++ 1334 c.Assert(op, Equals, "initial-setup") 1335 runFDESetupHookParams = append(runFDESetupHookParams, params) 1336 return []byte("sealed-key: " + strconv.Itoa(n)), nil 1337 }) 1338 defer restore() 1339 1340 modeenv := &boot.Modeenv{ 1341 RecoverySystem: "20200825", 1342 } 1343 key := secboot.EncryptionKey{1, 2, 3, 4} 1344 saveKey := secboot.EncryptionKey{5, 6, 7, 8} 1345 1346 model := boottest.MakeMockUC20Model() 1347 err := boot.SealKeyToModeenv(key, saveKey, model, modeenv) 1348 c.Assert(err, IsNil) 1349 // check that runFDESetupHook was called the expected way 1350 c.Check(runFDESetupHookParams, DeepEquals, []*boot.FDESetupHookParams{ 1351 {Key: secboot.EncryptionKey{1, 2, 3, 4}, KeyName: "ubuntu-data", Models: []*asserts.Model{model}}, 1352 {Key: secboot.EncryptionKey{1, 2, 3, 4}, KeyName: "ubuntu-data", Models: []*asserts.Model{model}}, 1353 {Key: secboot.EncryptionKey{5, 6, 7, 8}, KeyName: "ubuntu-save", Models: []*asserts.Model{model}}, 1354 }) 1355 // check that the sealed keys got written to the expected places 1356 for i, p := range []string{ 1357 filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"), 1358 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), 1359 filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), 1360 } { 1361 c.Check(p, testutil.FileEquals, "sealed-key: "+strconv.Itoa(i+1)) 1362 } 1363 marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys") 1364 c.Check(marker, testutil.FileEquals, "fde-setup-hook") 1365 } 1366 1367 func (s *sealSuite) TestSealToModeenvWithFdeHookSad(c *C) { 1368 rootdir := c.MkDir() 1369 dirs.SetRootDir(rootdir) 1370 defer dirs.SetRootDir("") 1371 1372 restore := boot.MockHasFDESetupHook(func() (bool, error) { 1373 return true, nil 1374 }) 1375 defer restore() 1376 1377 restore = boot.MockRunFDESetupHook(func(op string, params *boot.FDESetupHookParams) ([]byte, error) { 1378 return nil, fmt.Errorf("hook failed") 1379 }) 1380 defer restore() 1381 1382 modeenv := &boot.Modeenv{ 1383 RecoverySystem: "20200825", 1384 } 1385 key := secboot.EncryptionKey{1, 2, 3, 4} 1386 saveKey := secboot.EncryptionKey{5, 6, 7, 8} 1387 1388 model := boottest.MakeMockUC20Model() 1389 err := boot.SealKeyToModeenv(key, saveKey, model, modeenv) 1390 c.Assert(err, ErrorMatches, "hook failed") 1391 marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys") 1392 c.Check(marker, testutil.FileAbsent) 1393 } 1394 1395 func (s *sealSuite) TestResealKeyToModeenvWithFdeHookCalled(c *C) { 1396 rootdir := c.MkDir() 1397 dirs.SetRootDir(rootdir) 1398 defer dirs.SetRootDir("") 1399 1400 resealKeyToModeenvUsingFDESetupHookCalled := 0 1401 restore := boot.MockResealKeyToModeenvUsingFDESetupHook(func(string, *asserts.Model, *boot.Modeenv, bool) error { 1402 resealKeyToModeenvUsingFDESetupHookCalled++ 1403 return nil 1404 }) 1405 defer restore() 1406 1407 // TODO: this simulates that the hook is not available yet 1408 // because of e.g. seeding. Longer term there will be 1409 // more, see TODO in resealKeyToModeenvUsingFDESetupHookImpl 1410 restore = boot.MockHasFDESetupHook(func() (bool, error) { 1411 return false, fmt.Errorf("hook not available yet because e.g. seeding") 1412 }) 1413 defer restore() 1414 1415 marker := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys") 1416 err := os.MkdirAll(filepath.Dir(marker), 0755) 1417 c.Assert(err, IsNil) 1418 err = ioutil.WriteFile(marker, []byte("fde-setup-hook"), 0644) 1419 c.Assert(err, IsNil) 1420 1421 modeenv := &boot.Modeenv{ 1422 RecoverySystem: "20200825", 1423 } 1424 1425 model := boottest.MakeMockUC20Model() 1426 expectReseal := false 1427 err = boot.ResealKeyToModeenv(rootdir, model, modeenv, expectReseal) 1428 c.Assert(err, IsNil) 1429 c.Check(resealKeyToModeenvUsingFDESetupHookCalled, Equals, 1) 1430 } 1431 1432 func (s *sealSuite) TestResealKeyToModeenvWithFdeHookVerySad(c *C) { 1433 rootdir := c.MkDir() 1434 dirs.SetRootDir(rootdir) 1435 defer dirs.SetRootDir("") 1436 1437 resealKeyToModeenvUsingFDESetupHookCalled := 0 1438 restore := boot.MockResealKeyToModeenvUsingFDESetupHook(func(string, *asserts.Model, *boot.Modeenv, bool) error { 1439 resealKeyToModeenvUsingFDESetupHookCalled++ 1440 return fmt.Errorf("fde setup hook failed") 1441 }) 1442 defer restore() 1443 1444 marker := filepath.Join(dirs.SnapFDEDirUnder(rootdir), "sealed-keys") 1445 err := os.MkdirAll(filepath.Dir(marker), 0755) 1446 c.Assert(err, IsNil) 1447 err = ioutil.WriteFile(marker, []byte("fde-setup-hook"), 0644) 1448 c.Assert(err, IsNil) 1449 1450 modeenv := &boot.Modeenv{ 1451 RecoverySystem: "20200825", 1452 } 1453 1454 model := boottest.MakeMockUC20Model() 1455 expectReseal := false 1456 err = boot.ResealKeyToModeenv(rootdir, model, modeenv, expectReseal) 1457 c.Assert(err, ErrorMatches, "fde setup hook failed") 1458 c.Check(resealKeyToModeenvUsingFDESetupHookCalled, Equals, 1) 1459 }