-
Notifications
You must be signed in to change notification settings - Fork 1
Encrypted Partitions
Partition minor 255, along with a few others, are encrypted by default
since IGEL OS v11.
A custom encrypted partition can also be configured by the system administrator.
Partition minor 255 contains the wfs (presumably "writable filesystem") partition.
/wfs contains various configuration files, such as group.ini and setup.ini
(partially XML-formatted, despite the file extension), which store the
user-configured registry data. These files may be gzip-compressed.
IGEL encrypted filesystems contain two extents - of type WRITEABLE and
LOGIN respectively - and are handled internally by various tools:
-
/usr/bin/mkigelefs,chkigelefsandrmigelefs- extent filesystem -
/etc/igel/crypt/*- filesystem and key tools -
/usr/bin/kml/*- key management
Encrypted filesystems, such as partition minor 255, contain two partition extents
of types WRITEABLE and LOGIN respectively.
Extents of type WRITEABLE contain models encrypted using the
XChacha20-Poly1305 (AEAD) cryptosystem,
with a key derived from the boot_id (see CryptoHelper.get_extent_key).
The key can also be found using the method described in LD_PRELOAD,
overriding crypto_aead_xchacha20poly1305_ietf_decrypt instead of add_key.
This will reveal the ciphertext, authenticated data, cryptographic nonce and key.
The authenticated data and nonce are stored in the header of the extent filesystem. The header is 48 bytes, with a data section of 1048528 bytes; the actual payload size is also specified in the header. The decrypted data is an LZF-compressed tar archive.
Install the additional dependencies and use igelfs.models.efs.ExtentFilesystem
to handle these extents, for example:
key = CryptoHelper.get_extent_key(boot_id) # Derive key from boot ID
models = ExtentFilesystem.from_bytes_to_collection(extent)
for model in models:
data = model.decrypt(key) # Decrypt payload with key
decompressed = ExtentFilesystem.decompress(data) # Decompress LZF data
ExtentFilesystem.extract(decompressed, path) # Extract tar archive to pathThe tar archive contains a JSON configuration file, called kmlconfig.json, which
stores the required information to open the encrypted volumes.
The required JSON sections are: system, slots and keys, and optionally tpm.
Once the writable extent has been decrypted and kmlconfig.json has been extracted,
it is possible to derive the master key for decrypting individual filesystem keys.
The master key is derived in the following way (see CryptoHelper.get_master_key):
- Argon2ID KDF with the following parameters:
-
size: 32 bytes -
password: first 20 bytes ofCryptoHelper.get_extent_key(boot_id)(base64 decoded, then re-encoded) -
salt: fromsystem.salt -
opslimitandmemlimit: dependent onsystem.level
-
-
slots[n].pub(32 bytes) is appended to result = 64 bytes - Result is hashed with SHA-512 (64 bytes)
- Digest is used as key to decrypt
slots[n].privwith AES-XTS, where the initialisation vector is the second half of the key ([32:])
This master key is then used to decrypt each key in the same way.
Use igelfs.kml.Keyring and KmlConfig to manage these keys in an abstract manner:
keyring = Keyring.from_filesystem(filesystem)
keyring.get_keys()
keyring.get_key(255)Once the keys have been obtained, the encrypted filesystem can be decrypted;
in older IGEL OS versions, it appears these are often LUKS containers, but in
later versions, often are encrypted in plain mode, with the cipher aes-xts-plain64
and a key size of 512 bits (halved in XTS mode, i.e. 2x AES-256).
Plain mode (aes-xts-plain64):
cryptsetup open \
--type=plain \
--cipher=aes-xts-plain64 \
--key-size=512 \
--key-file=<keyfile> \
<device> \
<name>
LUKS:
cryptsetup --master-key-file=<keyfile> open <device> <name>
The tools in /usr/bin/kml/ internally add keys to the kernel's key management
facility with add_key, which can be
viewed in /proc/keys and managed by keyctl.
These keys have type logon, meaning the keys are not readable from user space.
The following methods can be used to find these keys:
If these keys are required, it is possible to patch the binary
/usr/bin/kml/load_cred - which is responsible for the key-derivation logic - to
add these keys with type user instead, allowing them to be read.
This binary can be patched with a reverse engineering tool, such as
Ghidra, or with sed as below:
# Check string "logon" exists in binary
strings /usr/bin/kml/load_cred | grep logon
# Patch the binary
# If the binary changes significantly, this method may require modification
sed 's/logon/user\x00/g' /usr/bin/kml/load_cred > load_cred_patched
# Make the patched binary executable
chmod +x ./load_cred_patched
# Run the patched binary
./load_cred_patched -D
# Read the added keys, see also keyctl print or pipe
# Common keys: kml:255, kml:248 and kml:default
keyctl read $(keyctl request user kml:255)
# Write the output from keyctl (as bytes) into a keyfile
# Open the encrypted image with aes-xts-plain64 mode
cryptsetup open \
--type=plain \
--cipher=aes-xts-plain64 \
--key-size=512 \
--key-file=<keyfile> \
<image> \
<name>Alternatively, the syscall add_key can be intercepted, by writing a library
which overrides the add_key function, then loading it first using LD_PRELOAD:
// add_key.c
#include <stdio.h>
#include <string.h>
int add_key(const char *type, const char *description, const char *payload,
const int size, const int keyring)
{
printf("add_key() call intercepted\n");
printf("Type: %s\n", type);
printf("Description: %s\n", description);
printf("Payload: ");
for (int i = 0; i < strlen(payload); i++) {
printf("%02x ", (unsigned char)payload[i]);
}
printf("\nSize: %d\n", size);
printf("Keyring: %d\n\n", keyring);
return 0;
}Compile on host: gcc -Wall -fPIC -shared add_key.c -o add_key.so
Run on guest: LD_PRELOAD=/path/to/add_key.so /usr/bin/kml/load_cred -D
add_key will be loaded by the user-modified library add_key.so first,
which will cause any calls to add_key from load_cred to simply output the
passed arguments; the keys will not actually be added to the keyring, but
it will return 0 for success to the caller.