Index: stable/6/sbin/geom/class/eli/geli.8 =================================================================== --- stable/6/sbin/geom/class/eli/geli.8 (revision 162433) +++ stable/6/sbin/geom/class/eli/geli.8 (revision 162434) @@ -1,635 +1,655 @@ -.\" Copyright (c) 2005 Pawel Jakub Dawidek +.\" Copyright (c) 2005-2006 Pawel Jakub Dawidek .\" 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. .\" .\" $FreeBSD$ .\" -.Dd August 9, 2006 +.Dd September 16, 2006 .Dt GELI 8 .Os .Sh NAME .Nm geli .Nd "control utility for cryptographic GEOM class" .Sh SYNOPSIS To compile GEOM_ELI into your kernel, place the following lines in your kernel configuration file: .Bd -ragged -offset indent .Cd "device crypto" .Cd "options GEOM_ELI" .Ed .Pp Alternately, to load the GEOM_ELI module at boot time, place the following line in your .Xr loader.conf 5 : .Bd -literal -offset indent geom_eli_load="YES" .Ed .Pp Usage of the .Xr geli 8 utility: .Pp .Nm .Cm init .Op Fl bPv .Op Fl a Ar aalgo .Op Fl e Ar ealgo .Op Fl i Ar iterations .Op Fl K Ar newkeyfile .Op Fl l Ar keylen .Op Fl s Ar sectorsize .Ar prov .Nm .Cm label - an alias for .Cm init .Nm .Cm attach .Op Fl dprv .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 d .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 bB +.Ar prov ... +.Nm .Cm setkey .Op Fl pPv .Op Fl i Ar iterations .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 v .Ar file .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 , .Nm Blowfish and .Nm 3DES ) . .It Can optionally perform data authentication (integrity verification) utilizing one of the following algorithms: .Nm HMAC/MD5 , .Nm HMAC/SHA1 , .Nm HMAC/RIPEMD160 , .Nm HMAC/SHA256 , .Nm HMAC/SHA384 or .Nm HMAC/SHA512 . .It Can create a key from a couple of components (user entered passphrase, random bits from a file, etc.). .It Allows to encrypt the root partition - the user will be asked for the passphrase before the root file system is mounted. .It The passphrase of the user is strengthened with: .Rs .%A B. Kaliski .%T "PKCS #5: Password-Based Cryptography Specification, Version 2.0." .%R RFC .%N 2898 .Re .It Allows to use two independent keys (e.g. .Qq "user key" and .Qq "company key" ) . .It It is fast - .Nm performs simple sector-to-sector encryption. .It Allows to backup/restore Master Keys, so when a user has to quickly destroy his keys, it is possible to get the data back by restoring keys from the 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 file systems). .It Allows to attach a provider with a random, one-time key - useful for swap partitions and temporary file systems. .It Allows to verify data integrity (data authentication). .El .Pp The first argument to .Nm indicates an action to be performed: -.Bl -tag -width ".Cm onetime" +.Bl -tag -width ".Cm configure" .It Cm init Initialize provider which needs to be encrypted. Here you can set up the cryptographic algorithm to use, key length, etc. The last provider's sector is used to store metadata. .Pp Additional options include: .Bl -tag -width ".Fl a Ar aalgo" .It Fl a Ar aalgo Enable data integrity verification (authentication) using the given algorithm. This will reduce size of available storage 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/MD5 , .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. .It Fl e Ar ealgo Encryption algorithm to use. Currently supported algorithms are: .Nm AES , .Nm Blowfish and .Nm 3DES . The default is .Nm AES . .It Fl b Ask for the passphrase on 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 i Ar iterations Number of iterations to use with PKCS#5v2. 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. .It Fl K Ar newkeyfile Specifies a file which contains part of the key. If .Ar newkeyfile is given as -, standard input will be used. Here is how more than one file with a key component can be used: .Bd -literal -offset indent # cat key1 key2 key3 | geli init -K - /dev/da0 .Ed .It Fl l Ar keylen Key length to use with the given cryptographic algorithm. If not given, the default key length for the given algorithm is used, which is: 128 for .Nm AES , 128 for .Nm Blowfish and 192 for .Nm 3DES . .It Fl s Ar sectorsize Change decrypted provider's sector size. Increasing sector size allows to increase performance, because we need to generate an IV and do encrypt/decrypt for every single sector - less number of sectors means less work to do. .It Fl P Do not use passphrase as the key component. .El .It Cm attach Attach the given provider. The master key will be decrypted using the given passphrase/keyfile and a new GEOM provider will be created using the given provider's name with an .Qq .eli suffix. .Pp Additional options include: .Bl -tag -width ".Fl a Ar algo" .It Fl d If specified, a decrypted provider will be detached automatically on last close. This can help with short memory - user does not have to remember to detach the provider after unmounting the file system. It only works when the provider was opened for writing, so it will not work if the file system on the provider is mounted read-only. Probably a better choice is the .Fl l option for the .Cm detach subcommand. .It Fl r Attach read-only provider. It will not be opened for writing. .It Fl k Ar keyfile Specifies a file which contains part of the key. For more information see the description of the .Fl K option for the .Cm init subcommand. .It Fl p Do not use passphrase as the key component. .El .It Cm detach Detach the given providers, which means remove the devfs entry and clear the keys from memory. .Pp Additional options include: .Bl -tag -width ".Fl a Ar algo" .It Fl f Force detach - detach even if the provider is open. .It Fl l Mark provider to detach on last close. If this option is specified, the provider will not be detached until it is open, but when it will be closed last time, it will be automatically detached (even if it was only opened for reading). .El .It Cm onetime Attach the given providers with random, one-time keys. The command can be used to encrypt swap partitions or temporary file systems. .Pp Additional options include: .Bl -tag -width ".Fl a Ar aalgo" .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. Note, the option is not usable for temporary file systems as the provider will be detached after creating the file system on it. 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 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. .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. +.El .It Cm setkey Change or setup (if not yet initialized) selected key. There is one master key, which can be encrypted with two independent user keys. With the .Cm init subcommand, only key number 0 is initialized. The key can always be changed: 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 old passphrase/keyfile. .Pp Additional options include: .Bl -tag -width ".Fl a Ar algo" .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 .Cm setkey subcommand, only one key have to be defined and this key has to be changed. .It Fl k Ar keyfile Specifies a file which contains part of the old key. .It Fl K Ar newkeyfile Specifies a file which contains part of the new key. .It Fl n Ar keyno Specifies the number of the key 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 key decrypted with the passphrase/keyfile will be changed. .It Fl p Do not use passphrase as the old key component. .It Fl P Do not use passphrase as the new key component. .El .It Cm delkey Destroy (overwrite with random data) the selected key. If one is destroying keys for an attached provider, the provider will not be detached even if all keys will be destroyed. It can be even rescued with the .Cm setkey subcommand. .Bl -tag -width ".Fl a Ar algo" .It Fl a Destroy all keys (does not need .Fl f option). .It Fl f Force key destruction. This option is needed to destroy the last key. .It Fl n Ar keyno Specifies the key number. 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 in emergency situations. It will destroy all keys on the 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 provider was attached with the +.Fl r +flag, the keys won't be destroyed, only the provider will be detached. .Bl -tag -width ".Fl a Ar algo" .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. .It Cm clear Clear metadata from the given providers. .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 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. All variables can also be set in .Pa /boot/loader.conf . .Bl -tag -width indent .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 should be attached on boot (before the root file system 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 will be overwritten with random values when it is destroyed. After this operation it is filled with zeros. .It Va kern.geom.eli.visible_passphrase : No 0 If set to 1, the passphrase entered on boot (before the root file system is mounted) will be visible. This possibility 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 hardware acceleration is available, only one thread will be started. If set to 0, CPU-bound thread will be started for every active CPU. .It Va kern.geom.eli.batch : No 0 When set to 1, can speed-up crypto operations by using batching. Batching allows to reduce number of interrupts by responding on a group of crypto requests with one interrupt. The crypto card and the driver has to support this feature. .El .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 file system 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 keys: one for your girlfriend and one for you (so there will be no tragedy if she forgets her passphrase): .Bd -literal -offset indent # geli init /dev/da2 Enter new passphrase: (enter your passphrase) Reenter new passphrase: # geli setkey -n 1 /dev/da2 Enter passphrase: (enter your passphrase) Enter new passphrase: (let your girlfriend enter her passphrase ...) Reenter new passphrase: (... twice) .Ed .Pp You are the security-person in your company. Create an encrypted provider for use by the user, but remember that users forget their passphrases, so back Master Key up 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/ad0s1e # geli backup /dev/ad0s1e /mnt/pendrive/backups/`hostname` (use key number 0, so the encrypted Master Key by you will be overwritten) # geli setkey -n 0 -k /mnt/pendrive/keys/`hostname` /dev/ad0s1e (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/ad0s1b bs=1m # geli onetime -d -e 3des ad0s1b # swapon /dev/ad0s1b.eli .Ed .Pp The example below shows how to configure two providers which will be attached on boot (before the root file system is mounted). One of them is using passphrase and three keyfiles and the other is using only a keyfile: .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 # cat /boot/keys/da0.key0 /boot/keys/da0.key1 /boot/keys/da0.key2 | geli init -b -K - 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 those 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 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 .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 or copied from one place on the disk to another, .Nm should be able to detect such a modification. If an attacker can remember the encrypted data, modify them and write them back to the same place, the modification will not be detected. .Nm will not protect your data against replay attacks. .Sh SEE ALSO .Xr crypto 4 , .Xr gbde 4 , .Xr geom 4 , .Xr loader.conf 5 , .Xr gbde 8 , .Xr geom 8 , .Xr crypto 9 .Sh HISTORY The .Nm utility appeared in .Fx 6.0 . .Sh AUTHORS .An Pawel Jakub Dawidek Aq pjd@FreeBSD.org Index: stable/6/sbin/geom/class/eli/geom_eli.c =================================================================== --- stable/6/sbin/geom/class/eli/geom_eli.c (revision 162433) +++ stable/6/sbin/geom/class/eli/geom_eli.c (revision 162434) @@ -1,1164 +1,1230 @@ /*- * Copyright (c) 2004-2006 Pawel Jakub Dawidek * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core/geom.h" #include "misc/subr.h" uint32_t lib_version = G_LIB_VERSION; uint32_t version = G_ELI_VERSION; static char aalgo[] = "none"; static char ealgo[] = "aes"; static intmax_t keylen = 0; static intmax_t keyno = -1; static intmax_t iterations = -1; static intmax_t sectorsize = 0; static char keyfile[] = "", newkeyfile[] = ""; 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_kill(struct gctl_req *req); static void eli_backup(struct gctl_req *req); static void eli_restore(struct gctl_req *req); static void eli_clear(struct gctl_req *req); static void eli_dump(struct gctl_req *req); /* * Available commands: * * init [-bhPv] [-a aalgo] [-e ealgo] [-i iterations] [-l keylen] [-K newkeyfile] prov * label - alias for 'init' * attach [-dprv] [-k keyfile] prov * detach [-fl] prov ... * stop - alias for 'detach' * onetime [-d] [-a aalgo] [-e ealgo] [-l keylen] prov ... + * configure [-bB] prov ... * setkey [-pPv] [-n keyno] [-k keyfile] [-K newkeyfile] prov * delkey [-afv] [-n keyno] prov * kill [-av] [prov ...] * backup [-v] prov file * restore [-v] file prov * clear [-v] prov ... * dump [-v] prov ... */ struct g_command class_commands[] = { { "init", G_FLAG_VERBOSE, eli_main, { { 'a', "aalgo", aalgo, G_TYPE_STRING }, { 'b', "boot", NULL, G_TYPE_NONE }, { 'e', "ealgo", ealgo, G_TYPE_STRING }, { 'i', "iterations", &iterations, G_TYPE_NUMBER }, { 'K', "newkeyfile", newkeyfile, G_TYPE_STRING }, { 'l', "keylen", &keylen, G_TYPE_NUMBER }, { 'P', "nonewpassphrase", NULL, G_TYPE_NONE }, { 's', "sectorsize", §orsize, G_TYPE_NUMBER }, G_OPT_SENTINEL }, "[-bPv] [-a aalgo] [-e ealgo] [-i iterations] [-l keylen] [-K newkeyfile] [-s sectorsize] prov" }, { "label", G_FLAG_VERBOSE, eli_main, { { 'a', "aalgo", aalgo, G_TYPE_STRING }, { 'b', "boot", NULL, G_TYPE_NONE }, { 'e', "ealgo", ealgo, G_TYPE_STRING }, { 'i', "iterations", &iterations, G_TYPE_NUMBER }, { 'K', "newkeyfile", newkeyfile, G_TYPE_STRING }, { 'l', "keylen", &keylen, G_TYPE_NUMBER }, { 'P', "nonewpassphrase", NULL, G_TYPE_NONE }, { 's', "sectorsize", §orsize, G_TYPE_NUMBER }, G_OPT_SENTINEL }, "- an alias for 'init'" }, { "attach", G_FLAG_VERBOSE | G_FLAG_LOADKLD, eli_main, { { 'd', "detach", NULL, G_TYPE_NONE }, { 'k', "keyfile", keyfile, G_TYPE_STRING }, { 'p', "nopassphrase", NULL, G_TYPE_NONE }, { 'r', "readonly", NULL, G_TYPE_NONE }, G_OPT_SENTINEL }, "[-dprv] [-k keyfile] prov" }, { "detach", 0, NULL, { { 'f', "force", NULL, G_TYPE_NONE }, { 'l', "last", NULL, G_TYPE_NONE }, G_OPT_SENTINEL }, "[-fl] prov ..." }, { "stop", 0, NULL, { { 'f', "force", NULL, G_TYPE_NONE }, { 'l', "last", NULL, G_TYPE_NONE }, G_OPT_SENTINEL }, "- an alias for 'detach'" }, { "onetime", G_FLAG_VERBOSE | G_FLAG_LOADKLD, NULL, { { 'a', "aalgo", aalgo, G_TYPE_STRING }, { 'd', "detach", NULL, G_TYPE_NONE }, { 'e', "ealgo", ealgo, G_TYPE_STRING }, { 'l', "keylen", &keylen, G_TYPE_NUMBER }, { 's', "sectorsize", §orsize, G_TYPE_NUMBER }, G_OPT_SENTINEL }, "[-d] [-a aalgo] [-e ealgo] [-l keylen] [-s sectorsize] prov ..." }, + { "configure", G_FLAG_VERBOSE, eli_main, + { + { 'b', "boot", NULL, G_TYPE_NONE }, + { 'B', "noboot", NULL, G_TYPE_NONE }, + G_OPT_SENTINEL + }, + "[-bB] prov ..." + }, { "setkey", G_FLAG_VERBOSE, eli_main, { { 'i', "iterations", &iterations, G_TYPE_NUMBER }, { 'k', "keyfile", keyfile, G_TYPE_STRING }, { 'K', "newkeyfile", newkeyfile, G_TYPE_STRING }, { 'n', "keyno", &keyno, G_TYPE_NUMBER }, { 'p', "nopassphrase", NULL, G_TYPE_NONE }, { 'P', "nonewpassphrase", NULL, G_TYPE_NONE }, G_OPT_SENTINEL }, "[-pPv] [-n keyno] [-i iterations] [-k keyfile] [-K newkeyfile] prov" }, { "delkey", G_FLAG_VERBOSE, eli_main, { { 'a', "all", NULL, G_TYPE_NONE }, { 'f', "force", NULL, G_TYPE_NONE }, { 'n', "keyno", &keyno, G_TYPE_NUMBER }, G_OPT_SENTINEL }, "[-afv] [-n keyno] prov" }, { "kill", G_FLAG_VERBOSE, eli_main, { { 'a', "all", NULL, G_TYPE_NONE }, G_OPT_SENTINEL }, "[-av] [prov ...]" }, { "backup", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS, "[-v] prov file" }, { "restore", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS, "[-v] file 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 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, "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, "dump") == 0) eli_dump(req); else if (strcmp(name, "clear") == 0) eli_clear(req); else gctl_error(req, "Unknown command: %s.", name); } static void arc4rand(unsigned char *buf, size_t size) { uint32_t *buf4; size_t size4; unsigned i; buf4 = (uint32_t *)buf; size4 = size / 4; for (i = 0; i < size4; i++) buf4[i] = arc4random(); for (i *= 4; i < size; i++) buf[i] = arc4random() % 0xff; } static int eli_is_attached(const char *prov) { char name[MAXPATHLEN]; unsigned secsize; /* * 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); secsize = g_get_sectorsize(name); if (secsize > 0) return (1); return (0); } static unsigned char * eli_genkey(struct gctl_req *req, struct g_eli_metadata *md, unsigned char *key, int new) { struct hmac_ctx ctx; const char *str; int error, nopassphrase; nopassphrase = gctl_get_int(req, new ? "nonewpassphrase" : "nopassphrase"); g_eli_crypto_hmac_init(&ctx, NULL, 0); str = gctl_get_ascii(req, new ? "newkeyfile" : "keyfile"); if (str[0] == '\0' && nopassphrase) { gctl_error(req, "No key components given."); return (NULL); } else if (str[0] != '\0') { char buf[MAXPHYS]; ssize_t done; int fd; if (strcmp(str, "-") == 0) fd = STDIN_FILENO; else { fd = open(str, O_RDONLY); if (fd == -1) { gctl_error(req, "Cannot open keyfile %s: %s.", str, strerror(errno)); return (NULL); } } while ((done = read(fd, buf, sizeof(buf))) > 0) g_eli_crypto_hmac_update(&ctx, buf, done); error = errno; if (strcmp(str, "-") != 0) close(fd); bzero(buf, sizeof(buf)); if (done == -1) { gctl_error(req, "Cannot read keyfile %s: %s.", str, strerror(error)); return (NULL); } } if (!nopassphrase) { char buf1[BUFSIZ], buf2[BUFSIZ], *p; if (!new && md->md_iterations == -1) { gctl_error(req, "Missing -p flag."); return (NULL); } for (;;) { p = readpassphrase( new ? "Enter new passphrase:" : "Enter passphrase:", buf1, sizeof(buf1), RPP_ECHO_OFF | RPP_REQUIRE_TTY); if (p == NULL) { bzero(buf1, sizeof(buf1)); gctl_error(req, "Cannot read passphrase: %s.", strerror(errno)); return (NULL); } if (new) { p = readpassphrase("Reenter new passphrase: ", buf2, sizeof(buf2), RPP_ECHO_OFF | RPP_REQUIRE_TTY); if (p == NULL) { bzero(buf1, sizeof(buf1)); gctl_error(req, "Cannot read passphrase: %s.", strerror(errno)); return (NULL); } if (strcmp(buf1, buf2) != 0) { bzero(buf2, sizeof(buf2)); fprintf(stderr, "They didn't match.\n"); continue; } bzero(buf2, sizeof(buf2)); } break; } /* * 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 don't want PKCS#5v2. */ if (md->md_iterations == 0) { g_eli_crypto_hmac_update(&ctx, md->md_salt, sizeof(md->md_salt)); g_eli_crypto_hmac_update(&ctx, buf1, strlen(buf1)); } else /* if (md->md_iterations > 0) */ { unsigned char dkey[G_ELI_USERKEYLEN]; pkcs5v2_genkey(dkey, sizeof(dkey), md->md_salt, sizeof(md->md_salt), buf1, md->md_iterations); g_eli_crypto_hmac_update(&ctx, dkey, sizeof(dkey)); bzero(dkey, sizeof(dkey)); } bzero(buf1, sizeof(buf1)); } g_eli_crypto_hmac_final(&ctx, key, 0); return (key); } 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); } } if (eli_metadata_decode(sector, md) != 0) { gctl_error(req, "MD5 hash mismatch for %s.", prov); 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)); 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)); 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)); bzero(sector, sizeof(sector)); return (-1); } } bzero(sector, sizeof(sector)); return (0); } static void eli_init(struct gctl_req *req) { struct g_eli_metadata md; unsigned char sector[sizeof(struct g_eli_metadata)]; unsigned char key[G_ELI_USERKEYLEN]; const char *str, *prov; unsigned secsize; off_t mediasize; intmax_t val; int error, nargs; nargs = gctl_get_int(req, "nargs"); if (nargs != 1) { gctl_error(req, "Invalid number of arguments."); return; } prov = gctl_get_ascii(req, "arg0"); mediasize = g_get_mediasize(prov); secsize = g_get_sectorsize(prov); if (mediasize == 0 || secsize == 0) { gctl_error(req, "Cannot get informations about %s: %s.", prov, strerror(errno)); return; } bzero(&md, sizeof(md)); strlcpy(md.md_magic, G_ELI_MAGIC, sizeof(md.md_magic)); md.md_version = G_ELI_VERSION; md.md_flags = 0; if (gctl_get_int(req, "boot")) md.md_flags |= G_ELI_FLAG_BOOT; md.md_ealgo = CRYPTO_ALGORITHM_MIN - 1; str = gctl_get_ascii(req, "aalgo"); if (strcmp(str, "none") != 0) { 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"); 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; } } 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; } md.md_provsize = mediasize; 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 == 0) md.md_sectorsize = secsize; else { if (val < 0 || (val % secsize) != 0) { gctl_error(req, "Invalid sector size."); return; } md.md_sectorsize = val; } md.md_keys = 0x01; arc4rand(md.md_salt, sizeof(md.md_salt)); arc4rand(md.md_mkeys, sizeof(md.md_mkeys)); /* Generate user key. */ if (eli_genkey(req, &md, key, 1) == NULL) { bzero(key, sizeof(key)); bzero(&md, sizeof(md)); return; } /* Encrypt the first and the only Master Key. */ error = g_eli_mkey_encrypt(md.md_ealgo, key, md.md_keylen, md.md_mkeys); bzero(key, sizeof(key)); if (error != 0) { bzero(&md, sizeof(md)); gctl_error(req, "Cannot encrypt Master Key: %s.", strerror(error)); return; } eli_metadata_encode(&md, sector); bzero(&md, sizeof(md)); error = g_metadata_store(prov, sector, sizeof(sector)); bzero(sector, sizeof(sector)); if (error != 0) { gctl_error(req, "Cannot store metadata on %s: %s.", prov, strerror(error)); return; } if (verbose) printf("Metadata value stored on %s.\n", prov); } static void eli_attach(struct gctl_req *req) { struct g_eli_metadata md; unsigned char key[G_ELI_USERKEYLEN]; 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_genkey(req, &md, key, 0) == NULL) { bzero(key, sizeof(key)); return; } gctl_ro_param(req, "key", sizeof(key), key); if (gctl_issue(req) == NULL) { if (verbose) printf("Attched to %s.\n", prov); } bzero(key, sizeof(key)); } static void +eli_configure_detached(struct gctl_req *req, const char *prov, int boot) +{ + struct g_eli_metadata md; + + if (eli_metadata_read(req, prov, &md) == -1) + return; + + if (boot && (md.md_flags & G_ELI_FLAG_BOOT)) { + if (verbose) + printf("BOOT flag already configured for %s.\n", prov); + } else if (!boot && !(md.md_flags & G_ELI_FLAG_BOOT)) { + if (verbose) + printf("BOOT flag not configured for %s.\n", prov); + } else { + if (boot) + md.md_flags |= G_ELI_FLAG_BOOT; + else + md.md_flags &= ~G_ELI_FLAG_BOOT; + eli_metadata_store(req, prov, &md); + } + bzero(&md, sizeof(md)); +} + +static void +eli_configure(struct gctl_req *req) +{ + const char *prov; + int i, nargs, boot, noboot; + + 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"); + + if (boot && noboot) { + gctl_error(req, "Options -b and -B are mutually exclusive."); + return; + } + if (!boot && !noboot) { + 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, boot); + } +} + +static void eli_setkey_attached(struct gctl_req *req, struct g_eli_metadata *md) { unsigned char key[G_ELI_USERKEYLEN]; intmax_t val; val = gctl_get_intmax(req, "iterations"); /* Check if iterations number should be changed. */ if (val != -1) md->md_iterations = val; /* Generate key for Master Key encryption. */ if (eli_genkey(req, md, key, 1) == NULL) { bzero(key, sizeof(key)); return; } gctl_ro_param(req, "key", sizeof(key), key); gctl_issue(req); 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; intmax_t val; unsigned nkey; 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(req, md, key, 0) == NULL) { bzero(key, sizeof(key)); return; } /* Decrypt Master Key. */ error = g_eli_mkey_decrypt(md, key, mkey, &nkey); bzero(key, sizeof(key)); if (error != 0) { 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) { 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)); bzero(mkey, sizeof(mkey)); /* Generate key for Master Key encryption. */ if (eli_genkey(req, md, key, 1) == NULL) { bzero(key, sizeof(key)); 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); bzero(key, sizeof(key)); if (error != 0) { 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); 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); } 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; intmax_t val; unsigned nkey; int all, force; if (eli_metadata_read(req, prov, &md) == -1) return; all = gctl_get_int(req, "all"); if (all) arc4rand(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; arc4rand(mkeydst, G_ELI_MKEYLEN); } eli_metadata_store(req, prov, &md); 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_kill_detached(struct gctl_req *req, const char *prov) { struct g_eli_metadata md; int error; /* * 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 arc4rand((unsigned char *)&md, sizeof(md)); error = g_metadata_store(prov, (unsigned char *)&md, sizeof(md)); if (error != 0) { gctl_error(req, "Cannot write metadata to %s: %s.", prov, strerror(error)); } } 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 provider 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 attached providers. - */ - gctl_issue(req); - /* - * Now the rest. - */ + /* First detached provider. */ 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 void eli_backup(struct gctl_req *req) { struct g_eli_metadata md; const char *file, *prov; unsigned secsize; unsigned char *sector; off_t mediasize; int nargs, filefd, provfd; 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"); provfd = filefd = -1; sector = NULL; secsize = 0; provfd = open(prov, O_RDONLY); if (provfd == -1 && errno == ENOENT && prov[0] != '/') { char devprov[MAXPATHLEN]; snprintf(devprov, sizeof(devprov), "%s%s", _PATH_DEV, prov); provfd = open(devprov, O_RDONLY); } if (provfd == -1) { gctl_error(req, "Cannot open %s: %s.", prov, strerror(errno)); return; } filefd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0600); if (filefd == -1) { gctl_error(req, "Cannot open %s: %s.", file, strerror(errno)); goto out; } mediasize = g_get_mediasize(prov); secsize = g_get_sectorsize(prov); if (mediasize == 0 || secsize == 0) { gctl_error(req, "Cannot get informations about %s: %s.", prov, strerror(errno)); return; } sector = malloc(secsize); if (sector == NULL) { gctl_error(req, "Cannot allocate memory."); return; } /* Read metadata from the provider. */ if (pread(provfd, sector, secsize, mediasize - secsize) != (ssize_t)secsize) { gctl_error(req, "Cannot read metadata: %s.", strerror(errno)); goto out; } /* Check if this is geli provider. */ if (eli_metadata_decode(sector, &md) != 0) { gctl_error(req, "MD5 hash mismatch: not a geli provider?"); goto out; } /* Write metadata to the destination file. */ if (write(filefd, sector, secsize) != (ssize_t)secsize) { gctl_error(req, "Cannot write to %s: %s.", file, strerror(errno)); goto out; } out: if (provfd > 0) close(provfd); if (filefd > 0) close(filefd); if (sector != NULL) { bzero(sector, secsize); free(sector); } } static void eli_restore(struct gctl_req *req) { struct g_eli_metadata md; const char *file, *prov; unsigned char *sector; unsigned secsize; off_t mediasize; int nargs, filefd, provfd; 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"); provfd = filefd = -1; sector = NULL; secsize = 0; filefd = open(file, O_RDONLY); if (filefd == -1) { gctl_error(req, "Cannot open %s: %s.", file, strerror(errno)); goto out; } provfd = open(prov, O_WRONLY); if (provfd == -1 && errno == ENOENT && prov[0] != '/') { char devprov[MAXPATHLEN]; snprintf(devprov, sizeof(devprov), "%s%s", _PATH_DEV, prov); provfd = open(devprov, O_WRONLY); } if (provfd == -1) { gctl_error(req, "Cannot open %s: %s.", prov, strerror(errno)); return; } mediasize = g_get_mediasize(prov); secsize = g_get_sectorsize(prov); if (mediasize == 0 || secsize == 0) { gctl_error(req, "Cannot get informations about %s: %s.", prov, strerror(errno)); return; } sector = malloc(secsize); if (sector == NULL) { gctl_error(req, "Cannot allocate memory."); return; } /* Read metadata from the backup file. */ if (read(filefd, sector, secsize) != (ssize_t)secsize) { gctl_error(req, "Cannot read from %s: %s.", file, strerror(errno)); goto out; } /* Check if this file contains geli metadata. */ if (eli_metadata_decode(sector, &md) != 0) { gctl_error(req, "MD5 hash mismatch: not a geli backup file?"); goto out; } - /* Read metadata from the provider. */ + /* Write metadata from the provider. */ if (pwrite(provfd, sector, secsize, mediasize - secsize) != (ssize_t)secsize) { gctl_error(req, "Cannot write metadata: %s.", strerror(errno)); goto out; } out: if (provfd > 0) close(provfd); if (filefd > 0) close(filefd); if (sector != NULL) { bzero(sector, secsize); free(sector); } } 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, tmpmd; 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_read(name, (unsigned char *)&tmpmd, sizeof(tmpmd), G_ELI_MAGIC); if (error != 0) { fprintf(stderr, "Cannot read metadata from %s: %s.\n", name, strerror(error)); gctl_error(req, "Not fully done."); continue; } if (eli_metadata_decode((unsigned char *)&tmpmd, &md) != 0) { fprintf(stderr, "MD5 hash mismatch for %s, skipping.\n", name); gctl_error(req, "Not fully done."); continue; } printf("Metadata on %s:\n", name); eli_metadata_dump(&md); printf("\n"); } } Index: stable/6/sys/geom/eli/g_eli.h =================================================================== --- stable/6/sys/geom/eli/g_eli.h (revision 162433) +++ stable/6/sys/geom/eli/g_eli.h (revision 162434) @@ -1,489 +1,491 @@ /*- * Copyright (c) 2005-2006 Pawel Jakub Dawidek * 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. * * $FreeBSD$ */ #ifndef _G_ELI_H_ #define _G_ELI_H_ #include #include #include #include #include #ifdef _KERNEL #include #include #include #else #include #include #include #endif #ifndef _OpenSSL_ #include #endif #define G_ELI_CLASS_NAME "ELI" #define G_ELI_MAGIC "GEOM::ELI" #define G_ELI_SUFFIX ".eli" /* * Version history: * 0 - Initial version number. * 1 - Added data authentication support (md_aalgo field and * G_ELI_FLAG_AUTH flag). * 2 - Added G_ELI_FLAG_READONLY. * - IV is generated from offset converted to little-endian * (flag G_ELI_FLAG_NATIVE_BYTE_ORDER will be set for older versions). + * 3 - Added 'configure' subcommand. */ -#define G_ELI_VERSION 2 +#define G_ELI_VERSION 3 /* ON DISK FLAGS. */ /* Use random, onetime keys. */ #define G_ELI_FLAG_ONETIME 0x00000001 /* Ask for the passphrase from the kernel, before mounting root. */ #define G_ELI_FLAG_BOOT 0x00000002 /* Detach on last close, if we were open for writing. */ #define G_ELI_FLAG_WO_DETACH 0x00000004 /* Detach on last close. */ #define G_ELI_FLAG_RW_DETACH 0x00000008 /* Provide data authentication. */ #define G_ELI_FLAG_AUTH 0x00000010 /* Provider is read-only, we should deny all write attempts. */ #define G_ELI_FLAG_RO 0x00000020 /* RUNTIME FLAGS. */ /* Provider was open for writing. */ #define G_ELI_FLAG_WOPEN 0x00010000 /* Destroy device. */ #define G_ELI_FLAG_DESTROY 0x00020000 /* Provider uses native byte-order for IV generation. */ #define G_ELI_FLAG_NATIVE_BYTE_ORDER 0x00040000 #define SHA512_MDLEN 64 #define G_ELI_AUTH_SECKEYLEN SHA256_DIGEST_LENGTH #define G_ELI_MAXMKEYS 2 #define G_ELI_MAXKEYLEN 64 #define G_ELI_USERKEYLEN G_ELI_MAXKEYLEN #define G_ELI_DATAKEYLEN G_ELI_MAXKEYLEN #define G_ELI_AUTHKEYLEN G_ELI_MAXKEYLEN #define G_ELI_IVKEYLEN G_ELI_MAXKEYLEN #define G_ELI_SALTLEN 64 #define G_ELI_DATAIVKEYLEN (G_ELI_DATAKEYLEN + G_ELI_IVKEYLEN) /* Data-Key, IV-Key, HMAC_SHA512(Derived-Key, Data-Key+IV-Key) */ #define G_ELI_MKEYLEN (G_ELI_DATAIVKEYLEN + SHA512_MDLEN) #ifdef _KERNEL extern u_int g_eli_debug; extern u_int g_eli_overwrites; extern u_int g_eli_batch; #define G_ELI_CRYPTO_HW 1 #define G_ELI_CRYPTO_SW 2 #define G_ELI_DEBUG(lvl, ...) do { \ if (g_eli_debug >= (lvl)) { \ printf("GEOM_ELI"); \ if (g_eli_debug > 0) \ printf("[%u]", lvl); \ printf(": "); \ printf(__VA_ARGS__); \ printf("\n"); \ } \ } while (0) #define G_ELI_LOGREQ(lvl, bp, ...) do { \ if (g_eli_debug >= (lvl)) { \ printf("GEOM_ELI"); \ if (g_eli_debug > 0) \ printf("[%u]", lvl); \ printf(": "); \ printf(__VA_ARGS__); \ printf(" "); \ g_print_bio(bp); \ printf("\n"); \ } \ } while (0) struct g_eli_worker { struct g_eli_softc *w_softc; struct proc *w_proc; u_int w_number; uint64_t w_sid; LIST_ENTRY(g_eli_worker) w_next; }; struct g_eli_softc { struct g_geom *sc_geom; u_int sc_crypto; uint8_t sc_mkey[G_ELI_DATAIVKEYLEN]; uint8_t sc_ekey[G_ELI_DATAKEYLEN]; u_int sc_ealgo; u_int sc_ekeylen; uint8_t sc_akey[G_ELI_AUTHKEYLEN]; u_int sc_aalgo; u_int sc_akeylen; u_int sc_alen; SHA256_CTX sc_akeyctx; uint8_t sc_ivkey[G_ELI_IVKEYLEN]; SHA256_CTX sc_ivctx; int sc_nkey; uint32_t sc_flags; u_int sc_bytes_per_sector; u_int sc_data_per_sector; /* Only for software cryptography. */ struct bio_queue_head sc_queue; struct mtx sc_queue_mtx; LIST_HEAD(, g_eli_worker) sc_workers; }; #define sc_name sc_geom->name #endif /* _KERNEL */ struct g_eli_metadata { char md_magic[16]; /* Magic value. */ uint32_t md_version; /* Version number. */ uint32_t md_flags; /* Additional flags. */ uint16_t md_ealgo; /* Encryption algorithm. */ uint16_t md_keylen; /* Key length. */ uint16_t md_aalgo; /* Authentication algorithm. */ uint64_t md_provsize; /* Provider's size. */ uint32_t md_sectorsize; /* Sector size. */ uint8_t md_keys; /* Available keys. */ int32_t md_iterations; /* Number of iterations for PKCS#5v2. */ uint8_t md_salt[G_ELI_SALTLEN]; /* Salt. */ /* Encrypted master key (IV-key, Data-key, HMAC). */ uint8_t md_mkeys[G_ELI_MAXMKEYS * G_ELI_MKEYLEN]; u_char md_hash[16]; /* MD5 hash. */ } __packed; #ifndef _OpenSSL_ static __inline void eli_metadata_encode(struct g_eli_metadata *md, u_char *data) { MD5_CTX ctx; u_char *p; p = data; bcopy(md->md_magic, p, sizeof(md->md_magic)); p += sizeof(md->md_magic); le32enc(p, md->md_version); p += sizeof(md->md_version); le32enc(p, md->md_flags); p += sizeof(md->md_flags); le16enc(p, md->md_ealgo); p += sizeof(md->md_ealgo); le16enc(p, md->md_keylen); p += sizeof(md->md_keylen); le16enc(p, md->md_aalgo); p += sizeof(md->md_aalgo); le64enc(p, md->md_provsize); p += sizeof(md->md_provsize); le32enc(p, md->md_sectorsize); p += sizeof(md->md_sectorsize); *p = md->md_keys; p += sizeof(md->md_keys); le32enc(p, md->md_iterations); p += sizeof(md->md_iterations); bcopy(md->md_salt, p, sizeof(md->md_salt)); p += sizeof(md->md_salt); bcopy(md->md_mkeys, p, sizeof(md->md_mkeys)); p += sizeof(md->md_mkeys); MD5Init(&ctx); MD5Update(&ctx, data, p - data); MD5Final(md->md_hash, &ctx); bcopy(md->md_hash, p, sizeof(md->md_hash)); } static __inline int eli_metadata_decode_v0(const u_char *data, struct g_eli_metadata *md) { MD5_CTX ctx; const u_char *p; p = data + sizeof(md->md_magic) + sizeof(md->md_version); md->md_flags = le32dec(p); p += sizeof(md->md_flags); md->md_ealgo = le16dec(p); p += sizeof(md->md_ealgo); md->md_keylen = le16dec(p); p += sizeof(md->md_keylen); md->md_provsize = le64dec(p); p += sizeof(md->md_provsize); md->md_sectorsize = le32dec(p); p += sizeof(md->md_sectorsize); md->md_keys = *p; p += sizeof(md->md_keys); md->md_iterations = le32dec(p); p += sizeof(md->md_iterations); bcopy(p, md->md_salt, sizeof(md->md_salt)); p += sizeof(md->md_salt); bcopy(p, md->md_mkeys, sizeof(md->md_mkeys)); p += sizeof(md->md_mkeys); MD5Init(&ctx); MD5Update(&ctx, data, p - data); MD5Final(md->md_hash, &ctx); if (bcmp(md->md_hash, p, 16) != 0) return (EINVAL); return (0); } static __inline int -eli_metadata_decode_v1v2(const u_char *data, struct g_eli_metadata *md) +eli_metadata_decode_v1v2v3(const u_char *data, struct g_eli_metadata *md) { MD5_CTX ctx; const u_char *p; p = data + sizeof(md->md_magic) + sizeof(md->md_version); md->md_flags = le32dec(p); p += sizeof(md->md_flags); md->md_ealgo = le16dec(p); p += sizeof(md->md_ealgo); md->md_keylen = le16dec(p); p += sizeof(md->md_keylen); md->md_aalgo = le16dec(p); p += sizeof(md->md_aalgo); md->md_provsize = le64dec(p); p += sizeof(md->md_provsize); md->md_sectorsize = le32dec(p); p += sizeof(md->md_sectorsize); md->md_keys = *p; p += sizeof(md->md_keys); md->md_iterations = le32dec(p); p += sizeof(md->md_iterations); bcopy(p, md->md_salt, sizeof(md->md_salt)); p += sizeof(md->md_salt); bcopy(p, md->md_mkeys, sizeof(md->md_mkeys)); p += sizeof(md->md_mkeys); MD5Init(&ctx); MD5Update(&ctx, data, p - data); MD5Final(md->md_hash, &ctx); if (bcmp(md->md_hash, p, 16) != 0) return (EINVAL); return (0); } static __inline int eli_metadata_decode(const u_char *data, struct g_eli_metadata *md) { int error; bcopy(data, md->md_magic, sizeof(md->md_magic)); md->md_version = le32dec(data + sizeof(md->md_magic)); switch (md->md_version) { case 0: error = eli_metadata_decode_v0(data, md); break; case 1: case 2: - error = eli_metadata_decode_v1v2(data, md); + case 3: + error = eli_metadata_decode_v1v2v3(data, md); break; default: error = EINVAL; break; } return (error); } #endif /* !_OpenSSL */ static __inline u_int g_eli_str2ealgo(const char *name) { if (strcasecmp("null", name) == 0) return (CRYPTO_NULL_CBC); else if (strcasecmp("aes", name) == 0) return (CRYPTO_AES_CBC); else if (strcasecmp("blowfish", name) == 0) return (CRYPTO_BLF_CBC); else if (strcasecmp("3des", name) == 0) return (CRYPTO_3DES_CBC); return (CRYPTO_ALGORITHM_MIN - 1); } static __inline u_int g_eli_str2aalgo(const char *name) { if (strcasecmp("hmac/md5", name) == 0) return (CRYPTO_MD5_HMAC); else if (strcasecmp("hmac/sha1", name) == 0) return (CRYPTO_SHA1_HMAC); else if (strcasecmp("hmac/ripemd160", name) == 0) return (CRYPTO_RIPEMD160_HMAC); else if (strcasecmp("hmac/sha256", name) == 0) return (CRYPTO_SHA2_256_HMAC); else if (strcasecmp("hmac/sha384", name) == 0) return (CRYPTO_SHA2_384_HMAC); else if (strcasecmp("hmac/sha512", name) == 0) return (CRYPTO_SHA2_512_HMAC); return (CRYPTO_ALGORITHM_MIN - 1); } static __inline const char * g_eli_algo2str(u_int algo) { switch (algo) { case CRYPTO_NULL_CBC: return ("NULL"); case CRYPTO_AES_CBC: return ("AES-CBC"); case CRYPTO_BLF_CBC: return ("Blowfish-CBC"); case CRYPTO_3DES_CBC: return ("3DES-CBC"); case CRYPTO_MD5_HMAC: return ("HMAC/MD5"); case CRYPTO_SHA1_HMAC: return ("HMAC/SHA1"); case CRYPTO_RIPEMD160_HMAC: return ("HMAC/RIPEMD160"); case CRYPTO_SHA2_256_HMAC: return ("HMAC/SHA256"); case CRYPTO_SHA2_384_HMAC: return ("HMAC/SHA384"); case CRYPTO_SHA2_512_HMAC: return ("HMAC/SHA512"); } return ("unknown"); } static __inline void eli_metadata_dump(const struct g_eli_metadata *md) { static const char hex[] = "0123456789abcdef"; char str[sizeof(md->md_mkeys) * 2 + 1]; u_int i; printf(" magic: %s\n", md->md_magic); printf(" version: %u\n", (u_int)md->md_version); printf(" flags: 0x%x\n", (u_int)md->md_flags); printf(" ealgo: %s\n", g_eli_algo2str(md->md_ealgo)); printf(" keylen: %u\n", (u_int)md->md_keylen); if (md->md_flags & G_ELI_FLAG_AUTH) printf(" aalgo: %s\n", g_eli_algo2str(md->md_aalgo)); printf(" provsize: %ju\n", (uintmax_t)md->md_provsize); printf("sectorsize: %u\n", (u_int)md->md_sectorsize); printf(" keys: 0x%02x\n", (u_int)md->md_keys); printf("iterations: %u\n", (u_int)md->md_iterations); bzero(str, sizeof(str)); for (i = 0; i < sizeof(md->md_salt); i++) { str[i * 2] = hex[md->md_salt[i] >> 4]; str[i * 2 + 1] = hex[md->md_salt[i] & 0x0f]; } printf(" Salt: %s\n", str); bzero(str, sizeof(str)); for (i = 0; i < sizeof(md->md_mkeys); i++) { str[i * 2] = hex[md->md_mkeys[i] >> 4]; str[i * 2 + 1] = hex[md->md_mkeys[i] & 0x0f]; } printf("Master Key: %s\n", str); bzero(str, sizeof(str)); for (i = 0; i < 16; i++) { str[i * 2] = hex[md->md_hash[i] >> 4]; str[i * 2 + 1] = hex[md->md_hash[i] & 0x0f]; } printf(" MD5 hash: %s\n", str); } static __inline u_int g_eli_keylen(u_int algo, u_int keylen) { switch (algo) { case CRYPTO_NULL_CBC: if (keylen == 0) keylen = 64 * 8; else { if (keylen > 64 * 8) keylen = 0; } return (keylen); case CRYPTO_AES_CBC: switch (keylen) { case 0: return (128); case 128: case 192: case 256: return (keylen); default: return (0); } case CRYPTO_BLF_CBC: if (keylen == 0) return (128); if (keylen < 128 || keylen > 448) return (0); if ((keylen % 32) != 0) return (0); return (keylen); case CRYPTO_3DES_CBC: if (keylen == 0 || keylen == 192) return (192); return (0); default: return (0); } } static __inline u_int g_eli_hashlen(u_int algo) { switch (algo) { case CRYPTO_MD5_HMAC: return (16); case CRYPTO_SHA1_HMAC: return (20); case CRYPTO_RIPEMD160_HMAC: return (20); case CRYPTO_SHA2_256_HMAC: return (32); case CRYPTO_SHA2_384_HMAC: return (48); case CRYPTO_SHA2_512_HMAC: return (64); } return (0); } #ifdef _KERNEL int g_eli_read_metadata(struct g_class *mp, struct g_provider *pp, struct g_eli_metadata *md); struct g_geom *g_eli_create(struct gctl_req *req, struct g_class *mp, struct g_provider *bpp, const struct g_eli_metadata *md, const u_char *mkey, int nkey); int g_eli_destroy(struct g_eli_softc *sc, boolean_t force); int g_eli_access(struct g_provider *pp, int dr, int dw, int de); void g_eli_config(struct gctl_req *req, struct g_class *mp, const char *verb); void g_eli_read_done(struct bio *bp); void g_eli_write_done(struct bio *bp); int g_eli_crypto_rerun(struct cryptop *crp); void g_eli_crypto_ivgen(struct g_eli_softc *sc, off_t offset, u_char *iv, size_t size); void g_eli_crypto_run(struct g_eli_worker *wr, struct bio *bp); void g_eli_auth_read(struct g_eli_softc *sc, struct bio *bp); void g_eli_auth_run(struct g_eli_worker *wr, struct bio *bp); #endif void g_eli_mkey_hmac(unsigned char *mkey, const unsigned char *key); int g_eli_mkey_decrypt(const struct g_eli_metadata *md, const unsigned char *key, unsigned char *mkey, unsigned *nkeyp); int g_eli_mkey_encrypt(unsigned algo, const unsigned char *key, unsigned keylen, unsigned char *mkey); #ifdef _KERNEL void g_eli_mkey_propagate(struct g_eli_softc *sc, const unsigned char *mkey); #endif int g_eli_crypto_encrypt(u_int algo, u_char *data, size_t datasize, const u_char *key, size_t keysize); int g_eli_crypto_decrypt(u_int algo, u_char *data, size_t datasize, const u_char *key, size_t keysize); struct hmac_ctx { SHA512_CTX shactx; u_char k_opad[128]; }; void g_eli_crypto_hmac_init(struct hmac_ctx *ctx, const uint8_t *hkey, size_t hkeylen); void g_eli_crypto_hmac_update(struct hmac_ctx *ctx, const uint8_t *data, size_t datasize); void g_eli_crypto_hmac_final(struct hmac_ctx *ctx, uint8_t *md, size_t mdsize); void g_eli_crypto_hmac(const uint8_t *hkey, size_t hkeysize, const uint8_t *data, size_t datasize, uint8_t *md, size_t mdsize); #endif /* !_G_ELI_H_ */ Index: stable/6/sys/geom/eli/g_eli_ctl.c =================================================================== --- stable/6/sys/geom/eli/g_eli_ctl.c (revision 162433) +++ stable/6/sys/geom/eli/g_eli_ctl.c (revision 162434) @@ -1,722 +1,832 @@ /*- * Copyright (c) 2005 Pawel Jakub Dawidek * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MALLOC_DECLARE(M_ELI); static void g_eli_ctl_attach(struct gctl_req *req, struct g_class *mp) { struct g_eli_metadata md; struct g_provider *pp; const char *name; u_char *key, mkey[G_ELI_DATAIVKEYLEN]; int *nargs, *detach, *readonly; int keysize, error; u_int nkey; g_topology_assert(); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } if (*nargs != 1) { gctl_error(req, "Invalid number of arguments."); return; } detach = gctl_get_paraml(req, "detach", sizeof(*detach)); if (detach == NULL) { gctl_error(req, "No '%s' argument.", "detach"); return; } readonly = gctl_get_paraml(req, "readonly", sizeof(*readonly)); if (readonly == NULL) { gctl_error(req, "No '%s' argument.", "readonly"); return; } name = gctl_get_asciiparam(req, "arg0"); if (name == NULL) { gctl_error(req, "No 'arg%u' argument.", 0); return; } if (strncmp(name, "/dev/", strlen("/dev/")) == 0) name += strlen("/dev/"); pp = g_provider_by_name(name); if (pp == NULL) { gctl_error(req, "Provider %s is invalid.", name); return; } error = g_eli_read_metadata(mp, pp, &md); if (error != 0) { gctl_error(req, "Cannot read metadata from %s (error=%d).", name, error); return; } if (md.md_keys == 0x00) { bzero(&md, sizeof(md)); gctl_error(req, "No valid keys on %s.", pp->name); return; } key = gctl_get_param(req, "key", &keysize); if (key == NULL || keysize != G_ELI_USERKEYLEN) { bzero(&md, sizeof(md)); gctl_error(req, "No '%s' argument.", "key"); return; } error = g_eli_mkey_decrypt(&md, key, mkey, &nkey); bzero(key, keysize); if (error == -1) { bzero(&md, sizeof(md)); gctl_error(req, "Wrong key for %s.", pp->name); return; } else if (error > 0) { bzero(&md, sizeof(md)); gctl_error(req, "Cannot decrypt Master Key for %s (error=%d).", pp->name, error); return; } G_ELI_DEBUG(1, "Using Master Key %u for %s.", nkey, pp->name); if (*detach && *readonly) { bzero(&md, sizeof(md)); - gctl_error(req, "Options -d and -r are mutually exclusive.", - pp->name, error); + gctl_error(req, "Options -d and -r are mutually exclusive."); return; } if (*detach) md.md_flags |= G_ELI_FLAG_WO_DETACH; if (*readonly) md.md_flags |= G_ELI_FLAG_RO; g_eli_create(req, mp, pp, &md, mkey, nkey); bzero(mkey, sizeof(mkey)); bzero(&md, sizeof(md)); } static struct g_eli_softc * g_eli_find_device(struct g_class *mp, const char *prov) { struct g_eli_softc *sc; struct g_geom *gp; struct g_provider *pp; struct g_consumer *cp; if (strncmp(prov, "/dev/", strlen("/dev/")) == 0) prov += strlen("/dev/"); LIST_FOREACH(gp, &mp->geom, geom) { sc = gp->softc; if (sc == NULL) continue; pp = LIST_FIRST(&gp->provider); if (pp != NULL && strcmp(pp->name, prov) == 0) return (sc); cp = LIST_FIRST(&gp->consumer); if (cp != NULL && cp->provider != NULL && strcmp(cp->provider->name, prov) == 0) { return (sc); } } return (NULL); } static void g_eli_ctl_detach(struct gctl_req *req, struct g_class *mp) { struct g_eli_softc *sc; int *force, *last, *nargs, error; const char *prov; char param[16]; int i; g_topology_assert(); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } if (*nargs <= 0) { gctl_error(req, "Missing device(s)."); return; } force = gctl_get_paraml(req, "force", sizeof(*force)); if (force == NULL) { gctl_error(req, "No '%s' argument.", "force"); return; } last = gctl_get_paraml(req, "last", sizeof(*last)); if (last == NULL) { gctl_error(req, "No '%s' argument.", "last"); return; } for (i = 0; i < *nargs; i++) { snprintf(param, sizeof(param), "arg%d", i); prov = gctl_get_asciiparam(req, param); if (prov == NULL) { gctl_error(req, "No 'arg%d' argument.", i); return; } sc = g_eli_find_device(mp, prov); if (sc == NULL) { gctl_error(req, "No such device: %s.", prov); return; } if (*last) { sc->sc_flags |= G_ELI_FLAG_RW_DETACH; sc->sc_geom->access = g_eli_access; } else { error = g_eli_destroy(sc, *force); if (error != 0) { gctl_error(req, "Cannot destroy device %s (error=%d).", sc->sc_name, error); return; } } } } static void g_eli_ctl_onetime(struct gctl_req *req, struct g_class *mp) { struct g_eli_metadata md; struct g_provider *pp; const char *name; intmax_t *keylen, *sectorsize; u_char mkey[G_ELI_DATAIVKEYLEN]; int *nargs, *detach; g_topology_assert(); bzero(&md, sizeof(md)); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } if (*nargs != 1) { gctl_error(req, "Invalid number of arguments."); return; } detach = gctl_get_paraml(req, "detach", sizeof(*detach)); if (detach == NULL) { gctl_error(req, "No '%s' argument.", "detach"); return; } strlcpy(md.md_magic, G_ELI_MAGIC, sizeof(md.md_magic)); md.md_version = G_ELI_VERSION; md.md_flags |= G_ELI_FLAG_ONETIME; if (*detach) md.md_flags |= G_ELI_FLAG_WO_DETACH; md.md_ealgo = CRYPTO_ALGORITHM_MIN - 1; name = gctl_get_asciiparam(req, "aalgo"); if (name == NULL) { gctl_error(req, "No '%s' argument.", "aalgo"); return; } if (strcmp(name, "none") != 0) { md.md_aalgo = g_eli_str2aalgo(name); 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(name); if (md.md_ealgo < CRYPTO_ALGORITHM_MIN || md.md_ealgo > CRYPTO_ALGORITHM_MAX) { gctl_error(req, "Invalid authentication algorithm."); return; } else { gctl_error(req, "warning: The -e option, not " "the -a option is now used to specify " "encryption algorithm to use."); } } } if (md.md_ealgo < CRYPTO_ALGORITHM_MIN || md.md_ealgo > CRYPTO_ALGORITHM_MAX) { name = gctl_get_asciiparam(req, "ealgo"); if (name == NULL) { gctl_error(req, "No '%s' argument.", "ealgo"); return; } md.md_ealgo = g_eli_str2ealgo(name); if (md.md_ealgo < CRYPTO_ALGORITHM_MIN || md.md_ealgo > CRYPTO_ALGORITHM_MAX) { gctl_error(req, "Invalid encryption algorithm."); return; } } keylen = gctl_get_paraml(req, "keylen", sizeof(*keylen)); if (keylen == NULL) { gctl_error(req, "No '%s' argument.", "keylen"); return; } md.md_keylen = g_eli_keylen(md.md_ealgo, *keylen); if (md.md_keylen == 0) { gctl_error(req, "Invalid '%s' argument.", "keylen"); return; } /* Not important here. */ md.md_provsize = 0; /* Not important here. */ bzero(md.md_salt, sizeof(md.md_salt)); md.md_keys = 0x01; arc4rand(mkey, sizeof(mkey), 0); /* Not important here. */ bzero(md.md_hash, sizeof(md.md_hash)); name = gctl_get_asciiparam(req, "arg0"); if (name == NULL) { gctl_error(req, "No 'arg%u' argument.", 0); return; } if (strncmp(name, "/dev/", strlen("/dev/")) == 0) name += strlen("/dev/"); pp = g_provider_by_name(name); if (pp == NULL) { gctl_error(req, "Provider %s is invalid.", name); return; } sectorsize = gctl_get_paraml(req, "sectorsize", sizeof(*sectorsize)); if (sectorsize == NULL) { gctl_error(req, "No '%s' argument.", "sectorsize"); return; } if (*sectorsize == 0) md.md_sectorsize = pp->sectorsize; else { if (*sectorsize < 0 || (*sectorsize % pp->sectorsize) != 0) { gctl_error(req, "Invalid sector size."); return; } md.md_sectorsize = *sectorsize; } g_eli_create(req, mp, pp, &md, mkey, -1); bzero(mkey, sizeof(mkey)); bzero(&md, sizeof(md)); } static void +g_eli_ctl_configure(struct gctl_req *req, struct g_class *mp) +{ + struct g_eli_softc *sc; + struct g_eli_metadata md; + struct g_provider *pp; + struct g_consumer *cp; + char param[16]; + const char *prov; + u_char *sector; + int *nargs, *boot, *noboot; + int error; + u_int i; + + g_topology_assert(); + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + if (*nargs <= 0) { + gctl_error(req, "Missing device(s)."); + return; + } + + boot = gctl_get_paraml(req, "boot", sizeof(*boot)); + if (boot == NULL) { + gctl_error(req, "No '%s' argument.", "boot"); + return; + } + noboot = gctl_get_paraml(req, "noboot", sizeof(*noboot)); + if (noboot == NULL) { + gctl_error(req, "No '%s' argument.", "noboot"); + return; + } + if (*boot && *noboot) { + gctl_error(req, "Options -b and -B are mutually exclusive."); + return; + } + if (!*boot && !*noboot) { + gctl_error(req, "No option given."); + return; + } + + for (i = 0; i < *nargs; i++) { + snprintf(param, sizeof(param), "arg%d", i); + prov = gctl_get_asciiparam(req, param); + if (prov == NULL) { + gctl_error(req, "No 'arg%d' argument.", i); + return; + } + sc = g_eli_find_device(mp, prov); + if (sc == NULL) { + /* + * We ignore not attached providers, userland part will + * take care of them. + */ + G_ELI_DEBUG(1, "Skipping configuration of not attached " + "provider %s.", prov); + continue; + } + if (*boot && (sc->sc_flags & G_ELI_FLAG_BOOT)) { + G_ELI_DEBUG(1, "BOOT flag already configured for %s.", + prov); + continue; + } else if (!*boot && !(sc->sc_flags & G_ELI_FLAG_BOOT)) { + G_ELI_DEBUG(1, "BOOT flag not configured for %s.", + prov); + continue; + } + if (sc->sc_flags & G_ELI_FLAG_RO) { + gctl_error(req, "Cannot change configuration of " + "read-only provider %s.", prov); + continue; + } + cp = LIST_FIRST(&sc->sc_geom->consumer); + pp = cp->provider; + error = g_eli_read_metadata(mp, pp, &md); + if (error != 0) { + gctl_error(req, + "Cannot read metadata from %s (error=%d).", + prov, error); + continue; + } + + if (*boot) { + md.md_flags |= G_ELI_FLAG_BOOT; + sc->sc_flags |= G_ELI_FLAG_BOOT; + } else { + md.md_flags &= ~G_ELI_FLAG_BOOT; + sc->sc_flags &= ~G_ELI_FLAG_BOOT; + } + + sector = malloc(pp->sectorsize, M_ELI, M_WAITOK | M_ZERO); + eli_metadata_encode(&md, sector); + error = g_write_data(cp, pp->mediasize - pp->sectorsize, sector, + pp->sectorsize); + if (error != 0) { + gctl_error(req, + "Cannot store metadata on %s (error=%d).", + prov, error); + } + bzero(&md, sizeof(md)); + bzero(sector, sizeof(sector)); + free(sector, M_ELI); + } +} + +static void g_eli_ctl_setkey(struct gctl_req *req, struct g_class *mp) { struct g_eli_softc *sc; struct g_eli_metadata md; struct g_provider *pp; struct g_consumer *cp; const char *name; u_char *key, *mkeydst, *sector; intmax_t *valp; int keysize, nkey, error; g_topology_assert(); name = gctl_get_asciiparam(req, "arg0"); if (name == NULL) { gctl_error(req, "No 'arg%u' argument.", 0); return; } sc = g_eli_find_device(mp, name); if (sc == NULL) { gctl_error(req, "Provider %s is invalid.", name); return; } if (sc->sc_flags & G_ELI_FLAG_RO) { gctl_error(req, "Cannot change keys for read-only provider."); return; } cp = LIST_FIRST(&sc->sc_geom->consumer); pp = cp->provider; error = g_eli_read_metadata(mp, pp, &md); if (error != 0) { gctl_error(req, "Cannot read metadata from %s (error=%d).", name, error); return; } valp = gctl_get_paraml(req, "keyno", sizeof(*valp)); if (valp == NULL) { gctl_error(req, "No '%s' argument.", "keyno"); return; } if (*valp != -1) nkey = *valp; else nkey = sc->sc_nkey; if (nkey < 0 || nkey >= G_ELI_MAXMKEYS) { gctl_error(req, "Invalid '%s' argument.", "keyno"); return; } valp = gctl_get_paraml(req, "iterations", sizeof(*valp)); if (valp == NULL) { gctl_error(req, "No '%s' argument.", "iterations"); return; } /* Check if iterations number should and can be changed. */ if (*valp != -1) { 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 = *valp; } key = gctl_get_param(req, "key", &keysize); if (key == NULL || keysize != G_ELI_USERKEYLEN) { bzero(&md, sizeof(md)); gctl_error(req, "No '%s' argument.", "key"); return; } mkeydst = md.md_mkeys + nkey * G_ELI_MKEYLEN; md.md_keys |= (1 << nkey); bcopy(sc->sc_mkey, mkeydst, sizeof(sc->sc_mkey)); /* Encrypt Master Key with the new key. */ error = g_eli_mkey_encrypt(md.md_ealgo, key, md.md_keylen, mkeydst); bzero(key, sizeof(key)); if (error != 0) { bzero(&md, sizeof(md)); gctl_error(req, "Cannot encrypt Master Key (error=%d).", error); return; } sector = malloc(pp->sectorsize, M_ELI, M_WAITOK | M_ZERO); /* Store metadata with fresh key. */ eli_metadata_encode(&md, sector); bzero(&md, sizeof(md)); error = g_write_data(cp, pp->mediasize - pp->sectorsize, sector, pp->sectorsize); bzero(sector, sizeof(sector)); free(sector, M_ELI); if (error != 0) { gctl_error(req, "Cannot store metadata on %s (error=%d).", pp->name, error); return; } G_ELI_DEBUG(1, "Key %u changed on %s.", nkey, pp->name); } static void g_eli_ctl_delkey(struct gctl_req *req, struct g_class *mp) { struct g_eli_softc *sc; struct g_eli_metadata md; struct g_provider *pp; struct g_consumer *cp; const char *name; u_char *mkeydst, *sector; intmax_t *valp; size_t keysize; int error, nkey, *all, *force; u_int i; g_topology_assert(); nkey = 0; /* fixes causeless gcc warning */ name = gctl_get_asciiparam(req, "arg0"); if (name == NULL) { gctl_error(req, "No 'arg%u' argument.", 0); return; } sc = g_eli_find_device(mp, name); if (sc == NULL) { gctl_error(req, "Provider %s is invalid.", name); return; } if (sc->sc_flags & G_ELI_FLAG_RO) { gctl_error(req, "Cannot delete keys for read-only provider."); return; } cp = LIST_FIRST(&sc->sc_geom->consumer); pp = cp->provider; error = g_eli_read_metadata(mp, pp, &md); if (error != 0) { gctl_error(req, "Cannot read metadata from %s (error=%d).", name, error); return; } all = gctl_get_paraml(req, "all", sizeof(*all)); if (all == NULL) { gctl_error(req, "No '%s' argument.", "all"); return; } if (*all) { mkeydst = md.md_mkeys; keysize = sizeof(md.md_mkeys); } else { force = gctl_get_paraml(req, "force", sizeof(*force)); if (force == NULL) { gctl_error(req, "No '%s' argument.", "force"); return; } valp = gctl_get_paraml(req, "keyno", sizeof(*valp)); if (valp == NULL) { gctl_error(req, "No '%s' argument.", "keyno"); return; } if (*valp != -1) nkey = *valp; else nkey = sc->sc_nkey; if (nkey < 0 || 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' " "flag if you really want to remove it."); return; } mkeydst = md.md_mkeys + nkey * G_ELI_MKEYLEN; keysize = G_ELI_MKEYLEN; } sector = malloc(pp->sectorsize, M_ELI, M_WAITOK | M_ZERO); for (i = 0; i <= g_eli_overwrites; i++) { if (i == g_eli_overwrites) bzero(mkeydst, keysize); else arc4rand(mkeydst, keysize, 0); /* Store metadata with destroyed key. */ eli_metadata_encode(&md, sector); error = g_write_data(cp, pp->mediasize - pp->sectorsize, sector, pp->sectorsize); if (error != 0) { G_ELI_DEBUG(0, "Cannot store metadata on %s " "(error=%d).", pp->name, error); } } bzero(&md, sizeof(md)); bzero(sector, sizeof(sector)); free(sector, M_ELI); if (*all) G_ELI_DEBUG(1, "All keys removed from %s.", pp->name); else G_ELI_DEBUG(1, "Key %d removed from %s.", nkey, pp->name); } static int g_eli_kill_one(struct g_eli_softc *sc) { struct g_provider *pp; struct g_consumer *cp; int error = 0; g_topology_assert(); if (sc == NULL) return (ENOENT); pp = LIST_FIRST(&sc->sc_geom->provider); g_error_provider(pp, ENXIO); cp = LIST_FIRST(&sc->sc_geom->consumer); pp = cp->provider; if (sc->sc_flags & G_ELI_FLAG_RO) { G_ELI_DEBUG(0, "WARNING: Metadata won't be erased on read-only " "provider: %s.", pp->name); } else { u_char *sector; u_int i; int err; sector = malloc(pp->sectorsize, M_ELI, M_WAITOK); for (i = 0; i <= g_eli_overwrites; i++) { if (i == g_eli_overwrites) bzero(sector, pp->sectorsize); else arc4rand(sector, pp->sectorsize, 0); err = g_write_data(cp, pp->mediasize - pp->sectorsize, sector, pp->sectorsize); if (err != 0) { G_ELI_DEBUG(0, "Cannot erase metadata on %s " "(error=%d).", pp->name, err); if (error == 0) error = err; } } free(sector, M_ELI); } if (error == 0) G_ELI_DEBUG(0, "%s has been killed.", pp->name); g_eli_destroy(sc, 1); return (error); } static void g_eli_ctl_kill(struct gctl_req *req, struct g_class *mp) { int *all, *nargs; int error; g_topology_assert(); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } all = gctl_get_paraml(req, "all", sizeof(*all)); if (all == NULL) { gctl_error(req, "No '%s' argument.", "all"); return; } if (!*all && *nargs == 0) { gctl_error(req, "Too few arguments."); return; } if (*all) { struct g_geom *gp, *gp2; LIST_FOREACH_SAFE(gp, &mp->geom, geom, gp2) { error = g_eli_kill_one(gp->softc); if (error != 0) gctl_error(req, "Not fully done."); } } else { struct g_eli_softc *sc; const char *prov; char param[16]; int i; for (i = 0; i < *nargs; i++) { snprintf(param, sizeof(param), "arg%d", i); prov = gctl_get_asciiparam(req, param); if (prov == NULL) { G_ELI_DEBUG(0, "No 'arg%d' argument.", i); continue; } sc = g_eli_find_device(mp, prov); if (sc == NULL) { G_ELI_DEBUG(0, "No such provider: %s.", prov); continue; } error = g_eli_kill_one(sc); if (error != 0) gctl_error(req, "Not fully done."); } } } void g_eli_config(struct gctl_req *req, struct g_class *mp, const char *verb) { uint32_t *version; g_topology_assert(); version = gctl_get_paraml(req, "version", sizeof(*version)); if (version == NULL) { gctl_error(req, "No '%s' argument.", "version"); return; } if (*version != G_ELI_VERSION) { gctl_error(req, "Userland and kernel parts are out of sync."); return; } if (strcmp(verb, "attach") == 0) g_eli_ctl_attach(req, mp); else if (strcmp(verb, "detach") == 0 || strcmp(verb, "stop") == 0) g_eli_ctl_detach(req, mp); else if (strcmp(verb, "onetime") == 0) g_eli_ctl_onetime(req, mp); + else if (strcmp(verb, "configure") == 0) + g_eli_ctl_configure(req, mp); else if (strcmp(verb, "setkey") == 0) g_eli_ctl_setkey(req, mp); else if (strcmp(verb, "delkey") == 0) g_eli_ctl_delkey(req, mp); else if (strcmp(verb, "kill") == 0) g_eli_ctl_kill(req, mp); else gctl_error(req, "Unknown verb."); }