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 |
|