Compare commits

..

70 Commits

Author SHA1 Message Date
Johannes Bauer
204f85c93f Release v0.04
Have been using this for long enough to consider it stable, releasing it
as v0.04.
2021-09-17 21:50:53 +02:00
Johannes Bauer
78a8cd70c2 Disabled command line default timeout
The command line default timeout of 60 seconds always took precedence,
we removed it. Also added a debug statement that lets the user know how
long they will have to wait.
2021-06-27 13:29:43 +02:00
Johannes Bauer
46b2cdb184 Remove duplicate newline
There's accidently two newlines in the editor, removed.
2021-06-27 13:06:23 +02:00
Johannes Bauer
a6db05b4d0 Allow editing of timeout parameter
Implemented the edit command that allows changing the timeout parameter.
2021-06-27 13:00:06 +02:00
Johannes Bauer
a764cc22e2 Print timeout parameter of database
Changed the list command to show the current timeout value.
2021-06-27 12:54:28 +02:00
Johannes Bauer
323db6d08d Default timeout parameter added
When we're modifying the binary format, we can introduce host-dependent
timeouts as well.
2021-06-27 12:51:51 +02:00
Johannes Bauer
37b239b179 Fixed issue with member access
This is kind of hacky, but it avoids the segfault at runtime. So,
preferrable. If we ever have more options, we need to properly generate
the argv[] array.
2021-06-27 12:32:44 +02:00
Johannes Bauer
b0fc16bfc7 Consistent naming and implemented flag honoring
Name the flag exactly as it's used by LUKS everywhere: allow_discards
(we had in some places "discard", "allow_discard"). Implement actually
honoring that flag if it's set. Untested code.
2021-06-27 09:47:59 +02:00
Johannes Bauer
cd38193993 Allow editing of volume flags
We can now set the "discard" flag of volumes, but it's not yet honored.
2021-06-27 09:42:04 +02:00
Johannes Bauer
bd5caae1ee Introduce new host_flags field
While we're at it with migration, might as well add a host_flags field
so that if we have host-specific configuration flags we want to add
later on, we only have to do a migration once.
2021-06-27 09:17:05 +02:00
Johannes Bauer
af29d9cbf8 Preliminary file migration
Pretty raw and untested code which migrates data from v2 to v3,
introducing a new field in the process. This field is neither editable
as of now nor is it honored if it were set.
2021-06-27 00:28:00 +02:00
Johannes Bauer
b0909557ad Refactoring of version code
We want to introduce a new feature (volumes with discard support) which
will cause file incompatibility.  This means we need to prepare data
migration code. This prepares that change.
2021-06-26 23:34:26 +02:00
Johannes Bauer
bd929be9fa Release v0.03
The previous bugfix commit justifies a new release.
2021-06-26 22:51:56 +02:00
Johannes Bauer
47f7ca6c31 Fix numerous log format issues
We had not declared function attributes that check the format syntax;
this led to a number of issues that remained undetected. Fixed.
2021-06-26 22:48:33 +02:00
Johannes Bauer
265dd0582a Modify keying in vault
We currently derive the dkey from the source key at every open or close
(decrypt or encrypt) operation. However, we want to keep the time that
the internal data is exposed (decrypted) as short as possible. While the
vault is open, there's no problem keeping a copy of the dkey around
(because the data is decrypted anyways, therefore it isn't important).
So we change things around and, at the expense of doubling the time that
decryption takes, we make encryption extremely fast. We do this by
computing the next (rekeyed) key at the start of the decryption routine
(but before the data has been decrypted) and keep the dkey stored in the
vault structure for direct access on the next encryption run.
2019-10-26 12:55:25 +02:00
Johannes Bauer
8681e49561 Slight refactoring of vault code
We want to keep the dkey in the vault structure as long as it's open
(because only the open operation should take long, the close operation
should be really fast).
2019-10-26 10:33:36 +02:00
Johannes Bauer
1a765b6369 Remove unneeded file
Another file that's not necessary anymore, now ditched.
2019-10-26 10:29:40 +02:00
Johannes Bauer
e2bb69144d Remove licensify files
We can do this by hand, no need for a special script.
2019-10-25 20:40:21 +02:00
Johannes Bauer
912b874f7a Spellcheck and remove unused files
Some minor, cosmetic cleanups.
2019-10-25 20:39:55 +02:00
Johannes Bauer
e0444c493e This version of luksrku is incompatible with v0.02
Database format has changed and messaging as well. Document this.
2019-10-25 18:51:45 +02:00
Johannes Bauer
f2e21ebdde Explain more of the internal workings of luksrku
Openness and transparency build trust, document what we're doing so that
it can be easily reviewed.
2019-10-25 18:49:09 +02:00
Johannes Bauer
5400e3716a Documentation of usage
Added documentation of simple usage and also integration into initramfs.
2019-10-25 18:39:10 +02:00
Johannes Bauer
9dc8164dcc Vaulted key database fully used
Now all keys are encrypted when they're not in use to thwart cold-boot
attacks. Furthermore, all unlocking messages are sent in bulk to avoid
fragmentation and improve performance.
2019-10-25 18:17:43 +02:00
Johannes Bauer
f01ec97d6b TLS-PSK now taken out of secure vault, but LUKS passphrases not
LUKS passphrases still broken, they're copied over into the secure vault
but then not used (i.e., the zeroed-out originals are read).
2019-10-25 18:02:51 +02:00
Johannes Bauer
dce9c1b323 Vaulted keydb should work, but it's not used yet
All the methods are implemented to get the vaulted key database running,
but it's not in use yet.
2019-10-25 17:46:21 +02:00
Johannes Bauer
40a0871e03 Vault creation works
We can now generated a vaulted key database from the key database and
cleanse the original key data.
2019-10-25 17:18:09 +02:00
Johannes Bauer
0bf0759c9c Make vault threadsafe
We might have multiple processes accessing the vault and need to always
keep a proper reference count.
2019-10-25 16:30:46 +02:00
Johannes Bauer
54063ec025 Remove duplicate "now" function
We also have this functionality in util, no need to copy it.
2019-10-25 16:21:37 +02:00
Johannes Bauer
6ac94dbd83 Integrate vault into build process
Right now it's still not used, but integrated into the build process
anyways.
2019-10-25 16:16:13 +02:00
Johannes Bauer
17d1b9a52d Remove redundant files and add more info
Show a more informative message when server's been successfully started
and remove unused files.
2019-10-25 16:13:28 +02:00
Johannes Bauer
1469d83a96 Fix default KDF
Inconsistency in KDF documentation fixed.
2019-10-25 13:33:48 +02:00
Johannes Bauer
78104a8b87 Remove debugging and set default timeout
While timeout was announced in "client" help page, it wasn't effective.
Fixed. Also disable debugging.
2019-10-25 13:24:08 +02:00
Johannes Bauer
ba46e5bb43 Adapt initramfs hooks
Unlocking entity is now the client, not the server anymore. Change
filenames and syntax in initramfs scripts to reflect both.
2019-10-25 13:06:20 +02:00
Johannes Bauer
ab670a431a Refactor command execution to not use tempfile
Previously, we wrote the passphrase contents to a temporary file on
/dev/shm and then wiped it afterwards. This is odd, why don't we use a
pipe for this purpose, like it's intended to be used? Replace all of
that previous code by piped IPC.
2019-10-25 13:02:35 +02:00
Johannes Bauer
3478fa4555 Unlocking LUKS volumes works
First complete technical round-trip complete, can unlock the LUKS
volumes described in the server/client databases successfully.
2019-10-25 12:19:01 +02:00
Johannes Bauer
849e3a5949 Implemented finding of keyserver and unlocking of volumes
We'll now parse the response messages on the client side, abort after a
previously defined timeout and trigger the LUKS unlocking process, if
requested (although the latter isn't fully implemented yet).
2019-10-25 11:08:20 +02:00
Johannes Bauer
05e112065e Implemented proper query response on server side
The server now checks the host database and responds correctly, but the
client still does not know how to get that response.
2019-10-25 10:21:29 +02:00
Johannes Bauer
8c7c0e5870 Receiving broadcast messages and plausibility-checking
Now we're receiving the client broadcasts on the server side and
checking if they match the magic number we're expecting.
2019-10-25 09:33:20 +02:00
Johannes Bauer
2f36b56417 Can now receive UDP broadcasts
Still need to figure out how to receive UDP broadcast, but respond as
unicast. Not entirely sure yet.
2019-10-24 19:03:48 +02:00
Johannes Bauer
60b1b2bf39 Refactoring of server code
Consolidate server state into one struct, similar to our client
solution.
2019-10-24 17:04:49 +02:00
Johannes Bauer
39ced77b98 More disabled code removal
Removed the code that was previously the main application.
2019-10-24 16:57:35 +02:00
Johannes Bauer
25649e0caa Add luksrku version in help page
Before we forget to include it, put it right in there so it's easy to
determine which version it was built from.
2019-10-23 22:32:35 +02:00
Johannes Bauer
4ee2739bac Prettify Makefile
Have the dependent objects in alphabetical order.
2019-10-23 22:31:41 +02:00
Johannes Bauer
2a4f2a8e3b Implemented client broadcasting again
Clients now broadcast their host UUID and magic number via UDP, but the
server does not respond nor would the client trigger anything if the
server did.
2019-10-23 22:29:40 +02:00
Johannes Bauer
36f9988fce Cleanup in server socket code
This is ancient programming style. Bring it up to 2019.
2019-10-23 22:13:36 +02:00
Johannes Bauer
6b5ed8f62c Remove unused code
Old, now unused code removed entirely.
2019-10-23 22:12:00 +02:00
Johannes Bauer
1f56e19361 Consolidated session establishment for client and server
Essentially, they share most of the same code. Consolidate everything
into one function.
2019-10-23 22:06:47 +02:00
Johannes Bauer
0e8e42d0ea Client and server commnunication now works
We can send our little datagrams over and that works nicely. Need to
consolidate the PSK session establishment into one shared function.
2019-10-23 21:54:10 +02:00
Johannes Bauer
983217ffbd Further work on the client code
Trying to get everything in shape, not looking too bad.
2019-10-23 21:13:50 +02:00
Johannes Bauer
425e2dcd66 Add client code back in
Client code basis back in, parsing of command line options as well.
Client does not do anything yet, though.
2019-10-23 20:13:25 +02:00
Johannes Bauer
9ea0a9695c Fix bug with commandline parsing
For each parameter, all previous parameters were overwritten with
default values. Fixed.
2019-10-23 20:01:54 +02:00
Johannes Bauer
2143adc91f Added detached thread handling code
Make it easier to create a detached thread, it's always the same and
error-checking is quite repetitive.
2019-10-23 19:47:26 +02:00
Johannes Bauer
8200c9668d Rewrite README
A lot has changed, let's update the README even though it's not all done
yet.
2019-10-23 16:13:23 +02:00
Johannes Bauer
c89ff552d4 Also print OpenSSL command line to debug the server
In debug mode, print the OpenSSL command line needed to connect to a
luksrku server.
2019-10-23 16:03:58 +02:00
Johannes Bauer
603e63876f Server implementation seems to work
Rudimentary functionality of server (not including responding to
announcements over UDP) is working now.
2019-10-23 15:56:06 +02:00
Johannes Bauer
3e5c7d541c Implement actual lookup of luksrku entry
Now with a proper UUID the PSK is looked up from the key database.
2019-10-23 15:28:38 +02:00
Johannes Bauer
d70bd1f672 TLS-PSK connection is working in TLSv1.3
Apparently, I need to spell out "-ciphersuites
TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384" in the openssl
s_client command, or it simply will not work.
2019-10-23 14:28:42 +02:00
Johannes Bauer
969eae12c7 Started with server implementation
Running into issues with TLSv1.3-PSK. Connection establishment does not
work at the moment.
2019-10-23 13:18:51 +02:00
Johannes Bauer
667ff55af1 Integrate editor properly from command line
Now have a way to invoke the editor functionality from the command line
and also provisions to include the server and client parsers.
2019-10-23 11:34:40 +02:00
Johannes Bauer
ecbf3827ca Integrate current state-of-affairs into luksrku
Now integrated into the official Makefile. All functionality is broken
(was for a while), but it's progress nevertheless.
2019-10-23 09:39:40 +02:00
Johannes Bauer
20ffe38b53 Implemented export of key database
Key database is exported on a client-per-client basis, but with
sanitized LUKS passphrases of course. This is implemented now.
2019-10-21 22:47:58 +02:00
Johannes Bauer
722476e7fd Implemented more useful commands
Implemented add/delete operations of hosts and volumes and rekeying of
both as well.
2019-10-21 21:30:29 +02:00
Johannes Bauer
0cb0e5d470 Further work in keydb
Work in transcribing the binary LUKS PSK to ASCII. Still buggy, had an
error in thinking (it's not 4 bytes transcribed to 3, but 3 to 4 of
course). Needs fixing.
2019-10-20 21:09:41 +02:00
Johannes Bauer
bcd794a6c1 Further work on creating correct type-4 UUIDs
Have the UUIDs actually look and feel like Type-4 UUIDs according to
RFC.
2019-10-20 17:45:21 +02:00
Johannes Bauer
ffca14559f Further work on UUIDs and the interactive editor
Listing now works and we've extracted the UUID code into separate files.
2019-10-20 10:12:37 +02:00
Johannes Bauer
68c74de050 Saving and loading of key database works
We now can save and load the database from a file and also add hosts.
2019-10-19 21:52:34 +02:00
Johannes Bauer
9c888cbe4e Major rework of keydb and file encryption
Currently, main program does not compile, massive rework of the internal
database storage mechanism to allow integration of vault and online
editing.
2019-10-19 21:28:26 +02:00
Johannes Bauer
b79ae0b417 Initial work on providing an editor
Just laid out the framework for online editing of the key database,
which was just horrible before.
2019-10-19 18:12:00 +02:00
Johannes Bauer
1790275960 Release v0.02
If this release ever makes it to a disto, we want to be able to identify
it by version number, not by commit. Therefore, introduce re-release
with proper tagging.
2019-10-19 15:08:30 +02:00
Johannes Bauer
73ab437fc9 Include tags in released version number
We want the displayed version number to contain tags, so add it to the
Makefile option.
2019-10-19 15:06:39 +02:00
67 changed files with 4187 additions and 1882 deletions

9
.gitignore vendored
View File

@ -1,11 +1,4 @@
.*.swp
*.o
luksrku
luksrku-config
openssl-1.1.0a.tar.gz
openssl-1.1.0a
client.bin
client.txt
server.bin
server.txt
__pycache__

View File

@ -1,3 +1,8 @@
Summary of changes of v0.02 (2019-10-19)
========================================
* Tiny change that shows the version number as a tag instead of just the
commit number in the help page.
Summary of changes of v0.01 (2019-10-19)
========================================
* Initial release. Have been using it daily for three years and it works

View File

@ -1,49 +1,63 @@
.PHONY: all clean test testclient derive install
all: luksrku luksrku-config
.PHONY: all clean test_s test_c install parsers
all: luksrku
BUILD_REVISION := $(shell git describe --abbrev=10 --dirty --always)
BUILD_REVISION := $(shell git describe --abbrev=10 --dirty --always --tags)
INSTALL_PREFIX := /usr/local/
CFLAGS := -Wall -Wextra -Wshadow -Wswitch -Wpointer-arith -Wcast-qual -Wstrict-prototypes -Wmissing-prototypes -Werror=implicit-function-declaration -Werror=format -Wno-unused-parameter
#CFLAGS := -Wall -Wextra -O2 -Wmissing-prototypes -Wstrict-prototypes
CFLAGS += -std=c11 -pthread -D_POSIX_SOURCE -D_XOPEN_SOURCE=500 -DBUILD_REVISION='"$(BUILD_REVISION)"'
#CFLAGS += -g -DDEBUG
CFLAGS += -O3 -std=c11 -pthread -D_POSIX_SOURCE -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=500 -DBUILD_REVISION='"$(BUILD_REVISION)"'
CFLAGS += `pkg-config --cflags openssl`
#CFLAGS += -ggdb3 -DDEBUG -fsanitize=address -fsanitize=undefined -fsanitize=leak
PYPGMOPTS := ../Python/pypgmopts/pypgmopts
LDFLAGS := `pkg-config --libs openssl`
TEST_PREFIX := local
OBJS := luksrku.o server.o log.o openssl.o client.o keyfile.o msg.o binkeyfile.o util.o cmdline.o luks.o exec.o blacklist.o
OBJS_CFG := luksrku-config.o keyfile.o binkeyfile.o parse-keyfile.o openssl.o log.o util.o
OBJS := \
argparse_client.o \
argparse_edit.o \
argparse_server.o \
blacklist.o \
client.o \
editor.o \
exec.o \
file_encryption.o \
keydb.o \
log.o \
luks.o \
luksrku.o \
openssl.o \
pgmopts.o \
server.o \
signals.o \
thread.o \
udp.o \
util.o \
uuid.o \
vaulted_keydb.o \
vault.o
parsers:
$(PYPGMOPTS) -n edit parsers/parser_edit.py
$(PYPGMOPTS) -n server parsers/parser_server.py
$(PYPGMOPTS) -n client parsers/parser_client.py
install: all
strip luksrku luksrku-config
cp luksrku luksrku-config $(INSTALL_PREFIX)sbin/
chown root:root $(INSTALL_PREFIX)sbin/luksrku $(INSTALL_PREFIX)sbin/luksrku-config
chmod 755 $(INSTALL_PREFIX)sbin/luksrku $(INSTALL_PREFIX)sbin/luksrku-config
strip luksrku
cp luksrku $(INSTALL_PREFIX)sbin/
chown root:root $(INSTALL_PREFIX)sbin/luksrku
chmod 755 $(INSTALL_PREFIX)sbin/luksrku
clean:
rm -f $(OBJS) $(OBJS_CFG) luksrku luksrku-config
rm -f $(OBJS) $(OBJS_CFG) luksrku
valgrind: luksrku
valgrind --leak-check=full --show-leak-kinds=all ./luksrku -v --client-mode -k client_keys.bin
test_s: luksrku
./luksrku server -vv testdata/$(TEST_PREFIX)_server.bin
test: luksrku
./luksrku -v --server-mode -k server_key.bin
gdb: luksrku
gdb --args ./luksrku -v --server-mode -k server_key.bin
testclient: luksrku
./luksrku -v --client-mode -k client_keys.bin
derive: luksrku-config
./luksrku-config server server_key.txt server_key.bin
./luksrku-config client client_keys.txt client_keys.bin
test_c: luksrku
./luksrku client -vv --no-luks testdata/$(TEST_PREFIX)_client.bin
.c.o:
$(CC) $(CFLAGS) -c -o $@ $<
luksrku: $(OBJS)
$(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS)
luksrku-config: $(OBJS_CFG)
$(CC) $(CFLAGS) -o $@ $(OBJS_CFG) $(LDFLAGS)

319
README.md
View File

@ -1,133 +1,232 @@
Disclaimer
==========
**Warning** luksrku is currently *highly* experimental software. It is not
intended for production use yet. It is released following the "release early,
release often" philosophy in the hope to get valuable feedback for possible
areas of improvement. Please only use it when you're pretty certain that you
know what you're doing. Better yet, only use it after code review. If you've
reviewed my code, please let me know. I'm very interested in any and all
feedback. Drop it at joe@johannes-bauer.com, please. Thanks!
# luksrku
luksrku is a tool that allows you to remotely unlock LUKS disks during boot up
from within your initrd. The intention is to have full-disk-encryption with
LUKS-rootfs running headlessly. You should be able to remotely unlock their
LUKS cryptographic file systems when you know they have been (legitimately)
rebooted.
luksrku
=======
luksrku is a tool that allows you to remotely unlock LUKS disks during bootup.
The intention is to have headless systems running and you should be able to
remotely unlock their LUKS cryptographic file systems when you know they have
been (legitimately) rebooted. This works as follows: The *TLS server* runs on
the computer which needs unlocking. This computer broadcasts a UDP packet onto
the network indicating that it needs unlocking. The *TLS client* which knows
the LUKS passphrase then catches that packet, connect to the server and sends
the passphrase. The TLS configuration that is used ensures mutual
authentication and perfect forward secrecy. Concretely, TLS v1.2 is used with a
ECDHE handshake on Curve25519 and using the ECDHE-PSK-CHACHA20-POLY1305 cipher
suite. For authentication, a 256 bit long random PSK is used. The passphrase
for unlocking should be in a own keyslot (i.e., do not use a passphrase which
you remember).
This works as follows: The luksrku client (which needs unlocking) and luksrku
server (which holds all the LUKS keys) share a secret. The client either knows
the address of the server or it can issue a broadcast in the network to find
the correct one. With the help of the shared secret, a TLS connection is
established between the client and a legitimate server (who also knows the same
secret). The server then tells the client all the LUKS passphrases, which
performs luksOpen on all volumes.
Configuration
=============
Clients and servers use a configuration file. This is originally a text file
that is then converted to encrypted binary format using the luksrku-config
tool. This binary configuration file is encrypted using AES256-GCM, uses a 128
bit randomized initialization vector and authenticated with a 128 bit
authentication tag. The key derivation function which is used to derive the 256
bit AES key from the passphrase is scrypt with N = 131072, r = 8, p = 1.
## Security
luksrku uses TLSv1.3-PSK with forward-secrecy key shares (i.e., ECDHE). The
curves that are used for key agreement are X448 and X25519.
TLS_CHACHA20_POLY1305_SHA256 or TLS_AES_256_GCM_SHA384 are accepted as cipher
suites.
Storing in a binary format serves two purposes:
1. Error-prone parsing of human-modifiable text is done in a separate
application. These chunks of code are not linked into the luksrku binary.
2. It allows for easy encryption.
The TLS PSKs are 256 bit long and randomly generated (`/dev/urandom`).
Likewise, the LUKS passphrases are based on 256 bit long secrets, also
generated from `/dev/urandom`, and are converted to Base64 for easier handling
(when setting up everything initially).
The server key database contains no secrets, yet it is encrypted nevertheless.
The sole purpose is to keep the number of alternative code paths minimal. There
is no technical reason to encrypt the server configuration file, but again: it
contains *no* secrets. Using the same storage for server and client was maybe
an awkward design choice, but this is something that is ugly, but not
security-critical.
The binary protocol that runs between server and client is intentionally
extremely simple to allow for easy code review. It exclusively uses fixed
message lengths. There are two portions to it, an UDP and a TCP portion:
The client key database contains the LUKS keys, therefore it is advisable to
keep it encrypted with a passphrase. Only if this passphrase is correctly
entered on the client, the password can be decrypted on the client and
transmitted to the server. Note that care is taken to ensure no
length-of-message side channels reveal information about the underlying LUKS
passphrase. Therefore the transmitted messages are always of the same length.
Via UDP, a client broadcasts its client UUID (randomly generated when creating
the client in the database) on the network (port 23170). A server then can
check if it's key database contains that client's LUKS keys. If it does, the
server will respond with a fixed unicast UDP datagram. The client receives this
datagram and tries to establish a TCP connection to that server on the luksrku
port 23170. This connection is secured using TLSv1.3-PSK, i.e., even when the
UDP messages are spoofed/forged, a successful connection will only then happen
if the server and client share the same, previously defined, PSK.
The PSK that is used to communicate between client and server ensures mutual
authentication. If the PSK is stolen by an adversary, that adversary can simply
pose as a server and ask the client for the LUKS key. Therefore it is integral
that this PSK is kept safe. Passive attacks (i.e., where the adversary is only
eavesdropping on communication), however, are not compromised because the TLS
channel provides PFS.
For persistent storage, the key database is encrypted, using AES256-GCM. A 128
bit randomized initialization vector is used and all data is authenticated with
a 128 bit authentication tag. Key derivation is done using scrypt with N =
262144 = 2^18, r = 8, p = 1 (although this is flexible in code and can be
easily adapted).
Prerequisites
=============
Since the used cryptography (such as ECDH on Curve25519 and the
ECDHE-PSK-CHACHA20-POLY1305 cipher suite) are fairly new, support for at least
OpenSSL-1.1.0 is essential.
When the key database is not in use, the server encrypts all LUKS passphrases
and PSKs in-memory (again, using AES256-GCM). A large, 1 MiB pre-key is also
kept in memory. The AES key is derived from this pre-key using
PBKDF2-HMAC-SHA256 and an iteration count that results in ~25ms key derivation.
While it might seem nonsensical to encrypt memory and have the key right next
to the encrypted data, the reason for this this is to thwart cold-boot attacks.
A successful cold-boot attack would require a complete and perfect 1 MiB
snapshot of the pre-key (or an acquisition in the short timeframe where the
key vault is open) -- something that is difficult to do because of naturally
occurring bit errors during cold boot acquisition.
## Dependencies
OpenSSL v1.1 is required for luksrku as well as pkg-config.
Example
=======
This is a very crude example. Feel free to improve it and send a PR. Let's say
we want to unlock the crypt-root of a headless system. I.e., only one LUKS
partition that should be unlocked. That LUKS partition has the UUID of
952ebed9-5256-4b4c-9de5-7f8829b4a74a (use blkid to find out). This is what we
can do:
1. Build >=OpenSSL-1.1.0 (e.g., using the provided ./build_openssl command)
2. Build and install luksrku: make && sudo make install
3. Generate the keyfiles. For this we use the provided gen_config script:
## Usage
The help pages of luksrku are fairly well documented, i.e.:
```
Disk UUID : 952ebed9-5256-4b4c-9de5-7f8829b4a74a
Disk name : crypt-root
Suggestion: TDFV6Z6XyDQ52ASswVFSEl8mrVfnH9F5b
Passphrase:
$ ./luksrku
error: no command supplied
Disk UUID :
# server.txt
# Host UUID Host PSK Disk UUIDs
d66f96fc-7056-46e1-aea6-0f3d705cd3bc d94f3fc6c3507123bda4034dd8c865a1b4cf9870bda50e9ed9f861621d581017 952ebed9-5256-4b4c-9de5-7f8829b4a74a=crypt-root
Available commands:
./luksrku edit Interactively edit a key database
./luksrku server Start a key server process
./luksrku client Unlock LUKS volumes by querying a key server
# client.txt
# Host UUID Host PSK Disk UUIDs
d66f96fc-7056-46e1-aea6-0f3d705cd3bc d94f3fc6c3507123bda4034dd8c865a1b4cf9870bda50e9ed9f861621d581017 952ebed9-5256-4b4c-9de5-7f8829b4a74a=54444656365a3658794451353241537377564653456c386d7256666e4839463562
For further help: ./luksrku (command) --help
luksrku version v0.02-45-gf01ec97d6b-dirty
```
4. We want to use the suggested passphrase, which should contain 192 bits of
entropy. For this, ee use cryptsetup luksAddKey to add the suggested
passphrase to the LUKS keyring on the server.
5. The config script has given suggestions for server.txt and client.txt. We
copy the respective contents into the files.
6. Then we create the client and server binary configuration files:
Then, for each command, you have an own help page:
```
$ luksrku-config server server.txt server.bin
Successfully read key file with 1 entries.
$ luksrku-config client client.txt client.bin
Successfully read key file with 1 entries.
Passphrase to encrypt keyfile:
$ ./luksrku edit --help
usage: luksrku edit [-v] [filename]
Edits a luksrku key database.
positional arguments:
filename Database file to edit.
optional arguments:
-v, --verbose Increase verbosity. Can be specified multiple times.
```
7. Now we'll have a server.bin and password-protected client.bin. On the
server machine (i.e., the one with the LUKS disk) we copy server.bin to
/etc/luksrku-server.bin.
8. On the server, we modify the luksrku-script in the initramfs/ subdirectory
to fit the NIC of the server and the IP address we want (this is really
ugly at the moment and needs to be fixed ASAP, but it is what it is now).
9. On the server, then run the "./install" script as root which will install
initramfs hooks.
10. On the server, update the initramfs (update-initramfs -u). Previously make
a copy of your initramfs so that you can boot your system in case things
go wrong (which they will, trust me).
11. Boot the server. If everything went fine (it won't at the first run), it
will now broadcast UDP packets onto the network indicating its presence.
These packets will be sent to UDP port 23170.
12. On the client, start the client to unlock the server's key:
```
$ luksrku --client-mode -k client.bin
Keyfile password:
$ ./luksrku server --help
usage: luksrku server [-p port] [-s] [-v] filename
Starts a luksrku key server.
positional arguments:
filename Database file to load keys from.
optional arguments:
-p port, --port port Port that is used for both UDP and TCP communication.
Defaults to 23170.
-s, --silent Do not answer UDP queries for clients trying to find a
key server, only serve key database using TCP.
-v, --verbose Increase verbosity. Can be specified multiple times.
```
```
$ ./luksrku client --help
usage: luksrku client [-t secs] [-p port] [--no-luks] [-v] filename [hostname]
Connects to a luksrku key server and unlocks local LUKS volumes.
positional arguments:
filename Exported database file to load TLS-PSKs and list of
disks from.
hostname When hostname is given, auto-searching for suitable
servers is disabled and only a connection to the given
hostname is attempted.
optional arguments:
-t secs, --timeout secs
When searching for a keyserver and not all volumes can
be unlocked, abort after this period of time, given in
seconds. Defaults to 60 seconds.
-p port, --port port Port that is used for both UDP and TCP communication.
Defaults to 23170.
--no-luks Do not call LUKS/cryptsetup. Useful for testing
unlocking procedure.
-v, --verbose Increase verbosity. Can be specified multiple times.
```
## Example
First, you need to create a server key database. For this you use the editor:
```
$ ./luksrku edit
> add_host my_host
```
Now there's a host "my_host" in the key database. At any point you can inspect
the database by using the "list" command:
```
Keydb version 2, server database, 1 hosts.
Host 1: "my_host" UUID e7ff6e3d-1793-48f6-b43b-9c7bb0348622 -- 0 volumes:
```
You'll see that the host has no volumes associated with it. Determine the UUID
of the LUKS device that you want luksrku to decrypt, then add this volume with
the name you want it to have after unlocking. In our case, the UUID is
18de9f14-2914-4a8b-9b46-b7deacbfbe8a and we want it to decrypt as "crypt-root":
```
> add_volume my_host crypt-root 18de9f14-2914-4a8b-9b46-b7deacbfbe8a
LUKS passphrase of crypt-root / 18de9f14-2914-4a8b-9b46-b7deacbfbe8a: 5DySDFcpVtBRoIMNv7mrLqlozPYeq7X5kPmB3M1wsW8A
```
At this point, luksrku will tell you, in clear text, the LUKS passphrase that
you need to add to the volume. Then, you save the server database:
```
> save server.bin
Database passphrase:
```
It asks you for a passphrase that is needed to decrypt the file. On disk it's
always stored encrypted. Using an encrypted server database is highly
recommended.
For the client, you export the client portion of the database:
```
> export my_host my_host.bin
Client passphrase:
```
Note that client databases can also be encrypted, but they're less critical
than the server database. The client database does *not* contain the LUKS
passphrases, it only contains the required TLS-PSK so that a successful
connection to a luksrku server can be established.
With these two in place, you can now start a luksrku server:
```
$ ./luksrku server server.bin
Database passphrase:
[I]: Serving luksrku database for 1 hosts.
```
And on your client, when you want the LUKS disks to be unlocked:
```
$ ./luksrku client my_host.bin
```
## Integration into initramfs
Using luksrku as part of your initramfs is quite easy. You'll need a server
somewhere in your network and an exported client database. On the client, you
copy the client database file into `/etc/luksrku-client.bin`.
Then, install luksrku globally by performing `make install` as root and install
the initramfs script by running `install` in the initramfs/ subdirectory.
You'll only need to install that once.
```
# make install
strip luksrku
cp luksrku /usr/local/sbin/
chown root:root /usr/local/sbin/luksrku
chmod 755 /usr/local/sbin/luksrku
# cd initramfs
# ./install
```
Finally, have initramfs recreate your initial ramdisk:
```
# update-initramfs -u
```
That's it, it should now work.
## Legacy version
luksrku has undergone an extensive rewrite of the internal code. The current
version v0.03 is compatible only to versions >= 0.02. For earlier versions,
servers and clients will not recognize each other, database formats and the
creation of database works entirely different.
## License
GNU GPL-3.

213
argparse_client.c Normal file
View File

@ -0,0 +1,213 @@
/*
* This file was AUTO-GENERATED by pypgmopts.
*
* https://github.com/johndoe31415/pypgmopts
*
* Do not edit it by hand, your changes will be overwritten.
*
* Generated at: 2021-06-27 13:24:40
*/
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <stdarg.h>
#include <string.h>
#include "argparse_client.h"
static enum argparse_client_option_t last_parsed_option;
static char last_error_message[256];
static const char *option_texts[] = {
[ARG_CLIENT_TIMEOUT] = "-t / --timeout",
[ARG_CLIENT_PORT] = "-p / --port",
[ARG_CLIENT_NO_LUKS] = "--no-luks",
[ARG_CLIENT_VERBOSE] = "-v / --verbose",
[ARG_CLIENT_FILENAME] = "filename",
[ARG_CLIENT_HOSTNAME] = "hostname",
};
enum argparse_client_option_internal_t {
ARG_CLIENT_TIMEOUT_SHORT = 't',
ARG_CLIENT_PORT_SHORT = 'p',
ARG_CLIENT_VERBOSE_SHORT = 'v',
ARG_CLIENT_TIMEOUT_LONG = 1000,
ARG_CLIENT_PORT_LONG = 1001,
ARG_CLIENT_NO_LUKS_LONG = 1002,
ARG_CLIENT_VERBOSE_LONG = 1003,
ARG_CLIENT_FILENAME_LONG = 1004,
ARG_CLIENT_HOSTNAME_LONG = 1005,
};
static void errmsg_callback(const char *errmsg, ...) {
va_list ap;
va_start(ap, errmsg);
vsnprintf(last_error_message, sizeof(last_error_message), errmsg, ap);
va_end(ap);
}
static void errmsg_option_callback(enum argparse_client_option_t error_option, const char *errmsg, ...) {
last_parsed_option = error_option;
va_list ap;
va_start(ap, errmsg);
vsnprintf(last_error_message, sizeof(last_error_message), errmsg, ap);
va_end(ap);
}
bool argparse_client_parse(int argc, char **argv, argparse_client_callback_t argument_callback, argparse_client_plausibilization_callback_t plausibilization_callback) {
last_parsed_option = ARGPARSE_CLIENT_NO_OPTION;
const char *short_options = "t:p:v";
struct option long_options[] = {
{ "timeout", required_argument, 0, ARG_CLIENT_TIMEOUT_LONG },
{ "port", required_argument, 0, ARG_CLIENT_PORT_LONG },
{ "no-luks", no_argument, 0, ARG_CLIENT_NO_LUKS_LONG },
{ "verbose", no_argument, 0, ARG_CLIENT_VERBOSE_LONG },
{ "filename", required_argument, 0, ARG_CLIENT_FILENAME_LONG },
{ "hostname", required_argument, 0, ARG_CLIENT_HOSTNAME_LONG },
{ 0 }
};
while (true) {
int optval = getopt_long(argc, argv, short_options, long_options, NULL);
if (optval == -1) {
break;
}
last_error_message[0] = 0;
enum argparse_client_option_internal_t arg = (enum argparse_client_option_internal_t)optval;
switch (arg) {
case ARG_CLIENT_TIMEOUT_SHORT:
case ARG_CLIENT_TIMEOUT_LONG:
last_parsed_option = ARG_CLIENT_TIMEOUT;
if (!argument_callback(ARG_CLIENT_TIMEOUT, optarg, errmsg_callback)) {
return false;
}
break;
case ARG_CLIENT_PORT_SHORT:
case ARG_CLIENT_PORT_LONG:
last_parsed_option = ARG_CLIENT_PORT;
if (!argument_callback(ARG_CLIENT_PORT, optarg, errmsg_callback)) {
return false;
}
break;
case ARG_CLIENT_NO_LUKS_LONG:
last_parsed_option = ARG_CLIENT_NO_LUKS;
if (!argument_callback(ARG_CLIENT_NO_LUKS, optarg, errmsg_callback)) {
return false;
}
break;
case ARG_CLIENT_VERBOSE_SHORT:
case ARG_CLIENT_VERBOSE_LONG:
last_parsed_option = ARG_CLIENT_VERBOSE;
if (!argument_callback(ARG_CLIENT_VERBOSE, optarg, errmsg_callback)) {
return false;
}
break;
default:
last_parsed_option = ARGPARSE_CLIENT_NO_OPTION;
errmsg_callback("unrecognized option supplied");
return false;
}
}
const int positional_argument_cnt = argc - optind;
const int flexible_positional_args_cnt = positional_argument_cnt - 1;
last_parsed_option = ARGPARSE_CLIENT_POSITIONAL_ARG;
if (positional_argument_cnt < 1) {
errmsg_callback("expected a minimum of 1 positional argument, but %d given.", positional_argument_cnt);
return false;
}
if (positional_argument_cnt > 2) {
errmsg_callback("expected a maximum of 2 positional arguments, but %d given.", positional_argument_cnt);
return false;
}
int positional_index = optind;
last_parsed_option = ARG_CLIENT_FILENAME;
if (!argument_callback(ARG_CLIENT_FILENAME, argv[positional_index++], errmsg_callback)) {
return false;
}
last_parsed_option = ARG_CLIENT_HOSTNAME;
for (int i = 0; i < flexible_positional_args_cnt; i++) {
if (!argument_callback(ARG_CLIENT_HOSTNAME, argv[positional_index++], errmsg_callback)) {
return false;
}
}
if (plausibilization_callback) {
if (!plausibilization_callback(errmsg_option_callback)) {
return false;
}
}
return true;
}
void argparse_client_show_syntax(void) {
fprintf(stderr, "usage: luksrku client [-t secs] [-p port] [--no-luks] [-v] filename [hostname]\n");
fprintf(stderr, "\n");
fprintf(stderr, "Connects to a luksrku key server and unlocks local LUKS volumes.\n");
fprintf(stderr, "\n");
fprintf(stderr, "positional arguments:\n");
fprintf(stderr, " filename Exported database file to load TLS-PSKs and list of disks from.\n");
fprintf(stderr, " hostname When hostname is given, auto-searching for suitable servers is disabled and\n");
fprintf(stderr, " only a connection to the given hostname is attempted.\n");
fprintf(stderr, "\n");
fprintf(stderr, "optional arguments:\n");
fprintf(stderr, " -t secs, --timeout secs\n");
fprintf(stderr, " When searching for a keyserver and not all volumes can be unlocked, abort\n");
fprintf(stderr, " after this period of time, given in seconds. Defaults to infinity. This\n");
fprintf(stderr, " argument can be specified as a host-based configuration parameter as well;\n");
fprintf(stderr, " the command-line argument always takes precedence.\n");
fprintf(stderr, " -p port, --port port Port that is used for both UDP and TCP communication. Defaults to 23170.\n");
fprintf(stderr, " --no-luks Do not call LUKS/cryptsetup. Useful for testing unlocking procedure.\n");
fprintf(stderr, " -v, --verbose Increase verbosity. Can be specified multiple times.\n");
}
void argparse_client_parse_or_quit(int argc, char **argv, argparse_client_callback_t argument_callback, argparse_client_plausibilization_callback_t plausibilization_callback) {
if (!argparse_client_parse(argc, argv, argument_callback, plausibilization_callback)) {
if (last_parsed_option > ARGPARSE_CLIENT_POSITIONAL_ARG) {
if (last_error_message[0]) {
fprintf(stderr, "luksrku client: error parsing argument %s -- %s\n", option_texts[last_parsed_option], last_error_message);
} else {
fprintf(stderr, "luksrku client: error parsing argument %s -- no details available\n", option_texts[last_parsed_option]);
}
} else if (last_parsed_option == ARGPARSE_CLIENT_POSITIONAL_ARG) {
fprintf(stderr, "luksrku client: error parsing optional arguments -- %s\n", last_error_message);
}
argparse_client_show_syntax();
exit(EXIT_FAILURE);
}
}
#ifdef __ARGPARSE_MAIN__
/* gcc -D __ARGPARSE_MAIN__ -O2 -Wall -o argparse argparse_client.c
*/
static const char *option_enum_to_str(enum argparse_client_option_t option) {
switch (option) {
case ARG_CLIENT_TIMEOUT: return "ARG_CLIENT_TIMEOUT";
case ARG_CLIENT_PORT: return "ARG_CLIENT_PORT";
case ARG_CLIENT_NO_LUKS: return "ARG_CLIENT_NO_LUKS";
case ARG_CLIENT_VERBOSE: return "ARG_CLIENT_VERBOSE";
case ARG_CLIENT_FILENAME: return "ARG_CLIENT_FILENAME";
case ARG_CLIENT_HOSTNAME: return "ARG_CLIENT_HOSTNAME";
}
return "UNKNOWN";
}
bool arg_print_callback(enum argparse_client_option_t option, const char *value, argparse_client_errmsg_callback_t errmsg_callback) {
fprintf(stderr, "%s = \"%s\"\n", option_enum_to_str(option), value);
return true;
}
int main(int argc, char **argv) {
argparse_client_parse_or_quit(argc, argv, arg_print_callback, NULL);
return 0;
}
#endif

41
argparse_client.h Normal file
View File

@ -0,0 +1,41 @@
/*
* This file was AUTO-GENERATED by pypgmopts.
*
* https://github.com/johndoe31415/pypgmopts
*
* Do not edit it by hand, your changes will be overwritten.
*
* Generated at: 2021-06-27 13:24:40
*/
#ifndef __ARGPARSE_CLIENT_H__
#define __ARGPARSE_CLIENT_H__
#include <stdbool.h>
#define ARGPARSE_CLIENT_DEFAULT_TIMEOUT 0
#define ARGPARSE_CLIENT_DEFAULT_PORT 23170
#define ARGPARSE_CLIENT_DEFAULT_VERBOSE 0
#define ARGPARSE_CLIENT_NO_OPTION 0
#define ARGPARSE_CLIENT_POSITIONAL_ARG 1
enum argparse_client_option_t {
ARG_CLIENT_TIMEOUT = 2,
ARG_CLIENT_PORT = 3,
ARG_CLIENT_NO_LUKS = 4,
ARG_CLIENT_VERBOSE = 5,
ARG_CLIENT_FILENAME = 6,
ARG_CLIENT_HOSTNAME = 7,
};
typedef void (*argparse_client_errmsg_callback_t)(const char *errmsg, ...);
typedef void (*argparse_client_errmsg_option_callback_t)(enum argparse_client_option_t error_option, const char *errmsg, ...);
typedef bool (*argparse_client_callback_t)(enum argparse_client_option_t option, const char *value, argparse_client_errmsg_callback_t errmsg_callback);
typedef bool (*argparse_client_plausibilization_callback_t)(argparse_client_errmsg_option_callback_t errmsg_callback);
bool argparse_client_parse(int argc, char **argv, argparse_client_callback_t argument_callback, argparse_client_plausibilization_callback_t plausibilization_callback);
void argparse_client_show_syntax(void);
void argparse_client_parse_or_quit(int argc, char **argv, argparse_client_callback_t argument_callback, argparse_client_plausibilization_callback_t plausibilization_callback);
#endif

159
argparse_edit.c Normal file
View File

@ -0,0 +1,159 @@
/*
* This file was AUTO-GENERATED by pypgmopts.
*
* https://github.com/johndoe31415/pypgmopts
*
* Do not edit it by hand, your changes will be overwritten.
*
* Generated at: 2021-06-27 13:24:40
*/
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <stdarg.h>
#include <string.h>
#include "argparse_edit.h"
static enum argparse_edit_option_t last_parsed_option;
static char last_error_message[256];
static const char *option_texts[] = {
[ARG_EDIT_VERBOSE] = "-v / --verbose",
[ARG_EDIT_FILENAME] = "filename",
};
enum argparse_edit_option_internal_t {
ARG_EDIT_VERBOSE_SHORT = 'v',
ARG_EDIT_VERBOSE_LONG = 1000,
ARG_EDIT_FILENAME_LONG = 1001,
};
static void errmsg_callback(const char *errmsg, ...) {
va_list ap;
va_start(ap, errmsg);
vsnprintf(last_error_message, sizeof(last_error_message), errmsg, ap);
va_end(ap);
}
static void errmsg_option_callback(enum argparse_edit_option_t error_option, const char *errmsg, ...) {
last_parsed_option = error_option;
va_list ap;
va_start(ap, errmsg);
vsnprintf(last_error_message, sizeof(last_error_message), errmsg, ap);
va_end(ap);
}
bool argparse_edit_parse(int argc, char **argv, argparse_edit_callback_t argument_callback, argparse_edit_plausibilization_callback_t plausibilization_callback) {
last_parsed_option = ARGPARSE_EDIT_NO_OPTION;
const char *short_options = "v";
struct option long_options[] = {
{ "verbose", no_argument, 0, ARG_EDIT_VERBOSE_LONG },
{ "filename", required_argument, 0, ARG_EDIT_FILENAME_LONG },
{ 0 }
};
while (true) {
int optval = getopt_long(argc, argv, short_options, long_options, NULL);
if (optval == -1) {
break;
}
last_error_message[0] = 0;
enum argparse_edit_option_internal_t arg = (enum argparse_edit_option_internal_t)optval;
switch (arg) {
case ARG_EDIT_VERBOSE_SHORT:
case ARG_EDIT_VERBOSE_LONG:
last_parsed_option = ARG_EDIT_VERBOSE;
if (!argument_callback(ARG_EDIT_VERBOSE, optarg, errmsg_callback)) {
return false;
}
break;
default:
last_parsed_option = ARGPARSE_EDIT_NO_OPTION;
errmsg_callback("unrecognized option supplied");
return false;
}
}
const int positional_argument_cnt = argc - optind;
const int flexible_positional_args_cnt = positional_argument_cnt - 0;
last_parsed_option = ARGPARSE_EDIT_POSITIONAL_ARG;
if (positional_argument_cnt < 0) {
errmsg_callback("expected a minimum of 0 positional arguments, but %d given.", positional_argument_cnt);
return false;
}
if (positional_argument_cnt > 1) {
errmsg_callback("expected a maximum of 1 positional argument, but %d given.", positional_argument_cnt);
return false;
}
int positional_index = optind;
last_parsed_option = ARG_EDIT_FILENAME;
for (int i = 0; i < flexible_positional_args_cnt; i++) {
if (!argument_callback(ARG_EDIT_FILENAME, argv[positional_index++], errmsg_callback)) {
return false;
}
}
if (plausibilization_callback) {
if (!plausibilization_callback(errmsg_option_callback)) {
return false;
}
}
return true;
}
void argparse_edit_show_syntax(void) {
fprintf(stderr, "usage: luksrku edit [-v] [filename]\n");
fprintf(stderr, "\n");
fprintf(stderr, "Edits a luksrku key database.\n");
fprintf(stderr, "\n");
fprintf(stderr, "positional arguments:\n");
fprintf(stderr, " filename Database file to edit.\n");
fprintf(stderr, "\n");
fprintf(stderr, "optional arguments:\n");
fprintf(stderr, " -v, --verbose Increase verbosity. Can be specified multiple times.\n");
}
void argparse_edit_parse_or_quit(int argc, char **argv, argparse_edit_callback_t argument_callback, argparse_edit_plausibilization_callback_t plausibilization_callback) {
if (!argparse_edit_parse(argc, argv, argument_callback, plausibilization_callback)) {
if (last_parsed_option > ARGPARSE_EDIT_POSITIONAL_ARG) {
if (last_error_message[0]) {
fprintf(stderr, "luksrku edit: error parsing argument %s -- %s\n", option_texts[last_parsed_option], last_error_message);
} else {
fprintf(stderr, "luksrku edit: error parsing argument %s -- no details available\n", option_texts[last_parsed_option]);
}
} else if (last_parsed_option == ARGPARSE_EDIT_POSITIONAL_ARG) {
fprintf(stderr, "luksrku edit: error parsing optional arguments -- %s\n", last_error_message);
}
argparse_edit_show_syntax();
exit(EXIT_FAILURE);
}
}
#ifdef __ARGPARSE_MAIN__
/* gcc -D __ARGPARSE_MAIN__ -O2 -Wall -o argparse argparse_edit.c
*/
static const char *option_enum_to_str(enum argparse_edit_option_t option) {
switch (option) {
case ARG_EDIT_VERBOSE: return "ARG_EDIT_VERBOSE";
case ARG_EDIT_FILENAME: return "ARG_EDIT_FILENAME";
}
return "UNKNOWN";
}
bool arg_print_callback(enum argparse_edit_option_t option, const char *value, argparse_edit_errmsg_callback_t errmsg_callback) {
fprintf(stderr, "%s = \"%s\"\n", option_enum_to_str(option), value);
return true;
}
int main(int argc, char **argv) {
argparse_edit_parse_or_quit(argc, argv, arg_print_callback, NULL);
return 0;
}
#endif

35
argparse_edit.h Normal file
View File

@ -0,0 +1,35 @@
/*
* This file was AUTO-GENERATED by pypgmopts.
*
* https://github.com/johndoe31415/pypgmopts
*
* Do not edit it by hand, your changes will be overwritten.
*
* Generated at: 2021-06-27 13:24:40
*/
#ifndef __ARGPARSE_EDIT_H__
#define __ARGPARSE_EDIT_H__
#include <stdbool.h>
#define ARGPARSE_EDIT_DEFAULT_VERBOSE 0
#define ARGPARSE_EDIT_NO_OPTION 0
#define ARGPARSE_EDIT_POSITIONAL_ARG 1
enum argparse_edit_option_t {
ARG_EDIT_VERBOSE = 2,
ARG_EDIT_FILENAME = 3,
};
typedef void (*argparse_edit_errmsg_callback_t)(const char *errmsg, ...);
typedef void (*argparse_edit_errmsg_option_callback_t)(enum argparse_edit_option_t error_option, const char *errmsg, ...);
typedef bool (*argparse_edit_callback_t)(enum argparse_edit_option_t option, const char *value, argparse_edit_errmsg_callback_t errmsg_callback);
typedef bool (*argparse_edit_plausibilization_callback_t)(argparse_edit_errmsg_option_callback_t errmsg_callback);
bool argparse_edit_parse(int argc, char **argv, argparse_edit_callback_t argument_callback, argparse_edit_plausibilization_callback_t plausibilization_callback);
void argparse_edit_show_syntax(void);
void argparse_edit_parse_or_quit(int argc, char **argv, argparse_edit_callback_t argument_callback, argparse_edit_plausibilization_callback_t plausibilization_callback);
#endif

181
argparse_server.c Normal file
View File

@ -0,0 +1,181 @@
/*
* This file was AUTO-GENERATED by pypgmopts.
*
* https://github.com/johndoe31415/pypgmopts
*
* Do not edit it by hand, your changes will be overwritten.
*
* Generated at: 2021-06-27 13:24:40
*/
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <stdarg.h>
#include <string.h>
#include "argparse_server.h"
static enum argparse_server_option_t last_parsed_option;
static char last_error_message[256];
static const char *option_texts[] = {
[ARG_SERVER_PORT] = "-p / --port",
[ARG_SERVER_SILENT] = "-s / --silent",
[ARG_SERVER_VERBOSE] = "-v / --verbose",
[ARG_SERVER_FILENAME] = "filename",
};
enum argparse_server_option_internal_t {
ARG_SERVER_PORT_SHORT = 'p',
ARG_SERVER_SILENT_SHORT = 's',
ARG_SERVER_VERBOSE_SHORT = 'v',
ARG_SERVER_PORT_LONG = 1000,
ARG_SERVER_SILENT_LONG = 1001,
ARG_SERVER_VERBOSE_LONG = 1002,
ARG_SERVER_FILENAME_LONG = 1003,
};
static void errmsg_callback(const char *errmsg, ...) {
va_list ap;
va_start(ap, errmsg);
vsnprintf(last_error_message, sizeof(last_error_message), errmsg, ap);
va_end(ap);
}
static void errmsg_option_callback(enum argparse_server_option_t error_option, const char *errmsg, ...) {
last_parsed_option = error_option;
va_list ap;
va_start(ap, errmsg);
vsnprintf(last_error_message, sizeof(last_error_message), errmsg, ap);
va_end(ap);
}
bool argparse_server_parse(int argc, char **argv, argparse_server_callback_t argument_callback, argparse_server_plausibilization_callback_t plausibilization_callback) {
last_parsed_option = ARGPARSE_SERVER_NO_OPTION;
const char *short_options = "p:sv";
struct option long_options[] = {
{ "port", required_argument, 0, ARG_SERVER_PORT_LONG },
{ "silent", no_argument, 0, ARG_SERVER_SILENT_LONG },
{ "verbose", no_argument, 0, ARG_SERVER_VERBOSE_LONG },
{ "filename", required_argument, 0, ARG_SERVER_FILENAME_LONG },
{ 0 }
};
while (true) {
int optval = getopt_long(argc, argv, short_options, long_options, NULL);
if (optval == -1) {
break;
}
last_error_message[0] = 0;
enum argparse_server_option_internal_t arg = (enum argparse_server_option_internal_t)optval;
switch (arg) {
case ARG_SERVER_PORT_SHORT:
case ARG_SERVER_PORT_LONG:
last_parsed_option = ARG_SERVER_PORT;
if (!argument_callback(ARG_SERVER_PORT, optarg, errmsg_callback)) {
return false;
}
break;
case ARG_SERVER_SILENT_SHORT:
case ARG_SERVER_SILENT_LONG:
last_parsed_option = ARG_SERVER_SILENT;
if (!argument_callback(ARG_SERVER_SILENT, optarg, errmsg_callback)) {
return false;
}
break;
case ARG_SERVER_VERBOSE_SHORT:
case ARG_SERVER_VERBOSE_LONG:
last_parsed_option = ARG_SERVER_VERBOSE;
if (!argument_callback(ARG_SERVER_VERBOSE, optarg, errmsg_callback)) {
return false;
}
break;
default:
last_parsed_option = ARGPARSE_SERVER_NO_OPTION;
errmsg_callback("unrecognized option supplied");
return false;
}
}
const int positional_argument_cnt = argc - optind;
last_parsed_option = ARGPARSE_SERVER_POSITIONAL_ARG;
if (positional_argument_cnt != 1) {
errmsg_callback("expected exactly 1 positional argument, but %d given.", positional_argument_cnt);
return false;
}
int positional_index = optind;
last_parsed_option = ARG_SERVER_FILENAME;
if (!argument_callback(ARG_SERVER_FILENAME, argv[positional_index++], errmsg_callback)) {
return false;
}
if (plausibilization_callback) {
if (!plausibilization_callback(errmsg_option_callback)) {
return false;
}
}
return true;
}
void argparse_server_show_syntax(void) {
fprintf(stderr, "usage: luksrku server [-p port] [-s] [-v] filename\n");
fprintf(stderr, "\n");
fprintf(stderr, "Starts a luksrku key server.\n");
fprintf(stderr, "\n");
fprintf(stderr, "positional arguments:\n");
fprintf(stderr, " filename Database file to load keys from.\n");
fprintf(stderr, "\n");
fprintf(stderr, "optional arguments:\n");
fprintf(stderr, " -p port, --port port Port that is used for both UDP and TCP communication. Defaults to 23170.\n");
fprintf(stderr, " -s, --silent Do not answer UDP queries for clients trying to find a key server, only\n");
fprintf(stderr, " serve key database using TCP.\n");
fprintf(stderr, " -v, --verbose Increase verbosity. Can be specified multiple times.\n");
}
void argparse_server_parse_or_quit(int argc, char **argv, argparse_server_callback_t argument_callback, argparse_server_plausibilization_callback_t plausibilization_callback) {
if (!argparse_server_parse(argc, argv, argument_callback, plausibilization_callback)) {
if (last_parsed_option > ARGPARSE_SERVER_POSITIONAL_ARG) {
if (last_error_message[0]) {
fprintf(stderr, "luksrku server: error parsing argument %s -- %s\n", option_texts[last_parsed_option], last_error_message);
} else {
fprintf(stderr, "luksrku server: error parsing argument %s -- no details available\n", option_texts[last_parsed_option]);
}
} else if (last_parsed_option == ARGPARSE_SERVER_POSITIONAL_ARG) {
fprintf(stderr, "luksrku server: error parsing optional arguments -- %s\n", last_error_message);
}
argparse_server_show_syntax();
exit(EXIT_FAILURE);
}
}
#ifdef __ARGPARSE_MAIN__
/* gcc -D __ARGPARSE_MAIN__ -O2 -Wall -o argparse argparse_server.c
*/
static const char *option_enum_to_str(enum argparse_server_option_t option) {
switch (option) {
case ARG_SERVER_PORT: return "ARG_SERVER_PORT";
case ARG_SERVER_SILENT: return "ARG_SERVER_SILENT";
case ARG_SERVER_VERBOSE: return "ARG_SERVER_VERBOSE";
case ARG_SERVER_FILENAME: return "ARG_SERVER_FILENAME";
}
return "UNKNOWN";
}
bool arg_print_callback(enum argparse_server_option_t option, const char *value, argparse_server_errmsg_callback_t errmsg_callback) {
fprintf(stderr, "%s = \"%s\"\n", option_enum_to_str(option), value);
return true;
}
int main(int argc, char **argv) {
argparse_server_parse_or_quit(argc, argv, arg_print_callback, NULL);
return 0;
}
#endif

38
argparse_server.h Normal file
View File

@ -0,0 +1,38 @@
/*
* This file was AUTO-GENERATED by pypgmopts.
*
* https://github.com/johndoe31415/pypgmopts
*
* Do not edit it by hand, your changes will be overwritten.
*
* Generated at: 2021-06-27 13:24:40
*/
#ifndef __ARGPARSE_SERVER_H__
#define __ARGPARSE_SERVER_H__
#include <stdbool.h>
#define ARGPARSE_SERVER_DEFAULT_PORT 23170
#define ARGPARSE_SERVER_DEFAULT_VERBOSE 0
#define ARGPARSE_SERVER_NO_OPTION 0
#define ARGPARSE_SERVER_POSITIONAL_ARG 1
enum argparse_server_option_t {
ARG_SERVER_PORT = 2,
ARG_SERVER_SILENT = 3,
ARG_SERVER_VERBOSE = 4,
ARG_SERVER_FILENAME = 5,
};
typedef void (*argparse_server_errmsg_callback_t)(const char *errmsg, ...);
typedef void (*argparse_server_errmsg_option_callback_t)(enum argparse_server_option_t error_option, const char *errmsg, ...);
typedef bool (*argparse_server_callback_t)(enum argparse_server_option_t option, const char *value, argparse_server_errmsg_callback_t errmsg_callback);
typedef bool (*argparse_server_plausibilization_callback_t)(argparse_server_errmsg_option_callback_t errmsg_callback);
bool argparse_server_parse(int argc, char **argv, argparse_server_callback_t argument_callback, argparse_server_plausibilization_callback_t plausibilization_callback);
void argparse_server_show_syntax(void);
void argparse_server_parse_or_quit(int argc, char **argv, argparse_server_callback_t argument_callback, argparse_server_plausibilization_callback_t plausibilization_callback);
#endif

View File

@ -1,6 +1,6 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
@ -24,31 +24,21 @@
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/time.h>
#include "blacklist.h"
#include "global.h"
#include "util.h"
static struct blacklist_entry_t blacklist[BLACKLIST_ENTRY_COUNT];
static double gettime(void) {
struct timeval tv;
if (gettimeofday(&tv, NULL)) {
return 0;
}
double now = tv.tv_sec + (tv.tv_usec * 1e-6);
return now;
}
static bool blacklist_entry_expired(int index) {
double now = gettime();
return now > blacklist[index].entered + BLACKLIST_ENTRY_TIMEOUT_SECS;
return now() > blacklist[index].timeout;
}
void blacklist_ip(uint32_t ip) {
void blacklist_ip(uint32_t ip, unsigned int timeout_seconds) {
for (int i = 0; i < BLACKLIST_ENTRY_COUNT; i++) {
if (blacklist_entry_expired(i)) {
blacklist[i].ip = ip;
blacklist[i].entered = gettime();
blacklist[i].timeout = now() + timeout_seconds;
return;
}
}
@ -62,4 +52,3 @@ bool is_ip_blacklisted(uint32_t ip) {
}
return false;
}

View File

@ -1,6 +1,6 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
@ -27,13 +27,15 @@
#include <stdint.h>
#include <stdbool.h>
#define BLACKLIST_ENTRY_COUNT 32
struct blacklist_entry_t {
uint32_t ip;
double entered;
double timeout;
};
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
void blacklist_ip(uint32_t ip);
void blacklist_ip(uint32_t ip, unsigned int timeout_seconds);
bool is_ip_blacklisted(uint32_t ip);
/*************** AUTO GENERATED SECTION ENDS ***************/

474
client.c
View File

@ -29,180 +29,346 @@
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include "log.h"
#include "openssl.h"
#include "keyfile.h"
#include "util.h"
#include "cmdline.h"
#include "msg.h"
#include "client.h"
#include "blacklist.h"
#include "keydb.h"
#include "uuid.h"
#include "udp.h"
#include "luks.h"
static const struct keydb_t *client_keydb;
struct keyclient_t {
const struct pgmopts_client_t *opts;
keydb_t *keydb;
bool volume_unlocked[MAX_VOLUMES_PER_HOST];
unsigned char identifier[ASCII_UUID_BUFSIZE];
double broadcast_start_time;
};
static unsigned int psk_client_callback(SSL *ssl, const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len) {
log_msg(LLVL_DEBUG, "psk_client_callback: SSL %p, hint '%s'.", ssl, hint);
if (max_psk_len < PSK_SIZE_BYTES) {
log_msg(LLVL_ERROR, "Client error: max_psk_len too small.");
return 0;
}
if (max_identity_len < strlen(CLIENT_PSK_IDENTITY) + 1) {
log_msg(LLVL_ERROR, "Client error: max_identity_len too small.");
return 0;
}
static int psk_client_callback(SSL *ssl, const EVP_MD *md, const unsigned char **id, size_t *idlen, SSL_SESSION **sessptr) {
struct keyclient_t *key_client = (struct keyclient_t*)SSL_get_app_data(ssl);
*id = key_client->identifier;
*idlen = ASCII_UUID_CHARACTER_COUNT;
uint8_t parsed_uuid[16];
if (!parse_uuid(parsed_uuid, hint)) {
log_msg(LLVL_ERROR, "Client error: given hint '%s' is not a valid UUID.", hint);
return 0;
}
const struct keyentry_t *entry = keydb_find_entry_by_host_uuid(client_keydb, parsed_uuid);
if (!entry) {
log_msg(LLVL_ERROR, "Client error: server hint '%s' not present in database.", hint);
return 0;
}
strncpy(identity, CLIENT_PSK_IDENTITY, max_identity_len);
memcpy(psk, entry->psk, PSK_SIZE_BYTES);
return PSK_SIZE_BYTES;
return openssl_tls13_psk_establish_session(ssl, key_client->keydb->hosts[0].tls_psk, PSK_SIZE_BYTES, EVP_sha256(), sessptr);
}
static int dtls_client_connect(const struct keyentry_t *keyentry, const char *host_port) {
static bool unlock_luks_volume(const volume_entry_t *volume, const struct msg_t *unlock_msg) {
bool success = true;
char luks_passphrase[LUKS_PASSPHRASE_TEXT_SIZE_BYTES];
if (ascii_encode(luks_passphrase, sizeof(luks_passphrase), unlock_msg->luks_passphrase_raw, sizeof(unlock_msg->luks_passphrase_raw))) {
bool allow_discards = volume->volume_flags & VOLUME_FLAG_ALLOW_DISCARDS;
success = open_luks_device(volume->volume_uuid, volume->devmapper_name, luks_passphrase, strlen(luks_passphrase), allow_discards);
} else {
log_msg(LLVL_FATAL, "Failed to transcribe raw LUKS passphrase to text form.");
success = false;
}
OPENSSL_cleanse(luks_passphrase, sizeof(luks_passphrase));
return success;
}
static bool attempt_unlock_luks_volume(struct keyclient_t *keyclient, const struct msg_t *unlock_msg) {
const host_entry_t *host = &keyclient->keydb->hosts[0];
const volume_entry_t* volume = keydb_get_volume_by_uuid(host, unlock_msg->volume_uuid);
char volume_uuid_str[ASCII_UUID_BUFSIZE];
sprintf_uuid(volume_uuid_str, unlock_msg->volume_uuid);
if (!volume) {
log_msg(LLVL_WARNING, "Keyserver provided key for unlocking volume UUID %s, but this volume is not known on the client side.", volume_uuid_str);
return false;
}
/* This is a valid volume which we need to unlock */
int volume_index = keydb_get_volume_index(host, volume);
if (volume_index != -1) {
if (keyclient->opts->no_luks) {
keyclient->volume_unlocked[volume_index] = true;
#ifdef DEBUG
dump_hexline(stderr, "Raw key: ", unlock_msg->luks_passphrase_raw, LUKS_PASSPHRASE_RAW_SIZE_BYTES, false);
#endif
} else {
if (!keyclient->volume_unlocked[volume_index]) {
bool success = unlock_luks_volume(volume, unlock_msg);
keyclient->volume_unlocked[volume_index] = success;
if (!success) {
log_msg(LLVL_ERROR, "Unlocking of volume %s / %s failed with the server-provided passphrase.", volume->devmapper_name, volume_uuid_str);
return false;
}
} else {
log_msg(LLVL_WARNING, "Volume %s / %s already unlocked, not attemping to unlock again.", volume->devmapper_name, volume_uuid_str);
}
}
} else {
log_msg(LLVL_FATAL, "Error calculating volume offset for volume %p from base %p.", volume, host->volumes);
return false;
}
return true;
}
static bool contact_keyserver_socket(struct keyclient_t *keyclient, int sd) {
struct generic_tls_ctx_t gctx;
create_generic_tls_context(&gctx, false);
SSL_CTX_set_psk_client_callback(gctx.ctx, psk_client_callback);
BIO *conn = BIO_new_ssl_connect(gctx.ctx);
if (!conn) {
log_openssl(LLVL_ERROR, "Cannot get SSL client connect BIO.");
if (!create_generic_tls_context(&gctx, false)) {
log_msg(LLVL_FATAL, "Failed to create OpenSSL client context.");
return false;
}
SSL_CTX_set_psk_use_session_callback(gctx.ctx, psk_client_callback);
if (BIO_set_conn_hostname(conn, host_port) != 1) {
log_openssl(LLVL_ERROR, "Cannot set SSL client connect hostname/port.");
return false;
}
SSL *ssl = NULL;
BIO_get_ssl(conn, &ssl);
if (!ssl) {
log_openssl(LLVL_ERROR, "Cannot get SSL client SSL context.");
return false;
}
if (BIO_do_connect(conn) != 1) {
log_openssl(LLVL_ERROR, "Cannot perform SSL client connect.");
return false;
}
if (BIO_do_handshake(conn) != 1) {
log_openssl(LLVL_ERROR, "Cannot perform SSL client handshake.");
return false;
}
log_msg(LLVL_DEBUG, "Client successfully connected to server.");
for (int i = 0; i < MAX_DISKS_PER_HOST; i++) {
if (keyentry->disk_keys[i].occupied) {
log_msg(LLVL_DEBUG, "Client sending key #%d", i);
SSL *ssl = SSL_new(gctx.ctx);
if (ssl) {
SSL_set_fd(ssl, sd);
SSL_set_app_data(ssl, keyclient);
if (SSL_connect(ssl) == 1) {
struct msg_t msg;
memset(&msg, 0, sizeof(msg));
memcpy(msg.disk_uuid, keyentry->disk_keys[i].disk_uuid, 16);
msg.passphrase_length = keyentry->disk_keys[i].passphrase_length;
memcpy(msg.passphrase, keyentry->disk_keys[i].passphrase, MAX_PASSPHRASE_LENGTH);
msg_to_nbo(&msg);
int txed = SSL_write(ssl, &msg, sizeof(msg));
if (txed != sizeof(msg)) {
log_msg(LLVL_ERROR, "Truncated message sent: tried to send %d bytes, but only %d bytes went through. Aborting connection.", sizeof(msg), txed);
while (true) {
int bytes_read = SSL_read(ssl, &msg, sizeof(msg));
if (bytes_read == 0) {
/* Server closed the connection. */
break;
}
if (bytes_read != sizeof(msg)) {
log_openssl(LLVL_FATAL, "SSL_read returned %d bytes when we expected to read %d", bytes_read, sizeof(msg));
break;
}
char uuid_str[ASCII_UUID_BUFSIZE];
sprintf_uuid(uuid_str, msg.volume_uuid);
log_msg(LLVL_TRACE, "Received LUKS key to unlock volume with UUID %s", uuid_str);
if (attempt_unlock_luks_volume(keyclient, &msg)) {
log_msg(LLVL_DEBUG, "Successfully unlocked volume with UUID %s", uuid_str);
} else {
log_msg(LLVL_ERROR, "Failed to unlocked volume with UUID %s", uuid_str);
}
}
OPENSSL_cleanse(&msg, sizeof(msg));
} else {
log_openssl(LLVL_FATAL, "SSL_connect failed");
}
} else {
log_openssl(LLVL_FATAL, "Cannot establish SSL context when trying to connect to server");
}
SSL_free(ssl);
free_generic_tls_context(&gctx);
return true;
}
static bool contact_keyserver_ipv4(struct keyclient_t *keyclient, struct sockaddr_in *sockaddr_in, unsigned int port) {
sockaddr_in->sin_port = htons(port);
int sd = socket(sockaddr_in->sin_family, SOCK_STREAM, 0);
if (sd == -1) {
log_libc(LLVL_ERROR, "Failed to create socket(3)");
return false;
}
if (connect(sd, (struct sockaddr*)sockaddr_in, sizeof(struct sockaddr_in)) == -1) {
log_libc(LLVL_ERROR, "Failed to connect(3) to %d.%d.%d.%d:%d", PRINTF_FORMAT_IP(sockaddr_in), port);
close(sd);
return false;
}
bool success = contact_keyserver_socket(keyclient, sd);
shutdown(sd, SHUT_RDWR);
close(sd);
return success;
}
static bool contact_keyserver_hostname(struct keyclient_t *keyclient, const char *hostname) {
struct addrinfo hints = {
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *result;
int resolve_result = getaddrinfo(hostname, NULL, &hints, &result);
if (resolve_result) {
log_msg(LLVL_ERROR, "Failed to resolve hostname %s using getaddrinfo(3): %s", hostname, gai_strerror(resolve_result));
return false;
}
if (result->ai_addr->sa_family != AF_INET) {
freeaddrinfo(result);
log_msg(LLVL_ERROR, "getaddrinfo(3) returned non-IPv4 entry");
return false;
}
struct sockaddr_in *sin_address = (struct sockaddr_in*)result->ai_addr;
log_msg(LLVL_TRACE, "Resolved %s to %d.%d.%d.%d", hostname, PRINTF_FORMAT_IP(sin_address));
bool success = contact_keyserver_ipv4(keyclient, sin_address, keyclient->opts->port);
freeaddrinfo(result);
return success;
}
static unsigned int locked_volume_count(struct keyclient_t *keyclient) {
unsigned int count = 0;
const unsigned int volume_count = keyclient->keydb->hosts[0].volume_count;
for (unsigned int i = 0; i < volume_count; i++) {
if (!keyclient->volume_unlocked[i]) {
count++;
}
}
return count;
}
static bool all_volumes_unlocked(struct keyclient_t *keyclient) {
return locked_volume_count(keyclient) == 0;
}
static unsigned int determine_timeout(struct keyclient_t *keyclient) {
unsigned int client_timeout_secs = 0;
if (keyclient->opts->timeout_seconds) {
/* Command line always has precedence */
client_timeout_secs = keyclient->opts->timeout_seconds;
} else {
/* Alternatively, take the one in the configuration file */
client_timeout_secs = keyclient->keydb->hosts[0].client_default_timeout_secs;
}
return client_timeout_secs;
}
static bool abort_searching_for_keyserver(struct keyclient_t *keyclient) {
if (all_volumes_unlocked(keyclient)) {
log_msg(LLVL_DEBUG, "All volumes unlocked successfully.");
return true;
}
unsigned int client_timeout_secs = determine_timeout(keyclient);
if (client_timeout_secs) {
double time_passed = now() - keyclient->broadcast_start_time;
if (time_passed >= client_timeout_secs) {
log_msg(LLVL_WARNING, "Could not unlock all volumes after %u seconds, giving up. %d volumes still locked.", client_timeout_secs, locked_volume_count(keyclient));
return true;
}
}
return false;
}
static bool broadcast_for_keyserver(struct keyclient_t *keyclient) {
{
unsigned int client_timeout_secs = determine_timeout(keyclient);
if (client_timeout_secs) {
log_msg(LLVL_DEBUG, "Searching luksrku keyserver, will give up after %u seconds", client_timeout_secs);
} else {
log_msg(LLVL_DEBUG, "Searching luksrku keyserver, will not give up until all volumes unlocked");
}
}
int sd = create_udp_socket(0, true, 1000);
if (sd == -1) {
return false;
}
keyclient->broadcast_start_time = now();
struct udp_query_t query;
memcpy(query.magic, UDP_MESSAGE_MAGIC, sizeof(query.magic));
memcpy(query.host_uuid, keyclient->keydb->hosts[0].host_uuid, 16);
while (true) {
log_msg(LLVL_TRACE, "Broadcasting search for luksrku keyserver");
send_udp_broadcast_message(sd, keyclient->opts->port, &query, sizeof(query));
struct sockaddr_in src = {
.sin_family = AF_INET,
.sin_port = htons(keyclient->opts->port),
.sin_addr.s_addr = htonl(INADDR_ANY),
};
struct udp_response_t response;
if (wait_udp_response(sd, &response, &src)) {
if (!is_ip_blacklisted(src.sin_addr.s_addr)) {
log_msg(LLVL_INFO, "Keyserver found at %d.%d.%d.%d", PRINTF_FORMAT_IP(&src));
blacklist_ip(src.sin_addr.s_addr, BLACKLIST_TIMEOUT_CLIENT);
if (!contact_keyserver_ipv4(keyclient, &src, keyclient->opts->port)) {
log_msg(LLVL_WARNING, "Keyserver announced at %d.%d.%d.%d, but connection to it failed.", PRINTF_FORMAT_IP(&src));
}
} else {
log_msg(LLVL_DEBUG, "Potential keyserver at %d.%d.%d.%d ignored, blacklist in effect.", PRINTF_FORMAT_IP(&src));
}
}
if (abort_searching_for_keyserver(keyclient)) {
break;
}
}
return true;
}
bool keyclient_start(const struct pgmopts_client_t *opts) {
/* Load key database first */
struct keyclient_t keyclient = {
.opts = opts,
};
bool success = true;
do {
keyclient.keydb = keydb_read(opts->filename);
if (!keyclient.keydb) {
log_msg(LLVL_FATAL, "Failed to load key database: %s", opts->filename);
success = false;
break;
}
if (keyclient.keydb->server_database) {
log_msg(LLVL_FATAL, "Not an exported key database: %s -- this database contains LUKS passphrases, refusing to work with it!", opts->filename);
success = false;
break;
}
if (keyclient.keydb->host_count != 1) {
log_msg(LLVL_FATAL, "Host count %d in %s -- expected exactly one host entry for an exported database.", keyclient.keydb->host_count, opts->filename);
success = false;
break;
}
host_entry_t *host = &keyclient.keydb->hosts[0];
if (host->volume_count == 0) {
log_msg(LLVL_FATAL, "No volumes found in exported database %s.", opts->filename);
success = false;
break;
}
/* Determine which of these volumes are already unlocked */
for (unsigned int i = 0; i < host->volume_count; i++) {
keyclient.volume_unlocked[i] = is_luks_device_opened(host->volumes[i].devmapper_name);
}
if (all_volumes_unlocked(&keyclient)) {
log_msg(LLVL_INFO, "All %u volumes are unlocked already, not contacting luksrku key server.", host->volume_count);
break;
} else {
log_msg(LLVL_DEBUG, "%u of %u volumes are currently locked.", locked_volume_count(&keyclient), host->volume_count);
}
/* Transcribe the host UUID to ASCII so we only have to do this once */
sprintf_uuid((char*)keyclient.identifier, host->host_uuid);
if (opts->hostname) {
if (!contact_keyserver_hostname(&keyclient, opts->hostname)) {
log_msg(LLVL_ERROR, "Failed to contact key server: %s", opts->hostname);
success = false;
break;
}
} else {
if (!broadcast_for_keyserver(&keyclient)) {
log_msg(LLVL_ERROR, "Failed to find key server using UDP broadcast.");
success = false;
break;
}
}
} while (false);
if (keyclient.keydb) {
keydb_free(keyclient.keydb);
}
BIO_free_all(conn);
free_generic_tls_context(&gctx);
return 0;
}
static bool parse_announcement(const struct options_t *options, const struct sockaddr_in *peer_addr, const struct announcement_t *announcement) {
log_msg(LLVL_DEBUG, "Parsing possible announcement from %d.%d.%d.%d:%d", PRINTF_FORMAT_IP(peer_addr), ntohs(peer_addr->sin_port));
const uint8_t expect_magic[16] = CLIENT_ANNOUNCE_MAGIC;
if (memcmp(announcement->magic, expect_magic, 16)) {
/* Magic number does not match, discard. */
return false;
}
const struct keyentry_t *keyentry = keydb_find_entry_by_host_uuid(client_keydb, announcement->host_uuid);
char ascii_host_uuid[40];
sprintf_uuid(ascii_host_uuid, announcement->host_uuid);
log_msg(LLVL_DEBUG, "Received valid announcement from %s host %s", (keyentry == NULL) ? "unknown" : "known", ascii_host_uuid);
if (keyentry == NULL) {
/* The announcement is valid, but we don't know the client -- so we
* can't do anything further */
return false;
}
/* We know the server. But maybe we've already tried to contact them and
* therefore they're blacklisted for a certain period of time. Check this
* now (we don't want to spam servers with maybe invalid passphrases). */
uint32_t ip = peer_addr->sin_addr.s_addr;
if (is_ip_blacklisted(ip)) {
log_msg(LLVL_DEBUG, "%d.%d.%d.%d is currently blacklisted for %d seconds.", PRINTF_FORMAT_IP(peer_addr), BLACKLIST_ENTRY_TIMEOUT_SECS);
return false;
} else {
/* Blacklist for next time */
blacklist_ip(ip);
}
char destination_address[32];
snprintf(destination_address, sizeof(destination_address) - 1, "%d.%d.%d.%d:%d", PRINTF_FORMAT_IP(peer_addr), options->port);
log_msg(LLVL_DEBUG, "Trying to connect to %s in order to transmit keys", destination_address);
dtls_client_connect(keyentry, destination_address);
return true;
}
bool dtls_client(const struct keydb_t *keydb, const struct options_t *options) {
client_keydb = keydb;
int sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
log_libc(LLVL_ERROR, "Unable to create UDP client socket(2)");
return false;
}
{
int value = 1;
setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
}
struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(options->port);
local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
memset(local_addr.sin_zero, 0, sizeof(local_addr.sin_zero));
if (bind(sd, (struct sockaddr*)&local_addr, sizeof(local_addr))) {
log_libc(LLVL_ERROR, "Unable to bind(2) UDP client socket to port %d", options->port);
return false;
}
int tries = 0;
while ((options->unlock_cnt == 0) || (tries < options->unlock_cnt)) {
uint8_t rxbuf[2048];
struct sockaddr_in peer_addr;
socklen_t addr_size = sizeof(peer_addr);
int rxlen = recvfrom(sd, rxbuf, sizeof(rxbuf), 0, (struct sockaddr *)&peer_addr, &addr_size);
if (rxlen == sizeof(struct announcement_t)) {
if (parse_announcement(options, &peer_addr, (struct announcement_t*)rxbuf)) {
tries++;
}
}
}
return true;
return success;
}

View File

@ -24,11 +24,11 @@
#ifndef __CLIENT_H__
#define __CLIENT_H__
#include "keyfile.h"
#include "cmdline.h"
#include <stdbool.h>
#include "pgmopts.h"
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
bool dtls_client(const struct keydb_t *keydb, const struct options_t *options);
bool keyclient_start(const struct pgmopts_client_t *opts);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

194
cmdline.c
View File

@ -1,194 +0,0 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <stdlib.h>
#include "cmdline.h"
#include "global.h"
enum longopts_t {
LONGOPT_VERBOSE,
LONGOPT_MODE_SERVER,
LONGOPT_MODE_CLIENT,
LONGOPT_PORT,
LONGOPT_KEYDB,
LONGOPT_UNLOCK_CNT,
LONGOPT_MAX_BCAST_ERRS
};
void print_syntax(const char *pgmname) {
fprintf(stderr, "%s (-c, --client-mode) (-s, --server-mode) (-k, --keydb=FILE) (-u, --unlock=CNT)\n", pgmname);
fprintf(stderr, " (-p, --port=PORT) (--max-bcast-errs=CNT) (-v, --verbose)\n");
fprintf(stderr, "\n");
fprintf(stderr, " -c, --client-mode Specifies client mode, i.e., that this host will unlock the LUKS\n");
fprintf(stderr, " disk of a different machine.\n");
fprintf(stderr, " -s, --server-mode Specifies server mode, i.e., that this host will announce its\n");
fprintf(stderr, " presence via UDP broadcasts and then receive the LUKS credentials\n");
fprintf(stderr, " from a peer.\n");
fprintf(stderr, " -k, --keydb=FILE Gives the binary key database file which will be used. In server\n");
fprintf(stderr, " mode, this contains only one entry (specifying the UUID of the\n");
fprintf(stderr, " host, the PSK and the UUIDs and names of the disks to be\n");
fprintf(stderr, " unlocked), while in client mode this may contain multiple entries\n");
fprintf(stderr, " (to unlock many different peers) and also contains the LUKS\n");
fprintf(stderr, " credentials for the respective disks.\n");
fprintf(stderr, " -u, --unlock=CNT Specifies the maximum number of unlocking actions that are taken.\n");
fprintf(stderr, " In client mode, this defaults to 1. In server mode, it defaults to\n");
fprintf(stderr, " infinite (or until all disks have successfully been unlocked).\n");
fprintf(stderr, " Zero means infinite.\n");
fprintf(stderr, " -p, --port=PORT Specifies the port on which is listened for UDP broadcasts and\n");
fprintf(stderr, " also the port on which TCP requests are sent out (the two are\n");
fprintf(stderr, " always identical). Default port ist 23170.\n");
fprintf(stderr, " --max-bcast-errs=CNT This is the number of UDP broadcast attempts luksrku will make\n");
fprintf(stderr, " before giving up. Usually this is because sendto(2) fails when the\n");
fprintf(stderr, " network is configured improperly. Giving up in this case enables\n");
fprintf(stderr, " manual key entry. This defaults to 5 tries.\n");
fprintf(stderr, " -v, --verbose Increase logging verbosity.\n");
fprintf(stderr, "\n");
fprintf(stderr, "luksrku version: " BUILD_REVISION "\n");
}
static void set_default_arguments(struct options_t *options) {
memset(options, 0, sizeof(struct options_t));
/* Default port :-) echo -n LUKS | md5sum | cut -c -5 */
options->port = 23170;
/* Default, overwritten later by fill_default_arguments() */
options->unlock_cnt = -1;
/* Give up after 5 failed broadcast attempts */
options->max_broadcast_errs = 5;
}
static void fill_default_arguments(struct options_t *options) {
/* Set default unlock count */
if (options->unlock_cnt == -1) {
if (options->mode == CLIENT_MODE) {
options->unlock_cnt = 1;
} else if (options->mode == SERVER_MODE) {
options->unlock_cnt = 0;
}
}
}
static bool check_arguments(const struct options_t *options) {
if (options->mode == UNDEFINED) {
fprintf(stderr, "Must specify client or server mode.\n");
return false;
}
if (options->keydbfile == NULL) {
fprintf(stderr, "Must specify a key database file.\n");
return false;
}
if ((options->port < 1) || (options->port > 65535)) {
fprintf(stderr, "Valid port range is 1-65535.\n");
return false;
}
if (options->unlock_cnt < 0) {
fprintf(stderr, "Unlock count must be a positive integer.\n");
return false;
}
return true;
}
bool parse_cmdline_arguments(struct options_t *options, int argc, char **argv) {
set_default_arguments(options);
struct option long_options[] = {
{ "verbose", no_argument, 0, LONGOPT_VERBOSE },
{ "server-mode", no_argument, 0, LONGOPT_MODE_SERVER },
{ "client-mode", no_argument, 0, LONGOPT_MODE_CLIENT },
{ "port", required_argument, 0, LONGOPT_PORT },
{ "keydb", required_argument, 0, LONGOPT_KEYDB },
{ "unlock", required_argument, 0, LONGOPT_UNLOCK_CNT },
{ "max-bcast-errs", required_argument, 0, LONGOPT_MAX_BCAST_ERRS },
{ 0 }
};
bool success = true;
bool parse = true;
do {
int c = getopt_long(argc, argv, "vscp:k:u:", long_options, NULL);
switch (c) {
case LONGOPT_VERBOSE:
case 'v':
options->verbose = true;
break;
case LONGOPT_MODE_SERVER:
case 's':
options->mode = SERVER_MODE;
break;
case LONGOPT_MODE_CLIENT:
case 'c':
options->mode = CLIENT_MODE;
break;
case LONGOPT_PORT:
case 'p':
options->port = atoi(optarg);
break;
case LONGOPT_KEYDB:
case 'k':
options->keydbfile = optarg;
break;
case LONGOPT_UNLOCK_CNT:
case 'u':
options->unlock_cnt = atoi(optarg);
break;
case LONGOPT_MAX_BCAST_ERRS:
options->max_broadcast_errs = atoi(optarg);
break;
case -1:
/* Out of arguments */
parse = false;
break;
case '?':
/* Syntax error */
parse = false;
success = false;
break;
default:
fprintf(stderr, "Programming error: unexpected getopt return value %d.\n", c);
parse = false;
success = false;
break;
}
} while (parse);
fill_default_arguments(options);
return success && check_arguments(options);
}

View File

@ -1,80 +0,0 @@
#!/usr/bin/python3
#
# TwoColPrint - Print text in two columns, wrap as appropriate.
# Copyright (C) 2011-2012 Johannes Bauer
#
# This file is part of jpycommon.
#
# jpycommon is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# jpycommon is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with jpycommon; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
# File UUID c2de9b77-c699-490d-930f-21689e04b12f
import sys
import textwrap
import collections
_Row = collections.namedtuple("Row", [ "left", "right", "annotation" ])
class TwoColPrint(object):
def __init__(self, prefix = "", total_width = 120, spacer_width = 3, width_ratio = 0.25):
self._rows = [ ]
self._prefix = prefix
self._total_width = total_width
self._spacer_width = spacer_width
self._width_ratio = width_ratio
def addrow(self, left_col, right_col, annotation = None):
self._rows.append(_Row(left = left_col, right = right_col, annotation = annotation))
return self
def __iter__(self):
text_width = self._total_width - len(self._prefix) - self._spacer_width
assert(text_width > 2)
left_width = round(self._width_ratio * text_width)
right_width = text_width - left_width
assert(len(self._prefix) + left_width + self._spacer_width + right_width == self._total_width)
spacer = " " * self._spacer_width
for row in self._rows:
left_break = textwrap.wrap(row.left, width = left_width)
right_break = textwrap.wrap(row.right, width = right_width)
if len(left_break) < len(right_break):
left_break += [ "" ] * (len(right_break) - len(left_break))
elif len(left_break) > len(right_break):
right_break += [ "" ] * (len(left_break) - len(right_break))
for (leftline, rightline) in zip(left_break, right_break):
yield ("%s%-*s%s%s" % (self._prefix, left_width, leftline, spacer, rightline), row.annotation)
def print(self, f = None):
if f is None:
f = sys.stdout
for (line, annotation) in self:
print(line, file = f)
if __name__ == "__main__":
t = TwoColPrint(prefix = " ")
t.addrow("foobar", "This is the first piece, which is foobar. A foobar is very cool! This is the first piece, which is foobar. A foobar is very cool!")
t.addrow("barfjdiojf", "And here's a barwhatever And here's a barwhatever And here's a barwhatever")
t.addrow("x", "Cool, a x.")
t.addrow("And here's a barwhatever And here's a barwhatever And here's a barwhatever", "barfjdiojf")
t.print()

View File

@ -1,93 +0,0 @@
#!/usr/bin/python3
import textwrap
class HelpPagePrinter(object):
def __init__(self):
self._entries = [ ]
self._lcolsize = None
def add(self, lhs, rhs):
if isinstance(lhs, str):
lhs = (lhs, )
else:
lhs = (", ".join(lhs), )
if isinstance(rhs, str):
rhs = (rhs, )
self._entries.append((lhs, rhs))
def _format_entry(self, entry):
(lhs, rhs) = entry
lhs = list(lhs)
rhs = list(rhs)
right_lines = [ ]
for block in rhs:
right_lines += textwrap.wrap(block, width = 86 - self._lcolsize)
if len(lhs) < len(right_lines):
lhs += [ "" ] * (len(right_lines) - len(lhs))
elif len(lhs) > len(right_lines):
right_lines += [ "" ] * (len(lhs) - len(right_lines))
for (left, right) in zip(lhs, right_lines):
yield "%-*s %s" % (self._lcolsize, left, right.replace("\xa0", " "))
def _determine_lcolsize(self):
self._lcolsize = 0
for (lhs, rhs) in self._entries:
for line in lhs:
self._lcolsize = max(self._lcolsize, len(line))
def format_params(self):
lines = [ "" ]
for (lhs, rhs) in self._entries:
par = lhs[0].strip()
newline = lines[-1] + (" (%s)" % (par))
if len(newline) < 80:
lines[-1] = newline
else:
lines.append("(%s)" % (par))
yield from lines
def format_help(self):
self._determine_lcolsize()
for entry in self._entries:
yield from self._format_entry(entry)
hpp = HelpPagePrinter()
hpp.add([ "-c", "--client-mode" ], "Specifies client mode, i.e., that this host will unlock the LUKS disk of a different machine.")
hpp.add([ "-s", "--server-mode" ], "Specifies server mode, i.e., that this host will announce its presence via UDP broadcasts and then receive the LUKS credentials from a peer.")
hpp.add([ "-k", "--keydb=FILE" ], "Gives the binary key database file which will be used. In server mode, this contains only one entry (specifying the UUID of the host, the PSK and the UUIDs and names of the disks to be unlocked), while in client mode this may contain multiple entries (to unlock many different peers) and also contains the LUKS credentials for the respective disks.")
hpp.add([ "-u", "--unlock=CNT" ], "Specifies the maximum number of unlocking actions that are taken. In client mode, this defaults to 1. In server mode, it defaults to infinite (or until all disks have successfully been unlocked). Zero means infinite.")
hpp.add([ "-p", "--port=PORT" ], "Specifies the port on which is listened for UDP broadcasts and also the port on which TCP requests are sent out (the two are always identical). Default port ist 23170.")
hpp.add([ "--max-bcast-errs=CNT" ], "This is the number of UDP broadcast attempts luksrku will make before giving up. Usually this is because sendto(2) fails when the network is configured improperly. Giving up in this case enables manual key entry. This defaults to 5 tries.")
hpp.add([ "-v", "--verbose" ], "Increase logging verbosity.")
for (index, line) in enumerate(hpp.format_params()):
if index == 0:
print(" fprintf(stderr, \"%%s%s\\n\", pgmname);" % (line))
else:
print(" fprintf(stderr, \" %s\\n\");" % (line))
print(" fprintf(stderr, \"\\n\");")
for line in hpp.format_help():
print(" fprintf(stderr, \" %s\\n\");" % (line))
print(" fprintf(stderr, \"\\n\");")
#examples = [
# ("--client-mode ",
# "Converts {device} to a LUKS partition with default parameters."),
# ("-d {device} --resume-file myresume.dat",
# "Converts {device} to a LUKS partition with default parameters and store resume information in myresume.dat in case of an abort."),
# ("-d {device} -k /root/secure_key/keyfile.bin --luksparams='-c,twofish-lrw-benbi,-s,320,-h,sha256'",
# "Converts {device} to a LUKS partition and stores the initially used keyfile in /root/secure_key/keyfile.bin. Additionally some LUKS parameters are passed that specify that the Twofish cipher should be used with a 320 bit keysize and SHA-256 as a hash function."),
# ("-d {device} --resume --resume-file /root/resume.bin",
# "Resumes a crashed LUKS conversion of {device} using the file /root/resume.bin which was generated at the first (crashed) luksipc run."),
# ("-d {device} --readdev /dev/mapper/oldluks",
# "Convert the raw device {device}, which is already a LUKS container, to a new LUKS container. For example, this can be used to change the encryption parameters of the LUKS container (different cipher) or to change the bulk encryption key. In this example the old container is unlocked and accessible under /dev/mapper/oldluks."),
#]
#print("fprintf(stderr, \"Examples:\\n\");")
#for (cmd, desc) in examples:
# print("fprintf(stderr, \" %%s %s\\n\", argv[0]);" % (cmd.replace("{device}", device)))
# for line in textwrap.wrap(desc.replace("{device}", device), width = 80):
# print("fprintf(stderr, \" %s\\n\");" % (line))

696
editor.c Normal file
View File

@ -0,0 +1,696 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdbool.h>
#include <openssl/crypto.h>
#include "editor.h"
#include "util.h"
#include "keydb.h"
#include "uuid.h"
#include "log.h"
#include "vaulted_keydb.h"
#define MAX_COMMAND_ALIAS_COUNT 2
enum cmd_returncode_t {
COMMAND_SUCCESS,
COMMAND_FAILURE,
COMMAND_TOO_FEW_PARAMETERS,
COMMAND_TOO_MANY_PARAMETERS,
};
struct editor_context_t {
bool running;
keydb_t *keydb;
char filename[MAX_FILENAME_LENGTH];
char passphrase[MAX_PASSPHRASE_LENGTH];
};
struct editor_command_t {
unsigned int min_params;
unsigned int max_params;
const char *cmdnames[MAX_COMMAND_ALIAS_COUNT];
const char *param_names;
const char *description;
enum cmd_returncode_t (*callback)(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
};
static enum cmd_returncode_t cmd_help(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_new(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_list(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_add_host(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_del_host(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_rekey_host(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_host_param(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_add_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_del_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_rekey_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_showkey_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_flag_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_open(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_save(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_export(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
#ifdef DEBUG
static enum cmd_returncode_t cmd_test(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
static enum cmd_returncode_t cmd_rawdump(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params);
#endif
static const struct editor_command_t commands[] = {
{
.cmdnames = { "help", "?" },
.callback = cmd_help,
.description = "Shows a help page describing all available commands",
},
{
.cmdnames = { "new" },
.callback = cmd_new,
.description = "Create a new database file",
},
{
.cmdnames = { "list", "l" },
.callback = cmd_list,
.description = "List contents of database file",
},
{
.cmdnames = { "add_host" },
.callback = cmd_add_host,
.param_names = "[hostname]",
.min_params = 1,
.max_params = 1,
.description = "Add a new host to the database file",
},
{
.cmdnames = { "del_host" },
.callback = cmd_del_host,
.param_names = "[hostname]",
.min_params = 1,
.max_params = 1,
.description = "Removes a host from the database file",
},
{
.cmdnames = { "rekey_host" },
.callback = cmd_rekey_host,
.param_names = "[hostname]",
.min_params = 1,
.max_params = 1,
.description = "Re-keys the TLS PSK of a given host",
},
{
.cmdnames = { "host_param" },
.callback = cmd_host_param,
.param_names = "[hostname] timeout [value]",
.min_params = 3,
.max_params = 3,
.description = "Set a parameter of a host (currently only timeout supported)",
},
{
.cmdnames = { "add_volume" },
.callback = cmd_add_volume,
.param_names = "[hostname] [devmappername] [volume-UUID]",
.min_params = 3,
.max_params = 3,
.description = "Add a new volume to the hostname",
},
{
.cmdnames = { "del_volume" },
.callback = cmd_del_volume,
.param_names = "[hostname] [devmappername]",
.min_params = 2,
.max_params = 2,
.description = "Removes a volume from the given host",
},
{
.cmdnames = { "rekey_volume" },
.callback = cmd_rekey_volume,
.param_names = "[hostname] [devmappername]",
.min_params = 2,
.max_params = 2,
.description = "Re-keys the LUKS passphrase of a volume of a given hostname",
},
{
.cmdnames = { "showkey_volume" },
.callback = cmd_showkey_volume,
.param_names = "[hostname] [devmappername]",
.min_params = 2,
.max_params = 2,
.description = "Shows the LUKS passphrase of a volume of a hostname",
},
{
.cmdnames = { "flag_volume" },
.callback = cmd_flag_volume,
.param_names = "[hostname] [devmappername] [(+-)(allow_discards)]",
.min_params = 3,
.max_params = 3,
.description = "Edits the flags of a volume",
},
{
.cmdnames = { "open", "load" },
.callback = cmd_open,
.param_names = "[filename]",
.min_params = 1,
.max_params = 1,
.description = "Opens a database file",
},
{
.cmdnames = { "save" },
.callback = cmd_save,
.param_names = "([filename])",
.min_params = 0,
.max_params = 1,
.description = "Saves a database file",
},
{
.cmdnames = { "export" },
.callback = cmd_export,
.param_names = "[hostname] [filename]",
.min_params = 2,
.max_params = 2,
.description = "Export a host database file for a specific host",
},
#ifdef DEBUG
{
.cmdnames = { "test", "t" },
.callback = cmd_test,
.min_params = 1,
.max_params = 1,
.description = "Test different aspects of luksrku. Used only for debugging.",
},
{
.cmdnames = { "rawdump", "raw" },
.callback = cmd_rawdump,
.min_params = 0,
.max_params = 0,
.description = "Dumps the raw representation of a file",
},
#endif
{ 0 }
};
static void format_command(char dest[static 128], const struct editor_command_t *cmd, const char *command_name) {
const char *used_command_name = command_name ? command_name : cmd->cmdnames[0];
snprintf(dest, 128, "%s%s%s", used_command_name, cmd->param_names ? " " : "", cmd->param_names ? cmd->param_names : "");
}
static enum cmd_returncode_t cmd_help(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
printf("List of commands:\n");
const struct editor_command_t *cmd = commands;
while (cmd->cmdnames[0]) {
char formatted_cmd[128];
format_command(formatted_cmd, cmd, NULL);
if (strlen(formatted_cmd) <= 40) {
printf(" %-40s %s\n", formatted_cmd, cmd->description);
} else {
printf(" %s\n %-40s %s\n", formatted_cmd, "", cmd->description);
}
cmd++;
}
return COMMAND_SUCCESS;
}
static enum cmd_returncode_t cmd_new(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
if (ctx->keydb) {
keydb_free(ctx->keydb);
}
ctx->keydb = keydb_new();
memset(ctx->passphrase, 0, sizeof(ctx->passphrase));
return (ctx->keydb != NULL) ? COMMAND_SUCCESS : COMMAND_FAILURE;
}
static enum cmd_returncode_t cmd_list(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
if (!ctx->keydb) {
printf("No key database loaded.\n");
return COMMAND_FAILURE;
}
printf("Keydb version %d, %s database, %d hosts.\n", ctx->keydb->common.keydb_version, ctx->keydb->server_database ? "server" : "client", ctx->keydb->host_count);
for (unsigned int i = 0; i < ctx->keydb->host_count; i++) {
const host_entry_t *host = &ctx->keydb->hosts[i];
char uuid[48];
sprintf_uuid(uuid, host->host_uuid);
printf(" Host %d: \"%s\" UUID %s -- %d volumes, ", i + 1, host->host_name, uuid, host->volume_count);
if (!host->client_default_timeout_secs) {
printf("no default timeout");
} else {
printf("default timeout %d secs", host->client_default_timeout_secs);
}
printf(":\n");
for (unsigned int j = 0; j < host->volume_count; j++) {
const volume_entry_t *volume = &host->volumes[j];
sprintf_uuid(uuid, volume->volume_uuid);
printf(" Volume %d: \"%s\" UUID %s ", j + 1, volume->devmapper_name, uuid);
if (volume->volume_flags == 0) {
printf("defaults");
} else {
if (volume->volume_flags & VOLUME_FLAG_ALLOW_DISCARDS) {
printf("allow_discards ");
}
}
printf("\n");
}
}
return COMMAND_SUCCESS;
}
static enum cmd_returncode_t cmd_add_host(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
if (!ctx->keydb) {
ctx->keydb = keydb_new();
if (!ctx->keydb) {
return COMMAND_FAILURE;
}
}
const char *host_name = params[0];
bool success = keydb_add_host(&ctx->keydb, host_name);
return success ? COMMAND_SUCCESS : COMMAND_FAILURE;
}
static enum cmd_returncode_t cmd_del_host(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
if (!ctx->keydb) {
fprintf(stderr, "No key database loaded.\n");
return COMMAND_FAILURE;
}
const char *host_name = params[0];
bool success = keydb_del_host_by_name(&ctx->keydb, host_name);
return success ? COMMAND_SUCCESS : COMMAND_FAILURE;
}
static host_entry_t* cmd_gethost(struct editor_context_t *ctx, const char *host_name) {
if (!ctx->keydb) {
fprintf(stderr, "No key database loaded.\n");
return NULL;
}
host_entry_t *host = keydb_get_host_by_name(ctx->keydb, host_name);
if (!host) {
fprintf(stderr, "No such host: %s\n", host_name);
return NULL;
}
return host;
}
static volume_entry_t* cmd_getvolume(struct editor_context_t *ctx, const char *host_name, const char *devmapper_name) {
host_entry_t *host = cmd_gethost(ctx, host_name);
if (!host) {
return NULL;
}
volume_entry_t *volume = keydb_get_volume_by_name(host, devmapper_name);
if (!volume) {
fprintf(stderr, "No such volume \"%s\" for host \"%s\"\n", devmapper_name, host_name);
return NULL;
}
return volume;
}
static enum cmd_returncode_t cmd_rekey_host(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
const char *host_name = params[0];
host_entry_t *host = cmd_gethost(ctx, host_name);
return host && keydb_rekey_host(host) ? COMMAND_SUCCESS : COMMAND_FAILURE;
}
static enum cmd_returncode_t cmd_host_param(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
const char *host_name = params[0];
const char *param = params[1];
const char *value = params[2];
host_entry_t *host = cmd_gethost(ctx, host_name);
if (!host) {
return COMMAND_FAILURE;
}
if (!strcasecmp(param, "timeout")) {
host->client_default_timeout_secs = atoi(value);
} else {
fprintf(stderr, "Invalid parameter: %s\n", param);
return COMMAND_FAILURE;
}
return COMMAND_SUCCESS;
}
static enum cmd_returncode_t cmd_do_showkey_volume(volume_entry_t *volume) {
char luks_passphrase[LUKS_PASSPHRASE_TEXT_SIZE_BYTES];
if (!keydb_get_volume_luks_passphrase(volume, luks_passphrase, sizeof(luks_passphrase))) {
OPENSSL_cleanse(luks_passphrase, sizeof(luks_passphrase));
fprintf(stderr, "Could not determine LUKS passphrase.\n");
return COMMAND_FAILURE;
}
char uuid[ASCII_UUID_BUFSIZE];
sprintf_uuid(uuid, volume->volume_uuid);
printf("LUKS passphrase of %s / %s: %s\n", volume->devmapper_name, uuid, luks_passphrase);
OPENSSL_cleanse(luks_passphrase, sizeof(luks_passphrase));
return COMMAND_SUCCESS;
}
static enum cmd_returncode_t cmd_add_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
const char *host_name = params[0];
host_entry_t *host = cmd_gethost(ctx, host_name);
if (!host) {
return COMMAND_FAILURE;
}
const char *devmapper_name = params[1];
const char *volume_uuid_str = params[2];
if (!is_valid_uuid(volume_uuid_str)) {
fprintf(stderr, "Not a valid UUID: %s\n", volume_uuid_str);
return COMMAND_FAILURE;
}
uint8_t volume_uuid[16];
parse_uuid(volume_uuid, volume_uuid_str);
volume_entry_t *volume = keydb_add_volume(host, devmapper_name, volume_uuid);
if (volume) {
return cmd_do_showkey_volume(volume);
} else {
return COMMAND_FAILURE;
}
}
static enum cmd_returncode_t cmd_del_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
const char *host_name = params[0];
const char *devmapper_name = params[1];
host_entry_t *host = cmd_gethost(ctx, host_name);
if (!host) {
return COMMAND_FAILURE;
}
if (!keydb_del_volume(host, devmapper_name)) {
return COMMAND_FAILURE;
}
return COMMAND_SUCCESS;
}
static enum cmd_returncode_t cmd_rekey_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
const char *host_name = params[0];
const char *devmapper_name = params[1];
volume_entry_t *volume = cmd_getvolume(ctx, host_name, devmapper_name);
if (!volume) {
return COMMAND_FAILURE;
}
if (!keydb_rekey_volume(volume)) {
return COMMAND_FAILURE;
}
return cmd_do_showkey_volume(volume);
}
static enum cmd_returncode_t cmd_showkey_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
const char *host_name = params[0];
const char *devmapper_name = params[1];
volume_entry_t *volume = cmd_getvolume(ctx, host_name, devmapper_name);
if (!volume) {
return COMMAND_FAILURE;
}
return cmd_do_showkey_volume(volume);
}
static enum cmd_returncode_t cmd_flag_volume(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
const char *host_name = params[0];
const char *devmapper_name = params[1];
const char *flag_str = params[2];
volume_entry_t *volume = cmd_getvolume(ctx, host_name, devmapper_name);
if (!volume) {
return COMMAND_FAILURE;
}
if ((flag_str[0] != '+') && (flag_str[0] != '-')) {
fprintf(stderr, "Flag string must start with '+' or '-' for adding or removing a flag.\n");
return COMMAND_FAILURE;
}
unsigned int flag_value = 0;
if (!strcasecmp(flag_str + 1, "allow_discards")) {
flag_value = VOLUME_FLAG_ALLOW_DISCARDS;
} else {
fprintf(stderr, "Invalid flag '%s': allowed is only 'allow_discards'.\n", flag_str + 1);
return COMMAND_FAILURE;
}
if (flag_str[0] == '+') {
volume->volume_flags |= flag_value;
} else {
volume->volume_flags &= ~flag_value;
}
return COMMAND_SUCCESS;
}
static enum cmd_returncode_t cmd_open(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
if (ctx->keydb) {
keydb_free(ctx->keydb);
}
const char *filename = params[0];
ctx->keydb = keydb_read(params[0]);
if (ctx->keydb) {
strncpy(ctx->filename, filename, sizeof(ctx->filename) - 1);
return COMMAND_SUCCESS;
} else {
return COMMAND_FAILURE;
}
}
static enum cmd_returncode_t cmd_save(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
if (!ctx->keydb) {
fprintf(stderr, "No key database loaded.\n");
return COMMAND_FAILURE;
}
const char *filename = (param_cnt == 1) ? params[0] : ctx->filename;
if (strlen(filename) == 0) {
fprintf(stderr, "No filename given.\n");
return COMMAND_FAILURE;
}
if (param_cnt == 1) {
strncpy(ctx->filename, filename, sizeof(ctx->filename) - 1);
}
if (strlen(ctx->passphrase) == 0) {
if (!query_passphrase("Database passphrase: ", ctx->passphrase, sizeof(ctx->passphrase))) {
fprintf(stderr, "Failed to read passphrase.\n");
return COMMAND_FAILURE;
}
}
bool success = keydb_write(ctx->keydb, ctx->filename, ctx->passphrase);
return success ? COMMAND_SUCCESS : COMMAND_FAILURE;
}
static enum cmd_returncode_t cmd_export(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
const char *host_name = params[0];
const char *filename = params[1];
host_entry_t *host = cmd_gethost(ctx, host_name);
if (!host) {
return COMMAND_FAILURE;
}
keydb_t *pubdb = keydb_export_public(host);
char passphrase[MAX_PASSPHRASE_LENGTH];
if (!query_passphrase("Client passphrase: ", passphrase, sizeof(passphrase))) {
fprintf(stderr, "Failed to read export passphrase.\n");
keydb_free(pubdb);
return COMMAND_FAILURE;
}
if (!keydb_write(pubdb, filename, passphrase)) {
fprintf(stderr, "Failed to write export passphrase.\n");
keydb_free(pubdb);
return COMMAND_FAILURE;
}
keydb_free(pubdb);
return COMMAND_SUCCESS;
}
#ifdef DEBUG
static enum cmd_returncode_t cmd_test(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
const char *cmd = params[0];
if (!strcmp(cmd, "vault")) {
if (!ctx->keydb) {
fprintf(stderr, "No keydb.\n");
return COMMAND_FAILURE;
}
struct vaulted_keydb_t *vkdb = vaulted_keydb_new(ctx->keydb);
fprintf(stderr, "Vault created at %p.\n", vkdb);
fprintf(stderr, "TLS-PSK vault %u bytes:\n", vkdb->tls_psk_vault->data_length);
dump_hex_long(stderr, vkdb->tls_psk_vault->data, vkdb->tls_psk_vault->data_length);
fprintf(stderr, "LUKS vault %u bytes:\n", vkdb->luks_passphrase_vault->data_length);
dump_hex_long(stderr, vkdb->luks_passphrase_vault->data, vkdb->luks_passphrase_vault->data_length);
fprintf(stderr, "~~~~~~~~~~~~~~~~ decrypted TLS-PSK ~~~~~~~~~~~~~~~~\n");
vault_open(vkdb->tls_psk_vault);
dump_hex_long(stderr, vkdb->tls_psk_vault->data, vkdb->tls_psk_vault->data_length);
fprintf(stderr, "~~~~~~~~~~~~~~~~ decrypted LUKS ~~~~~~~~~~~~~~~~\n");
vault_open(vkdb->luks_passphrase_vault);
dump_hex_long(stderr, vkdb->luks_passphrase_vault->data, vkdb->luks_passphrase_vault->data_length);
vaulted_keydb_free(vkdb);
return COMMAND_SUCCESS;
}
fprintf(stderr, "Test accepts one of these commands: vault\n");
return COMMAND_FAILURE;
}
static enum cmd_returncode_t cmd_rawdump(struct editor_context_t *ctx, const char *cmdname, unsigned int param_cnt, char **params) {
if (!ctx->keydb) {
return COMMAND_SUCCESS;
}
fprintf(stderr, "Version %d, %s, %d hosts.\n", ctx->keydb->common.keydb_version, ctx->keydb->server_database ? "server" : "client", ctx->keydb->host_count);
for (unsigned int i = 0; i < ctx->keydb->host_count; i++) {
host_entry_t *host = &ctx->keydb->hosts[i];
fprintf(stderr, "Host %d:\n", i);
{
char host_uuid[ASCII_UUID_BUFSIZE];
sprintf_uuid(host_uuid, host->host_uuid);
char hex_psk[(PSK_SIZE_BYTES * 2) + 1];
sprintf_hex(hex_psk, host->tls_psk, PSK_SIZE_BYTES);
fprintf(stderr, "openssl s_client -connect 127.0.0.1:23170 -psk %s -psk_identity %s -curves X448:X25519 -ciphersuites TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384 -tls1_3\n", hex_psk, host_uuid);
}
dump_hexline(stderr, " host_uuid ", host->host_uuid, sizeof(host->host_uuid), false);
dump_hexline(stderr, " host_name ", host->host_name, sizeof(host->host_name), true);
dump_hexline(stderr, " tls_psk ", host->tls_psk, sizeof(host->tls_psk), false);
fprintf(stderr, " volume_count %u\n", host->volume_count);
for (unsigned int j = 0; j < MAX_VOLUMES_PER_HOST; j++) {
volume_entry_t *volume = &host->volumes[j];
if (!is_zero(volume, sizeof(volume_entry_t))) {
fprintf(stderr, " Host %d / Volume %d:\n", i, j);
dump_hexline(stderr, " volume_uuid ", volume->volume_uuid, sizeof(volume->volume_uuid), false);
dump_hexline(stderr, " devmapper_name ", volume->devmapper_name, sizeof(volume->devmapper_name), true);
dump_hexline(stderr, " luks_passphrase ", volume->luks_passphrase_raw, sizeof(volume->luks_passphrase_raw), false);
}
}
}
return COMMAND_SUCCESS;
}
#endif
static const struct editor_command_t *find_command(const char *command_name) {
const struct editor_command_t *cmd = commands;
while (cmd->cmdnames[0]) {
for (unsigned int cmd_index = 0; cmd_index < MAX_COMMAND_ALIAS_COUNT; cmd_index++) {
const char *cmdname = cmd->cmdnames[cmd_index];
if (!cmdname) {
break;
}
if (!strcasecmp(cmdname, command_name)) {
return cmd;
}
}
cmd++;
}
return NULL;
}
static enum cmd_returncode_t execute_command(const struct editor_command_t *cmd, struct editor_context_t *ctx, unsigned int token_count, char **tokens) {
/* Found correct command */
const unsigned int parameter_count = token_count - 1;
if (parameter_count < cmd->min_params) {
return COMMAND_TOO_FEW_PARAMETERS;
} else if (parameter_count > cmd->max_params) {
return COMMAND_TOO_MANY_PARAMETERS;
} else {
return cmd->callback(ctx, tokens[0], parameter_count, tokens + 1);
}
}
bool editor_start(const struct pgmopts_edit_t *opts) {
struct editor_context_t editor_context = {
.running = true,
};
if (opts->filename) {
char *filename = strdup(opts->filename);
if (!filename) {
log_libc(LLVL_ERROR, "Unable to strdup(3)");
return false;
}
char *tokens[2] = {
"open",
filename,
};
enum cmd_returncode_t result = execute_command(find_command(tokens[0]), &editor_context, 2, tokens);
free(filename);
if (result != COMMAND_SUCCESS) {
return false;
}
}
while (editor_context.running) {
char command_buffer[256];
printf("> ");
if (!fgets(command_buffer, sizeof(command_buffer) - 1, stdin)) {
break;
}
if (!truncate_crlf(command_buffer)) {
/* Incomplete read? */
break;
}
const unsigned int max_token_count = 16;
unsigned int token_count = 0;
char *tokens[max_token_count];
char *strtok_inptr = command_buffer;
char *strtok_saveptr = NULL;
while (token_count < max_token_count) {
char *next_token = strtok_r(strtok_inptr, " \t", &strtok_saveptr);
if (!next_token) {
break;
}
tokens[token_count] = next_token;
token_count++;
strtok_inptr = NULL;
}
if (token_count == 0) {
continue;
}
const char *command_name = tokens[0];
const struct editor_command_t *command = find_command(command_name);
if (!command) {
printf("No such command: \"%s\" -- type \"help\" to get a list of valid commands\n", command_name);
continue;
}
enum cmd_returncode_t returncode = execute_command(command, &editor_context, token_count, tokens);
if (returncode == COMMAND_FAILURE) {
printf("Execution failed: %s\n", command_name);
} else if ((returncode == COMMAND_TOO_FEW_PARAMETERS) || (returncode == COMMAND_TOO_MANY_PARAMETERS)) {
char formatted_cmd[128];
format_command(formatted_cmd, command, command_name);
if (command->min_params == command->max_params) {
printf("Wrong number of parameters: \"%s\" requires %d parameters -- %s\n", command_name, command->min_params, formatted_cmd);
} else if (returncode == COMMAND_TOO_FEW_PARAMETERS) {
printf("Too few parameters: \"%s\" requires at least %d parameters -- %s\n", command_name, command->min_params, formatted_cmd);
} else if (returncode == COMMAND_TOO_MANY_PARAMETERS) {
printf("Too many parameters: \"%s\" requires at most %d parameters -- %s\n", command_name, command->max_params, formatted_cmd);
}
}
}
if (editor_context.keydb) {
keydb_free(editor_context.keydb);
}
OPENSSL_cleanse(&editor_context, sizeof(editor_context));
return true;
}

View File

@ -1,6 +1,6 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
@ -21,3 +21,14 @@
Johannes Bauer <JohannesBauer@gmx.de>
*/
#ifndef __EDITOR_H__
#define __EDITOR_H__
#include <stdint.h>
#include "pgmopts.h"
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
bool editor_start(const struct pgmopts_edit_t *opts);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

64
exec.c
View File

@ -1,6 +1,6 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
@ -27,6 +27,7 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include "exec.h"
@ -78,45 +79,80 @@ static char **argv_dup(const char **argv) {
return result;
}
struct runresult_t exec_command(const char **argv, bool show_output) {
struct runresult_t runresult;
char **argvcopy = argv_dup(argv);
struct exec_result_t exec_command(const struct exec_cmd_t *command) {
char **argvcopy = argv_dup(command->argv);
if (!argvcopy) {
return (struct exec_result_t) { .success = false };
}
memset(&runresult, 0, sizeof(runresult));
int pipefd[2];
if (pipe(pipefd) == -1) {
log_libc(LLVL_ERROR, "Creation of pipe(2) failed trying to execute %s", argvcopy[0]);
argv_free(argvcopy);
return (struct exec_result_t) { .success = false };
}
const int pipe_read_end = pipefd[0];
const int pipe_write_end = pipefd[1];
pid_t pid = fork();
if (pid == -1) {
perror("fork");
runresult.success = false;
argv_free(argvcopy);
return runresult;
return (struct exec_result_t) { .success = false };
}
if (pid == 0) {
/* Child */
if (!show_output) {
close(pipe_write_end);
if (dup2(pipe_read_end, STDIN_FILENO) == -1) {
log_libc(LLVL_ERROR, "Could not dup2(2) stdin while trying to execute %s", argvcopy[0]);
exit(EXIT_FAILURE);
}
if (!command->show_output) {
/* Shut up the child if user did not request debug output */
close(1);
close(2);
close(STDOUT_FILENO);
close(STDERR_FILENO);
}
execvp(argvcopy[0], argvcopy);
log_libc(LLVL_ERROR, "Execution of %s in forked child process failed execvp(3)", argvcopy[0]);
/* Exec failed, terminate child with EXIT_FAILUR (parent will catch
/* Exec failed, terminate child with EXIT_FAILURE (parent will catch
* this as the return code) */
exit(EXIT_FAILURE);
}
/* Parent process */
struct exec_result_t runresult = {
.success = true,
};
close(pipe_read_end);
if (command->stdin_data && command->stdin_length) {
unsigned int offset = 0;
unsigned int remaining_bytes = command->stdin_length;
const uint8_t *byte_buffer = (const uint8_t*)command->stdin_data;
while (remaining_bytes) {
ssize_t written = write(pipe_write_end, byte_buffer + offset, remaining_bytes);
if (written <= 0) {
log_libc(LLVL_ERROR, "writing to pipe returned %d", written);
runresult.success = false;
}
offset += written;
remaining_bytes -= written;
}
}
close(pipe_write_end);
int status;
if (waitpid(pid, &status, 0) == (pid_t)-1) {
log_libc(LLVL_ERROR, "exec_command %s failed executing waitpid(2)", argvcopy[0]);
runresult.success = false;
runresult.returncode = -1;
} else {
runresult.success = true;
runresult.returncode = WEXITSTATUS(status);
}
argv_free(argvcopy);
log_msg(LLVL_DEBUG, "Subprocess (PID %d): %s exited with returncode %d", pid, argv[0], runresult.returncode);
log_msg(LLVL_DEBUG, "Subprocess (PID %d): %s exited with returncode %d", pid, command->argv[0], runresult.returncode);
return runresult;
}

13
exec.h
View File

@ -1,6 +1,6 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
@ -26,14 +26,21 @@
#include <stdbool.h>
struct runresult_t {
struct exec_cmd_t {
const char **argv;
bool show_output;
const void *stdin_data;
unsigned int stdin_length;
};
struct exec_result_t {
bool success;
int returncode;
};
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
void argv_dump(const char **argv);
struct runresult_t exec_command(const char **argv, bool show_output);
struct exec_result_t exec_command(const struct exec_cmd_t *cmd);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

View File

@ -1,6 +1,6 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
@ -31,21 +31,27 @@
#include <openssl/rand.h>
#include "openssl.h"
#include "keyfile.h"
#include "binkeyfile.h"
#include "file_encryption.h"
#include "log.h"
#include "util.h"
#include "global.h"
struct key_t {
char passphrase[MAX_PASSPHRASE_LENGTH];
enum kdf_t kdf;
uint8_t salt[ENCRYPTED_FILE_SALT_SIZE];
uint8_t key[ENCRYPTED_FILE_KEY_SIZE];
};
#ifdef DEBUG
static void dump_key(const struct key_t *key) {
fprintf(stderr, "Dumping key:\n");
fprintf(stderr, " Passphrase : %s\n", key->passphrase);
fprintf(stderr, " Salt : ");
dump_hex(stderr, key->salt, BINKEYFILE_SALT_SIZE);
dump_hex(stderr, key->salt, ENCRYPTED_FILE_SALT_SIZE, false);
fprintf(stderr, "\n");
fprintf(stderr, " Derived key: ");
dump_hex(stderr, key->key, BINKEYFILE_KEY_SIZE);
dump_hex(stderr, key->key, ENCRYPTED_FILE_KEY_SIZE, false);
fprintf(stderr, "\n");
}
#endif
@ -53,14 +59,55 @@ static void dump_key(const struct key_t *key) {
/* Derives a previous key with known salt. Passphrase and salt must be set. */
static bool derive_previous_key(struct key_t *key) {
const unsigned int maxalloc_mib = 8 + ((128 * SCRYPT_N * SCRYPT_r * SCRYPT_p + (1024 * 1024 - 1)) / 1024 / 1024);
log_msg(LLVL_DEBUG, "Deriving scrypt key with N = %u, r = %u, p = %u, i.e., ~%u MiB of memory", SCRYPT_N, SCRYPT_r, SCRYPT_p, maxalloc_mib);
const unsigned int pwlen = strlen(key->passphrase);
const char *passphrase = (key->passphrase == NULL) ? "" : key->passphrase;
int pwlen = strlen(passphrase);
int result = EVP_PBE_scrypt(passphrase, pwlen, (unsigned char*)key->salt, BINKEYFILE_SALT_SIZE, SCRYPT_N, SCRYPT_r, SCRYPT_p, maxalloc_mib * 1024 * 1024, key->key, BINKEYFILE_KEY_SIZE);
if (result != 1) {
log_msg(LLVL_FATAL, "Fatal: key derivation using scrypt failed");
if ((key->kdf >= KDF_SCRYPT_MIN) && (key->kdf <= KDF_SCRYPT_MAX)) {
unsigned int N, r, p;
switch (key->kdf) {
case KDF_SCRYPT_N17_r8_p1:
N = 1 << 17;
r = 8;
p = 1;
break;
case KDF_SCRYPT_N18_r8_p1:
N = 1 << 18;
r = 8;
p = 1;
break;
default:
log_msg(LLVL_FATAL, "Fatal: unknown scrypt key derivation function (0x%x)", key->kdf);
return false;
}
const unsigned int maxalloc_mib = 8 + ((128 * N * r * p + (1024 * 1024 - 1)) / 1024 / 1024);
log_msg(LLVL_DEBUG, "Deriving scrypt key with N = %u, r = %u, p = %u, i.e., ~%u MiB of memory", N, r, p, maxalloc_mib);
int result = EVP_PBE_scrypt(key->passphrase, pwlen, (const unsigned char*)key->salt, ENCRYPTED_FILE_SALT_SIZE, N, r, p, maxalloc_mib * 1024 * 1024, key->key, ENCRYPTED_FILE_KEY_SIZE);
if (result != 1) {
log_msg(LLVL_FATAL, "Fatal: key derivation using scrypt failed");
return false;
}
} else if ((key->kdf >= KDF_PBKDF2_MIN) && (key->kdf <= KDF_PBKDF2_MAX)) {
unsigned int iterations;
switch (key->kdf) {
case KDF_PBKDF2_SHA256_1000:
iterations = 1000;
break;
default:
log_msg(LLVL_FATAL, "Fatal: unknown PBKDF2 key derivation function (0x%x)", key->kdf);
return false;
}
int result = PKCS5_PBKDF2_HMAC(key->passphrase, pwlen, (const unsigned char*)key->salt, ENCRYPTED_FILE_SALT_SIZE, iterations, EVP_sha256(), ENCRYPTED_FILE_KEY_SIZE, key->key);
if (result != 1) {
log_msg(LLVL_FATAL, "Fatal: key derivation using PBKDF2 failed");
return false;
}
} else {
log_msg(LLVL_FATAL, "Fatal: unknown key derivation function (0x%x)", key->kdf);
return false;
}
#ifdef DEBUG
@ -119,7 +166,7 @@ static bool encrypt_aes256_gcm(const void *plaintext, unsigned int plaintext_len
* this stage, but this does not occur in GCM mode. */
int padding_len = 0;
if (!EVP_EncryptFinal_ex(ctx, ciphertext + ciphertext_len, &padding_len)) {
log_openssl(LLVL_FATAL, "Encryption of tail failed.");
log_openssl(LLVL_FATAL, "Finalization of encryption failed.");
success = false;
break;
}
@ -204,7 +251,7 @@ static bool decrypt_aes256_gcm(unsigned char *ciphertext, unsigned int ciphertex
* anything else is a failure - the plaintext is not trustworthy. */
int padding_len = 0;
if (EVP_DecryptFinal_ex(ctx, (uint8_t*)plaintext + plaintext_len, &padding_len) <= 0) {
log_openssl(LLVL_FATAL, "Decryption of tail failed.");
log_openssl(LLVL_FATAL, "Finalization of decryption failed; likely authentication tag mismatch.");
success = false;
break;
}
@ -224,175 +271,172 @@ static bool decrypt_aes256_gcm(unsigned char *ciphertext, unsigned int ciphertex
/* Generates a random salt and derives a new key. Passphrase must be set. */
static bool derive_new_key(struct key_t *key) {
if (!RAND_bytes(key->salt, BINKEYFILE_SALT_SIZE)) {
if (!RAND_bytes(key->salt, ENCRYPTED_FILE_SALT_SIZE)) {
log_msg(LLVL_FATAL, "Cannot get salt entropy from RAND_bytes()");
return false;
}
return derive_previous_key(key);
}
bool read_binary_keyfile(const char *filename, struct keydb_t *keydb) {
bool success = true;
struct binkeyfile_t *binkeyfile = NULL;
struct keyentry_t *plaintext = NULL;
struct decrypted_file_t read_encrypted_file(const char *filename, passphrase_callback_function_t passphrase_callback) {
struct decrypted_file_t result = {
.success = true,
.data = NULL,
};
struct encrypted_file_t *encrypted_file = NULL;
struct key_t key = { 0 };
unsigned int binkeyfile_size = 0;
unsigned int plaintext_size = 0;
do {
memset(keydb, 0, sizeof(struct keydb_t));
/* Stat the file first to find out the size */
struct stat statbuf;
if (stat(filename, &statbuf) == -1) {
log_libc(LLVL_ERROR, "stat of %s failed", filename);
success = false;
result.success = false;
break;
}
/* Check if this is long enough to be a key file */
binkeyfile_size = statbuf.st_size;
if (binkeyfile_size < sizeof(struct binkeyfile_t)) {
log_msg(LLVL_ERROR, "Keyfile size of %s is too small to be valid (%d bytes).", filename, statbuf.st_size);
success = false;
break;
}
/* Check if the payload is a multiple of the keyent_t structure */
const unsigned int ciphertext_size = binkeyfile_size - sizeof(struct binkeyfile_t);
if ((ciphertext_size % sizeof(struct keyentry_t)) != 0) {
log_msg(LLVL_ERROR, "Keyfile size of %s has impossible/invalid file size (%d bytes not a multiple of %lu bytes).", filename, statbuf.st_size, sizeof(struct keyentry_t));
success = false;
/* Check if the file is long enough to be an encrypted file */
const unsigned int encrypted_file_size = statbuf.st_size;
if (encrypted_file_size < sizeof(struct encrypted_file_t)) {
log_msg(LLVL_ERROR, "%s: too small to be encrypted file (%u bytes)", filename, encrypted_file_size);
result.success = false;
break;
}
/* Now allocate memory for plain- and ciphertext */
plaintext_size = ciphertext_size;
binkeyfile = calloc(1, binkeyfile_size);
plaintext = calloc(1, plaintext_size);
encrypted_file = malloc(encrypted_file_size);
if (!encrypted_file) {
log_libc(LLVL_ERROR, "malloc(3) of encrypted file (%u bytes) failed", encrypted_file_size);
result.success = false;
break;
}
/* And read the file in */
const unsigned int ciphertext_size = encrypted_file_size - sizeof(struct encrypted_file_t);
const unsigned int plaintext_size = ciphertext_size;
result.data_length = plaintext_size;
result.data = malloc(plaintext_size);
if (!result.data) {
log_libc(LLVL_ERROR, "malloc(3) of plaintext (%u bytes) failed", plaintext_size);
result.success = false;
break;
}
/* Read in the encrypted file */
FILE *f = fopen(filename, "r");
if (!f) {
log_libc(LLVL_ERROR, "fopen");
success = false;
result.success = false;
break;
}
if (fread(binkeyfile, binkeyfile_size, 1, f) != 1) {
if (fread(encrypted_file, encrypted_file_size, 1, f) != 1) {
log_libc(LLVL_ERROR, "fread");
success = false;
result.success = false;
fclose(f);
break;
}
fclose(f);
/* Copy the file's salt into the key structure so we can derive the
* proper decryption key */
struct key_t key;
memset(&key, 0, sizeof(struct key_t));
memcpy(key.salt, binkeyfile->salt, BINKEYFILE_IV_SIZE);
memcpy(key.salt, encrypted_file->salt, ENCRYPTED_FILE_IV_SIZE);
key.kdf = encrypted_file->kdf;
/* Get the passphrase from the user (if it is protected with one) */
char *user_passphrase = NULL;
if (binkeyfile->empty_passphrase) {
key.passphrase = "";
if (encrypted_file->empty_passphrase) {
key.passphrase[0] = 0;
} else {
user_passphrase = query_passphrase("Keyfile password: ");
if (!user_passphrase) {
log_msg(LLVL_FATAL, "Failed to query passphrase.");
success = false;
if (!passphrase_callback) {
log_msg(LLVL_FATAL, "No passphrase callback given, but input file requires one.");
result.success = false;
break;
}
if (!passphrase_callback(key.passphrase, sizeof(key.passphrase))) {
log_msg(LLVL_FATAL, "Failed to query passphrase.");
result.success = false;
break;
}
key.passphrase = user_passphrase;
}
/* Then derive the key */
if (!derive_previous_key(&key)) {
log_msg(LLVL_FATAL, "Key derivation failed.");
success = false;
result.success = false;
break;
}
/* If we used a passphrase, free it again */
if (user_passphrase) {
key.passphrase = NULL;
memset(user_passphrase, 0, MAX_PASSPHRASE_LENGTH);
free(user_passphrase);
}
/* Then do the decryption and check if authentication is OK */
bool decryption_successful = decrypt_aes256_gcm(binkeyfile->ciphertext, ciphertext_size, binkeyfile->auth_tag, key.key, binkeyfile->iv, plaintext);
bool decryption_successful = decrypt_aes256_gcm(encrypted_file->ciphertext, ciphertext_size, encrypted_file->auth_tag, key.key, encrypted_file->iv, result.data);
if (!decryption_successful) {
log_msg(LLVL_FATAL, "Decryption error. Wrong passphrase or given file corrupt.");
success = false;
result.success = false;
break;
}
/* Finally copy the decrypted linear file over to the keydb_t structure
**/
for (unsigned int i = 0; i < plaintext_size / sizeof(struct keyentry_t); i++) {
if (!add_keyslot(keydb)) {
log_msg(LLVL_FATAL, "Failed to add keyslot.");
success = false;
break;
}
memcpy(last_keyentry(keydb), &plaintext[i], sizeof(struct keyentry_t));
}
} while (false);
if (plaintext) {
memset(plaintext, 0, plaintext_size);
free(plaintext);
OPENSSL_cleanse(&key, sizeof(key));
if (!result.success) {
if (result.data) {
OPENSSL_cleanse(result.data, result.data_length);
free(result.data);
}
result.data = NULL;
result.data_length = 0;
}
if (binkeyfile) {
memset(binkeyfile, 0, binkeyfile_size);
free(binkeyfile);
if (encrypted_file) {
free(encrypted_file);
}
return success;
return result;
}
bool write_binary_keyfile(const char *filename, const struct keydb_t *keydb, const char *passphrase) {
struct key_t key;
memset(&key, 0, sizeof(struct key_t));
key.passphrase = passphrase;
bool write_encrypted_file(const char *filename, const void *plaintext, unsigned int plaintext_length, const char *passphrase, enum kdf_t kdf) {
struct key_t key = {
.kdf = kdf,
};
strncpy(key.passphrase, passphrase, sizeof(key.passphrase) - 1);
if (!derive_new_key(&key)) {
log_msg(LLVL_FATAL, "Key derivation failed.");
return false;
}
/* Allocate memory for plain- and ciphertext */
const unsigned int payload_size = keydb->entrycnt * sizeof(struct keyentry_t);
const unsigned int binkeyfile_size = sizeof(struct binkeyfile_t) + payload_size;
struct keyentry_t *plaintext = calloc(1, payload_size);
struct binkeyfile_t *binkeyfile = calloc(1, binkeyfile_size);
if (!plaintext || !binkeyfile) {
log_libc(LLVL_FATAL, "malloc(3) plaintext or binkeyfile failed");
const unsigned int ciphertext_length = plaintext_length;
const unsigned int encrypted_file_size = sizeof(struct encrypted_file_t) + ciphertext_length;
struct encrypted_file_t *encrypted_file = calloc(1, encrypted_file_size);
if (!encrypted_file) {
log_libc(LLVL_FATAL, "malloc(3) of encrypted_file failed");
OPENSSL_cleanse(&key, sizeof(key));
return false;
}
/* Randomize encrypting IV and copy over key salt */
if (RAND_bytes(binkeyfile->iv, BINKEYFILE_IV_SIZE) != 1) {
/* Initialize encrypted file structure */
encrypted_file->empty_passphrase = (strlen(key.passphrase) == 0) ? 1 : 0;
encrypted_file->kdf = key.kdf;
memcpy(encrypted_file->salt, key.salt, ENCRYPTED_FILE_SALT_SIZE);
/* Randomize encrypting IV */
if (RAND_bytes(encrypted_file->iv, ENCRYPTED_FILE_IV_SIZE) != 1) {
log_openssl(LLVL_FATAL, "Failed to get entropy from RAND_bytes for IV");
OPENSSL_cleanse(&key, sizeof(key));
free(encrypted_file);
return false;
}
memcpy(binkeyfile->salt, key.salt, BINKEYFILE_SALT_SIZE);
binkeyfile->empty_passphrase = (passphrase == NULL) || (strlen(passphrase) == 0);
/* Copy plaintext into linear data array to prepare for encryption */
for (int i = 0; i < keydb->entrycnt; i++) {
memcpy(&plaintext[i], &keydb->entries[i], sizeof(struct keyentry_t));
}
/* Encrypt */
if (!encrypt_aes256_gcm(plaintext, payload_size, key.key, binkeyfile->iv, binkeyfile->ciphertext, binkeyfile->auth_tag)) {
/* Encrypt and authenticate plaintext */
if (!encrypt_aes256_gcm(plaintext, plaintext_length, key.key, encrypted_file->iv, encrypted_file->ciphertext, encrypted_file->auth_tag)) {
log_libc(LLVL_FATAL, "encryption failed");
OPENSSL_cleanse(&key, sizeof(key));
free(encrypted_file);
return false;
}
/* Destroy derived key */
OPENSSL_cleanse(&key, sizeof(key));
/* Write encrypted data to file */
FILE *f = fopen(filename, "w");
bool success = true;
if (f) {
if (fwrite(binkeyfile, binkeyfile_size, 1, f) != 1) {
if (fwrite(encrypted_file, encrypted_file_size, 1, f) != 1) {
log_libc(LLVL_ERROR, "fwrite(3) into %s failed", filename);
success = false;
}
@ -400,12 +444,8 @@ bool write_binary_keyfile(const char *filename, const struct keydb_t *keydb, con
} else {
log_libc(LLVL_ERROR, "fopen(3) of %s failed", filename);
success = false;
return false;
}
/* Destroy plaintext copy before freeing memory */
memset(plaintext, 0, payload_size);
free(plaintext);
free(encrypted_file);
return success;
}

69
file_encryption.h Normal file
View File

@ -0,0 +1,69 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#ifndef __FILE_ENCRYPTION_H__
#define __FILE_ENCRYPTION_H__
#include <stdint.h>
#include <stdbool.h>
enum kdf_t {
KDF_SCRYPT_MIN = 1,
KDF_SCRYPT_N17_r8_p1 = KDF_SCRYPT_MIN + 0,
KDF_SCRYPT_N18_r8_p1 = KDF_SCRYPT_MIN + 1,
KDF_SCRYPT_MAX = KDF_SCRYPT_MIN + 1,
KDF_PBKDF2_MIN = 0x100,
KDF_PBKDF2_SHA256_1000 = KDF_PBKDF2_MIN + 0, /* Deliberately crappy KDF for use with empty passphrases */
KDF_PBKDF2_MAX = KDF_PBKDF2_MIN + 0,
};
#define ENCRYPTED_FILE_DEFAULT_KDF KDF_SCRYPT_N18_r8_p1
#define ENCRYPTED_FILE_SALT_SIZE 16
#define ENCRYPTED_FILE_KEY_SIZE 32
#define ENCRYPTED_FILE_AUTH_TAG_SIZE 16
#define ENCRYPTED_FILE_IV_SIZE 16
typedef bool (*passphrase_callback_function_t)(char *buffer, unsigned int bufsize);
struct encrypted_file_t {
uint32_t empty_passphrase;
uint32_t kdf;
uint8_t salt[ENCRYPTED_FILE_SALT_SIZE];
uint8_t iv[ENCRYPTED_FILE_IV_SIZE];
uint8_t auth_tag[ENCRYPTED_FILE_AUTH_TAG_SIZE];
uint8_t ciphertext[];
};
struct decrypted_file_t {
bool success;
unsigned int data_length;
void *data;
};
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
struct decrypted_file_t read_encrypted_file(const char *filename, passphrase_callback_function_t passphrase_callback);
bool write_encrypted_file(const char *filename, const void *plaintext, unsigned int plaintext_length, const char *passphrase, enum kdf_t kdf);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

View File

@ -1,46 +0,0 @@
#!/usr/bin/python3
import os
import uuid
import collections
import getpass
import string
def tohex(data):
return "".join("%02x" % (c) for c in data)
def suggest_passphrase():
alphabet = string.ascii_lowercase + string.ascii_uppercase + string.digits
passphrase = os.urandom(24)
passphrase_int = sum(value << (8 * byteno) for (byteno, value) in enumerate(passphrase))
passphrase_ascii = ""
while passphrase_int > 0:
(passphrase_int, charno) = divmod(passphrase_int, len(alphabet))
passphrase_ascii += alphabet[charno]
return passphrase_ascii
Disk = collections.namedtuple("Disk", [ "uuid", "name", "passphrase" ])
host_uuid = str(uuid.uuid4())
host_psk = tohex(os.urandom(32))
disks = [ ]
while True:
disk_uuid = input("Disk UUID : ")
if disk_uuid == "":
break
disk_name = input("Disk name : ")
print("Suggestion: %s" % (suggest_passphrase()))
disk_passphrase = getpass.getpass("Passphrase: ")
disks.append(Disk(uuid = disk_uuid, name = disk_name, passphrase = disk_passphrase))
print()
server_string = ",".join("%s=%s" % (disk.uuid, disk.name) for disk in disks)
client_string = ",".join("%s=%s" % (disk.uuid, tohex(disk.passphrase.encode())) for disk in disks)
print("# server.txt")
print("# Host UUID Host PSK Disk UUIDs")
print("%s %s %s" % (host_uuid, host_psk, server_string))
print()
print("# client.txt")
print("# Host UUID Host PSK Disk UUIDs")
print("%s %s %s" % (host_uuid, host_psk, client_string))

View File

@ -24,36 +24,37 @@
#ifndef __GLOBAL_H__
#define __GLOBAL_H__
/* Magic is the prefix of announcement packages. It is the MD5SUM over the
* CLIENT_PSK_IDENTITY. This only changes when the protocol that is spoken
* changes. */
#define CLIENT_PSK_IDENTITY "luksrku v1"
#define CLIENT_ANNOUNCE_MAGIC { 0x46, 0xf2, 0xf6, 0xc6, 0x63, 0x12, 0x2e, 0x00, 0xa0, 0x8a, 0xae, 0x42, 0x0c, 0x51, 0xf5, 0x65 }
/* Blacklisting timeouts in seconds */
#define BLACKLIST_TIMEOUT_CLIENT 3600
#define BLACKLIST_TIMEOUT_SERVER 15
/* Size in bytes of the PSK that is used for TLS */
#define PSK_SIZE_BYTES 32
/* How many disks every entry may contain */
#define MAX_DISKS_PER_HOST 8
/* How many volumes every host may contain */
#define MAX_VOLUMES_PER_HOST 8
/* How long a passphrase can be maximally */
#define MAX_PASSPHRASE_LENGTH 64
/* How long in characters a host name may be */
#define MAX_HOST_NAME_LENGTH 64
/* How long in characters a cryptsetup device name mapping may be */
#define MAX_DEVMAPPER_NAME_LENGTH 63
#define MAX_DEVMAPPER_NAME_LENGTH 64
/* How long a passphrase is (this is raw binary, not text) */
#define LUKS_PASSPHRASE_RAW_SIZE_BYTES 32
/* How long a passphrase is in it's encoded form, storing it as a character array */
#define LUKS_PASSPHRASE_TEXT_SIZE_BYTES ((((LUKS_PASSPHRASE_RAW_SIZE_BYTES + 2) / 3) * 4) + 1)
/* Number of characters a user-defined passphrase may be long */
#define MAX_PASSPHRASE_LENGTH 256
/* Number of characters a database filename can be long */
#define MAX_FILENAME_LENGTH 256
/* In what interval the server should broadcast that it's waiting for unlocking */
#define WAITING_MESSAGE_BROADCAST_INTERVAL_MILLISECONDS 1000
#define BLACKLIST_ENTRY_COUNT 16
#define BLACKLIST_ENTRY_TIMEOUT_SECS 120
/* Scrypt parameters for config file encryption */
#define SCRYPT_N (1 << 17)
#define SCRYPT_r 8
#define SCRYPT_p 1
#define staticassert(cond) _Static_assert((cond), #cond)
#endif

View File

@ -2,7 +2,7 @@
#
# Initramfs-tools hook script for remote LUKS unlocking
#
# Copyright 2016 Johannes Bauer <joe@johannes-bauer.com>
# Copyright 2016-2019 Johannes Bauer <joe@johannes-bauer.com>
# Released under GPLv3
PREREQ=""
@ -21,8 +21,8 @@ esac
. /usr/share/initramfs-tools/hook-functions
if [ ! -f /etc/luksrku-server.bin ]; then
if [ ! -f /etc/luksrku-client.bin ]; then
exit 0
fi
cp /etc/luksrku-server.bin ${DESTDIR}/etc/
cp /etc/luksrku-client.bin ${DESTDIR}/etc/
copy_exec /usr/local/sbin/luksrku /sbin

View File

@ -1,6 +1,6 @@
#!/bin/sh
#
# Copyright 2016-2017 Johannes Bauer <joe@johannes-bauer.com>
# Copyright 2016-2019 Johannes Bauer <joe@johannes-bauer.com>
# Released under GPLv3
PREREQ="lvm2"
@ -22,11 +22,11 @@ esac
. /scripts/functions
if [ ! -f /etc/luksrku-server.bin ]; then
if [ ! -f /etc/luksrku-client.bin ]; then
exit 0
fi
configure_networking
/sbin/luksrku --server-mode -v -k /etc/luksrku-server.bin
/sbin/luksrku client -v /etc/luksrku-client.bin
exit 0

362
keydb.c Normal file
View File

@ -0,0 +1,362 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdbool.h>
#include <openssl/crypto.h>
#include "keydb.h"
#include "util.h"
#include "uuid.h"
#include "log.h"
static unsigned int keydb_getsize_v3_hostcount(unsigned int host_count) {
return sizeof(struct keydb_v3_t) + (host_count * sizeof(struct host_entry_v3_t));
}
static unsigned int keydb_getsize_v3(const struct keydb_v3_t *keydb) {
return keydb_getsize_v3_hostcount(keydb->host_count);
}
static unsigned int keydb_getsize_v2_hostcount(unsigned int host_count) {
return sizeof(struct keydb_v2_t) + (host_count * sizeof(struct host_entry_v2_t));
}
static unsigned int keydb_getsize_v2(const struct keydb_v2_t *keydb) {
return keydb_getsize_v2_hostcount(keydb->host_count);
}
static unsigned int keydb_getsize_hostcount(unsigned int host_count) {
return keydb_getsize_v3_hostcount(host_count);
}
static unsigned int keydb_getsize(const keydb_t *keydb) {
return keydb_getsize_v3(keydb);
}
keydb_t* keydb_new(void) {
keydb_t *keydb = calloc(sizeof(keydb_t), 1);
keydb->common.keydb_version = KEYDB_CURRENT_VERSION;
keydb->server_database = true;
return keydb;
}
keydb_t* keydb_export_public(host_entry_t *host) {
keydb_t *public_db = keydb_new();
if (!public_db) {
return NULL;
}
public_db->server_database = false;
if (!keydb_add_host(&public_db, host->host_name)) {
keydb_free(public_db);
return NULL;
}
/* Copy over whole entry */
host_entry_t *public_host = &public_db->hosts[0];
*public_host = *host;
/* But remove all LUKS passphrases of course, this is for the luksrku client */
for (unsigned int i = 0; i < host->volume_count; i++) {
volume_entry_t *volume = &public_host->volumes[i];
memset(volume->luks_passphrase_raw, 0, sizeof(volume->luks_passphrase_raw));
}
return public_db;
}
void keydb_free(keydb_t *keydb) {
if (keydb) {
OPENSSL_cleanse(keydb, keydb_getsize(keydb));
free(keydb);
}
}
volume_entry_t* keydb_get_volume_by_name(host_entry_t *host, const char *devmapper_name) {
for (unsigned int i = 0; i < host->volume_count; i++) {
volume_entry_t *volume = &host->volumes[i];
if (!strncasecmp(volume->devmapper_name, devmapper_name, sizeof(volume->devmapper_name) - 1)) {
return volume;
}
}
return NULL;
}
host_entry_t* keydb_get_host_by_name(keydb_t *keydb, const char *host_name) {
for (unsigned int i = 0; i < keydb->host_count; i++) {
host_entry_t *host = &keydb->hosts[i];
if (!strncasecmp(host->host_name, host_name, sizeof(host->host_name) - 1)) {
return host;
}
}
return NULL;
}
const volume_entry_t* keydb_get_volume_by_uuid(const host_entry_t *host, const uint8_t uuid[static 16]) {
for (unsigned int i = 0; i < host->volume_count; i++) {
const volume_entry_t *volume = &host->volumes[i];
if (!memcmp(volume->volume_uuid, uuid, 16)) {
return volume;
}
}
return NULL;
}
int keydb_get_host_index(const keydb_t *keydb, const host_entry_t *host) {
int index = host - keydb->hosts;
if (index < 0) {
return -1;
} else if ((unsigned int)index >= keydb ->host_count) {
return -1;
}
return index;
}
int keydb_get_volume_index(const host_entry_t *host, const volume_entry_t *volume) {
int index = volume - host->volumes;
if (index < 0) {
return -1;
} else if ((unsigned int)index >= host->volume_count) {
return -1;
}
return index;
}
const host_entry_t* keydb_get_host_by_uuid(const keydb_t *keydb, const uint8_t uuid[static 16]) {
for (unsigned int i = 0; i < keydb->host_count; i++) {
const host_entry_t *host = &keydb->hosts[i];
if (!memcmp(host->host_uuid, uuid, 16)) {
return host;
}
}
return NULL;
}
bool keydb_add_host(keydb_t **keydb, const char *host_name) {
if (strlen(host_name) > MAX_HOST_NAME_LENGTH - 1) {
log_msg(LLVL_ERROR, "Host name \"%s\" exceeds maximum length of %d characters.", host_name, MAX_HOST_NAME_LENGTH - 1);
return false;
}
keydb_t *old_keydb = *keydb;
if (keydb_get_host_by_name(old_keydb, host_name)) {
log_msg(LLVL_ERROR, "Host name \"%s\" already present in key database.", host_name);
return false;
}
keydb_t *new_keydb = realloc(old_keydb, keydb_getsize_hostcount(old_keydb->host_count + 1));
if (!new_keydb) {
return false;
}
*keydb = new_keydb;
host_entry_t *host = &new_keydb->hosts[new_keydb->host_count];
memset(host, 0, sizeof(host_entry_t));
if (!uuid_randomize(host->host_uuid)) {
/* We keep the reallocation but do not increase the host count */
return false;
}
strncpy(host->host_name, host_name, sizeof(host->host_name) - 1);
if (!keydb_rekey_host(host)) {
/* We keep the reallocation but do not increase the host count */
return false;
}
new_keydb->host_count++;
return true;
}
bool keydb_del_host_by_name(keydb_t **keydb, const char *host_name) {
keydb_t *old_keydb = *keydb;
host_entry_t *host = keydb_get_host_by_name(old_keydb, host_name);
if (!host) {
log_msg(LLVL_ERROR, "No such host: \"%s\"", host_name);
return false;
}
int host_index = keydb_get_host_index(old_keydb, host);
if (host_index < 0) {
log_msg(LLVL_FATAL, "Fatal error determining host index for hostname \"%s\".", host_name);
return false;
}
/* We keep the memory for now and do not realloc */
array_remove(old_keydb->hosts, sizeof(host_entry_t), old_keydb->host_count, host_index);
old_keydb->host_count--;
return true;
}
bool keydb_rekey_host(host_entry_t *host) {
return buffer_randomize(host->tls_psk, sizeof(host->tls_psk));
}
volume_entry_t* keydb_add_volume(host_entry_t *host, const char *devmapper_name, const uint8_t volume_uuid[static 16]) {
if (strlen(devmapper_name) > MAX_DEVMAPPER_NAME_LENGTH - 1) {
log_msg(LLVL_ERROR, "Device mapper name \"%s\" exceeds maximum length of %d characters.", devmapper_name, MAX_DEVMAPPER_NAME_LENGTH - 1);
return false;
}
if (host->volume_count >= MAX_VOLUMES_PER_HOST) {
log_msg(LLVL_ERROR, "Host \"%s\" already has maximum number of volumes (%d).", host->host_name, MAX_VOLUMES_PER_HOST);
return NULL;
}
if (keydb_get_volume_by_name(host, devmapper_name)) {
log_msg(LLVL_ERROR, "Volume name \"%s\" already present for host \"%s\" entry.", devmapper_name, host->host_name);
return NULL;
}
volume_entry_t *volume = &host->volumes[host->volume_count];
memcpy(volume->volume_uuid, volume_uuid, 16);
strncpy(volume->devmapper_name, devmapper_name, sizeof(volume->devmapper_name) - 1);
if (!buffer_randomize(volume->luks_passphrase_raw, sizeof(volume->luks_passphrase_raw))) {
log_msg(LLVL_ERROR, "Failed to produce %ld bytes of entropy for LUKS passphrase.", sizeof(volume->luks_passphrase_raw));
return NULL;
}
host->volume_count++;
return volume;
}
bool keydb_del_volume(host_entry_t *host, const char *devmapper_name) {
volume_entry_t *volume = keydb_get_volume_by_name(host, devmapper_name);
if (!volume) {
log_msg(LLVL_ERROR, "No such volume \"%s\" for host \"%s\".", devmapper_name, host->host_name);
return false;
}
int index = keydb_get_volume_index(host, volume);
if (index < 0) {
log_msg(LLVL_FATAL, "Fatal error determining volume index of \"%s\" for host \"%s\".", devmapper_name, host->host_name);
return false;
}
if (!array_remove(host->volumes, sizeof(volume_entry_t), host->volume_count, index)) {
log_msg(LLVL_ERROR, "Failed to remove \"%s\" of host \"%s\".", devmapper_name, host->host_name);
return false;
}
host->volume_count--;
return true;
}
bool keydb_rekey_volume(volume_entry_t *volume) {
return buffer_randomize(volume->luks_passphrase_raw, sizeof(volume->luks_passphrase_raw));
}
bool keydb_get_volume_luks_passphrase(const volume_entry_t *volume, char *dest, unsigned int dest_buffer_size) {
return ascii_encode(dest, dest_buffer_size, volume->luks_passphrase_raw, sizeof(volume->luks_passphrase_raw));
}
bool keydb_write(const keydb_t *keydb, const char *filename, const char *passphrase) {
enum kdf_t kdf;
if ((!passphrase) || (strlen(passphrase) == 0)) {
/* For empty password, we can also use garbage KDF */
kdf = KDF_PBKDF2_SHA256_1000;
} else {
kdf = ENCRYPTED_FILE_DEFAULT_KDF;
}
return write_encrypted_file(filename, keydb, keydb_getsize(keydb), passphrase, kdf);
}
static bool passphrase_callback(char *buffer, unsigned int bufsize) {
return query_passphrase("Database passphrase: ", buffer, bufsize);
}
static bool keydb_migrate_v2_to_v3(void **keydb_data, unsigned int *keydb_data_size) {
log_msg(LLVL_INFO, "Migrating keydb version 2 to version 3");
struct keydb_v2_t *old_db = *((struct keydb_v2_t **)keydb_data);
unsigned int new_db_size = keydb_getsize_v3_hostcount(old_db->host_count);
struct keydb_v3_t *new_db = calloc(1, new_db_size);
if (!new_db) {
log_msg(LLVL_ERROR, "keydb migration failed to allocate %d bytes of memory", new_db_size);
return false;
}
*new_db = (struct keydb_v3_t) {
.common.keydb_version = 3,
.server_database = old_db->server_database,
.host_count = old_db->host_count,
};
for (unsigned int i = 0; i < new_db->host_count; i++) {
/* Do not copy over host_flags or volumes */
memcpy(&new_db->hosts[i], &old_db->hosts[i], sizeof(old_db->hosts[i]) - sizeof(old_db->hosts[i].volumes));
for (unsigned int j = 0; j < new_db->hosts[i].volume_count; j++) {
/* Do not copy over volume_flags */
memcpy(&new_db->hosts[i].volumes[j], &old_db->hosts[i].volumes[j], sizeof(old_db->hosts[i].volumes[j]));
}
}
OPENSSL_cleanse(old_db, *keydb_data_size);
free(old_db);
*keydb_data = new_db;
*keydb_data_size = new_db_size;
return true;
}
static keydb_t* keydb_migrate(void **keydb_data, unsigned int *keydb_data_size) {
struct keydb_common_header_t *header;
header = *((struct keydb_common_header_t**)keydb_data);
if (header->keydb_version == 2) {
if (*keydb_data_size != keydb_getsize_v2(*keydb_data)) {
log_msg(LLVL_ERROR, "keydb version 2 has wrong size (%u bytes, but expected %u bytes).", *keydb_data_size, keydb_getsize_v2(*keydb_data));
return NULL;
}
if (!keydb_migrate_v2_to_v3(keydb_data, keydb_data_size)) {
log_msg(LLVL_ERROR, "keydb version 2 to 3 migration failed.");
return NULL;
}
}
header = *((struct keydb_common_header_t**)keydb_data);
if (header->keydb_version == 3) {
if (*keydb_data_size != keydb_getsize_v3(*keydb_data)) {
log_msg(LLVL_ERROR, "keydb version 3 has wrong size (%u bytes, but expected %u bytes).", *keydb_data_size, keydb_getsize_v3(*keydb_data));
return NULL;
}
}
header = *((struct keydb_common_header_t**)keydb_data);
if (header->keydb_version != KEYDB_CURRENT_VERSION) {
log_msg(LLVL_ERROR, "keydb could be read, but is of version %u (we expected %u).", header->keydb_version, KEYDB_CURRENT_VERSION);
return NULL;
}
return *((keydb_t**)keydb_data);
}
keydb_t* keydb_read(const char *filename) {
struct decrypted_file_t decrypted_file = read_encrypted_file(filename, passphrase_callback);
if (!decrypted_file.success) {
return NULL;
}
keydb_t *keydb = keydb_migrate(&decrypted_file.data, &decrypted_file.data_length);
if (!keydb) {
OPENSSL_cleanse(decrypted_file.data, decrypted_file.data_length);
free(decrypted_file.data);
return NULL;
}
return keydb;
}

121
keydb.h Normal file
View File

@ -0,0 +1,121 @@
/*
luksrku - Tool to remotely unlock LUKS volumes using TLS.
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#ifndef __KEYDB_H__
#define __KEYDB_H__
#include <stdint.h>
#include <stdbool.h>
#include "file_encryption.h"
#include "global.h"
#define ALIGNED __attribute__ ((aligned(4)))
enum volume_flag_t {
VOLUME_FLAG_ALLOW_DISCARDS = (1 << 0),
};
/* Unused so far */
enum host_flag_t {
HOST_FLAG_UNUSED = 0,
};
struct keydb_common_header_t {
unsigned int keydb_version;
} ALIGNED;
struct volume_entry_v2_t {
uint8_t volume_uuid[16]; /* UUID of crypt_LUKS volume */
char devmapper_name[MAX_DEVMAPPER_NAME_LENGTH]; /* dmsetup name when unlocked. Zero-terminated string. */
uint8_t luks_passphrase_raw[LUKS_PASSPHRASE_RAW_SIZE_BYTES]; /* LUKS passphrase used to unlock volume; raw byte data */
} ALIGNED;
struct host_entry_v2_t {
uint8_t host_uuid[16]; /* Host UUID */
char host_name[MAX_HOST_NAME_LENGTH]; /* Descriptive name of host */
uint8_t tls_psk[PSK_SIZE_BYTES]; /* Raw byte data of TLS-PSK that is used */
unsigned int volume_count; /* Number of volumes of this host */
struct volume_entry_v2_t volumes[MAX_VOLUMES_PER_HOST]; /* Volumes of this host */
} ALIGNED;
struct keydb_v2_t {
struct keydb_common_header_t common;
bool server_database;
unsigned int host_count;
struct host_entry_v2_t hosts[];
} ALIGNED;
struct volume_entry_v3_t {
uint8_t volume_uuid[16]; /* UUID of crypt_LUKS volume */
char devmapper_name[MAX_DEVMAPPER_NAME_LENGTH]; /* dmsetup name when unlocked. Zero-terminated string. */
uint8_t luks_passphrase_raw[LUKS_PASSPHRASE_RAW_SIZE_BYTES]; /* LUKS passphrase used to unlock volume; raw byte data */
unsigned int volume_flags; /* Bitset of enum volume_flag_t */
} ALIGNED;
struct host_entry_v3_t {
uint8_t host_uuid[16]; /* Host UUID */
char host_name[MAX_HOST_NAME_LENGTH]; /* Descriptive name of host */
uint8_t tls_psk[PSK_SIZE_BYTES]; /* Raw byte data of TLS-PSK that is used */
unsigned int volume_count; /* Number of volumes of this host */
unsigned int client_default_timeout_secs; /* Client gives up by default if not everything unlocked after this time */
unsigned int host_flags; /* Bitset of enum host_flag_t */
struct volume_entry_v3_t volumes[MAX_VOLUMES_PER_HOST]; /* Volumes of this host */
} ALIGNED;
struct keydb_v3_t {
struct keydb_common_header_t common;
bool server_database;
unsigned int host_count;
struct host_entry_v3_t hosts[];
} ALIGNED;
#define KEYDB_CURRENT_VERSION 3
typedef struct volume_entry_v3_t volume_entry_t;
typedef struct host_entry_v3_t host_entry_t;
typedef struct keydb_v3_t keydb_t;
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
keydb_t* keydb_new(void);
keydb_t* keydb_export_public(host_entry_t *host);
void keydb_free(keydb_t *keydb);
volume_entry_t* keydb_get_volume_by_name(host_entry_t *host, const char *devmapper_name);
host_entry_t* keydb_get_host_by_name(keydb_t *keydb, const char *host_name);
const volume_entry_t* keydb_get_volume_by_uuid(const host_entry_t *host, const uint8_t uuid[static 16]);
int keydb_get_host_index(const keydb_t *keydb, const host_entry_t *host);
int keydb_get_volume_index(const host_entry_t *host, const volume_entry_t *volume);
const host_entry_t* keydb_get_host_by_uuid(const keydb_t *keydb, const uint8_t uuid[static 16]);
bool keydb_add_host(keydb_t **keydb, const char *host_name);
bool keydb_del_host_by_name(keydb_t **keydb, const char *host_name);
bool keydb_rekey_host(host_entry_t *host);
volume_entry_t* keydb_add_volume(host_entry_t *host, const char *devmapper_name, const uint8_t volume_uuid[static 16]);
bool keydb_del_volume(host_entry_t *host, const char *devmapper_name);
bool keydb_rekey_volume(volume_entry_t *volume);
bool keydb_get_volume_luks_passphrase(const volume_entry_t *volume, char *dest, unsigned int dest_buffer_size);
bool keydb_write(const keydb_t *keydb, const char *filename, const char *passphrase);
keydb_t* keydb_read(const char *filename);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

107
keyfile.c
View File

@ -1,107 +0,0 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "keyfile.h"
#include "util.h"
struct keyentry_t *last_keyentry(struct keydb_t *keydb) {
return keydb_getentry(keydb, keydb->entrycnt - 1);
}
bool add_keyslot(struct keydb_t *keydb) {
struct keyentry_t *new_db = realloc(keydb->entries, (keydb->entrycnt + 1) * sizeof(struct keyentry_t));
if (!new_db) {
return false;
}
keydb->entries = new_db;
keydb->entrycnt++;
memset(&keydb->entries[keydb->entrycnt - 1], 0, sizeof(struct keyentry_t));
return true;
}
const struct keyentry_t *keydb_find_entry_by_host_uuid(const struct keydb_t *keydb, const uint8_t *server_uuid) {
for (int i = 0; i < keydb->entrycnt; i++) {
if (!memcmp(server_uuid, keydb->entries[i].host_uuid, 16)) {
return keydb->entries + i;
}
}
return NULL;
}
struct keyentry_t* keydb_getentry(struct keydb_t *keydb, int keyid) {
if ((keyid < 0) || (keyid >= keydb->entrycnt)) {
return NULL;
}
return keydb->entries + keyid;
}
void keydb_dump(const struct keydb_t *keydb) {
fprintf(stderr, "Dumping key database with %d host entries:\n", keydb->entrycnt);
for (int i = 0; i < keydb->entrycnt; i++) {
fprintf(stderr, " Host entry %d: host UUID ", i);
dump_uuid(stderr, keydb->entries[i].host_uuid);
fprintf(stderr, ", PSK ");
dump_hex(stderr, keydb->entries[i].psk, PSK_SIZE_BYTES);
fprintf(stderr, "\n");
for (int j = 0; j < MAX_DISKS_PER_HOST; j++) {
if (keydb->entries[i].disk_keys[j].occupied) {
fprintf(stderr, " Disk key %d: UUID ", j);
dump_uuid(stderr, keydb->entries[i].disk_keys[j].disk_uuid);
if (keydb->entries[i].disk_keys[j].passphrase_length) {
fprintf(stderr, " Key (%d bytes): ", keydb->entries[i].disk_keys[j].passphrase_length);
dump_hex(stderr, keydb->entries[i].disk_keys[j].passphrase, keydb->entries[i].disk_keys[j].passphrase_length);
}
if (keydb->entries[i].disk_keys[j].devmapper_name[0] != 0) {
fprintf(stderr, " devmapper name %s", keydb->entries[i].disk_keys[j].devmapper_name);
}
fprintf(stderr, "\n");
}
}
}
}
unsigned int keydb_disk_key_count(const struct keydb_t *keydb) {
unsigned int cnt = 0;
for (int i = 0; i < keydb->entrycnt; i++) {
for (int j = 0; j < MAX_DISKS_PER_HOST; j++) {
if (keydb->entries[i].disk_keys[j].occupied && (keydb->entries[i].disk_keys[j].passphrase_length > 0)) {
cnt++;
}
}
}
return cnt;
}
void keydb_free(struct keydb_t *keydb) {
memset(keydb->entries, 0, keydb->entrycnt * sizeof(struct keyentry_t));
free(keydb->entries);
memset(keydb, 0, sizeof(struct keydb_t));
}

View File

@ -1,61 +0,0 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#ifndef __KEYFILE_H__
#define __KEYFILE_H__
#include <stdint.h>
#include <stdbool.h>
#include "global.h"
struct diskentry_t {
bool occupied; /* Is this a valid entry (with a valid UUID) */
uint8_t disk_uuid[16]; /* Hex UUID of crypt_LUKS device */
char devmapper_name[MAX_DEVMAPPER_NAME_LENGTH + 1]; /* dmsetup name when unlocked. Zero-terminated string. */
uint32_t passphrase_length; /* LUKS passphrase size in bytes. Zero if no passphrase set. */
uint8_t passphrase[MAX_PASSPHRASE_LENGTH]; /* LUKS passphrase used to unlock disk */
};
struct keyentry_t {
uint8_t host_uuid[16]; /* Host UUID */
uint8_t psk[PSK_SIZE_BYTES]; /* Raw byte data */
struct diskentry_t disk_keys[MAX_DISKS_PER_HOST];
};
struct keydb_t {
int entrycnt;
struct keyentry_t *entries;
};
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
struct keyentry_t *last_keyentry(struct keydb_t *keydb);
bool add_keyslot(struct keydb_t *keydb);
const struct keyentry_t *keydb_find_entry_by_host_uuid(const struct keydb_t *keydb, const uint8_t *server_uuid);
struct keyentry_t* keydb_getentry(struct keydb_t *keydb, int keyid);
void keydb_dump(const struct keydb_t *keydb);
unsigned int keydb_disk_key_count(const struct keydb_t *keydb);
void keydb_free(struct keydb_t *keydb);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

View File

@ -1,18 +0,0 @@
#!/bin/bash
#
#
for FILENAME in $*; do
if [ ! -f "$FILENAME" ]; then
continue
fi
echo -n "${FILENAME}..."
grep "This file is part of luksrku" "$FILENAME" >/dev/null 2>&1
if [ "$?" == "0" ]; then
echo "Already tagged."
else
cat LICENSE-header "${FILENAME}" >tmp
mv tmp "${FILENAME}"
echo "Tagged!"
fi
done

12
log.c
View File

@ -29,14 +29,16 @@
#include <openssl/err.h>
#include "log.h"
#include "util.h"
static enum loglvl_t current_loglvl = LLVL_INFO;
static enum loglvl_t current_loglvl = LOGLEVEL_DEFAULT;
static const char *loglvl_names[] = {
[LLVL_FATAL] = "FATAL",
[LLVL_ERROR] = "ERROR",
[LLVL_WARNING] = "WARNING",
[LLVL_INFO] = "INFO",
[LLVL_DEBUG] = "DEBUG",
[LLVL_TRACE] = "TRACE",
};
void log_setlvl(enum loglvl_t level) {
@ -55,7 +57,7 @@ bool should_log(enum loglvl_t level) {
return level <= current_loglvl;
}
void log_msg(enum loglvl_t level, const char *msg, ...) {
void __attribute__ ((format (printf, 2, 3))) log_msg(enum loglvl_t level, const char *msg, ...) {
if (!should_log(level)) {
/* Suppress message */
return;
@ -87,8 +89,12 @@ void log_libc(enum loglvl_t level, const char *msg, ...) {
}
static int log_openssl_error_callback(const char *msg, size_t len, void *vlvlptr) {
char msgcopy[strlen(msg) + 1];
strcpy(msgcopy, msg);
truncate_crlf(msgcopy);
enum loglvl_t* levelptr = (enum loglvl_t*)vlvlptr;
log_msg(*levelptr, msg);
log_msg(*levelptr, "%s", msgcopy);
return 0;
}

7
log.h
View File

@ -24,18 +24,21 @@
#ifndef __LOG_H__
#define __LOG_H__
#define LOGLEVEL_DEFAULT LLVL_INFO
enum loglvl_t {
LLVL_FATAL = 0,
LLVL_ERROR = 1,
LLVL_WARNING = 2,
LLVL_INFO = 3,
LLVL_DEBUG = 4
LLVL_DEBUG = 4,
LLVL_TRACE = 5,
};
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
void log_setlvl(enum loglvl_t level);
bool should_log(enum loglvl_t level);
void log_msg(enum loglvl_t level, const char *msg, ...);
void __attribute__ ((format (printf, 2, 3))) log_msg(enum loglvl_t level, const char *msg, ...);
void log_libc(enum loglvl_t level, const char *msg, ...);
void log_openssl(enum loglvl_t level, const char *msg, ...);
/*************** AUTO GENERATED SECTION ENDS ***************/

106
luks.c
View File

@ -1,6 +1,6 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
@ -34,87 +34,51 @@
#include "log.h"
#include "exec.h"
#include "util.h"
#include "uuid.h"
bool is_luks_device_opened(const char *mapping_name) {
const char *command[] = {
"dmsetup",
"status",
mapping_name,
NULL,
struct exec_cmd_t cmd = {
.argv = (const char *[]){
"dmsetup",
"status",
mapping_name,
NULL,
},
.show_output = should_log(LLVL_TRACE),
};
struct runresult_t runresult = exec_command(command, should_log(LLVL_DEBUG));
struct exec_result_t runresult = exec_command(&cmd);
return runresult.success && (runresult.returncode == 0);
}
bool open_luks_device(const uint8_t *encrypted_device_uuid, const char *mapping_name, const char *passphrase_file) {
bool open_luks_device(const uint8_t *encrypted_device_uuid, const char *mapping_name, const char *passphrase, unsigned int passphrase_length, bool allow_discards) {
char encrypted_device[64];
strcpy(encrypted_device, "UUID=");
sprintf_uuid(encrypted_device + 5, encrypted_device_uuid);
log_msg(LLVL_INFO, "Trying to unlock LUKS mapping %s based on %s", mapping_name, encrypted_device);
const char *command[] = {
"cryptsetup",
"luksOpen",
"-T", "1",
"-d", passphrase_file,
encrypted_device,
mapping_name,
NULL,
struct exec_cmd_t cmd = {
.argv = !allow_discards ? (const char *[]) {
"cryptsetup",
"luksOpen",
"-T", "1",
encrypted_device,
mapping_name,
NULL,
} :
(const char *[]) {
"cryptsetup",
"--allow-discards",
"luksOpen",
"-T", "1",
encrypted_device,
mapping_name,
NULL,
},
.stdin_data = passphrase,
.stdin_length = passphrase_length,
.show_output = should_log(LLVL_DEBUG),
};
struct runresult_t runresult = exec_command(command, should_log(LLVL_DEBUG));
struct exec_result_t runresult = exec_command(&cmd);
return runresult.success && (runresult.returncode == 0);
}
static bool wipe_passphrase_file(const char *filename, int length) {
uint8_t wipe_buf[length];
memset(wipe_buf, 0, length);
int fd = open(filename, O_WRONLY);
if (fd == -1) {
log_libc(LLVL_ERROR, "Wiping of passphrase file %s failed in open(2)", filename);
return false;
}
if (write(fd, wipe_buf, length) != length) {
log_libc(LLVL_ERROR, "Wiping of passphrase file %s failed in write(2)", filename);
close(fd);
return false;
}
close(fd);
unlink(filename);
return true;
}
static const char *write_passphrase_file(const uint8_t *passphrase, int passphrase_length) {
//const char *filename = "/dev/shm/luksrku_passphrase.bin"; /* TODO make this variable */
const char *filename = "/tmp/luksrku_passphrase.bin";
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0600);
if (fd == -1) {
log_libc(LLVL_ERROR, "Creation of passphrase file %s failed", filename);
return NULL;
}
if (write(fd, passphrase, passphrase_length) != passphrase_length) {
log_libc(LLVL_ERROR, "Writing to passphrase file %s failed", filename);
wipe_passphrase_file(filename, passphrase_length);
close(fd);
return NULL;
}
close(fd);
return filename;
}
bool open_luks_device_pw(const uint8_t *encrypted_device_uuid, const char *mapping_name, const uint8_t *passphrase, int passphrase_length) {
const char *pw_filename = write_passphrase_file(passphrase, passphrase_length);
if (!pw_filename) {
return false;
}
bool success = open_luks_device(encrypted_device_uuid, mapping_name, pw_filename);
if (!wipe_passphrase_file(pw_filename, passphrase_length)) {
log_libc(LLVL_ERROR, "Wiping of passphrase file failed -- treating this unlock as failed (luksOpen %s)", success ? "succeeded" : "also failed");
success = false;
}
return success;
}

3
luks.h
View File

@ -29,8 +29,7 @@
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
bool is_luks_device_opened(const char *mapping_name);
bool open_luks_device(const uint8_t *encrypted_device_uuid, const char *mapping_name, const char *passphrase_file);
bool open_luks_device_pw(const uint8_t *encrypted_device_uuid, const char *mapping_name, const uint8_t *passphrase, int passphrase_length);
bool open_luks_device(const uint8_t *encrypted_device_uuid, const char *mapping_name, const char *passphrase, unsigned int passphrase_length, bool allow_discards);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

View File

@ -1,76 +0,0 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include "keyfile.h"
#include "parse-keyfile.h"
#include "binkeyfile.h"
#include "util.h"
int main(int argc, char **argv) {
#ifdef DEBUG
fprintf(stderr, "WARNING: This has been compiled in DEBUG mode and uses reduced security.\n");
#endif
if (argc != 4) {
fprintf(stderr, "%s [server|client] [Infile] [Outfile]\n", argv[0]);
exit(EXIT_FAILURE);
}
bool server = !strcasecmp(argv[1], "server");
bool client = !strcasecmp(argv[1], "client");
if (!server && !client) {
fprintf(stderr, "First parameter must be either 'client' or 'server'.\n");
exit(EXIT_FAILURE);
}
const char *infile = argv[2];
const char *outfile = argv[3];
struct keydb_t keydb;
if (!parse_keyfile(infile, &keydb, server)) {
fprintf(stderr, "Failed to parse key file %s. Aborting.\n", infile);
exit(EXIT_FAILURE);
}
fprintf(stderr, "Successfully read key file with %d entries.\n", keydb.entrycnt);
char *passphrase = NULL;
if (client) {
passphrase = query_passphrase("Passphrase to encrypt keyfile: ");
if (!passphrase) {
fprintf(stderr, "Failed to get passphrase.\n");
exit(EXIT_FAILURE);
}
}
if (!write_binary_keyfile(outfile, &keydb, passphrase)) {
fprintf(stderr, "Failed to write binary key file %s. Aborting.\n", outfile);
exit(EXIT_FAILURE);
}
free(passphrase);
return 0;
}

View File

@ -25,90 +25,53 @@
#include <stdlib.h>
#include <string.h>
#include "openssl.h"
#include "log.h"
#include "pgmopts.h"
#include "editor.h"
#include "openssl.h"
#include "server.h"
#include "client.h"
#include "openssl.h"
#include "binkeyfile.h"
#include "cmdline.h"
#include "log.h"
#include "keyfile.h"
#if OPENSSL_VERSION_NUMBER < 0x010100000
#error "luksrku requires at least OpenSSL v1.1 to work."
#endif
static int main_edit(const struct pgmopts_edit_t *opts) {
log_setlvl(LOGLEVEL_DEFAULT + opts->verbosity);
return editor_start(opts) ? 0 : 1;
}
static int main_server(const struct pgmopts_server_t *opts) {
log_setlvl(LOGLEVEL_DEFAULT + opts->verbosity);
return keyserver_start(opts) ? 0 : 1;
}
static int main_client(const struct pgmopts_client_t *opts) {
log_setlvl(LOGLEVEL_DEFAULT + opts->verbosity);
return keyclient_start(opts) ? 0 : 1;
}
int main(int argc, char **argv) {
#ifdef DEBUG
fprintf(stderr, "WARNING: This has been compiled in DEBUG mode and uses reduced security.\n");
#endif
struct options_t options;
if (!parse_cmdline_arguments(&options, argc, argv)) {
print_syntax(argv[0]);
exit(EXIT_FAILURE);
}
if (options.verbose) {
log_setlvl(LLVL_DEBUG);
}
parse_pgmopts_or_quit(argc, argv);
if (!openssl_init()) {
log_msg(LLVL_FATAL, "Could not initialize OpenSSL.");
exit(EXIT_FAILURE);
}
struct keydb_t keydb;
bool success = true;
memset(&keydb, 0, sizeof(keydb));
do {
if (!read_binary_keyfile(options.keydbfile, &keydb)) {
log_msg(LLVL_FATAL, "Could not read key database file %s.", options.keydbfile);
success = false;
break;
}
switch (pgmopts->pgm) {
case PGM_EDIT:
return main_edit(&pgmopts->edit);
log_msg(LLVL_DEBUG, "Successfully loaded key database file %s with %d entries and %d disk keys.", options.keydbfile, keydb.entrycnt, keydb_disk_key_count(&keydb));
#ifdef DEBUG
keydb_dump(&keydb);
#endif
case PGM_SERVER:
return main_server(&pgmopts->server);
if (keydb.entrycnt == 0) {
log_msg(LLVL_FATAL, "Key database file %s contains no keys.", options.keydbfile);
success = false;
break;
}
if (options.mode == SERVER_MODE) {
if (keydb.entrycnt != 1) {
log_msg(LLVL_FATAL, "Server configuration files need to have exactly one host entry.");
success = false;
break;
}
if (keydb_disk_key_count(&keydb) != 0) {
log_msg(LLVL_FATAL, "Server configuration files may not contain disk unlocking keys.");
success = false;
break;
}
if (!dtls_server(keydb_getentry(&keydb, 0), &options)) {
log_msg(LLVL_FATAL, "Failed to start DTLS server.");
success = false;
break;
}
} else {
if (!dtls_client(&keydb, &options)) {
log_msg(LLVL_FATAL, "Failed to connect DTLS client.");
success = false;
break;
}
}
} while (false);
keydb_free(&keydb);
if (!success) {
exit(EXIT_FAILURE);
case PGM_CLIENT:
return main_client(&pgmopts->client);
}
return 0;
}

26
msg.h
View File

@ -27,22 +27,26 @@
#include <stdint.h>
#include "global.h"
struct announcement_t {
uint8_t magic[16];
/* Magic is the prefix of announcement packages. It is the MD5SUM over the
* string "luksrku v2". This only changes when the protocol that is spoken
* changes. */
#define UDP_MESSAGE_MAGIC_SIZE 16
#define UDP_MESSAGE_MAGIC (const uint8_t[UDP_MESSAGE_MAGIC_SIZE]){ 0x46, 0xf2, 0xf6, 0xc6, 0x63, 0x12, 0x2e, 0x00, 0xa0, 0x8a, 0xae, 0x42, 0x0c, 0x51, 0xf5, 0x65 }
struct udp_query_t {
uint8_t magic[UDP_MESSAGE_MAGIC_SIZE];
uint8_t host_uuid[16];
} __attribute__ ((packed));
struct msg_t {
uint8_t disk_uuid[16];
uint32_t passphrase_length;
uint8_t passphrase[MAX_PASSPHRASE_LENGTH];
struct udp_response_t {
uint8_t magic[UDP_MESSAGE_MAGIC_SIZE];
} __attribute__ ((packed));
staticassert(sizeof(struct msg_t) == 16 + 4 + MAX_PASSPHRASE_LENGTH);
struct msg_t {
uint8_t volume_uuid[16];
uint8_t luks_passphrase_raw[LUKS_PASSPHRASE_RAW_SIZE_BYTES];
} __attribute__ ((packed));
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
void msg_to_nbo(struct msg_t *msg);
void msg_to_hbo(struct msg_t *msg);
/*************** AUTO GENERATED SECTION ENDS ***************/
staticassert(sizeof(struct msg_t) == 16 + LUKS_PASSPHRASE_RAW_SIZE_BYTES);
#endif

View File

@ -74,35 +74,32 @@ bool create_generic_tls_context(struct generic_tls_ctx_t *gctx, bool server) {
const long flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_COMPRESSION | SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
SSL_CTX_set_options(gctx->ctx, flags);
if (!SSL_CTX_set_min_proto_version(gctx->ctx, TLS1_2_VERSION)) {
if (!SSL_CTX_set_min_proto_version(gctx->ctx, TLS1_3_VERSION)) {
log_openssl(LLVL_FATAL, "Cannot set TLS generic context minimal version.");
return false;
}
if (!SSL_CTX_set_max_proto_version(gctx->ctx, TLS1_2_VERSION)) {
if (!SSL_CTX_set_max_proto_version(gctx->ctx, TLS1_3_VERSION)) {
log_openssl(LLVL_FATAL, "Cannot set TLS generic context maximal version.");
return false;
}
if (!SSL_CTX_set_cipher_list(gctx->ctx, "ECDHE-PSK-CHACHA20-POLY1305")) {
/* SSL_CTX_set_ciphersuites for TLSv1.3
* SSL_CTX_set_cipher_list for TLS v1.2 and below */
if (!SSL_CTX_set_ciphersuites(gctx->ctx, "TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384")) {
log_openssl(LLVL_FATAL, "Cannot set TLS generic context cipher suites.");
return false;
}
/* In the cipher suite we're using, none of these should be used anyways
* (PSK); however for the future we want to have proper crypto here as
* well. */
if (!SSL_CTX_set1_sigalgs_list(gctx->ctx, "ECDSA+SHA256:RSA+SHA256:ECDSA+SHA384:RSA+SHA384:ECDSA+SHA512:RSA+SHA512")) {
if (!SSL_CTX_set1_sigalgs_list(gctx->ctx, "ed448:ed25519")) {
log_openssl(LLVL_FATAL, "Cannot set TLS generic context signature algorithms.");
return false;
}
/* TODO: When X448 becomes available, include it here. */
if (!SSL_CTX_set1_curves_list(gctx->ctx, "X25519")) {
if (!SSL_CTX_set1_curves_list(gctx->ctx, "X448:X25519")) {
log_openssl(LLVL_FATAL, "Cannot set TLS generic context ECDHE curves.");
return false;
}
return true;
}
@ -114,3 +111,63 @@ void free_generic_tls_context(struct generic_tls_ctx_t *gctx) {
gctx->conf_ctx = NULL;
}
enum psk_hash_t {
PSK_HASH_SHA256,
PSK_HASH_SHA384,
};
int openssl_tls13_psk_establish_session(SSL *ssl, const uint8_t *psk, unsigned int psk_length, const EVP_MD *cipher_md, SSL_SESSION **new_session) {
uint8_t codepoint[2];
if (cipher_md == EVP_sha256()) {
// TLS_AES_128_GCM_SHA256
codepoint[0] = 0x13;
codepoint[1] = 0x01;
} else if (cipher_md == EVP_sha384()) {
// TLS_AES_256_GCM_SHA384
codepoint[0] = 0x13;
codepoint[1] = 0x02;
} else {
log_msg(LLVL_ERROR, "Unknown hash function %p (%s) passed for which we do not know how to create a SSL_CIPHER*.", cipher_md, EVP_MD_name(cipher_md));
return 0;
}
const SSL_CIPHER *cipher = SSL_CIPHER_find(ssl, codepoint);
if (!cipher) {
log_msg(LLVL_ERROR, "Unable to determine SSL_CIPHER* from codepoint 0x%02x 0x%02x (%s).", codepoint[0], codepoint[1], EVP_MD_name(cipher_md));
return 0;
}
SSL_SESSION *sess = SSL_SESSION_new();
if (!sess) {
log_openssl(LLVL_ERROR, "Failed to create SSL_SESSION context for client.");
return 0;
}
int return_value = 1;
do {
if (!SSL_SESSION_set1_master_key(sess, psk, psk_length)) {
log_openssl(LLVL_ERROR, "Failed to set TLSv1.3-PSK master key.");
return_value = 0;
break;
}
if (!SSL_SESSION_set_cipher(sess, cipher)) {
log_openssl(LLVL_ERROR, "Failed to set TLSv1.3-PSK cipher.");
return_value = 0;
break;
}
if (!SSL_SESSION_set_protocol_version(sess, TLS1_3_VERSION)) {
log_openssl(LLVL_ERROR, "Failed to set TLSv1.3-PSK protocol version.");
return_value = 0;
break;
}
} while (false);
if (return_value) {
*new_session = sess;
} else {
SSL_SESSION_free(sess);
}
return return_value;
}

View File

@ -37,6 +37,7 @@ struct generic_tls_ctx_t {
bool openssl_init(void);
bool create_generic_tls_context(struct generic_tls_ctx_t *gctx, bool server);
void free_generic_tls_context(struct generic_tls_ctx_t *gctx);
int openssl_tls13_psk_establish_session(SSL *ssl, const uint8_t *psk, unsigned int psk_length, const EVP_MD *cipher_md, SSL_SESSION **new_session);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

View File

@ -1,190 +0,0 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "keyfile.h"
#include "parse-keyfile.h"
#include "log.h"
#include "util.h"
static bool is_valid_psk(const char *hexpsk) {
if (strlen(hexpsk) != 2 * PSK_SIZE_BYTES) {
return false;
}
if (!is_hex(hexpsk, strlen(hexpsk))) {
return false;
}
return true;
}
static bool is_valid_passphrase(const char *hexpass) {
if ((strlen(hexpass) % 2) == 1) {
return false;
}
return is_hex(hexpass, strlen(hexpass));
}
bool parse_keyfile(const char *filename, struct keydb_t *keydb, bool server_keyfile) {
log_msg(LLVL_DEBUG, "Parsing %s keyfile from %s.", server_keyfile ? "server" : "client", filename);
memset(keydb, 0, sizeof(struct keydb_t));
FILE *f = fopen(filename, "r");
if (!f) {
return false;
}
char line[4096];
int lineno = 0;
while (fgets(line, sizeof(line) - 1, f) != NULL) {
lineno++;
/* Guarantee zero-termination */
line[sizeof(line) - 1] = 0;
int len = strlen(line);
/* Remove CR/LF */
if (line[len - 1] == '\r') {
line[--len] = 0;
}
if (line[len - 1] == '\n') {
line[--len] = 0;
}
if (len == 0) {
/* Empty line */
continue;
}
if ((line[0] == '#') || (line[0] == ';')) {
/* Comment */
continue;
}
if (!add_keyslot(keydb)) {
log_msg(LLVL_ERROR, "Cannot allocate memory for keydb in line %d.", lineno);
return false;
}
struct keyentry_t *slot = last_keyentry(keydb);
char *saveptr = NULL;
char *next;
next = strtok_r(line, "\t ", &saveptr);
if (!next) {
log_msg(LLVL_ERROR, "Cannot parse line %d of %s: Empty line.", lineno, filename);
return false;
}
if (!parse_uuid(slot->host_uuid, next)) {
log_msg(LLVL_ERROR, "Cannot parse line %d of %s: No UUID given as server identifier.", lineno, filename);
return false;
}
next = strtok_r(NULL, "\t ", &saveptr);
if (!next) {
log_msg(LLVL_ERROR, "Cannot parse line %d of %s: No second token (TLS PSK).", lineno, filename);
return false;
}
if (!is_valid_psk(next)) {
log_msg(LLVL_ERROR, "Cannot parse line %d of %s: No valid PSK given (needs to be %d bytes, i.e. %d hex characters).", lineno, filename, PSK_SIZE_BYTES, 2 * PSK_SIZE_BYTES);
return false;
}
int psk_len = parse_hexstr(next, slot->psk, PSK_SIZE_BYTES);
if (psk_len != PSK_SIZE_BYTES) {
/* Should never happen, but double-check for robustness */
log_msg(LLVL_ERROR, "Cannot parse line %d of %s: %d bytes parsed, but %d expected.", lineno, filename, psk_len, PSK_SIZE_BYTES);
return false;
}
next = strtok_r(NULL, "\t ", &saveptr);
if (!next) {
log_msg(LLVL_ERROR, "Cannot parse line %d of %s: No third token (disk UUIDs/passphrases).", lineno, filename);
return false;
}
if (next != NULL) {
for (int i = 0; i < MAX_DISKS_PER_HOST; i++) {
log_msg(LLVL_DEBUG, "Parsing keyentry #%d", i);
next = strtok_r(next, "=", &saveptr);
if (!next) {
log_msg(LLVL_DEBUG, "Done parsing host config.");
break;
}
if (!parse_uuid(slot->disk_keys[i].disk_uuid, next)) {
log_msg(LLVL_ERROR, "Cannot parse line %d of %s: disk identifier '%s' is not a valid UUID.", lineno, filename, next);
return false;
}
next = strtok_r(NULL, ",", &saveptr);
if (!server_keyfile) {
if (!next) {
log_msg(LLVL_ERROR, "Client config needs to have a hex passphrase after a LUKS disk UUID.");
return false;
}
if (!is_valid_passphrase(next)) {
log_msg(LLVL_ERROR, "Cannot parse line %d of %s: disk passphrase '%s' is invalid.", lineno, filename, next);
return false;
}
slot->disk_keys[i].passphrase_length = parse_hexstr(next, slot->disk_keys[i].passphrase, MAX_PASSPHRASE_LENGTH);
} else {
if (!next) {
log_msg(LLVL_ERROR, "Server config needs to have a device mapper name after a LUKS disk UUID.");
return false;
}
if (strlen(next) > MAX_DEVMAPPER_NAME_LENGTH) {
log_msg(LLVL_ERROR, "Cannot parse line %d of %s: device mapper name '%s' is too long (%d characters max).", lineno, filename, next, MAX_DEVMAPPER_NAME_LENGTH);
return false;
}
if (next[0] == 0) {
log_msg(LLVL_ERROR, "Cannot parse line %d of %s: device mapper name is empty.", lineno, filename);
return false;
}
strcpy(slot->disk_keys[i].devmapper_name, next);
}
slot->disk_keys[i].occupied = true;
next = NULL;
}
}
next = strtok_r(NULL, "\t ", &saveptr);
if (next) {
log_msg(LLVL_ERROR, "Cannot parse line %d of %s: Too many fields in line.", lineno, filename);
return false;
}
}
fclose(f);
#ifdef DEBUG
keydb_dump(keydb);
#endif
return true;
}

8
parsers/parser_client.py Executable file
View File

@ -0,0 +1,8 @@
import argparse
parser = argparse.ArgumentParser(prog = "luksrku client", description = "Connects to a luksrku key server and unlocks local LUKS volumes.", add_help = False)
parser.add_argument("-t", "--timeout", metavar = "secs", default = 0, help = "When searching for a keyserver and not all volumes can be unlocked, abort after this period of time, given in seconds. Defaults to infinity. This argument can be specified as a host-based configuration parameter as well; the command-line argument always takes precedence.")
parser.add_argument("-p", "--port", metavar = "port", default = 23170, help = "Port that is used for both UDP and TCP communication. Defaults to %(default)d.")
parser.add_argument("--no-luks", action = "store_true", help = "Do not call LUKS/cryptsetup. Useful for testing unlocking procedure.")
parser.add_argument("-v", "--verbose", action = "count", default = 0, help = "Increase verbosity. Can be specified multiple times.")
parser.add_argument("filename", metavar = "filename", help = "Exported database file to load TLS-PSKs and list of disks from.")
parser.add_argument("hostname", metavar = "hostname", nargs = "?", help = "When hostname is given, auto-searching for suitable servers is disabled and only a connection to the given hostname is attempted.")

4
parsers/parser_edit.py Executable file
View File

@ -0,0 +1,4 @@
import argparse
parser = argparse.ArgumentParser(prog = "luksrku edit", description = "Edits a luksrku key database.", add_help = False)
parser.add_argument("-v", "--verbose", action = "count", default = 0, help = "Increase verbosity. Can be specified multiple times.")
parser.add_argument("filename", metavar = "filename", nargs = "?", type = str, help = "Database file to edit.")

6
parsers/parser_server.py Executable file
View File

@ -0,0 +1,6 @@
import argparse
parser = argparse.ArgumentParser(prog = "luksrku server", description = "Starts a luksrku key server.", add_help = False)
parser.add_argument("-p", "--port", metavar = "port", default = 23170, help = "Port that is used for both UDP and TCP communication. Defaults to %(default)d.")
parser.add_argument("-s", "--silent", action = "store_true", help = "Do not answer UDP queries for clients trying to find a key server, only serve key database using TCP.")
parser.add_argument("-v", "--verbose", action = "count", default = 0, help = "Increase verbosity. Can be specified multiple times.")
parser.add_argument("filename", metavar = "filename", help = "Database file to load keys from.")

160
pgmopts.c Normal file
View File

@ -0,0 +1,160 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include "pgmopts.h"
#include "argparse_edit.h"
#include "argparse_server.h"
#include "argparse_client.h"
static struct pgmopts_t pgmopts_rw;
const struct pgmopts_t *pgmopts = &pgmopts_rw;
static void show_syntax(const char *errmsg, int argc, char **argv) {
if (errmsg) {
fprintf(stderr, "error: %s\n", errmsg);
fprintf(stderr, "\n");
}
fprintf(stderr, "Available commands:\n");
fprintf(stderr, " %s edit Interactively edit a key database\n", argv[0]);
fprintf(stderr, " %s server Start a key server process\n", argv[0]);
fprintf(stderr, " %s client Unlock LUKS volumes by querying a key server\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "For futher help: %s (command) --help\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "luksrku version " BUILD_REVISION "\n");
}
static bool edit_callback(enum argparse_edit_option_t option, const char *value, argparse_edit_errmsg_callback_t errmsg_callback) {
switch (option) {
case ARG_EDIT_FILENAME:
pgmopts_rw.edit.filename = value;
break;
case ARG_EDIT_VERBOSE:
pgmopts_rw.edit.verbosity++;
break;
}
return true;
}
static bool server_callback(enum argparse_server_option_t option, const char *value, argparse_server_errmsg_callback_t errmsg_callback) {
switch (option) {
case ARG_SERVER_FILENAME:
pgmopts_rw.server.filename = value;
break;
case ARG_SERVER_PORT:
pgmopts_rw.server.port = atoi(value);
break;
case ARG_SERVER_SILENT:
pgmopts_rw.server.answer_udp_queries = false;
break;
case ARG_SERVER_VERBOSE:
pgmopts_rw.server.verbosity++;
break;
}
return true;
}
static bool client_callback(enum argparse_client_option_t option, const char *value, argparse_client_errmsg_callback_t errmsg_callback) {
switch (option) {
case ARG_CLIENT_FILENAME:
pgmopts_rw.client.filename = value;
break;
case ARG_CLIENT_HOSTNAME:
pgmopts_rw.client.hostname = value;
break;
case ARG_CLIENT_PORT:
pgmopts_rw.client.port = atoi(value);
break;
case ARG_CLIENT_TIMEOUT:
pgmopts_rw.client.timeout_seconds = atoi(value);
break;
case ARG_CLIENT_NO_LUKS:
pgmopts_rw.client.no_luks = true;
break;
case ARG_CLIENT_VERBOSE:
pgmopts_rw.client.verbosity++;
break;
}
return true;
}
static void parse_pgmopts_edit(int argc, char **argv) {
pgmopts_rw.edit = (struct pgmopts_edit_t){
.verbosity = ARGPARSE_EDIT_DEFAULT_VERBOSE,
};
argparse_edit_parse_or_quit(argc - 1, argv + 1, edit_callback, NULL);
}
static void parse_pgmopts_server(int argc, char **argv) {
pgmopts_rw.server = (struct pgmopts_server_t){
.port = ARGPARSE_SERVER_DEFAULT_PORT,
.verbosity = ARGPARSE_SERVER_DEFAULT_VERBOSE,
.answer_udp_queries = true,
};
argparse_server_parse_or_quit(argc - 1, argv + 1, server_callback, NULL);
}
static void parse_pgmopts_client(int argc, char **argv) {
pgmopts_rw.client = (struct pgmopts_client_t){
.timeout_seconds = ARGPARSE_CLIENT_DEFAULT_TIMEOUT,
.port = ARGPARSE_SERVER_DEFAULT_PORT,
.verbosity = ARGPARSE_SERVER_DEFAULT_VERBOSE,
};
argparse_client_parse_or_quit(argc - 1, argv + 1, client_callback, NULL);
}
void parse_pgmopts_or_quit(int argc, char **argv) {
if (argc < 2) {
show_syntax("no command supplied", argc, argv);
exit(EXIT_FAILURE);
}
const char *command = argv[1];
if (!strcasecmp(command, "edit")) {
pgmopts_rw.pgm = PGM_EDIT;
parse_pgmopts_edit(argc, argv);
} else if (!strcasecmp(command, "server")) {
pgmopts_rw.pgm = PGM_SERVER;
parse_pgmopts_server(argc, argv);
} else if (!strcasecmp(command, "client")) {
pgmopts_rw.pgm = PGM_CLIENT;
parse_pgmopts_client(argc, argv);
} else {
show_syntax("unsupported command supplied", argc, argv);
exit(EXIT_FAILURE);
}
}

71
pgmopts.h Normal file
View File

@ -0,0 +1,71 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#ifndef __PGMOPTS_H__
#define __PGMOPTS_H__
#include <stdbool.h>
enum pgmopts_pgm_t {
PGM_EDIT,
PGM_SERVER,
PGM_CLIENT,
};
struct pgmopts_edit_t {
const char *filename;
unsigned int verbosity;
};
struct pgmopts_server_t {
const char *filename;
unsigned int port;
bool answer_udp_queries;
unsigned int verbosity;
};
struct pgmopts_client_t {
const char *filename;
const char *hostname;
unsigned int port;
unsigned int timeout_seconds;
bool no_luks;
unsigned int verbosity;
};
struct pgmopts_t {
enum pgmopts_pgm_t pgm;
union {
struct pgmopts_edit_t edit;
struct pgmopts_server_t server;
struct pgmopts_client_t client;
};
};
extern const struct pgmopts_t *pgmopts;
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
void parse_pgmopts_or_quit(int argc, char **argv);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

444
server.c
View File

@ -30,6 +30,7 @@
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <signal.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
@ -37,265 +38,292 @@
#include "log.h"
#include "openssl.h"
#include "global.h"
#include "keyfile.h"
#include "msg.h"
#include "util.h"
#include "cmdline.h"
#include "server.h"
#include "luks.h"
#include "pgmopts.h"
#include "uuid.h"
#include "thread.h"
#include "keydb.h"
#include "signals.h"
#include "udp.h"
#include "blacklist.h"
#include "vaulted_keydb.h"
static const struct keyentry_t *server_key;
struct keyserver_t {
keydb_t* keydb;
struct vaulted_keydb_t *vaulted_keydb;
struct generic_tls_ctx_t gctx;
const struct pgmopts_server_t *opts;
int tcp_sd, udp_sd;
};
static unsigned int psk_server_callback(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len) {
if (max_psk_len < PSK_SIZE_BYTES) {
log_msg(LLVL_FATAL, "Server error: max_psk_len too small.");
return 0;
}
if (strcmp(identity, CLIENT_PSK_IDENTITY)) {
log_msg(LLVL_FATAL, "Server error: client identity '%s' unexpected (expected '%s').", identity, CLIENT_PSK_IDENTITY);
return 0;
}
memcpy(psk, server_key->psk, PSK_SIZE_BYTES);
return PSK_SIZE_BYTES;
}
struct client_thread_ctx_t {
struct generic_tls_ctx_t *gctx;
const keydb_t *keydb;
struct vaulted_keydb_t *vaulted_keydb;
const host_entry_t *host;
int fd;
};
static int create_tcp_socket(int port) {
int s;
struct sockaddr_in addr;
struct udp_listen_thread_ctx_t {
const keydb_t *keydb;
int udp_sd;
unsigned int port;
};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
static int create_tcp_server_socket(int port) {
int sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0) {
log_libc(LLVL_ERROR, "Unable to create TCP socket(2)");
return -1;
}
{
int value = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
}
if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr.s_addr = htonl(INADDR_ANY),
};
if (bind(sd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
log_libc(LLVL_ERROR, "Unable to bind(2) socket");
return -1;
}
if (listen(s, 1) < 0) {
if (listen(sd, 1) < 0) {
log_libc(LLVL_ERROR, "Unable to listen(2) on socket");
return -1;
}
return s;
}
/* Wait for the socket to become acceptable or time out after given number of
* milliseconds. Return true if acceptable socket is present or false if
* timeout occured. */
static bool socket_wait_acceptable(int sd, int timeout_millis) {
struct timeval tv;
memset(&tv, 0, sizeof(tv));
tv.tv_usec = timeout_millis * 1000;
fd_set fds;
FD_ZERO(&fds);
FD_SET(sd, &fds);
int result = select(sd + 1, &fds, NULL, NULL, &tv);
return result != 0;
}
static int create_udp_socket(void) {
int sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
log_libc(LLVL_ERROR, "Unable to create UDP server socket(2)");
return -1;
}
{
int value = 1;
if (setsockopt(sd, SOL_SOCKET, SO_BROADCAST, &value, sizeof(value))) {
log_libc(LLVL_ERROR, "Unable to set UDP socket in broadcast mode using setsockopt(2)");
close(sd);
return -1;
}
}
return sd;
}
static bool send_udp_broadcast_message(int sd, int port, const void *data, int length) {
struct sockaddr_in destination;
memset(&destination, 0, sizeof(struct sockaddr_in));
destination.sin_family = AF_INET;
destination.sin_port = htons(port);
destination.sin_addr.s_addr = htonl(INADDR_BROADCAST);
static int psk_server_callback(SSL *ssl, const unsigned char *identity, size_t identity_len, SSL_SESSION **sessptr) {
struct client_thread_ctx_t *ctx = (struct client_thread_ctx_t*)SSL_get_app_data(ssl);
if (sendto(sd, data, length, 0, (struct sockaddr *)&destination, sizeof(struct sockaddr_in)) < 0) {
log_libc(LLVL_ERROR, "Unable to sendto(2)");
return false;
if (identity_len != ASCII_UUID_CHARACTER_COUNT) {
log_msg(LLVL_WARNING, "Received client identity of length %ld, cannot be a UUID.", identity_len);
return 0;
}
return true;
char uuid_str[ASCII_UUID_BUFSIZE];
memcpy(uuid_str, identity, ASCII_UUID_CHARACTER_COUNT);
uuid_str[ASCII_UUID_CHARACTER_COUNT] = 0;
if (!is_valid_uuid(uuid_str)) {
log_msg(LLVL_WARNING, "Received client identity of length %ld, but not a valid UUID.", identity_len);
return 0;
}
uint8_t uuid[16];
if (!parse_uuid(uuid, uuid_str)) {
log_msg(LLVL_ERROR, "Failed to parse valid UUID.");
return 0;
}
ctx->host = keydb_get_host_by_uuid(ctx->keydb, uuid);
if (!ctx->host) {
log_msg(LLVL_WARNING, "Client connected with client UUID %s, but not present in key database.", uuid_str);
return 0;
}
uint8_t psk[PSK_SIZE_BYTES];
if (!vaulted_keydb_get_tls_psk(ctx->vaulted_keydb, psk, ctx->host)) {
log_msg(LLVL_WARNING, "Cannot establish server connection without TLS-PSK.");
return 0;
}
int result = openssl_tls13_psk_establish_session(ssl, psk, PSK_SIZE_BYTES, EVP_sha256(), sessptr);
OPENSSL_cleanse(psk, PSK_SIZE_BYTES);
return result;
}
static bool announce_waiting_message(int sd, int port, const struct keyentry_t *key) {
struct announcement_t msg;
const uint8_t magic[16] = CLIENT_ANNOUNCE_MAGIC;
memset(&msg, 0, sizeof(msg));
memcpy(msg.magic, magic, 16);
memcpy(msg.host_uuid, key->host_uuid, 16);
return send_udp_broadcast_message(sd, port, &msg, sizeof(msg));
static void copy_luks_passphrase_callback(void *vctx, unsigned int volume_index, const void *source) {
struct msg_t *msgs = (struct msg_t*)vctx;
memcpy(msgs[volume_index].luks_passphrase_raw, source, LUKS_PASSPHRASE_RAW_SIZE_BYTES);
}
static bool unlock_disk(const struct diskentry_t *disk, const uint8_t *passphrase, int passphrase_length) {
char ascii_uuid[40];
sprintf_uuid(ascii_uuid, disk->disk_uuid);
log_msg(LLVL_INFO, "Trying to unlock disk %s with UUID %s", disk->devmapper_name, ascii_uuid);
#ifdef DEBUG
fprintf(stderr, "Using %d bytes key for unlocking: ", passphrase_length);
dump_hex(stderr, passphrase, passphrase_length);
fprintf(stderr, "\n");
#endif
if (is_luks_device_opened(disk->devmapper_name)) {
log_msg(LLVL_INFO, "Disk %s already unlocked, nothing to do.", disk->devmapper_name, ascii_uuid);
return true;
}
return open_luks_device_pw(disk->disk_uuid, disk->devmapper_name, passphrase, passphrase_length);
}
static void client_handler_thread(void *vctx) {
struct client_thread_ctx_t *client = (struct client_thread_ctx_t*)vctx;
static bool all_disks_unlocked(const struct keyentry_t *keyentry) {
for (int i = 0; i < MAX_DISKS_PER_HOST; i++) {
if (keyentry->disk_keys[i].occupied && !is_luks_device_opened(keyentry->disk_keys[i].devmapper_name)) {
return false;
}
}
return true;
}
SSL *ssl = SSL_new(client->gctx->ctx);
if (ssl) {
SSL_set_fd(ssl, client->fd);
SSL_set_app_data(ssl, client);
bool dtls_server(const struct keyentry_t *key, const struct options_t *options) {
if (all_disks_unlocked(key)) {
log_msg(LLVL_INFO, "Starting of server not necessary, all disks already unlocked.");
return true;
}
if (SSL_accept(ssl) <= 0) {
log_openssl(LLVL_WARNING, "Could not establish TLS connection to connecting client.");
ERR_print_errors_fp(stderr);
} else {
if (client->host) {
log_msg(LLVL_DEBUG, "Client \"%s\" connected, sending unlock data for %d volumes.", client->host->host_name, client->host->volume_count);
/* Initially prepare all messages we're about to send to the
* client by filling the UUID fields */
struct msg_t msgs[client->host->volume_count];
for (unsigned int i = 0; i < client->host->volume_count; i++) {
const volume_entry_t *volume = &client->host->volumes[i];
memcpy(msgs[i].volume_uuid, volume->volume_uuid, 16);
}
struct generic_tls_ctx_t gctx;
create_generic_tls_context(&gctx, true);
/* Then also fill the keys */
vaulted_keydb_get_volume_luks_passphases_raw(client->vaulted_keydb, copy_luks_passphrase_callback, msgs, client->host);
server_key = key;
{
char ascii_host_uuid[40];
sprintf_uuid(ascii_host_uuid, key->host_uuid);
SSL_CTX_use_psk_identity_hint(gctx.ctx, ascii_host_uuid);
}
SSL_CTX_set_psk_server_callback(gctx.ctx, psk_server_callback);
int tcp_sock = create_tcp_socket(options->port);
if (tcp_sock == -1) {
log_msg(LLVL_ERROR, "Cannot start server without server socket.");
free_generic_tls_context(&gctx);
return false;
}
int udp_sock = create_udp_socket();
if (tcp_sock == -1) {
log_msg(LLVL_ERROR, "Cannot broadcast without announcement UDP socket.");
close(tcp_sock);
free_generic_tls_context(&gctx);
return false;
}
log_msg(LLVL_DEBUG, "Created listening socket on port %d", options->port);
int tries = 0;
int failed_broadcast_cnt = 0;
while ((options->unlock_cnt == 0) || (tries < options->unlock_cnt)) {
struct sockaddr_in addr;
unsigned int len = sizeof(addr);
log_msg(LLVL_DEBUG, "Waiting for incoming connection...");
if (!announce_waiting_message(udp_sock, options->port, key)) {
failed_broadcast_cnt++;
if ((options->max_broadcast_errs != 0) && (failed_broadcast_cnt >= options->max_broadcast_errs)) {
log_msg(LLVL_ERROR, "Too many broadcast errors, aborting. Network unavailable?");
break;
int txlen = SSL_write(ssl, &msgs, sizeof(msgs));
OPENSSL_cleanse(&msgs, sizeof(msgs));
if (txlen != (long)sizeof(msgs)) {
log_msg(LLVL_WARNING, "Tried to send message of %ld bytes, but sent %d. Severing connection to client.", sizeof(msgs), txlen);
}
} else {
log_msg(LLVL_FATAL, "Client connected, but no host set.");
}
}
if (!socket_wait_acceptable(tcp_sock, WAITING_MESSAGE_BROADCAST_INTERVAL_MILLISECONDS)) {
/* No connection pending, timeout. */
} else {
log_openssl(LLVL_FATAL, "Cannot establish SSL context for connecting client");
}
SSL_free(ssl);
shutdown(client->fd, SHUT_RDWR);
close(client->fd);
}
static void udp_handler_thread(void *vctx) {
struct udp_listen_thread_ctx_t *client = (struct udp_listen_thread_ctx_t*)vctx;
while (true) {
struct udp_query_t rx_msg;
struct sockaddr_in origin;
if (!wait_udp_query(client->udp_sd, &rx_msg, &origin)) {
continue;
}
log_msg(LLVL_DEBUG, "Trying to accept connection...");
int client = accept(tcp_sock, (struct sockaddr*)&addr, &len);
if (client < 0) {
log_libc(LLVL_ERROR, "Unable to accept(2)");
close(udp_sock);
close(tcp_sock);
free_generic_tls_context(&gctx);
return false;
log_msg(LLVL_TRACE, "Recevied UDP query message from %d.%d.%d.%d:%d", PRINTF_FORMAT_IP(&origin), ntohs(origin.sin_port));
/* Ensure that we only reply to this host once every minute */
const uint32_t ipv4 = origin.sin_addr.s_addr;
if (is_ip_blacklisted(ipv4)) {
continue;
}
blacklist_ip(ipv4, BLACKLIST_TIMEOUT_SERVER);
/* Check if we have this host in our database */
if (keydb_get_host_by_uuid(client->keydb, rx_msg.host_uuid)) {
/* Yes, it is. Notify the client who's asking that we have their key. */
struct udp_response_t tx_msg;
memcpy(tx_msg.magic, UDP_MESSAGE_MAGIC, UDP_MESSAGE_MAGIC_SIZE);
send_udp_message(client->udp_sd, &origin, &tx_msg, sizeof(tx_msg), true);
}
}
}
bool keyserver_start(const struct pgmopts_server_t *opts) {
bool success = true;
struct keyserver_t keyserver = {
.opts = opts,
.tcp_sd = -1,
.udp_sd = -1,
};
do {
/* We ignore SIGPIPE or the server will die when clients disconnect suddenly */
ignore_signal(SIGPIPE);
/* Load key database first */
keyserver.keydb = keydb_read(opts->filename);
if (!keyserver.keydb) {
log_msg(LLVL_FATAL, "Failed to load key database: %s", opts->filename);
success = false;
break;
}
SSL *ssl = SSL_new(gctx.ctx);
SSL_set_fd(ssl, client);
if (!keyserver.keydb->server_database) {
log_msg(LLVL_FATAL, "Not a server key database: %s", opts->filename);
success = false;
break;
}
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
tries++;
log_msg(LLVL_DEBUG, "Client connected, waiting for data...");
while (true) {
struct msg_t msg;
int rxlen = SSL_read(ssl, &msg, sizeof(msg));
if (rxlen == 0) {
/* Client severed the connection */
break;
}
if (rxlen != sizeof(msg)) {
log_msg(LLVL_ERROR, "Truncated message (%d bytes) received, terminating connection. Expected %d bytes.", rxlen, sizeof(msg));
break;
}
msg_to_hbo(&msg);
if (keyserver.keydb->host_count == 0) {
log_msg(LLVL_FATAL, "No host entries in key database: %s", opts->filename);
success = false;
break;
}
if ((msg.passphrase_length == 0) || (msg.passphrase_length > MAX_PASSPHRASE_LENGTH)) {
log_msg(LLVL_FATAL, "Client sent malformed message indicating illegal passphrase length of %d bytes. Aborting connection.", msg.passphrase_length);
break;
}
/* Then convert it into a vaulted key database */
keyserver.vaulted_keydb = vaulted_keydb_new(keyserver.keydb);
if (!keyserver.vaulted_keydb) {
log_msg(LLVL_FATAL, "Failed to create vaulted key database.");
success = false;
break;
}
/* Now check if this is one of they keys we're actually looking for */
bool found = false;
for (int i = 0; i < MAX_DISKS_PER_HOST; i++) {
if (!memcmp(key->disk_keys[i].disk_uuid, msg.disk_uuid, 16)) {
bool success = unlock_disk(&key->disk_keys[i], msg.passphrase, msg.passphrase_length);
log_msg(LLVL_DEBUG, "Unlocking of disk was %s", success ? "successful" : "unsuccessful");
found = true;
break;
}
}
if (!found) {
char ascii_uuid[40];
sprintf_uuid(ascii_uuid, msg.disk_uuid);
log_msg(LLVL_INFO, "Client sent passphrase for UUID %s; we were not expecting it. Ignored.", ascii_uuid);
}
if (!create_generic_tls_context(&keyserver.gctx, true)) {
log_msg(LLVL_FATAL, "Failed to create OpenSSL server context.");
success = false;
break;
}
SSL_CTX_set_psk_find_session_callback(keyserver.gctx.ctx, psk_server_callback);
keyserver.tcp_sd = create_tcp_server_socket(opts->port);
if (keyserver.tcp_sd == -1) {
log_msg(LLVL_ERROR, "Cannot start server without server socket.");
success = false;
break;
}
if (opts->answer_udp_queries) {
keyserver.udp_sd = create_udp_socket(opts->port, false, 1000);
if (keyserver.udp_sd == -1) {
success = false;
break;
}
struct udp_listen_thread_ctx_t udp_thread_ctx = {
.keydb = keyserver.keydb,
.udp_sd = keyserver.udp_sd,
.port = keyserver.opts->port,
};
if (!pthread_create_detached_thread(udp_handler_thread, &udp_thread_ctx, sizeof(udp_thread_ctx))) {
log_libc(LLVL_FATAL, "Unable to create detached thread for UDP messages.");
success = false;
break;
}
}
SSL_free(ssl);
close(client);
log_msg(LLVL_INFO, "Serving luksrku database for %u hosts.", keyserver.keydb->host_count);
while (true) {
struct sockaddr_in addr;
unsigned int len = sizeof(addr);
int client = accept(keyserver.tcp_sd, (struct sockaddr*)&addr, &len);
if (client < 0) {
log_libc(LLVL_ERROR, "Unable to accept(2)");
success = false;
break;
}
/* Connection closed */
if (all_disks_unlocked(key)) {
log_msg(LLVL_INFO, "All disks successfully unlocked.");
break;
} else {
log_msg(LLVL_DEBUG, "At least one disk remains locked after communication.");
/* Client has connected, fire up client thread. */
struct client_thread_ctx_t client_ctx = {
.gctx = &keyserver.gctx,
.keydb = keyserver.keydb,
.vaulted_keydb = keyserver.vaulted_keydb,
.fd = client,
};
if (!pthread_create_detached_thread(client_handler_thread, &client_ctx, sizeof(client_ctx))) {
log_libc(LLVL_FATAL, "Unable to create detached thread for client.");
success = false;
break;
}
}
} while (false);
if (keyserver.udp_sd != -1) {
close(keyserver.udp_sd);
}
close(udp_sock);
close(tcp_sock);
free_generic_tls_context(&gctx);
return true;
if (keyserver.tcp_sd != -1) {
close(keyserver.tcp_sd);
}
free_generic_tls_context(&keyserver.gctx);
vaulted_keydb_free(keyserver.vaulted_keydb);
keydb_free(keyserver.keydb);
return success;
}

View File

@ -24,11 +24,11 @@
#ifndef __SERVER_H__
#define __SERVER_H__
#include "keyfile.h"
#include "cmdline.h"
#include <stdbool.h>
#include "pgmopts.h"
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
bool dtls_server(const struct keyentry_t *key, const struct options_t *options);
bool keyserver_start(const struct pgmopts_server_t *opts);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

View File

@ -21,15 +21,15 @@
Johannes Bauer <JohannesBauer@gmx.de>
*/
#include <arpa/inet.h>
#include <stddef.h>
#include <signal.h>
#include "signals.h"
#include "msg.h"
void msg_to_nbo(struct msg_t *msg) {
msg->passphrase_length = htonl(msg->passphrase_length);
bool ignore_signal(int signum) {
struct sigaction action = {
.sa_handler = SIG_IGN,
.sa_flags = SA_RESTART,
};
sigemptyset(&action.sa_mask);
return sigaction(signum, &action, NULL) == 0;
}
void msg_to_hbo(struct msg_t *msg) {
msg->passphrase_length = ntohl(msg->passphrase_length);
}

View File

@ -21,14 +21,13 @@
Johannes Bauer <JohannesBauer@gmx.de>
*/
#ifndef __PARSE_KEYFILE_H__
#define __PARSE_KEYFILE_H__
#ifndef __SIGNALS_H__
#define __SIGNALS_H__
#include <stdbool.h>
#include "keyfile.h"
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
bool parse_keyfile(const char *filename, struct keydb_t *keydb, bool server_keyfile);
bool ignore_signal(int signum);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

BIN
testdata/local_client.bin vendored Normal file

Binary file not shown.

BIN
testdata/local_server.bin vendored Normal file

Binary file not shown.

BIN
testdata/random_client.bin vendored Normal file

Binary file not shown.

BIN
testdata/random_server.bin vendored Normal file

Binary file not shown.

75
thread.c Normal file
View File

@ -0,0 +1,75 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <pthread.h>
#include "thread.h"
#include "log.h"
struct pthread_trampoline_data_t {
void (*thread_function)(void *ctx);
uint8_t ctx[];
};
static void* pthread_trampoline(void *vctx) {
struct pthread_trampoline_data_t *tdata = (struct pthread_trampoline_data_t*)vctx;
tdata->thread_function(tdata->ctx);
free(tdata);
return NULL;
}
bool pthread_create_detached_thread(void (*thread_function)(void *ctx), const void *ctx, unsigned int ctx_length) {
struct pthread_trampoline_data_t *tdata = calloc(1, sizeof(struct pthread_trampoline_data_t) + ctx_length);
if (!tdata) {
log_libc(LLVL_FATAL, "Failed to allocate trampoline data using calloc(3)");
return false;
}
tdata->thread_function = thread_function;
memcpy(tdata->ctx, ctx, ctx_length);
pthread_attr_t attrs;
if (pthread_attr_init(&attrs)) {
log_libc(LLVL_FATAL, "Unable to pthread_attr_init(3)");
free(tdata);
return false;
}
if (pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED)) {
log_libc(LLVL_FATAL, "Unable to pthread_attr_setdetachstate(3)");
free(tdata);
return false;
}
pthread_t thread;
if (pthread_create(&thread, &attrs, pthread_trampoline, tdata)) {
log_libc(LLVL_FATAL, "Unable to pthread_create(3) a client thread");
free(tdata);
return false;
}
return true;
}

View File

@ -21,30 +21,13 @@
Johannes Bauer <JohannesBauer@gmx.de>
*/
#ifndef __CMDLINE_H__
#define __CMDLINE_H__
#ifndef __THREAD_H__
#define __THREAD_H__
#include <stdbool.h>
enum mode_t {
UNDEFINED = 0,
SERVER_MODE,
CLIENT_MODE
};
struct options_t {
enum mode_t mode;
int port;
bool verbose;
const char *keydbfile;
int unlock_cnt;
int max_broadcast_errs;
};
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
enum longopts_t;
void print_syntax(const char *pgmname);
bool parse_cmdline_arguments(struct options_t *options, int argc, char **argv);
bool pthread_create_detached_thread(void (*thread_function)(void *ctx), const void *ctx, unsigned int ctx_length);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

125
udp.c Normal file
View File

@ -0,0 +1,125 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#include <stdio.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include "log.h"
#include "udp.h"
int create_udp_socket(unsigned int listen_port, bool send_broadcast, unsigned int rx_timeout_millis) {
int sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
log_libc(LLVL_ERROR, "Unable to create UDP server socket(2)");
return -1;
}
if (send_broadcast) {
int value = 1;
if (setsockopt(sd, SOL_SOCKET, SO_BROADCAST, &value, sizeof(value))) {
log_libc(LLVL_ERROR, "Unable to set UDP socket in broadcast mode using setsockopt(2)");
close(sd);
return -1;
}
}
if (listen_port) {
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(listen_port),
.sin_addr.s_addr = htonl(INADDR_ANY),
};
if (bind(sd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
log_libc(LLVL_ERROR, "Unable to bind UDP socket to listen to port %d", listen_port);
close(sd);
return -1;
}
}
if (rx_timeout_millis) {
struct timeval tv = {
.tv_sec = rx_timeout_millis / 1000,
.tv_usec = (rx_timeout_millis % 1000) * 1000,
};
if (setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
log_libc(LLVL_ERROR, "Unable to set UDP receive timeout to %u ms.", rx_timeout_millis);
close(sd);
return -1;
}
}
return sd;
}
bool wait_udp_message(int sd, void *data, unsigned int length, struct sockaddr_in *source) {
socklen_t socklen = sizeof(struct sockaddr_in);
ssize_t rx_bytes = recvfrom(sd,data, length, 0, (struct sockaddr*)source, &socklen);
return rx_bytes == length;
}
bool send_udp_message(int sd, struct sockaddr_in *destination, const void *data, unsigned int length, bool is_response) {
int flags = is_response ? MSG_CONFIRM : 0;
ssize_t tx_bytes = sendto(sd, data, length, flags, (struct sockaddr*)destination, sizeof(struct sockaddr_in));
if (tx_bytes < 0) {
log_libc(LLVL_ERROR, "Unable to sendto(2)");
return false;
} else if (tx_bytes != length) {
log_libc(LLVL_ERROR, "Unable to sendto(2) the complete message, %d bytes sent, but %u requested.", tx_bytes, length);
return false;
}
return true;
}
bool send_udp_broadcast_message(int sd, int port, const void *data, unsigned int length) {
struct sockaddr_in destination = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr.s_addr = htonl(INADDR_BROADCAST),
};
return send_udp_message(sd, &destination, data, length, false);
}
bool wait_udp_query(int sd, struct udp_query_t *query, struct sockaddr_in *source) {
bool rx_successful = wait_udp_message(sd, query, sizeof(struct udp_query_t), source);
if (rx_successful) {
/* Also check if the message contains the correct magic */
if (!memcmp(query->magic, UDP_MESSAGE_MAGIC, UDP_MESSAGE_MAGIC_SIZE)) {
return true;
}
}
return false;
}
bool wait_udp_response(int sd, struct udp_response_t *response, struct sockaddr_in *source) {
bool rx_successful = wait_udp_message(sd, response, sizeof(struct udp_response_t), source);
if (rx_successful) {
/* Also check if the message contains the correct magic */
if (!memcmp(response->magic, UDP_MESSAGE_MAGIC, UDP_MESSAGE_MAGIC_SIZE)) {
return true;
}
}
return false;
}

39
udp.h Normal file
View File

@ -0,0 +1,39 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#ifndef __UDP_H__
#define __UDP_H__
#include <stdbool.h>
#include "msg.h"
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
int create_udp_socket(unsigned int listen_port, bool send_broadcast, unsigned int rx_timeout_millis);
bool wait_udp_message(int sd, void *data, unsigned int length, struct sockaddr_in *source);
bool send_udp_message(int sd, struct sockaddr_in *destination, const void *data, unsigned int length, bool is_response);
bool send_udp_broadcast_message(int sd, int port, const void *data, unsigned int length);
bool wait_udp_query(int sd, struct udp_query_t *query, struct sockaddr_in *source);
bool wait_udp_response(int sd, struct udp_response_t *response, struct sockaddr_in *source);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

151
util.c
View File

@ -24,26 +24,24 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <openssl/evp.h>
#include "util.h"
#include "log.h"
#include "global.h"
char* query_passphrase(const char *prompt) {
char *passphrase = calloc(1, MAX_PASSPHRASE_LENGTH);
if (!passphrase) {
log_libc(LLVL_ERROR, "malloc(3) of passphrase memory");
return NULL;
bool query_passphrase(const char *prompt, char *passphrase, unsigned int passphrase_maxsize) {
if (passphrase_maxsize == 0) {
return false;
}
if (EVP_read_pw_string(passphrase, MAX_PASSPHRASE_LENGTH - 1, prompt, 0) != 0) {
if (EVP_read_pw_string(passphrase, passphrase_maxsize - 1, prompt, 0) != 0) {
log_openssl(LLVL_ERROR, "EVP_read_pw_string failed");
free(passphrase);
return NULL;
OPENSSL_cleanse(passphrase, passphrase_maxsize);
return false;
}
return passphrase;
return true;
}
void dump_hex_long(FILE *f, const void *vdata, unsigned int length) {
@ -57,13 +55,32 @@ void dump_hex_long(FILE *f, const void *vdata, unsigned int length) {
}
}
void dump_hex(FILE *f, const void *vdata, unsigned int length) {
void sprintf_hex(char *dest, const uint8_t *data, unsigned int length) {
for (unsigned int i = 0; i < length; i++) {
sprintf(dest + (2 * i), "%02x", data[i]);
}
}
void dump_hex(FILE *f, const void *vdata, unsigned int length, bool use_ascii) {
const uint8_t *data = (const uint8_t*)vdata;
for (unsigned int i = 0; i < length; i++) {
fprintf(f, "%02x", data[i]);
uint8_t character = data[i];
if (use_ascii && (character > 32) && (character < 127)) {
fprintf(f, "%c ", character);
} else {
fprintf(f, "%02x ", character);
}
}
}
void dump_hexline(FILE *f, const char *prefix, const void *vdata, unsigned int length, bool use_ascii) {
if (prefix) {
fprintf(f, "%s", prefix);
}
dump_hex(f, vdata, length, use_ascii);
fprintf(f, "\n");
}
bool is_hex(const char *str, int length) {
for (int i = 0; i < length; i++) {
if (((str[i] >= '0') && (str[i] <= '9')) ||
@ -113,49 +130,93 @@ int parse_hexstr(const char *hexstr, uint8_t *data, int maxlen) {
return length;
}
bool is_valid_uuid(const char *ascii_uuid) {
// e43fff25-5a01-40e8-b437-80b9d56c19ff
// '-' at offsets 8 13 18 23
if (!ascii_uuid) {
bool truncate_crlf(char *string) {
int length = strlen(string);
bool truncated = false;
if (length && (string[length - 1] == '\n')) {
truncated = true;
string[--length] = 0;
}
if (length && (string[length - 1] == '\r')) {
truncated = true;
string[--length] = 0;
}
return truncated;
}
bool buffer_randomize(uint8_t *buffer, unsigned int length) {
FILE *f = fopen("/dev/urandom", "r");
if (!f) {
log_libc(LLVL_FATAL, "Failed to access /dev/urandom");
return false;
}
if (strlen(ascii_uuid) != 36) {
return false;
}
if ((ascii_uuid[8] != '-') || (ascii_uuid[13] != '-') || (ascii_uuid[18] != '-') || (ascii_uuid[23] != '-')) {
return false;
}
if (!is_hex(ascii_uuid + 0, 8) || !is_hex(ascii_uuid + 9, 4) || !is_hex(ascii_uuid + 14, 4) || !is_hex(ascii_uuid + 19, 4) || !is_hex(ascii_uuid + 24, 12)) {
if (fread(buffer, length, 1, f) != 1) {
log_libc(LLVL_FATAL, "Error reading randomness from /dev/urandom");
fclose(f);
return false;
}
fclose(f);
return true;
}
bool parse_uuid(uint8_t *uuid, const char *ascii_uuid) {
if (!is_valid_uuid(ascii_uuid)) {
return false;
}
parse_hexstr(ascii_uuid + 0, uuid + 0, 4);
parse_hexstr(ascii_uuid + 9, uuid + 4, 2);
parse_hexstr(ascii_uuid + 14, uuid + 6, 2);
parse_hexstr(ascii_uuid + 19, uuid + 8, 2);
parse_hexstr(ascii_uuid + 24, uuid + 10, 6);
return true;
}
void sprintf_uuid(char *buffer, const uint8_t *uuid) {
buffer[0] = 0;
for (int i = 0; i < 16; i++) {
if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) {
buffer += sprintf(buffer, "-");
bool is_zero(const void *data, unsigned int length) {
const uint8_t *bytedata = (const uint8_t*)data;
for (unsigned int i = 0; i < length; i++) {
if (bytedata[i]) {
return false;
}
buffer += sprintf(buffer, "%02x", uuid[i]);
}
return true;
}
bool array_remove(void *base, unsigned int element_size, unsigned int element_count, unsigned int remove_element_index) {
if (remove_element_index >= element_count) {
return false;
}
uint8_t *bytebase = (uint8_t*)base;
const unsigned int destination_offset = remove_element_index * element_size;
const unsigned int source_offset = (remove_element_index + 1) * element_size;
const unsigned int copy_length = ((element_count - 1) - remove_element_index) * element_size;
if (copy_length) {
memcpy(bytebase + destination_offset, bytebase + source_offset, copy_length);
}
/* Then, wipe the last element */
const unsigned int last_element_offset = element_size * (element_count - 1);
memset(bytebase + last_element_offset, 0, element_size);
return true;
}
static uint8_t get_array_value(const uint8_t *array, unsigned int array_length, unsigned int array_index) {
if (array_index < array_length) {
return array[array_index];
} else {
return 0;
}
}
void dump_uuid(FILE *f, const uint8_t *uuid) {
char ascii_uuid[40];
sprintf_uuid(ascii_uuid, uuid);
fprintf(f, "%s", ascii_uuid);
bool ascii_encode(char *dest, unsigned int dest_buffer_size, const uint8_t *source_data, unsigned int source_data_length) {
const unsigned int require_dest_size = ((source_data_length + 2) / 3) * 4 + 1;
if (dest_buffer_size < require_dest_size) {
log_msg(LLVL_FATAL, "Encoding of %d bytes takes a %d byte buffer, but only %d bytes provided.", source_data_length, require_dest_size, dest_buffer_size);
return false;
}
const char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for (unsigned int i = 0; i < source_data_length; i += 3) {
uint32_t word = ((get_array_value(source_data, source_data_length, i + 0) << 16) | (get_array_value(source_data, source_data_length, i + 1) << 8) | (get_array_value(source_data, source_data_length, i + 2) << 0));
for (int shift = 18; shift >= 0; shift -= 6) {
*dest++ = alphabet[(word >> shift) & 0x3f];
}
}
*dest = 0;
return true;
}
double now(void) {
struct timeval tv;
if (gettimeofday(&tv, NULL)) {
return 0;
}
return tv.tv_sec + (tv.tv_usec * 1e-6);
}

18
util.h
View File

@ -28,18 +28,22 @@
#include <stdint.h>
#include <stdbool.h>
#define PRINTF_FORMAT_IP(saddrptr) (saddrptr->sin_addr.s_addr >> 0) & 0xff, (saddrptr->sin_addr.s_addr >> 8) & 0xff, (saddrptr->sin_addr.s_addr >> 16) & 0xff, (saddrptr->sin_addr.s_addr >> 24) & 0xff
#define PRINTF_FORMAT_IP(saddrptr) ((saddrptr)->sin_addr.s_addr >> 0) & 0xff, ((saddrptr)->sin_addr.s_addr >> 8) & 0xff, ((saddrptr)->sin_addr.s_addr >> 16) & 0xff, ((saddrptr)->sin_addr.s_addr >> 24) & 0xff
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
char* query_passphrase(const char *prompt);
bool query_passphrase(const char *prompt, char *passphrase, unsigned int passphrase_maxsize);
void dump_hex_long(FILE *f, const void *vdata, unsigned int length);
void dump_hex(FILE *f, const void *vdata, unsigned int length);
void sprintf_hex(char *dest, const uint8_t *data, unsigned int length);
void dump_hex(FILE *f, const void *vdata, unsigned int length, bool use_ascii);
void dump_hexline(FILE *f, const char *prefix, const void *vdata, unsigned int length, bool use_ascii);
bool is_hex(const char *str, int length);
int parse_hexstr(const char *hexstr, uint8_t *data, int maxlen);
bool is_valid_uuid(const char *ascii_uuid);
bool parse_uuid(uint8_t *uuid, const char *ascii_uuid);
void sprintf_uuid(char *buffer, const uint8_t *uuid);
void dump_uuid(FILE *f, const uint8_t *uuid);
bool truncate_crlf(char *string);
bool buffer_randomize(uint8_t *buffer, unsigned int length);
bool is_zero(const void *data, unsigned int length);
bool array_remove(void *base, unsigned int element_size, unsigned int element_count, unsigned int remove_element_index);
bool ascii_encode(char *dest, unsigned int dest_buffer_size, const uint8_t *source_data, unsigned int source_data_length);
double now(void);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

84
uuid.c Normal file
View File

@ -0,0 +1,84 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "uuid.h"
#include "util.h"
bool is_valid_uuid(const char *ascii_uuid) {
// e43fff25-5a01-40e8-b437-80b9d56c19ff
// '-' at offsets 8 13 18 23
if (!ascii_uuid) {
return false;
}
if (strlen(ascii_uuid) != 36) {
return false;
}
if ((ascii_uuid[8] != '-') || (ascii_uuid[13] != '-') || (ascii_uuid[18] != '-') || (ascii_uuid[23] != '-')) {
return false;
}
if (!is_hex(ascii_uuid + 0, 8) || !is_hex(ascii_uuid + 9, 4) || !is_hex(ascii_uuid + 14, 4) || !is_hex(ascii_uuid + 19, 4) || !is_hex(ascii_uuid + 24, 12)) {
return false;
}
return true;
}
bool parse_uuid(uint8_t *uuid, const char *ascii_uuid) {
if (!is_valid_uuid(ascii_uuid)) {
return false;
}
parse_hexstr(ascii_uuid + 0, uuid + 0, 4);
parse_hexstr(ascii_uuid + 9, uuid + 4, 2);
parse_hexstr(ascii_uuid + 14, uuid + 6, 2);
parse_hexstr(ascii_uuid + 19, uuid + 8, 2);
parse_hexstr(ascii_uuid + 24, uuid + 10, 6);
return true;
}
void sprintf_uuid(char *buffer, const uint8_t *uuid) {
buffer[0] = 0;
for (int i = 0; i < 16; i++) {
if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) {
buffer += sprintf(buffer, "-");
}
buffer += sprintf(buffer, "%02x", uuid[i]);
}
}
void dump_uuid(FILE *f, const uint8_t *uuid) {
char ascii_uuid[40];
sprintf_uuid(ascii_uuid, uuid);
fprintf(f, "%s", ascii_uuid);
}
bool uuid_randomize(uint8_t uuid[static 16]) {
if (!buffer_randomize(uuid, 16)) {
return false;
}
uuid[6] = (uuid[6] & (~0xf0)) | 0x40;
uuid[8] = (uuid[8] & (~0xc0)) | 0x80;
return true;
}

View File

@ -1,6 +1,6 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2016 Johannes Bauer
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
@ -21,34 +21,23 @@
Johannes Bauer <JohannesBauer@gmx.de>
*/
#ifndef __BINKEYFILE_H__
#define __BINKEYFILE_H__
#ifndef __UUID_H__
#define __UUID_H__
#include <stdint.h>
#include <stdbool.h>
#define BINKEYFILE_SALT_SIZE 16
#define BINKEYFILE_KEY_SIZE 32
#define BINKEYFILE_AUTH_TAG_SIZE 16
#define BINKEYFILE_IV_SIZE 16
#define ASCII_UUID_CHARACTER_COUNT 36
struct key_t {
const char *passphrase;
uint8_t salt[BINKEYFILE_SALT_SIZE];
uint8_t key[BINKEYFILE_KEY_SIZE];
};
struct binkeyfile_t {
bool empty_passphrase;
uint8_t salt[BINKEYFILE_SALT_SIZE];
uint8_t iv[BINKEYFILE_IV_SIZE];
uint8_t auth_tag[BINKEYFILE_AUTH_TAG_SIZE];
uint8_t ciphertext[];
};
/* Already includes zero termination */
#define ASCII_UUID_BUFSIZE (ASCII_UUID_CHARACTER_COUNT + 1)
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
bool read_binary_keyfile(const char *filename, struct keydb_t *keydb);
bool write_binary_keyfile(const char *filename, const struct keydb_t *keydb, const char *passphrase);
bool is_valid_uuid(const char *ascii_uuid);
bool parse_uuid(uint8_t *uuid, const char *ascii_uuid);
void sprintf_uuid(char *buffer, const uint8_t *uuid);
void dump_uuid(FILE *f, const uint8_t *uuid);
bool uuid_randomize(uint8_t uuid[static 16]);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif

142
vault.c
View File

@ -28,23 +28,25 @@
#include <openssl/crypto.h>
#include <openssl/rand.h>
#include <openssl/evp.h>
#include <pthread.h>
#include "vault.h"
#include "util.h"
#include "log.h"
static bool vault_derive_key(const struct vault_t *vault, uint8_t key[static 32]) {
static bool vault_derive_key(const struct vault_t *vault, uint8_t dkey[static 32]) {
/* Derive the AES key from it */
if (PKCS5_PBKDF2_HMAC((char*)vault->key, vault->key_length, NULL, 0, vault->iteration_cnt, EVP_sha256(), 32, key) != 1) {
if (PKCS5_PBKDF2_HMAC((char*)vault->source_key, vault->source_key_length, NULL, 0, vault->iteration_cnt, EVP_sha256(), 32, dkey) != 1) {
return false;
}
return true;
}
static double now(void) {
struct timeval tv;
if (gettimeofday(&tv, NULL) == 0) {
return tv.tv_sec + (1e-6 * tv.tv_usec);
} else {
return 0;
static bool vault_rekey(struct vault_t *vault) {
/* Generate a new source key */
if (RAND_bytes(vault->source_key, vault->source_key_length) != 1) {
return false;
}
return vault_derive_key(vault, vault->dkey);
}
static double vault_measure_key_derivation_time(struct vault_t *vault, unsigned int new_iteration_count) {
@ -73,7 +75,7 @@ static void vault_calibrate_derivation_time(struct vault_t *vault, double target
}
}
struct vault_t* vault_init(unsigned int data_length, double target_derivation_time) {
struct vault_t* vault_init(unsigned int data_length, double target_decryption_time) {
struct vault_t *vault;
vault = calloc(1, sizeof(struct vault_t));
@ -81,9 +83,14 @@ struct vault_t* vault_init(unsigned int data_length, double target_derivation_ti
return NULL;
}
vault->key = malloc(DEFAULT_KEY_LENGTH_BYTES);
vault->key_length = DEFAULT_KEY_LENGTH_BYTES;
if (!vault->key) {
if (pthread_mutex_init(&vault->mutex, NULL)) {
log_libc(LLVL_FATAL, "Unable to initialize vault mutex.");
free(vault);
return NULL;
}
vault->source_key_length = DEFAULT_SOURCE_KEY_LENGTH_BYTES;
vault->source_key = malloc(vault->source_key_length);
if (!vault->source_key) {
vault_free(vault);
return NULL;
}
@ -93,9 +100,20 @@ struct vault_t* vault_init(unsigned int data_length, double target_derivation_ti
vault_free(vault);
return NULL;
}
vault->is_open = true;
vault->reference_count = 1;
vault->data_length = data_length;
vault_calibrate_derivation_time(vault, target_derivation_time);
/* Decryption takes *two* derivations, one for the current key (to decrypt)
* and another in advance after re-keying, therefore we halve the time
* here. */
vault_calibrate_derivation_time(vault, target_decryption_time / 2);
/* Initially gernerate a full key and derive the dkey already (vault is
* open at this point) */
if (!vault_rekey(vault)) {
vault_free(vault);
return NULL;
}
return vault;
}
@ -104,18 +122,24 @@ static void vault_destroy_content(struct vault_t *vault) {
if (vault->data) {
OPENSSL_cleanse(vault->data, vault->data_length);
}
if (vault->key) {
OPENSSL_cleanse(vault->key, vault->key_length);
if (vault->source_key) {
OPENSSL_cleanse(vault->source_key, vault->source_key_length);
}
}
bool vault_open(struct vault_t *vault) {
if (vault->is_open) {
return true;
}
static bool vault_decrypt(struct vault_t *vault) {
/* At this point we only have the source key, not the dkey yet. Derive the
* dkey into a local piece of memory first */
uint8_t dkey[32];
if (!vault_derive_key(vault, dkey)) {
OPENSSL_cleanse(dkey, sizeof(dkey));
return false;
}
/* Then rekey the vault for the upcoming closing. Do this while the vault
* is still encrypted to minimize window of opportunity. */
if (!vault_rekey(vault)) {
OPENSSL_cleanse(dkey, sizeof(dkey));
return false;
}
@ -152,42 +176,37 @@ bool vault_open(struct vault_t *vault) {
break;
}
if (EVP_DecryptFinal_ex(ctx, vault->data + len, &len) != 1) {
if (EVP_DecryptFinal_ex(ctx, (uint8_t*)vault->data + len, &len) != 1) {
success = false;
break;
}
} while (false);
if (success) {
vault->is_open = true;
OPENSSL_cleanse(vault->key, vault->key_length);
OPENSSL_cleanse(vault->auth_tag, 16);
} else {
if (!success) {
/* Vault may be in an inconsistent state. Destroy contents. */
vault_destroy_content(vault);
}
EVP_CIPHER_CTX_free(ctx);
OPENSSL_cleanse(dkey, sizeof(dkey));
OPENSSL_cleanse(vault->auth_tag, 16);
EVP_CIPHER_CTX_free(ctx);
return success;
}
bool vault_close(struct vault_t *vault) {
if (!vault->is_open) {
return true;
}
/* Generate a new key source */
if (RAND_bytes(vault->key, vault->key_length) != 1) {
return false;
}
uint8_t key[32];
if (!vault_derive_key(vault, key)) {
return false;
bool vault_open(struct vault_t *vault) {
bool success = true;
pthread_mutex_lock(&vault->mutex);
vault->reference_count++;
if (vault->reference_count == 1) {
/* Vault was closed, we need to decrypt it. */
success = vault_decrypt(vault);
}
pthread_mutex_unlock(&vault->mutex);
return success;
}
static bool vault_encrypt(struct vault_t *vault) {
/* We already have a dkey in the structure, so we can quickly encrypt */
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
return false;
@ -209,7 +228,7 @@ bool vault_close(struct vault_t *vault) {
break;
}
if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, (unsigned char*)&vault->iv) != 1) {
if (EVP_EncryptInit_ex(ctx, NULL, NULL, vault->dkey, (unsigned char*)&vault->iv) != 1) {
success = false;
break;
}
@ -220,7 +239,7 @@ bool vault_close(struct vault_t *vault) {
break;
}
if (EVP_EncryptFinal_ex(ctx, vault->data + len, &len) != 1) {
if (EVP_EncryptFinal_ex(ctx, (uint8_t*)vault->data + len, &len) != 1) {
success = false;
break;
}
@ -231,26 +250,44 @@ bool vault_close(struct vault_t *vault) {
}
} while (false);
if (success) {
vault->is_open = false;
} else {
/* The data is encrypted, erase the dkey, but keep the source key (so we
* can decrypt later) */
OPENSSL_cleanse(vault->dkey, sizeof(vault->dkey));
if (!success) {
/* Vault may be in an inconsistent state. Destroy contents. */
vault_destroy_content(vault);
}
EVP_CIPHER_CTX_free(ctx);
OPENSSL_cleanse(key, sizeof(key));
return success;
}
bool vault_close(struct vault_t *vault) {
bool success = true;
pthread_mutex_lock(&vault->mutex);
vault->reference_count--;
if (vault->reference_count == 0) {
/* Vault is now closed, we need to encrypt it. */
success = vault_encrypt(vault);
}
pthread_mutex_unlock(&vault->mutex);
return success;
}
void vault_free(struct vault_t *vault) {
if (!vault) {
return;
}
pthread_mutex_destroy(&vault->mutex);
vault_destroy_content(vault);
free(vault->data);
free(vault->key);
free(vault->source_key);
free(vault);
}
#ifndef __TEST_VAULT__
#ifdef __TEST_VAULT__
static void dump(const uint8_t *data, unsigned int length) {
for (unsigned int i = 0; i < length; i++) {
@ -260,9 +297,9 @@ static void dump(const uint8_t *data, unsigned int length) {
}
int main(void) {
/* gcc -Wall -std=c11 -Wmissing-prototypes -Wstrict-prototypes -Werror=implicit-function-declaration -Wimplicit-fallthrough -Wshadow -pie -fPIE -fsanitize=address -fsanitize=undefined -fsanitize=leak -o vault vault.c -lasan -lubsan -lcrypto
/* gcc -D__TEST_VAULT__ -Wall -std=c11 -Wmissing-prototypes -Wstrict-prototypes -Werror=implicit-function-declaration -Wimplicit-fallthrough -Wshadow -pie -fPIE -fsanitize=address -fsanitize=undefined -fsanitize=leak -pthread -o vault vault.c util.c log.c -lcrypto
*/
struct vault_t *vault = vault_init(64, 0.1);
struct vault_t *vault = vault_init(64, 1);
dump(vault->data, vault->data_length);
for (int i = 0; i < 10; i++) {
if (!vault_close(vault)) {
@ -270,6 +307,7 @@ int main(void) {
abort();
}
dump(vault->data, vault->data_length);
if (!vault_open(vault)) {
fprintf(stderr, "vault open failed.\n");
abort();

11
vault.h
View File

@ -26,19 +26,22 @@
#include <stdbool.h>
#include <stdint.h>
#include <pthread.h>
struct vault_t {
bool is_open;
pthread_mutex_t mutex;
unsigned int reference_count;
void *data;
unsigned int data_length;
uint8_t *key;
unsigned int key_length;
uint8_t *source_key;
unsigned int source_key_length;
uint8_t auth_tag[16];
uint8_t dkey[32];
uint64_t iv;
unsigned int iteration_cnt;
};
#define DEFAULT_KEY_LENGTH_BYTES (1024 * 1024)
#define DEFAULT_SOURCE_KEY_LENGTH_BYTES (1024 * 1024)
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
struct vault_t* vault_init(unsigned int data_length, double target_derivation_time);

165
vaulted_keydb.c Normal file
View File

@ -0,0 +1,165 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#include <stdlib.h>
#include <string.h>
#include <openssl/crypto.h>
#include "vaulted_keydb.h"
#include "log.h"
static struct tls_psk_vault_entry_t *vaulted_keydb_get_tls_psk_for_hostindex(struct vaulted_keydb_t *vkeydb, unsigned int host_index) {
return ((struct tls_psk_vault_entry_t*)vkeydb->tls_psk_vault->data) + host_index;
}
static struct luks_passphrase_vault_entry_t *vaulted_keydb_get_luks_passphrase_for_hostindex(struct vaulted_keydb_t *vkeydb, unsigned int host_index) {
return ((struct luks_passphrase_vault_entry_t*)vkeydb->luks_passphrase_vault->data) + host_index;
}
static void move_data_into_vault(struct vaulted_keydb_t *dest, keydb_t *src) {
for (unsigned int i = 0; i < src->host_count; i++) {
host_entry_t *host = &src->hosts[i];
/* Copy over TLS-PSK and remove original */
struct tls_psk_vault_entry_t *dest_tls_psk = vaulted_keydb_get_tls_psk_for_hostindex(dest, i);
memcpy(&dest_tls_psk->tls_psk, host->tls_psk, PSK_SIZE_BYTES);
OPENSSL_cleanse(host->tls_psk, PSK_SIZE_BYTES);
/* Copy over all LUKS keys and remove originals */
struct luks_passphrase_vault_entry_t *dest_luks_passphrase = vaulted_keydb_get_luks_passphrase_for_hostindex(dest, i);
for (unsigned int j = 0; j < host->volume_count; j++) {
volume_entry_t *volume = &host->volumes[j];
memcpy(&dest_luks_passphrase->volumes[j].luks_passphrase_raw, volume->luks_passphrase_raw, LUKS_PASSPHRASE_RAW_SIZE_BYTES);
OPENSSL_cleanse(volume->luks_passphrase_raw, LUKS_PASSPHRASE_RAW_SIZE_BYTES);
}
}
}
bool vaulted_keydb_get_tls_psk(struct vaulted_keydb_t *vaulted_keydb, uint8_t dest[PSK_SIZE_BYTES], const host_entry_t *host) {
int host_index = keydb_get_host_index(vaulted_keydb->keydb, host);
if (host_index < 0) {
log_msg(LLVL_FATAL, "Unable to retrieve host index for vaulted key db entry.");
return false;
}
/* Get a pointer into the vaulted structure */
struct tls_psk_vault_entry_t *entry = vaulted_keydb_get_tls_psk_for_hostindex(vaulted_keydb, host_index);
/* Then decrypt vault */
if (!vault_open(vaulted_keydb->tls_psk_vault)) {
log_msg(LLVL_FATAL, "Unable to open TLS-PSK vault of vaulted key db entry.");
return false;
}
/* Copy out the data we need */
memcpy(dest, &entry->tls_psk, PSK_SIZE_BYTES);
/* And close it back up */
if (!vault_close(vaulted_keydb->tls_psk_vault)) {
OPENSSL_cleanse(dest, PSK_SIZE_BYTES);
log_msg(LLVL_FATAL, "Unable to close TLS-PSK vault of vaulted key db entry.");
return false;
}
return true;
}
bool vaulted_keydb_get_volume_luks_passphases_raw(struct vaulted_keydb_t *vaulted_keydb, void (*copy_callback)(void *vctx, unsigned int volume_index, const void *source), void *copy_ctx, const host_entry_t *host) {
int host_index = keydb_get_host_index(vaulted_keydb->keydb, host);
if (host_index < 0) {
log_msg(LLVL_FATAL, "Unable to retrieve host index for vaulted key db entry.");
return false;
}
/* Get a pointer into the vaulted structure */
struct luks_passphrase_vault_entry_t *entry = vaulted_keydb_get_luks_passphrase_for_hostindex(vaulted_keydb, host_index);
/* Then decrypt vault */
if (!vault_open(vaulted_keydb->luks_passphrase_vault)) {
log_msg(LLVL_FATAL, "Unable to open LUKS passphrase vault of vaulted key db entry.");
return false;
}
/* Copy out the data we need by calling back for all volumes */
for (unsigned int i = 0; i < host->volume_count; i++) {
copy_callback(copy_ctx, i, &entry->volumes[i].luks_passphrase_raw);
}
/* And close it back up */
if (!vault_close(vaulted_keydb->luks_passphrase_vault)) {
log_msg(LLVL_FATAL, "Unable to close LUKS passphrase vault of vaulted key db entry.");
return false;
}
return true;
}
struct vaulted_keydb_t *vaulted_keydb_new(keydb_t *keydb) {
struct vaulted_keydb_t *vaulted_keydb = calloc(1, sizeof(struct vaulted_keydb_t));
if (!vaulted_keydb) {
log_msg(LLVL_FATAL, "Unable to calloc(3) vaulted keydb");
return NULL;
}
vaulted_keydb->keydb = keydb;
vaulted_keydb->tls_psk_vault = vault_init(sizeof(struct tls_psk_vault_entry_t) * keydb->host_count, 0.025);
if (!vaulted_keydb->tls_psk_vault) {
log_msg(LLVL_FATAL, "Unable to create TLS-PSK vault");
vaulted_keydb_free(vaulted_keydb);
return NULL;
}
vaulted_keydb->luks_passphrase_vault = vault_init(sizeof(struct luks_passphrase_vault_entry_t) * keydb->host_count, 0.025);
if (!vaulted_keydb->luks_passphrase_vault) {
log_msg(LLVL_FATAL, "Unable to create LUKS passphrase vault");
vaulted_keydb_free(vaulted_keydb);
return NULL;
}
/* Now move data from the original keydb into the vaulted keydb (erase
* original keys) */
move_data_into_vault(vaulted_keydb, keydb);
/* Finally, close the vaults */
if (!vault_close(vaulted_keydb->tls_psk_vault)) {
log_msg(LLVL_FATAL, "Failed to close TLS-PSK vault");
vaulted_keydb_free(vaulted_keydb);
return NULL;
}
if (!vault_close(vaulted_keydb->luks_passphrase_vault)) {
log_msg(LLVL_FATAL, "Failed to close LUKS passhrase vault");
vaulted_keydb_free(vaulted_keydb);
return NULL;
}
return vaulted_keydb;
}
void vaulted_keydb_free(struct vaulted_keydb_t *vaulted_keydb) {
if (!vaulted_keydb) {
return;
}
vault_free(vaulted_keydb->luks_passphrase_vault);
vault_free(vaulted_keydb->tls_psk_vault);
free(vaulted_keydb);
}

53
vaulted_keydb.h Normal file
View File

@ -0,0 +1,53 @@
/*
luksrku - Tool to remotely unlock LUKS disks using TLS.
Copyright (C) 2016-2019 Johannes Bauer
This file is part of luksrku.
luksrku is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; this program is ONLY licensed under
version 3 of the License, later versions are explicitly excluded.
luksrku is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with luksrku; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Johannes Bauer <JohannesBauer@gmx.de>
*/
#ifndef __VAULTED_KEYDB_H__
#define __VAULTED_KEYDB_H__
#include "keydb.h"
#include "vault.h"
struct tls_psk_vault_entry_t {
uint8_t tls_psk[PSK_SIZE_BYTES];
};
struct luks_passphrase_vault_entry_t {
struct {
uint8_t luks_passphrase_raw[LUKS_PASSPHRASE_RAW_SIZE_BYTES];
} volumes[MAX_VOLUMES_PER_HOST];
};
struct vaulted_keydb_t {
keydb_t *keydb;
struct vault_t *tls_psk_vault;
struct vault_t *luks_passphrase_vault;
};
/*************** AUTO GENERATED SECTION FOLLOWS ***************/
bool vaulted_keydb_get_tls_psk(struct vaulted_keydb_t *vaulted_keydb, uint8_t dest[PSK_SIZE_BYTES], const host_entry_t *host);
bool vaulted_keydb_get_volume_luks_passphases_raw(struct vaulted_keydb_t *vaulted_keydb, void (*copy_callback)(void *ctx, unsigned int volume_index, const void *source), void *copy_ctx, const host_entry_t *host);
struct vaulted_keydb_t *vaulted_keydb_new(keydb_t *keydb);
void vaulted_keydb_free(struct vaulted_keydb_t *vaulted_keydb);
/*************** AUTO GENERATED SECTION ENDS ***************/
#endif