diff options
Diffstat (limited to 'lib/geom/eli')
-rw-r--r-- | lib/geom/eli/Makefile | 14 | ||||
-rw-r--r-- | lib/geom/eli/Makefile.depend | 18 | ||||
-rw-r--r-- | lib/geom/eli/geli.8 | 1289 | ||||
-rw-r--r-- | lib/geom/eli/geom_eli.c | 2014 |
4 files changed, 3335 insertions, 0 deletions
diff --git a/lib/geom/eli/Makefile b/lib/geom/eli/Makefile new file mode 100644 index 000000000000..a22eacb9d7e8 --- /dev/null +++ b/lib/geom/eli/Makefile @@ -0,0 +1,14 @@ +PACKAGE=geom +.PATH: ${SRCTOP}/sys/geom/eli + +GEOM_CLASS= eli +SRCS= g_eli_crypto.c +SRCS+= g_eli_hmac.c +SRCS+= g_eli_key.c +SRCS+= pkcs5v2.c + +LIBADD= md crypto + +CFLAGS+=-I${SRCTOP}/sys + +.include <bsd.lib.mk> diff --git a/lib/geom/eli/Makefile.depend b/lib/geom/eli/Makefile.depend new file mode 100644 index 000000000000..8e5ef128814d --- /dev/null +++ b/lib/geom/eli/Makefile.depend @@ -0,0 +1,18 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libgeom \ + lib/libmd \ + secure/lib/libcrypto \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/geom/eli/geli.8 b/lib/geom/eli/geli.8 new file mode 100644 index 000000000000..876caf67ab40 --- /dev/null +++ b/lib/geom/eli/geli.8 @@ -0,0 +1,1289 @@ +.\" Copyright (c) 2005-2019 Pawel Jakub Dawidek <pawel@dawidek.net> +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd April 24, 2024 +.Dt GELI 8 +.Os +.Sh NAME +.Nm geli +.Nd "control utility for the cryptographic GEOM class" +.Sh SYNOPSIS +To compile GEOM_ELI into your kernel, add the following lines to your kernel +configuration file: +.Bd -ragged -offset indent +.Cd "device crypto" +.Cd "options GEOM_ELI" +.Ed +.Pp +Alternatively, to load the GEOM_ELI module at boot time, add the following line +to your +.Xr loader.conf 5 : +.Bd -literal -offset indent +geom_eli_load="YES" +.Ed +.Pp +.No Usage of the Nm +utility: +.Pp +.Nm +.Cm init +.Op Fl bdgPRTv +.Op Fl a Ar aalgo +.Op Fl B Ar backupfile +.Op Fl e Ar ealgo +.Op Fl i Ar iterations +.Op Fl J Ar newpassfile +.Op Fl K Ar newkeyfile +.Op Fl l Ar keylen +.Op Fl s Ar sectorsize +.Op Fl V Ar version +.Ar prov ... +.Nm +.Cm label - an alias for +.Cm init +.Nm +.Cm attach +.Op Fl Cdprv +.Op Fl n Ar keyno +.Op Fl j Ar passfile +.Op Fl k Ar keyfile +.Ar prov ... +.Nm +.Cm detach +.Op Fl fl +.Ar prov ... +.Nm +.Cm stop - an alias for +.Cm detach +.Nm +.Cm onetime +.Op Fl dRT +.Op Fl a Ar aalgo +.Op Fl e Ar ealgo +.Op Fl l Ar keylen +.Op Fl s Ar sectorsize +.Ar prov +.Nm +.Cm configure +.Op Fl bBdDgGrRtT +.Ar prov ... +.Nm +.Cm setkey +.Op Fl pPv +.Op Fl i Ar iterations +.Op Fl j Ar passfile +.Op Fl J Ar newpassfile +.Op Fl k Ar keyfile +.Op Fl K Ar newkeyfile +.Op Fl n Ar keyno +.Ar prov +.Nm +.Cm delkey +.Op Fl afv +.Op Fl n Ar keyno +.Ar prov +.Nm +.Cm kill +.Op Fl av +.Op Ar prov ... +.Nm +.Cm backup +.Op Fl v +.Ar prov +.Ar file +.Nm +.Cm restore +.Op Fl fv +.Ar file +.Ar prov +.Nm +.Cm suspend +.Op Fl v +.Fl a | Ar prov ... +.Nm +.Cm resume +.Op Fl pv +.Op Fl j Ar passfile +.Op Fl k Ar keyfile +.Ar prov +.Nm +.Cm resize +.Op Fl v +.Fl s Ar oldsize +.Ar prov +.Nm +.Cm version +.Op Ar prov ... +.Nm +.Cm clear +.Op Fl v +.Ar prov ... +.Nm +.Cm dump +.Op Fl v +.Ar prov ... +.Nm +.Cm list +.Nm +.Cm status +.Nm +.Cm load +.Nm +.Cm unload +.Sh DESCRIPTION +The +.Nm +utility is used to configure encryption on GEOM providers. +.Pp +The following is a list of the most important features: +.Pp +.Bl -bullet -offset indent -compact +.It +Utilizes the +.Xr crypto 9 +framework, so when there is crypto hardware available, +.Nm +will make use of it automatically. +.It +Supports many cryptographic algorithms (currently +.Nm AES-XTS , +.Nm AES-CBC , +and +.Nm Camellia-CBC ) . +.It +Can optionally perform data authentication (integrity verification) utilizing +one of the following algorithms: +.Nm HMAC/SHA1 , +.Nm HMAC/RIPEMD160 , +.Nm HMAC/SHA256 , +.Nm HMAC/SHA384 +or +.Nm HMAC/SHA512 . +.It +Can create a User Key from up to two, piecewise components: a passphrase +entered via prompt or read from one or more passfiles; a keyfile read from +one or more files. +.It +Allows encryption of the root partition. +The user is asked for the passphrase before the root filesystem is mounted. +.It +Strengthens the passphrase component of the User Key with: +.Rs +.%A B. Kaliski +.%T "PKCS #5: Password-Based Cryptography Specification, Version 2.0." +.%R RFC +.%N 2898 +.Re +.It +Allows the use of two independent User Keys (e.g., a +.Qq "user key" +and a +.Qq "company key" ) . +.It +It is fast - +.Nm +performs simple sector-to-sector encryption. +.It +Allows the encrypted Master Key to be backed up and restored, +so that if a user has to quickly destroy key material, +it is possible to get the data back by restoring keys from +backup. +.It +Providers can be configured to automatically detach on last close, +so users do not have to remember to detach providers after unmounting +the filesystems. +.It +Allows attaching a provider with a random, one-time Master Key, +which is useful for swap partitions and temporary filesystems. +.It +Allows verification of data integrity (data authentication). +.It +Allows suspending and resuming encrypted devices. +.El +.Pp +The first argument to +.Nm +indicates an action to be performed: +.Bl -tag -width ".Cm configure" +.It Cm init +Initialize providers which need to be encrypted. +If multiple providers are listed as arguments, they will all be initialized +with the same passphrase and/or User Key. +A unique salt will be randomly generated for each provider to ensure the +Master Key for each is unique. +Here you can set up the cryptographic algorithm to use, Data Key length, +etc. +The last sector of the providers is used to store metadata. +The +.Cm init +subcommand also automatically writes metadata backups to +.Pa /var/backups/<prov>.eli +file. +The metadata can be recovered with the +.Cm restore +subcommand described below. +.Pp +Additional options include: +.Bl -tag -width ".Fl J Ar newpassfile" +.It Fl a Ar aalgo +Enable data integrity verification (authentication) using the given algorithm. +This will reduce the size of storage available and also reduce speed. +For example, when using 4096 bytes sector and +.Nm HMAC/SHA256 +algorithm, 89% of the original provider storage will be available for use. +Currently supported algorithms are: +.Nm HMAC/SHA1 , +.Nm HMAC/RIPEMD160 , +.Nm HMAC/SHA256 , +.Nm HMAC/SHA384 +and +.Nm HMAC/SHA512 . +If the option is not given, there will be no authentication, only encryption. +The recommended algorithm is +.Nm HMAC/SHA256 . +.It Fl b +Try to decrypt this partition during boot, before the root partition is mounted. +This makes it possible to use an encrypted root partition. +One will still need bootable unencrypted storage with a +.Pa /boot/ +directory, which can be a CD-ROM disc or USB pen-drive, that can be removed +after boot. +.It Fl B Ar backupfile +File name to use for metadata backup instead of the default +.Pa /var/backups/<prov>.eli . +To inhibit backups, you can use +.Pa none +as the +.Ar backupfile . +If multiple providers were initialized in the one command, you can use +.Pa PROV +(all upper-case) in the file name, and it will be replaced with the provider +name. +If +.Pa PROV +is not found in the file name and multiple providers were initialized in the +one command, +.Pa -<prov> +will be appended to the end of the file name specified. +.It Fl d +When entering the passphrase to boot from this encrypted root filesystem, echo +.Ql * +characters. +This makes the length of the passphrase visible. +.It Fl e Ar ealgo +Encryption algorithm to use. +Currently supported algorithms are: +.Nm AES-XTS , +.Nm AES-CBC , +.Nm Camellia-CBC , +and +.Nm NULL . +The default and recommended algorithm is +.Nm AES-XTS . +.Nm NULL +is unencrypted. +.It Fl g +Enable booting from this encrypted root filesystem. +The boot loader prompts for the passphrase and loads +.Xr loader 8 +from the encrypted partition. +.It Fl i Ar iterations +Number of iterations to use with PKCS#5v2 when processing User Key +passphrase component. +If this option is not specified, +.Nm +will find the number of iterations which is equal to 2 seconds of crypto work. +If 0 is given, PKCS#5v2 will not be used. +PKCS#5v2 processing is performed once, after all parts of the passphrase +component have been read. +.It Fl J Ar newpassfile +Specifies a file which contains the passphrase component of the User Key +(or part of it). +If +.Ar newpassfile +is given as -, standard input will be used. +Only the first line (excluding new-line character) is taken from the given file. +This argument can be specified multiple times, which has the effect of +reassembling a single passphrase split across multiple files. +Cannot be combined with the +.Fl P +option. +.It Fl K Ar newkeyfile +Specifies a file which contains the keyfile component of the User Key +(or part of it). +If +.Ar newkeyfile +is given as -, standard input will be used. +This argument can be specified multiple times, which has the effect of +reassembling a single keyfile split across multiple keyfile parts. +.It Fl l Ar keylen +Data Key length to use with the given cryptographic algorithm. +If the length is not specified, the selected algorithm uses its +.Em default +key length. +.Bl -ohang -offset indent +.It Nm AES-XTS +.Em 128 , +256 +.It Nm AES-CBC , Nm Camellia-CBC +.Em 128 , +192, +256 +.El +.It Fl P +Do not use a passphrase as a component of the User Key. +Cannot be combined with the +.Fl J +option. +.It Fl s Ar sectorsize +Change decrypted provider's sector size. +Increasing the sector size allows increased performance, +because encryption/decryption which requires an initialization vector +is done per sector; fewer sectors means less computational work. +.It Fl R +Turn off automatic expansion. +By default, if the underlying provider grows, the encrypted provider will +grow automatically too. +The metadata will be moved to the new location. +If automatic expansion if turned off and the underlying provider changes +size, attaching encrypted provider will no longer be possible as the metadata +will no longer be located in the last sector. +In this case +.Nm GELI +will only log the previous size of the underlying provider, so metadata can +be found easier, if resize was done by mistake. +.It Fl T +Don't pass through +.Dv BIO_DELETE +calls (i.e., TRIM/UNMAP). +This can prevent an attacker from knowing how much space you're actually +using and which sectors contain live data, but will also prevent the +backing store (SSD, etc) from reclaiming space you're not using, which +may degrade its performance and lifespan. +The underlying provider may or may not actually obliterate the deleted +sectors when TRIM is enabled, so it should not be considered to add any +security. +.It Fl V Ar version +Metadata version to use. +This option is helpful when creating a provider that may be used by older +.Nm FreeBSD/GELI +versions. +Consult the +.Sx HISTORY +section to find which metadata version is supported by which +.Fx +version. +Note that using an older version of metadata may limit the number of +features available. +.El +.It Cm attach +Attach the given providers. +The encrypted Master Keys are loaded from the metadata and decrypted +using the given passphrase/keyfile and new GEOM providers are created +using the specified provider names. +A +.Qq .eli +suffix is added to the user specified provider names. +Multiple providers can only be attached with a single +.Cm attach +command if they all have the same passphrase and keyfiles. +.Pp +Additional options include: +.Bl -tag -width ".Fl j Ar passfile" +.It Fl C +Do a dry-run decryption. +This is useful to verify passphrase and keyfile without decrypting the device. +.It Fl d +If specified, the decrypted providers are detached automatically on last close, +so the user does not have to remember to detach +providers after unmounting the filesystems. +This only works when providers were opened for writing, and will not work if +the filesystems on the providers were mounted read-only. +Probably a better choice is the +.Fl l +option for the +.Cm detach +subcommand. +.It Fl n Ar keyno +Specifies the index number of the Master Key copy to use (could be 0 or 1). +If the index number is not provided all keys will be tested. +.It Fl j Ar passfile +Specifies a file which contains the passphrase component of the User Key +(or part of it). +For more information see the description of the +.Fl J +option for the +.Cm init +subcommand. +The same passfiles are used for all listed providers. +.It Fl k Ar keyfile +Specifies a file which contains the keyfile component of the User Key +(or part of it). +For more information see the description of the +.Fl K +option for the +.Cm init +subcommand. +The same keyfiles are used for all listed providers. +.It Fl p +Do not use a passphrase as a component of the User Keys. +Cannot be combined with the +.Fl j +option. +.It Fl r +Attach read-only providers. +They are not opened for writing. +.El +.It Cm detach +Detach the given providers, which means remove the devfs entry +and clear the Master Key and Data Keys from memory. +.Pp +Additional options include: +.Bl -tag -width ".Fl f" +.It Fl f +Force detach - detach even if the provider is open. +.It Fl l +Mark provider to detach on last close, after the last filesystem has been +unmounted. +If this option is specified, the provider will not be detached +while it is open, but will be automatically detached when it is closed for the +last time even if it was only opened for reading. +.El +.It Cm onetime +Attach the given providers with a random, one-time (ephemeral) Master Key. +The command can be used to encrypt swap partitions or temporary filesystems. +.Pp +Additional options include: +.Bl -tag -width ".Fl a Ar sectorsize" +.It Fl a Ar aalgo +Enable data integrity verification (authentication). +For more information, see the description of the +.Cm init +subcommand. +.It Fl e Ar ealgo +Encryption algorithm to use. +For more information, see the description of the +.Cm init +subcommand. +.It Fl d +Detach on last close, after the last filesystem has been unmounted. +Note: this option is not usable for temporary filesystems as the provider is +detached after the filesystem has been created. +It still can, and should, be used for swap partitions. +For more information, see the description of the +.Cm attach +subcommand. +.It Fl l Ar keylen +Data Key length to use with the given cryptographic algorithm. +For more information, see the description of the +.Cm init +subcommand. +.It Fl s Ar sectorsize +Change decrypted provider's sector size. +For more information, see the description of the +.Cm init +subcommand. +.It Fl R +Turn off automatic expansion. +For more information, see the description of the +.Cm init +subcommand. +.It Fl T +Disable TRIM/UNMAP passthru. +For more information, see the description of the +.Cm init +subcommand. +.El +.It Cm configure +Change configuration of the given providers. +.Pp +Additional options include: +.Bl -tag -width ".Fl b" +.It Fl b +Set the BOOT flag on the given providers. +For more information, see the description of the +.Cm init +subcommand. +.It Fl B +Remove the BOOT flag from the given providers. +.It Fl d +When entering the passphrase to boot from this encrypted root filesystem, echo +.Ql * +characters. +This makes the length of the passphrase visible. +.It Fl D +Disable echoing of any characters when a passphrase is entered to boot from this +encrypted root filesystem. +This hides the passphrase length. +.It Fl g +Enable booting from this encrypted root filesystem. +The boot loader prompts for the passphrase and loads +.Xr loader 8 +from the encrypted partition. +.It Fl G +Deactivate booting from this encrypted root partition. +.It Fl r +Turn on automatic expansion. +For more information, see the description of the +.Cm init +subcommand. +.It Fl R +Turn off automatic expansion. +.It Fl t +Enable TRIM/UNMAP passthru. +For more information, see the description of the +.Cm init +subcommand. +.It Fl T +Disable TRIM/UNMAP passthru. +.El +.It Cm setkey +Install a copy of the Master Key into the selected slot, encrypted with +a new User Key. +If the selected slot is populated, replace the existing copy. +A provider has one Master Key, which can be stored in one or both slots, +each encrypted with an independent User Key. +With the +.Cm init +subcommand, only key number 0 is initialized. +The User Key can be changed at any time: for an attached provider, +for a detached provider, or on the backup file. +When a provider is attached, the user does not have to provide +an existing passphrase/keyfile. +.Pp +Additional options include: +.Bl -tag -width ".Fl J Ar newpassfile" +.It Fl i Ar iterations +Number of iterations to use with PKCS#5v2. +If 0 is given, PKCS#5v2 will not be used. +To be able to use this option with the +.Cm setkey +subcommand, only one key has to be defined and this key must be changed. +.It Fl j Ar passfile +Specifies a file which contains the passphrase component of a current User Key +(or part of it). +.It Fl J Ar newpassfile +Specifies a file which contains the passphrase component of the new User Key +(or part of it). +.It Fl k Ar keyfile +Specifies a file which contains the keyfile component of a current User Key +(or part of it). +.It Fl K Ar newkeyfile +Specifies a file which contains the keyfile component of the new User Key +(or part of it). +.It Fl n Ar keyno +Specifies the index number of the Master Key copy to change (could be 0 or 1). +If the provider is attached and no key number is given, the key +used for attaching the provider will be changed. +If the provider is detached (or we are operating on a backup file) +and no key number is given, the first Master Key copy to be successfully +decrypted with the provided User Key passphrase/keyfile will be changed. +.It Fl p +Do not use a passphrase as a component of the current User Key. +Cannot be combined with the +.Fl j +option. +.It Fl P +Do not use a passphrase as a component of the new User Key. +Cannot be combined with the +.Fl J +option. +.El +.It Cm delkey +Destroy (overwrite with random data) the selected Master Key copy. +If one is destroying keys for an attached provider, the provider +will not be detached even if all copies of the Master Key are destroyed. +It can even be rescued with the +.Cm setkey +subcommand because the Master Key is still in memory. +.Pp +Additional options include: +.Bl -tag -width ".Fl a Ar keyno" +.It Fl a +Destroy all copies of the Master Key (does not need +.Fl f +option). +.It Fl f +Force key destruction. +This option is needed to destroy the last copy of the Master Key. +.It Fl n Ar keyno +Specifies the index number of the Master Key copy. +If the provider is attached and no key number is given, the key +used for attaching the provider will be destroyed. +If provider is detached (or we are operating on a backup file) the key number +has to be given. +.El +.It Cm kill +This command should be used only in emergency situations. +It will destroy all copies of the Master Key on a given provider and will +detach it forcibly (if it is attached). +This is absolutely a one-way command - if you do not have a metadata +backup, your data is gone for good. +In case the provider was attached with the +.Fl r +flag, the keys will not be destroyed, only the provider will be detached. +.Pp +Additional options include: +.Bl -tag -width ".Fl a" +.It Fl a +If specified, all currently attached providers will be killed. +.El +.It Cm backup +Backup metadata from the given provider to the given file. +.It Cm restore +Restore metadata from the given file to the given provider. +.Pp +Additional options include: +.Bl -tag -width ".Fl f" +.It Fl f +Metadata contains the size of the provider to ensure that the correct +partition or slice is attached. +If an attempt is made to restore metadata to a provider that has a different +size, +.Nm +will refuse to restore the data unless the +.Fl f +switch is used. +If the partition or slice has been grown, the +.Cm resize +subcommand should be used rather than attempting to relocate the metadata +through +.Cm backup +and +.Cm restore . +.El +.It Cm suspend +Suspend device by waiting for all inflight requests to finish, clearing all +sensitive information such as the Master Key and Data Keys from kernel memory, +and blocking all further I/O requests until the +.Cm resume +subcommand is executed. +This functionality is useful for laptops. +Suspending a laptop should not leave an encrypted device attached. +The +.Cm suspend +subcommand can be used rather than closing all files and directories from +filesystems on the encrypted device, unmounting the filesystem, and +detaching the device. +Any access to the encrypted device will be blocked until the Master Key is +reloaded through the +.Cm resume +subcommand. +Thus there is no need to close nor unmount anything. +The +.Cm suspend +subcommand does not work with devices created with the +.Cm onetime +subcommand. +Please note that sensitive data might still be present in memory locations +such as the filesystem cache after suspending an encrypted device. +.Pp +Additional options include: +.Bl -tag -width ".Fl a" +.It Fl a +Suspend all +.Nm +devices. +.El +.It Cm resume +Resume previously suspended device. +The caller must ensure that executing this subcommand does not access the +suspended device, leading to a deadlock. +For example, suspending a device which contains the filesystem where the +.Nm +utility is stored is a bad idea. +.Pp +Additional options include: +.Bl -tag -width ".Fl j Ar passfile" +.It Fl j Ar passfile +Specifies a file which contains the passphrase component of the User Key, +or part of it. +For more information see the description of the +.Fl J +option for the +.Cm init +subcommand. +.It Fl k Ar keyfile +Specifies a file which contains the keyfile component of the User Key, +or part of it. +For more information see the description of the +.Fl K +option for the +.Cm init +subcommand. +.It Fl p +Do not use a passphrase as a component of the User Key. +Cannot be combined with the +.Fl j +option. +.El +.It Cm resize +Inform +.Nm +that the provider has been resized. +The old metadata block is relocated to the correct position at the end of the +provider and the provider size is updated. +.Pp +Additional options include: +.Bl -tag -width ".Fl s Ar oldsize" +.It Fl s Ar oldsize +The size of the provider before it was resized. +.El +.It Cm version +If no arguments are given, the +.Cm version +subcommand will print the version of +.Nm +userland utility as well as the version of the +.Nm ELI +GEOM class. +.Pp +If GEOM providers are specified, the +.Cm version +subcommand will print metadata version used by each of them. +.It Cm clear +Clear metadata from the given providers. +.Em WARNING : +This will erase with zeros the encrypted Master Key copies stored in the +metadata. +.It Cm dump +Dump metadata stored on the given providers. +.It Cm list +See +.Xr geom 8 . +.It Cm status +See +.Xr geom 8 . +.It Cm load +See +.Xr geom 8 . +.It Cm unload +See +.Xr geom 8 . +.El +.Pp +Additional options include: +.Bl -tag -width ".Fl v" +.It Fl v +Be more verbose. +.El +.Sh KEY SUMMARY +.Ss Master Key +Upon +.Cm init , +the +.Nm +utility generates a random Master Key for the provider. +The Master Key never changes during the lifetime of the provider. +Each copy of the provider metadata, active or backed up to a file, can store +up to two, independently-encrypted copies of the Master Key. +.Ss User Key +Each stored copy of the Master Key is encrypted with a User Key, which +is generated by the +.Nm +utility from a passphrase and/or a keyfile. +The +.Nm +utility first reads all parts of the keyfile in the order specified on the +command line, then reads all parts of the stored passphrase in the order +specified on the command line. +If no passphrase parts are specified, the system prompts the user to enter +the passphrase. +The passphrase is optionally strengthened by PKCS#5v2. +The User Key is a digest computed over the concatenated keyfile and passphrase. +.Ss Data Key +During operation, one or more Data Keys are deterministically derived by +the kernel from the Master Key and cached in memory. +The number of Data Keys used by a given provider, and the way they are +derived, depend on the GELI version and whether the provider is configured to +use data authentication. +.Sh SYSCTL VARIABLES +The following +.Xr sysctl 8 +variables can be used to control the behavior of the +.Nm ELI +GEOM class. +The default value is shown next to each variable. +Some variables can also be set in +.Pa /boot/loader.conf . +.Bl -tag -width indent +.It Va kern.geom.eli.version +Version number of the +.Nm ELI +GEOM class. +.It Va kern.geom.eli.debug : No 0 +Debug level of the +.Nm ELI +GEOM class. +This can be set to a number between 0 and 3 inclusive. +If set to 0, minimal debug information is printed. +If set to 3, the +maximum amount of debug information is printed. +.It Va kern.geom.eli.tries : No 3 +Number of times a user is asked for the passphrase. +This is only used for providers which are attached on boot, +before the root filesystem is mounted. +If set to 0, attaching providers on boot will be disabled. +This variable should be set in +.Pa /boot/loader.conf . +.It Va kern.geom.eli.overwrites : No 5 +Specifies how many times the Master Key is overwritten +with random values when it is destroyed. +After this operation it is filled with zeros. +.It Va kern.geom.eli.use_uma_bytes +.Nm +must allocate a buffer for every write operation, used when performing +encryption. +This sysctl reports the maximum size in bytes for which geli will perform the +allocation using +.Xr uma 9 , +as opposed to +.Xr malloc 9 . +.It Va kern.geom.eli.visible_passphrase : No 0 +If set to 1, the passphrase entered on boot will be visible. +This alternative should be used with caution as the entered +passphrase can be logged and exposed via +.Xr dmesg 8 . +This variable should be set in +.Pa /boot/loader.conf . +.It Va kern.geom.eli.threads : No 0 +Specifies how many kernel threads should be used for doing software +cryptography. +Its purpose is to increase performance on SMP systems. +If set to 0, a CPU-pinned thread will be started for every active CPU. +Note that this variable must be set prior to attaching +.Nm +to a disk. +.It Va kern.geom.eli.batch : No 0 +When set to 1, can speed-up crypto operations by using batching. +Batching reduces the number of interrupts by responding to a group of +crypto requests with one interrupt. +The crypto card and the driver have to support this feature. +.It Va kern.geom.eli.key_cache_limit : No 8192 +Specifies how many Data Keys to cache. +The default limit +(8192 keys) will allow caching of all keys for a 4TB provider with 512 byte +sectors and will take around 1MB of memory. +.It Va kern.geom.eli.key_cache_hits +Reports how many times we were looking up a Data Key and it was already in +cache. +This sysctl is not updated for providers that need fewer Data Keys than +the limit specified in +.Va kern.geom.eli.key_cache_limit . +.It Va kern.geom.eli.key_cache_misses +Reports how many times we were looking up a Data Key and it was not in cache. +This sysctl is not updated for providers that need fewer Data Keys than the limit +specified in +.Va kern.geom.eli.key_cache_limit . +.It Va kern.geom.eli.unmapped_io +Enable support for unmapped I/O buffers, currently implemented only on 64-bit +platforms. +This is an optimization which reduces the overhead of I/O processing. +This variable is intended for debugging purposes and must be set in +.Pa /boot/loader.conf . +.El +.Sh PERFORMANCE CONSIDERATIONS +The default value of +.Va kern.geom.eli.threads +is usually good for a system with one SSD. +However, it may need to be lowered on systems with many disks, +so as to avoid creating too much thread-switching overhead. +On systems with more disks than CPUs, it's best to set this variable +to 1. +.Pp +.Nm +internally uses +.Xr malloc 9 +to allocate memory for operations larger than +.Va kern.geom.eli.use_uma_bytes , +but malloc is slow for allocations larger than +.Va vm.kmem_zmax . +So it's best to avoid writing more than +.Ms MAX(kern.geom.eli.use_uma_bytes, vm.kmem_zmax) +in a single write operation. +On systems that format +.Xr zfs 4 +on top of +.Nm , +the maximum write size can be controlled by +.Va vfs.zfs.vdev.aggregation_limit +and +.Va vfs.zfs.vdev.aggregation_limit_non_rotating +for HDDs and SSDs, respectively. +.Sh EXIT STATUS +Exit status is 0 on success, and 1 if the command fails. +.Sh EXAMPLES +Initialize a provider which is going to be encrypted with a +passphrase and random data from a file on the user's pen drive. +Use 4kB sector size. +Attach the provider, create a filesystem, and mount it. +Do the work. +Unmount the provider and detach it: +.Bd -literal -offset indent +# dd if=/dev/random of=/mnt/pendrive/da2.key bs=64 count=1 +# geli init -s 4096 -K /mnt/pendrive/da2.key /dev/da2 +Enter new passphrase: +Reenter new passphrase: +# geli attach -k /mnt/pendrive/da2.key /dev/da2 +Enter passphrase: +# dd if=/dev/random of=/dev/da2.eli bs=1m +# newfs /dev/da2.eli +# mount /dev/da2.eli /mnt/secret +\&... +# umount /mnt/secret +# geli detach da2.eli +.Ed +.Pp +Create an encrypted provider, but use two User Keys: +one for your employee and one for you as the company's security officer +(so it is not a tragedy if the employee +.Qq accidentally +forgets his passphrase): +.Bd -literal -offset indent +# geli init /dev/da2 +Enter new passphrase: (enter security officer's passphrase) +Reenter new passphrase: +# geli setkey -n 1 /dev/da2 +Enter passphrase: (enter security officer's passphrase) +Enter new passphrase: (let your employee enter his passphrase ...) +Reenter new passphrase: (... twice) +.Ed +.Pp +You are the security officer in your company. +Create an encrypted provider for use by the user, but remember that users +forget their passphrases, so backup the Master Key with your own random key: +.Bd -literal -offset indent +# dd if=/dev/random of=/mnt/pendrive/keys/`hostname` bs=64 count=1 +# geli init -P -K /mnt/pendrive/keys/`hostname` /dev/ada0s1e +# geli backup /dev/ada0s1e /mnt/pendrive/backups/`hostname` +(use key number 0, so the encrypted Master Key will be re-encrypted by this) +# geli setkey -n 0 -k /mnt/pendrive/keys/`hostname` /dev/ada0s1e +(allow the user to enter his passphrase) +Enter new passphrase: +Reenter new passphrase: +.Ed +.Pp +Encrypted swap partition setup: +.Bd -literal -offset indent +# dd if=/dev/random of=/dev/ada0s1b bs=1m +# geli onetime -d ada0s1b +# swapon /dev/ada0s1b.eli +.Ed +.Pp +The example below shows how to configure two providers which will be attached +on boot, before the root filesystem is mounted. +One of them is using passphrase and three keyfile parts and the other is +using only a keyfile in one part: +.Bd -literal -offset indent +# dd if=/dev/random of=/dev/da0 bs=1m +# dd if=/dev/random of=/boot/keys/da0.key0 bs=32k count=1 +# dd if=/dev/random of=/boot/keys/da0.key1 bs=32k count=1 +# dd if=/dev/random of=/boot/keys/da0.key2 bs=32k count=1 +# geli init -b -K /boot/keys/da0.key0 -K /boot/keys/da0.key1 -K /boot/keys/da0.key2 da0 +Enter new passphrase: +Reenter new passphrase: +# dd if=/dev/random of=/dev/da1s3a bs=1m +# dd if=/dev/random of=/boot/keys/da1s3a.key bs=128k count=1 +# geli init -b -P -K /boot/keys/da1s3a.key da1s3a +.Ed +.Pp +The providers are initialized, now we have to add these lines to +.Pa /boot/loader.conf : +.Bd -literal -offset indent +geli_da0_keyfile0_load="YES" +geli_da0_keyfile0_type="da0:geli_keyfile0" +geli_da0_keyfile0_name="/boot/keys/da0.key0" +geli_da0_keyfile1_load="YES" +geli_da0_keyfile1_type="da0:geli_keyfile1" +geli_da0_keyfile1_name="/boot/keys/da0.key1" +geli_da0_keyfile2_load="YES" +geli_da0_keyfile2_type="da0:geli_keyfile2" +geli_da0_keyfile2_name="/boot/keys/da0.key2" + +geli_da1s3a_keyfile0_load="YES" +geli_da1s3a_keyfile0_type="da1s3a:geli_keyfile0" +geli_da1s3a_keyfile0_name="/boot/keys/da1s3a.key" +.Ed +.Pp +If there is only one keyfile, the index might be omitted: +.Bd -literal -offset indent +geli_da1s3a_keyfile_load="YES" +geli_da1s3a_keyfile_type="da1s3a:geli_keyfile" +geli_da1s3a_keyfile_name="/boot/keys/da1s3a.key" +.Ed +.Pp +By convention, these loader variables are called +.Sm off +.Va geli_ No < Ar device No > Va _load . +.Sm on +However, the actual name prefix before +.Va _load , _type , +or +.Va _name +does not matter. +At boot time, the +.Nm +module searches through all +.Sm off +.No < Va prefix No > Va _type No -like +.Sm on +variables that have a value of +.Sm off +.Dq < Ar device No > :geli_keyfile . +.Sm on +The paths to keyfiles are then extracted from +.Sm off +.No < Ar prefix No > Va _name +.Sm on +variables. +In the example above, +.Ar prefix +is +.Dq Li geli_da1s3a_keyfile . +.Pp +Not only configure encryption, but also data integrity verification using +.Nm HMAC/SHA256 . +.Bd -literal -offset indent +# geli init -a hmac/sha256 -s 4096 /dev/da0 +Enter new passphrase: +Reenter new passphrase: +# geli attach /dev/da0 +Enter passphrase: +# dd if=/dev/random of=/dev/da0.eli bs=1m +# newfs /dev/da0.eli +# mount /dev/da0.eli /mnt/secret +.Ed +.Pp +.Cm geli +writes the metadata backup by default to the +.Pa /var/backups/<prov>.eli +file. +If the metadata is lost in any way (e.g., by accidental overwrite), it can be restored. +Consider the following situation: +.Bd -literal -offset indent +# geli init /dev/da0 +Enter new passphrase: +Reenter new passphrase: + +Metadata backup can be found in /var/backups/da0.eli and +can be restored with the following command: + + # geli restore /var/backups/da0.eli /dev/da0 + +# geli clear /dev/da0 +# geli attach /dev/da0 +geli: Cannot read metadata from /dev/da0: Invalid argument. +# geli restore /var/backups/da0.eli /dev/da0 +# geli attach /dev/da0 +Enter passphrase: +.Ed +.Pp +If an encrypted filesystem is extended, it is necessary to relocate and +update the metadata: +.Bd -literal -offset indent +# gpart create -s GPT ada0 +# gpart add -s 1g -t freebsd-ufs -i 1 ada0 +# geli init -K keyfile -P ada0p1 +# gpart resize -s 2g -i 1 ada0 +# geli resize -s 1g ada0p1 +# geli attach -k keyfile -p ada0p1 +.Ed +.Pp +Initialize provider with the passphrase split into two files. +The provider can be attached using those two files or by entering +.Dq foobar +as the passphrase at the +.Nm +prompt: +.Bd -literal -offset indent +# echo foo > da0.pass0 +# echo bar > da0.pass1 +# geli init -J da0.pass0 -J da0.pass1 da0 +# geli attach -j da0.pass0 -j da0.pass1 da0 +# geli detach da0 +# geli attach da0 +Enter passphrase: foobar +.Ed +.Pp +Suspend all +.Nm +devices on a laptop, suspend the laptop, then resume devices one by one after +resuming the laptop: +.Bd -literal -offset indent +# geli suspend -a +# zzz +<resume your laptop> +# geli resume -p -k keyfile gpt/secret +# geli resume gpt/private +Enter passphrase: +.Ed +.Pp +To create a +.Nm +encrypted filesystem with a file as storage device follow this example. +First a file named private0 is created in +.Pa /usr +and attached as a memory disk like +.Pa /dev/md0 +for example. +.Bd -literal -offset indent +# dd if=/dev/zero of=/usr/private0 bs=1m count=256 +# chmod 0600 /usr/private0 +# mdconfig -t vnode -f /usr/private0 +.Ed +.Pp +It is recommended to place the following line in +.Xr rc.conf 5 +to have the memory disk automatically created during boot. +.Bd -literal -offset indent +mdconfig_md0="-t vnode -f /usr/private0" +.Ed +.Pp +After +.Pa /dev/md0 +is created a random key has to be generated and stored in a secure location, +like +.Pa /root +for example. +This key should be protected by a passphrase, which +is requested when geli init is called. +.Bd -literal -offset indent +# dd if=/dev/random of=/root/private0.key bs=64 count=1 +# geli init -K /root/private0.key -s 4096 /dev/md0 +Enter new passphrase: +Reenter new passphrase: +# geli attach -k /root/private0.key /dev/md0 +Enter passphrase: +# dd if=/dev/random of=/dev/md0.eli bs=1m +.Ed +.Pp +Once the initialization of the +.Pa /dev/md0.eli +device is ready create a UFS filesystem and mount it for example in +.Pa /private . +.Bd -literal -offset indent +# newfs /dev/md0.eli +# mount /dev/md0.eli /private +.Ed +.Pp +After a system reboot the +.Nm +device can be mounted again with the following commands. +The call of geli attach will ask for the passphrase. +It is recommended to do this procedure after the boot, because otherwise +the boot process would be waiting for the passphrase input. +.Bd -literal -offset indent +# geli attach -k /root/private0.key /dev/md0 +Enter passphrase: +# mount /dev/md0.eli /private +.Ed +.Sh ENCRYPTION MODES +.Nm +supports two encryption modes: +.Nm XTS , +which was standardized as +.Nm IEEE P1619 +and +.Nm CBC +with unpredictable IV. +The +.Nm CBC +mode used by +.Nm +is very similar to the mode +.Nm ESSIV . +.Sh DATA AUTHENTICATION +.Nm +can verify data integrity when an authentication algorithm is specified. +When data corruption/modification is detected, +.Nm +will not return any data, but instead will return an error +.Pq Er EINVAL . +The offset and size of the corrupted data will be printed on the console. +It is important to know against which attacks +.Nm +provides protection for your data. +If data is modified in-place or copied from one place on the disk +to another even without modification, +.Nm +should be able to detect such a change. +If an attacker can remember the encrypted data, he can overwrite any future +changes with the data he owns without it being noticed. +In other words +.Nm +will not protect your data against replay attacks. +.Pp +It is recommended to write to the whole provider before first use, +in order to make sure that all sectors and their corresponding +checksums are properly initialized into a consistent state. +One can safely ignore data authentication errors that occur immediately +after the first time a provider is attached and before it is +initialized in this way. +.Sh SEE ALSO +.Xr crypto 4 , +.Xr geom 4 , +.Xr loader.conf 5 , +.Xr geom 8 , +.Xr crypto 9 +.Sh HISTORY +The +.Nm +utility appeared in +.Fx 6.0 . +Support for the +.Nm Camellia +block cipher was implemented by Yoshisato Yanagisawa in +.Fx 7.0 . +.Pp +Highest +.Nm GELI +metadata version supported by the given +.Fx +version: +.Bl -column -offset indent ".Sy FreeBSD" ".Sy version" +.It Sy FreeBSD Ta Sy GELI +.It Sy version Ta Sy version +.Pp +.It Li 6.0 Ta 0 +.It Li 6.1 Ta 0 +.It Li 6.2 Ta 3 +.It Li 6.3 Ta 3 +.It Li 6.4 Ta 3 +.Pp +.It Li 7.0 Ta 3 +.It Li 7.1 Ta 3 +.It Li 7.2 Ta 3 +.It Li 7.3 Ta 3 +.It Li 7.4 Ta 3 +.Pp +.It Li 8.0 Ta 3 +.It Li 8.1 Ta 3 +.It Li 8.2 Ta 5 +.Pp +.It Li 9.0 Ta 6 +.Pp +.It Li 10.0 Ta 7 +.El +.Sh AUTHORS +.An Pawel Jakub Dawidek Aq Mt pjd@FreeBSD.org diff --git a/lib/geom/eli/geom_eli.c b/lib/geom/eli/geom_eli.c new file mode 100644 index 000000000000..4dd1c5dea35d --- /dev/null +++ b/lib/geom/eli/geom_eli.c @@ -0,0 +1,2014 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2004-2019 Pawel Jakub Dawidek <pawel@dawidek.net> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/mman.h> +#include <sys/sysctl.h> +#include <sys/resource.h> +#include <opencrypto/cryptodev.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <libgeom.h> +#include <paths.h> +#include <readpassphrase.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> + +#include <geom/eli/g_eli.h> +#include <geom/eli/pkcs5v2.h> + +#include "core/geom.h" +#include "misc/subr.h" + + +uint32_t lib_version = G_LIB_VERSION; +uint32_t version = G_ELI_VERSION; + +#define GELI_BACKUP_DIR "/var/backups/" +#define GELI_ENC_ALGO "aes" +#define BUFSIZE 1024 + +/* + * Passphrase cached when attaching multiple providers, in order to be more + * user-friendly if they are using the same passphrase. + */ +static char cached_passphrase[BUFSIZE] = ""; + +static void eli_main(struct gctl_req *req, unsigned flags); +static void eli_init(struct gctl_req *req); +static void eli_attach(struct gctl_req *req); +static void eli_configure(struct gctl_req *req); +static void eli_setkey(struct gctl_req *req); +static void eli_delkey(struct gctl_req *req); +static void eli_resume(struct gctl_req *req); +static void eli_kill(struct gctl_req *req); +static void eli_backup(struct gctl_req *req); +static void eli_restore(struct gctl_req *req); +static void eli_resize(struct gctl_req *req); +static void eli_version(struct gctl_req *req); +static void eli_clear(struct gctl_req *req); +static void eli_dump(struct gctl_req *req); + +static int eli_backup_create(struct gctl_req *req, const char *prov, + const char *file); + +/* + * Available commands: + * + * init [-bdgPRTv] [-a aalgo] [-B backupfile] [-e ealgo] [-i iterations] [-l keylen] [-J newpassfile] [-K newkeyfile] [-s sectorsize] [-V version] prov ... + * label - alias for 'init' + * attach [-Cdprv] [-n keyno] [-j passfile] [-k keyfile] prov ... + * detach [-fl] prov ... + * stop - alias for 'detach' + * onetime [-dRT] [-a aalgo] [-e ealgo] [-l keylen] prov + * configure [-bBgGrRtT] prov ... + * setkey [-pPv] [-n keyno] [-j passfile] [-J newpassfile] [-k keyfile] [-K newkeyfile] prov + * delkey [-afv] [-n keyno] prov + * suspend [-v] -a | prov ... + * resume [-pv] [-j passfile] [-k keyfile] prov + * kill [-av] [prov ...] + * backup [-v] prov file + * restore [-fv] file prov + * resize [-v] -s oldsize prov + * version [prov ...] + * clear [-v] prov ... + * dump [-v] prov ... + */ +struct g_command class_commands[] = { + { "init", G_FLAG_VERBOSE, eli_main, + { + { 'a', "aalgo", "", G_TYPE_STRING }, + { 'b', "boot", NULL, G_TYPE_BOOL }, + { 'B', "backupfile", "", G_TYPE_STRING }, + { 'd', "displaypass", NULL, G_TYPE_BOOL }, + { 'e', "ealgo", "", G_TYPE_STRING }, + { 'g', "geliboot", NULL, G_TYPE_BOOL }, + { 'i', "iterations", "-1", G_TYPE_NUMBER }, + { 'J', "newpassfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI }, + { 'K', "newkeyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI }, + { 'l', "keylen", "0", G_TYPE_NUMBER }, + { 'P', "nonewpassphrase", NULL, G_TYPE_BOOL }, + { 'R', "noautoresize", NULL, G_TYPE_BOOL }, + { 's', "sectorsize", "0", G_TYPE_NUMBER }, + { 'T', "notrim", NULL, G_TYPE_BOOL }, + { 'V', "mdversion", "-1", G_TYPE_NUMBER }, + G_OPT_SENTINEL + }, + "[-bdgPRTv] [-a aalgo] [-B backupfile] [-e ealgo] [-i iterations] [-l keylen] [-J newpassfile] [-K newkeyfile] [-s sectorsize] [-V version] prov ..." + }, + { "label", G_FLAG_VERBOSE, eli_main, + { + { 'a', "aalgo", "", G_TYPE_STRING }, + { 'b', "boot", NULL, G_TYPE_BOOL }, + { 'B', "backupfile", "", G_TYPE_STRING }, + { 'd', "displaypass", NULL, G_TYPE_BOOL }, + { 'e', "ealgo", "", G_TYPE_STRING }, + { 'g', "geliboot", NULL, G_TYPE_BOOL }, + { 'i', "iterations", "-1", G_TYPE_NUMBER }, + { 'J', "newpassfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI }, + { 'K', "newkeyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI }, + { 'l', "keylen", "0", G_TYPE_NUMBER }, + { 'P', "nonewpassphrase", NULL, G_TYPE_BOOL }, + { 'R', "noautoresize", NULL, G_TYPE_BOOL }, + { 's', "sectorsize", "0", G_TYPE_NUMBER }, + { 'T', "notrim", NULL, G_TYPE_BOOL }, + { 'V', "mdversion", "-1", G_TYPE_NUMBER }, + G_OPT_SENTINEL + }, + "- an alias for 'init'" + }, + { "attach", G_FLAG_VERBOSE | G_FLAG_LOADKLD, eli_main, + { + { 'C', "dryrun", NULL, G_TYPE_BOOL }, + { 'd', "detach", NULL, G_TYPE_BOOL }, + { 'j', "passfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI }, + { 'k', "keyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI }, + { 'n', "keyno", "-1", G_TYPE_NUMBER }, + { 'p', "nopassphrase", NULL, G_TYPE_BOOL }, + { 'r', "readonly", NULL, G_TYPE_BOOL }, + G_OPT_SENTINEL + }, + "[-Cdprv] [-n keyno] [-j passfile] [-k keyfile] prov ..." + }, + { "detach", 0, NULL, + { + { 'f', "force", NULL, G_TYPE_BOOL }, + { 'l', "last", NULL, G_TYPE_BOOL }, + G_OPT_SENTINEL + }, + "[-fl] prov ..." + }, + { "stop", 0, NULL, + { + { 'f', "force", NULL, G_TYPE_BOOL }, + { 'l', "last", NULL, G_TYPE_BOOL }, + G_OPT_SENTINEL + }, + "- an alias for 'detach'" + }, + { "onetime", G_FLAG_VERBOSE | G_FLAG_LOADKLD, NULL, + { + { 'a', "aalgo", "", G_TYPE_STRING }, + { 'd', "detach", NULL, G_TYPE_BOOL }, + { 'e', "ealgo", GELI_ENC_ALGO, G_TYPE_STRING }, + { 'l', "keylen", "0", G_TYPE_NUMBER }, + { 'R', "noautoresize", NULL, G_TYPE_BOOL }, + { 's', "sectorsize", "0", G_TYPE_NUMBER }, + { 'T', "notrim", NULL, G_TYPE_BOOL }, + G_OPT_SENTINEL + }, + "[-dRT] [-a aalgo] [-e ealgo] [-l keylen] [-s sectorsize] prov" + }, + { "configure", G_FLAG_VERBOSE, eli_main, + { + { 'b', "boot", NULL, G_TYPE_BOOL }, + { 'B', "noboot", NULL, G_TYPE_BOOL }, + { 'd', "displaypass", NULL, G_TYPE_BOOL }, + { 'D', "nodisplaypass", NULL, G_TYPE_BOOL }, + { 'g', "geliboot", NULL, G_TYPE_BOOL }, + { 'G', "nogeliboot", NULL, G_TYPE_BOOL }, + { 'r', "autoresize", NULL, G_TYPE_BOOL }, + { 'R', "noautoresize", NULL, G_TYPE_BOOL }, + { 't', "trim", NULL, G_TYPE_BOOL }, + { 'T', "notrim", NULL, G_TYPE_BOOL }, + G_OPT_SENTINEL + }, + "[-bBdDgGrRtT] prov ..." + }, + { "setkey", G_FLAG_VERBOSE, eli_main, + { + { 'i', "iterations", "-1", G_TYPE_NUMBER }, + { 'j', "passfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI }, + { 'J', "newpassfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI }, + { 'k', "keyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI }, + { 'K', "newkeyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI }, + { 'n', "keyno", "-1", G_TYPE_NUMBER }, + { 'p', "nopassphrase", NULL, G_TYPE_BOOL }, + { 'P', "nonewpassphrase", NULL, G_TYPE_BOOL }, + G_OPT_SENTINEL + }, + "[-pPv] [-n keyno] [-i iterations] [-j passfile] [-J newpassfile] [-k keyfile] [-K newkeyfile] prov" + }, + { "delkey", G_FLAG_VERBOSE, eli_main, + { + { 'a', "all", NULL, G_TYPE_BOOL }, + { 'f', "force", NULL, G_TYPE_BOOL }, + { 'n', "keyno", "-1", G_TYPE_NUMBER }, + G_OPT_SENTINEL + }, + "[-afv] [-n keyno] prov" + }, + { "suspend", G_FLAG_VERBOSE, NULL, + { + { 'a', "all", NULL, G_TYPE_BOOL }, + G_OPT_SENTINEL + }, + "[-v] -a | prov ..." + }, + { "resume", G_FLAG_VERBOSE, eli_main, + { + { 'j', "passfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI }, + { 'k', "keyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI }, + { 'p', "nopassphrase", NULL, G_TYPE_BOOL }, + G_OPT_SENTINEL + }, + "[-pv] [-j passfile] [-k keyfile] prov" + }, + { "kill", G_FLAG_VERBOSE, eli_main, + { + { 'a', "all", NULL, G_TYPE_BOOL }, + G_OPT_SENTINEL + }, + "[-av] [prov ...]" + }, + { "backup", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS, + "[-v] prov file" + }, + { "restore", G_FLAG_VERBOSE, eli_main, + { + { 'f', "force", NULL, G_TYPE_BOOL }, + G_OPT_SENTINEL + }, + "[-fv] file prov" + }, + { "resize", G_FLAG_VERBOSE, eli_main, + { + { 's', "oldsize", NULL, G_TYPE_NUMBER }, + G_OPT_SENTINEL + }, + "[-v] -s oldsize prov" + }, + { "version", G_FLAG_LOADKLD, eli_main, G_NULL_OPTS, + "[prov ...]" + }, + { "clear", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS, + "[-v] prov ..." + }, + { "dump", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS, + "[-v] prov ..." + }, + G_CMD_SENTINEL +}; + +static int verbose = 0; + +static int +eli_protect(struct gctl_req *req) +{ + struct rlimit rl; + + /* Disable core dumps. */ + rl.rlim_cur = 0; + rl.rlim_max = 0; + if (setrlimit(RLIMIT_CORE, &rl) == -1) { + gctl_error(req, "Cannot disable core dumps: %s.", + strerror(errno)); + return (-1); + } + /* Disable swapping. */ + if (mlockall(MCL_FUTURE) == -1) { + gctl_error(req, "Cannot lock memory: %s.", strerror(errno)); + return (-1); + } + return (0); +} + +static void +eli_main(struct gctl_req *req, unsigned int flags) +{ + const char *name; + + if (eli_protect(req) == -1) + return; + + if ((flags & G_FLAG_VERBOSE) != 0) + verbose = 1; + + name = gctl_get_ascii(req, "verb"); + if (name == NULL) { + gctl_error(req, "No '%s' argument.", "verb"); + return; + } + if (strcmp(name, "init") == 0 || strcmp(name, "label") == 0) + eli_init(req); + else if (strcmp(name, "attach") == 0) + eli_attach(req); + else if (strcmp(name, "configure") == 0) + eli_configure(req); + else if (strcmp(name, "setkey") == 0) + eli_setkey(req); + else if (strcmp(name, "delkey") == 0) + eli_delkey(req); + else if (strcmp(name, "resume") == 0) + eli_resume(req); + else if (strcmp(name, "kill") == 0) + eli_kill(req); + else if (strcmp(name, "backup") == 0) + eli_backup(req); + else if (strcmp(name, "restore") == 0) + eli_restore(req); + else if (strcmp(name, "resize") == 0) + eli_resize(req); + else if (strcmp(name, "version") == 0) + eli_version(req); + else if (strcmp(name, "dump") == 0) + eli_dump(req); + else if (strcmp(name, "clear") == 0) + eli_clear(req); + else + gctl_error(req, "Unknown command: %s.", name); +} + +static bool +eli_is_attached(const char *prov) +{ + char name[MAXPATHLEN]; + + /* + * Not the best way to do it, but the easiest. + * We try to open provider and check if it is a GEOM provider + * by asking about its sectorsize. + */ + snprintf(name, sizeof(name), "%s%s", prov, G_ELI_SUFFIX); + return (g_get_sectorsize(name) > 0); +} + +static int +eli_genkey_files(struct gctl_req *req, bool new, const char *type, + struct hmac_ctx *ctxp, char *passbuf, size_t passbufsize) +{ + char *p, buf[BUFSIZE], argname[16]; + const char *file; + int error, fd, i; + ssize_t done; + + assert((strcmp(type, "keyfile") == 0 && ctxp != NULL && + passbuf == NULL && passbufsize == 0) || + (strcmp(type, "passfile") == 0 && ctxp == NULL && + passbuf != NULL && passbufsize > 0)); + assert(strcmp(type, "keyfile") == 0 || passbuf[0] == '\0'); + + for (i = 0; ; i++) { + snprintf(argname, sizeof(argname), "%s%s%d", + new ? "new" : "", type, i); + + /* No more {key,pass}files? */ + if (!gctl_has_param(req, argname)) + return (i); + + file = gctl_get_ascii(req, "%s", argname); + assert(file != NULL); + + if (strcmp(file, "-") == 0) + fd = STDIN_FILENO; + else { + fd = open(file, O_RDONLY); + if (fd == -1) { + gctl_error(req, "Cannot open %s %s: %s.", + type, file, strerror(errno)); + return (-1); + } + } + if (strcmp(type, "keyfile") == 0) { + while ((done = read(fd, buf, sizeof(buf))) > 0) + g_eli_crypto_hmac_update(ctxp, buf, done); + } else /* if (strcmp(type, "passfile") == 0) */ { + assert(strcmp(type, "passfile") == 0); + + while ((done = read(fd, buf, sizeof(buf) - 1)) > 0) { + buf[done] = '\0'; + p = strchr(buf, '\n'); + if (p != NULL) { + *p = '\0'; + done = p - buf; + } + if (strlcat(passbuf, buf, passbufsize) >= + passbufsize) { + gctl_error(req, + "Passphrase in %s too long.", file); + explicit_bzero(buf, sizeof(buf)); + return (-1); + } + if (p != NULL) + break; + } + } + error = errno; + if (strcmp(file, "-") != 0) + close(fd); + explicit_bzero(buf, sizeof(buf)); + if (done == -1) { + gctl_error(req, "Cannot read %s %s: %s.", + type, file, strerror(error)); + return (-1); + } + } + /* NOTREACHED */ +} + +static int +eli_genkey_passphrase_prompt(struct gctl_req *req, bool new, char *passbuf, + size_t passbufsize) +{ + char *p; + + for (;;) { + p = readpassphrase( + new ? "Enter new passphrase: " : "Enter passphrase: ", + passbuf, passbufsize, RPP_ECHO_OFF | RPP_REQUIRE_TTY); + if (p == NULL) { + explicit_bzero(passbuf, passbufsize); + gctl_error(req, "Cannot read passphrase: %s.", + strerror(errno)); + return (-1); + } + + if (new) { + char tmpbuf[BUFSIZE]; + + p = readpassphrase("Reenter new passphrase: ", + tmpbuf, sizeof(tmpbuf), + RPP_ECHO_OFF | RPP_REQUIRE_TTY); + if (p == NULL) { + explicit_bzero(passbuf, passbufsize); + gctl_error(req, + "Cannot read passphrase: %s.", + strerror(errno)); + return (-1); + } + + if (strcmp(passbuf, tmpbuf) != 0) { + explicit_bzero(passbuf, passbufsize); + fprintf(stderr, "They didn't match.\n"); + continue; + } + explicit_bzero(tmpbuf, sizeof(tmpbuf)); + } + return (0); + } + /* NOTREACHED */ +} + +static int +eli_genkey_passphrase(struct gctl_req *req, struct g_eli_metadata *md, bool new, + struct hmac_ctx *ctxp) +{ + char passbuf[BUFSIZE]; + bool nopassphrase; + int nfiles; + + /* + * Return error if the 'do not use passphrase' flag was given but a + * passfile was provided. + */ + nopassphrase = + gctl_get_int(req, new ? "nonewpassphrase" : "nopassphrase"); + if (nopassphrase) { + if (gctl_has_param(req, new ? "newpassfile0" : "passfile0")) { + gctl_error(req, + "Options -%c and -%c are mutually exclusive.", + new ? 'J' : 'j', new ? 'P' : 'p'); + return (-1); + } + return (0); + } + + /* + * Return error if using a provider which does not require a passphrase + * but the 'do not use passphrase' flag was not given. + */ + if (!new && md->md_iterations == -1) { + gctl_error(req, "Missing -p flag."); + return (-1); + } + passbuf[0] = '\0'; + + /* Use cached passphrase if defined. */ + if (strlen(cached_passphrase) > 0) { + strlcpy(passbuf, cached_passphrase, sizeof(passbuf)); + } else { + nfiles = eli_genkey_files(req, new, "passfile", NULL, passbuf, + sizeof(passbuf)); + if (nfiles == -1) { + return (-1); + } else if (nfiles == 0) { + if (eli_genkey_passphrase_prompt(req, new, passbuf, + sizeof(passbuf)) == -1) { + return (-1); + } + } + /* Cache the passphrase for other providers. */ + strlcpy(cached_passphrase, passbuf, sizeof(cached_passphrase)); + } + /* + * Field md_iterations equal to -1 means "choose some sane + * value for me". + */ + if (md->md_iterations == -1) { + assert(new); + if (verbose) + printf("Calculating number of iterations...\n"); + md->md_iterations = pkcs5v2_calculate(2000000); + assert(md->md_iterations > 0); + if (verbose) { + printf("Done, using %d iterations.\n", + md->md_iterations); + } + } + /* + * If md_iterations is equal to 0, user doesn't want PKCS#5v2. + */ + if (md->md_iterations == 0) { + g_eli_crypto_hmac_update(ctxp, md->md_salt, + sizeof(md->md_salt)); + g_eli_crypto_hmac_update(ctxp, passbuf, strlen(passbuf)); + } else /* if (md->md_iterations > 0) */ { + unsigned char dkey[G_ELI_USERKEYLEN]; + + pkcs5v2_genkey(dkey, sizeof(dkey), md->md_salt, + sizeof(md->md_salt), passbuf, md->md_iterations); + g_eli_crypto_hmac_update(ctxp, dkey, sizeof(dkey)); + explicit_bzero(dkey, sizeof(dkey)); + } + explicit_bzero(passbuf, sizeof(passbuf)); + + return (0); +} + +static bool +eli_init_key_hmac_ctx(struct gctl_req *req, struct hmac_ctx *ctx, bool new) +{ + int nfiles; + bool nopassphrase; + + nopassphrase = + gctl_get_int(req, new ? "nonewpassphrase" : "nopassphrase"); + + g_eli_crypto_hmac_init(ctx, NULL, 0); + nfiles = eli_genkey_files(req, new, "keyfile", ctx, NULL, 0); + if (nfiles == -1) { + return (false); + } else if (nfiles == 0 && nopassphrase) { + gctl_error(req, "No key components given."); + return (false); + } + + return (true); +} + +static unsigned char * +eli_genkey(struct gctl_req *req, const struct hmac_ctx *ctxtemplate, + struct g_eli_metadata *md, unsigned char *key, bool new) +{ + struct hmac_ctx ctx; + + memcpy(&ctx, ctxtemplate, sizeof(ctx)); + + if (eli_genkey_passphrase(req, md, new, &ctx) == -1) + return (NULL); + + g_eli_crypto_hmac_final(&ctx, key, 0); + + return (key); +} + +static unsigned char * +eli_genkey_single(struct gctl_req *req, struct g_eli_metadata *md, + unsigned char *key, bool new) +{ + struct hmac_ctx ctx; + unsigned char *rkey; + + if (!eli_init_key_hmac_ctx(req, &ctx, new)) { + return (NULL); + } + rkey = eli_genkey(req, &ctx, md, key, new); + explicit_bzero(&ctx, sizeof(ctx)); + + return (rkey); +} + +static int +eli_metadata_read(struct gctl_req *req, const char *prov, + struct g_eli_metadata *md) +{ + unsigned char sector[sizeof(struct g_eli_metadata)]; + int error; + + if (g_get_sectorsize(prov) == 0) { + int fd; + + /* This is a file probably. */ + fd = open(prov, O_RDONLY); + if (fd == -1) { + gctl_error(req, "Cannot open %s: %s.", prov, + strerror(errno)); + return (-1); + } + if (read(fd, sector, sizeof(sector)) != sizeof(sector)) { + gctl_error(req, "Cannot read metadata from %s: %s.", + prov, strerror(errno)); + close(fd); + return (-1); + } + close(fd); + } else { + /* This is a GEOM provider. */ + error = g_metadata_read(prov, sector, sizeof(sector), + G_ELI_MAGIC); + if (error != 0) { + gctl_error(req, "Cannot read metadata from %s: %s.", + prov, strerror(error)); + return (-1); + } + } + error = eli_metadata_decode(sector, md); + switch (error) { + case 0: + break; + case EOPNOTSUPP: + gctl_error(req, + "Provider's %s metadata version %u is too new.\n" + "geli: The highest supported version is %u.", + prov, (unsigned int)md->md_version, G_ELI_VERSION); + return (-1); + case EINVAL: + gctl_error(req, "Inconsistent provider's %s metadata.", prov); + return (-1); + default: + gctl_error(req, + "Unexpected error while decoding provider's %s metadata: %s.", + prov, strerror(error)); + return (-1); + } + return (0); +} + +static int +eli_metadata_store(struct gctl_req *req, const char *prov, + struct g_eli_metadata *md) +{ + unsigned char sector[sizeof(struct g_eli_metadata)]; + int error; + + eli_metadata_encode(md, sector); + if (g_get_sectorsize(prov) == 0) { + int fd; + + /* This is a file probably. */ + fd = open(prov, O_WRONLY | O_TRUNC); + if (fd == -1) { + gctl_error(req, "Cannot open %s: %s.", prov, + strerror(errno)); + explicit_bzero(sector, sizeof(sector)); + return (-1); + } + if (write(fd, sector, sizeof(sector)) != sizeof(sector)) { + gctl_error(req, "Cannot write metadata to %s: %s.", + prov, strerror(errno)); + explicit_bzero(sector, sizeof(sector)); + close(fd); + return (-1); + } + close(fd); + } else { + /* This is a GEOM provider. */ + error = g_metadata_store(prov, sector, sizeof(sector)); + if (error != 0) { + gctl_error(req, "Cannot write metadata to %s: %s.", + prov, strerror(errno)); + explicit_bzero(sector, sizeof(sector)); + return (-1); + } + } + explicit_bzero(sector, sizeof(sector)); + return (0); +} + +static void +eli_init(struct gctl_req *req) +{ + struct g_eli_metadata md; + struct gctl_req *r; + unsigned char sector[sizeof(struct g_eli_metadata)] __aligned(4); + unsigned char key[G_ELI_USERKEYLEN]; + char backfile[MAXPATHLEN]; + const char *str, *prov; + unsigned int secsize, eli_version; + off_t mediasize; + intmax_t val; + int error, i, nargs, nparams, param; + const int one = 1; + struct hmac_ctx ctxtemplate; + + nargs = gctl_get_int(req, "nargs"); + if (nargs <= 0) { + gctl_error(req, "Too few arguments."); + return; + } + + /* Start generating metadata for provider(s) being initialized. */ + explicit_bzero(&md, sizeof(md)); + strlcpy(md.md_magic, G_ELI_MAGIC, sizeof(md.md_magic)); + val = gctl_get_intmax(req, "mdversion"); + if (val == -1) { + eli_version = G_ELI_VERSION; + } else if (val < 0 || val > G_ELI_VERSION) { + gctl_error(req, + "Invalid version specified should be between %u and %u.", + G_ELI_VERSION_00, G_ELI_VERSION); + return; + } else { + eli_version = val; + } + md.md_version = eli_version; + md.md_flags = G_ELI_FLAG_AUTORESIZE; + if (gctl_get_int(req, "boot")) + md.md_flags |= G_ELI_FLAG_BOOT; + if (gctl_get_int(req, "geliboot")) + md.md_flags |= G_ELI_FLAG_GELIBOOT; + if (gctl_get_int(req, "displaypass")) + md.md_flags |= G_ELI_FLAG_GELIDISPLAYPASS; + if (gctl_get_int(req, "notrim")) + md.md_flags |= G_ELI_FLAG_NODELETE; + if (gctl_get_int(req, "noautoresize")) + md.md_flags &= ~G_ELI_FLAG_AUTORESIZE; + md.md_ealgo = CRYPTO_ALGORITHM_MIN - 1; + str = gctl_get_ascii(req, "aalgo"); + if (*str != '\0') { + if (eli_version < G_ELI_VERSION_01) { + gctl_error(req, + "Data authentication is supported starting from version %u.", + G_ELI_VERSION_01); + return; + } + md.md_aalgo = g_eli_str2aalgo(str); + if (md.md_aalgo >= CRYPTO_ALGORITHM_MIN && + md.md_aalgo <= CRYPTO_ALGORITHM_MAX) { + md.md_flags |= G_ELI_FLAG_AUTH; + } else { + /* + * For backward compatibility, check if the -a option + * was used to provide encryption algorithm. + */ + md.md_ealgo = g_eli_str2ealgo(str); + if (md.md_ealgo < CRYPTO_ALGORITHM_MIN || + md.md_ealgo > CRYPTO_ALGORITHM_MAX) { + gctl_error(req, + "Invalid authentication algorithm."); + return; + } else { + fprintf(stderr, "warning: The -e option, not " + "the -a option is now used to specify " + "encryption algorithm to use.\n"); + } + } + } + if (md.md_ealgo < CRYPTO_ALGORITHM_MIN || + md.md_ealgo > CRYPTO_ALGORITHM_MAX) { + str = gctl_get_ascii(req, "ealgo"); + if (*str == '\0') { + if (eli_version < G_ELI_VERSION_05) + str = "aes-cbc"; + else + str = GELI_ENC_ALGO; + } + md.md_ealgo = g_eli_str2ealgo(str); + if (md.md_ealgo < CRYPTO_ALGORITHM_MIN || + md.md_ealgo > CRYPTO_ALGORITHM_MAX) { + gctl_error(req, "Invalid encryption algorithm."); + return; + } + if (md.md_ealgo == CRYPTO_CAMELLIA_CBC && + eli_version < G_ELI_VERSION_04) { + gctl_error(req, + "Camellia-CBC algorithm is supported starting from version %u.", + G_ELI_VERSION_04); + return; + } + if (md.md_ealgo == CRYPTO_AES_XTS && + eli_version < G_ELI_VERSION_05) { + gctl_error(req, + "AES-XTS algorithm is supported starting from version %u.", + G_ELI_VERSION_05); + return; + } + } + val = gctl_get_intmax(req, "keylen"); + md.md_keylen = val; + md.md_keylen = g_eli_keylen(md.md_ealgo, md.md_keylen); + if (md.md_keylen == 0) { + gctl_error(req, "Invalid key length."); + return; + } + + val = gctl_get_intmax(req, "iterations"); + if (val != -1) { + int nonewpassphrase; + + /* + * Don't allow to set iterations when there will be no + * passphrase. + */ + nonewpassphrase = gctl_get_int(req, "nonewpassphrase"); + if (nonewpassphrase) { + gctl_error(req, + "Options -i and -P are mutually exclusive."); + return; + } + } + md.md_iterations = val; + + val = gctl_get_intmax(req, "sectorsize"); + if (val > sysconf(_SC_PAGE_SIZE)) { + fprintf(stderr, + "warning: Using sectorsize bigger than the page size!\n"); + } + + md.md_keys = 0x01; + + /* + * Determine number of parameters in the parent geom request before the + * nargs parameter and list of providers. + */ + nparams = req->narg - nargs - 1; + + /* Generate HMAC context template. */ + if (!eli_init_key_hmac_ctx(req, &ctxtemplate, true)) + return; + + /* Create new child request for each provider and issue to kernel */ + for (i = 0; i < nargs; i++) { + r = gctl_get_handle(); + + /* Copy each parameter from the parent request to the child */ + for (param = 0; param < nparams; param++) { + gctl_ro_param(r, req->arg[param].name, + req->arg[param].len, req->arg[param].value); + } + + /* Add a single provider to the parameter list of the child */ + gctl_ro_param(r, "nargs", sizeof(one), &one); + prov = gctl_get_ascii(req, "arg%d", i); + gctl_ro_param(r, "arg0", -1, prov); + + mediasize = g_get_mediasize(prov); + secsize = g_get_sectorsize(prov); + if (mediasize == 0 || secsize == 0) { + gctl_error(r, "Cannot get information about %s: %s.", + prov, strerror(errno)); + goto out; + } + + md.md_provsize = mediasize; + + val = gctl_get_intmax(r, "sectorsize"); + if (val == 0) { + md.md_sectorsize = secsize; + } else { + if (val < 0 || (val % secsize) != 0 || !powerof2(val)) { + gctl_error(r, "Invalid sector size."); + goto out; + } + md.md_sectorsize = val; + } + + /* Use different salt and Master Key for each provider. */ + arc4random_buf(md.md_salt, sizeof(md.md_salt)); + arc4random_buf(md.md_mkeys, sizeof(md.md_mkeys)); + + /* Generate user key. */ + if (eli_genkey(r, &ctxtemplate, &md, key, true) == NULL) { + /* + * Error generating key - details added to geom request + * by eli_genkey(). + */ + goto out; + } + + /* Encrypt the first and the only Master Key. */ + error = g_eli_mkey_encrypt(md.md_ealgo, key, md.md_keylen, + md.md_mkeys); + if (error != 0) { + gctl_error(r, "Cannot encrypt Master Key: %s.", + strerror(error)); + goto out; + } + + /* Convert metadata to on-disk format. */ + eli_metadata_encode(&md, sector); + + /* Store metadata to disk. */ + error = g_metadata_store(prov, sector, sizeof(sector)); + if (error != 0) { + gctl_error(r, "Cannot store metadata on %s: %s.", prov, + strerror(error)); + goto out; + } + if (verbose) + printf("Metadata value stored on %s.\n", prov); + + /* Backup metadata to a file. */ + const char *p = prov; + unsigned int j; + + /* + * Check if provider string includes the devfs mountpoint + * (typically /dev/). + */ + if (strncmp(p, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) { + /* Skip forward to the device filename only. */ + p += sizeof(_PATH_DEV) - 1; + } + + str = gctl_get_ascii(r, "backupfile"); + if (str[0] != '\0') { + /* Backupfile given by the user, just copy it. */ + strlcpy(backfile, str, sizeof(backfile)); + + /* If multiple providers have been initialized in one + * command, and the backup filename has been specified + * as anything other than "none", make the backup + * filename unique for each provider. */ + if (nargs > 1 && strcmp(backfile, "none") != 0) { + /* + * Replace first occurrence of "PROV" with + * provider name. + */ + str = strnstr(backfile, "PROV", + sizeof(backfile)); + if (str != NULL) { + char suffix[MAXPATHLEN]; + j = str - backfile; + strlcpy(suffix, &backfile[j+4], + sizeof(suffix)); + backfile[j] = '\0'; + strlcat(backfile, p, sizeof(backfile)); + strlcat(backfile, suffix, + sizeof(backfile)); + } else { + /* + * "PROV" not found in backfile, append + * provider name. + */ + strlcat(backfile, "-", + sizeof(backfile)); + strlcat(backfile, p, sizeof(backfile)); + } + } + } else { + /* Generate filename automatically. */ + snprintf(backfile, sizeof(backfile), "%s%s.eli", + GELI_BACKUP_DIR, p); + /* Replace all / with _. */ + for (j = strlen(GELI_BACKUP_DIR); backfile[j] != '\0'; + j++) { + if (backfile[j] == '/') + backfile[j] = '_'; + } + } + if (strcmp(backfile, "none") != 0 && + eli_backup_create(r, prov, backfile) == 0) { + printf("\nMetadata backup for provider %s can be found " + "in %s\n", prov, backfile); + printf("and can be restored with the following " + "command:\n"); + printf("\n\t# geli restore %s %s\n\n", backfile, prov); + } + +out: + /* + * Print error for this request, and set parent request error + * message. + */ + if (r->error != NULL && r->error[0] != '\0') { + warnx("%s", r->error); + gctl_error(req, "There was an error with at least one " + "provider."); + } + + gctl_free(r); + + /* + * Erase sensitive and provider specific data from memory. + */ + explicit_bzero(key, sizeof(key)); + explicit_bzero(sector, sizeof(sector)); + explicit_bzero(&md.md_provsize, sizeof(md.md_provsize)); + explicit_bzero(&md.md_sectorsize, sizeof(md.md_sectorsize)); + explicit_bzero(&md.md_salt, sizeof(md.md_salt)); + explicit_bzero(&md.md_mkeys, sizeof(md.md_mkeys)); + } + + /* Clear the cached metadata, including keys. */ + explicit_bzero(&md, sizeof(md)); + explicit_bzero(&ctxtemplate, sizeof(ctxtemplate)); +} + +static void +eli_attach(struct gctl_req *req) +{ + struct g_eli_metadata md; + struct gctl_req *r; + const char *prov; + off_t mediasize; + int i, nargs, nparams, param; + const int one = 1; + struct hmac_ctx ctxtemplate; + + nargs = gctl_get_int(req, "nargs"); + if (nargs <= 0) { + gctl_error(req, "Too few arguments."); + return; + } + + unsigned char key[G_ELI_USERKEYLEN]; + + /* + * Determine number of parameters in the parent geom request before the + * nargs parameter and list of providers. + */ + nparams = req->narg - nargs - 1; + + /* Generate HMAC context template. */ + if (!eli_init_key_hmac_ctx(req, &ctxtemplate, false)) + return; + + /* Create new child request for each provider and issue to kernel */ + for (i = 0; i < nargs; i++) { + r = gctl_get_handle(); + + /* Copy each parameter from the parent request to the child */ + for (param = 0; param < nparams; param++) { + gctl_ro_param(r, req->arg[param].name, + req->arg[param].len, req->arg[param].value); + } + + /* Add a single provider to the parameter list of the child */ + gctl_ro_param(r, "nargs", sizeof(one), &one); + prov = gctl_get_ascii(req, "arg%d", i); + gctl_ro_param(r, "arg0", -1, prov); + + if (eli_metadata_read(r, prov, &md) == -1) { + /* + * Error reading metadata - details added to geom + * request by eli_metadata_read(). + */ + goto out; + } + + mediasize = g_get_mediasize(prov); + if (md.md_provsize != (uint64_t)mediasize) { + gctl_error(r, "Provider size mismatch."); + goto out; + } + + if (eli_genkey(r, &ctxtemplate, &md, key, false) == NULL) { + /* + * Error generating key - details added to geom request + * by eli_genkey(). + */ + goto out; + } + + gctl_ro_param(r, "key", sizeof(key), key); + + if (gctl_issue(r) == NULL) { + if (verbose) + printf("Attached to %s.\n", prov); + } + +out: + /* + * Print error for this request, and set parent request error + * message. + */ + if (r->error != NULL && r->error[0] != '\0') { + warnx("%s", r->error); + gctl_error(req, "There was an error with at least one " + "provider."); + } + + gctl_free(r); + + /* Clear sensitive data from memory. */ + explicit_bzero(key, sizeof(key)); + } + + /* Clear sensitive data from memory. */ + explicit_bzero(cached_passphrase, sizeof(cached_passphrase)); + explicit_bzero(&ctxtemplate, sizeof(ctxtemplate)); +} + +static void +eli_configure_detached(struct gctl_req *req, const char *prov, int boot, + int geliboot, int displaypass, int trim, int autoresize) +{ + struct g_eli_metadata md; + bool changed = 0; + + if (eli_metadata_read(req, prov, &md) == -1) + return; + + if (boot == 1 && (md.md_flags & G_ELI_FLAG_BOOT)) { + if (verbose) + printf("BOOT flag already configured for %s.\n", prov); + } else if (boot == 0 && !(md.md_flags & G_ELI_FLAG_BOOT)) { + if (verbose) + printf("BOOT flag not configured for %s.\n", prov); + } else if (boot >= 0) { + if (boot) + md.md_flags |= G_ELI_FLAG_BOOT; + else + md.md_flags &= ~G_ELI_FLAG_BOOT; + changed = 1; + } + + if (geliboot == 1 && (md.md_flags & G_ELI_FLAG_GELIBOOT)) { + if (verbose) + printf("GELIBOOT flag already configured for %s.\n", prov); + } else if (geliboot == 0 && !(md.md_flags & G_ELI_FLAG_GELIBOOT)) { + if (verbose) + printf("GELIBOOT flag not configured for %s.\n", prov); + } else if (geliboot >= 0) { + if (geliboot) + md.md_flags |= G_ELI_FLAG_GELIBOOT; + else + md.md_flags &= ~G_ELI_FLAG_GELIBOOT; + changed = 1; + } + + if (displaypass == 1 && (md.md_flags & G_ELI_FLAG_GELIDISPLAYPASS)) { + if (verbose) + printf("GELIDISPLAYPASS flag already configured for %s.\n", prov); + } else if (displaypass == 0 && + !(md.md_flags & G_ELI_FLAG_GELIDISPLAYPASS)) { + if (verbose) + printf("GELIDISPLAYPASS flag not configured for %s.\n", prov); + } else if (displaypass >= 0) { + if (displaypass) + md.md_flags |= G_ELI_FLAG_GELIDISPLAYPASS; + else + md.md_flags &= ~G_ELI_FLAG_GELIDISPLAYPASS; + changed = 1; + } + + if (trim == 0 && (md.md_flags & G_ELI_FLAG_NODELETE)) { + if (verbose) + printf("TRIM disable flag already configured for %s.\n", prov); + } else if (trim == 1 && !(md.md_flags & G_ELI_FLAG_NODELETE)) { + if (verbose) + printf("TRIM disable flag not configured for %s.\n", prov); + } else if (trim >= 0) { + if (trim) + md.md_flags &= ~G_ELI_FLAG_NODELETE; + else + md.md_flags |= G_ELI_FLAG_NODELETE; + changed = 1; + } + + if (autoresize == 1 && (md.md_flags & G_ELI_FLAG_AUTORESIZE)) { + if (verbose) + printf("AUTORESIZE flag already configured for %s.\n", prov); + } else if (autoresize == 0 && !(md.md_flags & G_ELI_FLAG_AUTORESIZE)) { + if (verbose) + printf("AUTORESIZE flag not configured for %s.\n", prov); + } else if (autoresize >= 0) { + if (autoresize) + md.md_flags |= G_ELI_FLAG_AUTORESIZE; + else + md.md_flags &= ~G_ELI_FLAG_AUTORESIZE; + changed = 1; + } + + if (changed) + eli_metadata_store(req, prov, &md); + explicit_bzero(&md, sizeof(md)); +} + +static void +eli_configure(struct gctl_req *req) +{ + const char *prov; + bool boot, noboot, geliboot, nogeliboot, displaypass, nodisplaypass; + bool autoresize, noautoresize, trim, notrim; + int doboot, dogeliboot, dodisplaypass, dotrim, doautoresize; + int i, nargs; + + nargs = gctl_get_int(req, "nargs"); + if (nargs == 0) { + gctl_error(req, "Too few arguments."); + return; + } + + boot = gctl_get_int(req, "boot"); + noboot = gctl_get_int(req, "noboot"); + geliboot = gctl_get_int(req, "geliboot"); + nogeliboot = gctl_get_int(req, "nogeliboot"); + displaypass = gctl_get_int(req, "displaypass"); + nodisplaypass = gctl_get_int(req, "nodisplaypass"); + trim = gctl_get_int(req, "trim"); + notrim = gctl_get_int(req, "notrim"); + autoresize = gctl_get_int(req, "autoresize"); + noautoresize = gctl_get_int(req, "noautoresize"); + + doboot = -1; + if (boot && noboot) { + gctl_error(req, "Options -b and -B are mutually exclusive."); + return; + } + if (boot) + doboot = 1; + else if (noboot) + doboot = 0; + + dogeliboot = -1; + if (geliboot && nogeliboot) { + gctl_error(req, "Options -g and -G are mutually exclusive."); + return; + } + if (geliboot) + dogeliboot = 1; + else if (nogeliboot) + dogeliboot = 0; + + dodisplaypass = -1; + if (displaypass && nodisplaypass) { + gctl_error(req, "Options -d and -D are mutually exclusive."); + return; + } + if (displaypass) + dodisplaypass = 1; + else if (nodisplaypass) + dodisplaypass = 0; + + dotrim = -1; + if (trim && notrim) { + gctl_error(req, "Options -t and -T are mutually exclusive."); + return; + } + if (trim) + dotrim = 1; + else if (notrim) + dotrim = 0; + + doautoresize = -1; + if (autoresize && noautoresize) { + gctl_error(req, "Options -r and -R are mutually exclusive."); + return; + } + if (autoresize) + doautoresize = 1; + else if (noautoresize) + doautoresize = 0; + + if (doboot == -1 && dogeliboot == -1 && dodisplaypass == -1 && + dotrim == -1 && doautoresize == -1) { + gctl_error(req, "No option given."); + return; + } + + /* First attached providers. */ + gctl_issue(req); + /* Now the rest. */ + for (i = 0; i < nargs; i++) { + prov = gctl_get_ascii(req, "arg%d", i); + if (!eli_is_attached(prov)) { + eli_configure_detached(req, prov, doboot, dogeliboot, + dodisplaypass, dotrim, doautoresize); + } + } +} + +static void +eli_setkey_attached(struct gctl_req *req, struct g_eli_metadata *md) +{ + unsigned char key[G_ELI_USERKEYLEN]; + intmax_t val, old = 0; + int error; + + val = gctl_get_intmax(req, "iterations"); + /* Check if iterations number should be changed. */ + if (val != -1) + md->md_iterations = val; + else + old = md->md_iterations; + + /* Generate key for Master Key encryption. */ + if (eli_genkey_single(req, md, key, true) == NULL) { + explicit_bzero(key, sizeof(key)); + return; + } + /* + * If number of iterations has changed, but wasn't given as a + * command-line argument, update the request. + */ + if (val == -1 && md->md_iterations != old) { + error = gctl_change_param(req, "iterations", sizeof(intmax_t), + &md->md_iterations); + assert(error == 0); + } + + gctl_ro_param(req, "key", sizeof(key), key); + gctl_issue(req); + explicit_bzero(key, sizeof(key)); +} + +static void +eli_setkey_detached(struct gctl_req *req, const char *prov, + struct g_eli_metadata *md) +{ + unsigned char key[G_ELI_USERKEYLEN], mkey[G_ELI_DATAIVKEYLEN]; + unsigned char *mkeydst; + unsigned int nkey; + intmax_t val; + int error; + + if (md->md_keys == 0) { + gctl_error(req, "No valid keys on %s.", prov); + return; + } + + /* Generate key for Master Key decryption. */ + if (eli_genkey_single(req, md, key, false) == NULL) { + explicit_bzero(key, sizeof(key)); + return; + } + + /* Decrypt Master Key. */ + error = g_eli_mkey_decrypt_any(md, key, mkey, &nkey); + explicit_bzero(key, sizeof(key)); + if (error != 0) { + explicit_bzero(md, sizeof(*md)); + if (error == -1) + gctl_error(req, "Wrong key for %s.", prov); + else /* if (error > 0) */ { + gctl_error(req, "Cannot decrypt Master Key: %s.", + strerror(error)); + } + return; + } + if (verbose) + printf("Decrypted Master Key %u.\n", nkey); + + val = gctl_get_intmax(req, "keyno"); + if (val != -1) + nkey = val; +#if 0 + else + ; /* Use the key number which was found during decryption. */ +#endif + if (nkey >= G_ELI_MAXMKEYS) { + gctl_error(req, "Invalid '%s' argument.", "keyno"); + return; + } + + val = gctl_get_intmax(req, "iterations"); + /* Check if iterations number should and can be changed. */ + if (val != -1 && md->md_iterations == -1) { + md->md_iterations = val; + } else if (val != -1 && val != md->md_iterations) { + if (bitcount32(md->md_keys) != 1) { + gctl_error(req, "To be able to use '-i' option, only " + "one key can be defined."); + return; + } + if (md->md_keys != (1 << nkey)) { + gctl_error(req, "Only already defined key can be " + "changed when '-i' option is used."); + return; + } + md->md_iterations = val; + } + + mkeydst = md->md_mkeys + nkey * G_ELI_MKEYLEN; + md->md_keys |= (1 << nkey); + + bcopy(mkey, mkeydst, sizeof(mkey)); + explicit_bzero(mkey, sizeof(mkey)); + + /* + * The previous eli_genkey() set cached_passphrase, we do not want to + * use that for the new passphrase so always prompt for it + */ + explicit_bzero(cached_passphrase, sizeof(cached_passphrase)); + + /* Generate key for Master Key encryption. */ + if (eli_genkey_single(req, md, key, true) == NULL) { + explicit_bzero(key, sizeof(key)); + explicit_bzero(md, sizeof(*md)); + return; + } + + /* Encrypt the Master-Key with the new key. */ + error = g_eli_mkey_encrypt(md->md_ealgo, key, md->md_keylen, mkeydst); + explicit_bzero(key, sizeof(key)); + if (error != 0) { + explicit_bzero(md, sizeof(*md)); + gctl_error(req, "Cannot encrypt Master Key: %s.", + strerror(error)); + return; + } + + /* Store metadata with fresh key. */ + eli_metadata_store(req, prov, md); + explicit_bzero(md, sizeof(*md)); +} + +static void +eli_setkey(struct gctl_req *req) +{ + struct g_eli_metadata md; + const char *prov; + int nargs; + + nargs = gctl_get_int(req, "nargs"); + if (nargs != 1) { + gctl_error(req, "Invalid number of arguments."); + return; + } + prov = gctl_get_ascii(req, "arg0"); + + if (eli_metadata_read(req, prov, &md) == -1) + return; + + if (eli_is_attached(prov)) + eli_setkey_attached(req, &md); + else + eli_setkey_detached(req, prov, &md); + + if (req->error == NULL || req->error[0] == '\0') { + printf("Note, that the master key encrypted with old keys " + "and/or passphrase may still exist in a metadata backup " + "file.\n"); + } +} + +static void +eli_delkey_attached(struct gctl_req *req, const char *prov __unused) +{ + + gctl_issue(req); +} + +static void +eli_delkey_detached(struct gctl_req *req, const char *prov) +{ + struct g_eli_metadata md; + unsigned char *mkeydst; + unsigned int nkey; + intmax_t val; + bool all, force; + + if (eli_metadata_read(req, prov, &md) == -1) + return; + + all = gctl_get_int(req, "all"); + if (all) + arc4random_buf(md.md_mkeys, sizeof(md.md_mkeys)); + else { + force = gctl_get_int(req, "force"); + val = gctl_get_intmax(req, "keyno"); + if (val == -1) { + gctl_error(req, "Key number has to be specified."); + return; + } + nkey = val; + if (nkey >= G_ELI_MAXMKEYS) { + gctl_error(req, "Invalid '%s' argument.", "keyno"); + return; + } + if (!(md.md_keys & (1 << nkey)) && !force) { + gctl_error(req, "Master Key %u is not set.", nkey); + return; + } + md.md_keys &= ~(1 << nkey); + if (md.md_keys == 0 && !force) { + gctl_error(req, "This is the last Master Key. Use '-f' " + "option if you really want to remove it."); + return; + } + mkeydst = md.md_mkeys + nkey * G_ELI_MKEYLEN; + arc4random_buf(mkeydst, G_ELI_MKEYLEN); + } + + eli_metadata_store(req, prov, &md); + explicit_bzero(&md, sizeof(md)); +} + +static void +eli_delkey(struct gctl_req *req) +{ + const char *prov; + int nargs; + + nargs = gctl_get_int(req, "nargs"); + if (nargs != 1) { + gctl_error(req, "Invalid number of arguments."); + return; + } + prov = gctl_get_ascii(req, "arg0"); + + if (eli_is_attached(prov)) + eli_delkey_attached(req, prov); + else + eli_delkey_detached(req, prov); +} + +static void +eli_resume(struct gctl_req *req) +{ + struct g_eli_metadata md; + unsigned char key[G_ELI_USERKEYLEN]; + const char *prov; + off_t mediasize; + int nargs; + + nargs = gctl_get_int(req, "nargs"); + if (nargs != 1) { + gctl_error(req, "Invalid number of arguments."); + return; + } + prov = gctl_get_ascii(req, "arg0"); + + if (eli_metadata_read(req, prov, &md) == -1) + return; + + mediasize = g_get_mediasize(prov); + if (md.md_provsize != (uint64_t)mediasize) { + gctl_error(req, "Provider size mismatch."); + return; + } + + if (eli_genkey_single(req, &md, key, false) == NULL) { + explicit_bzero(key, sizeof(key)); + return; + } + + gctl_ro_param(req, "key", sizeof(key), key); + if (gctl_issue(req) == NULL) { + if (verbose) + printf("Resumed %s.\n", prov); + } + explicit_bzero(key, sizeof(key)); +} + +static int +eli_trash_metadata(struct gctl_req *req, const char *prov, int fd, off_t offset) +{ + unsigned int overwrites; + unsigned char *sector; + ssize_t size; + int error; + + size = sizeof(overwrites); + if (sysctlbyname("kern.geom.eli.overwrites", &overwrites, &size, + NULL, 0) == -1 || overwrites == 0) { + overwrites = G_ELI_OVERWRITES; + } + + size = g_sectorsize(fd); + if (size <= 0) { + gctl_error(req, "Cannot obtain provider sector size %s: %s.", + prov, strerror(errno)); + return (-1); + } + sector = malloc(size); + if (sector == NULL) { + gctl_error(req, "Cannot allocate %zd bytes of memory.", size); + return (-1); + } + + error = 0; + do { + arc4random_buf(sector, size); + if (pwrite(fd, sector, size, offset) != size) { + if (error == 0) + error = errno; + } + (void)g_flush(fd); + } while (--overwrites > 0); + free(sector); + if (error != 0) { + gctl_error(req, "Cannot trash metadata on provider %s: %s.", + prov, strerror(error)); + return (-1); + } + return (0); +} + +static void +eli_kill_detached(struct gctl_req *req, const char *prov) +{ + off_t offset; + int fd; + + /* + * NOTE: Maybe we should verify if this is geli provider first, + * but 'kill' command is quite critical so better don't waste + * the time. + */ +#if 0 + error = g_metadata_read(prov, (unsigned char *)&md, sizeof(md), + G_ELI_MAGIC); + if (error != 0) { + gctl_error(req, "Cannot read metadata from %s: %s.", prov, + strerror(error)); + return; + } +#endif + + fd = g_open(prov, 1); + if (fd == -1) { + gctl_error(req, "Cannot open provider %s: %s.", prov, + strerror(errno)); + return; + } + offset = g_mediasize(fd) - g_sectorsize(fd); + if (offset <= 0) { + gctl_error(req, + "Cannot obtain media size or sector size for provider %s: %s.", + prov, strerror(errno)); + (void)g_close(fd); + return; + } + (void)eli_trash_metadata(req, prov, fd, offset); + (void)g_close(fd); +} + +static void +eli_kill(struct gctl_req *req) +{ + const char *prov; + int i, nargs, all; + + nargs = gctl_get_int(req, "nargs"); + all = gctl_get_int(req, "all"); + if (!all && nargs == 0) { + gctl_error(req, "Too few arguments."); + return; + } + /* + * How '-a' option combine with a list of providers: + * Delete Master Keys from all attached providers: + * geli kill -a + * Delete Master Keys from all attached providers and from + * detached da0 and da1: + * geli kill -a da0 da1 + * Delete Master Keys from (attached or detached) da0 and da1: + * geli kill da0 da1 + */ + + /* First detached providers. */ + for (i = 0; i < nargs; i++) { + prov = gctl_get_ascii(req, "arg%d", i); + if (!eli_is_attached(prov)) + eli_kill_detached(req, prov); + } + /* Now attached providers. */ + gctl_issue(req); +} + +static int +eli_backup_create(struct gctl_req *req, const char *prov, const char *file) +{ + unsigned char *sector; + ssize_t secsize; + int error, filefd, ret; + + ret = -1; + filefd = -1; + sector = NULL; + secsize = 0; + + secsize = g_get_sectorsize(prov); + if (secsize == 0) { + gctl_error(req, "Cannot get informations about %s: %s.", prov, + strerror(errno)); + goto out; + } + sector = malloc(secsize); + if (sector == NULL) { + gctl_error(req, "Cannot allocate memory."); + goto out; + } + /* Read metadata from the provider. */ + error = g_metadata_read(prov, sector, secsize, G_ELI_MAGIC); + if (error != 0) { + gctl_error(req, "Unable to read metadata from %s: %s.", prov, + strerror(error)); + goto out; + } + + filefd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0600); + if (filefd == -1) { + gctl_error(req, "Unable to open %s: %s.", file, + strerror(errno)); + goto out; + } + /* Write metadata to the destination file. */ + if (write(filefd, sector, secsize) != secsize) { + gctl_error(req, "Unable to write to %s: %s.", file, + strerror(errno)); + (void)close(filefd); + (void)unlink(file); + goto out; + } + (void)fsync(filefd); + (void)close(filefd); + /* Success. */ + ret = 0; +out: + if (sector != NULL) { + explicit_bzero(sector, secsize); + free(sector); + } + return (ret); +} + +static void +eli_backup(struct gctl_req *req) +{ + const char *file, *prov; + int nargs; + + nargs = gctl_get_int(req, "nargs"); + if (nargs != 2) { + gctl_error(req, "Invalid number of arguments."); + return; + } + prov = gctl_get_ascii(req, "arg0"); + file = gctl_get_ascii(req, "arg1"); + + eli_backup_create(req, prov, file); +} + +static void +eli_restore(struct gctl_req *req) +{ + struct g_eli_metadata md; + const char *file, *prov; + off_t mediasize; + int nargs; + + nargs = gctl_get_int(req, "nargs"); + if (nargs != 2) { + gctl_error(req, "Invalid number of arguments."); + return; + } + file = gctl_get_ascii(req, "arg0"); + prov = gctl_get_ascii(req, "arg1"); + + /* Read metadata from the backup file. */ + if (eli_metadata_read(req, file, &md) == -1) + return; + /* Obtain provider's mediasize. */ + mediasize = g_get_mediasize(prov); + if (mediasize == 0) { + gctl_error(req, "Cannot get informations about %s: %s.", prov, + strerror(errno)); + return; + } + /* Check if the provider size has changed since we did the backup. */ + if (md.md_provsize != (uint64_t)mediasize) { + if (gctl_get_int(req, "force")) { + md.md_provsize = mediasize; + } else { + gctl_error(req, "Provider size mismatch: " + "wrong backup file?"); + return; + } + } + /* Write metadata to the provider. */ + (void)eli_metadata_store(req, prov, &md); +} + +static void +eli_resize(struct gctl_req *req) +{ + struct g_eli_metadata md; + const char *prov; + unsigned char *sector; + ssize_t secsize; + off_t mediasize, oldsize; + int error, nargs, provfd; + + nargs = gctl_get_int(req, "nargs"); + if (nargs != 1) { + gctl_error(req, "Invalid number of arguments."); + return; + } + prov = gctl_get_ascii(req, "arg0"); + + provfd = -1; + sector = NULL; + secsize = 0; + + provfd = g_open(prov, 1); + if (provfd == -1) { + gctl_error(req, "Cannot open %s: %s.", prov, strerror(errno)); + goto out; + } + + mediasize = g_mediasize(provfd); + secsize = g_sectorsize(provfd); + if (mediasize == -1 || secsize == -1) { + gctl_error(req, "Cannot get information about %s: %s.", prov, + strerror(errno)); + goto out; + } + + sector = malloc(secsize); + if (sector == NULL) { + gctl_error(req, "Cannot allocate memory."); + goto out; + } + + oldsize = gctl_get_intmax(req, "oldsize"); + if (oldsize < 0 || oldsize > mediasize) { + gctl_error(req, "Invalid oldsize: Out of range."); + goto out; + } + + /* Read metadata from the 'oldsize' offset. */ + if (pread(provfd, sector, secsize, oldsize - secsize) != secsize) { + gctl_error(req, "Cannot read old metadata: %s.", + strerror(errno)); + goto out; + } + + /* Check if this sector contains geli metadata. */ + error = eli_metadata_decode(sector, &md); + switch (error) { + case 0: + break; + case EOPNOTSUPP: + gctl_error(req, + "Provider's %s metadata version %u is too new.\n" + "geli: The highest supported version is %u.", + prov, (unsigned int)md.md_version, G_ELI_VERSION); + goto out; + case EINVAL: + gctl_error(req, "Inconsistent provider's %s metadata.", prov); + goto out; + default: + gctl_error(req, + "Unexpected error while decoding provider's %s metadata: %s.", + prov, strerror(error)); + goto out; + } + + /* + * If the old metadata doesn't have a correct provider size, refuse + * to resize. + */ + if (md.md_provsize != (uint64_t)oldsize) { + gctl_error(req, "Provider size mismatch at oldsize."); + goto out; + } + + /* The metadata is valid and nothing has changed. Just exit. */ + if (oldsize == mediasize) + goto out; + + /* + * Update the old metadata with the current provider size and write + * it back to the correct place on the provider. + */ + md.md_provsize = mediasize; + /* Write metadata to the provider. */ + (void)eli_metadata_store(req, prov, &md); + /* Now trash the old metadata. */ + (void)eli_trash_metadata(req, prov, provfd, oldsize - secsize); +out: + if (provfd != -1) + (void)g_close(provfd); + if (sector != NULL) { + explicit_bzero(sector, secsize); + free(sector); + } +} + +static void +eli_version(struct gctl_req *req) +{ + struct g_eli_metadata md; + const char *name; + unsigned int eli_version; + int error, i, nargs; + + nargs = gctl_get_int(req, "nargs"); + + if (nargs == 0) { + unsigned int kernver; + ssize_t size; + + size = sizeof(kernver); + if (sysctlbyname("kern.geom.eli.version", &kernver, &size, + NULL, 0) == -1) { + warn("Unable to obtain GELI kernel version"); + } else { + printf("kernel: %u\n", kernver); + } + printf("userland: %u\n", G_ELI_VERSION); + return; + } + + for (i = 0; i < nargs; i++) { + name = gctl_get_ascii(req, "arg%d", i); + error = g_metadata_read(name, (unsigned char *)&md, + sizeof(md), G_ELI_MAGIC); + if (error != 0) { + warn("%s: Unable to read metadata: %s.", name, + strerror(error)); + gctl_error(req, "Not fully done."); + continue; + } + eli_version = le32dec(&md.md_version); + printf("%s: %u\n", name, eli_version); + } +} + +static void +eli_clear(struct gctl_req *req) +{ + const char *name; + int error, i, nargs; + + nargs = gctl_get_int(req, "nargs"); + if (nargs < 1) { + gctl_error(req, "Too few arguments."); + return; + } + + for (i = 0; i < nargs; i++) { + name = gctl_get_ascii(req, "arg%d", i); + error = g_metadata_clear(name, G_ELI_MAGIC); + if (error != 0) { + fprintf(stderr, "Cannot clear metadata on %s: %s.\n", + name, strerror(error)); + gctl_error(req, "Not fully done."); + continue; + } + if (verbose) + printf("Metadata cleared on %s.\n", name); + } +} + +static void +eli_dump(struct gctl_req *req) +{ + struct g_eli_metadata md; + const char *name; + int i, nargs; + + nargs = gctl_get_int(req, "nargs"); + if (nargs < 1) { + gctl_error(req, "Too few arguments."); + return; + } + + for (i = 0; i < nargs; i++) { + name = gctl_get_ascii(req, "arg%d", i); + if (eli_metadata_read(NULL, name, &md) == -1) { + gctl_error(req, "Not fully done."); + continue; + } + printf("Metadata on %s:\n", name); + eli_metadata_dump(&md); + printf("\n"); + } +} |