1 |
charliebrady |
1.1 |
# luks.py |
2 |
|
|
# Device format classes for anaconda's storage configuration module. |
3 |
|
|
# |
4 |
|
|
# Copyright (C) 2009 Red Hat, Inc. |
5 |
|
|
# |
6 |
|
|
# This copyrighted material is made available to anyone wishing to use, |
7 |
|
|
# modify, copy, or redistribute it subject to the terms and conditions of |
8 |
|
|
# the GNU General Public License v.2, or (at your option) any later version. |
9 |
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT |
10 |
|
|
# ANY WARRANTY expressed or implied, including the implied warranties of |
11 |
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
12 |
|
|
# Public License for more details. You should have received a copy of the |
13 |
|
|
# GNU General Public License along with this program; if not, write to the |
14 |
|
|
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 |
|
|
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the |
16 |
|
|
# source code or documentation are not subject to the GNU General Public |
17 |
|
|
# License and may only be used or replicated with the express permission of |
18 |
|
|
# Red Hat, Inc. |
19 |
|
|
# |
20 |
|
|
# Red Hat Author(s): Dave Lehman <dlehman@redhat.com> |
21 |
|
|
# |
22 |
|
|
|
23 |
|
|
|
24 |
|
|
|
25 |
|
|
import os |
26 |
|
|
|
27 |
|
|
try: |
28 |
|
|
import volume_key |
29 |
|
|
except ImportError: |
30 |
|
|
volume_key = None |
31 |
|
|
|
32 |
|
|
from ..storage_log import log_method_call |
33 |
|
|
from ..errors import * |
34 |
|
|
from ..devicelibs import crypto |
35 |
|
|
from . import DeviceFormat, register_device_format |
36 |
|
|
|
37 |
|
|
import gettext |
38 |
|
|
_ = lambda x: gettext.ldgettext("anaconda", x) |
39 |
|
|
|
40 |
|
|
import logging |
41 |
|
|
log = logging.getLogger("storage") |
42 |
|
|
|
43 |
|
|
|
44 |
|
|
class LUKS(DeviceFormat): |
45 |
|
|
""" A LUKS device. """ |
46 |
|
|
_type = "luks" |
47 |
|
|
_name = "LUKS" |
48 |
|
|
_lockedName = _("Encrypted") |
49 |
|
|
_udevTypes = ["crypto_LUKS"] |
50 |
|
|
_formattable = True # can be formatted |
51 |
|
|
_supported = False # is supported |
52 |
|
|
_linuxNative = True # for clearpart |
53 |
|
|
_packages = ["cryptsetup-luks"] # required packages |
54 |
|
|
|
55 |
|
|
def __init__(self, *args, **kwargs): |
56 |
|
|
""" Create a LUKS instance. |
57 |
|
|
|
58 |
|
|
Keyword Arguments: |
59 |
|
|
|
60 |
|
|
device -- the path to the underlying device |
61 |
|
|
name -- the name of the mapped device |
62 |
|
|
uuid -- this device's UUID |
63 |
|
|
passphrase -- device passphrase (string) |
64 |
|
|
key_file -- path to a file containing a key (string) |
65 |
|
|
cipher -- cipher mode string |
66 |
|
|
key_size -- key size in bits |
67 |
|
|
exists -- indicates whether this is an existing format |
68 |
|
|
escrow_cert -- certificate to use for key escrow |
69 |
|
|
add_backup_passphrase -- generate a backup passphrase? |
70 |
|
|
""" |
71 |
|
|
log_method_call(self, *args, **kwargs) |
72 |
|
|
DeviceFormat.__init__(self, *args, **kwargs) |
73 |
|
|
self.cipher = kwargs.get("cipher") |
74 |
|
|
self.key_size = kwargs.get("key_size") |
75 |
|
|
self.mapName = kwargs.get("name") |
76 |
|
|
|
77 |
|
|
if not self.exists and not self.cipher: |
78 |
|
|
self.cipher = "aes-xts-plain64" |
79 |
|
|
if not self.key_size: |
80 |
|
|
# default to the max (512 bits) for aes-xts |
81 |
|
|
self.key_size = 512 |
82 |
|
|
|
83 |
|
|
# FIXME: these should both be lists, but managing them will be a pain |
84 |
|
|
self.__passphrase = kwargs.get("passphrase") |
85 |
|
|
self._key_file = kwargs.get("key_file") |
86 |
|
|
self.escrow_cert = kwargs.get("escrow_cert") |
87 |
|
|
self.add_backup_passphrase = kwargs.get("add_backup_passphrase", False) |
88 |
|
|
|
89 |
|
|
if not self.mapName and self.exists and self.uuid: |
90 |
|
|
self.mapName = "luks-%s" % self.uuid |
91 |
|
|
elif not self.mapName and self.device: |
92 |
|
|
self.mapName = "luks-%s" % os.path.basename(self.device) |
93 |
|
|
|
94 |
|
|
def __str__(self): |
95 |
|
|
s = DeviceFormat.__str__(self) |
96 |
|
|
if self.__passphrase: |
97 |
|
|
passphrase = "(set)" |
98 |
|
|
else: |
99 |
|
|
passphrase = "(not set)" |
100 |
|
|
s += (" cipher = %(cipher)s keySize = %(keySize)s" |
101 |
|
|
" mapName = %(mapName)s\n" |
102 |
|
|
" keyFile = %(keyFile)s passphrase = %(passphrase)s\n" |
103 |
|
|
" escrowCert = %(escrowCert)s addBackup = %(backup)s" % |
104 |
|
|
{"cipher": self.cipher, "keySize": self.key_size, |
105 |
|
|
"mapName": self.mapName, "keyFile": self._key_file, |
106 |
|
|
"passphrase": passphrase, "escrowCert": self.escrow_cert, |
107 |
|
|
"backup": self.add_backup_passphrase}) |
108 |
|
|
return s |
109 |
|
|
|
110 |
|
|
def writeKS(self, f): |
111 |
|
|
f.write(" --encrypted") |
112 |
|
|
if self.cipher: |
113 |
|
|
f.write(" --cipher=\"%s\"" % self.cipher) |
114 |
|
|
|
115 |
|
|
@property |
116 |
|
|
def dict(self): |
117 |
|
|
d = super(LUKS, self).dict |
118 |
|
|
d.update({"cipher": self.cipher, "keySize": self.key_size, |
119 |
|
|
"mapName": self.mapName, "hasKey": self.hasKey, |
120 |
|
|
"escrowCert": self.escrow_cert, |
121 |
|
|
"backup": self.add_backup_passphrase}) |
122 |
|
|
return d |
123 |
|
|
|
124 |
|
|
@property |
125 |
|
|
def name(self): |
126 |
|
|
name = self._name |
127 |
|
|
# for existing locked devices, show "Encrypted" instead of LUKS |
128 |
|
|
if self.hasKey or not self.exists: |
129 |
|
|
name = self._name |
130 |
|
|
else: |
131 |
|
|
name = "%s (%s)" % (self._lockedName, self._name) |
132 |
|
|
return name |
133 |
|
|
|
134 |
|
|
def _setPassphrase(self, passphrase): |
135 |
|
|
""" Set the passphrase used to access this device. """ |
136 |
|
|
self.__passphrase = passphrase |
137 |
|
|
|
138 |
|
|
passphrase = property(fset=_setPassphrase) |
139 |
|
|
|
140 |
|
|
@property |
141 |
|
|
def hasKey(self): |
142 |
|
|
return (self.__passphrase or |
143 |
|
|
(self._key_file and os.access(self._key_file, os.R_OK))) |
144 |
|
|
|
145 |
|
|
@property |
146 |
|
|
def configured(self): |
147 |
|
|
""" To be ready we need a key or passphrase and a map name. """ |
148 |
|
|
return self.hasKey and self.mapName |
149 |
|
|
|
150 |
|
|
@property |
151 |
|
|
def status(self): |
152 |
|
|
if not self.exists or not self.mapName: |
153 |
|
|
return False |
154 |
|
|
return os.path.exists("/dev/mapper/%s" % self.mapName) |
155 |
|
|
|
156 |
|
|
def probe(self): |
157 |
|
|
""" Probe for any missing information about this format. |
158 |
|
|
|
159 |
|
|
cipher mode, key size |
160 |
|
|
""" |
161 |
|
|
raise NotImplementedError("probe method not defined for LUKS") |
162 |
|
|
|
163 |
|
|
def setup(self, *args, **kwargs): |
164 |
|
|
""" Open, or set up, the format. """ |
165 |
|
|
log_method_call(self, device=self.device, mapName=self.mapName, |
166 |
|
|
type=self.type, status=self.status) |
167 |
|
|
if not self.configured: |
168 |
|
|
raise LUKSError("luks device not configured") |
169 |
|
|
|
170 |
|
|
if self.status: |
171 |
|
|
return |
172 |
|
|
|
173 |
|
|
DeviceFormat.setup(self, *args, **kwargs) |
174 |
|
|
crypto.luks_open(self.device, self.mapName, |
175 |
|
|
passphrase=self.__passphrase, |
176 |
|
|
key_file=self._key_file) |
177 |
|
|
|
178 |
|
|
def teardown(self, *args, **kwargs): |
179 |
|
|
""" Close, or tear down, the format. """ |
180 |
|
|
log_method_call(self, device=self.device, |
181 |
|
|
type=self.type, status=self.status) |
182 |
|
|
if not self.exists: |
183 |
|
|
raise LUKSError("format has not been created") |
184 |
|
|
|
185 |
|
|
if self.status: |
186 |
|
|
log.debug("unmapping %s" % self.mapName) |
187 |
|
|
crypto.luks_close(self.mapName) |
188 |
|
|
|
189 |
|
|
def create(self, *args, **kwargs): |
190 |
|
|
""" Create the format. """ |
191 |
|
|
log_method_call(self, device=self.device, |
192 |
|
|
type=self.type, status=self.status) |
193 |
|
|
if not self.hasKey: |
194 |
|
|
raise LUKSError("luks device has no key/passphrase") |
195 |
|
|
|
196 |
|
|
intf = kwargs.get("intf") |
197 |
|
|
w = None |
198 |
|
|
if intf: |
199 |
|
|
w = intf.waitWindow(_("Formatting"), |
200 |
|
|
_("Encrypting %s") % kwargs.get("device", |
201 |
|
|
self.device)) |
202 |
|
|
|
203 |
|
|
try: |
204 |
|
|
DeviceFormat.create(self, *args, **kwargs) |
205 |
|
|
crypto.luks_format(self.device, |
206 |
|
|
passphrase=self.__passphrase, |
207 |
|
|
key_file=self._key_file, |
208 |
|
|
cipher=self.cipher, |
209 |
|
|
key_size=self.key_size) |
210 |
|
|
except Exception: |
211 |
|
|
raise |
212 |
|
|
else: |
213 |
|
|
self.uuid = crypto.luks_uuid(self.device) |
214 |
|
|
self.exists = True |
215 |
|
|
self.mapName = "luks-%s" % self.uuid |
216 |
|
|
self.notifyKernel() |
217 |
|
|
finally: |
218 |
|
|
if w: |
219 |
|
|
w.pop() |
220 |
|
|
|
221 |
|
|
def destroy(self, *args, **kwargs): |
222 |
|
|
""" Create the format. """ |
223 |
|
|
log_method_call(self, device=self.device, |
224 |
|
|
type=self.type, status=self.status) |
225 |
|
|
self.teardown() |
226 |
|
|
DeviceFormat.destroy(self, *args, **kwargs) |
227 |
|
|
|
228 |
|
|
@property |
229 |
|
|
def keyFile(self): |
230 |
|
|
""" Path to key file to be used in /etc/crypttab """ |
231 |
|
|
return self._key_file |
232 |
|
|
|
233 |
|
|
def addKeyFromFile(self, keyfile): |
234 |
|
|
""" Add a new key from a file. |
235 |
|
|
|
236 |
|
|
Add the contents of the specified key file to an available key |
237 |
|
|
slot in the LUKS header. |
238 |
|
|
""" |
239 |
|
|
log_method_call(self, device=self.device, |
240 |
|
|
type=self.type, status=self.status, file=keyfile) |
241 |
|
|
if not self.exists: |
242 |
|
|
raise LUKSError("format has not been created") |
243 |
|
|
|
244 |
|
|
crypto.luks_add_key(self.device, |
245 |
|
|
passphrase=self.__passphrase, |
246 |
|
|
key_file=self._key_file, |
247 |
|
|
new_key_file=keyfile) |
248 |
|
|
|
249 |
|
|
def addPassphrase(self, passphrase): |
250 |
|
|
""" Add a new passphrase. |
251 |
|
|
|
252 |
|
|
Add the specified passphrase to an available key slot in the |
253 |
|
|
LUKS header. |
254 |
|
|
""" |
255 |
|
|
log_method_call(self, device=self.device, |
256 |
|
|
type=self.type, status=self.status) |
257 |
|
|
if not self.exists: |
258 |
|
|
raise LUKSError("format has not been created") |
259 |
|
|
|
260 |
|
|
crypto.luks_add_key(self.device, |
261 |
|
|
passphrase=self.__passphrase, |
262 |
|
|
key_file=self._key_file, |
263 |
|
|
new_passphrase=passphrase) |
264 |
|
|
|
265 |
|
|
def removeKeyFromFile(self, keyfile): |
266 |
|
|
""" Remove a key contained in a file. |
267 |
|
|
|
268 |
|
|
Remove key contained in the specified key file from the LUKS |
269 |
|
|
header. |
270 |
|
|
""" |
271 |
|
|
log_method_call(self, device=self.device, |
272 |
|
|
type=self.type, status=self.status, file=keyfile) |
273 |
|
|
if not self.exists: |
274 |
|
|
raise LUKSError("format has not been created") |
275 |
|
|
|
276 |
|
|
crypto.luks_remove_key(self.device, |
277 |
|
|
passphrase=self.__passphrase, |
278 |
|
|
key_file=self._key_file, |
279 |
|
|
del_key_file=keyfile) |
280 |
|
|
|
281 |
|
|
|
282 |
|
|
def removePassphrase(self, passphrase): |
283 |
|
|
""" Remove the specified passphrase from the LUKS header. """ |
284 |
|
|
log_method_call(self, device=self.device, |
285 |
|
|
type=self.type, status=self.status) |
286 |
|
|
if not self.exists: |
287 |
|
|
raise LUKSError("format has not been created") |
288 |
|
|
|
289 |
|
|
crypto.luks_remove_key(self.device, |
290 |
|
|
passphrase=self.__passphrase, |
291 |
|
|
key_file=self._key_file, |
292 |
|
|
del_passphrase=passphrase) |
293 |
|
|
|
294 |
|
|
def _escrowVolumeIdent(self, vol): |
295 |
|
|
""" Return an escrow packet filename prefix for a volume_key.Volume. """ |
296 |
|
|
label = vol.label |
297 |
|
|
if label is not None: |
298 |
|
|
label = label.replace("/", "_") |
299 |
|
|
uuid = vol.uuid |
300 |
|
|
if uuid is not None: |
301 |
|
|
uuid = uuid.replace("/", "_") |
302 |
|
|
# uuid is never None on LUKS volumes |
303 |
|
|
if label is not None and uuid is not None: |
304 |
|
|
volume_ident = "%s-%s" % (label, uuid) |
305 |
|
|
elif uuid is not None: |
306 |
|
|
volume_ident = uuid |
307 |
|
|
elif label is not None: |
308 |
|
|
volume_ident = label |
309 |
|
|
else: |
310 |
|
|
volume_ident = "_unknown" |
311 |
|
|
return volume_ident |
312 |
|
|
|
313 |
|
|
def escrow(self, directory, backupPassphrase): |
314 |
|
|
log.debug("escrow: escrowVolume start for %s" % self.device) |
315 |
|
|
if volume_key is None: |
316 |
|
|
raise LUKSError("Missing key escrow support libraries") |
317 |
|
|
|
318 |
|
|
vol = volume_key.Volume.open(self.device) |
319 |
|
|
volume_ident = self._escrowVolumeIdent(vol) |
320 |
|
|
|
321 |
|
|
ui = volume_key.UI() |
322 |
|
|
# This callback is not expected to be used, let it always fail |
323 |
|
|
ui.generic_cb = lambda unused_prompt, unused_echo: None |
324 |
|
|
def known_passphrase_cb(unused_prompt, failed_attempts): |
325 |
|
|
if failed_attempts == 0: |
326 |
|
|
return self.__passphrase |
327 |
|
|
return None |
328 |
|
|
ui.passphrase_cb = known_passphrase_cb |
329 |
|
|
|
330 |
|
|
log.debug("escrow: getting secret") |
331 |
|
|
vol.get_secret(volume_key.SECRET_DEFAULT, ui) |
332 |
|
|
log.debug("escrow: creating packet") |
333 |
|
|
default_packet = vol.create_packet_assymetric_from_cert_data \ |
334 |
|
|
(volume_key.SECRET_DEFAULT, self.escrow_cert, ui) |
335 |
|
|
log.debug("escrow: packet created") |
336 |
|
|
with open("%s/%s-escrow" % (directory, volume_ident), "wb") as f: |
337 |
|
|
f.write(default_packet) |
338 |
|
|
log.debug("escrow: packet written") |
339 |
|
|
|
340 |
|
|
if self.add_backup_passphrase: |
341 |
|
|
log.debug("escrow: adding backup passphrase") |
342 |
|
|
vol.add_secret(volume_key.SECRET_PASSPHRASE, backupPassphrase) |
343 |
|
|
log.debug("escrow: creating backup packet") |
344 |
|
|
backup_passphrase_packet = \ |
345 |
|
|
vol.create_packet_assymetric_from_cert_data \ |
346 |
|
|
(volume_key.SECRET_PASSPHRASE, self.escrow_cert, ui) |
347 |
|
|
log.debug("escrow: backup packet created") |
348 |
|
|
with open("%s/%s-escrow-backup-passphrase" % |
349 |
|
|
(directory, volume_ident), "wb") as f: |
350 |
|
|
f.write(backup_passphrase_packet) |
351 |
|
|
log.debug("escrow: backup packet written") |
352 |
|
|
|
353 |
|
|
log.debug("escrow: escrowVolume done for %s" % repr(self.device)) |
354 |
|
|
|
355 |
|
|
|
356 |
|
|
register_device_format(LUKS) |
357 |
|
|
|