1 /* === This file is part of Calamares - <https://calamares.io> ===
2 *
3 * SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 *
6 */
7
8 #include "LuksBootKeyFileJob.h"
9
10 #include "utils/CalamaresUtilsSystem.h"
11 #include "utils/Entropy.h"
12 #include "utils/Logger.h"
13 #include "utils/NamedEnum.h"
14 #include "utils/UMask.h"
15 #include "utils/Variant.h"
16
17 #include "GlobalStorage.h"
18 #include "JobQueue.h"
19
LuksBootKeyFileJob(QObject * parent)20 LuksBootKeyFileJob::LuksBootKeyFileJob( QObject* parent )
21 : Calamares::CppJob( parent )
22 {
23 }
24
~LuksBootKeyFileJob()25 LuksBootKeyFileJob::~LuksBootKeyFileJob() {}
26
27 QString
prettyName() const28 LuksBootKeyFileJob::prettyName() const
29 {
30 return tr( "Configuring LUKS key file." );
31 }
32
33 struct LuksDevice
34 {
LuksDeviceLuksDevice35 LuksDevice( const QMap< QString, QVariant >& pinfo )
36 : isValid( false )
37 , isRoot( false )
38 {
39 if ( pinfo.contains( "luksMapperName" ) )
40 {
41 QString fs = pinfo[ "fs" ].toString();
42 QString mountPoint = pinfo[ "mountPoint" ].toString();
43
44 if ( !mountPoint.isEmpty() || fs == QStringLiteral( "linuxswap" ) )
45 {
46 isValid = true;
47 isRoot = mountPoint == '/';
48 device = pinfo[ "device" ].toString();
49 passphrase = pinfo[ "luksPassphrase" ].toString();
50 }
51 }
52 }
53
54 bool isValid;
55 bool isRoot;
56 QString device;
57 QString passphrase;
58 };
59
60 /** @brief Extract the luks passphrases setup.
61 *
62 * Given a list of partitions (as set up by the partitioning module,
63 * so there's maps with keys inside), returns just the list of
64 * luks passphrases for each device.
65 */
66 static QList< LuksDevice >
getLuksDevices(const QVariantList & list)67 getLuksDevices( const QVariantList& list )
68 {
69 QList< LuksDevice > luksItems;
70
71 for ( const auto& p : list )
72 {
73 if ( p.canConvert< QVariantMap >() )
74 {
75 LuksDevice d( p.toMap() );
76 if ( d.isValid )
77 {
78 luksItems.append( d );
79 }
80 }
81 }
82 return luksItems;
83 }
84
85 struct LuksDeviceList
86 {
LuksDeviceListLuksDeviceList87 LuksDeviceList( const QVariant& partitions )
88 : valid( false )
89 {
90 if ( partitions.canConvert< QVariantList >() )
91 {
92 devices = getLuksDevices( partitions.toList() );
93 valid = true;
94 }
95 }
96
97 QList< LuksDevice > devices;
98 bool valid;
99 };
100
101 static const char keyfile[] = "/crypto_keyfile.bin";
102
103 static bool
generateTargetKeyfile()104 generateTargetKeyfile()
105 {
106 CalamaresUtils::UMask m( CalamaresUtils::UMask::Safe );
107
108 // Get the data
109 QByteArray entropy;
110 auto entropySource = CalamaresUtils::getEntropy( 2048, entropy );
111 if ( entropySource != CalamaresUtils::EntropySource::URandom )
112 {
113 cWarning() << "Could not get entropy from /dev/urandom for LUKS.";
114 return false;
115 }
116
117 auto fileResult = CalamaresUtils::System::instance()->createTargetFile(
118 keyfile, entropy, CalamaresUtils::System::WriteMode::Overwrite );
119 entropy.fill( 'A' );
120 if ( !fileResult )
121 {
122 cWarning() << "Could not create LUKS keyfile:" << smash( fileResult.code() );
123 return false;
124 }
125
126 // Give ample time to check that the file was created correctly;
127 // we actually expect ls to return pretty-much-instantly.
128 auto r = CalamaresUtils::System::instance()->targetEnvCommand(
129 { "ls", "-la", "/" }, QString(), QString(), std::chrono::seconds( 5 ) );
130 cDebug() << "In target system after creating LUKS file" << r.getOutput();
131 return true;
132 }
133
134 static bool
setupLuks(const LuksDevice & d)135 setupLuks( const LuksDevice& d )
136 {
137 // Adding the key can take some times, measured around 15 seconds with
138 // a HDD (spinning rust) and a slow-ish computer. Give it a minute.
139 auto r = CalamaresUtils::System::instance()->targetEnvCommand(
140 { "cryptsetup", "luksAddKey", d.device, keyfile }, QString(), d.passphrase, std::chrono::seconds( 60 ) );
141 if ( r.getExitCode() != 0 )
142 {
143 cWarning() << "Could not configure LUKS keyfile on" << d.device << ':' << r.getOutput() << "(exit code"
144 << r.getExitCode() << ')';
145 return false;
146 }
147 return true;
148 }
149
150 static QVariantList
partitions()151 partitions()
152 {
153 Calamares::GlobalStorage* globalStorage = Calamares::JobQueue::instance()->globalStorage();
154 return globalStorage->value( QStringLiteral( "partitions" ) ).toList();
155 }
156
157 static bool
hasUnencryptedSeparateBoot()158 hasUnencryptedSeparateBoot()
159 {
160 const QVariantList partitions = ::partitions();
161 for ( const QVariant& partition : partitions )
162 {
163 QVariantMap partitionMap = partition.toMap();
164 QString mountPoint = partitionMap.value( QStringLiteral( "mountPoint" ) ).toString();
165 if ( mountPoint == QStringLiteral( "/boot" ) )
166 {
167 return !partitionMap.contains( QStringLiteral( "luksMapperName" ) );
168 }
169 }
170 return false;
171 }
172
173 Calamares::JobResult
exec()174 LuksBootKeyFileJob::exec()
175 {
176 const auto* gs = Calamares::JobQueue::instance()->globalStorage();
177 if ( !gs )
178 {
179 return Calamares::JobResult::internalError(
180 "LuksBootKeyFile", "No GlobalStorage defined.", Calamares::JobResult::InvalidConfiguration );
181 }
182 if ( !gs->contains( "partitions" ) )
183 {
184 cError() << "No GS[partitions] key.";
185 return Calamares::JobResult::internalError(
186 "LuksBootKeyFile", tr( "No partitions are defined." ), Calamares::JobResult::InvalidConfiguration );
187 }
188
189 LuksDeviceList s( gs->value( "partitions" ) );
190 if ( !s.valid )
191 {
192 cError() << "GS[partitions] is invalid";
193 return Calamares::JobResult::internalError(
194 "LuksBootKeyFile", tr( "No partitions are defined." ), Calamares::JobResult::InvalidConfiguration );
195 }
196
197 cDebug() << "There are" << s.devices.count() << "LUKS partitions";
198 if ( s.devices.count() < 1 )
199 {
200 cDebug() << Logger::SubEntry << "Nothing to do for LUKS.";
201 return Calamares::JobResult::ok();
202 }
203
204 auto it = std::partition( s.devices.begin(), s.devices.end(), []( const LuksDevice& d ) { return d.isRoot; } );
205 for ( const auto& d : s.devices )
206 {
207 cDebug() << Logger::SubEntry << ( d.isRoot ? "root" : "dev." ) << d.device << "passphrase?"
208 << !d.passphrase.isEmpty();
209 }
210
211 if ( it == s.devices.begin() )
212 {
213 // Then there was no root partition
214 cDebug() << Logger::SubEntry << "No root partition.";
215 return Calamares::JobResult::ok();
216 }
217
218 // /boot partition is not encrypted, keyfile must not be used
219 if ( hasUnencryptedSeparateBoot() )
220 {
221 cDebug() << Logger::SubEntry << "/boot partition is not encrypted, skipping keyfile creation.";
222 return Calamares::JobResult::ok();
223 }
224
225 if ( s.devices.first().passphrase.isEmpty() )
226 {
227 cDebug() << Logger::SubEntry << "No root passphrase.";
228 return Calamares::JobResult::error(
229 tr( "Encrypted rootfs setup error" ),
230 tr( "Root partition %1 is LUKS but no passphrase has been set." ).arg( s.devices.first().device ) );
231 }
232
233 if ( !generateTargetKeyfile() )
234 {
235 return Calamares::JobResult::error(
236 tr( "Encrypted rootfs setup error" ),
237 tr( "Could not create LUKS key file for root partition %1." ).arg( s.devices.first().device ) );
238 }
239
240 for ( const auto& d : s.devices )
241 {
242 if ( !setupLuks( d ) )
243 return Calamares::JobResult::error(
244 tr( "Encrypted rootfs setup error" ),
245 tr( "Could not configure LUKS key file on partition %1." ).arg( d.device ) );
246 }
247
248 return Calamares::JobResult::ok();
249 }
250
251 CALAMARES_PLUGIN_FACTORY_DEFINITION( LuksBootKeyFileJobFactory, registerPlugin< LuksBootKeyFileJob >(); )
252