github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/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 "crypto/ecdsa" 25 "encoding/base64" 26 "errors" 27 "fmt" 28 "io" 29 "io/ioutil" 30 "os" 31 "os/exec" 32 "path/filepath" 33 "testing" 34 "time" 35 36 "github.com/canonical/go-tpm2" 37 sb "github.com/snapcore/secboot" 38 . "gopkg.in/check.v1" 39 40 "github.com/snapcore/snapd/asserts" 41 "github.com/snapcore/snapd/bootloader" 42 "github.com/snapcore/snapd/bootloader/efi" 43 "github.com/snapcore/snapd/dirs" 44 "github.com/snapcore/snapd/osutil" 45 "github.com/snapcore/snapd/osutil/disks" 46 "github.com/snapcore/snapd/secboot" 47 "github.com/snapcore/snapd/snap" 48 "github.com/snapcore/snapd/snap/snapfile" 49 "github.com/snapcore/snapd/snap/squashfs" 50 "github.com/snapcore/snapd/testutil" 51 ) 52 53 func TestSecboot(t *testing.T) { TestingT(t) } 54 55 type secbootSuite struct { 56 testutil.BaseTest 57 } 58 59 var _ = Suite(&secbootSuite{}) 60 61 func (s *secbootSuite) SetUpTest(c *C) { 62 rootDir := c.MkDir() 63 err := os.MkdirAll(filepath.Join(rootDir, "/run"), 0755) 64 c.Assert(err, IsNil) 65 dirs.SetRootDir(rootDir) 66 s.AddCleanup(func() { dirs.SetRootDir("/") }) 67 } 68 69 func (s *secbootSuite) TestCheckKeySealingSupported(c *C) { 70 sbEmpty := []uint8{} 71 sbEnabled := []uint8{1} 72 sbDisabled := []uint8{0} 73 efiNotSupported := []uint8(nil) 74 tpmErr := errors.New("TPM error") 75 76 type testCase struct { 77 tpmErr error 78 tpmEnabled bool 79 sbData []uint8 80 err string 81 } 82 for i, tc := range []testCase{ 83 // happy case 84 {tpmErr: nil, tpmEnabled: true, sbData: sbEnabled, err: ""}, 85 // secure boot EFI var is empty 86 {tpmErr: nil, tpmEnabled: true, sbData: sbEmpty, err: "secure boot variable does not exist"}, 87 // secure boot is disabled 88 {tpmErr: nil, tpmEnabled: true, sbData: sbDisabled, err: "secure boot is disabled"}, 89 // EFI not supported 90 {tpmErr: nil, tpmEnabled: true, sbData: efiNotSupported, err: "not a supported EFI system"}, 91 // TPM connection error 92 {tpmErr: tpmErr, sbData: sbEnabled, err: "cannot connect to TPM device: TPM error"}, 93 // TPM was detected but it's not enabled 94 {tpmErr: nil, tpmEnabled: false, sbData: sbEnabled, err: "TPM device is not enabled"}, 95 // No TPM device 96 {tpmErr: sb.ErrNoTPM2Device, sbData: sbEnabled, err: "cannot connect to TPM device: no TPM2 device is available"}, 97 } { 98 c.Logf("%d: %v %v %v %q", i, tc.tpmErr, tc.tpmEnabled, tc.sbData, tc.err) 99 100 _, restore := mockSbTPMConnection(c, tc.tpmErr) 101 defer restore() 102 103 restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 104 return tc.tpmEnabled 105 }) 106 defer restore() 107 108 var vars map[string][]byte 109 if tc.sbData != nil { 110 vars = map[string][]byte{"SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c": tc.sbData} 111 } 112 restoreEfiVars := efi.MockVars(vars, nil) 113 defer restoreEfiVars() 114 115 err := secboot.CheckKeySealingSupported() 116 if tc.err == "" { 117 c.Assert(err, IsNil) 118 } else { 119 c.Assert(err, ErrorMatches, tc.err) 120 } 121 } 122 } 123 124 func (s *secbootSuite) TestMeasureSnapSystemEpochWhenPossible(c *C) { 125 for _, tc := range []struct { 126 tpmErr error 127 tpmEnabled bool 128 callNum int 129 err string 130 }{ 131 { 132 // normal connection to the TPM device 133 tpmErr: nil, tpmEnabled: true, callNum: 1, err: "", 134 }, 135 { 136 // TPM device exists but returns error 137 tpmErr: errors.New("tpm error"), callNum: 0, 138 err: "cannot measure snap system epoch: cannot open TPM connection: tpm error", 139 }, 140 { 141 // TPM device exists but is disabled 142 tpmErr: nil, tpmEnabled: false, 143 }, 144 { 145 // TPM device does not exist 146 tpmErr: sb.ErrNoTPM2Device, 147 }, 148 } { 149 mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr) 150 defer restore() 151 152 restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 153 return tc.tpmEnabled 154 }) 155 defer restore() 156 157 calls := 0 158 restore = secboot.MockSbMeasureSnapSystemEpochToTPM(func(tpm *sb.TPMConnection, pcrIndex int) error { 159 calls++ 160 c.Assert(tpm, Equals, mockTpm) 161 c.Assert(pcrIndex, Equals, 12) 162 return nil 163 }) 164 defer restore() 165 166 err := secboot.MeasureSnapSystemEpochWhenPossible() 167 if tc.err == "" { 168 c.Assert(err, IsNil) 169 } else { 170 c.Assert(err, ErrorMatches, tc.err) 171 } 172 c.Assert(calls, Equals, tc.callNum) 173 } 174 } 175 176 func (s *secbootSuite) TestMeasureSnapModelWhenPossible(c *C) { 177 for i, tc := range []struct { 178 tpmErr error 179 tpmEnabled bool 180 modelErr error 181 callNum int 182 err string 183 }{ 184 { 185 // normal connection to the TPM device 186 tpmErr: nil, tpmEnabled: true, modelErr: nil, callNum: 1, err: "", 187 }, 188 { 189 // normal connection to the TPM device with model error 190 tpmErr: nil, tpmEnabled: true, modelErr: errors.New("model error"), callNum: 0, 191 err: "cannot measure snap model: model error", 192 }, 193 { 194 // TPM device exists but returns error 195 tpmErr: errors.New("tpm error"), callNum: 0, 196 err: "cannot measure snap model: cannot open TPM connection: tpm error", 197 }, 198 { 199 // TPM device exists but is disabled 200 tpmErr: nil, tpmEnabled: false, 201 }, 202 { 203 // TPM device does not exist 204 tpmErr: sb.ErrNoTPM2Device, 205 }, 206 } { 207 c.Logf("%d: tpmErr:%v tpmEnabled:%v", i, tc.tpmErr, tc.tpmEnabled) 208 mockModel := &asserts.Model{} 209 210 mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr) 211 defer restore() 212 213 restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 214 return tc.tpmEnabled 215 }) 216 defer restore() 217 218 calls := 0 219 restore = secboot.MockSbMeasureSnapModelToTPM(func(tpm *sb.TPMConnection, pcrIndex int, model sb.SnapModel) error { 220 calls++ 221 c.Assert(tpm, Equals, mockTpm) 222 c.Assert(model, Equals, mockModel) 223 c.Assert(pcrIndex, Equals, 12) 224 return nil 225 }) 226 defer restore() 227 228 findModel := func() (*asserts.Model, error) { 229 if tc.modelErr != nil { 230 return nil, tc.modelErr 231 } 232 return mockModel, nil 233 } 234 235 err := secboot.MeasureSnapModelWhenPossible(findModel) 236 if tc.err == "" { 237 c.Assert(err, IsNil) 238 } else { 239 c.Assert(err, ErrorMatches, tc.err) 240 } 241 c.Assert(calls, Equals, tc.callNum) 242 } 243 } 244 245 func (s *secbootSuite) TestLockTPMSealedKeys(c *C) { 246 tt := []struct { 247 tpmErr error 248 tpmEnabled bool 249 lockOk bool 250 expError string 251 }{ 252 // can't connect to tpm 253 { 254 tpmErr: fmt.Errorf("failed to connect to tpm"), 255 expError: "cannot lock TPM: failed to connect to tpm", 256 }, 257 // no TPM2 device, shouldn't return an error 258 { 259 tpmErr: sb.ErrNoTPM2Device, 260 }, 261 // tpm is not enabled but we can lock it 262 { 263 tpmEnabled: false, 264 lockOk: true, 265 }, 266 // can't lock pcr protection profile 267 { 268 tpmEnabled: true, 269 lockOk: false, 270 expError: "block failed", 271 }, 272 // tpm enabled, we can lock it 273 { 274 tpmEnabled: true, 275 lockOk: true, 276 }, 277 } 278 279 for _, tc := range tt { 280 mockSbTPM, restoreConnect := mockSbTPMConnection(c, tc.tpmErr) 281 defer restoreConnect() 282 283 restore := secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 284 return tc.tpmEnabled 285 }) 286 defer restore() 287 288 sbBlockPCRProtectionPolicesCalls := 0 289 restore = secboot.MockSbBlockPCRProtectionPolicies(func(tpm *sb.TPMConnection, pcrs []int) error { 290 sbBlockPCRProtectionPolicesCalls++ 291 c.Assert(tpm, Equals, mockSbTPM) 292 c.Assert(pcrs, DeepEquals, []int{12}) 293 if tc.lockOk { 294 return nil 295 } 296 return errors.New("block failed") 297 }) 298 defer restore() 299 300 err := secboot.LockTPMSealedKeys() 301 if tc.expError == "" { 302 c.Assert(err, IsNil) 303 } else { 304 c.Assert(err, ErrorMatches, tc.expError) 305 } 306 // if there was no TPM connection error, we should have tried to lock it 307 if tc.tpmErr == nil { 308 c.Assert(sbBlockPCRProtectionPolicesCalls, Equals, 1) 309 } else { 310 c.Assert(sbBlockPCRProtectionPolicesCalls, Equals, 0) 311 } 312 } 313 } 314 315 func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) { 316 317 // setup mock disks to use for locating the partition 318 // restore := disks.MockMountPointDisksToPartitionMapping() 319 // defer restore() 320 321 mockDiskWithEncDev := &disks.MockDiskMapping{ 322 FilesystemLabelToPartUUID: map[string]string{ 323 "name-enc": "enc-dev-partuuid", 324 }, 325 } 326 327 mockDiskWithoutAnyDev := &disks.MockDiskMapping{ 328 FilesystemLabelToPartUUID: map[string]string{}, 329 } 330 331 mockDiskWithUnencDev := &disks.MockDiskMapping{ 332 FilesystemLabelToPartUUID: map[string]string{ 333 "name": "unenc-dev-partuuid", 334 }, 335 } 336 337 for idx, tc := range []struct { 338 tpmErr error 339 keyfile string // the keyfile to be used to unseal 340 tpmEnabled bool // TPM storage and endorsement hierarchies disabled, only relevant if TPM available 341 hasEncdev bool // an encrypted device exists 342 rkAllow bool // allow recovery key activation 343 rkErr error // recovery key unlock error, only relevant if TPM not available 344 activated bool // the activation operation succeeded 345 activateErr error // the activation error 346 err string 347 skipDiskEnsureCheck bool // whether to check to ensure the mock disk contains the device label 348 expUnlockMethod secboot.UnlockMethod 349 disk *disks.MockDiskMapping 350 }{ 351 { 352 // happy case with tpm and encrypted device 353 tpmEnabled: true, hasEncdev: true, 354 activated: true, 355 disk: mockDiskWithEncDev, 356 expUnlockMethod: secboot.UnlockedWithSealedKey, 357 }, { 358 // happy case with tpm and encrypted device 359 // with an alternative keyfile 360 tpmEnabled: true, hasEncdev: true, 361 activated: true, 362 disk: mockDiskWithEncDev, 363 keyfile: "some-other-keyfile", 364 expUnlockMethod: secboot.UnlockedWithSealedKey, 365 }, { 366 // device activation fails 367 tpmEnabled: true, hasEncdev: true, 368 err: "cannot activate encrypted device .*: activation error", 369 disk: mockDiskWithEncDev, 370 }, { 371 // device activation fails 372 tpmEnabled: true, hasEncdev: true, 373 err: "cannot activate encrypted device .*: activation error", 374 disk: mockDiskWithEncDev, 375 }, { 376 // happy case without encrypted device 377 tpmEnabled: true, 378 disk: mockDiskWithUnencDev, 379 }, { 380 // happy case with tpm and encrypted device, activation 381 // with recovery key 382 tpmEnabled: true, hasEncdev: true, activated: true, 383 activateErr: &sb.ActivateWithTPMSealedKeyError{ 384 // activation error with nil recovery key error 385 // implies volume activated successfully using 386 // the recovery key, 387 RecoveryKeyUsageErr: nil, 388 }, 389 disk: mockDiskWithEncDev, 390 expUnlockMethod: secboot.UnlockedWithRecoveryKey, 391 }, { 392 // tpm and encrypted device, successful activation, but 393 // recovery key non-nil is an unexpected state 394 tpmEnabled: true, hasEncdev: true, activated: true, 395 activateErr: &sb.ActivateWithTPMSealedKeyError{ 396 RecoveryKeyUsageErr: fmt.Errorf("unexpected"), 397 }, 398 expUnlockMethod: secboot.UnlockStatusUnknown, 399 err: `internal error: volume activated with unexpected error: .* \(unexpected\)`, 400 disk: mockDiskWithEncDev, 401 }, { 402 // tpm error, no encrypted device 403 tpmErr: errors.New("tpm error"), 404 disk: mockDiskWithUnencDev, 405 }, { 406 // tpm error, has encrypted device 407 tpmErr: errors.New("tpm error"), hasEncdev: true, 408 err: `cannot unlock encrypted device "name": tpm error`, 409 disk: mockDiskWithEncDev, 410 }, { 411 // tpm disabled, no encrypted device 412 disk: mockDiskWithUnencDev, 413 }, { 414 // tpm disabled, has encrypted device, unlocked using the recovery key 415 hasEncdev: true, 416 rkAllow: true, 417 disk: mockDiskWithEncDev, 418 expUnlockMethod: secboot.UnlockedWithRecoveryKey, 419 }, { 420 // tpm disabled, has encrypted device, recovery key unlocking fails 421 hasEncdev: true, rkErr: errors.New("cannot unlock with recovery key"), 422 rkAllow: true, 423 disk: mockDiskWithEncDev, 424 err: `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`, 425 }, { 426 // no tpm, has encrypted device, unlocked using the recovery key 427 tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, 428 rkAllow: true, 429 disk: mockDiskWithEncDev, 430 expUnlockMethod: secboot.UnlockedWithRecoveryKey, 431 }, { 432 // no tpm, has encrypted device, unlocking with recovery key not allowed 433 tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, 434 disk: mockDiskWithEncDev, 435 err: `cannot activate encrypted device ".*/enc-dev-partuuid": activation error`, 436 }, { 437 // no tpm, has encrypted device, recovery key unlocking fails 438 rkErr: errors.New("cannot unlock with recovery key"), 439 tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, 440 rkAllow: true, 441 disk: mockDiskWithEncDev, 442 err: `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`, 443 }, { 444 // no tpm, no encrypted device 445 tpmErr: sb.ErrNoTPM2Device, 446 disk: mockDiskWithUnencDev, 447 }, { 448 // no disks at all 449 disk: mockDiskWithoutAnyDev, 450 skipDiskEnsureCheck: true, 451 // error is specifically for failing to find name, NOT name-enc, we 452 // will properly fall back to looking for name if we didn't find 453 // name-enc 454 err: "error enumerating partitions for disk to find unencrypted device \"name\": filesystem label \"name\" not found", 455 }, 456 } { 457 randomUUID := fmt.Sprintf("random-uuid-for-test-%d", idx) 458 restore := secboot.MockRandomKernelUUID(func() string { 459 return randomUUID 460 }) 461 defer restore() 462 463 c.Logf("tc %v: %+v", idx, tc) 464 _, restoreConnect := mockSbTPMConnection(c, tc.tpmErr) 465 defer restoreConnect() 466 467 restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 468 return tc.tpmEnabled 469 }) 470 defer restore() 471 472 defaultDevice := "name" 473 474 fsLabel := defaultDevice 475 if tc.hasEncdev { 476 fsLabel += "-enc" 477 } 478 partuuid, ok := tc.disk.FilesystemLabelToPartUUID[fsLabel] 479 if !tc.skipDiskEnsureCheck { 480 c.Assert(ok, Equals, true) 481 } 482 devicePath := filepath.Join("/dev/disk/by-partuuid", partuuid) 483 484 expKeyPath := tc.keyfile 485 if expKeyPath == "" { 486 expKeyPath = "vanilla-keyfile" 487 } 488 489 restore = secboot.MockSbActivateVolumeWithTPMSealedKey(func(tpm *sb.TPMConnection, volumeName, sourceDevicePath, 490 keyPath string, pinReader io.Reader, options *sb.ActivateVolumeOptions) (bool, error) { 491 c.Assert(volumeName, Equals, "name-"+randomUUID) 492 c.Assert(sourceDevicePath, Equals, devicePath) 493 c.Assert(keyPath, Equals, expKeyPath) 494 if tc.rkAllow { 495 c.Assert(*options, DeepEquals, sb.ActivateVolumeOptions{ 496 PassphraseTries: 1, 497 RecoveryKeyTries: 3, 498 KeyringPrefix: "ubuntu-fde", 499 }) 500 } else { 501 c.Assert(*options, DeepEquals, sb.ActivateVolumeOptions{ 502 PassphraseTries: 1, 503 // activation with recovery key was disabled 504 RecoveryKeyTries: 0, 505 KeyringPrefix: "ubuntu-fde", 506 }) 507 } 508 if !tc.activated && tc.activateErr == nil { 509 return false, errors.New("activation error") 510 } 511 return tc.activated, tc.activateErr 512 }) 513 defer restore() 514 515 restore = secboot.MockSbActivateVolumeWithRecoveryKey(func(name, device string, keyReader io.Reader, 516 options *sb.ActivateVolumeOptions) error { 517 if !tc.rkAllow { 518 c.Fatalf("unexpected attempt to activate with recovery key") 519 return fmt.Errorf("unexpected call") 520 } 521 return tc.rkErr 522 }) 523 defer restore() 524 525 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{ 526 AllowRecoveryKey: tc.rkAllow, 527 } 528 unlockRes, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(tc.disk, defaultDevice, expKeyPath, opts) 529 if tc.err == "" { 530 c.Assert(err, IsNil) 531 c.Assert(unlockRes.IsEncrypted, Equals, tc.hasEncdev) 532 c.Assert(unlockRes.PartDevice, Equals, devicePath) 533 if tc.hasEncdev { 534 c.Assert(unlockRes.FsDevice, Equals, filepath.Join("/dev/mapper", defaultDevice+"-"+randomUUID)) 535 } else { 536 c.Assert(unlockRes.FsDevice, Equals, devicePath) 537 } 538 } else { 539 c.Assert(err, ErrorMatches, tc.err) 540 // also check that the IsEncrypted value matches, this is 541 // important for robust callers to know whether they should try to 542 // unlock using a different method or not 543 // this is only skipped on some test cases where we get an error 544 // very early, like trying to connect to the tpm 545 c.Assert(unlockRes.IsEncrypted, Equals, tc.hasEncdev) 546 if tc.hasEncdev { 547 c.Check(unlockRes.PartDevice, Equals, devicePath) 548 c.Check(unlockRes.FsDevice, Equals, "") 549 } else { 550 c.Check(unlockRes.PartDevice, Equals, "") 551 c.Check(unlockRes.FsDevice, Equals, "") 552 } 553 } 554 555 c.Assert(unlockRes.UnlockMethod, Equals, tc.expUnlockMethod) 556 } 557 } 558 559 func (s *secbootSuite) TestEFIImageFromBootFile(c *C) { 560 tmpDir := c.MkDir() 561 562 // set up some test files 563 existingFile := filepath.Join(tmpDir, "foo") 564 err := ioutil.WriteFile(existingFile, nil, 0644) 565 c.Assert(err, IsNil) 566 missingFile := filepath.Join(tmpDir, "bar") 567 snapFile := filepath.Join(tmpDir, "test.snap") 568 snapf, err := createMockSnapFile(c.MkDir(), snapFile, "app") 569 570 for _, tc := range []struct { 571 bootFile bootloader.BootFile 572 efiImage sb.EFIImage 573 err string 574 }{ 575 { 576 // happy case for EFI image 577 bootFile: bootloader.NewBootFile("", existingFile, bootloader.RoleRecovery), 578 efiImage: sb.FileEFIImage(existingFile), 579 }, 580 { 581 // missing EFI image 582 bootFile: bootloader.NewBootFile("", missingFile, bootloader.RoleRecovery), 583 err: fmt.Sprintf("file %s/bar does not exist", tmpDir), 584 }, 585 { 586 // happy case for snap file 587 bootFile: bootloader.NewBootFile(snapFile, "rel", bootloader.RoleRecovery), 588 efiImage: sb.SnapFileEFIImage{Container: snapf, FileName: "rel"}, 589 }, 590 { 591 // invalid snap file 592 bootFile: bootloader.NewBootFile(existingFile, "rel", bootloader.RoleRecovery), 593 err: fmt.Sprintf(`"%s/foo" is not a snap or snapdir`, tmpDir), 594 }, 595 { 596 // missing snap file 597 bootFile: bootloader.NewBootFile(missingFile, "rel", bootloader.RoleRecovery), 598 err: fmt.Sprintf(`"%s/bar" is not a snap or snapdir`, tmpDir), 599 }, 600 } { 601 o, err := secboot.EFIImageFromBootFile(&tc.bootFile) 602 if tc.err == "" { 603 c.Assert(err, IsNil) 604 c.Assert(o, DeepEquals, tc.efiImage) 605 } else { 606 c.Assert(err, ErrorMatches, tc.err) 607 } 608 } 609 } 610 611 func (s *secbootSuite) TestSealKey(c *C) { 612 mockErr := errors.New("some error") 613 614 for _, tc := range []struct { 615 tpmErr error 616 tpmEnabled bool 617 missingFile bool 618 badSnapFile bool 619 skipProvision bool 620 addEFISbPolicyErr error 621 addEFIBootManagerErr error 622 addSystemdEFIStubErr error 623 addSnapModelErr error 624 provisioningErr error 625 sealErr error 626 provisioningCalls int 627 sealCalls int 628 expectedErr string 629 }{ 630 {tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"}, 631 {tpmEnabled: false, expectedErr: "TPM device is not enabled"}, 632 {tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file /does/not/exist does not exist"}, 633 {tpmEnabled: true, badSnapFile: true, expectedErr: `.*/kernel.snap" is not a snap or snapdir`}, 634 {tpmEnabled: true, addEFISbPolicyErr: mockErr, expectedErr: "cannot add EFI secure boot policy profile: some error"}, 635 {tpmEnabled: true, addEFIBootManagerErr: mockErr, expectedErr: "cannot add EFI boot manager profile: some error"}, 636 {tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"}, 637 {tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"}, 638 {tpmEnabled: true, provisioningErr: mockErr, provisioningCalls: 1, expectedErr: "cannot provision TPM: some error"}, 639 {tpmEnabled: true, sealErr: mockErr, provisioningCalls: 1, sealCalls: 1, expectedErr: "some error"}, 640 {tpmEnabled: true, skipProvision: true, provisioningCalls: 0, sealCalls: 1, expectedErr: ""}, 641 {tpmEnabled: true, provisioningCalls: 1, sealCalls: 1, expectedErr: ""}, 642 } { 643 tmpDir := c.MkDir() 644 var mockBF []bootloader.BootFile 645 for _, name := range []string{"a", "b", "c", "d"} { 646 mockFileName := filepath.Join(tmpDir, name) 647 err := ioutil.WriteFile(mockFileName, nil, 0644) 648 c.Assert(err, IsNil) 649 mockBF = append(mockBF, bootloader.NewBootFile("", mockFileName, bootloader.RoleRecovery)) 650 } 651 652 if tc.missingFile { 653 mockBF[0].Path = "/does/not/exist" 654 } 655 656 var kernelSnap snap.Container 657 snapPath := filepath.Join(tmpDir, "kernel.snap") 658 if tc.badSnapFile { 659 err := ioutil.WriteFile(snapPath, nil, 0644) 660 c.Assert(err, IsNil) 661 } else { 662 var err error 663 kernelSnap, err = createMockSnapFile(c.MkDir(), snapPath, "kernel") 664 c.Assert(err, IsNil) 665 } 666 667 mockBF = append(mockBF, bootloader.NewBootFile(snapPath, "kernel.efi", bootloader.RoleRecovery)) 668 669 myAuthKey := &ecdsa.PrivateKey{} 670 671 myParams := secboot.SealKeysParams{ 672 ModelParams: []*secboot.SealKeyModelParams{ 673 { 674 EFILoadChains: []*secboot.LoadChain{ 675 secboot.NewLoadChain(mockBF[0], 676 secboot.NewLoadChain(mockBF[4])), 677 }, 678 KernelCmdlines: []string{"cmdline1"}, 679 Model: &asserts.Model{}, 680 }, 681 { 682 EFILoadChains: []*secboot.LoadChain{ 683 secboot.NewLoadChain(mockBF[0], 684 secboot.NewLoadChain(mockBF[2], 685 secboot.NewLoadChain(mockBF[4])), 686 secboot.NewLoadChain(mockBF[3], 687 secboot.NewLoadChain(mockBF[4]))), 688 secboot.NewLoadChain(mockBF[1], 689 secboot.NewLoadChain(mockBF[2], 690 secboot.NewLoadChain(mockBF[4])), 691 secboot.NewLoadChain(mockBF[3], 692 secboot.NewLoadChain(mockBF[4]))), 693 }, 694 KernelCmdlines: []string{"cmdline2", "cmdline3"}, 695 Model: &asserts.Model{}, 696 }, 697 }, 698 TPMPolicyAuthKey: myAuthKey, 699 TPMPolicyAuthKeyFile: filepath.Join(tmpDir, "policy-auth-key-file"), 700 TPMLockoutAuthFile: filepath.Join(tmpDir, "lockout-auth-file"), 701 TPMProvision: !tc.skipProvision, 702 PCRPolicyCounterHandle: 42, 703 } 704 705 myKey := secboot.EncryptionKey{} 706 myKey2 := secboot.EncryptionKey{} 707 for i := range myKey { 708 myKey[i] = byte(i) 709 myKey2[i] = byte(128 + i) 710 } 711 712 myKeys := []secboot.SealKeyRequest{ 713 { 714 Key: myKey, 715 KeyFile: "keyfile", 716 }, 717 { 718 Key: myKey2, 719 KeyFile: "keyfile2", 720 }, 721 } 722 723 // events for 724 // a -> kernel 725 sequences1 := []*sb.EFIImageLoadEvent{ 726 { 727 Source: sb.Firmware, 728 Image: sb.FileEFIImage(mockBF[0].Path), 729 Next: []*sb.EFIImageLoadEvent{ 730 { 731 Source: sb.Shim, 732 Image: sb.SnapFileEFIImage{ 733 Container: kernelSnap, 734 FileName: "kernel.efi", 735 }, 736 }, 737 }, 738 }, 739 } 740 741 // "cdk" events for 742 // c -> kernel OR 743 // d -> kernel 744 cdk := []*sb.EFIImageLoadEvent{ 745 { 746 Source: sb.Shim, 747 Image: sb.FileEFIImage(mockBF[2].Path), 748 Next: []*sb.EFIImageLoadEvent{ 749 { 750 Source: sb.Shim, 751 Image: sb.SnapFileEFIImage{ 752 Container: kernelSnap, 753 FileName: "kernel.efi", 754 }, 755 }, 756 }, 757 }, 758 { 759 Source: sb.Shim, 760 Image: sb.FileEFIImage(mockBF[3].Path), 761 Next: []*sb.EFIImageLoadEvent{ 762 { 763 Source: sb.Shim, 764 Image: sb.SnapFileEFIImage{ 765 Container: kernelSnap, 766 FileName: "kernel.efi", 767 }, 768 }, 769 }, 770 }, 771 } 772 773 // events for 774 // a -> "cdk" 775 // b -> "cdk" 776 sequences2 := []*sb.EFIImageLoadEvent{ 777 { 778 Source: sb.Firmware, 779 Image: sb.FileEFIImage(mockBF[0].Path), 780 Next: cdk, 781 }, 782 { 783 Source: sb.Firmware, 784 Image: sb.FileEFIImage(mockBF[1].Path), 785 Next: cdk, 786 }, 787 } 788 789 tpm, restore := mockSbTPMConnection(c, tc.tpmErr) 790 defer restore() 791 792 // mock adding EFI secure boot policy profile 793 var pcrProfile *sb.PCRProtectionProfile 794 addEFISbPolicyCalls := 0 795 restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error { 796 addEFISbPolicyCalls++ 797 pcrProfile = profile 798 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 799 switch addEFISbPolicyCalls { 800 case 1: 801 c.Assert(params.LoadSequences, DeepEquals, sequences1) 802 case 2: 803 c.Assert(params.LoadSequences, DeepEquals, sequences2) 804 default: 805 c.Error("AddEFISecureBootPolicyProfile shouldn't be called a third time") 806 } 807 return tc.addEFISbPolicyErr 808 }) 809 defer restore() 810 811 // mock adding EFI boot manager profile 812 addEFIBootManagerCalls := 0 813 restore = secboot.MockSbAddEFIBootManagerProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error { 814 addEFIBootManagerCalls++ 815 c.Assert(profile, Equals, pcrProfile) 816 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 817 switch addEFISbPolicyCalls { 818 case 1: 819 c.Assert(params.LoadSequences, DeepEquals, sequences1) 820 case 2: 821 c.Assert(params.LoadSequences, DeepEquals, sequences2) 822 default: 823 c.Error("AddEFIBootManagerProfile shouldn't be called a third time") 824 } 825 return tc.addEFIBootManagerErr 826 }) 827 defer restore() 828 829 // mock adding systemd EFI stub profile 830 addSystemdEfiStubCalls := 0 831 restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error { 832 addSystemdEfiStubCalls++ 833 c.Assert(profile, Equals, pcrProfile) 834 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 835 c.Assert(params.PCRIndex, Equals, 12) 836 switch addSystemdEfiStubCalls { 837 case 1: 838 c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[0].KernelCmdlines) 839 case 2: 840 c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[1].KernelCmdlines) 841 default: 842 c.Error("AddSystemdEFIStubProfile shouldn't be called a third time") 843 } 844 return tc.addSystemdEFIStubErr 845 }) 846 defer restore() 847 848 // mock adding snap model profile 849 addSnapModelCalls := 0 850 restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error { 851 addSnapModelCalls++ 852 c.Assert(profile, Equals, pcrProfile) 853 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 854 c.Assert(params.PCRIndex, Equals, 12) 855 switch addSnapModelCalls { 856 case 1: 857 c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[0].Model) 858 case 2: 859 c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[1].Model) 860 default: 861 c.Error("AddSnapModelProfile shouldn't be called a third time") 862 } 863 return tc.addSnapModelErr 864 }) 865 defer restore() 866 867 // mock provisioning 868 provisioningCalls := 0 869 restore = secboot.MockProvisionTPM(func(t *sb.TPMConnection, mode sb.ProvisionMode, newLockoutAuth []byte) error { 870 provisioningCalls++ 871 c.Assert(t, Equals, tpm) 872 c.Assert(mode, Equals, sb.ProvisionModeFull) 873 c.Assert(myParams.TPMLockoutAuthFile, testutil.FilePresent) 874 return tc.provisioningErr 875 }) 876 defer restore() 877 878 // mock sealing 879 sealCalls := 0 880 restore = secboot.MockSbSealKeyToTPMMultiple(func(t *sb.TPMConnection, kr []*sb.SealKeyRequest, params *sb.KeyCreationParams) (sb.TPMPolicyAuthKey, error) { 881 sealCalls++ 882 c.Assert(t, Equals, tpm) 883 c.Assert(kr, DeepEquals, []*sb.SealKeyRequest{{Key: myKey[:], Path: "keyfile"}, {Key: myKey2[:], Path: "keyfile2"}}) 884 c.Assert(params.AuthKey, Equals, myAuthKey) 885 c.Assert(params.PCRPolicyCounterHandle, Equals, tpm2.Handle(42)) 886 return sb.TPMPolicyAuthKey{}, tc.sealErr 887 }) 888 defer restore() 889 890 // mock TPM enabled check 891 restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool { 892 return tc.tpmEnabled 893 }) 894 defer restore() 895 896 err := secboot.SealKeys(myKeys, &myParams) 897 if tc.expectedErr == "" { 898 c.Assert(err, IsNil) 899 c.Assert(addEFISbPolicyCalls, Equals, 2) 900 c.Assert(addSystemdEfiStubCalls, Equals, 2) 901 c.Assert(addSnapModelCalls, Equals, 2) 902 c.Assert(osutil.FileExists(myParams.TPMPolicyAuthKeyFile), Equals, true) 903 } else { 904 c.Assert(err, ErrorMatches, tc.expectedErr) 905 } 906 c.Assert(provisioningCalls, Equals, tc.provisioningCalls) 907 c.Assert(sealCalls, Equals, tc.sealCalls) 908 909 } 910 } 911 912 func (s *secbootSuite) TestResealKey(c *C) { 913 mockErr := errors.New("some error") 914 915 for _, tc := range []struct { 916 tpmErr error 917 tpmEnabled bool 918 missingFile bool 919 addEFISbPolicyErr error 920 addEFIBootManagerErr error 921 addSystemdEFIStubErr error 922 addSnapModelErr error 923 provisioningErr error 924 resealErr error 925 resealCalls int 926 expectedErr string 927 }{ 928 {tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"}, 929 {tpmEnabled: false, expectedErr: "TPM device is not enabled"}, 930 {tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file .*/file.efi does not exist"}, 931 {tpmEnabled: true, addEFISbPolicyErr: mockErr, expectedErr: "cannot add EFI secure boot policy profile: some error"}, 932 {tpmEnabled: true, addEFIBootManagerErr: mockErr, expectedErr: "cannot add EFI boot manager profile: some error"}, 933 {tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"}, 934 {tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"}, 935 {tpmEnabled: true, resealErr: mockErr, resealCalls: 1, expectedErr: "some error"}, 936 {tpmEnabled: true, resealCalls: 1, expectedErr: ""}, 937 } { 938 mockTPMPolicyAuthKey := []byte{1, 3, 3, 7} 939 mockTPMPolicyAuthKeyFile := filepath.Join(c.MkDir(), "policy-auth-key-file") 940 err := ioutil.WriteFile(mockTPMPolicyAuthKeyFile, mockTPMPolicyAuthKey, 0600) 941 c.Assert(err, IsNil) 942 943 mockEFI := bootloader.NewBootFile("", filepath.Join(c.MkDir(), "file.efi"), bootloader.RoleRecovery) 944 if !tc.missingFile { 945 err := ioutil.WriteFile(mockEFI.Path, nil, 0644) 946 c.Assert(err, IsNil) 947 } 948 949 myParams := &secboot.ResealKeysParams{ 950 ModelParams: []*secboot.SealKeyModelParams{ 951 { 952 EFILoadChains: []*secboot.LoadChain{secboot.NewLoadChain(mockEFI)}, 953 KernelCmdlines: []string{"cmdline"}, 954 Model: &asserts.Model{}, 955 }, 956 }, 957 KeyFiles: []string{"keyfile", "keyfile2"}, 958 TPMPolicyAuthKeyFile: mockTPMPolicyAuthKeyFile, 959 } 960 961 sequences := []*sb.EFIImageLoadEvent{ 962 { 963 Source: sb.Firmware, 964 Image: sb.FileEFIImage(mockEFI.Path), 965 }, 966 } 967 968 // mock TPM connection 969 tpm, restore := mockSbTPMConnection(c, tc.tpmErr) 970 defer restore() 971 972 // mock TPM enabled check 973 restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool { 974 return tc.tpmEnabled 975 }) 976 defer restore() 977 978 // mock adding EFI secure boot policy profile 979 var pcrProfile *sb.PCRProtectionProfile 980 addEFISbPolicyCalls := 0 981 restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error { 982 addEFISbPolicyCalls++ 983 pcrProfile = profile 984 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 985 c.Assert(params.LoadSequences, DeepEquals, sequences) 986 return tc.addEFISbPolicyErr 987 }) 988 defer restore() 989 990 // mock adding EFI boot manager profile 991 addEFIBootManagerCalls := 0 992 restore = secboot.MockSbAddEFIBootManagerProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error { 993 addEFIBootManagerCalls++ 994 c.Assert(profile, Equals, pcrProfile) 995 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 996 c.Assert(params.LoadSequences, DeepEquals, sequences) 997 return tc.addEFIBootManagerErr 998 }) 999 defer restore() 1000 1001 // mock adding systemd EFI stub profile 1002 addSystemdEfiStubCalls := 0 1003 restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error { 1004 addSystemdEfiStubCalls++ 1005 c.Assert(profile, Equals, pcrProfile) 1006 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 1007 c.Assert(params.PCRIndex, Equals, 12) 1008 c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[0].KernelCmdlines) 1009 return tc.addSystemdEFIStubErr 1010 }) 1011 defer restore() 1012 1013 // mock adding snap model profile 1014 addSnapModelCalls := 0 1015 restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error { 1016 addSnapModelCalls++ 1017 c.Assert(profile, Equals, pcrProfile) 1018 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 1019 c.Assert(params.PCRIndex, Equals, 12) 1020 c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[0].Model) 1021 return tc.addSnapModelErr 1022 }) 1023 defer restore() 1024 1025 // mock PCR protection policy update 1026 resealCalls := 0 1027 restore = secboot.MockSbUpdateKeyPCRProtectionPolicyMultiple(func(t *sb.TPMConnection, keyPaths []string, authKey sb.TPMPolicyAuthKey, profile *sb.PCRProtectionProfile) error { 1028 resealCalls++ 1029 c.Assert(t, Equals, tpm) 1030 c.Assert(keyPaths, DeepEquals, []string{"keyfile", "keyfile2"}) 1031 c.Assert(authKey, DeepEquals, sb.TPMPolicyAuthKey(mockTPMPolicyAuthKey)) 1032 c.Assert(profile, Equals, pcrProfile) 1033 return tc.resealErr 1034 }) 1035 defer restore() 1036 1037 err = secboot.ResealKeys(myParams) 1038 if tc.expectedErr == "" { 1039 c.Assert(err, IsNil) 1040 c.Assert(addEFISbPolicyCalls, Equals, 1) 1041 c.Assert(addSystemdEfiStubCalls, Equals, 1) 1042 c.Assert(addSnapModelCalls, Equals, 1) 1043 } else { 1044 c.Assert(err, ErrorMatches, tc.expectedErr) 1045 } 1046 c.Assert(resealCalls, Equals, tc.resealCalls) 1047 } 1048 } 1049 1050 func (s *secbootSuite) TestSealKeyNoModelParams(c *C) { 1051 myKeys := []secboot.SealKeyRequest{ 1052 { 1053 Key: secboot.EncryptionKey{}, 1054 KeyFile: "keyfile", 1055 }, 1056 } 1057 myParams := secboot.SealKeysParams{ 1058 TPMPolicyAuthKeyFile: "policy-auth-key-file", 1059 TPMLockoutAuthFile: "lockout-auth-file", 1060 } 1061 1062 err := secboot.SealKeys(myKeys, &myParams) 1063 c.Assert(err, ErrorMatches, "at least one set of model-specific parameters is required") 1064 } 1065 1066 func createMockSnapFile(snapDir, snapPath, snapType string) (snap.Container, error) { 1067 snapYamlPath := filepath.Join(snapDir, "meta/snap.yaml") 1068 if err := os.MkdirAll(filepath.Dir(snapYamlPath), 0755); err != nil { 1069 return nil, err 1070 } 1071 if err := ioutil.WriteFile(snapYamlPath, []byte("name: foo"), 0644); err != nil { 1072 return nil, err 1073 } 1074 sqfs := squashfs.New(snapPath) 1075 if err := sqfs.Build(snapDir, &squashfs.BuildOpts{SnapType: snapType}); err != nil { 1076 return nil, err 1077 } 1078 return snapfile.Open(snapPath) 1079 } 1080 1081 func mockSbTPMConnection(c *C, tpmErr error) (*sb.TPMConnection, func()) { 1082 tcti, err := tpm2.OpenTPMDevice("/dev/null") 1083 c.Assert(err, IsNil) 1084 tpmctx, err := tpm2.NewTPMContext(tcti) 1085 c.Assert(err, IsNil) 1086 tpm := &sb.TPMConnection{TPMContext: tpmctx} 1087 restore := secboot.MockSbConnectToDefaultTPM(func() (*sb.TPMConnection, error) { 1088 if tpmErr != nil { 1089 return nil, tpmErr 1090 } 1091 return tpm, nil 1092 }) 1093 return tpm, restore 1094 } 1095 1096 func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyBadDisk(c *C) { 1097 disk := &disks.MockDiskMapping{ 1098 FilesystemLabelToPartUUID: map[string]string{}, 1099 } 1100 unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo")) 1101 c.Assert(err, ErrorMatches, `filesystem label "ubuntu-save-enc" not found`) 1102 c.Check(unlockRes, DeepEquals, secboot.UnlockResult{}) 1103 } 1104 1105 func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyHappy(c *C) { 1106 disk := &disks.MockDiskMapping{ 1107 FilesystemLabelToPartUUID: map[string]string{ 1108 "ubuntu-save-enc": "123-123-123", 1109 }, 1110 } 1111 restore := secboot.MockRandomKernelUUID(func() string { 1112 return "random-uuid-123-123" 1113 }) 1114 defer restore() 1115 restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte, 1116 options *sb.ActivateVolumeOptions) error { 1117 c.Check(options, DeepEquals, &sb.ActivateVolumeOptions{}) 1118 c.Check(key, DeepEquals, []byte("fooo")) 1119 c.Check(volumeName, Matches, "ubuntu-save-random-uuid-123-123") 1120 c.Check(sourceDevicePath, Equals, "/dev/disk/by-partuuid/123-123-123") 1121 return nil 1122 }) 1123 defer restore() 1124 unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo")) 1125 c.Assert(err, IsNil) 1126 c.Check(unlockRes, DeepEquals, secboot.UnlockResult{ 1127 PartDevice: "/dev/disk/by-partuuid/123-123-123", 1128 FsDevice: "/dev/mapper/ubuntu-save-random-uuid-123-123", 1129 IsEncrypted: true, 1130 UnlockMethod: secboot.UnlockedWithKey, 1131 }) 1132 } 1133 1134 func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyErr(c *C) { 1135 disk := &disks.MockDiskMapping{ 1136 FilesystemLabelToPartUUID: map[string]string{ 1137 "ubuntu-save-enc": "123-123-123", 1138 }, 1139 } 1140 restore := secboot.MockRandomKernelUUID(func() string { 1141 return "random-uuid-123-123" 1142 }) 1143 defer restore() 1144 restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte, 1145 options *sb.ActivateVolumeOptions) error { 1146 return fmt.Errorf("failed") 1147 }) 1148 defer restore() 1149 unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo")) 1150 c.Assert(err, ErrorMatches, "failed") 1151 // we would have at least identified that the device is a decrypted one 1152 c.Check(unlockRes, DeepEquals, secboot.UnlockResult{ 1153 IsEncrypted: true, 1154 PartDevice: "/dev/disk/by-partuuid/123-123-123", 1155 FsDevice: "", 1156 }) 1157 } 1158 1159 func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyTruncatesStreamFiles(c *C) { 1160 // this test uses a real systemd-run --user so check here if that 1161 // actually works 1162 if output, err := exec.Command("systemd-run", "--user", "--wait", "--collect", "--service-type=exec", "true").CombinedOutput(); err != nil { 1163 c.Skip(fmt.Sprintf("systemd-run not working: %v", osutil.OutputErr(output, err))) 1164 } 1165 1166 // create the temporary output file streams with garbage data to ensure that 1167 // by the time the hook runs the files are emptied and recreated with the 1168 // right permissions 1169 streamFiles := []string{} 1170 for _, stream := range []string{"stdin", "stdout", "stderr"} { 1171 streamFile := filepath.Join(dirs.GlobalRootDir, "/run/fde-reveal-key/fde-reveal-key."+stream) 1172 streamFiles = append(streamFiles, streamFile) 1173 // make the dir 0700 1174 err := os.MkdirAll(filepath.Dir(streamFile), 0700) 1175 c.Assert(err, IsNil) 1176 // but make the file world-readable as it should be reset to 0600 before 1177 // the hook is run 1178 err = ioutil.WriteFile(streamFile, []byte("blah blah blah blah blah blah blah blah blah blah"), 0755) 1179 c.Assert(err, IsNil) 1180 } 1181 1182 restore := secboot.MockFDEHasRevealKey(func() bool { 1183 return true 1184 }) 1185 defer restore() 1186 1187 // the hook script only verifies that the stdout file is empty since we 1188 // need to write to the stderr file for performing the test, but we still 1189 // check the stderr file for correct permissions 1190 mockSystemdRun := testutil.MockCommand(c, "fde-reveal-key", fmt.Sprintf(` 1191 # check that stdin has the right sealed key content 1192 if [ "$(cat %[1]s)" != "{\"op\":\"reveal\",\"sealed-key\":\"AQIDBA==\",\"key-name\":\"name\"}" ]; then 1193 echo "test failed: stdin file has wrong content: $(cat %[1]s)" 1>&2 1194 else 1195 echo "stdin file has correct content" 1>&2 1196 fi 1197 1198 # check that stdout is empty 1199 if [ -n "$(cat %[2]s)" ]; then 1200 echo "test failed: stdout file is not empty: $(cat %[2]s)" 1>&2 1201 else 1202 echo "stdout file is correctly empty" 1>&2 1203 fi 1204 1205 # check that stdin has the right 600 perms 1206 if [ "$(stat --format=%%a %[1]s)" != "600" ]; then 1207 echo "test failed: stdin file has wrong permissions: $(stat --format=%%a %[1]s)" 1>&2 1208 else 1209 echo "stdin file has correct 600 permissions" 1>&2 1210 fi 1211 1212 # check that stdout has the right 600 perms 1213 if [ "$(stat --format=%%a %[2]s)" != "600" ]; then 1214 echo "test failed: stdout file has wrong permissions: $(stat --format=%%a %[2]s)" 1>&2 1215 else 1216 echo "stdout file has correct 600 permissions" 1>&2 1217 fi 1218 1219 # check that stderr has the right 600 perms 1220 if [ "$(stat --format=%%a %[3]s)" != "600" ]; then 1221 echo "test failed: stderr file has wrong permissions: $(stat --format=%%a %[3]s)" 1>&2 1222 else 1223 echo "stderr file has correct 600 permissions" 1>&2 1224 fi 1225 1226 echo "making the hook always fail for simpler test code" 1>&2 1227 1228 # always make the hook exit 1 for simpler test code 1229 exit 1 1230 `, streamFiles[0], streamFiles[1], streamFiles[2])) 1231 defer mockSystemdRun.Restore() 1232 restore = secboot.MockFdeRevealKeyCommandExtra([]string{"--user"}) 1233 defer restore() 1234 1235 mockDiskWithEncDev := &disks.MockDiskMapping{ 1236 FilesystemLabelToPartUUID: map[string]string{ 1237 "name-enc": "enc-dev-partuuid", 1238 }, 1239 } 1240 defaultDevice := "name" 1241 mockSealedKeyFile := filepath.Join(c.MkDir(), "vanilla-keyfile") 1242 err := ioutil.WriteFile(mockSealedKeyFile, []byte{1, 2, 3, 4}, 0600) 1243 c.Assert(err, IsNil) 1244 1245 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{} 1246 _, err = secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts) 1247 c.Assert(err, ErrorMatches, `(?s)cannot run fde-reveal-key: 1248 ----- 1249 stdin file has correct content 1250 stdout file is correctly empty 1251 stdin file has correct 600 permissions 1252 stdout file has correct 600 permissions 1253 stderr file has correct 600 permissions 1254 making the hook always fail for simpler test code 1255 service result: exit-code 1256 -----`) 1257 // ensure no tmp files are left behind 1258 c.Check(osutil.FileExists(filepath.Join(dirs.GlobalRootDir, "/run/fde-reveal-key")), Equals, false) 1259 } 1260 1261 func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyErr(c *C) { 1262 // this test uses a real systemd-run --user so check here if that 1263 // actually works 1264 if output, err := exec.Command("systemd-run", "--user", "--wait", "--collect", "--service-type=exec", "/bin/true").CombinedOutput(); err != nil { 1265 c.Skip(fmt.Sprintf("systemd-run not working: %v", osutil.OutputErr(output, err))) 1266 } 1267 1268 restore := secboot.MockFDEHasRevealKey(func() bool { 1269 return true 1270 }) 1271 defer restore() 1272 1273 mockSystemdRun := testutil.MockCommand(c, "fde-reveal-key", `echo failed 1>&2; false`) 1274 defer mockSystemdRun.Restore() 1275 restore = secboot.MockFdeRevealKeyCommandExtra([]string{"--user"}) 1276 defer restore() 1277 1278 mockDiskWithEncDev := &disks.MockDiskMapping{ 1279 FilesystemLabelToPartUUID: map[string]string{ 1280 "name-enc": "enc-dev-partuuid", 1281 }, 1282 } 1283 defaultDevice := "name" 1284 mockSealedKeyFile := filepath.Join(c.MkDir(), "vanilla-keyfile") 1285 err := ioutil.WriteFile(mockSealedKeyFile, []byte{1, 2, 3, 4}, 0600) 1286 c.Assert(err, IsNil) 1287 1288 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{} 1289 _, err = secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts) 1290 c.Assert(err, ErrorMatches, `(?s)cannot run fde-reveal-key: 1291 ----- 1292 failed 1293 service result: exit-code 1294 -----`) 1295 // ensure no tmp files are left behind 1296 c.Check(osutil.FileExists(filepath.Join(dirs.GlobalRootDir, "/run/fde-reveal-key")), Equals, false) 1297 } 1298 1299 func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKey(c *C) { 1300 // this test uses a real systemd-run --user so check here if that 1301 // actually works 1302 if output, err := exec.Command("systemd-run", "--user", "--wait", "--collect", "--service-type=exec", "/bin/true").CombinedOutput(); err != nil { 1303 c.Skip(fmt.Sprintf("systemd-run not working: %v", osutil.OutputErr(output, err))) 1304 } 1305 1306 restore := secboot.MockFDEHasRevealKey(func() bool { 1307 return true 1308 }) 1309 defer restore() 1310 1311 restore = secboot.MockRandomKernelUUID(func() string { 1312 return "random-uuid-for-test" 1313 }) 1314 defer restore() 1315 1316 mockDiskWithEncDev := &disks.MockDiskMapping{ 1317 FilesystemLabelToPartUUID: map[string]string{ 1318 "name-enc": "enc-dev-partuuid", 1319 }, 1320 } 1321 1322 restore = secboot.MockFdeRevealKeyCommandExtra([]string{"--user"}) 1323 defer restore() 1324 fdeRevealKeyStdin := filepath.Join(c.MkDir(), "stdin") 1325 mockSystemdRun := testutil.MockCommand(c, "fde-reveal-key", fmt.Sprintf(` 1326 cat - > %s 1327 printf "unsealed-key-from-hook" 1328 `, fdeRevealKeyStdin)) 1329 defer mockSystemdRun.Restore() 1330 1331 restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte, options *sb.ActivateVolumeOptions) error { 1332 c.Check(string(key), Equals, "unsealed-key-from-hook") 1333 return nil 1334 }) 1335 defer restore() 1336 1337 defaultDevice := "name" 1338 mockSealedKeyFile := filepath.Join(c.MkDir(), "vanilla-keyfile") 1339 err := ioutil.WriteFile(mockSealedKeyFile, []byte("sealed-key"), 0600) 1340 c.Assert(err, IsNil) 1341 1342 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{} 1343 res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts) 1344 c.Assert(err, IsNil) 1345 c.Check(res, DeepEquals, secboot.UnlockResult{ 1346 UnlockMethod: secboot.UnlockedWithSealedKey, 1347 IsEncrypted: true, 1348 PartDevice: "/dev/disk/by-partuuid/enc-dev-partuuid", 1349 FsDevice: "/dev/mapper/name-random-uuid-for-test", 1350 }) 1351 c.Check(mockSystemdRun.Calls(), DeepEquals, [][]string{ 1352 {"fde-reveal-key"}, 1353 }) 1354 c.Check(fdeRevealKeyStdin, testutil.FileEquals, fmt.Sprintf(`{"op":"reveal","sealed-key":%q,"key-name":"name"}`, base64.StdEncoding.EncodeToString([]byte("sealed-key")))) 1355 1356 // ensure no tmp files are left behind 1357 c.Check(osutil.FileExists(filepath.Join(dirs.GlobalRootDir, "/run/fde-reveal-key")), Equals, false) 1358 } 1359 1360 func (s *secbootSuite) TestLockSealedKeysCallsFdeReveal(c *C) { 1361 // this test uses a real systemd-run --user so check here if that 1362 // actually works 1363 if output, err := exec.Command("systemd-run", "--user", "--wait", "--collect", "--service-type=exec", "/bin/true").CombinedOutput(); err != nil { 1364 c.Skip(fmt.Sprintf("systemd-run not working: %v", osutil.OutputErr(output, err))) 1365 } 1366 1367 restore := secboot.MockFDEHasRevealKey(func() bool { 1368 return true 1369 }) 1370 defer restore() 1371 1372 restore = secboot.MockFdeRevealKeyCommandExtra([]string{"--user"}) 1373 defer restore() 1374 fdeRevealKeyStdin := filepath.Join(c.MkDir(), "stdin") 1375 mockSystemdRun := testutil.MockCommand(c, "fde-reveal-key", fmt.Sprintf(` 1376 cat - > %s 1377 `, fdeRevealKeyStdin)) 1378 defer mockSystemdRun.Restore() 1379 1380 err := secboot.LockSealedKeys() 1381 c.Assert(err, IsNil) 1382 c.Check(mockSystemdRun.Calls(), DeepEquals, [][]string{ 1383 {"fde-reveal-key"}, 1384 }) 1385 c.Check(fdeRevealKeyStdin, testutil.FileEquals, `{"op":"lock"}`) 1386 1387 // ensure no tmp files are left behind 1388 c.Check(osutil.FileExists(filepath.Join(dirs.GlobalRootDir, "/run/fde-reveal-key")), Equals, false) 1389 } 1390 1391 func (s *secbootSuite) TestLockSealedKeysHonorsRuntimeMax(c *C) { 1392 // this test uses a real systemd-run --user so check here if that 1393 // actually works 1394 if output, err := exec.Command("systemd-run", "--user", "--wait", "--collect", "--service-type=exec", "/bin/true").CombinedOutput(); err != nil { 1395 c.Skip(fmt.Sprintf("systemd-run not working: %v", osutil.OutputErr(output, err))) 1396 } 1397 1398 restore := secboot.MockFDEHasRevealKey(func() bool { 1399 return true 1400 }) 1401 defer restore() 1402 1403 restore = secboot.MockFdeRevealKeyCommandExtra([]string{"--user"}) 1404 defer restore() 1405 mockSystemdRun := testutil.MockCommand(c, "fde-reveal-key", "sleep 60") 1406 defer mockSystemdRun.Restore() 1407 1408 restore = secboot.MockFdeRevealKeyPollWaitParanoiaFactor(100) 1409 defer restore() 1410 1411 restore = secboot.MockFdeRevealKeyRuntimeMax(100 * time.Millisecond) 1412 defer restore() 1413 1414 err := secboot.LockSealedKeys() 1415 c.Assert(err, ErrorMatches, `cannot run fde-reveal-key "lock": service result: timeout`) 1416 } 1417 1418 func (s *secbootSuite) TestLockSealedKeysHonorsParanoia(c *C) { 1419 // this test uses a real systemd-run --user so check here if that 1420 // actually works 1421 if output, err := exec.Command("systemd-run", "--user", "--wait", "--collect", "--service-type=exec", "/bin/true").CombinedOutput(); err != nil { 1422 c.Skip(fmt.Sprintf("systemd-run not working: %v", osutil.OutputErr(output, err))) 1423 } 1424 1425 restore := secboot.MockFDEHasRevealKey(func() bool { 1426 return true 1427 }) 1428 defer restore() 1429 1430 restore = secboot.MockFdeRevealKeyCommandExtra([]string{"--user"}) 1431 defer restore() 1432 mockSystemdRun := testutil.MockCommand(c, "fde-reveal-key", "sleep 60") 1433 defer mockSystemdRun.Restore() 1434 1435 restore = secboot.MockFdeRevealKeyPollWaitParanoiaFactor(1) 1436 defer restore() 1437 1438 // shorter than the fdeRevealKeyPollWait time 1439 restore = secboot.MockFdeRevealKeyRuntimeMax(1 * time.Millisecond) 1440 defer restore() 1441 1442 err := secboot.LockSealedKeys() 1443 c.Assert(err, ErrorMatches, `cannot run fde-reveal-key "lock": internal error: systemd-run did not honor RuntimeMax=1ms setting`) 1444 }