github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/secboot/secboot_sb_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 // +build !nosecboot 3 4 /* 5 * Copyright (C) 2021 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 "bytes" 25 "crypto/ecdsa" 26 "encoding/base64" 27 "encoding/json" 28 "errors" 29 "fmt" 30 "io" 31 "io/ioutil" 32 "os" 33 "path/filepath" 34 35 "github.com/canonical/go-tpm2" 36 sb "github.com/snapcore/secboot" 37 . "gopkg.in/check.v1" 38 39 "github.com/snapcore/snapd/asserts" 40 "github.com/snapcore/snapd/asserts/assertstest" 41 "github.com/snapcore/snapd/bootloader" 42 "github.com/snapcore/snapd/bootloader/efi" 43 "github.com/snapcore/snapd/dirs" 44 "github.com/snapcore/snapd/kernel/fde" 45 "github.com/snapcore/snapd/osutil" 46 "github.com/snapcore/snapd/osutil/disks" 47 "github.com/snapcore/snapd/secboot" 48 "github.com/snapcore/snapd/snap" 49 "github.com/snapcore/snapd/snap/snapfile" 50 "github.com/snapcore/snapd/snap/squashfs" 51 "github.com/snapcore/snapd/testutil" 52 ) 53 54 type secbootSuite struct { 55 testutil.BaseTest 56 } 57 58 var _ = Suite(&secbootSuite{}) 59 60 func (s *secbootSuite) SetUpTest(c *C) { 61 rootDir := c.MkDir() 62 err := os.MkdirAll(filepath.Join(rootDir, "/run"), 0755) 63 c.Assert(err, IsNil) 64 dirs.SetRootDir(rootDir) 65 s.AddCleanup(func() { dirs.SetRootDir("/") }) 66 } 67 68 func (s *secbootSuite) TestCheckTPMKeySealingSupported(c *C) { 69 c.Check(secboot.WithSecbootSupport, Equals, true) 70 71 sbEmpty := []uint8{} 72 sbEnabled := []uint8{1} 73 sbDisabled := []uint8{0} 74 efiNotSupported := []uint8(nil) 75 tpmErr := errors.New("TPM error") 76 77 type testCase struct { 78 tpmErr error 79 tpmEnabled bool 80 sbData []uint8 81 err string 82 } 83 for i, tc := range []testCase{ 84 // happy case 85 {tpmErr: nil, tpmEnabled: true, sbData: sbEnabled, err: ""}, 86 // secure boot EFI var is empty 87 {tpmErr: nil, tpmEnabled: true, sbData: sbEmpty, err: "secure boot variable does not exist"}, 88 // secure boot is disabled 89 {tpmErr: nil, tpmEnabled: true, sbData: sbDisabled, err: "secure boot is disabled"}, 90 // EFI not supported 91 {tpmErr: nil, tpmEnabled: true, sbData: efiNotSupported, err: "not a supported EFI system"}, 92 // TPM connection error 93 {tpmErr: tpmErr, sbData: sbEnabled, err: "cannot connect to TPM device: TPM error"}, 94 // TPM was detected but it's not enabled 95 {tpmErr: nil, tpmEnabled: false, sbData: sbEnabled, err: "TPM device is not enabled"}, 96 // No TPM device 97 {tpmErr: sb.ErrNoTPM2Device, sbData: sbEnabled, err: "cannot connect to TPM device: no TPM2 device is available"}, 98 } { 99 c.Logf("%d: %v %v %v %q", i, tc.tpmErr, tc.tpmEnabled, tc.sbData, tc.err) 100 101 _, restore := mockSbTPMConnection(c, tc.tpmErr) 102 defer restore() 103 104 restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 105 return tc.tpmEnabled 106 }) 107 defer restore() 108 109 var vars map[string][]byte 110 if tc.sbData != nil { 111 vars = map[string][]byte{"SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c": tc.sbData} 112 } 113 restoreEfiVars := efi.MockVars(vars, nil) 114 defer restoreEfiVars() 115 116 err := secboot.CheckTPMKeySealingSupported() 117 if tc.err == "" { 118 c.Assert(err, IsNil) 119 } else { 120 c.Assert(err, ErrorMatches, tc.err) 121 } 122 } 123 } 124 125 func (s *secbootSuite) TestMeasureSnapSystemEpochWhenPossible(c *C) { 126 for _, tc := range []struct { 127 tpmErr error 128 tpmEnabled bool 129 callNum int 130 err string 131 }{ 132 { 133 // normal connection to the TPM device 134 tpmErr: nil, tpmEnabled: true, callNum: 1, err: "", 135 }, 136 { 137 // TPM device exists but returns error 138 tpmErr: errors.New("tpm error"), callNum: 0, 139 err: "cannot measure snap system epoch: cannot open TPM connection: tpm error", 140 }, 141 { 142 // TPM device exists but is disabled 143 tpmErr: nil, tpmEnabled: false, 144 }, 145 { 146 // TPM device does not exist 147 tpmErr: sb.ErrNoTPM2Device, 148 }, 149 } { 150 mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr) 151 defer restore() 152 153 restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 154 return tc.tpmEnabled 155 }) 156 defer restore() 157 158 calls := 0 159 restore = secboot.MockSbMeasureSnapSystemEpochToTPM(func(tpm *sb.TPMConnection, pcrIndex int) error { 160 calls++ 161 c.Assert(tpm, Equals, mockTpm) 162 c.Assert(pcrIndex, Equals, 12) 163 return nil 164 }) 165 defer restore() 166 167 err := secboot.MeasureSnapSystemEpochWhenPossible() 168 if tc.err == "" { 169 c.Assert(err, IsNil) 170 } else { 171 c.Assert(err, ErrorMatches, tc.err) 172 } 173 c.Assert(calls, Equals, tc.callNum) 174 } 175 } 176 177 func (s *secbootSuite) TestMeasureSnapModelWhenPossible(c *C) { 178 for i, tc := range []struct { 179 tpmErr error 180 tpmEnabled bool 181 modelErr error 182 callNum int 183 err string 184 }{ 185 { 186 // normal connection to the TPM device 187 tpmErr: nil, tpmEnabled: true, modelErr: nil, callNum: 1, err: "", 188 }, 189 { 190 // normal connection to the TPM device with model error 191 tpmErr: nil, tpmEnabled: true, modelErr: errors.New("model error"), callNum: 0, 192 err: "cannot measure snap model: model error", 193 }, 194 { 195 // TPM device exists but returns error 196 tpmErr: errors.New("tpm error"), callNum: 0, 197 err: "cannot measure snap model: cannot open TPM connection: tpm error", 198 }, 199 { 200 // TPM device exists but is disabled 201 tpmErr: nil, tpmEnabled: false, 202 }, 203 { 204 // TPM device does not exist 205 tpmErr: sb.ErrNoTPM2Device, 206 }, 207 } { 208 c.Logf("%d: tpmErr:%v tpmEnabled:%v", i, tc.tpmErr, tc.tpmEnabled) 209 mockModel := &asserts.Model{} 210 211 mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr) 212 defer restore() 213 214 restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 215 return tc.tpmEnabled 216 }) 217 defer restore() 218 219 calls := 0 220 restore = secboot.MockSbMeasureSnapModelToTPM(func(tpm *sb.TPMConnection, pcrIndex int, model sb.SnapModel) error { 221 calls++ 222 c.Assert(tpm, Equals, mockTpm) 223 c.Assert(model, Equals, mockModel) 224 c.Assert(pcrIndex, Equals, 12) 225 return nil 226 }) 227 defer restore() 228 229 findModel := func() (*asserts.Model, error) { 230 if tc.modelErr != nil { 231 return nil, tc.modelErr 232 } 233 return mockModel, nil 234 } 235 236 err := secboot.MeasureSnapModelWhenPossible(findModel) 237 if tc.err == "" { 238 c.Assert(err, IsNil) 239 } else { 240 c.Assert(err, ErrorMatches, tc.err) 241 } 242 c.Assert(calls, Equals, tc.callNum) 243 } 244 } 245 246 func (s *secbootSuite) TestLockTPMSealedKeys(c *C) { 247 tt := []struct { 248 tpmErr error 249 tpmEnabled bool 250 lockOk bool 251 expError string 252 }{ 253 // can't connect to tpm 254 { 255 tpmErr: fmt.Errorf("failed to connect to tpm"), 256 expError: "cannot lock TPM: failed to connect to tpm", 257 }, 258 // no TPM2 device, shouldn't return an error 259 { 260 tpmErr: sb.ErrNoTPM2Device, 261 }, 262 // tpm is not enabled but we can lock it 263 { 264 tpmEnabled: false, 265 lockOk: true, 266 }, 267 // can't lock pcr protection profile 268 { 269 tpmEnabled: true, 270 lockOk: false, 271 expError: "block failed", 272 }, 273 // tpm enabled, we can lock it 274 { 275 tpmEnabled: true, 276 lockOk: true, 277 }, 278 } 279 280 for _, tc := range tt { 281 mockSbTPM, restoreConnect := mockSbTPMConnection(c, tc.tpmErr) 282 defer restoreConnect() 283 284 restore := secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 285 return tc.tpmEnabled 286 }) 287 defer restore() 288 289 sbBlockPCRProtectionPolicesCalls := 0 290 restore = secboot.MockSbBlockPCRProtectionPolicies(func(tpm *sb.TPMConnection, pcrs []int) error { 291 sbBlockPCRProtectionPolicesCalls++ 292 c.Assert(tpm, Equals, mockSbTPM) 293 c.Assert(pcrs, DeepEquals, []int{12}) 294 if tc.lockOk { 295 return nil 296 } 297 return errors.New("block failed") 298 }) 299 defer restore() 300 301 err := secboot.LockTPMSealedKeys() 302 if tc.expError == "" { 303 c.Assert(err, IsNil) 304 } else { 305 c.Assert(err, ErrorMatches, tc.expError) 306 } 307 // if there was no TPM connection error, we should have tried to lock it 308 if tc.tpmErr == nil { 309 c.Assert(sbBlockPCRProtectionPolicesCalls, Equals, 1) 310 } else { 311 c.Assert(sbBlockPCRProtectionPolicesCalls, Equals, 0) 312 } 313 } 314 } 315 316 func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) { 317 318 // setup mock disks to use for locating the partition 319 // restore := disks.MockMountPointDisksToPartitionMapping() 320 // defer restore() 321 322 mockDiskWithEncDev := &disks.MockDiskMapping{ 323 FilesystemLabelToPartUUID: map[string]string{ 324 "name-enc": "enc-dev-partuuid", 325 }, 326 } 327 328 mockDiskWithoutAnyDev := &disks.MockDiskMapping{ 329 FilesystemLabelToPartUUID: map[string]string{}, 330 } 331 332 mockDiskWithUnencDev := &disks.MockDiskMapping{ 333 FilesystemLabelToPartUUID: map[string]string{ 334 "name": "unenc-dev-partuuid", 335 }, 336 } 337 338 for idx, tc := range []struct { 339 tpmErr error 340 keyfile string // the keyfile to be used to unseal 341 tpmEnabled bool // TPM storage and endorsement hierarchies disabled, only relevant if TPM available 342 hasEncdev bool // an encrypted device exists 343 rkAllow bool // allow recovery key activation 344 rkErr error // recovery key unlock error, only relevant if TPM not available 345 activated bool // the activation operation succeeded 346 activateErr error // the activation error 347 err string 348 skipDiskEnsureCheck bool // whether to check to ensure the mock disk contains the device label 349 expUnlockMethod secboot.UnlockMethod 350 disk *disks.MockDiskMapping 351 }{ 352 { 353 // happy case with tpm and encrypted device 354 tpmEnabled: true, hasEncdev: true, 355 activated: true, 356 disk: mockDiskWithEncDev, 357 expUnlockMethod: secboot.UnlockedWithSealedKey, 358 }, { 359 // happy case with tpm and encrypted device 360 // with an alternative keyfile 361 tpmEnabled: true, hasEncdev: true, 362 activated: true, 363 disk: mockDiskWithEncDev, 364 keyfile: "some-other-keyfile", 365 expUnlockMethod: secboot.UnlockedWithSealedKey, 366 }, { 367 // device activation fails 368 tpmEnabled: true, hasEncdev: true, 369 err: "cannot activate encrypted device .*: activation error", 370 disk: mockDiskWithEncDev, 371 }, { 372 // device activation fails 373 tpmEnabled: true, hasEncdev: true, 374 err: "cannot activate encrypted device .*: activation error", 375 disk: mockDiskWithEncDev, 376 }, { 377 // happy case without encrypted device 378 tpmEnabled: true, 379 disk: mockDiskWithUnencDev, 380 }, { 381 // happy case with tpm and encrypted device, activation 382 // with recovery key 383 tpmEnabled: true, hasEncdev: true, activated: true, 384 activateErr: &sb.ActivateWithTPMSealedKeyError{ 385 // activation error with nil recovery key error 386 // implies volume activated successfully using 387 // the recovery key, 388 RecoveryKeyUsageErr: nil, 389 }, 390 disk: mockDiskWithEncDev, 391 expUnlockMethod: secboot.UnlockedWithRecoveryKey, 392 }, { 393 // tpm and encrypted device, successful activation, but 394 // recovery key non-nil is an unexpected state 395 tpmEnabled: true, hasEncdev: true, activated: true, 396 activateErr: &sb.ActivateWithTPMSealedKeyError{ 397 RecoveryKeyUsageErr: fmt.Errorf("unexpected"), 398 }, 399 expUnlockMethod: secboot.UnlockStatusUnknown, 400 err: `internal error: volume activated with unexpected error: .* \(unexpected\)`, 401 disk: mockDiskWithEncDev, 402 }, { 403 // tpm error, no encrypted device 404 tpmErr: errors.New("tpm error"), 405 disk: mockDiskWithUnencDev, 406 }, { 407 // tpm error, has encrypted device 408 tpmErr: errors.New("tpm error"), hasEncdev: true, 409 err: `cannot unlock encrypted device "name": tpm error`, 410 disk: mockDiskWithEncDev, 411 }, { 412 // tpm disabled, no encrypted device 413 disk: mockDiskWithUnencDev, 414 }, { 415 // tpm disabled, has encrypted device, unlocked using the recovery key 416 hasEncdev: true, 417 rkAllow: true, 418 disk: mockDiskWithEncDev, 419 expUnlockMethod: secboot.UnlockedWithRecoveryKey, 420 }, { 421 // tpm disabled, has encrypted device, recovery key unlocking fails 422 hasEncdev: true, rkErr: errors.New("cannot unlock with recovery key"), 423 rkAllow: true, 424 disk: mockDiskWithEncDev, 425 err: `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`, 426 }, { 427 // no tpm, has encrypted device, unlocked using the recovery key 428 tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, 429 rkAllow: true, 430 disk: mockDiskWithEncDev, 431 expUnlockMethod: secboot.UnlockedWithRecoveryKey, 432 }, { 433 // no tpm, has encrypted device, unlocking with recovery key not allowed 434 tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, 435 disk: mockDiskWithEncDev, 436 err: `cannot activate encrypted device ".*/enc-dev-partuuid": activation error`, 437 }, { 438 // no tpm, has encrypted device, recovery key unlocking fails 439 rkErr: errors.New("cannot unlock with recovery key"), 440 tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, 441 rkAllow: true, 442 disk: mockDiskWithEncDev, 443 err: `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`, 444 }, { 445 // no tpm, no encrypted device 446 tpmErr: sb.ErrNoTPM2Device, 447 disk: mockDiskWithUnencDev, 448 }, { 449 // no disks at all 450 disk: mockDiskWithoutAnyDev, 451 skipDiskEnsureCheck: true, 452 // error is specifically for failing to find name, NOT name-enc, we 453 // will properly fall back to looking for name if we didn't find 454 // name-enc 455 err: "error enumerating partitions for disk to find unencrypted device \"name\": filesystem label \"name\" not found", 456 }, 457 } { 458 randomUUID := fmt.Sprintf("random-uuid-for-test-%d", idx) 459 restore := secboot.MockRandomKernelUUID(func() string { 460 return randomUUID 461 }) 462 defer restore() 463 464 c.Logf("tc %v: %+v", idx, tc) 465 _, restoreConnect := mockSbTPMConnection(c, tc.tpmErr) 466 defer restoreConnect() 467 468 restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { 469 return tc.tpmEnabled 470 }) 471 defer restore() 472 473 defaultDevice := "name" 474 475 fsLabel := defaultDevice 476 if tc.hasEncdev { 477 fsLabel += "-enc" 478 } 479 partuuid, ok := tc.disk.FilesystemLabelToPartUUID[fsLabel] 480 if !tc.skipDiskEnsureCheck { 481 c.Assert(ok, Equals, true) 482 } 483 devicePath := filepath.Join("/dev/disk/by-partuuid", partuuid) 484 485 expKeyPath := tc.keyfile 486 if expKeyPath == "" { 487 expKeyPath = "vanilla-keyfile" 488 } 489 490 restore = secboot.MockSbActivateVolumeWithTPMSealedKey(func(tpm *sb.TPMConnection, volumeName, sourceDevicePath, 491 keyPath string, pinReader io.Reader, options *sb.ActivateVolumeOptions) (bool, error) { 492 c.Assert(volumeName, Equals, "name-"+randomUUID) 493 c.Assert(sourceDevicePath, Equals, devicePath) 494 c.Assert(keyPath, Equals, expKeyPath) 495 if tc.rkAllow { 496 c.Assert(*options, DeepEquals, sb.ActivateVolumeOptions{ 497 PassphraseTries: 1, 498 RecoveryKeyTries: 3, 499 KeyringPrefix: "ubuntu-fde", 500 }) 501 } else { 502 c.Assert(*options, DeepEquals, sb.ActivateVolumeOptions{ 503 PassphraseTries: 1, 504 // activation with recovery key was disabled 505 RecoveryKeyTries: 0, 506 KeyringPrefix: "ubuntu-fde", 507 }) 508 } 509 if !tc.activated && tc.activateErr == nil { 510 return false, errors.New("activation error") 511 } 512 return tc.activated, tc.activateErr 513 }) 514 defer restore() 515 516 restore = secboot.MockSbActivateVolumeWithRecoveryKey(func(name, device string, keyReader io.Reader, 517 options *sb.ActivateVolumeOptions) error { 518 if !tc.rkAllow { 519 c.Fatalf("unexpected attempt to activate with recovery key") 520 return fmt.Errorf("unexpected call") 521 } 522 return tc.rkErr 523 }) 524 defer restore() 525 526 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{ 527 AllowRecoveryKey: tc.rkAllow, 528 } 529 unlockRes, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(tc.disk, defaultDevice, expKeyPath, opts) 530 if tc.err == "" { 531 c.Assert(err, IsNil) 532 c.Assert(unlockRes.IsEncrypted, Equals, tc.hasEncdev) 533 c.Assert(unlockRes.PartDevice, Equals, devicePath) 534 if tc.hasEncdev { 535 c.Assert(unlockRes.FsDevice, Equals, filepath.Join("/dev/mapper", defaultDevice+"-"+randomUUID)) 536 } else { 537 c.Assert(unlockRes.FsDevice, Equals, devicePath) 538 } 539 } else { 540 c.Assert(err, ErrorMatches, tc.err) 541 // also check that the IsEncrypted value matches, this is 542 // important for robust callers to know whether they should try to 543 // unlock using a different method or not 544 // this is only skipped on some test cases where we get an error 545 // very early, like trying to connect to the tpm 546 c.Assert(unlockRes.IsEncrypted, Equals, tc.hasEncdev) 547 if tc.hasEncdev { 548 c.Check(unlockRes.PartDevice, Equals, devicePath) 549 c.Check(unlockRes.FsDevice, Equals, "") 550 } else { 551 c.Check(unlockRes.PartDevice, Equals, "") 552 c.Check(unlockRes.FsDevice, Equals, "") 553 } 554 } 555 556 c.Assert(unlockRes.UnlockMethod, Equals, tc.expUnlockMethod) 557 } 558 } 559 560 func (s *secbootSuite) TestEFIImageFromBootFile(c *C) { 561 tmpDir := c.MkDir() 562 563 // set up some test files 564 existingFile := filepath.Join(tmpDir, "foo") 565 err := ioutil.WriteFile(existingFile, nil, 0644) 566 c.Assert(err, IsNil) 567 missingFile := filepath.Join(tmpDir, "bar") 568 snapFile := filepath.Join(tmpDir, "test.snap") 569 snapf, err := createMockSnapFile(c.MkDir(), snapFile, "app") 570 571 for _, tc := range []struct { 572 bootFile bootloader.BootFile 573 efiImage sb.EFIImage 574 err string 575 }{ 576 { 577 // happy case for EFI image 578 bootFile: bootloader.NewBootFile("", existingFile, bootloader.RoleRecovery), 579 efiImage: sb.FileEFIImage(existingFile), 580 }, 581 { 582 // missing EFI image 583 bootFile: bootloader.NewBootFile("", missingFile, bootloader.RoleRecovery), 584 err: fmt.Sprintf("file %s/bar does not exist", tmpDir), 585 }, 586 { 587 // happy case for snap file 588 bootFile: bootloader.NewBootFile(snapFile, "rel", bootloader.RoleRecovery), 589 efiImage: sb.SnapFileEFIImage{Container: snapf, FileName: "rel"}, 590 }, 591 { 592 // invalid snap file 593 bootFile: bootloader.NewBootFile(existingFile, "rel", bootloader.RoleRecovery), 594 err: fmt.Sprintf(`"%s/foo" is not a snap or snapdir`, tmpDir), 595 }, 596 { 597 // missing snap file 598 bootFile: bootloader.NewBootFile(missingFile, "rel", bootloader.RoleRecovery), 599 err: fmt.Sprintf(`"%s/bar" is not a snap or snapdir`, tmpDir), 600 }, 601 } { 602 o, err := secboot.EFIImageFromBootFile(&tc.bootFile) 603 if tc.err == "" { 604 c.Assert(err, IsNil) 605 c.Assert(o, DeepEquals, tc.efiImage) 606 } else { 607 c.Assert(err, ErrorMatches, tc.err) 608 } 609 } 610 } 611 612 func (s *secbootSuite) TestSealKey(c *C) { 613 mockErr := errors.New("some error") 614 615 for _, tc := range []struct { 616 tpmErr error 617 tpmEnabled bool 618 missingFile bool 619 badSnapFile bool 620 skipProvision bool 621 addEFISbPolicyErr error 622 addEFIBootManagerErr error 623 addSystemdEFIStubErr error 624 addSnapModelErr error 625 provisioningErr error 626 sealErr error 627 provisioningCalls int 628 sealCalls int 629 expectedErr string 630 }{ 631 {tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"}, 632 {tpmEnabled: false, expectedErr: "TPM device is not enabled"}, 633 {tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file /does/not/exist does not exist"}, 634 {tpmEnabled: true, badSnapFile: true, expectedErr: `.*/kernel.snap" is not a snap or snapdir`}, 635 {tpmEnabled: true, addEFISbPolicyErr: mockErr, expectedErr: "cannot add EFI secure boot policy profile: some error"}, 636 {tpmEnabled: true, addEFIBootManagerErr: mockErr, expectedErr: "cannot add EFI boot manager profile: some error"}, 637 {tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"}, 638 {tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"}, 639 {tpmEnabled: true, provisioningErr: mockErr, provisioningCalls: 1, expectedErr: "cannot provision TPM: some error"}, 640 {tpmEnabled: true, sealErr: mockErr, provisioningCalls: 1, sealCalls: 1, expectedErr: "some error"}, 641 {tpmEnabled: true, skipProvision: true, provisioningCalls: 0, sealCalls: 1, expectedErr: ""}, 642 {tpmEnabled: true, provisioningCalls: 1, sealCalls: 1, expectedErr: ""}, 643 } { 644 tmpDir := c.MkDir() 645 var mockBF []bootloader.BootFile 646 for _, name := range []string{"a", "b", "c", "d"} { 647 mockFileName := filepath.Join(tmpDir, name) 648 err := ioutil.WriteFile(mockFileName, nil, 0644) 649 c.Assert(err, IsNil) 650 mockBF = append(mockBF, bootloader.NewBootFile("", mockFileName, bootloader.RoleRecovery)) 651 } 652 653 if tc.missingFile { 654 mockBF[0].Path = "/does/not/exist" 655 } 656 657 var kernelSnap snap.Container 658 snapPath := filepath.Join(tmpDir, "kernel.snap") 659 if tc.badSnapFile { 660 err := ioutil.WriteFile(snapPath, nil, 0644) 661 c.Assert(err, IsNil) 662 } else { 663 var err error 664 kernelSnap, err = createMockSnapFile(c.MkDir(), snapPath, "kernel") 665 c.Assert(err, IsNil) 666 } 667 668 mockBF = append(mockBF, bootloader.NewBootFile(snapPath, "kernel.efi", bootloader.RoleRecovery)) 669 670 myAuthKey := &ecdsa.PrivateKey{} 671 672 myParams := secboot.SealKeysParams{ 673 ModelParams: []*secboot.SealKeyModelParams{ 674 { 675 EFILoadChains: []*secboot.LoadChain{ 676 secboot.NewLoadChain(mockBF[0], 677 secboot.NewLoadChain(mockBF[4])), 678 }, 679 KernelCmdlines: []string{"cmdline1"}, 680 Model: &asserts.Model{}, 681 }, 682 { 683 EFILoadChains: []*secboot.LoadChain{ 684 secboot.NewLoadChain(mockBF[0], 685 secboot.NewLoadChain(mockBF[2], 686 secboot.NewLoadChain(mockBF[4])), 687 secboot.NewLoadChain(mockBF[3], 688 secboot.NewLoadChain(mockBF[4]))), 689 secboot.NewLoadChain(mockBF[1], 690 secboot.NewLoadChain(mockBF[2], 691 secboot.NewLoadChain(mockBF[4])), 692 secboot.NewLoadChain(mockBF[3], 693 secboot.NewLoadChain(mockBF[4]))), 694 }, 695 KernelCmdlines: []string{"cmdline2", "cmdline3"}, 696 Model: &asserts.Model{}, 697 }, 698 }, 699 TPMPolicyAuthKey: myAuthKey, 700 TPMPolicyAuthKeyFile: filepath.Join(tmpDir, "policy-auth-key-file"), 701 TPMLockoutAuthFile: filepath.Join(tmpDir, "lockout-auth-file"), 702 TPMProvision: !tc.skipProvision, 703 PCRPolicyCounterHandle: 42, 704 } 705 706 myKey := secboot.EncryptionKey{} 707 myKey2 := secboot.EncryptionKey{} 708 for i := range myKey { 709 myKey[i] = byte(i) 710 myKey2[i] = byte(128 + i) 711 } 712 713 myKeys := []secboot.SealKeyRequest{ 714 { 715 Key: myKey, 716 KeyFile: "keyfile", 717 }, 718 { 719 Key: myKey2, 720 KeyFile: "keyfile2", 721 }, 722 } 723 724 // events for 725 // a -> kernel 726 sequences1 := []*sb.EFIImageLoadEvent{ 727 { 728 Source: sb.Firmware, 729 Image: sb.FileEFIImage(mockBF[0].Path), 730 Next: []*sb.EFIImageLoadEvent{ 731 { 732 Source: sb.Shim, 733 Image: sb.SnapFileEFIImage{ 734 Container: kernelSnap, 735 FileName: "kernel.efi", 736 }, 737 }, 738 }, 739 }, 740 } 741 742 // "cdk" events for 743 // c -> kernel OR 744 // d -> kernel 745 cdk := []*sb.EFIImageLoadEvent{ 746 { 747 Source: sb.Shim, 748 Image: sb.FileEFIImage(mockBF[2].Path), 749 Next: []*sb.EFIImageLoadEvent{ 750 { 751 Source: sb.Shim, 752 Image: sb.SnapFileEFIImage{ 753 Container: kernelSnap, 754 FileName: "kernel.efi", 755 }, 756 }, 757 }, 758 }, 759 { 760 Source: sb.Shim, 761 Image: sb.FileEFIImage(mockBF[3].Path), 762 Next: []*sb.EFIImageLoadEvent{ 763 { 764 Source: sb.Shim, 765 Image: sb.SnapFileEFIImage{ 766 Container: kernelSnap, 767 FileName: "kernel.efi", 768 }, 769 }, 770 }, 771 }, 772 } 773 774 // events for 775 // a -> "cdk" 776 // b -> "cdk" 777 sequences2 := []*sb.EFIImageLoadEvent{ 778 { 779 Source: sb.Firmware, 780 Image: sb.FileEFIImage(mockBF[0].Path), 781 Next: cdk, 782 }, 783 { 784 Source: sb.Firmware, 785 Image: sb.FileEFIImage(mockBF[1].Path), 786 Next: cdk, 787 }, 788 } 789 790 tpm, restore := mockSbTPMConnection(c, tc.tpmErr) 791 defer restore() 792 793 // mock adding EFI secure boot policy profile 794 var pcrProfile *sb.PCRProtectionProfile 795 addEFISbPolicyCalls := 0 796 restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error { 797 addEFISbPolicyCalls++ 798 pcrProfile = profile 799 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 800 switch addEFISbPolicyCalls { 801 case 1: 802 c.Assert(params.LoadSequences, DeepEquals, sequences1) 803 case 2: 804 c.Assert(params.LoadSequences, DeepEquals, sequences2) 805 default: 806 c.Error("AddEFISecureBootPolicyProfile shouldn't be called a third time") 807 } 808 return tc.addEFISbPolicyErr 809 }) 810 defer restore() 811 812 // mock adding EFI boot manager profile 813 addEFIBootManagerCalls := 0 814 restore = secboot.MockSbAddEFIBootManagerProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error { 815 addEFIBootManagerCalls++ 816 c.Assert(profile, Equals, pcrProfile) 817 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 818 switch addEFISbPolicyCalls { 819 case 1: 820 c.Assert(params.LoadSequences, DeepEquals, sequences1) 821 case 2: 822 c.Assert(params.LoadSequences, DeepEquals, sequences2) 823 default: 824 c.Error("AddEFIBootManagerProfile shouldn't be called a third time") 825 } 826 return tc.addEFIBootManagerErr 827 }) 828 defer restore() 829 830 // mock adding systemd EFI stub profile 831 addSystemdEfiStubCalls := 0 832 restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error { 833 addSystemdEfiStubCalls++ 834 c.Assert(profile, Equals, pcrProfile) 835 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 836 c.Assert(params.PCRIndex, Equals, 12) 837 switch addSystemdEfiStubCalls { 838 case 1: 839 c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[0].KernelCmdlines) 840 case 2: 841 c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[1].KernelCmdlines) 842 default: 843 c.Error("AddSystemdEFIStubProfile shouldn't be called a third time") 844 } 845 return tc.addSystemdEFIStubErr 846 }) 847 defer restore() 848 849 // mock adding snap model profile 850 addSnapModelCalls := 0 851 restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error { 852 addSnapModelCalls++ 853 c.Assert(profile, Equals, pcrProfile) 854 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 855 c.Assert(params.PCRIndex, Equals, 12) 856 switch addSnapModelCalls { 857 case 1: 858 c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[0].Model) 859 case 2: 860 c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[1].Model) 861 default: 862 c.Error("AddSnapModelProfile shouldn't be called a third time") 863 } 864 return tc.addSnapModelErr 865 }) 866 defer restore() 867 868 // mock provisioning 869 provisioningCalls := 0 870 restore = secboot.MockProvisionTPM(func(t *sb.TPMConnection, mode sb.ProvisionMode, newLockoutAuth []byte) error { 871 provisioningCalls++ 872 c.Assert(t, Equals, tpm) 873 c.Assert(mode, Equals, sb.ProvisionModeFull) 874 c.Assert(myParams.TPMLockoutAuthFile, testutil.FilePresent) 875 return tc.provisioningErr 876 }) 877 defer restore() 878 879 // mock sealing 880 sealCalls := 0 881 restore = secboot.MockSbSealKeyToTPMMultiple(func(t *sb.TPMConnection, kr []*sb.SealKeyRequest, params *sb.KeyCreationParams) (sb.TPMPolicyAuthKey, error) { 882 sealCalls++ 883 c.Assert(t, Equals, tpm) 884 c.Assert(kr, DeepEquals, []*sb.SealKeyRequest{{Key: myKey, Path: "keyfile"}, {Key: myKey2, Path: "keyfile2"}}) 885 c.Assert(params.AuthKey, Equals, myAuthKey) 886 c.Assert(params.PCRPolicyCounterHandle, Equals, tpm2.Handle(42)) 887 return sb.TPMPolicyAuthKey{}, tc.sealErr 888 }) 889 defer restore() 890 891 // mock TPM enabled check 892 restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool { 893 return tc.tpmEnabled 894 }) 895 defer restore() 896 897 err := secboot.SealKeys(myKeys, &myParams) 898 if tc.expectedErr == "" { 899 c.Assert(err, IsNil) 900 c.Assert(addEFISbPolicyCalls, Equals, 2) 901 c.Assert(addSystemdEfiStubCalls, Equals, 2) 902 c.Assert(addSnapModelCalls, Equals, 2) 903 c.Assert(osutil.FileExists(myParams.TPMPolicyAuthKeyFile), Equals, true) 904 } else { 905 c.Assert(err, ErrorMatches, tc.expectedErr) 906 } 907 c.Assert(provisioningCalls, Equals, tc.provisioningCalls) 908 c.Assert(sealCalls, Equals, tc.sealCalls) 909 910 } 911 } 912 913 func (s *secbootSuite) TestResealKey(c *C) { 914 mockErr := errors.New("some error") 915 916 for _, tc := range []struct { 917 tpmErr error 918 tpmEnabled bool 919 missingFile bool 920 addEFISbPolicyErr error 921 addEFIBootManagerErr error 922 addSystemdEFIStubErr error 923 addSnapModelErr error 924 provisioningErr error 925 resealErr error 926 resealCalls int 927 expectedErr string 928 }{ 929 {tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"}, 930 {tpmEnabled: false, expectedErr: "TPM device is not enabled"}, 931 {tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file .*/file.efi does not exist"}, 932 {tpmEnabled: true, addEFISbPolicyErr: mockErr, expectedErr: "cannot add EFI secure boot policy profile: some error"}, 933 {tpmEnabled: true, addEFIBootManagerErr: mockErr, expectedErr: "cannot add EFI boot manager profile: some error"}, 934 {tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"}, 935 {tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"}, 936 {tpmEnabled: true, resealErr: mockErr, resealCalls: 1, expectedErr: "some error"}, 937 {tpmEnabled: true, resealCalls: 1, expectedErr: ""}, 938 } { 939 mockTPMPolicyAuthKey := []byte{1, 3, 3, 7} 940 mockTPMPolicyAuthKeyFile := filepath.Join(c.MkDir(), "policy-auth-key-file") 941 err := ioutil.WriteFile(mockTPMPolicyAuthKeyFile, mockTPMPolicyAuthKey, 0600) 942 c.Assert(err, IsNil) 943 944 mockEFI := bootloader.NewBootFile("", filepath.Join(c.MkDir(), "file.efi"), bootloader.RoleRecovery) 945 if !tc.missingFile { 946 err := ioutil.WriteFile(mockEFI.Path, nil, 0644) 947 c.Assert(err, IsNil) 948 } 949 950 myParams := &secboot.ResealKeysParams{ 951 ModelParams: []*secboot.SealKeyModelParams{ 952 { 953 EFILoadChains: []*secboot.LoadChain{secboot.NewLoadChain(mockEFI)}, 954 KernelCmdlines: []string{"cmdline"}, 955 Model: &asserts.Model{}, 956 }, 957 }, 958 KeyFiles: []string{"keyfile", "keyfile2"}, 959 TPMPolicyAuthKeyFile: mockTPMPolicyAuthKeyFile, 960 } 961 962 sequences := []*sb.EFIImageLoadEvent{ 963 { 964 Source: sb.Firmware, 965 Image: sb.FileEFIImage(mockEFI.Path), 966 }, 967 } 968 969 // mock TPM connection 970 tpm, restore := mockSbTPMConnection(c, tc.tpmErr) 971 defer restore() 972 973 // mock TPM enabled check 974 restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool { 975 return tc.tpmEnabled 976 }) 977 defer restore() 978 979 // mock adding EFI secure boot policy profile 980 var pcrProfile *sb.PCRProtectionProfile 981 addEFISbPolicyCalls := 0 982 restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error { 983 addEFISbPolicyCalls++ 984 pcrProfile = profile 985 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 986 c.Assert(params.LoadSequences, DeepEquals, sequences) 987 return tc.addEFISbPolicyErr 988 }) 989 defer restore() 990 991 // mock adding EFI boot manager profile 992 addEFIBootManagerCalls := 0 993 restore = secboot.MockSbAddEFIBootManagerProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error { 994 addEFIBootManagerCalls++ 995 c.Assert(profile, Equals, pcrProfile) 996 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 997 c.Assert(params.LoadSequences, DeepEquals, sequences) 998 return tc.addEFIBootManagerErr 999 }) 1000 defer restore() 1001 1002 // mock adding systemd EFI stub profile 1003 addSystemdEfiStubCalls := 0 1004 restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error { 1005 addSystemdEfiStubCalls++ 1006 c.Assert(profile, Equals, pcrProfile) 1007 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 1008 c.Assert(params.PCRIndex, Equals, 12) 1009 c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[0].KernelCmdlines) 1010 return tc.addSystemdEFIStubErr 1011 }) 1012 defer restore() 1013 1014 // mock adding snap model profile 1015 addSnapModelCalls := 0 1016 restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error { 1017 addSnapModelCalls++ 1018 c.Assert(profile, Equals, pcrProfile) 1019 c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) 1020 c.Assert(params.PCRIndex, Equals, 12) 1021 c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[0].Model) 1022 return tc.addSnapModelErr 1023 }) 1024 defer restore() 1025 1026 // mock PCR protection policy update 1027 resealCalls := 0 1028 restore = secboot.MockSbUpdateKeyPCRProtectionPolicyMultiple(func(t *sb.TPMConnection, keyPaths []string, authKey sb.TPMPolicyAuthKey, profile *sb.PCRProtectionProfile) error { 1029 resealCalls++ 1030 c.Assert(t, Equals, tpm) 1031 c.Assert(keyPaths, DeepEquals, []string{"keyfile", "keyfile2"}) 1032 c.Assert(authKey, DeepEquals, sb.TPMPolicyAuthKey(mockTPMPolicyAuthKey)) 1033 c.Assert(profile, Equals, pcrProfile) 1034 return tc.resealErr 1035 }) 1036 defer restore() 1037 1038 err = secboot.ResealKeys(myParams) 1039 if tc.expectedErr == "" { 1040 c.Assert(err, IsNil) 1041 c.Assert(addEFISbPolicyCalls, Equals, 1) 1042 c.Assert(addSystemdEfiStubCalls, Equals, 1) 1043 c.Assert(addSnapModelCalls, Equals, 1) 1044 } else { 1045 c.Assert(err, ErrorMatches, tc.expectedErr) 1046 } 1047 c.Assert(resealCalls, Equals, tc.resealCalls) 1048 } 1049 } 1050 1051 func (s *secbootSuite) TestSealKeyNoModelParams(c *C) { 1052 myKeys := []secboot.SealKeyRequest{ 1053 { 1054 Key: secboot.EncryptionKey{}, 1055 KeyFile: "keyfile", 1056 }, 1057 } 1058 myParams := secboot.SealKeysParams{ 1059 TPMPolicyAuthKeyFile: "policy-auth-key-file", 1060 TPMLockoutAuthFile: "lockout-auth-file", 1061 } 1062 1063 err := secboot.SealKeys(myKeys, &myParams) 1064 c.Assert(err, ErrorMatches, "at least one set of model-specific parameters is required") 1065 } 1066 1067 func createMockSnapFile(snapDir, snapPath, snapType string) (snap.Container, error) { 1068 snapYamlPath := filepath.Join(snapDir, "meta/snap.yaml") 1069 if err := os.MkdirAll(filepath.Dir(snapYamlPath), 0755); err != nil { 1070 return nil, err 1071 } 1072 if err := ioutil.WriteFile(snapYamlPath, []byte("name: foo"), 0644); err != nil { 1073 return nil, err 1074 } 1075 sqfs := squashfs.New(snapPath) 1076 if err := sqfs.Build(snapDir, &squashfs.BuildOpts{SnapType: snapType}); err != nil { 1077 return nil, err 1078 } 1079 return snapfile.Open(snapPath) 1080 } 1081 1082 func mockSbTPMConnection(c *C, tpmErr error) (*sb.TPMConnection, func()) { 1083 tcti, err := tpm2.OpenTPMDevice("/dev/null") 1084 c.Assert(err, IsNil) 1085 tpmctx, err := tpm2.NewTPMContext(tcti) 1086 c.Assert(err, IsNil) 1087 tpm := &sb.TPMConnection{TPMContext: tpmctx} 1088 restore := secboot.MockSbConnectToDefaultTPM(func() (*sb.TPMConnection, error) { 1089 if tpmErr != nil { 1090 return nil, tpmErr 1091 } 1092 return tpm, nil 1093 }) 1094 return tpm, restore 1095 } 1096 1097 func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyBadDisk(c *C) { 1098 disk := &disks.MockDiskMapping{ 1099 FilesystemLabelToPartUUID: map[string]string{}, 1100 } 1101 unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo")) 1102 c.Assert(err, ErrorMatches, `filesystem label "ubuntu-save-enc" not found`) 1103 c.Check(unlockRes, DeepEquals, secboot.UnlockResult{}) 1104 } 1105 1106 func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyHappy(c *C) { 1107 disk := &disks.MockDiskMapping{ 1108 FilesystemLabelToPartUUID: map[string]string{ 1109 "ubuntu-save-enc": "123-123-123", 1110 }, 1111 } 1112 restore := secboot.MockRandomKernelUUID(func() string { 1113 return "random-uuid-123-123" 1114 }) 1115 defer restore() 1116 restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte, 1117 options *sb.ActivateVolumeOptions) error { 1118 c.Check(options, DeepEquals, &sb.ActivateVolumeOptions{}) 1119 c.Check(key, DeepEquals, []byte("fooo")) 1120 c.Check(volumeName, Matches, "ubuntu-save-random-uuid-123-123") 1121 c.Check(sourceDevicePath, Equals, "/dev/disk/by-partuuid/123-123-123") 1122 return nil 1123 }) 1124 defer restore() 1125 unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo")) 1126 c.Assert(err, IsNil) 1127 c.Check(unlockRes, DeepEquals, secboot.UnlockResult{ 1128 PartDevice: "/dev/disk/by-partuuid/123-123-123", 1129 FsDevice: "/dev/mapper/ubuntu-save-random-uuid-123-123", 1130 IsEncrypted: true, 1131 UnlockMethod: secboot.UnlockedWithKey, 1132 }) 1133 } 1134 1135 func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyErr(c *C) { 1136 disk := &disks.MockDiskMapping{ 1137 FilesystemLabelToPartUUID: map[string]string{ 1138 "ubuntu-save-enc": "123-123-123", 1139 }, 1140 } 1141 restore := secboot.MockRandomKernelUUID(func() string { 1142 return "random-uuid-123-123" 1143 }) 1144 defer restore() 1145 restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte, 1146 options *sb.ActivateVolumeOptions) error { 1147 return fmt.Errorf("failed") 1148 }) 1149 defer restore() 1150 unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo")) 1151 c.Assert(err, ErrorMatches, "failed") 1152 // we would have at least identified that the device is a decrypted one 1153 c.Check(unlockRes, DeepEquals, secboot.UnlockResult{ 1154 IsEncrypted: true, 1155 PartDevice: "/dev/disk/by-partuuid/123-123-123", 1156 FsDevice: "", 1157 }) 1158 } 1159 1160 func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyErr(c *C) { 1161 restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) { 1162 return nil, fmt.Errorf("helper error") 1163 }) 1164 defer restore() 1165 1166 restore = secboot.MockFDEHasRevealKey(func() bool { 1167 return true 1168 }) 1169 defer restore() 1170 1171 mockDiskWithEncDev := &disks.MockDiskMapping{ 1172 FilesystemLabelToPartUUID: map[string]string{ 1173 "name-enc": "enc-dev-partuuid", 1174 }, 1175 } 1176 defaultDevice := "name" 1177 mockSealedKeyFile := makeMockSealedKeyFile(c, nil) 1178 1179 restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) { 1180 // XXX: this is what the real 1181 // MockSbActivateVolumeWithKeyData will do 1182 _, _, err := keyData.RecoverKeys() 1183 if err != nil { 1184 return nil, err 1185 } 1186 c.Fatal("should not get this far") 1187 return nil, nil 1188 }) 1189 defer restore() 1190 1191 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{} 1192 _, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts) 1193 c.Assert(err, ErrorMatches, `cannot unlock encrypted partition: cannot recover keys because of an unexpected error: cannot run fde-reveal-key "reveal": helper error`) 1194 } 1195 1196 // this test that v1 hooks and raw binary v1 created sealedKey files still work 1197 func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV1AndV1GeneratedSealedKeyFile(c *C) { 1198 // The v1 hooks will just return raw bytes. This is deprecated but 1199 // we need to keep compatbility with the v1 implementation because 1200 // there is a project "denver" that ships with v1 hooks. 1201 var reqs []*fde.RevealKeyRequest 1202 restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) { 1203 reqs = append(reqs, req) 1204 return []byte("unsealed-key-64-chars-long-when-not-json-to-match-denver-project"), nil 1205 }) 1206 defer restore() 1207 1208 restore = secboot.MockFDEHasRevealKey(func() bool { 1209 return true 1210 }) 1211 defer restore() 1212 1213 restore = secboot.MockRandomKernelUUID(func() string { 1214 return "random-uuid-for-test" 1215 }) 1216 defer restore() 1217 1218 mockDiskWithEncDev := &disks.MockDiskMapping{ 1219 FilesystemLabelToPartUUID: map[string]string{ 1220 "device-name-enc": "enc-dev-partuuid", 1221 }, 1222 } 1223 1224 activated := 0 1225 restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte, options *sb.ActivateVolumeOptions) error { 1226 activated++ 1227 c.Check(string(key), Equals, "unsealed-key-64-chars-long-when-not-json-to-match-denver-project") 1228 return nil 1229 }) 1230 defer restore() 1231 1232 defaultDevice := "device-name" 1233 // note that we write a v1 created keyfile here, i.e. it's a raw 1234 // disk-key without any json 1235 mockSealedKeyFile := filepath.Join(c.MkDir(), "keyfile") 1236 sealedKeyContent := []byte("USK$sealed-key-not-json-to-match-denver-project") 1237 err := ioutil.WriteFile(mockSealedKeyFile, sealedKeyContent, 0600) 1238 c.Assert(err, IsNil) 1239 1240 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{} 1241 res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts) 1242 c.Assert(err, IsNil) 1243 c.Check(res, DeepEquals, secboot.UnlockResult{ 1244 UnlockMethod: secboot.UnlockedWithSealedKey, 1245 IsEncrypted: true, 1246 PartDevice: "/dev/disk/by-partuuid/enc-dev-partuuid", 1247 FsDevice: "/dev/mapper/device-name-random-uuid-for-test", 1248 }) 1249 c.Check(activated, Equals, 1) 1250 c.Check(reqs, HasLen, 1) 1251 c.Check(reqs[0].Op, Equals, "reveal") 1252 c.Check(reqs[0].SealedKey, DeepEquals, sealedKeyContent) 1253 } 1254 1255 func (s *secbootSuite) TestLockSealedKeysCallsFdeReveal(c *C) { 1256 var ops []string 1257 restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) { 1258 ops = append(ops, req.Op) 1259 return nil, nil 1260 }) 1261 defer restore() 1262 restore = secboot.MockFDEHasRevealKey(func() bool { 1263 return true 1264 }) 1265 defer restore() 1266 1267 err := secboot.LockSealedKeys() 1268 c.Assert(err, IsNil) 1269 1270 c.Check(ops, DeepEquals, []string{"lock"}) 1271 } 1272 1273 func (s *secbootSuite) TestSealKeysWithFDESetupHookHappy(c *C) { 1274 tmpdir := c.MkDir() 1275 1276 n := 0 1277 sealedPrefix := []byte("SEALED:") 1278 rawHandle1 := json.RawMessage(`{"handle-for":"key1"}`) 1279 var runFDESetupHookReqs []*fde.SetupRequest 1280 runFDESetupHook := func(req *fde.SetupRequest) ([]byte, error) { 1281 n++ 1282 runFDESetupHookReqs = append(runFDESetupHookReqs, req) 1283 payload := append(sealedPrefix, req.Key...) 1284 var handle *json.RawMessage 1285 if req.KeyName == "key1" { 1286 handle = &rawHandle1 1287 } 1288 res := &fde.InitialSetupResult{ 1289 EncryptedKey: payload, 1290 Handle: handle, 1291 } 1292 return json.Marshal(res) 1293 } 1294 1295 key1 := secboot.EncryptionKey{1, 2, 3, 4} 1296 key2 := secboot.EncryptionKey{5, 6, 7, 8} 1297 auxKey := secboot.AuxKey{9, 10, 11, 12} 1298 key1Fn := filepath.Join(tmpdir, "key1.key") 1299 key2Fn := filepath.Join(tmpdir, "key2.key") 1300 auxKeyFn := filepath.Join(tmpdir, "aux-key") 1301 params := secboot.SealKeysWithFDESetupHookParams{ 1302 Model: fakeModel, 1303 AuxKey: auxKey, 1304 AuxKeyFile: auxKeyFn, 1305 } 1306 err := secboot.SealKeysWithFDESetupHook(runFDESetupHook, 1307 []secboot.SealKeyRequest{ 1308 {Key: key1, KeyName: "key1", KeyFile: key1Fn}, 1309 {Key: key2, KeyName: "key2", KeyFile: key2Fn}, 1310 }, ¶ms) 1311 c.Assert(err, IsNil) 1312 // check that runFDESetupHook was called the expected way 1313 key1Payload := sb.MarshalKeys([]byte(key1), auxKey[:]) 1314 key2Payload := sb.MarshalKeys([]byte(key2), auxKey[:]) 1315 c.Check(runFDESetupHookReqs, DeepEquals, []*fde.SetupRequest{ 1316 {Op: "initial-setup", Key: key1Payload, KeyName: "key1"}, 1317 {Op: "initial-setup", Key: key2Payload, KeyName: "key2"}, 1318 }) 1319 // check that the sealed keys got written to the expected places 1320 for _, p := range []string{key1Fn, key2Fn} { 1321 c.Check(p, testutil.FilePresent) 1322 } 1323 c.Check(auxKeyFn, testutil.FileEquals, auxKey[:]) 1324 1325 // roundtrip to check what was written 1326 s.checkV2Key(c, key1Fn, sealedPrefix, key1, auxKey[:], fakeModel, &rawHandle1) 1327 nullHandle := json.RawMessage("null") 1328 s.checkV2Key(c, key2Fn, sealedPrefix, key2, auxKey[:], fakeModel, &nullHandle) 1329 } 1330 1331 func (s *secbootSuite) TestSealKeysWithFDESetupHookSad(c *C) { 1332 tmpdir := c.MkDir() 1333 1334 runFDESetupHook := func(req *fde.SetupRequest) ([]byte, error) { 1335 return nil, fmt.Errorf("hook failed") 1336 } 1337 1338 key := secboot.EncryptionKey{1, 2, 3, 4} 1339 auxKey := secboot.AuxKey{5, 6, 7, 8} 1340 keyFn := filepath.Join(tmpdir, "key.key") 1341 auxKeyFn := filepath.Join(tmpdir, "aux-key") 1342 params := secboot.SealKeysWithFDESetupHookParams{ 1343 Model: fakeModel, 1344 AuxKey: auxKey, 1345 AuxKeyFile: auxKeyFn, 1346 } 1347 err := secboot.SealKeysWithFDESetupHook(runFDESetupHook, 1348 []secboot.SealKeyRequest{ 1349 {Key: key, KeyName: "key1", KeyFile: keyFn}, 1350 }, ¶ms) 1351 c.Assert(err, ErrorMatches, "hook failed") 1352 c.Check(keyFn, testutil.FileAbsent) 1353 c.Check(auxKeyFn, testutil.FileAbsent) 1354 } 1355 1356 func makeMockDiskKey() secboot.EncryptionKey { 1357 return secboot.EncryptionKey{0, 1, 2, 3, 4, 5} 1358 } 1359 1360 func makeMockAuxKey() secboot.AuxKey { 1361 return secboot.AuxKey{6, 7, 8, 9} 1362 } 1363 1364 func makeMockUnencryptedPayload() []byte { 1365 diskKey := makeMockDiskKey() 1366 auxKey := makeMockAuxKey() 1367 return sb.MarshalKeys([]byte(diskKey), auxKey[:]) 1368 } 1369 1370 func makeMockEncryptedPayload() []byte { 1371 pl := makeMockUnencryptedPayload() 1372 // rot13 ftw 1373 for i := range pl { 1374 pl[i] = pl[i] ^ 0x13 1375 } 1376 return pl 1377 } 1378 1379 func makeMockEncryptedPayloadString() string { 1380 return base64.StdEncoding.EncodeToString(makeMockEncryptedPayload()) 1381 } 1382 1383 func makeMockSealedKeyFile(c *C, handle json.RawMessage) string { 1384 mockSealedKeyFile := filepath.Join(c.MkDir(), "keyfile") 1385 var handleJSON string 1386 if len(handle) != 0 { 1387 handleJSON = fmt.Sprintf(`"platform_handle":%s,`, handle) 1388 } 1389 sealedKeyContent := fmt.Sprintf(`{"platform_name":"fde-hook-v2",%s"encrypted_payload":"%s"}`, handleJSON, makeMockEncryptedPayloadString()) 1390 err := ioutil.WriteFile(mockSealedKeyFile, []byte(sealedKeyContent), 0600) 1391 c.Assert(err, IsNil) 1392 return mockSealedKeyFile 1393 } 1394 1395 var fakeModel = assertstest.FakeAssertion(map[string]interface{}{ 1396 "type": "model", 1397 "authority-id": "my-brand", 1398 "series": "16", 1399 "brand-id": "my-brand", 1400 "model": "my-model", 1401 "grade": "signed", 1402 "architecture": "amd64", 1403 "base": "core20", 1404 "snaps": []interface{}{ 1405 map[string]interface{}{ 1406 "name": "pc-kernel", 1407 "id": "pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza", 1408 "type": "kernel", 1409 "default-channel": "20", 1410 }, 1411 map[string]interface{}{ 1412 "name": "pc", 1413 "id": "UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH", 1414 "type": "gadget", 1415 "default-channel": "20", 1416 }}, 1417 }).(*asserts.Model) 1418 1419 type mockSnapModelChecker struct { 1420 mockIsAuthorized bool 1421 mockError error 1422 } 1423 1424 func (c *mockSnapModelChecker) IsModelAuthorized(model sb.SnapModel) (bool, error) { 1425 if model.BrandID() != "my-brand" || model.Model() != "my-model" { 1426 return false, fmt.Errorf("not the test model") 1427 } 1428 return c.mockIsAuthorized, c.mockError 1429 } 1430 func (c *mockSnapModelChecker) VolumeName() string { 1431 return "volume-name" 1432 } 1433 1434 func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2(c *C) { 1435 var reqs []*fde.RevealKeyRequest 1436 restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) { 1437 reqs = append(reqs, req) 1438 return []byte(fmt.Sprintf(`{"key": "%s"}`, base64.StdEncoding.EncodeToString(makeMockUnencryptedPayload()))), nil 1439 }) 1440 defer restore() 1441 1442 restore = secboot.MockFDEHasRevealKey(func() bool { 1443 return true 1444 }) 1445 defer restore() 1446 1447 restore = secboot.MockRandomKernelUUID(func() string { 1448 return "random-uuid-for-test" 1449 }) 1450 defer restore() 1451 1452 mockDiskWithEncDev := &disks.MockDiskMapping{ 1453 FilesystemLabelToPartUUID: map[string]string{ 1454 "device-name-enc": "enc-dev-partuuid", 1455 }, 1456 } 1457 1458 expectedKey := makeMockDiskKey() 1459 expectedAuxKey := makeMockAuxKey() 1460 activated := 0 1461 restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) { 1462 activated++ 1463 c.Check(options.RecoveryKeyTries, Equals, 0) 1464 // XXX: this is what the real 1465 // MockSbActivateVolumeWithKeyData will do 1466 key, auxKey, err := keyData.RecoverKeys() 1467 c.Assert(err, IsNil) 1468 c.Check([]byte(key), DeepEquals, []byte(expectedKey)) 1469 c.Check([]byte(auxKey), DeepEquals, expectedAuxKey[:]) 1470 modChecker := &mockSnapModelChecker{mockIsAuthorized: true} 1471 return modChecker, nil 1472 }) 1473 defer restore() 1474 1475 defaultDevice := "device-name" 1476 handle := json.RawMessage(`{"a": "handle"}`) 1477 mockSealedKeyFile := makeMockSealedKeyFile(c, handle) 1478 1479 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{ 1480 WhichModel: func() (*asserts.Model, error) { 1481 return fakeModel, nil 1482 }, 1483 } 1484 res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts) 1485 c.Assert(err, IsNil) 1486 c.Check(res, DeepEquals, secboot.UnlockResult{ 1487 UnlockMethod: secboot.UnlockedWithSealedKey, 1488 IsEncrypted: true, 1489 PartDevice: "/dev/disk/by-partuuid/enc-dev-partuuid", 1490 FsDevice: "/dev/mapper/device-name-random-uuid-for-test", 1491 }) 1492 c.Check(activated, Equals, 1) 1493 c.Check(reqs, HasLen, 1) 1494 c.Check(reqs[0].Op, Equals, "reveal") 1495 c.Check(reqs[0].SealedKey, DeepEquals, makeMockEncryptedPayload()) 1496 c.Check(reqs[0].Handle, DeepEquals, &handle) 1497 } 1498 1499 func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2ModelUnauthorized(c *C) { 1500 restore := secboot.MockFDEHasRevealKey(func() bool { 1501 return true 1502 }) 1503 defer restore() 1504 1505 restore = secboot.MockRandomKernelUUID(func() string { 1506 return "random-uuid-for-test" 1507 }) 1508 defer restore() 1509 1510 mockDiskWithEncDev := &disks.MockDiskMapping{ 1511 FilesystemLabelToPartUUID: map[string]string{ 1512 "device-name-enc": "enc-dev-partuuid", 1513 }, 1514 } 1515 1516 activated := 0 1517 restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) { 1518 activated++ 1519 modChecker := &mockSnapModelChecker{mockIsAuthorized: false} 1520 return modChecker, nil 1521 }) 1522 defer restore() 1523 1524 deactivated := 0 1525 restore = secboot.MockSbDeactivateVolume(func(volumeName string) error { 1526 deactivated++ 1527 c.Check(volumeName, Equals, "device-name-random-uuid-for-test") 1528 return nil 1529 }) 1530 defer restore() 1531 1532 defaultDevice := "device-name" 1533 handle := json.RawMessage(`{"a": "handle"}`) 1534 mockSealedKeyFile := makeMockSealedKeyFile(c, handle) 1535 1536 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{ 1537 WhichModel: func() (*asserts.Model, error) { 1538 return fakeModel, nil 1539 }, 1540 } 1541 res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts) 1542 c.Assert(err, ErrorMatches, `cannot unlock volume: model my-brand/my-model not authorized`) 1543 c.Check(res, DeepEquals, secboot.UnlockResult{ 1544 IsEncrypted: true, 1545 PartDevice: "/dev/disk/by-partuuid/enc-dev-partuuid", 1546 }) 1547 c.Check(activated, Equals, 1) 1548 c.Check(deactivated, Equals, 1) 1549 } 1550 1551 func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2ModelCheckerError(c *C) { 1552 restore := secboot.MockFDEHasRevealKey(func() bool { 1553 return true 1554 }) 1555 defer restore() 1556 1557 restore = secboot.MockRandomKernelUUID(func() string { 1558 return "random-uuid-for-test" 1559 }) 1560 defer restore() 1561 1562 mockDiskWithEncDev := &disks.MockDiskMapping{ 1563 FilesystemLabelToPartUUID: map[string]string{ 1564 "device-name-enc": "enc-dev-partuuid", 1565 }, 1566 } 1567 1568 activated := 0 1569 restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) { 1570 activated++ 1571 modChecker := &mockSnapModelChecker{mockError: errors.New("model checker error")} 1572 return modChecker, nil 1573 }) 1574 defer restore() 1575 1576 defaultDevice := "device-name" 1577 handle := json.RawMessage(`{"a": "handle"}`) 1578 mockSealedKeyFile := makeMockSealedKeyFile(c, handle) 1579 1580 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{ 1581 WhichModel: func() (*asserts.Model, error) { 1582 return fakeModel, nil 1583 }, 1584 } 1585 res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts) 1586 c.Assert(err, ErrorMatches, `cannot check if model is authorized to unlock disk: model checker error`) 1587 c.Check(res, DeepEquals, secboot.UnlockResult{ 1588 IsEncrypted: true, 1589 PartDevice: "/dev/disk/by-partuuid/enc-dev-partuuid", 1590 }) 1591 c.Check(activated, Equals, 1) 1592 } 1593 1594 func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2AllowRecoverKey(c *C) { 1595 var reqs []*fde.RevealKeyRequest 1596 restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) { 1597 reqs = append(reqs, req) 1598 return []byte("invalid-json"), nil 1599 }) 1600 defer restore() 1601 1602 restore = secboot.MockFDEHasRevealKey(func() bool { 1603 return true 1604 }) 1605 defer restore() 1606 1607 restore = secboot.MockRandomKernelUUID(func() string { 1608 return "random-uuid-for-test" 1609 }) 1610 defer restore() 1611 1612 mockDiskWithEncDev := &disks.MockDiskMapping{ 1613 FilesystemLabelToPartUUID: map[string]string{ 1614 "device-name-enc": "enc-dev-partuuid", 1615 }, 1616 } 1617 1618 activated := 0 1619 restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) { 1620 activated++ 1621 c.Check(options.RecoveryKeyTries, Equals, 3) 1622 // XXX: this is what the real 1623 // MockSbActivateVolumeWithKeyData will do 1624 _, _, err := keyData.RecoverKeys() 1625 c.Assert(err, NotNil) 1626 return nil, sb.ErrRecoveryKeyUsed 1627 }) 1628 defer restore() 1629 1630 defaultDevice := "device-name" 1631 handle := json.RawMessage(`{"a": "handle"}`) 1632 mockSealedKeyFile := makeMockSealedKeyFile(c, handle) 1633 1634 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{AllowRecoveryKey: true} 1635 res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts) 1636 c.Assert(err, IsNil) 1637 c.Check(res, DeepEquals, secboot.UnlockResult{ 1638 UnlockMethod: secboot.UnlockedWithRecoveryKey, 1639 IsEncrypted: true, 1640 PartDevice: "/dev/disk/by-partuuid/enc-dev-partuuid", 1641 FsDevice: "/dev/mapper/device-name-random-uuid-for-test", 1642 }) 1643 c.Check(activated, Equals, 1) 1644 c.Check(reqs, HasLen, 1) 1645 c.Check(reqs[0].Op, Equals, "reveal") 1646 c.Check(reqs[0].SealedKey, DeepEquals, makeMockEncryptedPayload()) 1647 c.Check(reqs[0].Handle, DeepEquals, &handle) 1648 } 1649 1650 func (s *secbootSuite) checkV2Key(c *C, keyFn string, prefixToDrop, expectedKey, expectedAuxKey []byte, authModel *asserts.Model, handle *json.RawMessage) { 1651 restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) { 1652 c.Check(req.Handle, DeepEquals, handle) 1653 c.Check(bytes.HasPrefix(req.SealedKey, prefixToDrop), Equals, true) 1654 payload := req.SealedKey[len(prefixToDrop):] 1655 return []byte(fmt.Sprintf(`{"key": "%s"}`, base64.StdEncoding.EncodeToString(payload))), nil 1656 }) 1657 defer restore() 1658 1659 restore = secboot.MockFDEHasRevealKey(func() bool { 1660 return true 1661 }) 1662 defer restore() 1663 1664 restore = secboot.MockRandomKernelUUID(func() string { 1665 return "random-uuid-for-test" 1666 }) 1667 defer restore() 1668 1669 mockDiskWithEncDev := &disks.MockDiskMapping{ 1670 FilesystemLabelToPartUUID: map[string]string{ 1671 "device-name-enc": "enc-dev-partuuid", 1672 }, 1673 } 1674 1675 activated := 0 1676 restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) { 1677 activated++ 1678 // XXX: this is what the real 1679 // MockSbActivateVolumeWithKeyData will do 1680 key, auxKey, err := keyData.RecoverKeys() 1681 c.Assert(err, IsNil) 1682 c.Check([]byte(key), DeepEquals, []byte(expectedKey)) 1683 c.Check([]byte(auxKey), DeepEquals, []byte(expectedAuxKey)) 1684 // check against model 1685 ok, err := keyData.IsSnapModelAuthorized(auxKey, authModel) 1686 c.Assert(err, IsNil) 1687 c.Check(ok, Equals, true) 1688 modChecker := &mockSnapModelChecker{mockIsAuthorized: true} 1689 return modChecker, nil 1690 }) 1691 defer restore() 1692 1693 defaultDevice := "device-name" 1694 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{ 1695 WhichModel: func() (*asserts.Model, error) { 1696 return fakeModel, nil 1697 }, 1698 } 1699 res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, keyFn, opts) 1700 c.Assert(err, IsNil) 1701 c.Check(res, DeepEquals, secboot.UnlockResult{ 1702 UnlockMethod: secboot.UnlockedWithSealedKey, 1703 IsEncrypted: true, 1704 PartDevice: "/dev/disk/by-partuuid/enc-dev-partuuid", 1705 FsDevice: "/dev/mapper/device-name-random-uuid-for-test", 1706 }) 1707 c.Check(activated, Equals, 1) 1708 } 1709 1710 func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV1(c *C) { 1711 mockDiskKey := []byte("unsealed-key--64-chars-long-and-not-json-to-match-denver-project") 1712 c.Assert(len(mockDiskKey), Equals, 64) 1713 1714 var reqs []*fde.RevealKeyRequest 1715 // The v1 hooks will just return raw bytes. This is deprecated but 1716 // we need to keep compatbility with the v1 implementation because 1717 // there is a project "denver" that ships with v1 hooks. 1718 restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) { 1719 reqs = append(reqs, req) 1720 return mockDiskKey, nil 1721 }) 1722 defer restore() 1723 1724 restore = secboot.MockFDEHasRevealKey(func() bool { 1725 return true 1726 }) 1727 defer restore() 1728 1729 restore = secboot.MockRandomKernelUUID(func() string { 1730 return "random-uuid-for-test" 1731 }) 1732 defer restore() 1733 1734 mockDiskWithEncDev := &disks.MockDiskMapping{ 1735 FilesystemLabelToPartUUID: map[string]string{ 1736 "device-name-enc": "enc-dev-partuuid", 1737 }, 1738 } 1739 1740 mockEncryptedDiskKey := []byte("USK$encrypted-key-no-json-to-match-denver-project") 1741 activated := 0 1742 restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte, options *sb.ActivateVolumeOptions) error { 1743 activated++ 1744 c.Check(key, DeepEquals, mockDiskKey) 1745 return nil 1746 }) 1747 defer restore() 1748 1749 defaultDevice := "device-name" 1750 // note that we write a v1 created keyfile here, i.e. it's a raw 1751 // disk-key without any json 1752 mockSealedKeyFile := filepath.Join(c.MkDir(), "keyfile") 1753 err := ioutil.WriteFile(mockSealedKeyFile, mockEncryptedDiskKey, 0600) 1754 c.Assert(err, IsNil) 1755 1756 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{} 1757 res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts) 1758 c.Assert(err, IsNil) 1759 c.Check(res, DeepEquals, secboot.UnlockResult{ 1760 UnlockMethod: secboot.UnlockedWithSealedKey, 1761 IsEncrypted: true, 1762 PartDevice: "/dev/disk/by-partuuid/enc-dev-partuuid", 1763 FsDevice: "/dev/mapper/device-name-random-uuid-for-test", 1764 }) 1765 c.Check(activated, Equals, 1) 1766 c.Check(reqs, HasLen, 1) 1767 c.Check(reqs[0].Op, Equals, "reveal") 1768 c.Check(reqs[0].SealedKey, DeepEquals, mockEncryptedDiskKey) 1769 c.Check(reqs[0].Handle, IsNil) 1770 } 1771 1772 func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyBadJSONv2(c *C) { 1773 restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) { 1774 return []byte("invalid-json"), nil 1775 }) 1776 defer restore() 1777 1778 restore = secboot.MockFDEHasRevealKey(func() bool { 1779 return true 1780 }) 1781 defer restore() 1782 1783 restore = secboot.MockRandomKernelUUID(func() string { 1784 return "random-uuid-for-test" 1785 }) 1786 defer restore() 1787 1788 mockDiskWithEncDev := &disks.MockDiskMapping{ 1789 FilesystemLabelToPartUUID: map[string]string{ 1790 "device-name-enc": "enc-dev-partuuid", 1791 }, 1792 } 1793 1794 restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) { 1795 // XXX: this is what the real 1796 // MockSbActivateVolumeWithKeyData will do 1797 _, _, err := keyData.RecoverKeys() 1798 if err != nil { 1799 return nil, err 1800 } 1801 c.Fatal("should not get this far") 1802 return nil, nil 1803 }) 1804 defer restore() 1805 1806 defaultDevice := "device-name" 1807 mockSealedKeyFile := makeMockSealedKeyFile(c, nil) 1808 1809 opts := &secboot.UnlockVolumeUsingSealedKeyOptions{} 1810 _, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts) 1811 1812 c.Check(err, ErrorMatches, `cannot unlock encrypted partition: invalid key data:.*`) 1813 }