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
|
/*
* recovery.go - support for generating recovery passphrases
*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package actions
import (
"fmt"
"os"
"strconv"
"golang.org/x/sys/unix"
"google.golang.org/protobuf/proto"
"github.com/google/fscrypt/crypto"
"github.com/google/fscrypt/metadata"
"github.com/google/fscrypt/util"
)
// modifiedContextWithSource returns a copy of ctx with the protector source
// replaced by source.
func modifiedContextWithSource(ctx *Context, source metadata.SourceType) *Context {
modifiedConfig := proto.Clone(ctx.Config).(*metadata.Config)
modifiedConfig.Source = source
modifiedCtx := *ctx
modifiedCtx.Config = modifiedConfig
return &modifiedCtx
}
// AddRecoveryPassphrase randomly generates a recovery passphrase and adds it as
// a custom_passphrase protector for the given Policy.
func AddRecoveryPassphrase(policy *Policy, dirname string) (*crypto.Key, *Protector, error) {
// 20 random characters in a-z is 94 bits of entropy, which is way more
// than enough for a passphrase which still goes through the usual
// passphrase hashing which makes it extremely costly to brute force.
passphrase, err := crypto.NewRandomPassphrase(20)
if err != nil {
return nil, nil, err
}
defer func() {
if err != nil {
passphrase.Wipe()
}
}()
getPassphraseFn := func(info ProtectorInfo, retry bool) (*crypto.Key, error) {
// CreateProtector() wipes the passphrase, but in this case we
// still need it for later, so make a copy.
return passphrase.Clone()
}
var recoveryProtector *Protector
customCtx := modifiedContextWithSource(policy.Context, metadata.SourceType_custom_passphrase)
seq := 1
for {
// Automatically generate a name for the recovery protector.
name := "Recovery passphrase for " + dirname
if seq != 1 {
name += " (" + strconv.Itoa(seq) + ")"
}
recoveryProtector, err = CreateProtector(customCtx, name, getPassphraseFn, policy.ownerIfCreating)
if err == nil {
break
}
if _, ok := err.(*ErrProtectorNameExists); !ok {
return nil, nil, err
}
seq++
}
if err := policy.AddProtector(recoveryProtector); err != nil {
recoveryProtector.Revert()
return nil, nil, err
}
return passphrase, recoveryProtector, nil
}
// WriteRecoveryInstructions writes a recovery passphrase and instructions to a
// file. This file should initially be located in the encrypted directory
// protected by the passphrase itself. It's up to the user to store the
// passphrase in a different location if they actually need it.
func WriteRecoveryInstructions(recoveryPassphrase *crypto.Key, recoveryProtector *Protector,
policy *Policy, path string) error {
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL|unix.O_NOFOLLOW, 0600)
if err != nil {
return err
}
defer file.Close()
str := fmt.Sprintf(
`fscrypt automatically generated a recovery passphrase for this directory:
%s
It did this because you chose to protect this directory with your login
passphrase, but this directory is not on the root filesystem.
Copy this passphrase to a safe place if you want to still be able to unlock this
directory if you re-install the operating system or connect this storage media
to a different system (which would result in your login protector being lost).
To unlock this directory using this recovery passphrase, run 'fscrypt unlock'
and select the protector named %q.
If you want to disable recovery passphrase generation (not recommended),
re-create this directory and pass the --no-recovery option to 'fscrypt encrypt'.
Alternatively, you can remove this recovery passphrase protector using:
fscrypt metadata remove-protector-from-policy --force --protector=%s:%s --policy=%s:%s
It is safe to keep it around though, as the recovery passphrase is high-entropy.
`, recoveryPassphrase.Data(), recoveryProtector.data.Name,
recoveryProtector.Context.Mount.Path, recoveryProtector.data.ProtectorDescriptor,
policy.Context.Mount.Path, policy.data.KeyDescriptor)
if _, err = file.WriteString(str); err != nil {
return err
}
if recoveryProtector.ownerIfCreating != nil {
if err = util.Chown(file, recoveryProtector.ownerIfCreating); err != nil {
return err
}
}
return file.Sync()
}
|