gitee.com/mysnapcore/mysnapd@v0.1.0/secboot/keymgr/keymgr_luks2_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2022 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 package keymgr_test 20 21 import ( 22 "bytes" 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "testing" 28 29 sb "gitee.com/mysnapcore/mysecboot" 30 . "gopkg.in/check.v1" 31 32 "gitee.com/mysnapcore/mysnapd/dirs" 33 "gitee.com/mysnapcore/mysnapd/osutil" 34 "gitee.com/mysnapcore/mysnapd/secboot/keymgr" 35 "gitee.com/mysnapcore/mysnapd/secboot/keys" 36 "gitee.com/mysnapcore/mysnapd/secboot/luks2" 37 "gitee.com/mysnapcore/mysnapd/testutil" 38 ) 39 40 type keymgrSuite struct { 41 testutil.BaseTest 42 43 rootDir string 44 cryptsetupCmd *testutil.MockCmd 45 } 46 47 var _ = Suite(&keymgrSuite{}) 48 49 func TestT(t *testing.T) { 50 TestingT(t) 51 } 52 53 const mockedMeminfo = `MemTotal: 929956 kB 54 CmaTotal: 131072 kB 55 ` 56 57 var mockRecoveryKey = keys.RecoveryKey{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 58 59 func (s *keymgrSuite) SetUpTest(c *C) { 60 s.BaseTest.SetUpTest(c) 61 62 s.cryptsetupCmd = testutil.MockCommand(c, "cryptsetup", ``) 63 s.AddCleanup(s.cryptsetupCmd.Restore) 64 s.rootDir = c.MkDir() 65 dirs.SetRootDir(s.rootDir) 66 s.AddCleanup(func() { dirs.SetRootDir("/") }) 67 c.Assert(os.MkdirAll(dirs.RunDir, 0755), IsNil) 68 69 mockedMeminfoFile := filepath.Join(c.MkDir(), "meminfo") 70 err := ioutil.WriteFile(mockedMeminfoFile, []byte(mockedMeminfo), 0644) 71 c.Assert(err, IsNil) 72 s.AddCleanup(osutil.MockProcMeminfo(mockedMeminfoFile)) 73 } 74 75 func (s *keymgrSuite) mockCryptsetupForAddKey(c *C) *testutil.MockCmd { 76 cmd := testutil.MockCommand(c, "cryptsetup", fmt.Sprintf(` 77 while [ "$#" -gt 1 ]; do 78 case "$1" in 79 luksAddKey) 80 cat > %s 81 shift 82 ;; 83 --key-file) 84 cat "$2" > %s 85 shift 2 86 ;; 87 *) 88 shift 89 ;; 90 esac 91 done 92 `, filepath.Join(s.rootDir, "new.key"), filepath.Join(s.rootDir, "unlock.key"))) 93 return cmd 94 } 95 96 func (s *keymgrSuite) verifyCryptsetupAddKey(c *C, cmd *testutil.MockCmd, unlockKey, newKey []byte) { 97 c.Assert(cmd, NotNil) 98 calls := cmd.Calls() 99 c.Assert(calls, HasLen, 2) 100 c.Assert(calls[0], HasLen, 16) 101 c.Assert(calls[0][5], testutil.Contains, s.rootDir) 102 calls[0][5] = "<fifo>" 103 c.Assert(calls[0], DeepEquals, []string{ 104 "cryptsetup", "luksAddKey", "--type", "luks2", 105 "--key-file", "<fifo>", 106 "--pbkdf", "argon2i", 107 "--pbkdf-force-iterations", "4", 108 "--pbkdf-memory", "202834", 109 "--key-slot", "1", 110 "/dev/foobar", "-", 111 }) 112 c.Assert(calls[1], DeepEquals, []string{ 113 "cryptsetup", "config", "--priority", "prefer", "--key-slot", "0", "/dev/foobar", 114 }) 115 c.Check(filepath.Join(s.rootDir, "unlock.key"), testutil.FileEquals, unlockKey) 116 c.Check(filepath.Join(s.rootDir, "new.key"), testutil.FileEquals, newKey) 117 } 118 119 func (s *keymgrSuite) TestAddRecoveryKeyToDeviceUnlockFromKeyring(c *C) { 120 unlockKey := "1234abcd" 121 getCalls := 0 122 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 123 getCalls++ 124 c.Check(devicePath, Equals, "/dev/foobar") 125 c.Check(remove, Equals, false) 126 c.Check(prefix, Equals, "ubuntu-fde") 127 return []byte(unlockKey), nil 128 }) 129 defer restore() 130 131 cmd := s.mockCryptsetupForAddKey(c) 132 defer cmd.Restore() 133 err := keymgr.AddRecoveryKeyToLUKSDevice(mockRecoveryKey, "/dev/foobar") 134 c.Assert(err, IsNil) 135 c.Assert(getCalls, Equals, 1) 136 s.verifyCryptsetupAddKey(c, cmd, []byte(unlockKey), mockRecoveryKey[:]) 137 } 138 139 func (s *keymgrSuite) TestAddRecoveryKeyToDeviceNoUnlockKey(c *C) { 140 getCalls := 0 141 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 142 getCalls++ 143 c.Check(devicePath, Equals, "/dev/foobar") 144 return nil, fmt.Errorf("cannot find key in kernel keyring") 145 }) 146 defer restore() 147 148 cmd := s.mockCryptsetupForAddKey(c) 149 defer cmd.Restore() 150 err := keymgr.AddRecoveryKeyToLUKSDevice(mockRecoveryKey, "/dev/foobar") 151 c.Assert(err, ErrorMatches, "cannot obtain current unlock key for /dev/foobar: cannot find key in kernel keyring") 152 c.Assert(getCalls, Equals, 1) 153 c.Assert(cmd.Calls(), HasLen, 0) 154 } 155 156 func (s *keymgrSuite) TestAddRecoveryKeyToDeviceCryptsetupFail(c *C) { 157 unlockKey := "1234abcd" 158 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 159 return []byte(unlockKey), nil 160 }) 161 defer restore() 162 163 cmd := testutil.MockCommand(c, "cryptsetup", ` 164 while [ "$#" -gt 1 ]; do 165 case "$1" in 166 --key-file) 167 cat "$2" > /dev/null 168 shift 2 169 ;; 170 *) 171 shift 1 172 ;; 173 esac 174 done 175 echo "Other error, cryptsetup boom" 176 exit 1 177 `) 178 defer cmd.Restore() 179 err := keymgr.AddRecoveryKeyToLUKSDevice(mockRecoveryKey, "/dev/foobar") 180 c.Assert(err, ErrorMatches, "cannot add key: cryptsetup failed with: Other error, cryptsetup boom") 181 // should match the keyslot full error too 182 c.Assert(keymgr.IsKeyslotAlreadyUsed(err), Equals, false) 183 } 184 185 func (s *keymgrSuite) TestAddRecoveryKeyToDeviceOccupiedSlot(c *C) { 186 unlockKey := "1234abcd" 187 getCalls := 0 188 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 189 getCalls++ 190 c.Check(devicePath, Equals, "/dev/foobar") 191 c.Check(remove, Equals, false) 192 c.Check(prefix, Equals, "ubuntu-fde") 193 return []byte(unlockKey), nil 194 }) 195 defer restore() 196 197 cmd := testutil.MockCommand(c, "cryptsetup", ` 198 while [ "$#" -gt 1 ]; do 199 case "$1" in 200 --key-file) 201 cat "$2" > /dev/null 202 shift 2 203 ;; 204 *) 205 shift 1 206 ;; 207 esac 208 done 209 echo "Key slot 1 is full, please select another one." >&2 210 exit 1 211 `) 212 defer cmd.Restore() 213 err := keymgr.AddRecoveryKeyToLUKSDevice(mockRecoveryKey, "/dev/foobar") 214 c.Assert(err, ErrorMatches, "cannot add key: cryptsetup failed with: Key slot 1 is full.*") 215 c.Assert(getCalls, Equals, 1) 216 calls := cmd.Calls() 217 c.Assert(calls, HasLen, 1) 218 c.Assert(calls[0], HasLen, 16) 219 c.Assert(calls[0][:2], DeepEquals, []string{"cryptsetup", "luksAddKey"}) 220 // should match the keyslot full error too 221 c.Assert(keymgr.IsKeyslotAlreadyUsed(err), Equals, true) 222 } 223 224 func (s *keymgrSuite) TestAddRecoveryKeyToDeviceUsingExistingKey(c *C) { 225 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 226 return nil, fmt.Errorf("unexpected call") 227 }) 228 defer restore() 229 230 cmd := s.mockCryptsetupForAddKey(c) 231 defer cmd.Restore() 232 key := bytes.Repeat([]byte{1}, 32) 233 err := keymgr.AddRecoveryKeyToLUKSDeviceUsingKey(mockRecoveryKey, keys.EncryptionKey(key), "/dev/foobar") 234 c.Assert(err, IsNil) 235 s.verifyCryptsetupAddKey(c, cmd, []byte(key), mockRecoveryKey[:]) 236 } 237 238 func (s *keymgrSuite) TestRemoveRecoveryKeyFromDevice(c *C) { 239 unlockKey := "1234abcd" 240 getCalls := 0 241 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 242 getCalls++ 243 c.Check(devicePath, Equals, "/dev/foobar") 244 c.Check(remove, Equals, false) 245 c.Check(prefix, Equals, "ubuntu-fde") 246 return []byte(unlockKey), nil 247 }) 248 defer restore() 249 250 err := keymgr.RemoveRecoveryKeyFromLUKSDevice("/dev/foobar") 251 c.Assert(err, IsNil) 252 c.Assert(getCalls, Equals, 1) 253 calls := s.cryptsetupCmd.Calls() 254 c.Assert(calls, DeepEquals, [][]string{ 255 {"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "1"}, 256 }) 257 } 258 259 func (s *keymgrSuite) TestRemoveRecoveryKeyFromDeviceAlreadyEmpty(c *C) { 260 unlockKey := "1234abcd" 261 getCalls := 0 262 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 263 getCalls++ 264 return []byte(unlockKey), nil 265 }) 266 defer restore() 267 268 cmd := testutil.MockCommand(c, "cryptsetup", ` 269 echo "Keyslot 1 is not active." >&2 270 exit 1 271 `) 272 defer cmd.Restore() 273 274 err := keymgr.RemoveRecoveryKeyFromLUKSDevice("/dev/foobar") 275 c.Assert(err, IsNil) 276 c.Assert(getCalls, Equals, 1) 277 calls := cmd.Calls() 278 c.Assert(calls, DeepEquals, [][]string{ 279 {"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "1"}, 280 }) 281 } 282 283 func (s *keymgrSuite) TestRemoveRecoveryKeyFromDeviceKeyNotInKeyring(c *C) { 284 getCalls := 0 285 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 286 getCalls++ 287 c.Check(devicePath, Equals, "/dev/foobar") 288 return nil, fmt.Errorf("cannot find key in kernel keyring") 289 }) 290 defer restore() 291 292 err := keymgr.RemoveRecoveryKeyFromLUKSDevice("/dev/foobar") 293 c.Assert(err, ErrorMatches, "cannot obtain current unlock key for /dev/foobar: cannot find key in kernel keyring") 294 c.Assert(getCalls, Equals, 1) 295 c.Assert(s.cryptsetupCmd.Calls(), HasLen, 0) 296 } 297 298 func (s *keymgrSuite) TestRemoveRecoveryKeyFromDeviceUsingKey(c *C) { 299 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 300 c.Fail() 301 return nil, fmt.Errorf("unexpected call") 302 }) 303 defer restore() 304 305 cryptsetupCmd := testutil.MockCommand(c, "cryptsetup", fmt.Sprintf(` 306 while [ "$#" -gt 1 ]; do 307 case "$1" in 308 luksKillSlot) 309 cat > %s 310 shift 311 ;; 312 *) 313 shift 314 ;; 315 esac 316 done 317 `, filepath.Join(s.rootDir, "unlock.key"))) 318 defer cryptsetupCmd.Restore() 319 320 key := bytes.Repeat([]byte{1}, 32) 321 err := keymgr.RemoveRecoveryKeyFromLUKSDeviceUsingKey(keys.EncryptionKey(key), "/dev/foobar") 322 c.Assert(err, IsNil) 323 calls := cryptsetupCmd.Calls() 324 c.Assert(calls, DeepEquals, [][]string{ 325 {"cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "1"}, 326 }) 327 c.Assert(filepath.Join(s.rootDir, "unlock.key"), testutil.FileEquals, key) 328 } 329 330 func (s *keymgrSuite) TestStageEncryptionKeyHappy(c *C) { 331 unlockKey := "1234abcd" 332 getCalls := 0 333 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 334 getCalls++ 335 c.Check(devicePath, Equals, "/dev/foobar") 336 c.Check(remove, Equals, false) 337 c.Check(prefix, Equals, "ubuntu-fde") 338 return []byte(unlockKey), nil 339 }) 340 defer restore() 341 342 cmd := testutil.MockCommand(c, "cryptsetup", ` 343 while [ "$#" -gt 1 ]; do 344 case "$1" in 345 --key-file) 346 cat "$2" 347 shift 2 348 ;; 349 *) 350 shift 1 351 ;; 352 esac 353 done 354 `) 355 defer cmd.Restore() 356 // try a too short key 357 key := bytes.Repeat([]byte{1}, 12) 358 err := keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") 359 c.Assert(err, ErrorMatches, "cannot use a key of size different than 32") 360 361 key = bytes.Repeat([]byte{1}, 32) 362 err = keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") 363 c.Assert(err, IsNil) 364 c.Assert(getCalls, Equals, 1) 365 calls := cmd.Calls() 366 c.Assert(calls, HasLen, 2) 367 c.Assert(calls[0], DeepEquals, []string{ 368 "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2", 369 }) 370 c.Assert(calls[1], HasLen, 14) 371 c.Assert(calls[1][5], testutil.Contains, dirs.RunDir) 372 calls[1][5] = "<fifo>" 373 // temporary key 374 c.Assert(calls[1], DeepEquals, []string{ 375 "cryptsetup", "luksAddKey", "--type", "luks2", 376 "--key-file", "<fifo>", 377 "--pbkdf", "argon2i", 378 "--iter-time", "100", 379 "--key-slot", "2", 380 "/dev/foobar", "-", 381 }) 382 } 383 384 func (s *keymgrSuite) TestStageEncryptionKeyKilledSlotAlreadyEmpty(c *C) { 385 unlockKey := "1234abcd" 386 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 387 return []byte(unlockKey), nil 388 }) 389 defer restore() 390 391 cmd := testutil.MockCommand(c, "cryptsetup", ` 392 while [ "$#" -gt 1 ]; do 393 case "$1" in 394 luksKillSlot) 395 killslot=1 396 shift 397 ;; 398 --key-file) 399 cat "$2" > /dev/null 400 shift 2 401 ;; 402 *) 403 shift 404 ;; 405 esac 406 done 407 if [ "$killslot" = "1" ]; then 408 echo "Keyslot 1 is not active." >&2 409 exit 1 410 fi 411 `) 412 defer cmd.Restore() 413 414 key := bytes.Repeat([]byte{1}, 32) 415 err := keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") 416 c.Assert(err, IsNil) 417 calls := cmd.Calls() 418 c.Assert(calls, HasLen, 2) 419 c.Assert(calls[0], DeepEquals, []string{ 420 "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2", 421 }) 422 c.Assert(calls[1], HasLen, 14) 423 c.Assert(calls[1][5], testutil.Contains, dirs.RunDir) 424 calls[1][5] = "<fifo>" 425 // temporary key 426 c.Assert(calls[1], DeepEquals, []string{ 427 "cryptsetup", "luksAddKey", "--type", "luks2", 428 "--key-file", "<fifo>", 429 "--pbkdf", "argon2i", 430 "--iter-time", "100", 431 "--key-slot", "2", 432 "/dev/foobar", "-", 433 }) 434 } 435 436 func (s *keymgrSuite) TestStageEncryptionKeyGetUnlockFail(c *C) { 437 getCalls := 0 438 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 439 getCalls++ 440 c.Check(devicePath, Equals, "/dev/foobar") 441 return nil, fmt.Errorf("cannot find key in kernel keyring") 442 }) 443 defer restore() 444 445 key := bytes.Repeat([]byte{1}, 32) 446 err := keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") 447 c.Assert(err, ErrorMatches, "cannot obtain current unlock key for /dev/foobar: cannot find key in kernel keyring") 448 c.Assert(s.cryptsetupCmd.Calls(), HasLen, 0) 449 } 450 451 func (s *keymgrSuite) TestChangeEncryptionTempKeyFails(c *C) { 452 unlockKey := "1234abcd" 453 getCalls := 0 454 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 455 getCalls++ 456 return []byte(unlockKey), nil 457 }) 458 defer restore() 459 460 cmd := testutil.MockCommand(c, "cryptsetup", ` 461 while [ "$#" -gt 1 ]; do 462 case "$1" in 463 --key-file) 464 cat "$2" > /dev/null 465 shift 466 ;; 467 luksAddKey) 468 add=1 469 ;; 470 --key-slot) 471 if [ "$add" = "1" ] && [ "$2" = "2" ]; then 472 echo "mock failure" >&2 473 exit 1 474 fi 475 ;; 476 *) 477 ;; 478 esac 479 shift 480 done 481 `) 482 defer cmd.Restore() 483 484 key := bytes.Repeat([]byte{1}, 32) 485 err := keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") 486 c.Assert(err, ErrorMatches, "cannot add temporary key: cryptsetup failed with: mock failure") 487 c.Assert(getCalls, Equals, 1) 488 calls := cmd.Calls() 489 c.Assert(calls, HasLen, 2) 490 } 491 492 func (s *keymgrSuite) TestTransitionEncryptionKeyExpectedHappy(c *C) { 493 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 494 c.Errorf("unepected call") 495 return nil, fmt.Errorf("unexpected call") 496 }) 497 defer restore() 498 499 cmd := testutil.MockCommand(c, "cryptsetup", ` 500 while [ "$#" -gt 1 ]; do 501 case "$1" in 502 luksAddKey) 503 keyadd=1 504 shift 505 ;; 506 --key-slot) 507 keyslot="$2" 508 shift 2 509 ;; 510 --key-file) 511 cat "$2" > /dev/null 512 shift 2 513 ;; 514 *) 515 shift 1 516 ;; 517 esac 518 done 519 if [ "$keyadd" = "1" ] && [ "$keyslot" = "2" ]; then 520 echo "Key slot 2 is full, please select another one." >&2 521 exit 1 522 fi 523 `) 524 defer cmd.Restore() 525 // try a too short key 526 key := bytes.Repeat([]byte{1}, 12) 527 err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") 528 c.Assert(err, ErrorMatches, "cannot use a key of size different than 32") 529 530 key = bytes.Repeat([]byte{1}, 32) 531 err = keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") 532 c.Assert(err, IsNil) 533 calls := cmd.Calls() 534 c.Assert(calls, HasLen, 5) 535 // probing the key slot use 536 c.Assert(calls[0], HasLen, 14) 537 c.Assert(calls[0][5], testutil.Contains, dirs.RunDir) 538 calls[0][5] = "<fifo>" 539 // temporary key 540 c.Assert(calls[0], DeepEquals, []string{ 541 "cryptsetup", "luksAddKey", "--type", "luks2", 542 "--key-file", "<fifo>", 543 "--pbkdf", "argon2i", 544 "--iter-time", "100", 545 "--key-slot", "2", 546 "/dev/foobar", "-", 547 }) 548 // killing the encryption key 549 c.Assert(calls[1], DeepEquals, []string{ 550 "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "0", 551 }) 552 // adding the new encryption key 553 c.Assert(calls[2], HasLen, 14) 554 c.Assert(calls[2][5], testutil.Contains, dirs.RunDir) 555 calls[2][5] = "<fifo>" 556 c.Assert(calls[2], DeepEquals, []string{ 557 "cryptsetup", "luksAddKey", "--type", "luks2", 558 "--key-file", "<fifo>", 559 "--pbkdf", "argon2i", 560 "--iter-time", "100", 561 "--key-slot", "0", 562 "/dev/foobar", "-", 563 }) 564 // kill the temp key 565 c.Assert(calls[3], DeepEquals, []string{ 566 "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2", 567 }) 568 // set priority 569 c.Assert(calls[4], DeepEquals, []string{ 570 "cryptsetup", "config", "--priority", "prefer", "--key-slot", "0", "/dev/foobar", 571 }) 572 } 573 574 func (s *keymgrSuite) TestTransitionEncryptionKeyHappyKillSlotsInactive(c *C) { 575 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 576 c.Errorf("unepected call") 577 return nil, fmt.Errorf("unexpected call") 578 }) 579 defer restore() 580 581 cmd := testutil.MockCommand(c, "cryptsetup", ` 582 while [ "$#" -gt 1 ]; do 583 case "$1" in 584 luksKillSlot) 585 killslot=1 586 shift 587 ;; 588 luksAddKey) 589 keyadd=1 590 shift 591 ;; 592 --key-slot) 593 keyslot="$2" 594 shift 2 595 ;; 596 --key-file) 597 cat "$2" > /dev/null 598 shift 2 599 ;; 600 *) 601 shift 1 602 ;; 603 esac 604 done 605 if [ "$keyadd" = "1" ] && [ "$keyslot" = "2" ]; then 606 echo "Key slot 2 is full, please select another one." >&2 607 exit 1 608 elif [ "$killslot" = "1" ]; then 609 echo "Keyslot 2 is not active." >&2 610 exit 1 611 fi 612 `) 613 defer cmd.Restore() 614 // try a too short key 615 key := bytes.Repeat([]byte{1}, 12) 616 err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") 617 c.Assert(err, ErrorMatches, "cannot use a key of size different than 32") 618 619 key = bytes.Repeat([]byte{1}, 32) 620 err = keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") 621 c.Assert(err, IsNil) 622 calls := cmd.Calls() 623 c.Assert(calls, HasLen, 5) 624 // probing the key slot use 625 c.Assert(calls[0], HasLen, 14) 626 // temporary key 627 c.Assert(calls[0][:2], DeepEquals, []string{ 628 "cryptsetup", "luksAddKey", 629 }) 630 // killing the encryption key 631 c.Assert(calls[1], DeepEquals, []string{ 632 "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "0", 633 }) 634 // adding the new encryption key 635 c.Assert(calls[2], HasLen, 14) 636 c.Assert(calls[2][5], testutil.Contains, dirs.RunDir) 637 calls[2][5] = "<fifo>" 638 c.Assert(calls[2], DeepEquals, []string{ 639 "cryptsetup", "luksAddKey", "--type", "luks2", 640 "--key-file", "<fifo>", 641 "--pbkdf", "argon2i", 642 "--iter-time", "100", 643 "--key-slot", "0", 644 "/dev/foobar", "-", 645 }) 646 // kill the temp key 647 c.Assert(calls[3], DeepEquals, []string{ 648 "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2", 649 }) 650 // set priority 651 c.Assert(calls[4], DeepEquals, []string{ 652 "cryptsetup", "config", "--priority", "prefer", "--key-slot", "0", "/dev/foobar", 653 }) 654 } 655 656 func (s *keymgrSuite) TestTransitionEncryptionKeyHappyOtherErrs(c *C) { 657 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 658 c.Errorf("unepected call") 659 return nil, fmt.Errorf("unexpected call") 660 }) 661 defer restore() 662 663 cmd := testutil.MockCommand(c, "cryptsetup", ` 664 while [ "$#" -gt 1 ]; do 665 case "$1" in 666 luksAddKey) 667 keyadd=1 668 shift 669 ;; 670 --key-slot) 671 keyslot="$2" 672 shift 2 673 ;; 674 --key-file) 675 cat "$2" > /dev/null 676 shift 2 677 ;; 678 *) 679 shift 1 680 ;; 681 esac 682 done 683 if [ "$keyadd" = "1" ] && [ "$keyslot" = "2" ]; then 684 echo "Key slot 2 is full, please select another one." >&2 685 exit 1 686 elif [ "$keyadd" = "1" ] && [ "$keyslot" = "0" ]; then 687 echo "mock error" >&2 688 exit 1 689 fi 690 `) 691 defer cmd.Restore() 692 key := bytes.Repeat([]byte{1}, 32) 693 err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") 694 c.Assert(err, ErrorMatches, "cannot add new encryption key: cryptsetup failed with: mock error") 695 calls := cmd.Calls() 696 c.Assert(calls, HasLen, 3) 697 // probing the key slot use 698 c.Assert(calls[0], HasLen, 14) 699 // temporary key 700 c.Assert(calls[0][:2], DeepEquals, []string{ 701 "cryptsetup", "luksAddKey", 702 }) 703 // killing the encryption key 704 c.Assert(calls[1], DeepEquals, []string{ 705 "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "0", 706 }) 707 // adding the new encryption key 708 c.Assert(calls[2], HasLen, 14) 709 c.Assert(calls[2][5], testutil.Contains, dirs.RunDir) 710 calls[2][5] = "<fifo>" 711 c.Assert(calls[2], DeepEquals, []string{ 712 "cryptsetup", "luksAddKey", "--type", "luks2", 713 "--key-file", "<fifo>", 714 "--pbkdf", "argon2i", 715 "--iter-time", "100", 716 "--key-slot", "0", 717 "/dev/foobar", "-", 718 }) 719 } 720 721 func (s *keymgrSuite) TestTransitionEncryptionKeyCannotAddKeyNotStaged(c *C) { 722 // conditions like when the encryption key has not been previously 723 // staged 724 725 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 726 c.Errorf("unepected call") 727 return nil, fmt.Errorf("unexpected call") 728 }) 729 defer restore() 730 731 cmd := testutil.MockCommand(c, "cryptsetup", ` 732 while [ "$#" -gt 1 ]; do 733 case "$1" in 734 luksAddKey) 735 keyadd=1 736 shift 737 ;; 738 --key-file) 739 cat "$2" > /dev/null 740 shift 2 741 ;; 742 *) 743 shift 1 744 ;; 745 esac 746 done 747 if [ "$keyadd" = "1" ] ; then 748 echo "No key available with this passphrase." >&2 749 exit 1 750 fi 751 `) 752 defer cmd.Restore() 753 754 key := bytes.Repeat([]byte{1}, 32) 755 err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") 756 c.Assert(err, ErrorMatches, "cannot add new encryption key: cryptsetup failed with: No key available with this passphrase.") 757 calls := cmd.Calls() 758 c.Assert(calls, HasLen, 1) 759 // probing the key slot use 760 c.Assert(calls[0], HasLen, 14) 761 c.Assert(calls[0][5], testutil.Contains, dirs.RunDir) 762 calls[0][5] = "<fifo>" 763 // temporary key 764 c.Assert(calls[0], DeepEquals, []string{ 765 "cryptsetup", "luksAddKey", "--type", "luks2", 766 "--key-file", "<fifo>", 767 "--pbkdf", "argon2i", 768 "--iter-time", "100", 769 "--key-slot", "2", 770 "/dev/foobar", "-", 771 }) 772 } 773 774 func (s *keymgrSuite) TestTransitionEncryptionKeyPostRebootHappy(c *C) { 775 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 776 c.Errorf("unepected call") 777 return nil, fmt.Errorf("unexpected call") 778 }) 779 defer restore() 780 781 cmd := testutil.MockCommand(c, "cryptsetup", ` 782 while [ "$#" -gt 1 ]; do 783 case "$1" in 784 --key-file) 785 cat "$2" > /dev/null 786 shift 2 787 ;; 788 *) 789 shift 790 ;; 791 esac 792 done 793 `) 794 defer cmd.Restore() 795 796 key := bytes.Repeat([]byte{1}, 32) 797 err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") 798 c.Assert(err, IsNil) 799 calls := cmd.Calls() 800 c.Assert(calls, HasLen, 2) 801 c.Assert(calls[0], HasLen, 14) 802 c.Assert(calls[0][5], testutil.Contains, dirs.RunDir) 803 calls[0][5] = "<fifo>" 804 // adding to a temporary key slot is successful, indicating a previously 805 // successful transition 806 c.Assert(calls[0], DeepEquals, []string{ 807 "cryptsetup", "luksAddKey", "--type", "luks2", 808 "--key-file", "<fifo>", 809 "--pbkdf", "argon2i", 810 "--iter-time", "100", 811 "--key-slot", "2", 812 "/dev/foobar", "-", 813 }) 814 // an a cleanup of the temp key slot 815 c.Assert(calls[1], DeepEquals, []string{ 816 "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2", 817 }) 818 } 819 820 func (s *keymgrSuite) TestTransitionEncryptionKeyPostRebootCannotKillSlot(c *C) { 821 // a post reboot scenario in which the luksKillSlot fails unexpectedly 822 823 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { 824 c.Errorf("unepected call") 825 return nil, fmt.Errorf("unexpected call") 826 }) 827 defer restore() 828 829 cmd := testutil.MockCommand(c, "cryptsetup", ` 830 while [ "$#" -gt 1 ]; do 831 case "$1" in 832 luksKillSlot) 833 killslot=1 834 shift 835 ;; 836 --key-file) 837 cat "$2" > /dev/null 838 shift 2 839 ;; 840 *) 841 shift 842 ;; 843 esac 844 done 845 if [ "$killslot" = "1" ]; then 846 echo "mock error" >&2 847 exit 5 848 fi 849 `) 850 defer cmd.Restore() 851 852 key := bytes.Repeat([]byte{1}, 32) 853 err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") 854 c.Assert(err, ErrorMatches, "cannot kill temporary key slot: cryptsetup failed with: mock error") 855 calls := cmd.Calls() 856 c.Assert(calls, HasLen, 2) 857 c.Assert(calls[0], HasLen, 14) 858 c.Assert(calls[0][:2], DeepEquals, []string{ 859 "cryptsetup", "luksAddKey", 860 }) 861 // an a cleanup of the temp key slot 862 c.Assert(calls[1], DeepEquals, []string{ 863 "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2", 864 }) 865 } 866 867 func (s *keymgrSuite) TestRecoveryKDF(c *C) { 868 mockedMeminfoFile := filepath.Join(c.MkDir(), "meminfo") 869 s.AddCleanup(osutil.MockProcMeminfo(mockedMeminfoFile)) 870 871 _, err := keymgr.RecoveryKDF() 872 c.Assert(err, ErrorMatches, "cannot get usable memory for KDF parameters when adding the recovery key: open .*") 873 874 c.Assert(ioutil.WriteFile(mockedMeminfoFile, []byte(mockedMeminfo), 0644), IsNil) 875 876 opts, err := keymgr.RecoveryKDF() 877 c.Assert(err, IsNil) 878 c.Assert(opts, DeepEquals, &luks2.KDFOptions{ 879 MemoryKiB: 202834, 880 ForceIterations: 4, 881 }) 882 883 const lotsOfMem = `MemTotal: 2097152 kB 884 CmaTotal: 131072 kB 885 ` 886 c.Assert(ioutil.WriteFile(mockedMeminfoFile, []byte(lotsOfMem), 0644), IsNil) 887 opts, err = keymgr.RecoveryKDF() 888 c.Assert(err, IsNil) 889 c.Assert(opts, DeepEquals, &luks2.KDFOptions{ 890 MemoryKiB: 786432, 891 ForceIterations: 4, 892 }) 893 894 const littleMem = `MemTotal: 262144 kB 895 CmaTotal: 131072 kB 896 ` 897 c.Assert(ioutil.WriteFile(mockedMeminfoFile, []byte(littleMem), 0644), IsNil) 898 opts, err = keymgr.RecoveryKDF() 899 c.Assert(err, IsNil) 900 c.Assert(opts, DeepEquals, &luks2.KDFOptions{ 901 MemoryKiB: 32, 902 ForceIterations: 4, 903 }) 904 }