1 |
charliebrady |
1.1 |
# |
2 |
|
|
# crypto.py |
3 |
|
|
# |
4 |
|
|
# Copyright (C) 2009 Red Hat, Inc. All rights reserved. |
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 as published by |
8 |
|
|
# the Free Software Foundation; either version 2 of the License, or |
9 |
|
|
# (at your option) any later version. |
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 |
|
|
# Author(s): Dave Lehman <dlehman@redhat.com> |
20 |
|
|
# Martin Sivak <msivak@redhat.com> |
21 |
|
|
# |
22 |
|
|
|
23 |
|
|
import os |
24 |
|
|
from pycryptsetup import CryptSetup |
25 |
|
|
import iutil |
26 |
|
|
|
27 |
|
|
from ..errors import * |
28 |
|
|
|
29 |
|
|
import gettext |
30 |
|
|
_ = lambda x: gettext.ldgettext("anaconda", x) |
31 |
|
|
|
32 |
|
|
# Keep the character set size a power of two to make sure all characters are |
33 |
|
|
# equally likely |
34 |
|
|
GENERATED_PASSPHRASE_CHARSET = ("0123456789" |
35 |
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
36 |
|
|
"abcdefghijklmnopqrstuvwxyz" |
37 |
|
|
"./") |
38 |
|
|
# 20 chars * 6 bits per char = 120 "bits of security" |
39 |
|
|
GENERATED_PASSPHRASE_LENGTH = 20 |
40 |
|
|
|
41 |
|
|
def generateBackupPassphrase(): |
42 |
|
|
rnd = os.urandom(GENERATED_PASSPHRASE_LENGTH) |
43 |
|
|
cs = GENERATED_PASSPHRASE_CHARSET |
44 |
|
|
raw = "".join([cs[ord(c) % len(cs)] for c in rnd]) |
45 |
|
|
|
46 |
|
|
# Make the result easier to read |
47 |
|
|
parts = [] |
48 |
|
|
for i in xrange(0, len(raw), 5): |
49 |
|
|
parts.append(raw[i : i + 5]) |
50 |
|
|
return "-".join(parts) |
51 |
|
|
|
52 |
|
|
def askyes(question): |
53 |
|
|
return True |
54 |
|
|
|
55 |
|
|
def dolog(priority, text): |
56 |
|
|
pass |
57 |
|
|
|
58 |
|
|
def is_luks(device): |
59 |
|
|
cs = CryptSetup(yesDialog = askyes, logFunc = dolog) |
60 |
|
|
return cs.isLuks(device) |
61 |
|
|
|
62 |
|
|
def luks_uuid(device): |
63 |
|
|
cs = CryptSetup(yesDialog = askyes, logFunc = dolog) |
64 |
|
|
return cs.luksUUID(device).strip() |
65 |
|
|
|
66 |
|
|
def luks_status(name): |
67 |
|
|
"""True means active, False means inactive (or non-existent)""" |
68 |
|
|
cs = CryptSetup(yesDialog = askyes, logFunc = dolog) |
69 |
|
|
return cs.luksStatus(name)!=0 |
70 |
|
|
|
71 |
|
|
def luks_format(device, |
72 |
|
|
passphrase=None, key_file=None, |
73 |
|
|
cipher=None, key_size=None): |
74 |
|
|
cs = CryptSetup(yesDialog = askyes, logFunc = dolog) |
75 |
|
|
key_file_unlink = False |
76 |
|
|
|
77 |
|
|
if passphrase: |
78 |
|
|
key_file = cs.prepare_passphrase_file(passphrase) |
79 |
|
|
key_file_unlink = True |
80 |
|
|
elif key_file and os.path.isfile(key_file): |
81 |
|
|
pass |
82 |
|
|
else: |
83 |
|
|
raise ValueError("luks_format requires either a passphrase or a key file") |
84 |
|
|
|
85 |
|
|
#None is not considered as default value and pycryptsetup doesn't accept it |
86 |
|
|
#so we need to filter out all Nones |
87 |
|
|
kwargs = {} |
88 |
|
|
kwargs["device"] = device |
89 |
|
|
if cipher: kwargs["cipher"] = cipher |
90 |
|
|
if key_file: kwargs["keyfile"] = key_file |
91 |
|
|
if key_size: kwargs["keysize"] = key_size |
92 |
|
|
|
93 |
|
|
rc = cs.luksFormat(**kwargs) |
94 |
|
|
if key_file_unlink: os.unlink(key_file) |
95 |
|
|
|
96 |
|
|
if rc: |
97 |
|
|
raise CryptoError("luks_format failed for '%s'" % device) |
98 |
|
|
|
99 |
|
|
def luks_open(device, name, passphrase=None, key_file=None): |
100 |
|
|
cs = CryptSetup(yesDialog = askyes, logFunc = dolog) |
101 |
|
|
key_file_unlink = False |
102 |
|
|
|
103 |
|
|
if passphrase: |
104 |
|
|
key_file = cs.prepare_passphrase_file(passphrase) |
105 |
|
|
key_file_unlink = True |
106 |
|
|
elif key_file and os.path.isfile(key_file): |
107 |
|
|
pass |
108 |
|
|
else: |
109 |
|
|
raise ValueError("luks_open requires either a passphrase or a key file") |
110 |
|
|
|
111 |
|
|
rc = cs.luksOpen(device = device, name = name, keyfile = key_file) |
112 |
|
|
if key_file_unlink: os.unlink(key_file) |
113 |
|
|
if rc: |
114 |
|
|
raise CryptoError("luks_open failed for %s (%s)" % (device, name)) |
115 |
|
|
|
116 |
|
|
def luks_close(name): |
117 |
|
|
cs = CryptSetup(yesDialog = askyes, logFunc = dolog) |
118 |
|
|
rc = cs.luksClose(name) |
119 |
|
|
if rc: |
120 |
|
|
raise CryptoError("luks_close failed for %s" % name) |
121 |
|
|
|
122 |
|
|
def luks_add_key(device, |
123 |
|
|
new_passphrase=None, new_key_file=None, |
124 |
|
|
passphrase=None, key_file=None): |
125 |
|
|
|
126 |
|
|
params = ["-q"] |
127 |
|
|
|
128 |
|
|
p = os.pipe() |
129 |
|
|
if passphrase: |
130 |
|
|
os.write(p[1], "%s\n" % passphrase) |
131 |
|
|
elif key_file and os.path.isfile(key_file): |
132 |
|
|
params.extend(["--key-file", key_file]) |
133 |
|
|
else: |
134 |
|
|
raise CryptoError("luks_add_key requires either a passphrase or a key file") |
135 |
|
|
|
136 |
|
|
params.extend(["luksAddKey", device]) |
137 |
|
|
|
138 |
|
|
if new_passphrase: |
139 |
|
|
os.write(p[1], "%s\n" % new_passphrase) |
140 |
|
|
elif new_key_file and os.path.isfile(new_key_file): |
141 |
|
|
params.append("%s" % new_key_file) |
142 |
|
|
else: |
143 |
|
|
raise CryptoError("luks_add_key requires either a passphrase or a key file to add") |
144 |
|
|
|
145 |
|
|
os.close(p[1]) |
146 |
|
|
|
147 |
|
|
rc = iutil.execWithRedirect("cryptsetup", params, |
148 |
|
|
stdin = p[0], |
149 |
|
|
stdout = "/dev/tty5", |
150 |
|
|
stderr = "/dev/tty5") |
151 |
|
|
|
152 |
|
|
os.close(p[0]) |
153 |
|
|
if rc: |
154 |
|
|
raise CryptoError("luks add key failed with errcode %d" % (rc,)) |
155 |
|
|
|
156 |
|
|
def luks_remove_key(device, |
157 |
|
|
del_passphrase=None, del_key_file=None, |
158 |
|
|
passphrase=None, key_file=None): |
159 |
|
|
|
160 |
|
|
params = [] |
161 |
|
|
|
162 |
|
|
p = os.pipe() |
163 |
|
|
if del_passphrase: #the first question is about the key we want to remove |
164 |
|
|
os.write(p[1], "%s\n" % del_passphrase) |
165 |
|
|
|
166 |
|
|
if passphrase: |
167 |
|
|
os.write(p[1], "%s\n" % passphrase) |
168 |
|
|
elif key_file and os.path.isfile(key_file): |
169 |
|
|
params.extend(["--key-file", key_file]) |
170 |
|
|
else: |
171 |
|
|
raise CryptoError("luks_remove_key requires either a passphrase or a key file") |
172 |
|
|
|
173 |
|
|
params.extend(["luksRemoveKey", device]) |
174 |
|
|
|
175 |
|
|
if del_passphrase: |
176 |
|
|
pass |
177 |
|
|
elif del_key_file and os.path.isfile(del_key_file): |
178 |
|
|
params.append("%s" % del_key_file) |
179 |
|
|
else: |
180 |
|
|
raise CryptoError("luks_remove_key requires either a passphrase or a key file to remove") |
181 |
|
|
|
182 |
|
|
os.close(p[1]) |
183 |
|
|
|
184 |
|
|
rc = iutil.execWithRedirect("cryptsetup", params, |
185 |
|
|
stdin = p[0], |
186 |
|
|
stdout = "/dev/tty5", |
187 |
|
|
stderr = "/dev/tty5") |
188 |
|
|
|
189 |
|
|
os.close(p[0]) |
190 |
|
|
if rc: |
191 |
|
|
raise CryptoError("luks_remove_key failed with errcode %d" % (rc,)) |
192 |
|
|
|
193 |
|
|
|