/
cryptohome_client.go
925 lines (800 loc) · 38.2 KB
/
cryptohome_client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package hwsec
import (
"context"
"encoding/hex"
"fmt"
"regexp"
"strconv"
"strings"
"time"
cpb "chromiumos/system_api/cryptohome_proto"
"chromiumos/tast/errors"
"chromiumos/tast/testing"
)
const (
cryptohomeWrappedKeysetString = "TPM_WRAPPED"
installAttributesFinalizeSuccessOutput = "InstallAttributesFinalize(): 1"
listKeysExLabelPrefix = "Label: "
addKeyExSuccessMessage = "Key added."
removeKeyExSuccessMessage = "Key removed."
migrateKeyExSucessMessage = "Key migration succeeded."
)
var (
// userHashRegexp extracts the hash from a cryptohome dir's path.
// Example: "/home/.shadow/118c4648065f5cd3660e17a53533ec7bc924d01f"
userHashRegexp = regexp.MustCompile("^/home/user/([[:xdigit:]]+)$")
// authSessionIDRegexp matches the auth session ID.
// It would match "auth_session_id:*"
authSessionIDRegexp = regexp.MustCompile(`(auth_session_id:)(.+)(\n)`)
)
func getLastLine(s string) string {
lines := strings.Split(strings.TrimSpace(s), "\n")
if len(lines) == 0 {
return ""
}
return lines[len(lines)-1]
}
// CryptohomeClient wraps and the functions of cryptohomeBinary and parses the outputs to
// structured data.
type CryptohomeClient struct {
runner CmdRunner
binary *cryptohomeBinary
cryptohomePathBinary *cryptohomePathBinary
daemonController *DaemonController
}
// NewCryptohomeClient creates a new CryptohomeClient.
func NewCryptohomeClient(r CmdRunner) *CryptohomeClient {
return &CryptohomeClient{
runner: r,
binary: newCryptohomeBinary(r),
cryptohomePathBinary: newCryptohomePathBinary(r),
daemonController: NewDaemonController(r),
}
}
// InstallAttributesStatus retrieves the a status string from cryptohome. The status string is in JSON format and holds the various cryptohome related status.
func (u *CryptohomeClient) InstallAttributesStatus(ctx context.Context) (string, error) {
out, err := u.binary.installAttributesGetStatus(ctx)
if err != nil {
return "", errors.Wrapf(err, "failed to get Install Attributes status with the following output %q", out)
}
// Strip the ending new line.
out = strings.TrimSuffix(out, "\n")
return out, err
}
// InstallAttributesGet retrieves the install attributes with the name of attributeName, and returns the tuple (value, error), whereby value is the value of the attributes, and error is nil iff the operation is successful, otherwise error is the error that occurred.
func (u *CryptohomeClient) InstallAttributesGet(ctx context.Context, attributeName string) (string, error) {
out, err := u.binary.installAttributesGet(ctx, attributeName)
if err != nil {
return "", errors.Wrapf(err, "failed to get Install Attributes with the following output %q", out)
}
// Strip the ending new line.
out = strings.TrimSuffix(out, "\n")
return out, err
}
// InstallAttributesSet sets the install attributes with the name of attributeName with the value attributeValue, and returns error, whereby error is nil iff the operation is successful, otherwise error is the error that occurred.
func (u *CryptohomeClient) InstallAttributesSet(ctx context.Context, attributeName, attributeValue string) error {
out, err := u.binary.installAttributesSet(ctx, attributeName, attributeValue)
if err != nil {
return errors.Wrapf(err, "failed to set Install Attributes with the following output %q", out)
}
return nil
}
// InstallAttributesFinalize finalizes the install attributes, and returns error encountered if any. error is nil iff the operation completes successfully.
func (u *CryptohomeClient) InstallAttributesFinalize(ctx context.Context) error {
out, err := u.binary.installAttributesFinalize(ctx)
if err != nil {
return errors.Wrapf(err, "failed to finalize Install Attributes with the following output %q", out)
}
if !strings.Contains(out, installAttributesFinalizeSuccessOutput) {
return errors.Errorf("failed to finalize Install Attributes, incorrect output message %q", out)
}
return nil
}
// InstallAttributesCount retrieves the number of entries in install attributes. It returns count and error. error is nil iff the operation completes successfully, and in this case count holds the number of entries in install attributes.
func (u *CryptohomeClient) InstallAttributesCount(ctx context.Context) (int, error) {
out, err := u.binary.installAttributesCount(ctx)
if err != nil {
return -1, errors.Wrapf(err, "failed to query install attributes count with the following output %q", out)
}
var result int
n, err := fmt.Sscanf(out, "InstallAttributesCount(): %d", &result)
if err != nil {
return -1, errors.Wrapf(err, "failed to parse InstallAttributesCount output %q", out)
}
if n != 1 {
return -1, errors.Errorf("invalid InstallAttributesCount output %q", out)
}
return result, nil
}
// installAttributesBooleanHelper is a helper function that helps to parse the output of install attribute series of command that returns a boolean.
func installAttributesBooleanHelper(out string, err error, methodName string) (bool, error) {
if err != nil {
return false, errors.Wrapf(err, "failed to run %s(), output %q", methodName, out)
}
var result int
n, err := fmt.Sscanf(out, methodName+"(): %d", &result)
if err != nil {
return false, errors.Wrapf(err, "failed to parse %s(), output %q", methodName, out)
}
if n != 1 {
return false, errors.Errorf("invalid %s() output %q", methodName, out)
}
return result != 0, nil
}
// InstallAttributesIsReady checks if install attributes is ready, returns isReady and error. error is nil iff the operation completes successfully, and in this case isReady is whether install attributes is ready.
func (u *CryptohomeClient) InstallAttributesIsReady(ctx context.Context) (bool, error) {
out, err := u.binary.installAttributesIsReady(ctx)
return installAttributesBooleanHelper(out, err, "InstallAttributesIsReady")
}
// InstallAttributesIsSecure checks if install attributes is secure, returns isSecure and error. error is nil iff the operation completes successfully, and in this case isSecure is whether install attributes is secure.
func (u *CryptohomeClient) InstallAttributesIsSecure(ctx context.Context) (bool, error) {
out, err := u.binary.installAttributesIsSecure(ctx)
return installAttributesBooleanHelper(out, err, "InstallAttributesIsSecure")
}
// InstallAttributesIsInvalid checks if install attributes is invalid, returns isInvalid and error. error is nil iff the operation completes successfully, and in this case isInvalid is whether install attributes is invalid.
func (u *CryptohomeClient) InstallAttributesIsInvalid(ctx context.Context) (bool, error) {
out, err := u.binary.installAttributesIsInvalid(ctx)
return installAttributesBooleanHelper(out, err, "InstallAttributesIsInvalid")
}
// InstallAttributesIsFirstInstall checks if install attributes is the first install state, returns isFirstInstall and error. error is nil iff the operation completes successfully, and in this case isFirstInstall is whether install attributes is in the first install state.
func (u *CryptohomeClient) InstallAttributesIsFirstInstall(ctx context.Context) (bool, error) {
out, err := u.binary.installAttributesIsFirstInstall(ctx)
return installAttributesBooleanHelper(out, err, "InstallAttributesIsFirstInstall")
}
// IsMounted checks if any vault is mounted.
func (u *CryptohomeClient) IsMounted(ctx context.Context) (bool, error) {
out, err := u.binary.isMounted(ctx)
if err != nil {
return false, errors.Wrap(err, "failed to check if mounted")
}
result, err := strconv.ParseBool(strings.TrimSuffix(string(out), "\n"))
if err != nil {
return false, errors.Wrap(err, "failed to parse output from cryptohome")
}
return result, nil
}
// Unmount unmounts the vault for username.
func (u *CryptohomeClient) Unmount(ctx context.Context, username string) (bool, error) {
if err := u.UnmountAll(ctx); err != nil {
return false, errors.Wrap(err, "failed to unmount all")
}
return true, nil
}
// UnmountAll unmounts all vault.
func (u *CryptohomeClient) UnmountAll(ctx context.Context) error {
goal, _, _, err := u.daemonController.Status(ctx, UIDaemon)
if err != nil {
return errors.Wrap(err, "failed to get the status of ui")
}
if goal == startGoal {
// Restart the UI to make sure nothing is still using cryptohome and unmount the mount point.
if err := u.daemonController.Restart(ctx, UIDaemon); err != nil {
return errors.Wrap(err, "failed to restart the UI")
}
} else {
// Running the ui-post-stop directly if UI doesn't start.
if _, err := u.runner.Run(ctx, "/usr/share/cros/init/ui-post-stop"); err != nil {
return errors.Wrap(err, "failed to run ui-post-stop")
}
}
return nil
}
// VaultConfig specifies the extra options to Mounting/Creating a vault.
type VaultConfig struct {
// Ephemeral is set to true if the vault is ephemeral, that is, the vault is erased after the user logs out.
Ephemeral bool
// Ecryptfs is set to true if the vault should be backed by eCryptfs.
Ecryptfs bool
}
// NewVaultConfig creates a default vault config.
func NewVaultConfig() *VaultConfig {
return &VaultConfig{}
}
// vaultConfigToExtraFlags converts VaultConfig to flags accepted by the cryptohome command line.
func vaultConfigToExtraFlags(config *VaultConfig) []string {
const (
// mountFlagEphemeral is the flag passed to cryptohome command line when you want the vault to be ephemeral.
mountFlagEphemeral = "--ensure_ephemeral"
// mountFlagEcryptfs is the flag passed to cryptohome command line when you want the vault to use ecryptfs.
mountFlagEcryptfs = "--ecryptfs"
)
var extraFlags []string
if config.Ephemeral {
extraFlags = append(extraFlags, mountFlagEphemeral)
}
if config.Ecryptfs {
extraFlags = append(extraFlags, mountFlagEcryptfs)
}
return extraFlags
}
const (
// PassAuth is the constant for AuthConfig.AuthType, representing password authentication.
PassAuth = iota
// ChallengeAuth is the constant for AuthConfig.AuthType, representing challenge-response authenticating.
ChallengeAuth = iota
)
// AuthConfig represents the data required to authenticate a user.
// It could be password authentication or challenge-response authentication.
type AuthConfig struct {
// AuthType is the type of authentication.
AuthType int
// Username is the username for authentication.
Username string
// Password is the user's password.
// Used only when AuthType is PassAuth
Password string
// KeyDelegateName is the dbus service name for the authentication delegate.
// Used only when AuthType is ChallengeAuth
KeyDelegateName string
// KeyDelegatePath is the dbus service path for the authentication delegate.
// Used only when AuthType is ChallengeAuth
KeyDelegatePath string
// ChallengeSPKI is the SPKI that contains the public key for challenge response. It's in DER format.
// Used only when AuthType is ChallengeAuth
ChallengeSPKI []byte
// ChallengeAlg is the cryptographic algorithm to use when
// Used only when AuthType is ChallengeAuth
ChallengeAlg cpb.ChallengeSignatureAlgorithm
}
// NewPassAuthConfig creates an AuthConfig for Password Authentication.
func NewPassAuthConfig(username, password string) *AuthConfig {
config := &AuthConfig{}
config.AuthType = PassAuth
config.Username = username
config.Password = password
return config
}
// NewChallengeAuthConfig creates an AuthConfig for Challenge-Response Authentication.
func NewChallengeAuthConfig(username, keyDelegateName, keyDelegatePath string, challengeSPKI []byte, challengeAlg cpb.ChallengeSignatureAlgorithm) *AuthConfig {
config := &AuthConfig{}
config.AuthType = ChallengeAuth
config.Username = username
config.KeyDelegateName = keyDelegateName
config.KeyDelegatePath = keyDelegatePath
config.ChallengeSPKI = challengeSPKI
config.ChallengeAlg = challengeAlg
return config
}
// authConfigToExtraFlags converts AuthConfig to flags accepted by the cryptohome command line.
func authConfigToExtraFlags(config *AuthConfig) []string {
var extraFlags []string
if config.AuthType == PassAuth {
extraFlags = append(extraFlags, "--password="+config.Password)
} else if config.AuthType == ChallengeAuth {
extraFlags = append(extraFlags, "--challenge_alg="+config.ChallengeAlg.String())
extraFlags = append(extraFlags, "--challenge_spki="+hex.EncodeToString(config.ChallengeSPKI))
extraFlags = append(extraFlags, "--key_delegate_name="+config.KeyDelegateName)
extraFlags = append(extraFlags, "--key_delegate_path="+config.KeyDelegatePath)
} else {
panic("Invalid AuthType in CryptohomeClient")
}
return extraFlags
}
// MountVault mounts the vault for username; creates a new vault if no vault yet if create is true. error is nil if the operation completed successfully.
func (u *CryptohomeClient) MountVault(ctx context.Context, label string, authConfig *AuthConfig, create bool, vaultConfig *VaultConfig) error {
extraFlags := vaultConfigToExtraFlags(vaultConfig)
extraFlags = append(extraFlags, authConfigToExtraFlags(authConfig)...)
if _, err := u.binary.mountEx(ctx, authConfig.Username, create, label, extraFlags); err != nil {
return errors.Wrap(err, "failed to mount")
}
return nil
}
// MountGuest creates a mount point for a guest user; error is nil if the operation completed successfully.
func (u *CryptohomeClient) MountGuest(ctx context.Context) error {
if _, err := u.binary.mountGuestEx(ctx); err != nil {
return errors.Wrap(err, "failed to mount guest")
}
return nil
}
// MountKiosk creates a mount point for a kiosk; error is nil if the operation completed successfully.
func (u *CryptohomeClient) MountKiosk(ctx context.Context) error {
extraFlags := []string{"--public_mount"}
if _, err := u.binary.mountEx(ctx, "kiosk", true, "public_mount", extraFlags); err != nil {
return errors.Wrap(err, "failed to mount kiosk")
}
return nil
}
// GetSanitizedUsername computes the sanitized username for the given user.
// If useDBus is true, the sanitized username will be computed by cryptohome (through dbus). Otherwise, it'll be computed directly by libbrillo (without dbus).
func (u *CryptohomeClient) GetSanitizedUsername(ctx context.Context, username string, useDBus bool) (string, error) {
out, err := u.binary.getSanitizedUsername(ctx, username, useDBus)
if err != nil {
return "", errors.Wrap(err, "failed to call cryptohomeBinary.GetSanitizedUsername")
}
outs := strings.TrimSpace(string(out))
exp := regexp.MustCompile("^[a-f0-9]{40}$")
// A proper sanitized username should be a hex string of length 40.
if !exp.MatchString(outs) {
return "", errors.Errorf("invalid sanitized username %q", outs)
}
return outs, nil
}
// GetSystemSalt retrieves the system salt and return the hex encoded version of it.
// If useDBus is true, the system salt will be retrieved from cryptohome (through dbus). Otherwise, it'll be loaded directly by libbrillo (without dbus).
func (u *CryptohomeClient) GetSystemSalt(ctx context.Context, useDBus bool) (string, error) {
out, err := u.binary.getSystemSalt(ctx, useDBus)
if err != nil {
return "", errors.Wrap(err, "failed to call cryptohomeBinary.GetSystemSalt")
}
outs := strings.TrimSpace(string(out))
exp := regexp.MustCompile("^[a-f0-9]+$")
// System salt should be a non-empty hex string.
if !exp.MatchString(outs) {
return "", errors.Errorf("invalid system salt %q", outs)
}
return outs, nil
}
// CheckVaultAndUnlockWebAuthnSecret checks the vault via |CheckKeyEx| dbus method, and set the unlock_webauthn_secret param to true.
func (u *CryptohomeClient) CheckVaultAndUnlockWebAuthnSecret(ctx context.Context, label string, authConfig *AuthConfig) (bool, error) {
extraFlags := authConfigToExtraFlags(authConfig)
_, err := u.binary.checkKeyEx(ctx, authConfig.Username, label, true, extraFlags)
if err != nil {
return false, errors.Wrap(err, "failed to check key")
}
return true, nil
}
// CheckVault checks the vault via |CheckKeyEx| dbus method.
func (u *CryptohomeClient) CheckVault(ctx context.Context, label string, authConfig *AuthConfig) (bool, error) {
extraFlags := authConfigToExtraFlags(authConfig)
_, err := u.binary.checkKeyEx(ctx, authConfig.Username, label, false, extraFlags)
if err != nil {
return false, errors.Wrap(err, "failed to check key")
}
return true, nil
}
// ListVaultKeys queries the vault associated with user username and password password, and returns nil for error iff the operation is completed successfully, in that case, the returned slice of string contains the labels of keys belonging to that vault.
func (u *CryptohomeClient) ListVaultKeys(ctx context.Context, username string) ([]string, error) {
binaryOutput, err := u.binary.listKeysEx(ctx, username)
if err != nil {
return []string{}, errors.Wrap(err, "failed to call list keys")
}
output := string(binaryOutput)
lines := strings.Split(output, "\n")
var result []string
for _, s := range lines {
if strings.HasPrefix(s, listKeysExLabelPrefix) {
result = append(result, s[len(listKeysExLabelPrefix):])
}
}
return result, nil
}
// AddVaultKey adds the key with newLabel and newPassword to the user specified by username, with password password and label label. nil is returned iff the operation is successful.
func (u *CryptohomeClient) AddVaultKey(ctx context.Context, username, password, label, newPassword, newLabel string, lowEntropy bool) error {
binaryOutput, err := u.binary.addKeyEx(ctx, username, password, label, newPassword, newLabel, lowEntropy)
if err != nil {
return errors.Wrap(err, "failed to call AddKeyEx")
}
output := string(binaryOutput)
if !strings.Contains(output, addKeyExSuccessMessage) {
testing.ContextLogf(ctx, "Incorrect AddKeyEx message; got %q, want %q", output, addKeyExSuccessMessage)
return errors.Errorf("incorrect message from AddKeyEx; got %q, want %q", output, addKeyExSuccessMessage)
}
return nil
}
// RemoveVaultKey removes the key with label removeLabel from user specified by username's vault. password for username is supplied so the operation can be proceeded. nil is returned iff the operation is successful.
func (u *CryptohomeClient) RemoveVaultKey(ctx context.Context, username, password, removeLabel string) error {
binaryOutput, err := u.binary.removeKeyEx(ctx, username, password, removeLabel)
if err != nil {
return errors.Wrap(err, "failed to call RemoveKeyEx")
}
output := string(binaryOutput)
if !strings.Contains(output, removeKeyExSuccessMessage) {
testing.ContextLogf(ctx, "Incorrect RemoveKeyEx message; got %q, want %q", output, removeKeyExSuccessMessage)
return errors.Errorf("incorrect message from RemoveKeyEx; got %q, want %q", output, removeKeyExSuccessMessage)
}
return nil
}
// ChangeVaultPassword changes the vault for user username with label and password to newPassword. nil is returned iff the operation is successful.
func (u *CryptohomeClient) ChangeVaultPassword(ctx context.Context, username, password, label, newPassword string) error {
binaryOutput, err := u.binary.migrateKeyEx(ctx, username, password, label, newPassword)
if err != nil {
return errors.Wrap(err, "failed to call MigrateKeyEx")
}
output := string(binaryOutput)
if !strings.Contains(output, migrateKeyExSucessMessage) {
testing.ContextLogf(ctx, "Incorrect MigrateKeyEx message; got %q, want %q", output, migrateKeyExSucessMessage)
return errors.Errorf("incorrect message from MigrateKeyEx; got %q, want %q", output, migrateKeyExSucessMessage)
}
return nil
}
// RemoveVault remove the vault for username.
func (u *CryptohomeClient) RemoveVault(ctx context.Context, username string) (bool, error) {
_, err := u.binary.remove(ctx, username)
if err != nil {
return false, errors.Wrap(err, "failed to remove vault")
}
return true, nil
}
// UnmountAndRemoveVault attempts to unmount all vaults and remove the vault for username.
// This is a simple helper, and it's created because this is a commonly used combination.
func (u *CryptohomeClient) UnmountAndRemoveVault(ctx context.Context, username string) error {
// Note: Vault must be unmounted to be removed.
if err := u.UnmountAll(ctx); err != nil {
return errors.Wrap(err, "failed to unmount all")
}
if _, err := u.RemoveVault(ctx, username); err != nil {
return errors.Wrap(err, "failed to remove vault")
}
return nil
}
// LockToSingleUserMountUntilReboot will block users other than the specified from logging in if the call succeeds, and in that case, nil is returned.
func (u *CryptohomeClient) LockToSingleUserMountUntilReboot(ctx context.Context, username string) error {
const successMessage = "Login disabled."
binaryOutput, err := u.binary.lockToSingleUserMountUntilReboot(ctx, username)
if err != nil {
return errors.Wrap(err, "failed to call lock_to_single_user_mount_until_reboot")
}
output := strings.TrimSuffix(string(binaryOutput), "\n")
// Note that we are checking the output message again because we want to
// catch cases that return 0 but wasn't successful.
if !strings.Contains(output, successMessage) {
return errors.Errorf("incorrect message from LockToSingleUserMountUntilReboot; got %q, want %q", output, successMessage)
}
return nil
}
// IsTPMWrappedKeySet checks if the current user vault is TPM-backed.
func (u *CryptohomeClient) IsTPMWrappedKeySet(ctx context.Context, username string) (bool, error) {
out, err := u.binary.dumpKeyset(ctx, username)
if err != nil {
return false, errors.Wrap(err, "failed to dump keyset")
}
return strings.Contains(string(out), cryptohomeWrappedKeysetString), nil
}
// CheckTPMWrappedUserKeyset checks if the given user's keyset is backed by TPM.
// Returns an error if the keyset is not TPM-backed or if there's anything wrong.
func (u *CryptohomeClient) CheckTPMWrappedUserKeyset(ctx context.Context, user string) error {
if keysetTPMBacked, err := u.IsTPMWrappedKeySet(ctx, user); err != nil {
return errors.Wrap(err, "failed to check user keyset")
} else if !keysetTPMBacked {
return errors.New("user keyset is not TPM-backed")
}
return nil
}
// parseTokenStatus parse the output of cryptohome --action=pkcs11_system_token_status or cryptohome --action=pkcs11_token_status and return the label, pin, slot and error (in that order).
func parseTokenStatus(cmdOutput string) (returnedLabel, returnedPin string, returnedSlot int, returnedErr error) {
arr := strings.Split(cmdOutput, "\n")
labels := []string{"Label", "Pin", "Slot"}
params := make(map[string]string)
for _, str := range arr {
for _, label := range labels {
labelPrefix := label + " = "
if strings.HasPrefix(str, labelPrefix) {
params[label] = str[len(labelPrefix):]
}
}
}
// Check that we've got all the parameters
if len(params) != len(labels) {
return "", "", -1, errors.Errorf("missing parameters in token status output, got: %v", params)
}
// Slot should be an integer
slot, err := strconv.Atoi(params["Slot"])
if err != nil {
return "", "", -1, errors.Wrap(err, "token slot not integer")
}
// Fill up the return values
return params["Label"], params["Pin"], slot, nil
}
// GetTokenInfoForUser retrieve the token label, pin and slot for the user token if username is non-empty, or system token if username is empty.
func (u *CryptohomeClient) GetTokenInfoForUser(ctx context.Context, username string) (returnedLabel, returnedPin string, returnedSlot int, returnedErr error) {
cmdOutput := ""
if username == "" {
// We want the system token.
out, err := u.binary.pkcs11SystemTokenInfo(ctx)
cmdOutput = string(out)
if err != nil {
return "", "", -1, errors.Wrapf(err, "failed to get system token info %q", cmdOutput)
}
} else {
// We want the user token.
out, err := u.binary.pkcs11UserTokenInfo(ctx, username)
cmdOutput = string(out)
if err != nil {
return "", "", -1, errors.Wrapf(err, "failed to get user token info %q", cmdOutput)
}
}
label, pin, slot, err := parseTokenStatus(cmdOutput)
if err != nil {
return "", "", -1, errors.Wrapf(err, "failed to parse token status %q", cmdOutput)
}
return label, pin, slot, nil
}
// GetTokenForUser retrieve the token slot for the user token if username is non-empty, or system token if username is empty.
func (u *CryptohomeClient) GetTokenForUser(ctx context.Context, username string) (int, error) {
_, _, slot, err := u.GetTokenInfoForUser(ctx, username)
return slot, err
}
// WaitForUserToken wait until the user token for the specified user is ready. Otherwise, return an error if the token is still unavailable.
func (u *CryptohomeClient) WaitForUserToken(ctx context.Context, username string) error {
const waitForUserTokenTimeout = 15 * time.Second
if username == "" {
// This method is for user token, not system token.
// Note: For those who want to wait for system token, system token is always ready, no need to wait.
return errors.New("empty username in WaitForUserToken")
}
if err := testing.Poll(ctx, func(ctx context.Context) error {
slot, err := u.GetTokenForUser(ctx, username)
if err != nil {
// This is unexpected and shouldn't usually happen.
return testing.PollBreak(errors.Wrapf(err, "failed to get slot ID for username %q", username))
}
if slot <= 0 {
return errors.Wrapf(err, "invalid slot ID %d for username %q", slot, username)
}
return nil
}, &testing.PollOptions{Timeout: waitForUserTokenTimeout}); err != nil {
return errors.Wrap(err, "failed waiting for user token")
}
return nil
}
// FWMPError is a custom error type that conveys the error as well as parsed
// ErrorCode from cryptohome API.
type FWMPError struct {
*errors.E
// ErrorCode is the error code from FWMP methods.
ErrorCode string
}
// GetFirmwareManagementParameters retrieves the firmware parameter flags and hash.
// It returns (flags, hash, msg, errorCode, err), whereby flags and hash is part of FWMP, and will be valid iff err is nil; msg is the message from the command line; errorCode is the error code from dbus call, if available.
// The operation is successful iff err is nil.
func (u *CryptohomeClient) GetFirmwareManagementParameters(ctx context.Context) (flags, hash string, returnedError *FWMPError) {
binaryMsg, err := u.binary.getFirmwareManagementParameters(ctx)
msg := string(binaryMsg)
// Parse for the error code and stuffs because we might need the error code in the return error in case it failed.
const flagsPrefix = "flags=0x"
const hashPrefix = "hash="
const errorPrefix = "error: "
prefixes := []string{flagsPrefix, hashPrefix, errorPrefix}
params := make(map[string]string, len(prefixes))
for _, line := range strings.Split(msg, "\n") {
line := strings.Trim(line, " ")
for _, prefix := range prefixes {
if strings.HasPrefix(line, prefix) {
if _, existing := params[prefix]; existing {
return "", "", &FWMPError{E: errors.Errorf("duplicate attribute %q found GetFirmwareManagementParameters output", prefix)}
}
params[prefix] = line[len(prefix):]
}
}
}
if err != nil {
testing.ContextLogf(ctx, "GetFirmwareManagementParameters failed with %q", msg)
errorCode, haveError := params[errorPrefix]
if haveError {
return "", "", &FWMPError{E: errors.Wrapf(err, "failed to call GetFirmwareManagementParameters command, error %q", errorCode), ErrorCode: errorCode}
}
return "", "", &FWMPError{E: errors.Wrap(err, "failed to call GetFirmwareManagementParameters with unknown error")}
}
// return the hash and flags if they exist, and return error otherwise.
paramsFlags, haveFlags := params[flagsPrefix]
paramsHash, haveHash := params[hashPrefix]
if !haveFlags {
return "", "", &FWMPError{E: errors.New("no flags in GetFirmwareManagementParameters output")}
}
if !haveHash {
return "", "", &FWMPError{E: errors.New("no hash in GetFirmwareManagementParameters output")}
}
return paramsFlags, paramsHash, nil
}
// SetFirmwareManagementParameters sets the firmware management parameters flags and hash (both as a hex string), then returns (msg, error).
// msg is the command line output from cryptohome command; error is nil iff the operation is successful.
func (u *CryptohomeClient) SetFirmwareManagementParameters(ctx context.Context, flags, hash string) (string, error) {
binaryMsg, err := u.binary.setFirmwareManagementParameters(ctx, "0x"+flags, hash)
msg := string(binaryMsg)
// Note that error code is not parsed because currently no tests requires it.
if err != nil {
testing.ContextLogf(ctx, "SetFirmwareManagementParameters failed with %q", msg)
return msg, errors.Wrap(err, "failed to call SetFirmwareManagementParameters")
}
return msg, nil
}
// RemoveFirmwareManagementParameters removes the firmware management parameters.
// msg is the command line output from cryptohome command; error is nil iff the operation is successful.
func (u *CryptohomeClient) RemoveFirmwareManagementParameters(ctx context.Context) (string, error) {
binaryMsg, err := u.binary.removeFirmwareManagementParameters(ctx)
msg := string(binaryMsg)
// Note that error code is not parsed because currently no tests requires it.
if err != nil {
testing.ContextLogf(ctx, "RemoveFirmwareManagementParameters failed with %q", msg)
return msg, errors.Wrap(err, "failed to call RemoveFirmwareManagementParameters")
}
return msg, nil
}
// FirmwareManagementParametersInfo contains the information regarding FWMP, so that it can be backed up and restored.
type FirmwareManagementParametersInfo struct {
// parametersExist is true iff the FWMP is set/exists on the DUT.
parametersExist bool
// flags contain the flags in the FWMP. This is valid iff parametersExist is true.
flags string
// hash contains the developer hash in the FWMP. This is valid iff parametersExist is true.
hash string
}
// BackupFWMP backs up the current FWMP by returning the FWMP. The operation is successful iff error is nil.
func (u *CryptohomeClient) BackupFWMP(ctx context.Context) (*FirmwareManagementParametersInfo, error) {
flags, hash, err := u.GetFirmwareManagementParameters(ctx)
if err != nil {
if err.ErrorCode != "CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_INVALID" {
return nil, errors.Wrap(err, "failed to get FWMP for backup")
}
// FWMP doesn't exist.
return &FirmwareManagementParametersInfo{parametersExist: false}, nil
}
fwmp := FirmwareManagementParametersInfo{parametersExist: true, flags: flags, hash: hash}
return &fwmp, nil
}
// RestoreFWMP restores the FWMP from fwmp in parameter, and return nil iff the operation is successful.
func (u *CryptohomeClient) RestoreFWMP(ctx context.Context, fwmp *FirmwareManagementParametersInfo) error {
if !fwmp.parametersExist {
// Parameters doesn't exist, so let's clear it.
if _, err := u.RemoveFirmwareManagementParameters(ctx); err != nil {
return errors.Wrap(err, "failed to clear FWMP")
}
return nil
}
// FWMP exists, so let's set the correct values.
if _, err := u.SetFirmwareManagementParameters(ctx, fwmp.flags, fwmp.hash); err != nil {
return errors.Wrap(err, "failed to set FWMP")
}
return nil
}
// GetAccountDiskUsage returns the disk space (in bytes) used by the username.
func (u *CryptohomeClient) GetAccountDiskUsage(ctx context.Context, username string) (diskUsage int64, returnedError error) {
binaryMsg, err := u.binary.getAccountDiskUsage(ctx, username)
msg := string(binaryMsg)
if err != nil {
testing.ContextLogf(ctx, "Failure to call GetAccountDiskUsage, got %q", msg)
return -1, errors.Wrap(err, "failed to call GetAccountDiskUsage")
}
var result int64
for _, s := range strings.Split(strings.TrimSpace(msg), "\n") {
if n, err := fmt.Sscanf(s, "Account Disk Usage in bytes: %d", &result); err == nil && n == 1 {
// We've found the output that we need.
return result, nil
}
}
testing.ContextLogf(ctx, "Unexpected GetAccountDiskUsage output, got %q", msg)
return -1, errors.New("failed to parse GetAccountDiskUsage output")
}
// GetHomeUserPath retrieves the user specified by username's user home path.
func (u *CryptohomeClient) GetHomeUserPath(ctx context.Context, username string) (string, error) {
binaryMsg, err := u.cryptohomePathBinary.userPath(ctx, username)
msg := string(binaryMsg)
if err != nil {
testing.ContextLogf(ctx, "Failed to call cryptohome-path user, got %q", msg)
return "", errors.Wrap(err, "failed to call cryptohome-path user")
}
return strings.TrimSpace(msg), nil
}
// GetRootUserPath retrieves the user specified by username's user root path.
func (u *CryptohomeClient) GetRootUserPath(ctx context.Context, username string) (string, error) {
binaryMsg, err := u.cryptohomePathBinary.systemPath(ctx, username)
msg := string(binaryMsg)
if err != nil {
testing.ContextLogf(ctx, "Failed to call cryptohome-path system, got %q", msg)
return "", errors.Wrap(err, "failed to call cryptohome-path system")
}
return strings.TrimSpace(msg), nil
}
// GetUserHash returns user's cryptohome hash.
func (u *CryptohomeClient) GetUserHash(ctx context.Context, username string) (string, error) {
binaryMsg, err := u.cryptohomePathBinary.userPath(ctx, username)
msg := string(binaryMsg)
if err != nil {
testing.ContextLogf(ctx, "Failed to call cryptohome-path user, got %q", msg)
return "", errors.Wrap(err, "failed to call cryptohome-path user")
}
p := strings.TrimSpace(msg)
m := userHashRegexp.FindStringSubmatch(p)
if m == nil {
return "", errors.Errorf("didn't find hash in path %q", p)
}
return m[1], nil
}
// SupportsLECredentials calls GetSupportedKeyPolicies and parses the output for low entropy credential support.
func (u *CryptohomeClient) SupportsLECredentials(ctx context.Context) (bool, error) {
binaryMsg, err := u.binary.getSupportedKeyPolicies(ctx)
if err != nil {
return false, errors.Wrap(err, "GetSupportedKeyPolicies failed")
}
// TODO(crbug.com/1187192): Parsing human readable output is error prone.
// Parse the text output which looks something like:
// low_entropy_credentials_supported: true
// GetSupportedKeyPolicies success.
return strings.Contains(string(binaryMsg), "low_entropy_credentials_supported: true"), nil
}
// GetKeyData returns the key data for the specified user and label.
func (u *CryptohomeClient) GetKeyData(ctx context.Context, user, keyLabel string) (string, error) {
binaryMsg, err := u.binary.getKeyData(ctx, user, keyLabel)
if err != nil {
return "", errors.Wrap(err, "GetKeyData failed")
}
return string(binaryMsg), nil
}
// StartAuthSession starts an AuthSession for a given user.
func (u *CryptohomeClient) StartAuthSession(ctx context.Context, user string, isEphemeral bool) (string, error) {
binaryMsg, err := u.binary.startAuthSession(ctx, user, isEphemeral)
if err != nil {
return "", err
}
m := authSessionIDRegexp.FindSubmatch(binaryMsg)
if m == nil {
return "", errors.Errorf("didn't find auth session in output %q", string(binaryMsg))
}
return strings.TrimSpace(string(m[2])), nil
}
// AuthenticateAuthSession authenticates an AuthSession with a given authSessionID.
// password is ignored if publicMount is set to true.
func (u *CryptohomeClient) AuthenticateAuthSession(ctx context.Context, password, authSessionID string, publicMount bool) error {
_, err := u.binary.authenticateAuthSession(ctx, password, authSessionID, publicMount)
return err
}
// UpdateCredentialWithAuthSession updated a credential using an AuthSession with a given authSessionID.
// password is ignored if publicMount is set to true.
func (u *CryptohomeClient) UpdateCredentialWithAuthSession(ctx context.Context, password, authSessionID string, publicMount bool) error {
_, err := u.binary.updateCredentialWithAuthSession(ctx, password, authSessionID, publicMount)
return err
}
// AuthenticateAuthFactor authenticates an AuthSession with a given authSessionID via an auth factor.
func (u *CryptohomeClient) AuthenticateAuthFactor(ctx context.Context, authSessionID, label, password string) error {
_, err := u.binary.authenticateAuthFactor(ctx, authSessionID, label, password)
return err
}
// AuthenticatePinAuthFactor authenticates an AuthSession with a given authSessionID via pin.
func (u *CryptohomeClient) AuthenticatePinAuthFactor(ctx context.Context, authSessionID, label, pin string) error {
_, err := u.binary.authenticatePinAuthFactor(ctx, authSessionID, label, pin)
return err
}
// AddCredentialsWithAuthSession creates the credentials for the user with given password.
// password is ignored if publicMount is set to true.
func (u *CryptohomeClient) AddCredentialsWithAuthSession(ctx context.Context, user, password, authSessionID string, publicMount bool) error {
_, err := u.binary.addCredentialsWithAuthSession(ctx, user, password, authSessionID, publicMount)
return err
}
// AddAuthFactor creates an auth factor for the user with given password.
func (u *CryptohomeClient) AddAuthFactor(ctx context.Context, authSessionID, label, password string) error {
_, err := u.binary.addAuthFactor(ctx, authSessionID, label, password)
return err
}
// AddPinAuthFactor creates an auth factor for the user with given password.
func (u *CryptohomeClient) AddPinAuthFactor(ctx context.Context, authSessionID, label, pin string) error {
_, err := u.binary.addPinAuthFactor(ctx, authSessionID, label, pin)
return err
}
// PrepareGuestVault prepares vault for guest session.
func (u *CryptohomeClient) PrepareGuestVault(ctx context.Context) error {
_, err := u.binary.prepareGuestVault(ctx)
return err
}
// PrepareEphemeralVault prepares vault for ephemeral session.
func (u *CryptohomeClient) PrepareEphemeralVault(ctx context.Context, authSessionID string) error {
_, err := u.binary.prepareEphemeralVault(ctx, authSessionID)
return err
}
// PreparePersistentVault prepares vault for persistent user session.
func (u *CryptohomeClient) PreparePersistentVault(ctx context.Context, authSessionID string, ecryptfs bool) error {
_, err := u.binary.preparePersistentVault(ctx, authSessionID, ecryptfs)
return err
}
// PrepareVaultForMigration prepares vault for migration.
func (u *CryptohomeClient) PrepareVaultForMigration(ctx context.Context, authSessionID string) error {
_, err := u.binary.prepareVaultForMigration(ctx, authSessionID)
return err
}
// CreatePersistentUser creates persistent user.
func (u *CryptohomeClient) CreatePersistentUser(ctx context.Context, authSessionID string) error {
_, err := u.binary.createPersistentUser(ctx, authSessionID)
return err
}
// MountWithAuthSession mounts a user with AuthSessionID.
func (u *CryptohomeClient) MountWithAuthSession(ctx context.Context, authSessionID string, publicMount bool) error {
_, err := u.binary.mountWithAuthSession(ctx, authSessionID, publicMount)
return err
}
// InvalidateAuthSession invalidates a user with AuthSessionID.
func (u *CryptohomeClient) InvalidateAuthSession(ctx context.Context, authSessionID string) error {
_, err := u.binary.invalidateAuthSession(ctx, authSessionID)
return err
}