[partition] Add support for LUKS2

This commit adds support for LUKS2 behind a new `partition.conf` key:
`luksGeneration`.

A bit of context, LUKS2 is the default encryption operating mode since
cryptsetup >= 2.1.0 (See [Arch
wiki](https://wiki.archlinux.org/title/dm-crypt/Device_encryption#Encryption_options_with_dm-crypt).
It is considered more secured and allows additional extensions. It also
comes with Argon2id as the default Password Based Key Derivation
Function (`--pbkdf` option). So it's important to provide this as an
option for Calamares in order to make Linux installs more secure, for
those who wish to encrypt their system.

This commit was tested on a custom Manjaro installer with:

- grub bootloader with the [argon patches](https://aur.archlinux.org/packages/grub-improved-luks2-git).
- [rEFInd](https://wiki.archlinux.org/title/REFInd) bootloader with
  unencrypted `/boot` partition because rEFInd [doesn't support booting
  from an encrypted volume](https://sourceforge.net/p/refind/discussion/general/thread/400418ac/)

**Important consideration for distribution maintainers**:

- You need to have compile flag `WITH_KPMCORE4API` on
- If you are shipping with grub by default please note that you need to
  ship it with the Argon patches. Example on Arch Linux: [grub-improved-luks2-git](https://aur.archlinux.org/packages/grub-improved-luks2-git)
- If `luksGeneration` is not found in partition.conf, it will default to
  luks1
- Please test this on your own distribution as this was only tested on
  Manjaro installer (see above).
This commit is contained in:
Jeremy Attali 2022-05-25 19:27:07 +00:00
parent ddf65a2437
commit 9def0cb66f
20 changed files with 158 additions and 24 deletions

View File

@ -46,6 +46,7 @@ prettyNameForFileSystemType( FileSystem::Type t )
case FileSystem::Ufs:
case FileSystem::Hpfs:
case FileSystem::Luks:
case FileSystem::Luks2:
case FileSystem::Ocfs2:
case FileSystem::Zfs:
case FileSystem::Nilfs2:

View File

@ -46,6 +46,32 @@ Config::swapChoiceNames()
return names;
}
const NamedEnumTable< Config::LuksGeneration >&
Config::luksGenerationNames()
{
static const NamedEnumTable< LuksGeneration > names { { QStringLiteral( "luks1" ), LuksGeneration::Luks1 },
{ QStringLiteral( "luks2" ), LuksGeneration::Luks2 } };
return names;
}
const QString
Config::luksGenerationToFSName( Config::LuksGeneration luksGeneration )
{
// Convert luksGenerationChoice from partition.conf into its
// corresponding file system type from KPMCore.
switch ( luksGeneration )
{
case Config::LuksGeneration::Luks2:
return QStringLiteral( "luks2" );
case Config::LuksGeneration::Luks1:
return QStringLiteral( "luks" );
default:
cWarning() << "luksGeneration not supported, defaulting to \"luks\"";
return QStringLiteral( "luks" );
}
}
Config::SwapChoice
pickOne( const Config::SwapChoiceSet& s )
{
@ -323,13 +349,24 @@ Config::fillConfigurationFSTypes( const QVariantMap& configurationMap )
}
}
// Set LUKS file system based on luksGeneration provided, defaults to 'luks'.
bool nameFound = false;
Config::LuksGeneration luksGeneration
= luksGenerationNames().find( CalamaresUtils::getString( configurationMap, "luksGeneration" ), nameFound );
if ( !nameFound )
{
cWarning() << "Partition-module setting *luksGeneration* not found or invalid. Defaulting to luks1.";
luksGeneration = Config::LuksGeneration::Luks1;
}
m_luksFileSystemType = Config::luksGenerationToFSName( luksGeneration );
gs->insert( "luksFileSystemType", m_luksFileSystemType );
Q_ASSERT( !m_eraseFsTypes.isEmpty() );
Q_ASSERT( m_eraseFsTypes.contains( fsRealName ) );
m_eraseFsTypeChoice = fsRealName;
Q_EMIT eraseModeFilesystemChanged( m_eraseFsTypeChoice );
}
void
Config::setConfigurationMap( const QVariantMap& configurationMap )
{

View File

@ -63,6 +63,16 @@ public:
using EraseFsTypesSet = QStringList;
/** @brief Choice of LUKS disk encryption generation */
enum LuksGeneration
{
Luks1, // First generation of LUKS
Luks2, // Second generation of LUKS, default since cryptsetup >= 2.1.0
};
Q_ENUM( LuksGeneration )
static const NamedEnumTable< LuksGeneration >& luksGenerationNames();
static const QString luksGenerationToFSName( LuksGeneration choice );
void setConfigurationMap( const QVariantMap& );
/** @brief Set GS values where other modules configuration has priority
*
@ -112,6 +122,15 @@ public:
*/
SwapChoice swapChoice() const { return m_swapChoice; }
/** @brief The conversion of the luksGeneration into its FS type.
*
* Will convert Luks1 into "luks" and Luks2 into "luks2" for KPMCore
* partitionning functions.
*
* @return The LUKS FS type (default @c "luks" )
*/
QString luksFileSystemType() const { return m_luksFileSystemType; }
/** @brief Get the list of configured FS types to use with *erase* mode
*
* This list is not empty.
@ -162,6 +181,7 @@ private:
SwapChoiceSet m_swapChoices;
SwapChoice m_initialSwapChoice = NoSwap;
SwapChoice m_swapChoice = NoSwap;
QString m_luksFileSystemType;
InstallChoice m_initialInstallChoice = NoChoice;
InstallChoice m_installChoice = NoChoice;
qreal m_requiredStorageGiB = 0.0; // May duplicate setting in the welcome module

View File

@ -616,7 +616,9 @@ PartitionViewStep::onLeave()
// If the root partition is encrypted, and there's a separate boot
// partition which is not encrypted
if ( root_p->fileSystem().type() == FileSystem::Luks && boot_p->fileSystem().type() != FileSystem::Luks )
if ( ( root_p->fileSystem().type() == FileSystem::Luks && boot_p->fileSystem().type() != FileSystem::Luks )
|| ( root_p->fileSystem().type() == FileSystem::Luks2
&& boot_p->fileSystem().type() != FileSystem::Luks2 ) )
{
message = tr( "Boot partition not encrypted" );
description = tr( "A separate boot partition was set up together with "

View File

@ -98,7 +98,7 @@ colorForPartition( Partition* partition )
if ( partition->fileSystem().supportGetUUID() != FileSystem::cmdSupportNone
&& !partition->fileSystem().uuid().isEmpty() )
{
if ( partition->fileSystem().type() == FileSystem::Luks )
if ( partition->fileSystem().type() == FileSystem::Luks || partition->fileSystem().type() == FileSystem::Luks2 )
{
FS::luks& luksFs = dynamic_cast< FS::luks& >( partition->fileSystem() );
if ( !luksFs.outerUuid().isEmpty() && s_partitionColorsCache.contains( luksFs.outerUuid() ) )
@ -146,7 +146,7 @@ colorForPartition( Partition* partition )
if ( partition->fileSystem().supportGetUUID() != FileSystem::cmdSupportNone
&& !partition->fileSystem().uuid().isEmpty() )
{
if ( partition->fileSystem().type() == FileSystem::Luks )
if ( partition->fileSystem().type() == FileSystem::Luks || partition->fileSystem().type() == FileSystem::Luks2 )
{
FS::luks& luksFs = dynamic_cast< FS::luks& >( partition->fileSystem() );
if ( !luksFs.outerUuid().isEmpty() )

View File

@ -84,6 +84,7 @@ createNewEncryptedPartition( PartitionNode* parent,
const QString& fsLabel,
qint64 firstSector,
qint64 lastSector,
const QString& luksFsType, // "luks" or "luks2"
const QString& passphrase,
PartitionTable::Flags flags )
{
@ -93,8 +94,10 @@ createNewEncryptedPartition( PartitionNode* parent,
newRoles |= PartitionRole::Luks;
}
FileSystem::Type luksType = FileSystem::typeForName( luksFsType );
FS::luks* fs = dynamic_cast< FS::luks* >(
FileSystemFactory::create( FileSystem::Luks, firstSector, lastSector, device.logicalSize() ) );
FileSystemFactory::create( luksType, firstSector, lastSector, device.logicalSize() ) );
if ( !fs )
{
cError() << "cannot create LUKS filesystem. Giving up.";

View File

@ -83,6 +83,7 @@ Partition* createNewEncryptedPartition( PartitionNode* parent,
const QString& fsLabel,
qint64 firstSector,
qint64 lastSector,
const QString& luksFsType,
const QString& passphrase,
PartitionTable::Flags flags );

View File

@ -169,7 +169,7 @@ doAutopartition( PartitionCoreModule* core, Device* dev, Choices::AutoPartitionO
lastSectorForRoot -= suggestedSwapSizeB / sectorSize + 1;
}
core->layoutApply( dev, firstFreeSector, lastSectorForRoot, o.luksPassphrase );
core->layoutApply( dev, firstFreeSector, lastSectorForRoot, o.luksFsType, o.luksPassphrase );
if ( shouldCreateSwap )
{
@ -194,6 +194,7 @@ doAutopartition( PartitionCoreModule* core, Device* dev, Choices::AutoPartitionO
QStringLiteral( "swap" ),
lastSectorForRoot + 1,
dev->totalLogical() - 1,
o.luksFsType,
o.luksPassphrase,
KPM_PARTITION_FLAG( None ) );
}
@ -244,7 +245,7 @@ doReplacePartition( PartitionCoreModule* core, Device* dev, Partition* partition
core->deletePartition( dev, partition );
}
core->layoutApply( dev, firstSector, lastSector, o.luksPassphrase );
core->layoutApply( dev, firstSector, lastSector, o.luksFsType, o.luksPassphrase );
core->dumpQueue();
}

View File

@ -31,12 +31,17 @@ struct ReplacePartitionOptions
{
QString defaultPartitionTableType; // e.g. "gpt" or "msdos"
QString defaultFsType; // e.g. "ext4" or "btrfs"
QString luksFsType; // optional ("luks", "luks2")
QString luksPassphrase; // optional
ReplacePartitionOptions( const QString& pt, const QString& fs, const QString& luks )
ReplacePartitionOptions( const QString& pt,
const QString& fs,
const QString& luksFs,
const QString& luksPassphrase )
: defaultPartitionTableType( pt )
, defaultFsType( fs )
, luksPassphrase( luks )
, luksFsType( luksFs )
, luksPassphrase( luksPassphrase )
{
}
};
@ -49,11 +54,12 @@ struct AutoPartitionOptions : ReplacePartitionOptions
AutoPartitionOptions( const QString& pt,
const QString& fs,
const QString& luks,
const QString& luksFs,
const QString& luksPassphrase,
const QString& efi,
qint64 requiredBytes,
Config::SwapChoice s )
: ReplacePartitionOptions( pt, fs, luks )
: ReplacePartitionOptions( pt, fs, luksFs, luksPassphrase )
, efiPartitionMountPoint( efi )
, requiredSpaceB( requiredBytes > 0 ? quint64( requiredBytes ) : 0U )
, swap( s )

View File

@ -952,13 +952,14 @@ void
PartitionCoreModule::layoutApply( Device* dev,
qint64 firstSector,
qint64 lastSector,
QString luksFsType,
QString luksPassphrase,
PartitionNode* parent,
const PartitionRole& role )
{
bool isEfi = PartUtils::isEfiSystem();
QList< Partition* > partList
= m_partLayout.createPartitions( dev, firstSector, lastSector, luksPassphrase, parent, role );
= m_partLayout.createPartitions( dev, firstSector, lastSector, luksFsType, luksPassphrase, parent, role );
// Partition::mountPoint() tells us where it is mounted **now**, while
// PartitionInfo::mountPoint() says where it will be mounted in the target system.
@ -999,10 +1000,19 @@ PartitionCoreModule::layoutApply( Device* dev,
}
void
PartitionCoreModule::layoutApply( Device* dev, qint64 firstSector, qint64 lastSector, QString luksPassphrase )
PartitionCoreModule::layoutApply( Device* dev,
qint64 firstSector,
qint64 lastSector,
QString luksFsType,
QString luksPassphrase )
{
layoutApply(
dev, firstSector, lastSector, luksPassphrase, dev->partitionTable(), PartitionRole( PartitionRole::Primary ) );
layoutApply( dev,
firstSector,
lastSector,
luksFsType,
luksPassphrase,
dev->partitionTable(),
PartitionRole( PartitionRole::Primary ) );
}
void

View File

@ -166,10 +166,11 @@ public:
*/
PartitionLayout& partitionLayout() { return m_partLayout; }
void layoutApply( Device* dev, qint64 firstSector, qint64 lastSector, QString luksPassphrase );
void layoutApply( Device* dev, qint64 firstSector, qint64 lastSector, QString luksFsType, QString luksPassphrase );
void layoutApply( Device* dev,
qint64 firstSector,
qint64 lastSector,
QString luksFsType,
QString luksPassphrase,
PartitionNode* parent,
const PartitionRole& role );

View File

@ -204,6 +204,7 @@ QList< Partition* >
PartitionLayout::createPartitions( Device* dev,
qint64 firstSector,
qint64 lastSector,
QString luksFsType,
QString luksPassphrase,
PartitionNode* parent,
const PartitionRole& role )
@ -317,6 +318,7 @@ PartitionLayout::createPartitions( Device* dev,
entry.partLabel,
currentSector,
currentSector + sectors - 1,
luksFsType,
luksPassphrase,
KPM_PARTITION_FLAG( None ) );
}

View File

@ -116,6 +116,7 @@ public:
QList< Partition* > createPartitions( Device* dev,
qint64 firstSector,
qint64 lastSector,
QString luksFsType,
QString luksPassphrase,
PartitionNode* parent,
const PartitionRole& role );

View File

@ -496,6 +496,7 @@ ChoicePage::applyActionChoice( InstallChoice choice )
auto gs = Calamares::JobQueue::instance()->globalStorage();
PartitionActions::Choices::AutoPartitionOptions options { gs->value( "defaultPartitionTableType" ).toString(),
m_config->eraseFsType(),
gs->value( "luksFileSystemType" ).toString(),
m_encryptWidget->passphrase(),
gs->value( "efiSystemPartition" ).toString(),
CalamaresUtils::GiBtoBytes(
@ -752,6 +753,7 @@ ChoicePage::doAlongsideApply()
m_core->layoutApply( dev,
newLastSector + 2,
oldLastSector,
m_config->luksFileSystemType(),
m_encryptWidget->passphrase(),
candidate->parent(),
candidate->roles() );
@ -826,6 +828,7 @@ ChoicePage::doReplaceSelectedPartition( const QModelIndex& current )
m_core->layoutApply( selectedDevice(),
selectedPartition->firstSector(),
selectedPartition->lastSector(),
m_config->luksFileSystemType(),
m_encryptWidget->passphrase(),
newParent,
newRoles );
@ -858,6 +861,7 @@ ChoicePage::doReplaceSelectedPartition( const QModelIndex& current )
selectedPartition,
{ gs->value( "defaultPartitionType" ).toString(),
gs->value( "defaultFileSystemType" ).toString(),
gs->value( "luksFileSystemType" ).toString(),
m_encryptWidget->passphrase() } );
Partition* homePartition = findPartitionByPath( { selectedDevice() }, *homePartitionPath );

View File

@ -33,6 +33,7 @@
#include <kpmcore/fs/filesystem.h>
#include <kpmcore/fs/filesystemfactory.h>
#include <kpmcore/fs/luks.h>
#include <kpmcore/fs/luks2.h>
#include <QComboBox>
#include <QDir>
@ -223,6 +224,8 @@ CreatePartitionDialog::initGptPartitionTypeUi()
Partition*
CreatePartitionDialog::getNewlyCreatedPartition()
{
Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
if ( m_role.roles() == PartitionRole::None )
{
m_role = PartitionRole( m_ui->extendedRadioButton->isChecked() ? PartitionRole::Extended
@ -242,12 +245,21 @@ CreatePartitionDialog::getNewlyCreatedPartition()
// newFlags() and the consumer (see PartitionPage::onCreateClicked)
// does so, to set up the partition for create-and-then-set-flags.
Partition* partition = nullptr;
QString luksFsType = gs->value( "luksFileSystemType" ).toString();
QString luksPassphrase = m_ui->encryptWidget->passphrase();
if ( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty()
&& fsType != FileSystem::Zfs )
{
partition = KPMHelpers::createNewEncryptedPartition(
m_parent, *m_device, m_role, fsType, fsLabel, first, last, luksPassphrase, PartitionTable::Flags() );
partition = KPMHelpers::createNewEncryptedPartition( m_parent,
*m_device,
m_role,
fsType,
fsLabel,
first,
last,
luksFsType,
luksPassphrase,
PartitionTable::Flags() );
}
else
{
@ -308,6 +320,12 @@ CreatePartitionDialog::updateMountPointUi()
m_ui->encryptWidget->show();
m_ui->encryptWidget->reset();
}
else if ( FileSystemFactory::map()[ FileSystem::Type::Luks2 ]->supportCreate()
&& FS::luks2::canEncryptType( type ) && !m_role.has( PartitionRole::Extended ) )
{
m_ui->encryptWidget->show();
m_ui->encryptWidget->reset();
}
else
{
m_ui->encryptWidget->reset();

View File

@ -90,6 +90,7 @@ ReplaceWidget::applyChanges()
partition,
{ gs->value( "defaultPartitionTableType" ).toString(),
gs->value( "defaultFileSystemType" ).toString(),
gs->value( "luksFileSystemType" ).toString(),
QString() } );
if ( m_isEfi )

View File

@ -26,6 +26,9 @@
#include <kpmcore/core/partition.h>
#include <kpmcore/fs/filesystem.h>
#include <kpmcore/fs/luks.h>
#ifdef WITH_KPMCORE42API
#include <kpmcore/fs/luks2.h>
#endif
#include <QDebug>
#include <QDir>
@ -96,6 +99,13 @@ mapForPartition( Partition* partition, const QString& uuid )
{
map[ "fs" ] = untranslatedFS( dynamic_cast< FS::luks& >( partition->fileSystem() ).innerFS() );
}
#ifdef WITH_KPMCORE42API
if ( partition->fileSystem().type() == FileSystem::Luks2
&& dynamic_cast< FS::luks2& >( partition->fileSystem() ).innerFS() )
{
map[ "fs" ] = untranslatedFS( dynamic_cast< FS::luks2& >( partition->fileSystem() ).innerFS() );
}
#endif
map[ "uuid" ] = uuid;
map[ "claimed" ] = PartitionInfo::format( partition ); // If we formatted it, it's ours

View File

@ -64,6 +64,20 @@ userSwapChoices:
# ensureSuspendToDisk: true
# neverCreateSwap: false
# This setting specifies the LUKS generation (i.e LUKS1, LUKS2) used internally by
# cryptsetup when creating an encrypted partition.
#
# This option is set to luks1 by default, as grub doesn't support LUKS2 + Argon2id
# currently. On the other hand grub does support LUKS2 with PBKDF2 and could therefore be
# also set to luks2. Also there are some patches for grub and Argon2.
# See: https://aur.archlinux.org/packages/grub-improved-luks2-git
#
# Choices: luks1, luks2
#
# The default is luks1
#
luksGeneration: luks1
# Correctly draw nested (e.g. logical) partitions as such.
drawNestedPartitions: false

View File

@ -20,7 +20,9 @@ properties:
defaultFileSystemType: { type: string }
availableFileSystemTypes: { type: array, items: { type: string } }
luksGeneration: { type: string, enum: [luks1, luks2] }
enableLuksAutomatedPartitioning: { type: boolean, default: false }
allowManualPartitioning: { type: boolean, default: true }
partitionLayout: { type: array } # TODO: specify items
initialPartitioningChoice: { type: string, enum: [ none, erase, replace, alongside, manual ] }

View File

@ -63,8 +63,8 @@ CreateLayoutsTests::testFixedSizePartition()
QFAIL( qPrintable( "Unable to create / partition" ) );
}
partitions
= layout.createPartitions( static_cast< Device* >( &dev ), 0, dev.totalLogical(), nullptr, nullptr, role );
partitions = layout.createPartitions(
static_cast< Device* >( &dev ), 0, dev.totalLogical(), nullptr, nullptr, nullptr, role );
QCOMPARE( partitions.count(), 1 );
@ -84,8 +84,8 @@ CreateLayoutsTests::testPercentSizePartition()
QFAIL( qPrintable( "Unable to create / partition" ) );
}
partitions
= layout.createPartitions( static_cast< Device* >( &dev ), 0, dev.totalLogical(), nullptr, nullptr, role );
partitions = layout.createPartitions(
static_cast< Device* >( &dev ), 0, dev.totalLogical(), nullptr, nullptr, nullptr, role );
QCOMPARE( partitions.count(), 1 );
@ -115,8 +115,8 @@ CreateLayoutsTests::testMixedSizePartition()
QFAIL( qPrintable( "Unable to create /bkup partition" ) );
}
partitions
= layout.createPartitions( static_cast< Device* >( &dev ), 0, dev.totalLogical(), nullptr, nullptr, role );
partitions = layout.createPartitions(
static_cast< Device* >( &dev ), 0, dev.totalLogical(), nullptr, nullptr, nullptr, role );
QCOMPARE( partitions.count(), 3 );