making valgrind happy
[accounts-sso:signon.git] / lib / signond / SignOn / crypto-manager.cpp
1 /* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of signon
4  *
5  * Copyright (C) 2009-2010 Nokia Corporation.
6  *
7  * Contact: Aurel Popirtac <ext-aurel.popirtac@nokia.com>
8  * Contact: Alberto Mardegan <alberto.mardegan@nokia.com>
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public License
12  * version 2.1 as published by the Free Software Foundation.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22  * 02110-1301 USA
23  */
24
25
26 #include "crypto-manager.h"
27 #include "crypto-handlers.h"
28 #include "debug.h"
29 #include "misc.h"
30
31 #include <QFile>
32 #include <QDir>
33 #include <QMetaEnum>
34 #include <QSettings>
35
36 #define DEVICE_MAPPER_DIR "/dev/mapper/"
37 #define EXT2 "ext2"
38 #define EXT3 "ext3"
39 #define EXT4 "ext4"
40
41 namespace SignOn {
42
43     const QString CryptoManager::keychainFilePath() const
44     {
45 #ifdef SIGNON_AEGISFS
46           return m_aegisFSFileSystemPath + QDir::separator() + QLatin1String("keychain");
47 #else
48           return m_fileSystemMountPath + QDir::separator() + QLatin1String("keychain");
49 #endif
50     }
51
52     void CryptoManager::addKeyToKeychain(const QByteArray &key) const
53     {
54         //If key is already present do not add it - backwards compat. feature
55         if (keychainContainsKey(key)) return;
56
57         QSettings keychain(keychainFilePath(), QSettings::IniFormat);
58         int keyCount = keychain.allKeys().count();
59         keychain.setValue(QString::number(++keyCount), QVariant(key));
60     }
61
62     void CryptoManager::removeKeyFromKeychain(const QByteArray &key) const
63     {
64         QSettings keychain(keychainFilePath(), QSettings::IniFormat);
65         foreach (QString keyIt, keychain.allKeys()) {
66             if (keychain.value(keyIt).toByteArray() == key) {
67                 keychain.remove(keyIt);
68                 break;
69             }
70         }
71     }
72
73     bool CryptoManager::keychainContainsKey(const QByteArray &key) const
74     {
75         QSettings keychain(keychainFilePath(), QSettings::IniFormat);
76         foreach (QString keyIt, keychain.allKeys()) {
77             if (keychain.value(keyIt).toByteArray() == key)
78                 return true;
79         }
80
81         return false;
82     }
83
84     CryptoManager::CryptoManager(QObject *parent)
85             : QObject(parent),
86               m_accessCode(QByteArray()),
87               m_fileSystemPath(QString()),
88               m_fileSystemMapPath(QString()),
89               m_fileSystemName(QString()),
90               m_fileSystemMountPath(QString()),
91               m_aegisFSFileSystemPath(QString()),
92               m_loopDeviceName(QString()),
93               m_mountState(Unmounted),
94               m_fileSystemType(Ext2),
95               m_fileSystemSize(4)
96     {
97         if (!CryptsetupHandler::loadDmMod())
98             BLAME() << "Could not load `dm_mod`!";
99     }
100
101     CryptoManager::CryptoManager(const QByteArray &encryptionKey,
102                                  const QString &fileSystemPath,
103                                  QObject *parent)
104             : QObject(parent),
105               m_accessCode(encryptionKey),
106               m_loopDeviceName(QString()),
107               m_mountState(Unmounted),
108               m_fileSystemType(Ext2),
109               m_fileSystemSize(4)
110     {
111         setFileSystemPath(fileSystemPath);
112
113         if (!CryptsetupHandler::loadDmMod())
114             BLAME() << "Could not load `dm_mod`!";
115     }
116
117     CryptoManager::~CryptoManager()
118     {
119         unmountFileSystem();
120     }
121
122     void CryptoManager::setFileSystemPath(const QString &path)
123     {
124         m_fileSystemPath = path;
125
126         QFileInfo fsFileInfo(path);
127
128         m_fileSystemName = fsFileInfo.fileName();
129         m_fileSystemMapPath = QLatin1String(DEVICE_MAPPER_DIR) + m_fileSystemName;
130         m_fileSystemMountPath = path + QLatin1String("-mnt");
131     }
132
133     void CryptoManager::setAegisFSFileSystemPath(const QString &path)
134     {
135         m_aegisFSFileSystemPath = path;
136     }
137
138     bool CryptoManager::setFileSystemSize(const quint32 size)
139     {
140         if (size < MINUMUM_ENCRYPTED_FILE_SYSTEM_SIZE) {
141             TRACE() << "Minumum encrypted file size is 4 Mb.";
142             return false;
143         }
144         m_fileSystemSize = size;
145         return true;
146     }
147
148     void CryptoManager::setEncryptionKey(const QByteArray &key)
149     {
150         if (fileSystemIsMounted()) {
151             BLAME() << "File system already mounted";
152             return;
153         }
154
155         m_accessCode = key;
156     }
157
158     bool CryptoManager::setFileSystemType(const QString &type)
159     {
160         QString cmpBase = type.toLower();
161         if (cmpBase == QLatin1String(EXT2)) {
162             m_fileSystemType = Ext2;
163             return true;
164         } else if (cmpBase == QLatin1String(EXT3)) {
165             m_fileSystemType = Ext3;
166             return true;
167         } else if (cmpBase == QLatin1String(EXT4)) {
168             m_fileSystemType = Ext4;
169             return true;
170         }
171         return false;
172     }
173
174     bool CryptoManager::setupFileSystem()
175     {
176         if (m_mountState == Mounted) {
177             TRACE() << "Ecrypyted file system already mounted.";
178             return false;
179         }
180
181         if (m_accessCode.isEmpty()) {
182             TRACE() << "No access code set. Stopping mount process.";
183             return false;
184         }
185
186         if (!CryptsetupHandler::loadDmMod()) {
187             BLAME() << "Could not load `dm_mod`!";
188             return false;
189         }
190
191         clearFileSystemResources();
192
193         m_loopDeviceName = LosetupHandler::findAvailableDevice();
194         if (m_loopDeviceName.isNull()) {
195             BLAME() << "No free loop device available!";
196             return false;
197         }
198
199         if (!PartitionHandler::createPartitionFile(m_fileSystemPath, m_fileSystemSize)) {
200             BLAME() << "Could not create partition file.";
201             unmountFileSystem();
202             return false;
203         }
204
205         if (!LosetupHandler::setupDevice(m_loopDeviceName,
206                                          m_fileSystemPath)) {
207             BLAME() << "Failed to setup loop device:" << m_loopDeviceName;
208             unmountFileSystem();
209             return false;
210         }
211         updateMountState(LoopSet);
212
213         if (!CryptsetupHandler::formatFile(m_accessCode, m_loopDeviceName)) {
214             BLAME() << "Failed to LUKS format.";
215             unmountFileSystem();
216             return false;
217         }
218         updateMountState(LoopLuksFormatted);
219
220         //attempt luks close, in case of a leftover.
221         if (QFile::exists(QLatin1String(DEVICE_MAPPER_DIR) + m_fileSystemName)) {
222             TRACE() << "Filesystem exists, closing";
223             CryptsetupHandler::closeFile(m_fileSystemName);
224         }
225
226         if (!CryptsetupHandler::openFile(m_accessCode,
227                                          m_loopDeviceName,
228                                          m_fileSystemName)) {
229             BLAME() << "Failed to LUKS open";
230             unmountFileSystem();
231             return false;
232         }
233         updateMountState(LoopLuksOpened);
234
235         if (!PartitionHandler::formatPartitionFile(m_fileSystemMapPath,
236                                                    m_fileSystemType)) {
237             BLAME() << "Could not format mapped partition.";
238             unmountFileSystem();
239             return false;
240         }
241
242         if (!mountMappedDevice()) {
243             BLAME() << "Failed to mount ecrypted file system.";
244             unmountFileSystem();
245             return false;
246         }
247
248         addKeyToKeychain(m_accessCode);
249         updateMountState(Mounted);
250         return true;
251     }
252
253     //TODO - add checking for LUKS header in case of preavious app run formatting failure
254     bool CryptoManager::mountFileSystem()
255     {
256         if (m_mountState == Mounted) {
257             TRACE() << "Ecrypyted file system already mounted.";
258             return false;
259         }
260
261         if (m_accessCode.isEmpty()) {
262             TRACE() << "No access code set. Stopping mount process.";
263             return false;
264         }
265
266         clearFileSystemResources();
267
268         if (!CryptsetupHandler::loadDmMod()) {
269             BLAME() << "Could not load `dm_mod`!";
270             return false;
271         }
272
273         m_loopDeviceName = LosetupHandler::findAvailableDevice();
274         if (m_loopDeviceName.isNull()) {
275             BLAME() << "No free loop device available!";
276             return false;
277         }
278
279         if (!LosetupHandler::setupDevice(m_loopDeviceName, m_fileSystemPath)) {
280             BLAME() << "Failed to setup loop device:" << m_loopDeviceName;
281             unmountFileSystem();
282             return false;
283         }
284         updateMountState(LoopSet);
285
286         //attempt luks close, in case of a leftover.
287         if (QFile::exists(QLatin1String(DEVICE_MAPPER_DIR) + m_fileSystemName))
288             CryptsetupHandler::closeFile(m_fileSystemName);
289
290         if (!CryptsetupHandler::openFile(m_accessCode,
291                                          m_loopDeviceName,
292                                          m_fileSystemName)) {
293             BLAME() << "Failed to LUKS open.";
294             unmountFileSystem();
295             return false;
296         }
297         updateMountState(LoopLuksOpened);
298
299         if (!mountMappedDevice()) {
300             TRACE() << "Failed to mount ecrypted file system.";
301             unmountFileSystem();
302             return false;
303         }
304
305         addKeyToKeychain(m_accessCode);
306         updateMountState(Mounted);
307         return true;
308     }
309
310     void CryptoManager::clearFileSystemResources()
311     {
312         /*
313             This method is a `just in case call` for the situations
314             when signond closes whithout handling the unmounting of
315             the secure storage.
316         */
317
318         TRACE() << "--- START clearing secure storage possibly used resources."
319                    " Ignore possible errors. ---";
320
321         if (!unmountMappedDevice())
322             TRACE() << "Unmounting mapped device failed.";
323
324         if (QFile::exists(QLatin1String(DEVICE_MAPPER_DIR) + m_fileSystemName)) {
325             if (!CryptsetupHandler::closeFile(m_fileSystemName))
326                 TRACE() << "Failed to LUKS close.";
327         }
328
329         /*
330          TODO - find a way to check which loop device was previously used by
331                 signond and close that specific one, until then this will be
332                 skipped as it might close devices used by different processes.
333
334         if (!LosetupHandler::releaseDevice(m_loopDeviceName)) {
335             TRACE() << "Failed to release loop device.";
336         */
337
338         TRACE() << "--- DONE clearing secure storage possibly used resources. ---";
339     }
340
341     bool CryptoManager::unmountFileSystem()
342     {
343         if (m_mountState == Unmounted) {
344             TRACE() << "Ecrypyted file system not mounted.";
345             return true;
346         }
347
348         emit fileSystemUnmounting();
349         bool isOk = true;
350
351         if ((m_mountState >= Mounted)
352             && !unmountMappedDevice()) {
353             TRACE() << "Failed to unmount mapped loop device.";
354             isOk = false;
355         } else {
356             TRACE() << "Mapped loop device unmounted.";
357         }
358
359         if ((m_mountState >= LoopLuksOpened)
360             && (!CryptsetupHandler::closeFile(m_fileSystemName))) {
361             TRACE() << "Failed to LUKS close.";
362             isOk = false;
363         } else {
364             TRACE() << "Luks close succeeded.";
365         }
366
367         if ((m_mountState >= LoopSet)
368             && (!LosetupHandler::releaseDevice(m_loopDeviceName))) {
369             TRACE() << "Failed to release loop device.";
370             isOk = false;
371         } else {
372             TRACE() << "Loop device released.";
373         }
374
375         updateMountState(Unmounted);
376         return isOk;
377     }
378
379     bool CryptoManager::deleteFileSystem()
380     {
381         if (m_mountState > Unmounted) {
382             if (!unmountFileSystem())
383                 return false;
384         }
385
386         //TODO - implement effective deletion in specific handler object
387         return false;
388     }
389
390     bool CryptoManager::fileSystemIsSetup() const
391     {
392         return QFile::exists(fileSystemPath());
393     }
394
395     //TODO - debug this method. Current test scenarios do no cover this one
396     bool CryptoManager::fileSystemContainsFile(const QString &filePath)
397     {
398         if (!fileSystemIsMounted()) {
399             TRACE() << "Ecrypyted file system not mounted.";
400             return false;
401         }
402
403         QDir mountDir(m_fileSystemMountPath);
404         return mountDir.exists(QDir::toNativeSeparators(filePath));
405     }
406
407     QString CryptoManager::fileSystemMountPath() const
408     {
409         return m_fileSystemMountPath;
410     }
411
412     void CryptoManager::updateMountState(const FileSystemMountState state)
413     {
414         TRACE() << "Updating mount state:" << state;
415         if (state == m_mountState) return;
416
417         m_mountState = state;
418         if (state == Mounted)
419             emit fileSystemMounted();
420     }
421
422     bool CryptoManager::mountMappedDevice()
423     {
424         //create mnt dir if not existant
425         if (!QFile::exists(m_fileSystemMountPath)) {
426             QDir dir;
427             if (!dir.mkpath(m_fileSystemMountPath)) {
428                 BLAME() << "Could not create target mount dir path.";
429                 return false;
430             }
431
432             if (!setUserOwnership(m_fileSystemMountPath))
433                 TRACE() << "Failed to set User ownership for "
434                            "the secure storage mount target.";
435         }
436
437         MountHandler::mount(m_fileSystemMapPath, m_fileSystemMountPath);
438         return true;
439     }
440
441     bool CryptoManager::unmountMappedDevice()
442     {
443         return MountHandler::umount(m_fileSystemMountPath);
444     }
445
446     bool CryptoManager::addEncryptionKey(const QByteArray &key,
447                                          const QByteArray &existingKey)
448     {
449         /*
450          * TODO -- limit number of stored keys to the total available slots - 1.
451          */
452         if (m_mountState >= LoopLuksOpened) {
453             if (CryptsetupHandler::addKeySlot(
454                     m_loopDeviceName, key, existingKey)) {
455
456                 addKeyToKeychain(key);
457                 return true;
458             }
459         }
460         TRACE() << "FAILED to occupy key slot on the encrypted file system header.";
461         return false;
462     }
463
464     bool CryptoManager::removeEncryptionKey(const QByteArray &key,
465                                             const QByteArray &remainingKey)
466     {
467         if (m_mountState >= LoopLuksOpened) {
468             if (CryptsetupHandler::removeKeySlot(
469                 m_loopDeviceName, key, remainingKey))
470
471                 removeKeyFromKeychain(key);
472                 return true;
473         }
474         TRACE() << "FAILED to release key slot from the encrypted file system header.";
475         return false;
476     }
477
478     bool CryptoManager::encryptionKeyInUse(const QByteArray &key)
479     {
480         if (fileSystemIsMounted() && (m_accessCode == key))
481             return true;
482
483         if(!fileSystemIsMounted()) {
484            setEncryptionKey(key);
485            return mountFileSystem();
486         }
487
488         /* Variant that tests if the key is in the LUKS keychain
489          * by using a file on the encrypted storage containing the keychain.
490          */
491         return keychainContainsKey(key);
492
493         /*
494          * Variant that tests if the key is in the LUKS keychain
495          * by directly accessing the LUKS system
496          *
497          * QByteArray dummyKey("dummy");
498          * if (addEncryptionKey(dummyKey, key)) {
499          *     if (!removeEncryptionKey(dummyKey, key))
500          *         BLAME() << "Could not remove dummy auxiliary key "
501          *                    "from encrypted file system header.";
502          *    return true;
503          * }
504          *
505          * return false;
506          */
507     }
508
509     //TODO - remove this after stable version is achieved.
510     void CryptoManager::serializeData()
511     {
512         TRACE() << "m_accessCode" << m_accessCode;
513         TRACE() << "m_fileSystemPath" << m_fileSystemPath;
514         TRACE() << "m_fileSystemMapPath" << m_fileSystemMapPath;
515         TRACE() << "m_fileSystemName" << m_fileSystemName;
516         TRACE() << "m_loopDeviceName" << m_loopDeviceName;
517         TRACE() << "m_fileSystemType" << m_fileSystemType;
518         TRACE() << "m_fileSystemSize" << m_fileSystemSize;
519     }
520 } //namespace SignonDaemonNS