From fff5a43469382489c09ad96cab00c48f6ed6df16 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 2 Jul 2019 21:50:43 +0200 Subject: [PATCH 1/8] Changes: document luksbootkey issue --- CHANGES | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGES b/CHANGES index 5085ad3c7..723747e44 100644 --- a/CHANGES +++ b/CHANGES @@ -6,11 +6,31 @@ website will have to do for older versions. # 3.2.11 (unreleased) # This release contains contributions from (alphabetically by first name): + - No other contributors this time around. + +This is a security release with no functional changes (except for +improved security) relative to 3.2.10. The Calamares team would like +to acknowledge the help of the following people in reporting and +understanding the issues (alphabetically by first name): + - Kevin Kofler + - Seth Arnold + - Simon Quigley + - Thomas Ward + ## Core ## +No core changes. + ## Modules ## + - *initramfs* could create an initramfs with insecure permissions. + Since the keyfile is included in the initramfs, an attacker could + read the file from the initramfs. #1190 + - *luksbootkeyfile* created a key file where a window of opportunity + existed where the key file could have too-lax file permissions. + #1191 CVE-2019-13179 + # 3.2.10 (2019-06-28) # From c2fa31573566fd668a3a9646f7bf0bb27cccecb6 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 2 Jul 2019 22:52:22 +0200 Subject: [PATCH 2/8] [luksbootkeyfile] Prepare for C++-ification - Mess around with the CMakeFile in preparation of new code - Drop the Python implementation already --- src/modules/luksbootkeyfile/CMakeLists.txt | 9 ++ src/modules/luksbootkeyfile/main.py | 103 --------------------- src/modules/luksbootkeyfile/module.desc | 5 - 3 files changed, 9 insertions(+), 108 deletions(-) create mode 100644 src/modules/luksbootkeyfile/CMakeLists.txt delete mode 100644 src/modules/luksbootkeyfile/main.py delete mode 100644 src/modules/luksbootkeyfile/module.desc diff --git a/src/modules/luksbootkeyfile/CMakeLists.txt b/src/modules/luksbootkeyfile/CMakeLists.txt new file mode 100644 index 000000000..be0f0fa28 --- /dev/null +++ b/src/modules/luksbootkeyfile/CMakeLists.txt @@ -0,0 +1,9 @@ +calamares_add_plugin( luksbootkeyfile + TYPE job + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + LuksBootKeyFileJob.cpp + LINK_PRIVATE_LIBRARIES + calamares + SHARED_LIB +) diff --git a/src/modules/luksbootkeyfile/main.py b/src/modules/luksbootkeyfile/main.py deleted file mode 100644 index fb0146cf8..000000000 --- a/src/modules/luksbootkeyfile/main.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# === This file is part of Calamares - === -# -# Copyright 2016, Teo Mrnjavac -# Copyright 2017, Alf Gaida -# Copyright 2017, 2019, Adriaan de Groot -# -# Calamares is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Calamares is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Calamares. If not, see . - -import libcalamares -from libcalamares.utils import check_target_env_call - - -import gettext -_ = gettext.translation("calamares-python", - localedir=libcalamares.utils.gettext_path(), - languages=libcalamares.utils.gettext_languages(), - fallback=True).gettext - - -def pretty_name(): - return _("Configuring LUKS key file.") - - -def run(): - """ - This module sets up a file crypto_keyfile.bin on the rootfs, assuming the - rootfs is LUKS encrypted and a passphrase is provided. This file is then - included in the initramfs and used for unlocking the rootfs from a - previously unlocked GRUB2 session. - :return: - """ - - partitions = libcalamares.globalstorage.value("partitions") - - if not partitions: - libcalamares.utils.warning("partitions is empty, {!s}".format(partitions)) - return (_("Configuration Error"), - _("No partitions are defined for
{!s}
to use." ).format("luksbootkey")) - - luks_root_device = "" - luks_root_passphrase = "" - - additional_luks_devices = [] - - for partition in partitions: - if partition["mountPoint"] == "/" and "luksMapperName" in partition: - luks_root_device = partition["device"] - luks_root_passphrase = partition["luksPassphrase"] - elif "luksMapperName" in partition and\ - (partition["mountPoint"] or partition["fs"] == "linuxswap"): - additional_luks_devices.append((partition["device"], - partition["luksPassphrase"])) - - if not luks_root_device: - return None - - if not luks_root_passphrase: - libcalamares.utils.debug("No LUKS passphrase, root {!s}".format(luks_root_device)) - return ( - _("Encrypted rootfs setup error"), - _("Rootfs partition {!s} is LUKS but no passphrase found.").format(luks_root_device)) - - # Generate random keyfile - check_target_env_call(["dd", - "bs=512", - "count=4", - "if=/dev/urandom", - "of=/crypto_keyfile.bin"]) - - check_target_env_call(["cryptsetup", - "luksAddKey", - luks_root_device, - "/crypto_keyfile.bin"], - luks_root_passphrase, - 15) # timeout 15s - - for additional_device in additional_luks_devices: - check_target_env_call(["cryptsetup", - "luksAddKey", - additional_device[0], - "/crypto_keyfile.bin"], - additional_device[1], - 15) # timeout 15s - - check_target_env_call(["chmod", - "g-rwx,o-rwx", - "/crypto_keyfile.bin"]) - - return None diff --git a/src/modules/luksbootkeyfile/module.desc b/src/modules/luksbootkeyfile/module.desc deleted file mode 100644 index 11a0173d5..000000000 --- a/src/modules/luksbootkeyfile/module.desc +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: "job" -name: "luksbootkeyfile" -interface: "python" -script: "main.py" From 676df86712c0726bff113341487ed8dfed4ae2f5 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 3 Jul 2019 00:01:19 +0200 Subject: [PATCH 3/8] [luksbootkeyfile] Stub job implementation in C++ --- .../luksbootkeyfile/LuksBootKeyFileJob.cpp | 80 +++++++++++++++++++ .../luksbootkeyfile/LuksBootKeyFileJob.h | 48 +++++++++++ 2 files changed, 128 insertions(+) create mode 100644 src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp create mode 100644 src/modules/luksbootkeyfile/LuksBootKeyFileJob.h diff --git a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp new file mode 100644 index 000000000..fb659cfab --- /dev/null +++ b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp @@ -0,0 +1,80 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "LuksBootKeyFileJob.h" + +#include "utils/CalamaresUtilsSystem.h" +#include "utils/Logger.h" +#include "utils/Variant.h" + +#include "GlobalStorage.h" +#include "JobQueue.h" + +LuksBootKeyFileJob::LuksBootKeyFileJob( QObject* parent ) + : Calamares::CppJob( parent ) +{ +} + +LuksBootKeyFileJob::~LuksBootKeyFileJob() {} + +QString +LuksBootKeyFileJob::prettyName() const +{ + return tr( "Configuring LUKS key file." ); +} + +struct LuksPassphrase +{ + QString device; + QString passphrase; +}; + +struct GlobalSettings +{ + GlobalSettings( const QVariant& partitions ) + : valid( false ) + { + cDebug() << partitions; + } + + QList< LuksPassphrase > filesystems; + bool valid; +}; + +Calamares::JobResult +LuksBootKeyFileJob::exec() +{ + const auto* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( !gs ) + { + return Calamares::JobResult::internalError( + "LukeBootKeyFile", "No GlobalStorage defined.", Calamares::JobResult::InvalidConfiguration ); + } + if ( !gs->contains( "partitions" ) ) + { + return Calamares::JobResult::internalError( + "LukeBootKeyFile", + tr( "No partitions are defined for
%1
to use." ).arg( "luksbootkeyfile" ), + Calamares::JobResult::InvalidConfiguration ); + } + + GlobalSettings s( gs->value( "partitions" ) ); + return Calamares::JobResult::ok(); +} + +CALAMARES_PLUGIN_FACTORY_DEFINITION( LuksBootKeyFileJobFactory, registerPlugin< LuksBootKeyFileJob >(); ) diff --git a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.h b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.h new file mode 100644 index 000000000..2d4d6d319 --- /dev/null +++ b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.h @@ -0,0 +1,48 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef LUKSBOOTKEYFILEJOB_H +#define LUKSBOOTKEYFILEJOB_H + +#include "CppJob.h" +#include "PluginDllMacro.h" +#include "utils/PluginFactory.h" + +#include +#include + +/** @brief Creates the LUKS boot key file and adds it to the cryptsetup. + * + * This job has no configuration, because it takes everything + * from the global storage settings set by others. + */ +class PLUGINDLLEXPORT LuksBootKeyFileJob : public Calamares::CppJob +{ + Q_OBJECT +public: + explicit LuksBootKeyFileJob( QObject* parent = nullptr ); + virtual ~LuksBootKeyFileJob() override; + + QString prettyName() const override; + + Calamares::JobResult exec() override; +}; + +CALAMARES_PLUGIN_FACTORY_DECLARATION( LuksBootKeyFileJobFactory ) + +#endif // LUKSBOOTKEYFILEJOB_H From 745b4b169322724dca15d408f0ce391940064520 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 4 Jul 2019 13:17:34 +0200 Subject: [PATCH 4/8] [luksbootkeyfile] Extract partitioning keyphrases from GS --- .../luksbootkeyfile/LuksBootKeyFileJob.cpp | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp index fb659cfab..c05cb970d 100644 --- a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp +++ b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp @@ -49,7 +49,37 @@ struct GlobalSettings GlobalSettings( const QVariant& partitions ) : valid( false ) { - cDebug() << partitions; + if ( partitions.canConvert() ) + { + filesystems = getPartitionInfo( partitions.toList() ); + valid = true; + } + } + + /** @brief Extract the luks passphrases setup. + * + * Given a list of partitions (as set up by the partitioning module, + * so there's maps with keys inside), returns just the list of + * luks passphrases for each device. + */ + static QList< LuksPassphrase > getPartitionInfo( const QVariantList& list ) + { + int count = 0; + for( const auto& p : list ) + { + if ( p.canConvert< QVariantMap>() ) + { + auto pinfo = p.toMap(); + QString device = pinfo["device"].toString(); + QString fs = pinfo["fs"].toString(); + QString mountPoint = pinfo["mountPoint"].toString(); + QString uuid = pinfo["uuid"].toString(); + + cDebug() << count << "D=" << device << mountPoint << '(' << fs << ')'; + } + count++; + } + return QList< LuksPassphrase >(); } QList< LuksPassphrase > filesystems; @@ -69,7 +99,7 @@ LuksBootKeyFileJob::exec() { return Calamares::JobResult::internalError( "LukeBootKeyFile", - tr( "No partitions are defined for
%1
to use." ).arg( "luksbootkeyfile" ), + tr( "No partitions are defined for LUKS to use." ).arg( "luksbootkeyfile" ), Calamares::JobResult::InvalidConfiguration ); } From f6c50564cd5f2314ee3b6ef449519bd193910b1b Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 4 Jul 2019 13:45:02 +0200 Subject: [PATCH 5/8] [luksbootkeyfile] Extract devices that need cryptsetup - Rename classes and functions to be more descriptive (a LuksDevice is .. information for a LUKS device, for instance). - Move the smarts of unpacking a QVariantMap to LuksDevice. - Apply code formatting --- .../luksbootkeyfile/LuksBootKeyFileJob.cpp | 66 +++++++++++++------ 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp index c05cb970d..6589c5330 100644 --- a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp +++ b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp @@ -38,20 +38,41 @@ LuksBootKeyFileJob::prettyName() const return tr( "Configuring LUKS key file." ); } -struct LuksPassphrase +struct LuksDevice { + LuksDevice( const QMap< QString, QVariant >& pinfo ) + : isValid( false ) + , isRoot( false ) + { + if ( pinfo.contains( "luksMapperName" ) ) + { + QString fs = pinfo[ "fs" ].toString(); + QString mountPoint = pinfo[ "mountPoint" ].toString(); + + if ( !mountPoint.isEmpty() || fs == QStringLiteral( "linuxswap" ) ) + { + isValid = true; + isRoot = mountPoint == '/'; + device = pinfo[ "device" ].toString(); + passphrase = pinfo[ "luksPassphrase" ].toString(); + } + } + } + + bool isValid; + bool isRoot; QString device; QString passphrase; }; -struct GlobalSettings +struct LuksDeviceList { - GlobalSettings( const QVariant& partitions ) + LuksDeviceList( const QVariant& partitions ) : valid( false ) { - if ( partitions.canConvert() ) + if ( partitions.canConvert< QVariantList >() ) { - filesystems = getPartitionInfo( partitions.toList() ); + devices = getLuksDevices( partitions.toList() ); valid = true; } } @@ -62,27 +83,26 @@ struct GlobalSettings * so there's maps with keys inside), returns just the list of * luks passphrases for each device. */ - static QList< LuksPassphrase > getPartitionInfo( const QVariantList& list ) + static QList< LuksDevice > + getLuksDevices( const QVariantList& list ) { - int count = 0; - for( const auto& p : list ) + QList< LuksDevice > luksItems; + + for ( const auto& p : list ) { - if ( p.canConvert< QVariantMap>() ) + if ( p.canConvert< QVariantMap >() ) { - auto pinfo = p.toMap(); - QString device = pinfo["device"].toString(); - QString fs = pinfo["fs"].toString(); - QString mountPoint = pinfo["mountPoint"].toString(); - QString uuid = pinfo["uuid"].toString(); - - cDebug() << count << "D=" << device << mountPoint << '(' << fs << ')'; + LuksDevice d( p.toMap() ); + if ( d.isValid ) + { + luksItems.append( d ); + } } - count++; } - return QList< LuksPassphrase >(); + return luksItems; } - QList< LuksPassphrase > filesystems; + QList< LuksDevice > devices; bool valid; }; @@ -103,7 +123,13 @@ LuksBootKeyFileJob::exec() Calamares::JobResult::InvalidConfiguration ); } - GlobalSettings s( gs->value( "partitions" ) ); + LuksDeviceList s( gs->value( "partitions" ) ); + cDebug() << "There are" << s.devices.count() << "LUKS partitions"; + for ( const auto& p : s.devices ) + { + cDebug() << Logger::SubEntry << p.isRoot << p.device << p.passphrase; + } + return Calamares::JobResult::ok(); } From 8706b579ec6db3c9d5fe67486c62c4f1dc364cfe Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 4 Jul 2019 15:14:06 +0200 Subject: [PATCH 6/8] [luksbootkeyfile] Stub out an implementation - stubs for the actual work to be done - program-flow for looping over all the work --- .../luksbootkeyfile/LuksBootKeyFileJob.cpp | 65 +++++++++++++++++-- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp index 6589c5330..c9c1cc8a1 100644 --- a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp +++ b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp @@ -106,6 +106,18 @@ struct LuksDeviceList bool valid; }; +static bool +generateTargetKeyfile() +{ + return false; +} + +static bool +setupLuks( const LuksDevice& d ) +{ + return false; +} + Calamares::JobResult LuksBootKeyFileJob::exec() { @@ -117,17 +129,60 @@ LuksBootKeyFileJob::exec() } if ( !gs->contains( "partitions" ) ) { + cError() << "No GS[partitions] key."; return Calamares::JobResult::internalError( - "LukeBootKeyFile", - tr( "No partitions are defined for LUKS to use." ).arg( "luksbootkeyfile" ), - Calamares::JobResult::InvalidConfiguration ); + "LukeBootKeyFile", tr( "No partitions are defined." ), Calamares::JobResult::InvalidConfiguration ); } LuksDeviceList s( gs->value( "partitions" ) ); + if ( !s.valid ) + { + cError() << "GS[partitions] is invalid"; + return Calamares::JobResult::internalError( + "LukeBootKeyFile", tr( "No partitions are defined." ), Calamares::JobResult::InvalidConfiguration ); + } + cDebug() << "There are" << s.devices.count() << "LUKS partitions"; - for ( const auto& p : s.devices ) + if ( s.devices.count() < 1 ) + { + cDebug() << Logger::SubEntry << "Nothing to do for LUKS."; + return Calamares::JobResult::ok(); + } + + auto it = std::partition( s.devices.begin(), s.devices.end(), []( const LuksDevice& d ) { return d.isRoot; } ); + for ( const auto& d : s.devices ) + { + cDebug() << Logger::SubEntry << d.isRoot << d.device << d.passphrase; + } + + if ( it == s.devices.begin() ) + { + // Then there was no root partition + cDebug() << Logger::SubEntry << "No root partition."; + return Calamares::JobResult::ok(); + } + + if ( s.devices.first().passphrase.isEmpty() ) + { + cDebug() << Logger::SubEntry << "No root passphrase."; + return Calamares::JobResult::error( + tr( "Encrypted rootfs setup error" ), + tr( "Root partition %1 is LUKS but no passphrase has been set." ).arg( s.devices.first().device ) ); + } + + if ( !generateTargetKeyfile() ) + { + return Calamares::JobResult::error( + tr( "Encrypted rootfs setup error" ), + tr( "Could not create LUKS key file for root partition %1." ).arg( s.devices.first().device ) ); + } + + for ( const auto& d : s.devices ) { - cDebug() << Logger::SubEntry << p.isRoot << p.device << p.passphrase; + if ( !setupLuks( d ) ) + return Calamares::JobResult::error( + tr( "Encrypted rootfs setup error" ), + tr( "Could configure LUKS key file on partition %1." ).arg( d.device ) ); } return Calamares::JobResult::ok(); From 856a2eaa1d766b32f2d3e76a8a41873a91d4890a Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 4 Jul 2019 15:47:37 +0200 Subject: [PATCH 7/8] [luksbootkeyfile] Implement keyfile creation and use - Just copy the commands from the existing Python code, including nonsensical dd. --- .../luksbootkeyfile/LuksBootKeyFileJob.cpp | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp index c9c1cc8a1..b1a5fffdb 100644 --- a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp +++ b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp @@ -106,16 +106,33 @@ struct LuksDeviceList bool valid; }; +static const char keyfile[] = "/crypto_keyfile.bin"; + static bool generateTargetKeyfile() { - return false; + auto r = CalamaresUtils::System::instance()->targetEnvCommand( + { "dd", "bs=512", "count=4", "if=/dev/urandom", QString( "of=%1" ).arg( keyfile ) } ); + if ( r.getExitCode() != 0 ) + { + cWarning() << "Could not create LUKS keyfile:" << r.getOutput() << "(exit code" << r.getExitCode() << ')'; + return false; + } + return true; } static bool setupLuks( const LuksDevice& d ) { - return false; + auto r = CalamaresUtils::System::instance()->targetEnvCommand( + { "cryptsetup", "luksAddKey", d.device, keyfile }, QString(), d.passphrase, 15 ); + if ( r.getExitCode() != 0 ) + { + cWarning() << "Could not configure LUKS keyfile on" << d.device << ':' << r.getOutput() << "(exit code" + << r.getExitCode() << ')'; + return false; + } + return true; } Calamares::JobResult From af2a900276ee1d8b1fe84df96a6cd9005bf505ae Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 4 Jul 2019 15:55:54 +0200 Subject: [PATCH 8/8] [luksbootkeyfile] Fix lax permissions on file. --- src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp index b1a5fffdb..9b49b91cb 100644 --- a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp +++ b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp @@ -20,6 +20,7 @@ #include "utils/CalamaresUtilsSystem.h" #include "utils/Logger.h" +#include "utils/UMask.h" #include "utils/Variant.h" #include "GlobalStorage.h" @@ -111,6 +112,7 @@ static const char keyfile[] = "/crypto_keyfile.bin"; static bool generateTargetKeyfile() { + CalamaresUtils::UMask m( CalamaresUtils::UMask::Safe ); auto r = CalamaresUtils::System::instance()->targetEnvCommand( { "dd", "bs=512", "count=4", "if=/dev/urandom", QString( "of=%1" ).arg( keyfile ) } ); if ( r.getExitCode() != 0 )