Compare commits
70 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
204f85c93f | ||
![]() |
78a8cd70c2 | ||
![]() |
46b2cdb184 | ||
![]() |
a6db05b4d0 | ||
![]() |
a764cc22e2 | ||
![]() |
323db6d08d | ||
![]() |
37b239b179 | ||
![]() |
b0fc16bfc7 | ||
![]() |
cd38193993 | ||
![]() |
bd5caae1ee | ||
![]() |
af29d9cbf8 | ||
![]() |
b0909557ad | ||
![]() |
bd929be9fa | ||
![]() |
47f7ca6c31 | ||
![]() |
265dd0582a | ||
![]() |
8681e49561 | ||
![]() |
1a765b6369 | ||
![]() |
e2bb69144d | ||
![]() |
912b874f7a | ||
![]() |
e0444c493e | ||
![]() |
f2e21ebdde | ||
![]() |
5400e3716a | ||
![]() |
9dc8164dcc | ||
![]() |
f01ec97d6b | ||
![]() |
dce9c1b323 | ||
![]() |
40a0871e03 | ||
![]() |
0bf0759c9c | ||
![]() |
54063ec025 | ||
![]() |
6ac94dbd83 | ||
![]() |
17d1b9a52d | ||
![]() |
1469d83a96 | ||
![]() |
78104a8b87 | ||
![]() |
ba46e5bb43 | ||
![]() |
ab670a431a | ||
![]() |
3478fa4555 | ||
![]() |
849e3a5949 | ||
![]() |
05e112065e | ||
![]() |
8c7c0e5870 | ||
![]() |
2f36b56417 | ||
![]() |
60b1b2bf39 | ||
![]() |
39ced77b98 | ||
![]() |
25649e0caa | ||
![]() |
4ee2739bac | ||
![]() |
2a4f2a8e3b | ||
![]() |
36f9988fce | ||
![]() |
6b5ed8f62c | ||
![]() |
1f56e19361 | ||
![]() |
0e8e42d0ea | ||
![]() |
983217ffbd | ||
![]() |
425e2dcd66 | ||
![]() |
9ea0a9695c | ||
![]() |
2143adc91f | ||
![]() |
8200c9668d | ||
![]() |
c89ff552d4 | ||
![]() |
603e63876f | ||
![]() |
3e5c7d541c | ||
![]() |
d70bd1f672 | ||
![]() |
969eae12c7 | ||
![]() |
667ff55af1 | ||
![]() |
ecbf3827ca | ||
![]() |
20ffe38b53 | ||
![]() |
722476e7fd | ||
![]() |
0cb0e5d470 | ||
![]() |
bcd794a6c1 | ||
![]() |
ffca14559f | ||
![]() |
68c74de050 | ||
![]() |
9c888cbe4e | ||
![]() |
b79ae0b417 | ||
![]() |
1790275960 | ||
![]() |
73ab437fc9 |
9
.gitignore
vendored
9
.gitignore
vendored
@ -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__
|
||||
|
@ -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
|
||||
|
74
Makefile
74
Makefile
@ -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
319
README.md
@ -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
213
argparse_client.c
Normal 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
41
argparse_client.h
Normal 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
159
argparse_edit.c
Normal 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
35
argparse_edit.h
Normal 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
181
argparse_server.c
Normal 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
38
argparse_server.h
Normal 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
|
21
blacklist.c
21
blacklist.c
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
474
client.c
@ -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;
|
||||
}
|
||||
|
6
client.h
6
client.h
@ -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
194
cmdline.c
@ -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);
|
||||
}
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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
696
editor.c
Normal 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;
|
||||
}
|
@ -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
64
exec.c
@ -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
13
exec.h
@ -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
|
||||
|
@ -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
69
file_encryption.h
Normal 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
|
46
gen_config
46
gen_config
@ -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))
|
39
global.h
39
global.h
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
362
keydb.c
Normal 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
121
keydb.h
Normal 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
107
keyfile.c
@ -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));
|
||||
}
|
61
keyfile.h
61
keyfile.h
@ -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
|
18
licensify
18
licensify
@ -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
12
log.c
@ -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
7
log.h
@ -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
106
luks.c
@ -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
3
luks.h
@ -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
|
||||
|
@ -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;
|
||||
}
|
93
luksrku.c
93
luksrku.c
@ -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
26
msg.h
@ -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
|
||||
|
77
openssl.c
77
openssl.c
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
190
parse-keyfile.c
190
parse-keyfile.c
@ -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
8
parsers/parser_client.py
Executable 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
4
parsers/parser_edit.py
Executable 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
6
parsers/parser_server.py
Executable 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
160
pgmopts.c
Normal 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
71
pgmopts.h
Normal 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
444
server.c
@ -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;
|
||||
}
|
||||
|
||||
|
6
server.h
6
server.h
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
BIN
testdata/local_client.bin
vendored
Normal file
Binary file not shown.
BIN
testdata/local_server.bin
vendored
Normal file
BIN
testdata/local_server.bin
vendored
Normal file
Binary file not shown.
BIN
testdata/random_client.bin
vendored
Normal file
BIN
testdata/random_client.bin
vendored
Normal file
Binary file not shown.
BIN
testdata/random_server.bin
vendored
Normal file
BIN
testdata/random_server.bin
vendored
Normal file
Binary file not shown.
75
thread.c
Normal file
75
thread.c
Normal 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;
|
||||
}
|
@ -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
125
udp.c
Normal 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
39
udp.h
Normal 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
151
util.c
@ -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
18
util.h
@ -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
84
uuid.c
Normal 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;
|
||||
}
|
@ -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
142
vault.c
@ -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
11
vault.h
@ -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
165
vaulted_keydb.c
Normal 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
53
vaulted_keydb.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user