github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/secboot/secboot_tpm_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 // +build !nosecboot 3 4 /* 5 * Copyright (C) 2020 Canonical Ltd 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 3 as 9 * published by the Free Software Foundation. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 * 19 */ 20 21 package secboot_test 22 23 import ( 24 "errors" 25 "fmt" 26 "io" 27 "io/ioutil" 28 "os" 29 "path/filepath" 30 "testing" 31 32 "github.com/canonical/go-tpm2" 33 sb "github.com/snapcore/secboot" 34 . "gopkg.in/check.v1" 35 36 "github.com/snapcore/snapd/asserts" 37 "github.com/snapcore/snapd/bootloader" 38 "github.com/snapcore/snapd/bootloader/efi" 39 "github.com/snapcore/snapd/dirs" 40 "github.com/snapcore/snapd/osutil/disks" 41 "github.com/snapcore/snapd/secboot" 42 "github.com/snapcore/snapd/snap" 43 "github.com/snapcore/snapd/snap/snapfile" 44 "github.com/snapcore/snapd/snap/squashfs" 45 "github.com/snapcore/snapd/testutil" 46 ) 47 48 func TestSecboot(t *testing.T) { TestingT(t) } 49 50 type secbootSuite struct { 51 testutil.BaseTest 52 } 53 54 var _ = Suite(&secbootSuite{}) 55 56 func (s *secbootSuite) SetUpTest(c *C) { 57 dirs.SetRootDir(c.MkDir()) 58 s.AddCleanup(func() { dirs.SetRootDir("/") }) 59 } 60 61 func (s *secbootSuite) TestCheckKeySealingSupported(c *C) { 62 sbEmpty := []uint8{} 63 sbEnabled := []uint8{1} 64 sbDisabled := []uint8{0} 65 efiNotSupported := []uint8(nil) 66 tpmErr := errors.New("TPM error") 67 68 type testCase struct { 69 tpmErr error 70 tpmEnabled bool 71 sbData []uint8 72 err string 73 } 74 for i, tc := range []testCase{ 75 // happy case 76 {tpmErr: nil, tpmEnabled: true, sbData: sbEnabled, err: ""}, 77 // secure boot EFI var is empty 78 {tpmErr: nil, tpmEnabled: true, sbData: sbEmpty, err: "secure boot variable does not exist"}, 79 // secure boot is disabled 80 {tpmErr: nil, tpmEnabled: true, sbData: sbDisabled, err: "secure boot is disabled"}, 81 // EFI not supported 82 {tpmErr: nil, tpmEnabled: true, sbData: efiNotSupported, err: "not a supported EFI system"}, 83 // TPM connection error 84 {tpmErr: tpmErr, sbData: sbEnabled, err: "cannot connect to TPM device: TPM error"}, 85 // TPM was detected but it's not enabled 86 {tpmErr: nil, tpmEnabled: false, sbData: sbEnabled, err: "TPM device is not enabled"}, 87 // No TPM device 88 {tpmErr: sb.ErrNoTPM2Device, sbData: sbEnabled, err: "cannot connect to TPM device: no TPM2 device is available"}, 89 } { 90 c.Logf("%d: %v %v %v %q", i, tc.tpmErr, tc.tpmEnabled, tc.sbData, tc.err) 91 92 _, restore := mockSbTPMConnection(c, tc.tpmErr) 93 defer restore() 94 95 restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 96 return tc.tpmEnabled 97 }) 98 defer restore() 99 100 var vars map[string][]byte 101 if tc.sbData != nil { 102 vars = map[string][]byte{"SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c": tc.sbData} 103 } 104 restoreEfiVars := efi.MockVars(vars, nil) 105 defer restoreEfiVars() 106 107 err := secboot.CheckKeySealingSupported() 108 if tc.err == "" { 109 c.Assert(err, IsNil) 110 } else { 111 c.Assert(err, ErrorMatches, tc.err) 112 } 113 } 114 } 115 116 func (s *secbootSuite) TestMeasureSnapSystemEpochWhenPossible(c *C) { 117 for _, tc := range []struct { 118 tpmErr error 119 tpmEnabled bool 120 callNum int 121 err string 122 }{ 123 { 124 // normal connection to the TPM device 125 tpmErr: nil, tpmEnabled: true, callNum: 1, err: "", 126 }, 127 { 128 // TPM device exists but returns error 129 tpmErr: errors.New("tpm error"), callNum: 0, 130 err: "cannot measure snap system epoch: cannot open TPM connection: tpm error", 131 }, 132 { 133 // TPM device exists but is disabled 134 tpmErr: nil, tpmEnabled: false, 135 }, 136 { 137 // TPM device does not exist 138 tpmErr: sb.ErrNoTPM2Device, 139 }, 140 } { 141 mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr) 142 defer restore() 143 144 restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 145 return tc.tpmEnabled 146 }) 147 defer restore() 148 149 calls := 0 150 restore = secboot.MockSbMeasureSnapSystemEpochToTPM(func(tpm *sb.TPMConnection, pcrIndex int) error { 151 calls++ 152 c.Assert(tpm, Equals, mockTpm) 153 c.Assert(pcrIndex, Equals, 12) 154 return nil 155 }) 156 defer restore() 157 158 err := secboot.MeasureSnapSystemEpochWhenPossible() 159 if tc.err == "" { 160 c.Assert(err, IsNil) 161 } else { 162 c.Assert(err, ErrorMatches, tc.err) 163 } 164 c.Assert(calls, Equals, tc.callNum) 165 } 166 } 167 168 func (s *secbootSuite) TestMeasureSnapModelWhenPossible(c *C) { 169 for i, tc := range []struct { 170 tpmErr error 171 tpmEnabled bool 172 modelErr error 173 callNum int 174 err string 175 }{ 176 { 177 // normal connection to the TPM device 178 tpmErr: nil, tpmEnabled: true, modelErr: nil, callNum: 1, err: "", 179 }, 180 { 181 // normal connection to the TPM device with model error 182 tpmErr: nil, tpmEnabled: true, modelErr: errors.New("model error"), callNum: 0, 183 err: "cannot measure snap model: model error", 184 }, 185 { 186 // TPM device exists but returns error 187 tpmErr: errors.New("tpm error"), callNum: 0, 188 err: "cannot measure snap model: cannot open TPM connection: tpm error", 189 }, 190 { 191 // TPM device exists but is disabled 192 tpmErr: nil, tpmEnabled: false, 193 }, 194 { 195 // TPM device does not exist 196 tpmErr: sb.ErrNoTPM2Device, 197 }, 198 } { 199 c.Logf("%d: tpmErr:%v tpmEnabled:%v", i, tc.tpmErr, tc.tpmEnabled) 200 mockModel := &asserts.Model{} 201 202 mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr) 203 defer restore() 204 205 restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 206 return tc.tpmEnabled 207 }) 208 defer restore() 209 210 calls := 0 211 restore = secboot.MockSbMeasureSnapModelToTPM(func(tpm *sb.TPMConnection, pcrIndex int, model *asserts.Model) error { 212 calls++ 213 c.Assert(tpm, Equals, mockTpm) 214 c.Assert(model, Equals, mockModel) 215 c.Assert(pcrIndex, Equals, 12) 216 return nil 217 }) 218 defer restore() 219 220 findModel := func() (*asserts.Model, error) { 221 if tc.modelErr != nil { 222 return nil, tc.modelErr 223 } 224 return mockModel, nil 225 } 226 227 err := secboot.MeasureSnapModelWhenPossible(findModel) 228 if tc.err == "" { 229 c.Assert(err, IsNil) 230 } else { 231 c.Assert(err, ErrorMatches, tc.err) 232 } 233 c.Assert(calls, Equals, tc.callNum) 234 } 235 } 236 237 func (s *secbootSuite) TestUnlockIfEncrypted(c *C) { 238 239 // setup mock disks to use for locating the partition 240 // restore := disks.MockMountPointDisksToPartitionMapping() 241 // defer restore() 242 243 mockDiskWithEncDev := &disks.MockDiskMapping{ 244 FilesystemLabelToPartUUID: map[string]string{ 245 "name-enc": "enc-dev-partuuid", 246 }, 247 } 248 249 mockDiskWithoutAnyDev := &disks.MockDiskMapping{ 250 FilesystemLabelToPartUUID: map[string]string{}, 251 } 252 253 mockDiskWithUnencDev := &disks.MockDiskMapping{ 254 FilesystemLabelToPartUUID: map[string]string{ 255 "name": "unenc-dev-partuuid", 256 }, 257 } 258 259 for idx, tc := range []struct { 260 tpmErr error 261 tpmEnabled bool // TPM storage and endorsement hierarchies disabled, only relevant if TPM available 262 hasEncdev bool // an encrypted device exists 263 rkErr error // recovery key unlock error, only relevant if TPM not available 264 lockRequest bool // request to lock access to the sealed key, only relevant if TPM available 265 lockOk bool // the lock operation succeeded 266 activated bool // the activation operation succeeded 267 device string 268 err string 269 disk *disks.MockDiskMapping 270 }{ 271 { 272 // happy case with tpm and encrypted device (lock requested) 273 tpmEnabled: true, hasEncdev: true, lockRequest: true, lockOk: true, 274 activated: true, device: "name", 275 disk: mockDiskWithEncDev, 276 }, { 277 // device activation fails (lock requested) 278 tpmEnabled: true, hasEncdev: true, lockRequest: true, lockOk: true, 279 err: "cannot activate encrypted device .*: activation error", 280 device: "name", 281 disk: mockDiskWithEncDev, 282 }, { 283 // activation works but lock fails (lock requested) 284 tpmEnabled: true, hasEncdev: true, lockRequest: true, activated: true, 285 err: "cannot lock access to sealed keys: lock failed", 286 device: "name", 287 disk: mockDiskWithEncDev, 288 }, { 289 // happy case with tpm and encrypted device 290 tpmEnabled: true, hasEncdev: true, lockOk: true, activated: true, 291 device: "name", 292 disk: mockDiskWithEncDev, 293 }, { 294 // device activation fails 295 tpmEnabled: true, hasEncdev: true, 296 err: "cannot activate encrypted device .*: activation error", 297 device: "name", 298 disk: mockDiskWithEncDev, 299 }, { 300 // activation works but lock fails 301 tpmEnabled: true, hasEncdev: true, activated: true, device: "name", 302 disk: mockDiskWithEncDev, 303 }, { 304 // happy case without encrypted device (lock requested) 305 tpmEnabled: true, lockRequest: true, lockOk: true, activated: true, 306 device: "name", 307 disk: mockDiskWithUnencDev, 308 }, { 309 // activation works but lock fails, without encrypted device (lock requested) 310 tpmEnabled: true, lockRequest: true, activated: true, 311 err: "cannot lock access to sealed keys: lock failed", 312 disk: mockDiskWithUnencDev, 313 }, { 314 // happy case without encrypted device 315 tpmEnabled: true, lockOk: true, activated: true, device: "name", 316 disk: mockDiskWithUnencDev, 317 }, { 318 // activation works but lock fails, no encrypted device 319 tpmEnabled: true, activated: true, device: "name", 320 disk: mockDiskWithUnencDev, 321 }, { 322 // tpm error, no encrypted device 323 tpmErr: errors.New("tpm error"), 324 err: `cannot unlock encrypted device "name": tpm error`, 325 disk: mockDiskWithUnencDev, 326 }, { 327 // tpm error, has encrypted device 328 tpmErr: errors.New("tpm error"), hasEncdev: true, 329 err: `cannot unlock encrypted device "name": tpm error`, 330 disk: mockDiskWithEncDev, 331 }, { 332 // tpm disabled, no encrypted device 333 device: "name", 334 disk: mockDiskWithUnencDev, 335 }, { 336 // tpm disabled, has encrypted device, unlocked using the recovery key 337 hasEncdev: true, 338 device: "name", 339 disk: mockDiskWithEncDev, 340 }, { 341 // tpm disabled, has encrypted device, recovery key unlocking fails 342 hasEncdev: true, rkErr: errors.New("cannot unlock with recovery key"), 343 disk: mockDiskWithEncDev, 344 err: `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`, 345 }, { 346 // no tpm, has encrypted device, unlocked using the recovery key (lock requested) 347 tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, lockRequest: true, 348 disk: mockDiskWithEncDev, 349 device: "name", 350 }, { 351 // no tpm, has encrypted device, recovery key unlocking fails 352 rkErr: errors.New("cannot unlock with recovery key"), 353 tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, lockRequest: true, 354 disk: mockDiskWithEncDev, 355 err: `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`, 356 }, { 357 // no tpm, has encrypted device, unlocked using the recovery key 358 tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, 359 disk: mockDiskWithEncDev, 360 device: "name", 361 }, { 362 // no tpm, no encrypted device (lock requested) 363 tpmErr: sb.ErrNoTPM2Device, lockRequest: true, 364 disk: mockDiskWithUnencDev, 365 device: "name", 366 }, { 367 // no tpm, no encrypted device 368 tpmErr: sb.ErrNoTPM2Device, 369 disk: mockDiskWithUnencDev, 370 device: "name", 371 }, { 372 // no disks at all 373 disk: mockDiskWithoutAnyDev, 374 device: "name", 375 // error is specifically for failing to find name, NOT name-enc, we 376 // will properly fall back to looking for name if we didn't find 377 // name-enc 378 err: "filesystem label \"name\" not found", 379 }, 380 } { 381 randomUUID := fmt.Sprintf("random-uuid-for-test-%d", idx) 382 restore := secboot.MockRandomKernelUUID(func() string { 383 return randomUUID 384 }) 385 defer restore() 386 387 c.Logf("tc %v: %+v", idx, tc) 388 mockSbTPM, restoreConnect := mockSbTPMConnection(c, tc.tpmErr) 389 defer restoreConnect() 390 391 restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 392 return tc.tpmEnabled 393 }) 394 defer restore() 395 396 n := 0 397 restore = secboot.MockSbLockAccessToSealedKeys(func(tpm *sb.TPMConnection) error { 398 n++ 399 c.Assert(tpm, Equals, mockSbTPM) 400 if tc.lockOk { 401 return nil 402 } 403 return errors.New("lock failed") 404 }) 405 defer restore() 406 407 fsLabel := tc.device 408 if tc.hasEncdev { 409 fsLabel += "-enc" 410 } 411 partuuid := tc.disk.FilesystemLabelToPartUUID[fsLabel] 412 devicePath := filepath.Join("/dev/disk/by-partuuid", partuuid) 413 414 restore = secboot.MockSbActivateVolumeWithTPMSealedKey(func(tpm *sb.TPMConnection, volumeName, sourceDevicePath, 415 keyPath string, pinReader io.Reader, options *sb.ActivateWithTPMSealedKeyOptions) (bool, error) { 416 c.Assert(volumeName, Equals, "name-"+randomUUID) 417 c.Assert(sourceDevicePath, Equals, devicePath) 418 c.Assert(keyPath, Equals, filepath.Join("encrypt-key-dir", "name.sealed-key")) 419 c.Assert(*options, DeepEquals, sb.ActivateWithTPMSealedKeyOptions{ 420 PINTries: 1, 421 RecoveryKeyTries: 3, 422 LockSealedKeyAccess: tc.lockRequest, 423 }) 424 if !tc.activated { 425 return false, errors.New("activation error") 426 } 427 return true, nil 428 }) 429 defer restore() 430 431 restore = secboot.MockSbActivateVolumeWithRecoveryKey(func(name, device string, keyReader io.Reader, 432 options *sb.ActivateWithRecoveryKeyOptions) error { 433 return tc.rkErr 434 }) 435 defer restore() 436 437 device, isDecryptDev, err := secboot.UnlockVolumeIfEncrypted(tc.disk, "name", "encrypt-key-dir", tc.lockRequest) 438 if tc.err == "" { 439 c.Assert(err, IsNil) 440 c.Assert(isDecryptDev, Equals, tc.hasEncdev) 441 if tc.hasEncdev { 442 c.Assert(device, Equals, filepath.Join("/dev/mapper", tc.device+"-"+randomUUID)) 443 } else { 444 c.Assert(device, Equals, devicePath) 445 } 446 } else { 447 c.Assert(err, ErrorMatches, tc.err) 448 } 449 // LockAccessToSealedKeys should be called whenever there is a TPM device 450 // detected, regardless of whether secure boot is enabled or there is an 451 // encrypted volume to unlock. If we have multiple encrypted volumes, we 452 // should call it after the last one is unlocked. 453 if tc.tpmErr == nil && tc.lockRequest { 454 c.Assert(n, Equals, 1) 455 } else { 456 c.Assert(n, Equals, 0) 457 } 458 } 459 } 460 461 func (s *secbootSuite) TestEFIImageFromBootFile(c *C) { 462 tmpDir := c.MkDir() 463 464 // set up some test files 465 existingFile := filepath.Join(tmpDir, "foo") 466 err := ioutil.WriteFile(existingFile, nil, 0644) 467 c.Assert(err, IsNil) 468 missingFile := filepath.Join(tmpDir, "bar") 469 snapFile := filepath.Join(tmpDir, "test.snap") 470 snapf, err := createMockSnapFile(c.MkDir(), snapFile, "app") 471 472 for _, tc := range []struct { 473 bootFile bootloader.BootFile 474 efiImage sb.EFIImage 475 err string 476 }{ 477 { 478 // happy case for EFI image 479 bootFile: bootloader.NewBootFile("", existingFile, bootloader.RoleRecovery), 480 efiImage: sb.FileEFIImage(existingFile), 481 }, 482 { 483 // missing EFI image 484 bootFile: bootloader.NewBootFile("", missingFile, bootloader.RoleRecovery), 485 err: fmt.Sprintf("file %s/bar does not exist", tmpDir), 486 }, 487 { 488 // happy case for snap file 489 bootFile: bootloader.NewBootFile(snapFile, "rel", bootloader.RoleRecovery), 490 efiImage: sb.SnapFileEFIImage{Container: snapf, Path: snapFile, FileName: "rel"}, 491 }, 492 { 493 // invalid snap file 494 bootFile: bootloader.NewBootFile(existingFile, "rel", bootloader.RoleRecovery), 495 err: fmt.Sprintf(`"%s/foo" is not a snap or snapdir`, tmpDir), 496 }, 497 { 498 // missing snap file 499 bootFile: bootloader.NewBootFile(missingFile, "rel", bootloader.RoleRecovery), 500 err: fmt.Sprintf(`"%s/bar" is not a snap or snapdir`, tmpDir), 501 }, 502 } { 503 o, err := secboot.EFIImageFromBootFile(&tc.bootFile) 504 if tc.err == "" { 505 c.Assert(err, IsNil) 506 c.Assert(o, DeepEquals, tc.efiImage) 507 } else { 508 c.Assert(err, ErrorMatches, tc.err) 509 } 510 } 511 } 512 513 func (s *secbootSuite) TestSealKey(c *C) { 514 mockErr := errors.New("some error") 515 516 for _, tc := range []struct { 517 tpmErr error 518 tpmEnabled bool 519 missingFile bool 520 badSnapFile bool 521 addEFISbPolicyErr error 522 addSystemdEFIStubErr error 523 addSnapModelErr error 524 provisioningErr error 525 sealErr error 526 provisioningCalls int 527 sealCalls int 528 expectedErr string 529 }{ 530 {tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"}, 531 {tpmEnabled: false, expectedErr: "TPM device is not enabled"}, 532 {tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file /does/not/exist does not exist"}, 533 {tpmEnabled: true, badSnapFile: true, expectedErr: `.*/kernel.snap" is not a snap or snapdir`}, 534 {tpmEnabled: true, addEFISbPolicyErr: mockErr, expectedErr: "cannot add EFI secure boot policy profile: some error"}, 535 {tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"}, 536 {tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"}, 537 {tpmEnabled: true, provisioningErr: mockErr, provisioningCalls: 1, expectedErr: "cannot provision TPM: some error"}, 538 {tpmEnabled: true, sealErr: mockErr, provisioningCalls: 1, sealCalls: 1, expectedErr: "some error"}, 539 {tpmEnabled: true, provisioningCalls: 1, sealCalls: 1, expectedErr: ""}, 540 } { 541 tmpDir := c.MkDir() 542 var mockBF []bootloader.BootFile 543 for _, name := range []string{"a", "b", "c", "d"} { 544 mockFileName := filepath.Join(tmpDir, name) 545 err := ioutil.WriteFile(mockFileName, nil, 0644) 546 c.Assert(err, IsNil) 547 mockBF = append(mockBF, bootloader.NewBootFile("", mockFileName, bootloader.RoleRecovery)) 548 } 549 550 if tc.missingFile { 551 mockBF[0].Path = "/does/not/exist" 552 } 553 554 var kernelSnap snap.Container 555 snapPath := filepath.Join(tmpDir, "kernel.snap") 556 if tc.badSnapFile { 557 err := ioutil.WriteFile(snapPath, nil, 0644) 558 c.Assert(err, IsNil) 559 } else { 560 var err error 561 kernelSnap, err = createMockSnapFile(c.MkDir(), snapPath, "kernel") 562 c.Assert(err, IsNil) 563 } 564 565 mockBF = append(mockBF, bootloader.NewBootFile(snapPath, "kernel.efi", bootloader.RoleRecovery)) 566 567 myParams := secboot.SealKeyParams{ 568 ModelParams: []*secboot.SealKeyModelParams{ 569 { 570 EFILoadChains: []*secboot.LoadChain{ 571 secboot.NewLoadChain(mockBF[0], 572 secboot.NewLoadChain(mockBF[4])), 573 }, 574 KernelCmdlines: []string{"cmdline1"}, 575 Model: &asserts.Model{}, 576 }, 577 { 578 EFILoadChains: []*secboot.LoadChain{ 579 secboot.NewLoadChain(mockBF[0], 580 secboot.NewLoadChain(mockBF[2], 581 secboot.NewLoadChain(mockBF[4])), 582 secboot.NewLoadChain(mockBF[3], 583 secboot.NewLoadChain(mockBF[4]))), 584 secboot.NewLoadChain(mockBF[1], 585 secboot.NewLoadChain(mockBF[2], 586 secboot.NewLoadChain(mockBF[4])), 587 secboot.NewLoadChain(mockBF[3], 588 secboot.NewLoadChain(mockBF[4]))), 589 }, 590 KernelCmdlines: []string{"cmdline2", "cmdline3"}, 591 Model: &asserts.Model{}, 592 }, 593 }, 594 KeyFile: "keyfile", 595 TPMPolicyUpdateDataFile: "policy-update-data-file", 596 TPMLockoutAuthFile: filepath.Join(tmpDir, "lockout-auth-file"), 597 } 598 599 myKey := secboot.EncryptionKey{} 600 for i := range myKey { 601 myKey[i] = byte(i) 602 } 603 604 // events for 605 // a -> kernel 606 sequences1 := []*sb.EFIImageLoadEvent{ 607 { 608 Source: sb.Firmware, 609 Image: sb.FileEFIImage(mockBF[0].Path), 610 Next: []*sb.EFIImageLoadEvent{ 611 { 612 Source: sb.Shim, 613 Image: sb.SnapFileEFIImage{ 614 Container: kernelSnap, 615 Path: mockBF[4].Snap, 616 FileName: "kernel.efi", 617 }, 618 }, 619 }, 620 }, 621 } 622 623 // "cdk" events for 624 // c -> kernel OR 625 // d -> kernel 626 cdk := []*sb.EFIImageLoadEvent{ 627 { 628 Source: sb.Shim, 629 Image: sb.FileEFIImage(mockBF[2].Path), 630 Next: []*sb.EFIImageLoadEvent{ 631 { 632 Source: sb.Shim, 633 Image: sb.SnapFileEFIImage{ 634 Container: kernelSnap, 635 Path: mockBF[4].Snap, 636 FileName: "kernel.efi", 637 }, 638 }, 639 }, 640 }, 641 { 642 Source: sb.Shim, 643 Image: sb.FileEFIImage(mockBF[3].Path), 644 Next: []*sb.EFIImageLoadEvent{ 645 { 646 Source: sb.Shim, 647 Image: sb.SnapFileEFIImage{ 648 Container: kernelSnap, 649 Path: mockBF[4].Snap, 650 FileName: "kernel.efi", 651 }, 652 }, 653 }, 654 }, 655 } 656 657 // events for 658 // a -> "cdk" 659 // b -> "cdk" 660 sequences2 := []*sb.EFIImageLoadEvent{ 661 { 662 Source: sb.Firmware, 663 Image: sb.FileEFIImage(mockBF[0].Path), 664 Next: cdk, 665 }, 666 { 667 Source: sb.Firmware, 668 Image: sb.FileEFIImage(mockBF[1].Path), 669 Next: cdk, 670 }, 671 } 672 673 tpm, restore := mockSbTPMConnection(c, tc.tpmErr) 674 defer restore() 675 676 // mock adding EFI secure boot policy profile 677 var pcrProfile *sb.PCRProtectionProfile 678 addEFISbPolicyCalls := 0 679 restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error { 680 addEFISbPolicyCalls++ 681 pcrProfile = profile 682 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 683 switch addEFISbPolicyCalls { 684 case 1: 685 c.Assert(params.LoadSequences, DeepEquals, sequences1) 686 case 2: 687 c.Assert(params.LoadSequences, DeepEquals, sequences2) 688 default: 689 c.Error("AddEFISecureBootPolicyProfile shouldn't be called a third time") 690 } 691 return tc.addEFISbPolicyErr 692 }) 693 defer restore() 694 695 // mock adding systemd EFI stub profile 696 addSystemdEfiStubCalls := 0 697 restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error { 698 addSystemdEfiStubCalls++ 699 c.Assert(profile, Equals, pcrProfile) 700 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 701 c.Assert(params.PCRIndex, Equals, 12) 702 switch addSystemdEfiStubCalls { 703 case 1: 704 c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[0].KernelCmdlines) 705 case 2: 706 c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[1].KernelCmdlines) 707 default: 708 c.Error("AddSystemdEFIStubProfile shouldn't be called a third time") 709 } 710 return tc.addSystemdEFIStubErr 711 }) 712 defer restore() 713 714 // mock adding snap model profile 715 addSnapModelCalls := 0 716 restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error { 717 addSnapModelCalls++ 718 c.Assert(profile, Equals, pcrProfile) 719 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 720 c.Assert(params.PCRIndex, Equals, 12) 721 switch addSnapModelCalls { 722 case 1: 723 c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[0].Model) 724 case 2: 725 c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[1].Model) 726 default: 727 c.Error("AddSnapModelProfile shouldn't be called a third time") 728 } 729 return tc.addSnapModelErr 730 }) 731 defer restore() 732 733 // mock provisioning 734 provisioningCalls := 0 735 restore = secboot.MockSbProvisionTPM(func(t *sb.TPMConnection, mode sb.ProvisionMode, newLockoutAuth []byte) error { 736 provisioningCalls++ 737 c.Assert(t, Equals, tpm) 738 c.Assert(mode, Equals, sb.ProvisionModeFull) 739 c.Assert(myParams.TPMLockoutAuthFile, testutil.FilePresent) 740 return tc.provisioningErr 741 }) 742 defer restore() 743 744 // mock sealing 745 sealCalls := 0 746 restore = secboot.MockSbSealKeyToTPM(func(t *sb.TPMConnection, key []byte, keyPath, policyUpdatePath string, params *sb.KeyCreationParams) error { 747 sealCalls++ 748 c.Assert(t, Equals, tpm) 749 c.Assert(key, DeepEquals, myKey[:]) 750 c.Assert(keyPath, Equals, myParams.KeyFile) 751 c.Assert(policyUpdatePath, Equals, myParams.TPMPolicyUpdateDataFile) 752 c.Assert(params.PINHandle, Equals, tpm2.Handle(0x01880000)) 753 return tc.sealErr 754 }) 755 defer restore() 756 757 // mock TPM enabled check 758 restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool { 759 return tc.tpmEnabled 760 }) 761 defer restore() 762 763 err := secboot.SealKey(myKey, &myParams) 764 if tc.expectedErr == "" { 765 c.Assert(err, IsNil) 766 c.Assert(addEFISbPolicyCalls, Equals, 2) 767 c.Assert(addSystemdEfiStubCalls, Equals, 2) 768 c.Assert(addSnapModelCalls, Equals, 2) 769 } else { 770 c.Assert(err, ErrorMatches, tc.expectedErr) 771 } 772 c.Assert(provisioningCalls, Equals, tc.provisioningCalls) 773 c.Assert(sealCalls, Equals, tc.sealCalls) 774 775 } 776 } 777 778 func (s *secbootSuite) TestSealKeyNoModelParams(c *C) { 779 myKey := secboot.EncryptionKey{} 780 myParams := secboot.SealKeyParams{ 781 KeyFile: "keyfile", 782 TPMPolicyUpdateDataFile: "policy-update-data-file", 783 TPMLockoutAuthFile: "lockout-auth-file", 784 } 785 786 err := secboot.SealKey(myKey, &myParams) 787 c.Assert(err, ErrorMatches, "at least one set of model-specific parameters is required") 788 } 789 790 func createMockSnapFile(snapDir, snapPath, snapType string) (snap.Container, error) { 791 snapYamlPath := filepath.Join(snapDir, "meta/snap.yaml") 792 if err := os.MkdirAll(filepath.Dir(snapYamlPath), 0755); err != nil { 793 return nil, err 794 } 795 if err := ioutil.WriteFile(snapYamlPath, []byte("name: foo"), 0644); err != nil { 796 return nil, err 797 } 798 sqfs := squashfs.New(snapPath) 799 if err := sqfs.Build(snapDir, &squashfs.BuildOpts{SnapType: snapType}); err != nil { 800 return nil, err 801 } 802 return snapfile.Open(snapPath) 803 } 804 805 func mockSbTPMConnection(c *C, tpmErr error) (*sb.TPMConnection, func()) { 806 tcti, err := os.Open("/dev/null") 807 c.Assert(err, IsNil) 808 tpmctx, err := tpm2.NewTPMContext(tcti) 809 c.Assert(err, IsNil) 810 tpm := &sb.TPMConnection{TPMContext: tpmctx} 811 restore := secboot.MockSbConnectToDefaultTPM(func() (*sb.TPMConnection, error) { 812 if tpmErr != nil { 813 return nil, tpmErr 814 } 815 return tpm, nil 816 }) 817 return tpm, restore 818 }